pkg/logger/logger.go (131 lines of code) (raw):
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
// Package logger implements a logging system with a module tag.
// The module tag represents a scope where the log event is emitted.
package logger
import (
"context"
"fmt"
"strings"
"time"
"github.com/rs/zerolog"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// ContextKey is the key to store Logger in the context.
var ContextKey = contextKey{}
type contextKey struct{}
// Logging is the config info.
type Logging struct {
Env string
Level string
Modules []string
Levels []string
}
// Logger is wrapper for rs/zerolog logger with module, it is singleton.
type Logger struct {
*zerolog.Logger
modules map[string]zerolog.Level
module string
development bool
isDefaultLevel bool
}
// Module returns logger's module name.
func (l Logger) Module() string {
return l.module
}
// Named creates a new Logger and assigns a module to it.
func (l *Logger) Named(name ...string) *Logger {
var mm []string
if l.module == rootName {
mm = name
} else {
mm = append([]string{l.module}, name...)
}
var moduleBuilder strings.Builder
var module string
level := l.GetLevel()
isDefaultLevel := true
for i, m := range mm {
if i != 0 {
moduleBuilder.WriteString(".")
}
moduleBuilder.WriteString(strings.ToUpper(m))
module = moduleBuilder.String()
if ml, ok := l.modules[module]; ok {
level = ml
isDefaultLevel = false
break
}
}
subLogger := root.get().With().Str("module", moduleBuilder.String()).Logger().Level(level)
return &Logger{module: module, modules: l.modules, development: l.development, Logger: &subLogger, isDefaultLevel: isDefaultLevel}
}
// Sampled return a Logger with a sampler that will send every Nth events.
func (l *Logger) Sampled(n uint32) *Logger {
sampled := l.Logger.Sample(&zerolog.BasicSampler{N: n})
l.Logger = &sampled
return l
}
// ToZapConfig outputs the zap config is derived from l.
func (l *Logger) ToZapConfig() zap.Config {
level, err := zap.ParseAtomicLevel(l.GetLevel().String())
if err != nil {
panic(err)
}
if !l.development {
config := zap.NewProductionConfig()
switch l.GetLevel() {
case zerolog.DebugLevel:
config.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
case zerolog.InfoLevel:
config.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
case zerolog.WarnLevel:
config.Level = zap.NewAtomicLevelAt(zap.WarnLevel)
default:
config.Level = zap.NewAtomicLevelAt(zap.ErrorLevel)
}
return config
}
encoderConfig := zapcore.EncoderConfig{
// Keys can be anything except the empty string.
TimeKey: "T",
LevelKey: "L",
NameKey: "N",
CallerKey: "C",
FunctionKey: zapcore.OmitKey,
MessageKey: "M",
StacktraceKey: "S",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: func(l zapcore.Level, pae zapcore.PrimitiveArrayEncoder) {
pae.AppendString(strings.ToUpper(fmt.Sprintf("| %-6s|", l.String())))
},
EncodeTime: func(t time.Time, pae zapcore.PrimitiveArrayEncoder) {
pae.AppendString(t.Format(time.RFC3339))
},
EncodeDuration: zapcore.StringDurationEncoder,
EncodeCaller: zapcore.FullCallerEncoder,
ConsoleSeparator: " ",
}
return zap.Config{
Level: level,
Development: true,
Encoding: "console",
EncoderConfig: encoderConfig,
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}
}
// DefaultLevel sets the default level for the logger.
func (l *Logger) DefaultLevel(level zerolog.Level) *Logger {
if l.isDefaultLevel {
nl := l.Logger.Level(level)
l.Logger = &nl
}
return l
}
// Loggable indicates the implement supports logging.
type Loggable interface {
SetLogger(*Logger)
}
// Fetch gets a Logger in a context, then creates a new Logger based on it.
func Fetch(ctx context.Context, newModuleName string) *Logger {
return FetchOrDefault(ctx, newModuleName, nil)
}
// FetchOrDefault gets a Logger in a context, then creates a new Logger based on it
// If the context doesn't include a Logger. The default Logger will be picked.
func FetchOrDefault(ctx context.Context, newModuleName string, defaultLogger *Logger) *Logger {
parentLogger := ctx.Value(ContextKey)
if parentLogger != nil {
if pl, ok := parentLogger.(*Logger); ok {
return pl.Named(newModuleName)
}
}
if defaultLogger == nil {
return GetLogger(newModuleName)
}
return defaultLogger
}