mirror of
https://github.com/trufflesecurity/trufflehog.git
synced 2024-11-14 00:47:21 +00:00
f3367d7910
* [THOG-643] Implement independent log level controls There are two log level controls to mentally distinguish. Log levels associated with a sink (e.g. stdout and streamed), and log levels associated with a logger (e.g. a GitHub source). The level is determined to be the minimum of the two. If a sink is at level 0, then it will only output level 0 logs regardless of the logger's level. This is best demonstrated by TestSinkWithName. * Rename WithName to WithNamedLevel * Check flush errors * Replace IncreaseLevelCore with custom LevelCore Adding a leveler that was less verbose would cause the initialization fail, and therefore not be added to the core. This check is only at the time of initialization. An alternative approach to creating our own core is to set the child log level equal to the parent, so initialization is guaranteed (with the added benefit of intuitive behavior). * Use controller if it exists, otherwise inherit parent's log level * Cleanup some tests
219 lines
5.9 KiB
Go
219 lines
5.9 KiB
Go
package log
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"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 logConfig struct {
|
|
core zapcore.Core
|
|
cleanup func() error
|
|
err error
|
|
}
|
|
|
|
// New creates a new log object with the provided configurations. 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, configs ...logConfig) (logr.Logger, func() error) {
|
|
var cores []zapcore.Core
|
|
var cleanupFuncs []func() error
|
|
|
|
// create cores for the logger
|
|
for _, config := range configs {
|
|
if config.err != nil {
|
|
continue
|
|
}
|
|
cores = append(cores, config.core)
|
|
if config.cleanup != nil {
|
|
cleanupFuncs = append(cleanupFuncs, config.cleanup)
|
|
}
|
|
}
|
|
// create logger
|
|
zapLogger := zap.New(zapcore.NewTee(cores...))
|
|
cleanupFuncs = append(cleanupFuncs, zapLogger.Sync)
|
|
logger := zapr.NewLogger(zapLogger).WithName(service)
|
|
|
|
// report the errors we encountered in the configs
|
|
for _, config := range configs {
|
|
if config.err != nil {
|
|
logger.Error(config.err, "error configuring logger")
|
|
}
|
|
}
|
|
|
|
return logger, firstErrorFunc(cleanupFuncs...)
|
|
}
|
|
|
|
// WithSentry adds sentry integration to the logger. This configuration may
|
|
// fail, in which case, sentry will not be added and execution will continue
|
|
// normally.
|
|
func WithSentry(opts sentry.ClientOptions, tags map[string]string) logConfig {
|
|
client, err := sentry.NewClient(opts)
|
|
if err != nil {
|
|
return logConfig{err: err}
|
|
}
|
|
|
|
// 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 {
|
|
return logConfig{err: err}
|
|
}
|
|
|
|
return logConfig{
|
|
core: core,
|
|
cleanup: func() error {
|
|
sentry.Flush(5 * time.Second)
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
type sinkConfig struct {
|
|
encoder zapcore.Encoder
|
|
sink zapcore.WriteSyncer
|
|
level levelSetter
|
|
}
|
|
|
|
// WithJSONSink adds a JSON encoded output to the logger.
|
|
func WithJSONSink(sink io.Writer, opts ...func(*sinkConfig)) logConfig {
|
|
return newCoreConfig(
|
|
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 ...func(*sinkConfig)) logConfig {
|
|
return newCoreConfig(
|
|
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
|
|
}
|
|
|
|
// WithCore adds any user supplied zap core to the logger.
|
|
func WithCore(core zapcore.Core) logConfig {
|
|
return logConfig{core: core}
|
|
}
|
|
|
|
// AddSentry initializes a sentry client and extends an existing
|
|
// logr.Logger with the hook.
|
|
func AddSentry(l logr.Logger, opts sentry.ClientOptions, tags map[string]string) (logr.Logger, func() error, error) {
|
|
return AddSink(l, WithSentry(opts, tags))
|
|
}
|
|
|
|
// AddSink extends an existing logr.Logger with a new sink. It returns the new
|
|
// logr.Logger, a cleanup function, and an error.
|
|
func AddSink(l logr.Logger, sink logConfig) (logr.Logger, func() error, error) {
|
|
if sink.err != nil {
|
|
return l, nil, sink.err
|
|
}
|
|
zapLogger, err := getZapLogger(l)
|
|
if err != nil {
|
|
return l, nil, errors.New("unsupported logr implementation")
|
|
}
|
|
zapLogger = zapLogger.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core {
|
|
return zapcore.NewTee(core, sink.core)
|
|
}))
|
|
return zapr.NewLogger(zapLogger), firstErrorFunc(zapLogger.Sync, sink.cleanup), 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) func(*sinkConfig) {
|
|
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) func(*sinkConfig) {
|
|
return func(conf *sinkConfig) {
|
|
conf.level = leveler
|
|
}
|
|
}
|
|
|
|
// firstErrorFunc is a helper function that returns a function that executes
|
|
// all provided args and returns the first error, if any.
|
|
func firstErrorFunc(fs ...func() error) func() error {
|
|
return func() error {
|
|
var firstErr error = nil
|
|
for _, f := range fs {
|
|
if f == nil {
|
|
continue
|
|
}
|
|
if err := f(); err != nil && firstErr == nil {
|
|
firstErr = err
|
|
}
|
|
}
|
|
return firstErr
|
|
}
|
|
}
|
|
|
|
// newCoreConfig is a helper function that creates a default sinkConfig,
|
|
// applies the options, then creates a zapcore.Core.
|
|
func newCoreConfig(
|
|
defaultEncoder zapcore.Encoder,
|
|
defaultSink zapcore.WriteSyncer,
|
|
defaultLevel levelSetter,
|
|
opts ...func(*sinkConfig),
|
|
) logConfig {
|
|
conf := sinkConfig{
|
|
encoder: defaultEncoder,
|
|
sink: defaultSink,
|
|
level: defaultLevel,
|
|
}
|
|
for _, f := range opts {
|
|
f(&conf)
|
|
}
|
|
return logConfig{
|
|
core: zapcore.NewCore(
|
|
conf.encoder,
|
|
conf.sink,
|
|
conf.level,
|
|
),
|
|
}
|
|
}
|