package logger import ( "fmt" "log" "os" "path" "path/filepath" "runtime" "strings" "time" "github.com/getsentry/raven-go" "github.com/tchap/zapext/zapsentry" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" ) var zapLogger *zap.Logger func Named(name string) *zap.Logger { return zapLogger.Named(name) } func With(fields ...zap.Field) *zap.Logger { return zapLogger.With(fields...) } func LogPanic(msg string) { if r := recover(); r != nil { buf := make([]byte, 10240) n := runtime.Stack(buf, false) err := fmt.Sprintf("%v\n\n%s", r, buf[:n]) Error(msg, zap.String("error", err)) time.Sleep(5 * time.Second) // wait to write logs and sent to sentry } } func Fatal(msg string, fields ...zap.Field) { zapLogger.Fatal(msg, fields...) } func Error(msg string, fields ...zap.Field) { zapLogger.Error(msg, fields...) } func Warn(msg string, fields ...zap.Field) { zapLogger.Warn(msg, fields...) } func Info(msg string, fields ...zap.Field) { zapLogger.Info(msg, fields...) } func Debug(msg string, fields ...zap.Field) { zapLogger.Debug(msg, fields...) } func Sync() error { return zapLogger.Sync() } func buildErrorsFilename(logFileName string) string { ext := path.Ext(logFileName) errFileName := strings.TrimSuffix(path.Base(logFileName), ext) errFileName += "-errors" + ext return filepath.Join(path.Dir(logFileName), errFileName) } func Init(conf *Configuration) { cfg := zap.NewProductionEncoderConfig() cfg.EncodeTime = zapcore.RFC3339NanoTimeEncoder cores := []zapcore.Core{ zapcore.NewCore(zapcore.NewJSONEncoder(cfg), zapcore.Lock(os.Stdout), loggingLevelGTE(conf.Stdout.Level)), } // file logger if conf.File.Enabled { loggerConf := conf.File fileLevelEnabler := loggingLevelGTE(loggerConf.Level) writer := zapcore.AddSync( &lumberjack.Logger{ Filename: loggerConf.Filename, LocalTime: loggerConf.LocalTime, MaxSize: loggerConf.MaxSize, MaxBackups: loggerConf.MaxBackups, }, ) cores = append(cores, zapcore.NewCore(zapcore.NewJSONEncoder(cfg), writer, fileLevelEnabler)) redirectStderrToFile(conf) } // sentry if conf.Sentry.DSN != "" { loggerConf := conf.Sentry tags := map[string]string{ "env": os.Getenv("ENVIRONMENT"), } client, err := raven.NewWithTags(loggerConf.DSN, tags) if err != nil { log.Fatal("failed to get a Sentry client", err) } sentryLevelEnabler := loggingLevelGTE(loggerConf.Level) cores = append(cores, zapsentry.NewCore(sentryLevelEnabler, client)) } core := zapcore.NewTee(cores...) logger := zap.New(core) defer func() { _ = logger.Sync() }() SetLogger(logger) } type levelComparator func(msgLevel zapcore.Level, loggingLevel zapcore.Level) bool func loggingLevelGTE(level string) zap.LevelEnablerFunc { return loggingLevelEnabledFunc( level, func(msgLevel zapcore.Level, loggingLevel zapcore.Level) bool { return msgLevel >= loggingLevel }, ) } func loggingLevelEnabledFunc(level string, comparator levelComparator) zap.LevelEnablerFunc { var loggingLevel zapcore.Level if err := loggingLevel.UnmarshalText([]byte(level)); err != nil { log.Fatal("Fail parse console log level", err) } return func(msgLevel zapcore.Level) bool { return comparator(msgLevel, loggingLevel) } } func SetLogger(logger *zap.Logger) { zapLogger = logger }