Skip to content

Commit

Permalink
[exporter/logging] Deprecate 'loglevel' in favor of 'verbosity' (#6334)
Browse files Browse the repository at this point in the history
* [exporter/logging] Deprecated 'loglevel' in favor of 'verbosity'

* Fix tests

* Remove check for mismatch between levels

* Add newline at EOF

* Use set instead of switch for checking level

* More newlines

* Move supportedLevels out of Validate method
  • Loading branch information
mx-psi authored Oct 24, 2022
1 parent ef4d4e2 commit 2363b52
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 26 deletions.
11 changes: 11 additions & 0 deletions .chloggen/mx-psi_logging-verbosity.yaml
Original file line number Diff line number Diff line change
@@ -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]
65 changes: 65 additions & 0 deletions exporter/loggingexporter/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,93 @@
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.
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
}
104 changes: 92 additions & 12 deletions exporter/loggingexporter/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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)
}
})
}
}
21 changes: 18 additions & 3 deletions exporter/loggingexporter/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ package loggingexporter // import "go.opentelemetry.io/collector/exporter/loggin

import (
"context"
"sync"
"time"

"go.uber.org/zap"
"go.uber.org/zap/zapcore"

"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"
)
Expand All @@ -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(
Expand All @@ -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,
}
Expand All @@ -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}),
Expand All @@ -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}),
Expand All @@ -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}),
Expand All @@ -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,
Expand Down
16 changes: 7 additions & 9 deletions exporter/loggingexporter/logging_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ 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"
"go.opentelemetry.io/collector/pdata/ptrace"
)

type loggingExporter struct {
logLevel zapcore.Level
verbosity configtelemetry.Level
logger *zap.Logger
logsMarshaler plog.Marshaler
metricsMarshaler pmetric.Marshaler
Expand All @@ -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
}

Expand All @@ -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
}

Expand All @@ -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
}

Expand All @@ -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(),
Expand Down
4 changes: 2 additions & 2 deletions exporter/loggingexporter/logging_exporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")
Expand Down
3 changes: 3 additions & 0 deletions exporter/loggingexporter/testdata/config_verbosity.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
verbosity: detailed
sampling_initial: 10
sampling_thereafter: 50
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
loglevel: info
verbosity: detailed
1 change: 1 addition & 0 deletions exporter/loggingexporter/testdata/loglevel_info.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
loglevel: info

0 comments on commit 2363b52

Please sign in to comment.