From 4964d925b59c0b2fe16f3b2d657fe22a1eb5664e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Thu, 19 Sep 2024 13:29:26 +0200 Subject: [PATCH 1/4] [Spike] MultiLoggerProvider --- sdk/log/multi.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 sdk/log/multi.go diff --git a/sdk/log/multi.go b/sdk/log/multi.go new file mode 100644 index 00000000000..0f51aa98b96 --- /dev/null +++ b/sdk/log/multi.go @@ -0,0 +1,62 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package log // import "go.opentelemetry.io/otel/sdk/log" + +import ( + "context" + + "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/log/embedded" +) + +type multiLoggerProvider struct { + embedded.LoggerProvider + + providers []log.LoggerProvider + + noCmp [0]func() //nolint: unused // This is indeed used. +} + +// NewMultiLoggerProvider returns a composite (fan-out) provider. +// It duplicates its calls to all the provided providers. +// It can be used to set up multiple processing pipelines. +// For instance, you can have separate providers for OTel events +// and application logs. +func NewMultiLoggerProvider(providers ...log.LoggerProvider) log.LoggerProvider { + return &multiLoggerProvider{ + providers: providers, + } +} + +// Logger returns a logger delegating to loggers created by all providers. +func (p *multiLoggerProvider) Logger(name string, opts ...log.LoggerOption) log.Logger { + var loggers []log.Logger + for _, p := range p.providers { + loggers = append(loggers, p.Logger(name, opts...)) + } + return &multiLogger{loggers: loggers} +} + +type multiLogger struct { + embedded.Logger + + loggers []log.Logger + + noCmp [0]func() //nolint: unused // This is indeed used. +} + +func (l *multiLogger) Emit(ctx context.Context, r log.Record) { + for _, l := range l.loggers { + l.Emit(ctx, r) + } +} + +func (l *multiLogger) Enabled(ctx context.Context, param log.EnabledParameters) bool { + for _, l := range l.loggers { + if !l.Enabled(ctx, param) { + return false + } + } + return true +} From d6aa31b8be88d4682b35633e23db220d9b17390b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Thu, 19 Sep 2024 13:32:18 +0200 Subject: [PATCH 2/4] Rename --- sdk/log/multi.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/log/multi.go b/sdk/log/multi.go index 0f51aa98b96..d45d79e55b7 100644 --- a/sdk/log/multi.go +++ b/sdk/log/multi.go @@ -18,12 +18,12 @@ type multiLoggerProvider struct { noCmp [0]func() //nolint: unused // This is indeed used. } -// NewMultiLoggerProvider returns a composite (fan-out) provider. +// MultiLoggerProvider returns a composite (fan-out) provider. // It duplicates its calls to all the provided providers. // It can be used to set up multiple processing pipelines. // For instance, you can have separate providers for OTel events // and application logs. -func NewMultiLoggerProvider(providers ...log.LoggerProvider) log.LoggerProvider { +func MultiLoggerProvider(providers ...log.LoggerProvider) log.LoggerProvider { return &multiLoggerProvider{ providers: providers, } From 823582afe8eafe3f62c9e7f9fa4e42d1848174d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Thu, 19 Sep 2024 13:35:47 +0200 Subject: [PATCH 3/4] Refine comment --- sdk/log/multi.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/log/multi.go b/sdk/log/multi.go index d45d79e55b7..7068517a9d5 100644 --- a/sdk/log/multi.go +++ b/sdk/log/multi.go @@ -19,7 +19,7 @@ type multiLoggerProvider struct { } // MultiLoggerProvider returns a composite (fan-out) provider. -// It duplicates its calls to all the provided providers. +// It duplicates its calls to all the passed providers. // It can be used to set up multiple processing pipelines. // For instance, you can have separate providers for OTel events // and application logs. From 0ebc71fb3998d62e1d9f2788e82060b42f96b76e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Thu, 19 Sep 2024 14:03:11 +0200 Subject: [PATCH 4/4] Add ExampleMultiLoggerProvider --- sdk/log/example_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/sdk/log/example_test.go b/sdk/log/example_test.go index 8070beef771..207f26ec10f 100644 --- a/sdk/log/example_test.go +++ b/sdk/log/example_test.go @@ -156,3 +156,44 @@ func (p *RedactTokensProcessor) Shutdown(ctx context.Context) error { func (p *RedactTokensProcessor) ForceFlush(ctx context.Context) error { return nil } + +func ExampleMultiLoggerProvider() { + // Set up a pipeline that emits redacted logs via OTLP with batching. + var otlpExporter log.Exporter // exporter, err := otlploghttp.New(ctx) + redactProcessor := &RedactTokensProcessor{} + processor := log.NewBatchProcessor(otlpExporter) + provider1 := log.NewLoggerProvider( + log.WithProcessor(redactProcessor), + log.WithProcessor(processor), + ) + defer func() { + err := provider1.Shutdown(context.Background()) + if err != nil { + fmt.Println(err) + } + }() + + // Set up a pipeline that synchrnously emits logs to stdout. + var stdoutExporter log.Exporter // exporter, err := stdoutlog.New(ctx) + provider2 := log.NewLoggerProvider( + log.WithProcessor( + log.NewSimpleProcessor(stdoutExporter), + ), + ) + defer func() { + err := provider2.Shutdown(context.Background()) + if err != nil { + fmt.Println(err) + } + }() + + // Create a multi provider which handles both pipelines. + multiProvider := log.MultiLoggerProvider(provider1, provider2) + + // Register as global logger provider so that it can be used via global.Meter + // and accessed using global.GetMeterProvider. + // Most log bridges use the global logger provider as default. + // If the global logger provider is not set then a no-op implementation + // is used, which fails to generate data. + global.SetLoggerProvider(multiProvider) +}