diff options
Diffstat (limited to 'pkg/logging/logging.go')
-rw-r--r-- | pkg/logging/logging.go | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/pkg/logging/logging.go b/pkg/logging/logging.go new file mode 100644 index 0000000..e310695 --- /dev/null +++ b/pkg/logging/logging.go @@ -0,0 +1,131 @@ +package logging + +import ( + "fmt" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "os" + "sync" + "time" +) + +const ( + CONSOLE = "console" + JOURNAL = "systemd-journald" +) + +// defaultEncConfig defines the default zapcore.EncoderConfig for the logging package. +var defaultEncConfig = zapcore.EncoderConfig{ + TimeKey: "ts", + LevelKey: "level", + NameKey: "logger", + CallerKey: "caller", + MessageKey: "msg", + StacktraceKey: "stacktrace", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.CapitalLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, +} + +// Options define child loggers with their desired log level. +type Options map[string]zapcore.Level + +// Logging implements access to a default logger and named child loggers. +// Log levels can be configured per named child via Options which, if not configured, +// fall back on a default log level. +// Logs either to the console or to systemd-journald. +type Logging struct { + logger *Logger + output string + verbosity zap.AtomicLevel + interval time.Duration + + // coreFactory creates zapcore.Core based on the log level and the log output. + coreFactory func(zap.AtomicLevel) zapcore.Core + + mu sync.Mutex + loggers map[string]*Logger + + options Options +} + +// NewLogging takes the name and log level for the default logger, +// output where log messages are written to, +// options having log levels for named child loggers +// and returns a new Logging. +func NewLogging(name string, level zapcore.Level, output string, options Options, interval time.Duration) (*Logging, error) { + verbosity := zap.NewAtomicLevelAt(level) + + var coreFactory func(zap.AtomicLevel) zapcore.Core + switch output { + case CONSOLE: + enc := zapcore.NewConsoleEncoder(defaultEncConfig) + ws := zapcore.Lock(os.Stderr) + coreFactory = func(verbosity zap.AtomicLevel) zapcore.Core { + return zapcore.NewCore(enc, ws, verbosity) + } + case JOURNAL: + coreFactory = func(verbosity zap.AtomicLevel) zapcore.Core { + return NewJournaldCore(name, verbosity) + } + default: + return nil, invalidOutput(output) + } + + logger := NewLogger(zap.New(coreFactory(verbosity)).Named(name).Sugar(), interval) + + return &Logging{ + logger: logger, + output: output, + verbosity: verbosity, + interval: interval, + coreFactory: coreFactory, + loggers: make(map[string]*Logger), + options: options, + }, + nil +} + +// GetChildLogger returns a named child logger. +// Log levels for named child loggers are obtained from the logging options and, if not found, +// set to the default log level. +func (l *Logging) GetChildLogger(name string) *Logger { + l.mu.Lock() + defer l.mu.Unlock() + + if logger, ok := l.loggers[name]; ok { + return logger + } + + var verbosity zap.AtomicLevel + if level, found := l.options[name]; found { + verbosity = zap.NewAtomicLevelAt(level) + } else { + verbosity = l.verbosity + } + + logger := NewLogger(zap.New(l.coreFactory(verbosity)).Named(name).Sugar(), l.interval) + l.loggers[name] = logger + + return logger +} + +// GetLogger returns the default logger. +func (l *Logging) GetLogger() *Logger { + return l.logger +} + +// AssertOutput returns an error if output is not a valid logger output. +func AssertOutput(o string) error { + if o == CONSOLE || o == JOURNAL { + return nil + } + + return invalidOutput(o) +} + +func invalidOutput(o string) error { + return fmt.Errorf("%s is not a valid logger output. Must be either %q or %q", o, CONSOLE, JOURNAL) +} |