diff --git a/.chloggen/debugexporter-output-paths.yaml b/.chloggen/debugexporter-output-paths.yaml new file mode 100644 index 00000000000..5909455fc15 --- /dev/null +++ b/.chloggen/debugexporter-output-paths.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. receiver/otlp) +component: exporter/debug + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add `output_paths` configuration option to allow redirecting output when `use_internal_logger` is false + +# One or more tracking issues or pull requests related to the change +issues: [10472] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: | + The `output_paths` option accepts an array of destinations (stdout, stderr, or file paths) and defaults to stdout. + This option can only be used when `use_internal_logger` is set to false. + +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [user] diff --git a/exporter/debugexporter/README.md b/exporter/debugexporter/README.md index 9b6297c2341..6e6b78c61fc 100644 --- a/exporter/debugexporter/README.md +++ b/exporter/debugexporter/README.md @@ -38,6 +38,7 @@ The following settings are optional: Refer to [Zap docs](https://godoc.org/go.uber.org/zap/zapcore#NewSampler) for more details on how sampling parameters impact number of messages. - `use_internal_logger` (default = `true`): uses the collector's internal logger for output. See [below](#using-the-collectors-internal-logger) for description. +- `output_paths` (default = `[stdout]` when `use_internal_logger` is `false`): list of destinations for the exporter's output when bypassing the collector logger. Accepts `stdout`, `stderr`, or filesystem paths supported by Zap. - `sending_queue` (disabled by default): see [Sending Queue](../exporterhelper/README.md#sending-queue) for the full set of available options. Example configuration: @@ -138,7 +139,7 @@ This comes with the following consequences: When `use_internal_logger` is set to `false`, the exporter does not use the collector's internal logger. Changing the values in `service::telemetry::logs` has no effect on the exporter's output. -The exporter's output is sent to `stdout`. +The exporter's output is sent to `stdout` by default and can be redirected via `output_paths`. [internal_telemetry]: https://opentelemetry.io/docs/collector/internal-telemetry/ [internal_logs_config]: https://opentelemetry.io/docs/collector/internal-telemetry/#configure-internal-logs diff --git a/exporter/debugexporter/config.go b/exporter/debugexporter/config.go index acc69038821..0f030b3bc13 100644 --- a/exporter/debugexporter/config.go +++ b/exporter/debugexporter/config.go @@ -34,6 +34,9 @@ type Config struct { // UseInternalLogger defines whether the exporter sends the output to the collector's internal logger. UseInternalLogger bool `mapstructure:"use_internal_logger"` + // OutputPaths holds the list of destinations for the exporter's output when UseInternalLogger is false. + OutputPaths []string `mapstructure:"output_paths"` + QueueConfig configoptional.Optional[exporterhelper.QueueBatchConfig] `mapstructure:"sending_queue"` // prevent unkeyed literal initialization @@ -48,5 +51,16 @@ func (cfg *Config) Validate() error { return fmt.Errorf("verbosity level %q is not supported", cfg.Verbosity) } + if len(cfg.OutputPaths) > 0 { + if cfg.UseInternalLogger { + return fmt.Errorf("output_paths requires use_internal_logger to be false") + } + for _, path := range cfg.OutputPaths { + if path == "" { + return fmt.Errorf("output_paths cannot contain empty values") + } + } + } + return nil } diff --git a/exporter/debugexporter/config_test.go b/exporter/debugexporter/config_test.go index ffd98e14832..01fa6d8e0a1 100644 --- a/exporter/debugexporter/config_test.go +++ b/exporter/debugexporter/config_test.go @@ -37,6 +37,7 @@ func TestUnmarshalConfig(t *testing.T) { Verbosity: configtelemetry.LevelDetailed, SamplingInitial: 10, SamplingThereafter: 50, + OutputPaths: []string{"stderr"}, QueueConfig: configoptional.Default(queueCfg), }, }, @@ -81,6 +82,16 @@ func Test_UnmarshalMarshalled(t *testing.T) { Verbosity: configtelemetry.LevelDetailed, }, }, + "OutputPathsSpecified": { + inCfg: &Config{ + UseInternalLogger: false, + OutputPaths: []string{"stderr"}, + }, + expectedConfig: &Config{ + UseInternalLogger: false, + OutputPaths: []string{"stderr"}, + }, + }, } { t.Run(name, func(t *testing.T) { conf := confmap.New() @@ -125,6 +136,29 @@ func TestValidate(t *testing.T) { Verbosity: configtelemetry.LevelDetailed, }, }, + { + name: "output paths with internal logger", + cfg: &Config{ + UseInternalLogger: true, + OutputPaths: []string{"stderr"}, + }, + expectedErr: "output_paths requires use_internal_logger to be false", + }, + { + name: "output paths empty entry", + cfg: &Config{ + UseInternalLogger: false, + OutputPaths: []string{""}, + }, + expectedErr: "output_paths cannot contain empty values", + }, + { + name: "output paths valid", + cfg: &Config{ + UseInternalLogger: false, + OutputPaths: []string{"stderr"}, + }, + }, } for _, tt := range tests { diff --git a/exporter/debugexporter/exporter_test.go b/exporter/debugexporter/exporter_test.go index 61372c24124..814b6324505 100644 --- a/exporter/debugexporter/exporter_test.go +++ b/exporter/debugexporter/exporter_test.go @@ -128,6 +128,17 @@ func createTestCases() []testCase { return cfg }(), }, + { + name: "custom output paths", + config: func() *Config { + cfg := createDefaultConfig().(*Config) + cfg.QueueConfig = configoptional.Some(exporterhelper.NewDefaultQueueConfig()) + cfg.QueueConfig.Get().QueueSize = 10 + cfg.UseInternalLogger = false + cfg.OutputPaths = []string{"stderr"} + return cfg + }(), + }, } } diff --git a/exporter/debugexporter/factory.go b/exporter/debugexporter/factory.go index 0e5fa79a1be..578d0c0f4e0 100644 --- a/exporter/debugexporter/factory.go +++ b/exporter/debugexporter/factory.go @@ -126,6 +126,10 @@ func createCustomLogger(exporterConfig *Config) *zap.Logger { encoderConfig.LevelKey = "" // Do not prefix the output with current timestamp. encoderConfig.TimeKey = "" + outputPaths := exporterConfig.OutputPaths + if len(outputPaths) == 0 { + outputPaths = []string{"stdout"} + } zapConfig := zap.Config{ Level: zap.NewAtomicLevelAt(zap.InfoLevel), DisableCaller: true, @@ -135,8 +139,8 @@ func createCustomLogger(exporterConfig *Config) *zap.Logger { }, Encoding: "console", EncoderConfig: encoderConfig, - // Send exporter's output to stdout. This should be made configurable. - OutputPaths: []string{"stdout"}, + // Honor optional output paths (stdout by default). + OutputPaths: outputPaths, } return zap.Must(zapConfig.Build()) } diff --git a/exporter/debugexporter/testdata/config_verbosity.yaml b/exporter/debugexporter/testdata/config_verbosity.yaml index 682c45f73d4..ccc8f4b5eef 100644 --- a/exporter/debugexporter/testdata/config_verbosity.yaml +++ b/exporter/debugexporter/testdata/config_verbosity.yaml @@ -2,3 +2,5 @@ verbosity: detailed sampling_initial: 10 sampling_thereafter: 50 use_internal_logger: false +output_paths: + - stderr