diff --git a/otelcol/collector.go b/otelcol/collector.go index e7983017d3d..a15d0c60583 100644 --- a/otelcol/collector.go +++ b/otelcol/collector.go @@ -15,6 +15,7 @@ import ( "go.uber.org/multierr" "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/confmap" @@ -109,6 +110,8 @@ type Collector struct { signalsChannel chan os.Signal // asyncErrorChannel is used to signal a fatal error from any component. asyncErrorChannel chan error + + ol *observer.ObservedLogs } // NewCollector creates and returns a new instance of Collector. @@ -116,7 +119,8 @@ func NewCollector(set CollectorSettings) (*Collector, error) { var err error configProvider := set.ConfigProvider - set.ConfigProviderSettings.ResolverSettings.ProviderSettings = confmap.ProviderSettings{Logger: zap.NewNop()} + core, ol := observer.New(zap.DebugLevel) + set.ConfigProviderSettings.ResolverSettings.ProviderSettings = confmap.ProviderSettings{Logger: zap.New(core)} set.ConfigProviderSettings.ResolverSettings.ConverterSettings = confmap.ConverterSettings{} if configProvider == nil { @@ -137,6 +141,7 @@ func NewCollector(set CollectorSettings) (*Collector, error) { signalsChannel: make(chan os.Signal, 3), asyncErrorChannel: make(chan error), configProvider: configProvider, + ol: ol, }, nil } @@ -202,6 +207,12 @@ func (col *Collector) setupConfigurationComponents(ctx context.Context) error { return err } + if col.ol != nil { + for _, log := range col.ol.All() { + col.service.Logger().Log(log.Level, log.Message) + } + } + if !col.set.SkipSettingGRPCLogger { grpclog.SetLogger(col.service.Logger(), cfg.Service.Telemetry.Logs.Level) } diff --git a/otelcol/collector_test.go b/otelcol/collector_test.go index 2ae56fc58c0..7f42b3d71a4 100644 --- a/otelcol/collector_test.go +++ b/otelcol/collector_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uber.org/zap" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/confmap" @@ -476,6 +477,61 @@ func TestPassConfmapToServiceFailure(t *testing.T) { require.Error(t, err) } +type provider struct { + logger *zap.Logger +} + +func newWithSettings(ps confmap.ProviderSettings) confmap.Provider { + return &provider{ + logger: ps.Logger, + } +} + +func newFactory() confmap.ProviderFactory { + return confmap.NewProviderFactory(newWithSettings) +} + +func (p *provider) Retrieve(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) { + p.logger.Info("this is a test log") + return confmap.NewRetrieved("test") +} + +func (*provider) Scheme() string { + return "test" +} + +func (*provider) Shutdown(context.Context) error { + return nil +} + +func TestCollectorConfmapLogs(t *testing.T) { + configProviderSettings := newDefaultConfigProviderSettings([]string{filepath.Join("testdata", "otelcol-confmap-logging.yaml")}) + configProviderSettings.ResolverSettings.ProviderFactories = append(configProviderSettings.ResolverSettings.ProviderFactories, newFactory()) + + set := CollectorSettings{ + BuildInfo: component.NewDefaultBuildInfo(), + Factories: nopFactories, + ConfigProviderSettings: configProviderSettings, + } + col, err := NewCollector(set) + require.NoError(t, err) + + wg := startCollector(context.Background(), t, col) + + col.Shutdown() + wg.Wait() + assert.Equal(t, StateClosed, col.GetState()) + + require.NotNil(t, col.ol) + require.Greater(t, col.ol.Len(), 0) + + messages := make([]string, 0, col.ol.Len()) + for _, l := range col.ol.All() { + messages = append(messages, l.Message) + } + assert.Contains(t, messages, "this is a test log") +} + func startCollector(ctx context.Context, t *testing.T, col *Collector) *sync.WaitGroup { wg := &sync.WaitGroup{} wg.Add(1) diff --git a/otelcol/testdata/otelcol-confmap-logging.yaml b/otelcol/testdata/otelcol-confmap-logging.yaml new file mode 100644 index 00000000000..c8e7e0c434f --- /dev/null +++ b/otelcol/testdata/otelcol-confmap-logging.yaml @@ -0,0 +1,34 @@ +receivers: + nop: + +processors: + nop: + +exporters: + nop: + +extensions: + nop: + +connectors: + nop/con: + +service: + telemetry: + logs: + initial_fields: + "test": ${test:ANYTHING} + extensions: [nop] + pipelines: + traces: + receivers: [nop] + processors: [nop] + exporters: [nop, nop/con] + metrics: + receivers: [nop] + processors: [nop] + exporters: [nop] + logs: + receivers: [nop, nop/con] + processors: [nop] + exporters: [nop]