Created
July 4, 2018 14:14
-
-
Save panta/ec6efc8909feec5c6eca0d9f61d07e3b to your computer and use it in GitHub Desktop.
zap logging to console (colors) and file with rotation (lumberjack)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package logging | |
import ( | |
"go.uber.org/zap/zapcore" | |
"go.uber.org/zap" | |
//"github.com/natefinch/lumberjack" | |
"gopkg.in/natefinch/lumberjack.v2" | |
"os" | |
"path" | |
"strings" | |
) | |
// Configuration for logging | |
type Config struct { | |
ConsoleEnabled bool | |
ConsoleLevel string | |
ConsoleJson bool | |
FileEnabled bool | |
FileLevel string | |
FileJson bool | |
// Directory to log to to when filelogging is enabled | |
Directory string | |
// Filename is the name of the logfile which will be placed inside the directory | |
Filename string | |
// MaxSize the max size in MB of the logfile before it's rolled | |
MaxSize int | |
// MaxBackups the max number of rolled files to keep | |
MaxBackups int | |
// MaxAge the max age in days to keep a logfile | |
MaxAge int | |
} | |
type LoggerInterface interface { | |
Debugt(msg string, fields ...zapcore.Field) | |
Debugf(template string, args ...interface{}) | |
Debugw(msg string, keysAndValues ...interface{}) | |
Debug(msg string, keysAndValues ...interface{}) | |
Debugs(args ...interface{}) | |
Infot(msg string, fields ...zapcore.Field) | |
Infof(template string, args ...interface{}) | |
Infow(msg string, keysAndValues ...interface{}) | |
Info(msg string, keysAndValues ...interface{}) | |
Infos(args ...interface{}) | |
Warnt(msg string, fields ...zapcore.Field) | |
Warnf(template string, args ...interface{}) | |
Warnw(msg string, keysAndValues ...interface{}) | |
Warn(msg string, keysAndValues ...interface{}) | |
Warns(args ...interface{}) | |
Errort(msg string, fields ...zapcore.Field) | |
Errorf(template string, args ...interface{}) | |
Errorw(msg string, keysAndValues ...interface{}) | |
Error(msg string, keysAndValues ...interface{}) | |
Errors(args ...interface{}) | |
Panict(msg string, fields ...zapcore.Field) | |
Panicf(template string, args ...interface{}) | |
Panicw(msg string, keysAndValues ...interface{}) | |
Panic(msg string, keysAndValues ...interface{}) | |
Panics(args ...interface{}) | |
Fatalt(msg string, fields ...zapcore.Field) | |
Fatalf(template string, args ...interface{}) | |
Fatalw(msg string, keysAndValues ...interface{}) | |
Fatal(msg string, keysAndValues ...interface{}) | |
Fatals(args ...interface{}) | |
AtLevel(level zapcore.Level, msg string, fields ...zapcore.Field) *Logger | |
} | |
type Logger struct { | |
Unsugared *zap.Logger | |
*zap.SugaredLogger | |
} | |
func (logger *Logger) AtLevel(level zapcore.Level, msg string, fields ...zapcore.Field) *Logger { | |
switch level { | |
case zapcore.DebugLevel: | |
logger.Unsugared.Debug(msg, fields...) | |
case zapcore.PanicLevel: | |
logger.Unsugared.Panic(msg, fields...) | |
case zapcore.ErrorLevel: | |
logger.Unsugared.Error(msg, fields...) | |
case zapcore.WarnLevel: | |
logger.Unsugared.Warn(msg, fields...) | |
case zapcore.InfoLevel: | |
logger.Unsugared.Info(msg, fields...) | |
case zapcore.FatalLevel: | |
logger.Unsugared.Fatal(msg, fields...) | |
default: | |
logger.Unsugared.Warn("Logging at unknown level", zap.Any("level", level)) | |
logger.Unsugared.Warn(msg, fields...) | |
} | |
return logger | |
} | |
// Use zap.String(key, value), zap.Int(key, value) to log fields. These fields | |
// will be marshalled as JSON in the logfile and key value pairs in the console! | |
func (logger *Logger) Debugt(msg string, fields ...zapcore.Field) { | |
logger.Unsugared.Debug(msg, fields...) | |
} | |
func (logger *Logger) Debug(msg string, keysAndValues ...interface{}) { | |
logger.SugaredLogger.Debugw(msg, keysAndValues...) | |
} | |
func (logger *Logger) Debugs(args ...interface{}) { | |
logger.SugaredLogger.Debug(args...) | |
} | |
// Use zap.String(key, value), zap.Int(key, value) to log fields. These fields | |
// will be marshalled as JSON in the logfile and key value pairs in the console! | |
func (logger *Logger) Infot(msg string, fields ...zapcore.Field) { | |
logger.Unsugared.Info(msg, fields...) | |
} | |
func (logger *Logger) Info(msg string, keysAndValues ...interface{}) { | |
logger.SugaredLogger.Infow(msg, keysAndValues...) | |
} | |
func (logger *Logger) Infos(args ...interface{}) { | |
logger.SugaredLogger.Info(args...) | |
} | |
// Use zap.String(key, value), zap.Int(key, value) to log fields. These fields | |
// will be marshalled as JSON in the logfile and key value pairs in the console! | |
func (logger *Logger) Warnt(msg string, fields ...zapcore.Field) { | |
logger.Unsugared.Warn(msg, fields...) | |
} | |
func (logger *Logger) Warn(msg string, keysAndValues ...interface{}) { | |
logger.SugaredLogger.Warnw(msg, keysAndValues...) | |
} | |
func (logger *Logger) Warns(args ...interface{}) { | |
logger.SugaredLogger.Warn(args...) | |
} | |
// Use zap.String(key, value), zap.Int(key, value) to log fields. These fields | |
// will be marshalled as JSON in the logfile and key value pairs in the console! | |
func (logger *Logger) Errort(msg string, fields ...zapcore.Field) { | |
logger.Unsugared.Error(msg, fields...) | |
} | |
func (logger *Logger) Error(msg string, keysAndValues ...interface{}) { | |
logger.SugaredLogger.Errorw(msg, keysAndValues...) | |
} | |
func (logger *Logger) Errors(args ...interface{}) { | |
logger.SugaredLogger.Error(args...) | |
} | |
// Use zap.String(key, value), zap.Int(key, value) to log fields. These fields | |
// will be marshalled as JSON in the logfile and key value pairs in the console! | |
func (logger *Logger) Panict(msg string, fields ...zapcore.Field) { | |
logger.Unsugared.Panic(msg, fields...) | |
} | |
func (logger *Logger) Panic(msg string, keysAndValues ...interface{}) { | |
logger.SugaredLogger.Panicw(msg, keysAndValues...) | |
} | |
func (logger *Logger) Panics(args ...interface{}) { | |
logger.SugaredLogger.Panic(args...) | |
} | |
// Use zap.String(key, value), zap.Int(key, value) to log fields. These fields | |
// will be marshalled as JSON in the logfile and key value pairs in the console! | |
func (logger *Logger) Fatalt(msg string, fields ...zapcore.Field) { | |
logger.Unsugared.Fatal(msg, fields...) | |
} | |
func (logger *Logger) Fatal(msg string, keysAndValues ...interface{}) { | |
logger.SugaredLogger.Fatalw(msg, keysAndValues...) | |
} | |
func (logger *Logger) Fatals(args ...interface{}) { | |
logger.SugaredLogger.Fatal(args...) | |
} | |
// How to log, by example: | |
// logger.Infot("Importing new file", zap.String("source", filename), zap.Int("size", 1024)) | |
// logger.Info("Importing new file", "source", filename, "size", 1024) | |
// To log a stacktrace: | |
// logger.Errort("It went wrong, zap.Stack()) | |
// DefaultZapLogger is the default logger instance that should be used to log | |
// It's assigned a default value here for tests (which do not call log.Configure()) | |
var DefaultZapLogger *Logger | |
func init() { | |
DefaultZapLogger = newZapLogger(Config{ | |
ConsoleEnabled: true, | |
ConsoleLevel: "DEBUG", | |
ConsoleJson: false, | |
FileEnabled: false, | |
FileJson: true, | |
}) | |
} | |
func Debugt(msg string, fields ...zapcore.Field) { | |
DefaultZapLogger.Debugt(msg, fields...) | |
} | |
func Debugf(template string, args ...interface{}) { | |
DefaultZapLogger.Debugf(template, args...) | |
} | |
func Debugw(msg string, keysAndValues ...interface{}) { | |
DefaultZapLogger.Debugw(msg, keysAndValues...) | |
} | |
func Debug(msg string, keysAndValues ...interface{}) { | |
DefaultZapLogger.Debug(msg, keysAndValues...) | |
} | |
func Debugs(args ...interface{}) { | |
DefaultZapLogger.Debugs(args...) | |
} | |
func Infot(msg string, fields ...zapcore.Field) { | |
DefaultZapLogger.Infot(msg, fields...) | |
} | |
func Infof(template string, args ...interface{}) { | |
DefaultZapLogger.Infof(template, args...) | |
} | |
func Infow(msg string, keysAndValues ...interface{}) { | |
DefaultZapLogger.Infow(msg, keysAndValues...) | |
} | |
func Info(msg string, keysAndValues ...interface{}) { | |
DefaultZapLogger.Info(msg, keysAndValues...) | |
} | |
func Infos(args ...interface{}) { | |
DefaultZapLogger.Infos(args...) | |
} | |
func Warnt(msg string, fields ...zapcore.Field) { | |
DefaultZapLogger.Warnt(msg, fields...) | |
} | |
func Warnf(template string, args ...interface{}) { | |
DefaultZapLogger.Warnf(template, args...) | |
} | |
func Warnw(msg string, keysAndValues ...interface{}) { | |
DefaultZapLogger.Warnw(msg, keysAndValues...) | |
} | |
func Warn(msg string, keysAndValues ...interface{}) { | |
DefaultZapLogger.Warn(msg, keysAndValues...) | |
} | |
func Warns(args ...interface{}) { | |
DefaultZapLogger.Warns(args...) | |
} | |
func Errort(msg string, fields ...zapcore.Field) { | |
DefaultZapLogger.Errort(msg, fields...) | |
} | |
func Errorf(template string, args ...interface{}) { | |
DefaultZapLogger.Errorf(template, args...) | |
} | |
func Errorw(msg string, keysAndValues ...interface{}) { | |
DefaultZapLogger.Errorw(msg, keysAndValues...) | |
} | |
func Error(msg string, keysAndValues ...interface{}) { | |
DefaultZapLogger.Error(msg, keysAndValues...) | |
} | |
func Errors(args ...interface{}) { | |
DefaultZapLogger.Errors(args...) | |
} | |
func Panict(msg string, fields ...zapcore.Field) { | |
DefaultZapLogger.Panict(msg, fields...) | |
} | |
func Panicf(template string, args ...interface{}) { | |
DefaultZapLogger.Panicf(template, args...) | |
} | |
func Panicw(msg string, keysAndValues ...interface{}) { | |
DefaultZapLogger.Panicw(msg, keysAndValues...) | |
} | |
func Panic(msg string, keysAndValues ...interface{}) { | |
DefaultZapLogger.Panic(msg, keysAndValues...) | |
} | |
func Panics(args ...interface{}) { | |
DefaultZapLogger.Panics(args...) | |
} | |
func Fatalt(msg string, fields ...zapcore.Field) { | |
DefaultZapLogger.Fatalt(msg, fields...) | |
} | |
func Fatalf(template string, args ...interface{}) { | |
DefaultZapLogger.Fatalf(template, args...) | |
} | |
func Fatalw(msg string, keysAndValues ...interface{}) { | |
DefaultZapLogger.Fatalw(msg, keysAndValues...) | |
} | |
func Fatal(msg string, keysAndValues ...interface{}) { | |
DefaultZapLogger.Fatal(msg, keysAndValues...) | |
} | |
func Fatals(args ...interface{}) { | |
DefaultZapLogger.Fatals(args...) | |
} | |
// AtLevel logs the message at a specific log level | |
func AtLevel(level zapcore.Level, msg string, fields ...zapcore.Field) { | |
switch level { | |
case zapcore.DebugLevel: | |
Debugt(msg, fields...) | |
case zapcore.PanicLevel: | |
Panict(msg, fields...) | |
case zapcore.ErrorLevel: | |
Errort(msg, fields...) | |
case zapcore.WarnLevel: | |
Warnt(msg, fields...) | |
case zapcore.InfoLevel: | |
Infot(msg, fields...) | |
case zapcore.FatalLevel: | |
Fatalt(msg, fields...) | |
default: | |
Warnt("Logging at unknown level", zap.Any("level", level)) | |
Warnt(msg, fields...) | |
} | |
} | |
// Configure sets up the logging framework | |
// | |
// In production, the container logs will be collected and file logging should be disabled. However, | |
// during development it's nicer to see logs as text and optionally write to a file when debugging | |
// problems in the containerized pipeline | |
// | |
// The output log file will be located at /var/log/service-xyz/service-xyz.log and | |
// will be rolled according to configuration set. | |
func Configure(config Config) *Logger { | |
logger := newZapLogger(config) | |
logger.Infot("logging configured", | |
zap.Bool("consoleEnabled", config.ConsoleEnabled), | |
zap.String("consoleLevel", config.ConsoleLevel), | |
zap.Bool("consoleJson", config.ConsoleJson), | |
zap.Bool("fileEnabled", config.FileEnabled), | |
zap.String("fileLevel", config.FileLevel), | |
zap.Bool("fileJson", config.FileJson), | |
zap.String("logDirectory", config.Directory), | |
zap.String("fileName", config.Filename), | |
zap.Int("maxSizeMB", config.MaxSize), | |
zap.Int("maxBackups", config.MaxBackups), | |
zap.Int("maxAgeInDays", config.MaxAge)) | |
DefaultZapLogger = logger | |
zap.RedirectStdLog(DefaultZapLogger.Unsugared) | |
return logger | |
} | |
func newRollingFile(config Config) zapcore.WriteSyncer { | |
if err := os.MkdirAll(config.Directory, 0744); err != nil { | |
Error("can't create log directory", zap.Error(err), zap.String("path", config.Directory)) | |
return nil | |
} | |
// lumberjack.Logger is already safe for concurrent use, so we don't need to | |
// lock it. | |
return zapcore.AddSync(&lumberjack.Logger{ | |
Filename: path.Join(config.Directory, config.Filename), | |
MaxSize: config.MaxSize, // megabytes | |
MaxAge: config.MaxAge, // days | |
MaxBackups: config.MaxBackups, // files | |
}) | |
} | |
func newZapLogger(config Config) *Logger { | |
var consoleLevel zapcore.Level | |
consoleLevel.Set(strings.ToLower(config.ConsoleLevel)) | |
var fileLevel zapcore.Level | |
fileLevel.Set(strings.ToLower(config.FileLevel)) | |
consoleEncCfg := zapcore.EncoderConfig{ | |
TimeKey: "@timestamp", | |
LevelKey: "level", | |
NameKey: "logger", | |
CallerKey: "caller", | |
MessageKey: "msg", | |
StacktraceKey: "stacktrace", | |
EncodeLevel: zapcore.CapitalColorLevelEncoder, | |
EncodeTime: zapcore.ISO8601TimeEncoder, | |
EncodeDuration: zapcore.NanosDurationEncoder, | |
} | |
jsonEncCfg := zapcore.EncoderConfig{ | |
TimeKey: "@timestamp", | |
LevelKey: "level", | |
NameKey: "logger", | |
CallerKey: "caller", | |
MessageKey: "msg", | |
StacktraceKey: "stacktrace", | |
EncodeLevel: zapcore.LowercaseLevelEncoder, | |
EncodeTime: zapcore.ISO8601TimeEncoder, | |
EncodeDuration: zapcore.NanosDurationEncoder, | |
} | |
consoleLevelEnabler := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { | |
return lvl >= consoleLevel | |
}) | |
fileLevelEnabler := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { | |
return lvl >= fileLevel | |
}) | |
consoleEncoder := zapcore.NewConsoleEncoder(consoleEncCfg) | |
fileEncoder := zapcore.NewJSONEncoder(jsonEncCfg) | |
var cores []zapcore.Core | |
if config.ConsoleEnabled { | |
cores = append(cores, zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stderr), consoleLevelEnabler)) | |
} | |
if config.FileEnabled { | |
cores = append(cores, zapcore.NewCore(fileEncoder, newRollingFile(config), fileLevelEnabler)) | |
} | |
core := zapcore.NewTee(cores...) | |
unsugared := zap.New(core) | |
return &Logger{ | |
Unsugared: unsugared, | |
SugaredLogger: unsugared.Sugar(), | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment