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) +} diff --git a/sdk/log/multi.go b/sdk/log/multi.go new file mode 100644 index 00000000000..7068517a9d5 --- /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. +} + +// MultiLoggerProvider returns a composite (fan-out) provider. +// 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. +func MultiLoggerProvider(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 +}