-
Notifications
You must be signed in to change notification settings - Fork 486
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
otelcolconvert: support converting loki exporter #6505
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package otelcolconvert | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
"strings" | ||
|
||
"github.com/grafana/agent/component/common/config" | ||
"github.com/grafana/agent/component/loki/write" | ||
"github.com/grafana/agent/component/otelcol/exporter/loki" | ||
"github.com/grafana/agent/converter/diag" | ||
"github.com/grafana/agent/converter/internal/common" | ||
"github.com/grafana/river/rivertypes" | ||
pconfig "github.com/prometheus/common/config" | ||
"go.opentelemetry.io/collector/component" | ||
"go.opentelemetry.io/collector/config/configtls" | ||
|
||
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/lokiexporter" | ||
) | ||
|
||
func init() { | ||
converters = append(converters, lokiExporterConverter{}) | ||
} | ||
|
||
type lokiExporterConverter struct{} | ||
|
||
func (lokiExporterConverter) Factory() component.Factory { | ||
return lokiexporter.NewFactory() | ||
} | ||
|
||
func (lokiExporterConverter) InputComponentName() string { return "otelcol.exporter.loki" } | ||
|
||
func (lokiExporterConverter) ConvertAndAppend(state *state, id component.InstanceID, cfg component.Config) diag.Diagnostics { | ||
var diags diag.Diagnostics | ||
|
||
label := state.FlowComponentLabel() | ||
|
||
lokiWriteComponentID := []componentID{{ | ||
Name: strings.Split("loki.write", "."), | ||
Label: label, | ||
}} | ||
|
||
args1 := toOtelcolExporterLoki(lokiWriteComponentID) | ||
block1 := common.NewBlockWithOverride([]string{"otelcol", "exporter", "loki"}, label, args1) | ||
|
||
diags.Add( | ||
diag.SeverityLevelInfo, | ||
fmt.Sprintf("Converted %s into %s", stringifyInstanceID(id), stringifyBlock(block1)), | ||
) | ||
|
||
args2, err := toLokiWrite(label, cfg.(*lokiexporter.Config)) | ||
if err != nil { | ||
diags.Add( | ||
diag.SeverityLevelError, | ||
fmt.Sprintf("could not build loki.write block: %s", err), | ||
) | ||
} | ||
block2 := common.NewBlockWithOverride([]string{"loki", "write"}, label, args2) | ||
Comment on lines
+52
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm pretty sure |
||
|
||
diags.Add( | ||
diag.SeverityLevelInfo, | ||
fmt.Sprintf("Converted %s into %s", stringifyInstanceID(id), stringifyBlock(block2)), | ||
) | ||
diags.Add( | ||
diag.SeverityLevelInfo, | ||
"Created a loki.write block with a best-effort conversion of the lokiexporter's confighttp, retry and queue configuration settings. You may want to double check the converted configuration as most fields do not have a 1:1 match", | ||
) | ||
|
||
state.Body().AppendBlock(block1) | ||
state.Body().AppendBlock(block2) | ||
return diags | ||
} | ||
|
||
func toOtelcolExporterLoki(ids []componentID) *loki.Arguments { | ||
return &loki.Arguments{ | ||
ForwardTo: toTokenizedLogsReceivers(ids), | ||
} | ||
} | ||
|
||
func toLokiWrite(name string, cfg *lokiexporter.Config) (*write.Arguments, error) { | ||
// Defaults for MaxStreams and WAL should be handled on the Flow side. | ||
res := &write.Arguments{} | ||
|
||
if cfg.Endpoint != "" { | ||
// TODO(@tpaschalis) Wire in auth from auth extension. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Just a note) 😬 Handling auth here will be tricky, because we can't pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if there's a great way to do this yet. |
||
e := write.GetDefaultEndpointOptions() | ||
e.Name = name | ||
e.URL = cfg.Endpoint | ||
|
||
e.RemoteTimeout = cfg.Timeout | ||
e.TenantID = string(cfg.Headers["X-Scope-OrgID"]) | ||
if !reflect.DeepEqual(cfg.TLSSetting, configtls.TLSClientSetting{}) { | ||
minv, ok := pconfig.TLSVersions[cfg.TLSSetting.MinVersion] | ||
if !ok { | ||
return nil, fmt.Errorf("invalid min tls version provided: %s", cfg.TLSSetting.MinVersion) | ||
} | ||
e.HTTPClientConfig.TLSConfig.CA = string(cfg.TLSSetting.CAPem) | ||
e.HTTPClientConfig.TLSConfig.CAFile = cfg.TLSSetting.CAFile | ||
e.HTTPClientConfig.TLSConfig.Cert = string(cfg.TLSSetting.CertPem) | ||
e.HTTPClientConfig.TLSConfig.CertFile = cfg.TLSSetting.CertFile | ||
e.HTTPClientConfig.TLSConfig.Key = rivertypes.Secret(cfg.TLSSetting.KeyPem) | ||
e.HTTPClientConfig.TLSConfig.KeyFile = cfg.TLSSetting.KeyFile | ||
e.HTTPClientConfig.TLSConfig.ServerName = cfg.TLSSetting.ServerName | ||
e.HTTPClientConfig.TLSConfig.InsecureSkipVerify = cfg.TLSSetting.InsecureSkipVerify | ||
e.HTTPClientConfig.TLSConfig.MinVersion = config.TLSVersion(minv) | ||
} | ||
|
||
e.MaxBackoff = cfg.RetrySettings.MaxInterval | ||
e.MinBackoff = cfg.RetrySettings.InitialInterval | ||
|
||
headers := toHeadersMap(cfg.Headers) | ||
if len(headers) > 0 { | ||
e.Headers = headers | ||
} | ||
tenant, ok := headers["X-Scope-OrgID"] | ||
if ok { | ||
e.TenantID = tenant | ||
} | ||
|
||
// After trying to translate all the OTel HTTP Client options onto the | ||
// loki.write component, append it as an endpoint. | ||
res.Endpoints = append(res.Endpoints, e) | ||
} | ||
|
||
return res, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
otelcol.receiver.otlp "default" { | ||
grpc { } | ||
|
||
http { } | ||
|
||
output { | ||
metrics = [otelcol.exporter.otlp.default.input] | ||
logs = [otelcol.exporter.loki.default.input] | ||
traces = [otelcol.exporter.otlp.default.input] | ||
} | ||
} | ||
|
||
otelcol.exporter.otlp "default" { | ||
client { | ||
endpoint = "database:4317" | ||
} | ||
} | ||
|
||
otelcol.exporter.loki "default" { | ||
forward_to = [loki.write.default.receiver] | ||
} | ||
|
||
loki.write "default" { | ||
endpoint { | ||
name = "default" | ||
url = "https://loki.example.com:3100/loki/api/v1/push" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
receivers: | ||
otlp: | ||
protocols: | ||
grpc: | ||
http: | ||
|
||
exporters: | ||
loki: | ||
endpoint: https://loki.example.com:3100/loki/api/v1/push | ||
# Our loki.write component uses some different defaults | ||
timeout: "10s" | ||
retry_on_failure: | ||
initial_interval: "500ms" | ||
max_interval: "5m" | ||
|
||
otlp: | ||
# Our defaults have drifted from upstream, so we explicitly set our | ||
# defaults below (balancer_name and queue_size). | ||
endpoint: database:4317 | ||
balancer_name: pick_first | ||
sending_queue: | ||
queue_size: 5000 | ||
|
||
service: | ||
pipelines: | ||
traces: | ||
receivers: [otlp] | ||
processors: [] | ||
exporters: [otlp] | ||
metrics: | ||
receivers: [otlp] | ||
processors: [] | ||
exporters: [otlp] | ||
logs: | ||
receivers: [otlp] | ||
processors: [] | ||
exporters: [loki] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
naming nit: can we give thiese more descriptive names than args1/block1/args2/block2 please? 😛