diff --git a/.chloggen/mx-psi_logging-verbosity.yaml b/.chloggen/mx-psi_logging-verbosity.yaml new file mode 100755 index 00000000000..4231fd0a0e5 --- /dev/null +++ b/.chloggen/mx-psi_logging-verbosity.yaml @@ -0,0 +1,11 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: deprecation + +# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver) +component: exporter/logging + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Deprecate 'loglevel' in favor of 'verbosity' option + +# One or more tracking issues or pull requests related to the change +issues: [5878] diff --git a/exporter/loggingexporter/config.go b/exporter/loggingexporter/config.go index 6629e7a824b..5741d498cc0 100644 --- a/exporter/loggingexporter/config.go +++ b/exporter/loggingexporter/config.go @@ -15,9 +15,23 @@ package loggingexporter // import "go.opentelemetry.io/collector/exporter/loggingexporter" import ( + "fmt" + "go.uber.org/zap/zapcore" "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/confmap" +) + +var ( + // supportedLevels in this exporter's configuration. + // configtelemetry.LevelNone and other future values are not supported. + supportedLevels map[configtelemetry.Level]struct{} = map[configtelemetry.Level]struct{}{ + configtelemetry.LevelBasic: {}, + configtelemetry.LevelNormal: {}, + configtelemetry.LevelDetailed: {}, + } ) // Config defines configuration for logging exporter. @@ -25,18 +39,69 @@ type Config struct { config.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct // LogLevel defines log level of the logging exporter; options are debug, info, warn, error. + // Deprecated: Use `Verbosity` instead. LogLevel zapcore.Level `mapstructure:"loglevel"` + // Verbosity defines the logging exporter verbosity. + Verbosity configtelemetry.Level `mapstructure:"verbosity"` + // SamplingInitial defines how many samples are initially logged during each second. SamplingInitial int `mapstructure:"sampling_initial"` // SamplingThereafter defines the sampling rate after the initial samples are logged. SamplingThereafter int `mapstructure:"sampling_thereafter"` + + // warnLogLevel is set on unmarshaling to warn users about `loglevel` usage. + warnLogLevel bool } var _ config.Exporter = (*Config)(nil) +var _ confmap.Unmarshaler = (*Config)(nil) + +func mapLevel(level zapcore.Level) (configtelemetry.Level, error) { + switch level { + case zapcore.DebugLevel: + return configtelemetry.LevelDetailed, nil + case zapcore.InfoLevel: + return configtelemetry.LevelNormal, nil + case zapcore.WarnLevel, zapcore.ErrorLevel, + zapcore.DPanicLevel, zapcore.PanicLevel, zapcore.FatalLevel: + // Anything above info is mapped to 'basic' level. + return configtelemetry.LevelBasic, nil + default: + return configtelemetry.LevelNone, fmt.Errorf("log level %q is not supported", level) + } +} + +func (cfg *Config) Unmarshal(conf *confmap.Conf) error { + if conf.IsSet("loglevel") && conf.IsSet("verbosity") { + return fmt.Errorf("'loglevel' and 'verbosity' are incompatible. Use only 'verbosity' instead") + } + + if err := conf.Unmarshal(cfg, confmap.WithErrorUnused()); err != nil { + return err + } + + if conf.IsSet("loglevel") { + verbosity, err := mapLevel(cfg.LogLevel) + if err != nil { + return fmt.Errorf("failed to map 'loglevel': %w", err) + } + + // 'verbosity' is unset but 'loglevel' is set. + // Override default verbosity. + cfg.Verbosity = verbosity + cfg.warnLogLevel = true + } + + return nil +} // Validate checks if the exporter configuration is valid func (cfg *Config) Validate() error { + if _, ok := supportedLevels[cfg.Verbosity]; !ok { + return fmt.Errorf("verbosity level %q is not supported", cfg.Verbosity) + } + return nil } diff --git a/exporter/loggingexporter/config_test.go b/exporter/loggingexporter/config_test.go index ab7fdc86240..d390c1fa2d4 100644 --- a/exporter/loggingexporter/config_test.go +++ b/exporter/loggingexporter/config_test.go @@ -23,6 +23,7 @@ import ( "go.uber.org/zap/zapcore" "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/confmap/confmaptest" ) @@ -35,16 +36,95 @@ func TestUnmarshalDefaultConfig(t *testing.T) { } func TestUnmarshalConfig(t *testing.T) { - cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml")) - require.NoError(t, err) - factory := NewFactory() - cfg := factory.CreateDefaultConfig() - assert.NoError(t, config.UnmarshalExporter(cm, cfg)) - assert.Equal(t, - &Config{ - ExporterSettings: config.NewExporterSettings(config.NewComponentID(typeStr)), - LogLevel: zapcore.DebugLevel, - SamplingInitial: 10, - SamplingThereafter: 50, - }, cfg) + tests := []struct { + filename string + cfg *Config + expectedErr string + }{ + { + filename: "config_loglevel.yaml", + cfg: &Config{ + ExporterSettings: config.NewExporterSettings(config.NewComponentID(typeStr)), + LogLevel: zapcore.DebugLevel, + Verbosity: configtelemetry.LevelDetailed, + SamplingInitial: 10, + SamplingThereafter: 50, + warnLogLevel: true, + }, + }, + { + filename: "config_verbosity.yaml", + cfg: &Config{ + ExporterSettings: config.NewExporterSettings(config.NewComponentID(typeStr)), + LogLevel: zapcore.InfoLevel, + Verbosity: configtelemetry.LevelDetailed, + SamplingInitial: 10, + SamplingThereafter: 50, + }, + }, + { + filename: "loglevel_info.yaml", + cfg: &Config{ + ExporterSettings: config.NewExporterSettings(config.NewComponentID(typeStr)), + LogLevel: zapcore.InfoLevel, + Verbosity: configtelemetry.LevelNormal, + SamplingInitial: 2, + SamplingThereafter: 500, + warnLogLevel: true, + }, + }, + { + filename: "invalid_verbosity_loglevel.yaml", + expectedErr: "'loglevel' and 'verbosity' are incompatible. Use only 'verbosity' instead", + }, + } + + for _, tt := range tests { + t.Run(tt.filename, func(t *testing.T) { + cm, err := confmaptest.LoadConf(filepath.Join("testdata", tt.filename)) + require.NoError(t, err) + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + err = config.UnmarshalExporter(cm, cfg) + if tt.expectedErr != "" { + assert.EqualError(t, err, tt.expectedErr) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.cfg, cfg) + } + }) + } +} + +func TestValidate(t *testing.T) { + tests := []struct { + name string + cfg *Config + expectedErr string + }{ + { + name: "verbosity none", + cfg: &Config{ + Verbosity: configtelemetry.LevelNone, + }, + expectedErr: "verbosity level \"none\" is not supported", + }, + { + name: "verbosity detailed", + cfg: &Config{ + Verbosity: configtelemetry.LevelDetailed, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.cfg.Validate() + if tt.expectedErr != "" { + assert.EqualError(t, err, tt.expectedErr) + } else { + assert.NoError(t, err) + } + }) + } } diff --git a/exporter/loggingexporter/factory.go b/exporter/loggingexporter/factory.go index ac8dc22e409..c17ab55fb5c 100644 --- a/exporter/loggingexporter/factory.go +++ b/exporter/loggingexporter/factory.go @@ -16,6 +16,7 @@ package loggingexporter // import "go.opentelemetry.io/collector/exporter/loggin import ( "context" + "sync" "time" "go.uber.org/zap" @@ -23,6 +24,7 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/exporter/exporterhelper" ) @@ -34,6 +36,8 @@ const ( defaultSamplingThereafter = 500 ) +var onceWarnLogLevel sync.Once + // NewFactory creates a factory for Logging exporter func NewFactory() component.ExporterFactory { return component.NewExporterFactory( @@ -49,6 +53,7 @@ func createDefaultConfig() config.Exporter { return &Config{ ExporterSettings: config.NewExporterSettings(config.NewComponentID(typeStr)), LogLevel: zapcore.InfoLevel, + Verbosity: configtelemetry.LevelNormal, SamplingInitial: defaultSamplingInitial, SamplingThereafter: defaultSamplingThereafter, } @@ -57,7 +62,7 @@ func createDefaultConfig() config.Exporter { func createTracesExporter(ctx context.Context, set component.ExporterCreateSettings, config config.Exporter) (component.TracesExporter, error) { cfg := config.(*Config) exporterLogger := createLogger(cfg, set.TelemetrySettings.Logger) - s := newLoggingExporter(exporterLogger, cfg.LogLevel) + s := newLoggingExporter(exporterLogger, cfg.Verbosity) return exporterhelper.NewTracesExporter(ctx, set, cfg, s.pushTraces, exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}), @@ -72,7 +77,7 @@ func createTracesExporter(ctx context.Context, set component.ExporterCreateSetti func createMetricsExporter(ctx context.Context, set component.ExporterCreateSettings, config config.Exporter) (component.MetricsExporter, error) { cfg := config.(*Config) exporterLogger := createLogger(cfg, set.TelemetrySettings.Logger) - s := newLoggingExporter(exporterLogger, cfg.LogLevel) + s := newLoggingExporter(exporterLogger, cfg.Verbosity) return exporterhelper.NewMetricsExporter(ctx, set, cfg, s.pushMetrics, exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}), @@ -87,7 +92,7 @@ func createMetricsExporter(ctx context.Context, set component.ExporterCreateSett func createLogsExporter(ctx context.Context, set component.ExporterCreateSettings, config config.Exporter) (component.LogsExporter, error) { cfg := config.(*Config) exporterLogger := createLogger(cfg, set.TelemetrySettings.Logger) - s := newLoggingExporter(exporterLogger, cfg.LogLevel) + s := newLoggingExporter(exporterLogger, cfg.Verbosity) return exporterhelper.NewLogsExporter(ctx, set, cfg, s.pushLogs, exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}), @@ -100,6 +105,16 @@ func createLogsExporter(ctx context.Context, set component.ExporterCreateSetting } func createLogger(cfg *Config, logger *zap.Logger) *zap.Logger { + if cfg.warnLogLevel { + onceWarnLogLevel.Do(func() { + logger.Warn( + "'loglevel' option is deprecated in favor of 'verbosity'. Set 'verbosity' to equivalent value to preserve behavior.", + zap.Stringer("loglevel", cfg.LogLevel), + zap.Stringer("equivalent verbosity level", cfg.Verbosity), + ) + }) + } + core := zapcore.NewSamplerWithOptions( logger.Core(), 1*time.Second, diff --git a/exporter/loggingexporter/logging_exporter.go b/exporter/loggingexporter/logging_exporter.go index 213bf77caab..fc3f5ff4260 100644 --- a/exporter/loggingexporter/logging_exporter.go +++ b/exporter/loggingexporter/logging_exporter.go @@ -20,8 +20,8 @@ import ( "os" "go.uber.org/zap" - "go.uber.org/zap/zapcore" + "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/exporter/loggingexporter/internal/otlptext" "go.opentelemetry.io/collector/pdata/plog" "go.opentelemetry.io/collector/pdata/pmetric" @@ -29,7 +29,7 @@ import ( ) type loggingExporter struct { - logLevel zapcore.Level + verbosity configtelemetry.Level logger *zap.Logger logsMarshaler plog.Marshaler metricsMarshaler pmetric.Marshaler @@ -38,7 +38,7 @@ type loggingExporter struct { func (s *loggingExporter) pushTraces(_ context.Context, td ptrace.Traces) error { s.logger.Info("TracesExporter", zap.Int("#spans", td.SpanCount())) - if s.logLevel != zapcore.DebugLevel { + if s.verbosity != configtelemetry.LevelDetailed { return nil } @@ -52,8 +52,7 @@ func (s *loggingExporter) pushTraces(_ context.Context, td ptrace.Traces) error func (s *loggingExporter) pushMetrics(_ context.Context, md pmetric.Metrics) error { s.logger.Info("MetricsExporter", zap.Int("#metrics", md.MetricCount())) - - if s.logLevel != zapcore.DebugLevel { + if s.verbosity != configtelemetry.LevelDetailed { return nil } @@ -67,8 +66,7 @@ func (s *loggingExporter) pushMetrics(_ context.Context, md pmetric.Metrics) err func (s *loggingExporter) pushLogs(_ context.Context, ld plog.Logs) error { s.logger.Info("LogsExporter", zap.Int("#logs", ld.LogRecordCount())) - - if s.logLevel != zapcore.DebugLevel { + if s.verbosity != configtelemetry.LevelDetailed { return nil } @@ -80,9 +78,9 @@ func (s *loggingExporter) pushLogs(_ context.Context, ld plog.Logs) error { return nil } -func newLoggingExporter(logger *zap.Logger, logLevel zapcore.Level) *loggingExporter { +func newLoggingExporter(logger *zap.Logger, verbosity configtelemetry.Level) *loggingExporter { return &loggingExporter{ - logLevel: logLevel, + verbosity: verbosity, logger: logger, logsMarshaler: otlptext.NewTextLogsMarshaler(), metricsMarshaler: otlptext.NewTextMetricsMarshaler(), diff --git a/exporter/loggingexporter/logging_exporter_test.go b/exporter/loggingexporter/logging_exporter_test.go index 65e1dc41857..afb09277900 100644 --- a/exporter/loggingexporter/logging_exporter_test.go +++ b/exporter/loggingexporter/logging_exporter_test.go @@ -20,10 +20,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest" "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/internal/testdata" "go.opentelemetry.io/collector/pdata/plog" "go.opentelemetry.io/collector/pdata/pmetric" @@ -70,7 +70,7 @@ func TestLoggingLogsExporterNoErrors(t *testing.T) { } func TestLoggingExporterErrors(t *testing.T) { - le := newLoggingExporter(zaptest.NewLogger(t), zapcore.DebugLevel) + le := newLoggingExporter(zaptest.NewLogger(t), configtelemetry.LevelDetailed) require.NotNil(t, le) errWant := errors.New("my error") diff --git a/exporter/loggingexporter/testdata/config.yaml b/exporter/loggingexporter/testdata/config_loglevel.yaml similarity index 100% rename from exporter/loggingexporter/testdata/config.yaml rename to exporter/loggingexporter/testdata/config_loglevel.yaml diff --git a/exporter/loggingexporter/testdata/config_verbosity.yaml b/exporter/loggingexporter/testdata/config_verbosity.yaml new file mode 100644 index 00000000000..4ea62997627 --- /dev/null +++ b/exporter/loggingexporter/testdata/config_verbosity.yaml @@ -0,0 +1,3 @@ +verbosity: detailed +sampling_initial: 10 +sampling_thereafter: 50 diff --git a/exporter/loggingexporter/testdata/invalid_verbosity_loglevel.yaml b/exporter/loggingexporter/testdata/invalid_verbosity_loglevel.yaml new file mode 100644 index 00000000000..37f8f16b24f --- /dev/null +++ b/exporter/loggingexporter/testdata/invalid_verbosity_loglevel.yaml @@ -0,0 +1,2 @@ +loglevel: info +verbosity: detailed diff --git a/exporter/loggingexporter/testdata/loglevel_info.yaml b/exporter/loggingexporter/testdata/loglevel_info.yaml new file mode 100644 index 00000000000..63506faefb4 --- /dev/null +++ b/exporter/loggingexporter/testdata/loglevel_info.yaml @@ -0,0 +1 @@ +loglevel: info