github.com/rudderlabs/rudder-go-kit@v0.30.0/logger/factory.go (about) 1 package logger 2 3 import ( 4 "io" 5 "os" 6 "strings" 7 8 "go.uber.org/zap" 9 "go.uber.org/zap/zapcore" 10 "gopkg.in/natefinch/lumberjack.v2" 11 12 "github.com/rudderlabs/rudder-go-kit/config" 13 ) 14 15 // Default factory instance 16 var Default *Factory 17 18 func init() { 19 Default = NewFactory(config.Default) 20 } 21 22 // Reset resets the default logger factory. 23 // Shall only be used by tests, until we move to a proper DI framework 24 func Reset() { 25 Default = NewFactory(config.Default) 26 } 27 28 // NewFactory creates a new logger factory 29 func NewFactory(config *config.Config, options ...Option) *Factory { 30 f := &Factory{} 31 f.config = newConfig(config) 32 for _, option := range options { 33 option.apply(f) 34 } 35 f.zap = newZapLogger(config, f.config) 36 f.sugaredZap = f.zap.Sugar() 37 return f 38 } 39 40 // Factory is a factory for creating new loggers 41 type Factory struct { 42 config *factoryConfig 43 zap *zap.Logger 44 sugaredZap *zap.SugaredLogger 45 } 46 47 // NewLogger creates a new logger using the default logger factory 48 func NewLogger() Logger { 49 return Default.NewLogger() 50 } 51 52 // NewLogger creates a new logger 53 func (f *Factory) NewLogger() Logger { 54 return &logger{ 55 logConfig: f.config, 56 zap: f.zap, 57 sugaredZap: f.sugaredZap, 58 } 59 } 60 61 // GetLoggingConfig returns the log levels for default logger factory 62 func GetLoggingConfig() map[string]int { 63 return Default.GetLoggingConfig() 64 } 65 66 // GetLoggingConfig returns the log levels 67 func (f *Factory) GetLoggingConfig() map[string]int { 68 return f.config.levelConfigCache.m 69 } 70 71 // SetLogLevel sets the log level for a module for the default logger factory 72 func SetLogLevel(name, levelStr string) error { 73 return Default.SetLogLevel(name, levelStr) 74 } 75 76 // SetLogLevel sets the log level for a module 77 func (f *Factory) SetLogLevel(name, levelStr string) error { 78 err := f.config.SetLogLevel(name, levelStr) 79 if err != nil { 80 f.sugaredZap.Info(f.config.levelConfig) 81 } 82 return err 83 } 84 85 // Sync flushes the loggers' output buffers for the default logger factory 86 func Sync() { 87 Default.Sync() 88 } 89 90 // Sync flushes the loggers' output buffers 91 func (f *Factory) Sync() { 92 _ = f.zap.Sync() 93 _ = f.sugaredZap.Sync() 94 } 95 96 func newConfig(config *config.Config) *factoryConfig { 97 fc := &factoryConfig{ 98 levelConfig: &syncMap[string, int]{m: make(map[string]int)}, 99 levelConfigCache: &syncMap[string, int]{m: make(map[string]int)}, 100 } 101 fc.rootLevel = levelMap[config.GetString("LOG_LEVEL", "INFO")] 102 fc.enableNameInLog = config.GetBool("Logger.enableLoggerNameInLog", true) 103 fc.enableStackTrace = config.GetReloadableBoolVar(false, "Logger.enableStackTrace") 104 config.GetBool("Logger.enableLoggerNameInLog", true) 105 106 // colon separated key value pairs 107 // Example: "router.GA=DEBUG:warehouse.REDSHIFT=DEBUG" 108 levelConfigStr := strings.TrimSpace(config.GetString("Logger.moduleLevels", "")) 109 if levelConfigStr != "" { 110 moduleLevelKVs := strings.Split(levelConfigStr, ":") 111 for _, moduleLevelKV := range moduleLevelKVs { 112 pair := strings.SplitN(moduleLevelKV, "=", 2) 113 if len(pair) < 2 { 114 continue 115 } 116 module := strings.TrimSpace(pair[0]) 117 if module == "" { 118 continue 119 } 120 levelStr := strings.TrimSpace(pair[1]) 121 level, ok := levelMap[levelStr] 122 if !ok { 123 continue 124 } 125 fc.levelConfig.set(module, level) 126 } 127 } 128 return fc 129 } 130 131 // newZapLogger configures the zap logger based on the config provide in config.toml 132 func newZapLogger(config *config.Config, fc *factoryConfig) *zap.Logger { 133 var cores []zapcore.Core 134 if config.GetBool("Logger.enableConsole", true) { 135 var writeSyncer zapcore.WriteSyncer = os.Stdout 136 if config.GetBool("Logger.discardConsole", false) { 137 writeSyncer = &discarder{} 138 } 139 writer := zapcore.Lock(writeSyncer) 140 core := zapcore.NewCore(zapEncoder(config, config.GetBool("Logger.consoleJsonFormat", false)), writer, zapcore.DebugLevel) 141 cores = append(cores, core) 142 } 143 if config.GetBool("Logger.enableFile", false) { 144 writer := zapcore.AddSync(&lumberjack.Logger{ 145 Filename: config.GetString("Logger.logFileLocation", "/tmp/rudder_log.log"), 146 MaxSize: config.GetInt("Logger.logFileSize", 100), 147 Compress: true, 148 LocalTime: true, 149 }) 150 core := zapcore.NewCore(zapEncoder(config, config.GetBool("Logger.fileJsonFormat", false)), writer, zapcore.DebugLevel) 151 cores = append(cores, core) 152 } 153 combinedCore := zapcore.NewTee(cores...) 154 var options []zap.Option 155 if config.GetBool("Logger.enableFileNameInLog", true) { 156 options = append(options, zap.AddCaller(), zap.AddCallerSkip(1)) 157 } 158 if config.GetBool("Logger.enableStackTrace", false) { 159 // enables stack track for log level error 160 options = append(options, zap.AddStacktrace(zap.ErrorLevel)) 161 } 162 163 if fc.clock != nil { 164 options = append(options, zap.WithClock(fc.clock)) 165 } 166 167 return zap.New(combinedCore, options...) 168 } 169 170 // zapEncoder configures the output of the log 171 func zapEncoder(config *config.Config, json bool) zapcore.Encoder { 172 encoderConfig := zap.NewProductionEncoderConfig() 173 encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder 174 if config.GetBool("Logger.enableTimestamp", true) { 175 encoderConfig.TimeKey = "ts" 176 encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 177 } else { 178 encoderConfig.TimeKey = "" 179 } 180 if json { 181 return zapcore.NewJSONEncoder(encoderConfig) 182 } 183 return zapcore.NewConsoleEncoder(encoderConfig) 184 } 185 186 type discarder struct{} 187 188 func (d *discarder) Sync() error { return nil } 189 func (d *discarder) Write(b []byte) (int, error) { return io.Discard.Write(b) }