opentelemetry-collector/internal/telemetry/componentattribute/logger_zap.go

145 lines
4.9 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package componentattribute // import "go.opentelemetry.io/collector/internal/telemetry/componentattribute"
import (
"go.opentelemetry.io/contrib/bridges/otelzap"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/log"
"go.uber.org/multierr"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// Interface for Zap cores that support setting and resetting a set of component attributes.
//
// There are two wrappers that implement this interface:
//
// - [NewConsoleCoreWithAttributes] injects component attributes as Zap fields.
//
// This is used for the Collector's console output.
//
// - [NewOTelTeeCoreWithAttributes] copies logs to a [log.LoggerProvider] using [otelzap]. For the
// copied logs, component attributes are injected as instrumentation scope attributes.
//
// This is used when service::telemetry::logs::processors is configured.
type coreWithAttributes interface {
zapcore.Core
withAttributeSet(attribute.Set) zapcore.Core
}
// Tries setting the component attribute set for a Zap core.
//
// Does nothing if the core does not implement [coreWithAttributes].
func tryWithAttributeSet(c zapcore.Core, attrs attribute.Set) zapcore.Core {
if cwa, ok := c.(coreWithAttributes); ok {
return cwa.withAttributeSet(attrs)
}
zap.New(c).Debug("Logger core does not support injecting component attributes")
return c
}
type consoleCoreWithAttributes struct {
zapcore.Core
from zapcore.Core
}
var _ coreWithAttributes = (*consoleCoreWithAttributes)(nil)
// NewConsoleCoreWithAttributes wraps a Zap core in order to inject component attributes as Zap fields.
//
// This is used for the Collector's console output.
func NewConsoleCoreWithAttributes(c zapcore.Core, attrs attribute.Set) zapcore.Core {
var fields []zap.Field
for _, kv := range attrs.ToSlice() {
fields = append(fields, zap.String(string(kv.Key), kv.Value.AsString()))
}
return &consoleCoreWithAttributes{
Core: c.With(fields),
from: c,
}
}
func (ccwa *consoleCoreWithAttributes) withAttributeSet(attrs attribute.Set) zapcore.Core {
return NewConsoleCoreWithAttributes(ccwa.from, attrs)
}
type otelTeeCoreWithAttributes struct {
sourceCore zapcore.Core
otelCore zapcore.Core
lp log.LoggerProvider
scopeName string
}
var _ coreWithAttributes = (*otelTeeCoreWithAttributes)(nil)
// NewOTelTeeCoreWithAttributes wraps a Zap core in order to copy logs to a [log.LoggerProvider] using [otelzap].
// For the copied logs, component attributes are injected as instrumentation scope attributes.
//
// Note that we intentionally do not use zapcore.NewTee here, because it will simply duplicate all log entries
// to each core. The provided Zap core may have sampling or a minimum log level applied to it, so in order to
// maintain consistency we need to ensure that only the logs accepted by the provided core are copied to the
// log.LoggerProvider.
func NewOTelTeeCoreWithAttributes(core zapcore.Core, lp log.LoggerProvider, scopeName string, attrs attribute.Set) zapcore.Core {
otelCore := otelzap.NewCore(
scopeName,
otelzap.WithLoggerProvider(lp),
otelzap.WithAttributes(attrs.ToSlice()...),
)
return &otelTeeCoreWithAttributes{
sourceCore: core,
otelCore: otelCore,
lp: lp,
scopeName: scopeName,
}
}
func (ocwa *otelTeeCoreWithAttributes) withAttributeSet(attrs attribute.Set) zapcore.Core {
return NewOTelTeeCoreWithAttributes(
tryWithAttributeSet(ocwa.sourceCore, attrs),
ocwa.lp, ocwa.scopeName, attrs,
)
}
func (ocwa *otelTeeCoreWithAttributes) With(fields []zapcore.Field) zapcore.Core {
sourceCoreWith := ocwa.sourceCore.With(fields)
otelCoreWith := ocwa.otelCore.With(fields)
return &otelTeeCoreWithAttributes{
sourceCore: sourceCoreWith,
otelCore: otelCoreWith,
lp: ocwa.lp,
scopeName: ocwa.scopeName,
}
}
func (ocwa *otelTeeCoreWithAttributes) Enabled(level zapcore.Level) bool {
return ocwa.sourceCore.Enabled(level)
}
func (ocwa *otelTeeCoreWithAttributes) Check(entry zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
ce = ocwa.sourceCore.Check(entry, ce)
if ce != nil {
// Only log to the otelzap core if the input core accepted the log entry.
ce = ce.AddCore(entry, ocwa.otelCore)
}
return ce
}
func (ocwa *otelTeeCoreWithAttributes) Write(entry zapcore.Entry, fields []zapcore.Field) error {
err := ocwa.sourceCore.Write(entry, fields)
return multierr.Append(err, ocwa.otelCore.Write(entry, fields))
}
func (ocwa *otelTeeCoreWithAttributes) Sync() error {
err := ocwa.sourceCore.Sync()
return multierr.Append(err, ocwa.otelCore.Sync())
}
// ZapLoggerWithAttributes creates a Zap Logger with a new set of injected component attributes.
func ZapLoggerWithAttributes(logger *zap.Logger, attrs attribute.Set) *zap.Logger {
return logger.WithOptions(zap.WrapCore(func(c zapcore.Core) zapcore.Core {
return tryWithAttributeSet(c, attrs)
}))
}