router/pkg/logging/logging.go (150 lines of code) (raw):
package logging
import (
"fmt"
"math"
"os"
"strings"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
const (
requestIDField = "request_id"
traceIDField = "trace_id"
)
type RequestIDKey struct{}
func New(pretty bool, development bool, level zapcore.Level) *zap.Logger {
return NewZapLogger(zapcore.AddSync(os.Stdout), pretty, development, level)
}
func zapBaseEncoderConfig() zapcore.EncoderConfig {
ec := zap.NewProductionEncoderConfig()
ec.EncodeDuration = zapcore.SecondsDurationEncoder
ec.TimeKey = "time"
return ec
}
func ZapJsonEncoder() zapcore.Encoder {
ec := zapBaseEncoderConfig()
ec.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
nanos := t.UnixNano()
millis := int64(math.Trunc(float64(nanos) / float64(time.Millisecond)))
enc.AppendInt64(millis)
}
return zapcore.NewJSONEncoder(ec)
}
func zapConsoleEncoder() zapcore.Encoder {
ec := zapBaseEncoderConfig()
ec.ConsoleSeparator = " "
ec.EncodeTime = zapcore.TimeEncoderOfLayout("15:04:05 PM")
ec.EncodeLevel = zapcore.CapitalColorLevelEncoder
return zapcore.NewConsoleEncoder(ec)
}
func attachBaseFields(logger *zap.Logger) *zap.Logger {
host, err := os.Hostname()
if err != nil {
host = "unknown"
}
logger = logger.With(
zap.String("hostname", host),
zap.Int("pid", os.Getpid()),
)
return logger
}
func defaultZapCoreOptions(development bool) []zap.Option {
var zapOpts []zap.Option
if development {
zapOpts = append(zapOpts, zap.AddCaller(), zap.Development())
}
// Stacktrace is included on logs of ErrorLevel and above.
zapOpts = append(zapOpts,
zap.AddStacktrace(zap.ErrorLevel),
)
return zapOpts
}
func NewZapLoggerWithCore(core zapcore.Core, development bool) *zap.Logger {
zapLogger := zap.New(core, defaultZapCoreOptions(development)...)
zapLogger = attachBaseFields(zapLogger)
return zapLogger
}
func NewZapLogger(syncer zapcore.WriteSyncer, pretty, development bool, level zapcore.Level) *zap.Logger {
var encoder zapcore.Encoder
if pretty {
encoder = zapConsoleEncoder()
} else {
encoder = ZapJsonEncoder()
}
c := zapcore.NewCore(
encoder,
syncer,
level,
)
zapLogger := zap.New(c, defaultZapCoreOptions(development)...)
zapLogger = attachBaseFields(zapLogger)
return zapLogger
}
func NewZapAccessLogger(syncer zapcore.WriteSyncer, development, pretty bool) *zap.Logger {
var encoder zapcore.Encoder
if pretty {
encoder = zapConsoleEncoder()
} else {
encoder = ZapJsonEncoder()
}
c := zapcore.NewCore(
encoder,
syncer,
zapcore.InfoLevel,
)
zapLogger := zap.New(c, defaultZapCoreOptions(development)...)
zapLogger = attachBaseFields(zapLogger)
return zapLogger
}
type BufferedLogger struct {
Logger *zap.Logger
bufferedWriteSyncer *zapcore.BufferedWriteSyncer
}
type BufferedLoggerOptions struct {
WS *os.File
BufferSize int
FlushInterval time.Duration
Development bool
Level zapcore.Level
Pretty bool
}
func NewJSONZapBufferedLogger(options BufferedLoggerOptions) (*BufferedLogger, error) {
fl := &BufferedLogger{}
fl.bufferedWriteSyncer = &zapcore.BufferedWriteSyncer{
WS: options.WS,
Size: options.BufferSize,
FlushInterval: options.FlushInterval,
}
fl.Logger = NewZapAccessLogger(fl.bufferedWriteSyncer, options.Development, options.Pretty)
return fl, nil
}
func (f *BufferedLogger) Close() error {
return f.bufferedWriteSyncer.Stop()
}
func NewLogFile(path string) (*os.File, error) {
return os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
}
func ZapLogLevelFromString(logLevel string) (zapcore.Level, error) {
switch strings.ToUpper(logLevel) {
case "DEBUG":
return zap.DebugLevel, nil
case "INFO":
return zap.InfoLevel, nil
case "WARNING":
return zap.WarnLevel, nil
case "ERROR":
return zap.ErrorLevel, nil
case "FATAL":
return zap.FatalLevel, nil
case "PANIC":
return zap.PanicLevel, nil
default:
return -1, fmt.Errorf("unknown log level: %s", logLevel)
}
}
func WithRequestID(reqID string) zap.Field {
return zap.String(requestIDField, reqID)
}
func WithTraceID(traceId string) zap.Field {
return zap.String(traceIDField, traceId)
}