diff --git a/CHANGELOG.md b/CHANGELOG.md index e1278c8e7aa3..272b0fbdfda2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ internal API changes are not present. Main (unreleased) ----------------- +### Features + +- A new `otelcol.exporter.debug` component for printing OTel telemetry from + other `otelcol` components to the console. (@BarunKGP) + v0.41.1 (2024-06-07) -------------------- diff --git a/docs/sources/flow/reference/compatibility/_index.md b/docs/sources/flow/reference/compatibility/_index.md index 61775bcf26b5..97d113cdb10f 100644 --- a/docs/sources/flow/reference/compatibility/_index.md +++ b/docs/sources/flow/reference/compatibility/_index.md @@ -287,6 +287,7 @@ The following components, grouped by namespace, _export_ OpenTelemetry `otelcol. - [otelcol.connector.servicegraph](../components/otelcol.connector.servicegraph) - [otelcol.connector.spanlogs](../components/otelcol.connector.spanlogs) - [otelcol.connector.spanmetrics](../components/otelcol.connector.spanmetrics) +- [otelcol.exporter.debug](../components/otelcol.exporter.debug) - [otelcol.exporter.loadbalancing](../components/otelcol.exporter.loadbalancing) - [otelcol.exporter.logging](../components/otelcol.exporter.logging) - [otelcol.exporter.loki](../components/otelcol.exporter.loki) diff --git a/docs/sources/flow/reference/components/otelcol.exporter.debug.md b/docs/sources/flow/reference/components/otelcol.exporter.debug.md new file mode 100644 index 000000000000..a3006d9d9bf2 --- /dev/null +++ b/docs/sources/flow/reference/components/otelcol.exporter.debug.md @@ -0,0 +1,102 @@ +--- +aliases: +- /docs/grafana-cloud/agent/flow/reference/components/otelcol.exporter.debug/ +- /docs/grafana-cloud/monitor-infrastructure/agent/flow/reference/components/otelcol.exporter.debug/ +- /docs/grafana-cloud/monitor-infrastructure/integrations/agent/flow/reference/components/otelcol.exporter.debug/ +- /docs/grafana-cloud/send-data/agent/flow/reference/components/otelcol.exporter.debug/ +canonical: https://grafana.com/docs/agent/latest/flow/reference/components/otelcol.exporter.debug/ +description: Learn about otelcol.exporter.debug +labels: + stage: experimental +title: otelcol.exporter.debug +--- + +# otelcol.exporter.debug + +`otelcol.exporter.debug` accepts telemetry data from other `otelcol` components and writes them to the console (stderr). +You can control the verbosity of the logs. + +{{< admonition type="note" >}} +`otelcol.exporter.debug` is a wrapper over the upstream OpenTelemetry Collector `debug` exporter. +If necessary, bug reports or feature requests are redirected to the upstream repository. +{{< /admonition >}} + +Multiple `otelcol.exporter.debug` components can be specified by giving them different labels. + +## Usage + +```river +otelcol.exporter.debug "LABEL" { } +``` + +## Arguments + +`otelcol.exporter.debug` supports the following arguments: + +Name | Type | Description | Default | Required +---- | ---- | ----------- | ------- | -------- +`verbosity` | `string` | Verbosity of the generated logs. | `"normal"` | no +`sampling_initial` | `int` | Number of messages initially logged each second. | `2` | no +`sampling_thereafter` | `int` | Sampling rate after the initial messages are logged. | `500` | no + +The `verbosity` argument must be one of `"basic"`, `"normal"`, or `"detailed"`. + +## Exported fields + +The following fields are exported and can be referenced by other components: + +Name | Type | Description +---- | ---- | ----------- +`input` | `otelcol.Consumer` | A value that other components can use to send telemetry data to. + +`input` accepts `otelcol.Consumer` data for any telemetry signal (metrics, +logs, or traces). + +## Component health + +`otelcol.exporter.debug` is only reported as unhealthy if given an invalid +configuration. + +## Debug information + +`otelcol.exporter.debug` does not expose any component-specific debug +information. + +## Example + +This example scrapes Prometheus UNIX metrics and writes them to the console: + +```river +prometheus.exporter.unix "default" { } + +prometheus.scrape "default" { + targets = prometheus.exporter.unix.default.targets + forward_to = [otelcol.receiver.prometheus.default.receiver] +} + +otelcol.receiver.prometheus "default" { + output { + metrics = [otelcol.exporter.debug.default.input] + } +} + +otelcol.exporter.debug "default" { + verbosity = "detailed" + sampling_initial = 1 + sampling_thereafter = 1 +} +``` + + +## Compatible components + +`otelcol.exporter.debug` has exports that can be consumed by the following components: + +- Components that consume [OpenTelemetry `otelcol.Consumer`](../../compatibility/#opentelemetry-otelcolconsumer-consumers) + +{{< admonition type="note" >}} +Connecting some components may not be sensible or components may require further configuration to make the connection work correctly. +Refer to the linked documentation for more details. +{{< /admonition >}} + + \ No newline at end of file diff --git a/go.mod b/go.mod index da9ba840cc99..aa4750d6fb0c 100644 --- a/go.mod +++ b/go.mod @@ -615,6 +615,7 @@ require ( go.opentelemetry.io/collector/confmap/converter/expandconverter v0.96.0 go.opentelemetry.io/collector/confmap/provider/fileprovider v0.96.0 go.opentelemetry.io/collector/confmap/provider/yamlprovider v0.96.0 + go.opentelemetry.io/collector/exporter/debugexporter v0.96.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.24.0 golang.org/x/crypto/x509roots/fallback v0.0.0-20240208163226-62c9f1799c91 k8s.io/apimachinery v0.29.2 diff --git a/go.sum b/go.sum index ea0d93d3a82c..16547bbd5f45 100644 --- a/go.sum +++ b/go.sum @@ -2348,6 +2348,8 @@ go.opentelemetry.io/collector/consumer v0.96.0 h1:JN4JHelp5EGMGoC2UVelTMG6hyZjgt go.opentelemetry.io/collector/consumer v0.96.0/go.mod h1:Vn+qzzKgekDFayCVV8peSH5Btx1xrt/bmzD9gTxgidQ= go.opentelemetry.io/collector/exporter v0.96.0 h1:SmOSaP+zUNq0nl+BcllsCSsYePdUNIIUfW5sXKKaUlI= go.opentelemetry.io/collector/exporter v0.96.0/go.mod h1:DcuGaxcINhOV2LgojDI56r3830cUtuCsNadINMIU23c= +go.opentelemetry.io/collector/exporter/debugexporter v0.96.0 h1:88v2GWCIuYgd3e4KdwF0JLklIgBzETBw0e3dkEJ7BbI= +go.opentelemetry.io/collector/exporter/debugexporter v0.96.0/go.mod h1:mZjJ0G6Pn6aSS7T4UeEjXSHt3pgslvaZa/4Uam8DKuo= go.opentelemetry.io/collector/exporter/loggingexporter v0.96.0 h1:fKHt4iTcD7C0utDzeww6ZYVlDYaC0dw9wtzVwLha4CM= go.opentelemetry.io/collector/exporter/loggingexporter v0.96.0/go.mod h1:vpuKdiIQ6yjwZbKiiAs/MV8rZMKiQfPF55vX8UxO8fk= go.opentelemetry.io/collector/exporter/otlpexporter v0.96.0 h1:vZEd10B/zj7WkBWSVegDkGOwv7FZhUwyk60E2zkYwL4= diff --git a/internal/component/all/all.go b/internal/component/all/all.go index f6911eb95b30..75156137b027 100644 --- a/internal/component/all/all.go +++ b/internal/component/all/all.go @@ -70,6 +70,7 @@ import ( _ "github.com/grafana/agent/internal/component/otelcol/connector/servicegraph" // Import otelcol.connector.servicegraph _ "github.com/grafana/agent/internal/component/otelcol/connector/spanlogs" // Import otelcol.connector.spanlogs _ "github.com/grafana/agent/internal/component/otelcol/connector/spanmetrics" // Import otelcol.connector.spanmetrics + _ "github.com/grafana/agent/internal/component/otelcol/exporter/debug" // Import otelcol.exporter.debug _ "github.com/grafana/agent/internal/component/otelcol/exporter/loadbalancing" // Import otelcol.exporter.loadbalancing _ "github.com/grafana/agent/internal/component/otelcol/exporter/logging" // Import otelcol.exporter.logging _ "github.com/grafana/agent/internal/component/otelcol/exporter/loki" // Import otelcol.exporter.loki diff --git a/internal/component/otelcol/exporter/debug/debug.go b/internal/component/otelcol/exporter/debug/debug.go new file mode 100644 index 000000000000..66d0ad40b9ca --- /dev/null +++ b/internal/component/otelcol/exporter/debug/debug.go @@ -0,0 +1,96 @@ +package debug + +import ( + "fmt" + + "github.com/grafana/agent/internal/component" + "github.com/grafana/agent/internal/component/otelcol" + "github.com/grafana/agent/internal/component/otelcol/exporter" + "github.com/grafana/agent/internal/featuregate" + otelcomponent "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configtelemetry" + debugexporter "go.opentelemetry.io/collector/exporter/debugexporter" + otelextension "go.opentelemetry.io/collector/extension" +) + +func init() { + component.Register(component.Registration{ + Name: "otelcol.exporter.debug", + Args: Arguments{}, + Exports: otelcol.ConsumerExports{}, + Stability: featuregate.StabilityExperimental, + + Build: func(opts component.Options, args component.Arguments) (component.Component, error) { + fact := debugexporter.NewFactory() + return exporter.New(opts, fact, args.(Arguments), exporter.TypeAll) + }, + }) +} + +type Arguments struct { + Verbosity string `river:"verbosity,attr,optional"` + SamplingInitial int `river:"sampling_initial,attr,optional"` + SamplingThereafter int `river:"sampling_thereafter,attr,optional"` +} + +func (args Arguments) convertVerbosity() (configtelemetry.Level, error) { + var verbosity configtelemetry.Level + switch args.Verbosity { + case "basic": + verbosity = configtelemetry.LevelBasic + case "normal": + verbosity = configtelemetry.LevelNormal + case "detailed": + verbosity = configtelemetry.LevelDetailed + default: + // Invalid verbosity + // debugexporter only supports basic, normal and detailed levels + return verbosity, fmt.Errorf("invalid verbosity %q", args.Verbosity) + } + + return verbosity, nil +} + +var _ exporter.Arguments = Arguments{} + +// DefaultArguments holds default values for Arguments. +var DefaultArguments = Arguments{ + Verbosity: "normal", + SamplingInitial: 2, + SamplingThereafter: 500, +} + +// SetToDefault implements river.Defaulter. +func (args *Arguments) SetToDefault() { + *args = DefaultArguments +} + +// Convert implements exporter.Arguments. +func (args Arguments) Convert() (otelcomponent.Config, error) { + verbosity, err := args.convertVerbosity() + if err != nil { + return nil, fmt.Errorf("error in conversion to config arguments, %v", err) + } + + return &debugexporter.Config{ + Verbosity: verbosity, + SamplingInitial: args.SamplingInitial, + SamplingThereafter: args.SamplingThereafter, + }, nil +} + +// Extensions implements exporter.Arguments. +func (args Arguments) Extensions() map[otelcomponent.ID]otelextension.Extension { + return nil +} + +// Exporters implements exporter.Arguments. +func (args Arguments) Exporters() map[otelcomponent.DataType]map[otelcomponent.ID]otelcomponent.Component { + return nil +} + +// DebugMetricsConfig implements receiver.Arguments. +func (args Arguments) DebugMetricsConfig() otelcol.DebugMetricsArguments { + var debugMetrics otelcol.DebugMetricsArguments + return debugMetrics +} diff --git a/internal/component/otelcol/exporter/debug/debug_test.go b/internal/component/otelcol/exporter/debug/debug_test.go new file mode 100644 index 000000000000..85d180bf9a51 --- /dev/null +++ b/internal/component/otelcol/exporter/debug/debug_test.go @@ -0,0 +1,79 @@ +package debug_test + +import ( + "fmt" + "testing" + + "github.com/grafana/agent/internal/component/otelcol/exporter/debug" + "github.com/grafana/river" + "github.com/stretchr/testify/require" + otelcomponent "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configtelemetry" + debugexporter "go.opentelemetry.io/collector/exporter/debugexporter" +) + +func Test(t *testing.T) { + tests := []struct { + testName string + args string + expectedReturn debugexporter.Config + errorMsg string + }{ + { + testName: "defaultConfig", + args: ``, + expectedReturn: debugexporter.Config{ + Verbosity: configtelemetry.LevelNormal, + SamplingInitial: 2, + SamplingThereafter: 500, + }, + }, + + { + testName: "validConfig", + args: ` + verbosity = "detailed" + sampling_initial = 5 + sampling_thereafter = 20 + `, + expectedReturn: debugexporter.Config{ + Verbosity: configtelemetry.LevelDetailed, + SamplingInitial: 5, + SamplingThereafter: 20, + }, + }, + + { + testName: "invalidConfig", + args: ` + verbosity = "test" + sampling_initial = 5 + sampling_thereafter = 20 + `, + errorMsg: "error in conversion to config arguments", + }, + } + + for _, tc := range tests { + t.Run(tc.testName, func(t *testing.T) { + var args debug.Arguments + err := river.Unmarshal([]byte(tc.args), &args) + require.NoError(t, err) + + actualPtr, err := args.Convert() + if tc.errorMsg != "" { + require.ErrorContains(t, err, tc.errorMsg) + return + } + + require.NoError(t, err) + + actual := actualPtr.(*debugexporter.Config) + fmt.Printf("Passed conversion") + + require.NoError(t, otelcomponent.ValidateConfig(actual)) + + require.Equal(t, tc.expectedReturn, *actual) + }) + } +}