mirror of
https://github.com/trufflesecurity/trufflehog.git
synced 2026-06-15 13:24:37 +00:00
* Replace logConfig with zapcore.Core * Add SyncFunc type and tests * Change Sentry functions to accept a *sentry.Client This allows the caller to do the error handling. * Rename newCoreConfig to newCore * Remove redundant WithCore option * Update doc comments
234 lines
7.4 KiB
Go
234 lines
7.4 KiB
Go
package log
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/TheZeroSlave/zapsentry"
|
|
"github.com/getsentry/sentry-go"
|
|
"github.com/go-logr/logr"
|
|
"github.com/go-logr/zapr"
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
)
|
|
|
|
type SyncFunc func() error
|
|
|
|
type SinkOption func(*sinkConfig)
|
|
|
|
// New creates a new log object with the provided sinks. If no sinks are
|
|
// provided, a no-op sink will be used. Returns the logger and a cleanup
|
|
// function that should be executed before the program exits.
|
|
func New(service string, cores ...zapcore.Core) (logr.Logger, SyncFunc) {
|
|
return NewWithCaller(service, false, cores...)
|
|
}
|
|
|
|
// NewWithCaller creates a new logger named after the specified service with
|
|
// the provided sinks. If addCaller is true, call site information will be
|
|
// attached to each emitted log message. (This behavior can be disabled on a
|
|
// per-sink basis using WithSuppressCaller.)
|
|
func NewWithCaller(service string, addCaller bool, cores ...zapcore.Core) (logr.Logger, SyncFunc) {
|
|
// create logger
|
|
zapLogger := zap.New(zapcore.NewTee(cores...), zap.WithCaller(addCaller))
|
|
logger := zapr.NewLogger(zapLogger).WithName(service)
|
|
|
|
return logger, zapLogger.Sync
|
|
}
|
|
|
|
// WithSentry adds sentry integration to the logger.
|
|
func WithSentry(client *sentry.Client, tags map[string]string) zapcore.Core {
|
|
// create sentry core
|
|
cfg := zapsentry.Configuration{
|
|
Tags: tags,
|
|
Level: zapcore.ErrorLevel,
|
|
EnableBreadcrumbs: true,
|
|
BreadcrumbLevel: zapcore.InfoLevel,
|
|
}
|
|
core, err := zapsentry.NewCore(cfg, zapsentry.NewSentryClientFromClient(client))
|
|
if err != nil {
|
|
// NewCore should never fail because NewSentryClientFromClient
|
|
// never returns an error and NewCore only returns an error if
|
|
// the zapsentry.Configuration is invalid, which would indicate
|
|
// a programmer error.
|
|
panic(err)
|
|
}
|
|
|
|
return core
|
|
}
|
|
|
|
type sinkConfig struct {
|
|
encoder zapcore.Encoder
|
|
sink zapcore.WriteSyncer
|
|
level levelSetter
|
|
redactor *dynamicRedactor
|
|
suppressCaller bool
|
|
}
|
|
|
|
// WithJSONSink adds a JSON encoded output to the logger.
|
|
func WithJSONSink(sink io.Writer, opts ...SinkOption) zapcore.Core {
|
|
return newCore(
|
|
zapcore.NewJSONEncoder(defaultEncoderConfig()),
|
|
zapcore.Lock(zapcore.AddSync(sink)),
|
|
globalLogLevel,
|
|
opts...,
|
|
)
|
|
}
|
|
|
|
// WithConsoleSink adds a console-style output to the logger.
|
|
func WithConsoleSink(sink io.Writer, opts ...SinkOption) zapcore.Core {
|
|
return newCore(
|
|
zapcore.NewConsoleEncoder(defaultEncoderConfig()),
|
|
zapcore.Lock(zapcore.AddSync(sink)),
|
|
globalLogLevel,
|
|
opts...,
|
|
)
|
|
}
|
|
|
|
func defaultEncoderConfig() zapcore.EncoderConfig {
|
|
conf := zap.NewProductionEncoderConfig()
|
|
// Use more human-readable time format.
|
|
conf.EncodeTime = zapcore.TimeEncoderOfLayout(time.RFC3339)
|
|
conf.EncodeLevel = func(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
|
|
if level == zapcore.ErrorLevel {
|
|
enc.AppendString("error")
|
|
return
|
|
}
|
|
enc.AppendString(fmt.Sprintf("info-%d", -int8(level)))
|
|
}
|
|
return conf
|
|
}
|
|
|
|
// AddSentry initializes a sentry client and extends an existing
|
|
// logr.Logger with the hook.
|
|
func AddSentry(l logr.Logger, client *sentry.Client, tags map[string]string) (logr.Logger, SyncFunc, error) {
|
|
return AddSink(l, WithSentry(client, tags))
|
|
}
|
|
|
|
// AddSink extends an existing logr.Logger with a new sink. It returns the new logr.Logger, a cleanup function, and an
|
|
// error.
|
|
//
|
|
// The new sink will not inherit any of the existing logger's key-value pairs. Key-value pairs can be added to the new
|
|
// sink specifically by passing them to this function.
|
|
func AddSink(l logr.Logger, core zapcore.Core, keysAndValues ...any) (logr.Logger, SyncFunc, error) {
|
|
// New key-value pairs cannot be ergonomically added directly to cores. logr has code to do it, but that code is not
|
|
// exported. Rather than replicating it ourselves, we indirectly use it by creating a temporary logger for the new
|
|
// core, adding the key-value pairs to the temporary logger, and then extracting the temporary logger's modified
|
|
// core.
|
|
newSinkLogger := zapr.NewLogger(zap.New(core))
|
|
newSinkLogger = newSinkLogger.WithValues(keysAndValues...)
|
|
newCoreLogger, err := getZapLogger(newSinkLogger)
|
|
if err != nil {
|
|
return l, nil, fmt.Errorf("error setting up new key-value pairs: %w", err)
|
|
}
|
|
newSinkCore := newCoreLogger.Core()
|
|
|
|
zapLogger, err := getZapLogger(l)
|
|
if err != nil {
|
|
return l, nil, errors.New("unsupported logr implementation")
|
|
}
|
|
|
|
newLoggerOptions := []zap.Option{
|
|
// Tee the new core together with the original core
|
|
zap.WrapCore(func(core zapcore.Core) zapcore.Core { return zapcore.NewTee(core, newSinkCore) }),
|
|
|
|
// CMR: zapr.NewLogger, for whatever reason, assumes that the passed-in logger doesn't have its caller frame
|
|
// adjustment already set up, so it adds a frame skip of 2. However, that assumption doesn't hold here because
|
|
// we're adding a core to an existing logger rather than creating a new one. I can't figure out a way to disable
|
|
// this automatic frame adjustment, so we compensate for it with the hamfisted kludge of a compensating offset.
|
|
zap.AddCallerSkip(-2),
|
|
}
|
|
|
|
zapLogger = zapLogger.WithOptions(newLoggerOptions...)
|
|
newLogger := zapr.NewLogger(zapLogger)
|
|
return newLogger, zapLogger.Sync, nil
|
|
}
|
|
|
|
// getZapLogger is a helper function that gets the underlying zap logger from a
|
|
// logr.Logger interface.
|
|
func getZapLogger(l logr.Logger) (*zap.Logger, error) {
|
|
if u, ok := l.GetSink().(zapr.Underlier); ok {
|
|
return u.GetUnderlying(), nil
|
|
}
|
|
return nil, errors.New("not a zapr logger")
|
|
}
|
|
|
|
// WithLevel sets the sink's level to a static level. This option prevents
|
|
// changing the log level for this sink later on.
|
|
func WithLevel(level int8) SinkOption {
|
|
return WithLeveler(
|
|
// Zap's levels get more verbose as the number gets smaller, as explained
|
|
// by zapr here: https://github.com/go-logr/zapr#increasing-verbosity
|
|
// For example setting the level to -2 below, means log.V(2) will be enabled.
|
|
zap.NewAtomicLevelAt(zapcore.Level(-level)),
|
|
)
|
|
}
|
|
|
|
// WithLeveler sets the sink's level enabler to leveler.
|
|
func WithLeveler(leveler levelSetter) SinkOption {
|
|
return func(conf *sinkConfig) {
|
|
conf.level = leveler
|
|
}
|
|
}
|
|
|
|
// WithGlobalRedaction adds values to be redacted from logs.
|
|
func WithGlobalRedaction() SinkOption {
|
|
return func(conf *sinkConfig) {
|
|
conf.redactor = globalRedactor
|
|
}
|
|
}
|
|
|
|
// WithSuppressCaller prevents the sink being configured from logging any caller information, irrespective of any other
|
|
// logger settings.
|
|
func WithSuppressCaller() SinkOption {
|
|
return func(conf *sinkConfig) {
|
|
conf.suppressCaller = true
|
|
}
|
|
}
|
|
|
|
// ToLogger converts the logr.Logger into a legacy *log.Logger.
|
|
func ToLogger(l logr.Logger) *log.Logger {
|
|
return slog.NewLogLogger(logr.ToSlogHandler(l), slog.LevelInfo)
|
|
}
|
|
|
|
// ToSlogger converts the logr.Logger into a *slog.Logger.
|
|
func ToSlogger(l logr.Logger) *slog.Logger {
|
|
return slog.New(logr.ToSlogHandler(l))
|
|
}
|
|
|
|
// newCore is a helper function that creates a default sinkConfig,
|
|
// applies the options, then creates a zapcore.Core.
|
|
func newCore(
|
|
defaultEncoder zapcore.Encoder,
|
|
defaultSink zapcore.WriteSyncer,
|
|
defaultLevel levelSetter,
|
|
opts ...SinkOption,
|
|
) zapcore.Core {
|
|
conf := sinkConfig{
|
|
encoder: defaultEncoder,
|
|
sink: defaultSink,
|
|
level: defaultLevel,
|
|
}
|
|
for _, f := range opts {
|
|
f(&conf)
|
|
}
|
|
core := zapcore.NewCore(
|
|
conf.encoder,
|
|
conf.sink,
|
|
conf.level,
|
|
)
|
|
|
|
if conf.redactor != nil {
|
|
core = NewRedactionCore(core, conf.redactor)
|
|
}
|
|
|
|
if conf.suppressCaller {
|
|
core = &suppressCallerCore{Core: core}
|
|
}
|
|
|
|
return core
|
|
}
|