github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/zlog/zlog.go (about) 1 // Package zlog provides opinionated high-level logging facilities 2 // based on go.uber.org/zap. 3 // 4 // Logger and SugaredLogger are simple wrappers of *zap.Logger and *zap.SugaredLogger, 5 // to provide more user-friendly API in addition to zap. 6 // 7 // # TraceLevel 8 // 9 // This package defines an extra [TraceLevel], for the most fine-grained 10 // information which helps developer "tracing" their code. 11 // Users can expect this logging level to be very verbose, you can use it, 12 // for example, to annotate each step in an algorithm or each individual 13 // calling with parameters in your program. 14 // 15 // In production deployment, it's strongly suggested to disable TraceLevel logs. 16 // In development, user may also configure to only allow or deny TraceLevel messages 17 // from some packages or some files to reduce the number of tracing logs. 18 // See [Logger.Trace], [SugaredLogger.Tracef], [TRACE], [GlobalConfig].TraceFilterRule 19 // for detailed documents. 20 // 21 // # Dynamic Level 22 // 23 // This package supports changing logging level dynamically, in two ways: 24 // 25 // 1. Loggers creates by this package wraps the zap Core by a dynamic-level Core. 26 // User can use [Config].PerLoggerLevels to configure different level for different 27 // loggers by logger names. 28 // The format is "loggerName.subLogger=level". 29 // If a level is configured for a parent logger, bug not configured for a child logger, 30 // the child logger derives from its parent. 31 // 2. [GlobalConfig].CtxHandler, if configured, can optionally change level according to 32 // contextual information, by returning a non-nil [CtxResult].Level value. 33 // 34 // # Context Integration 35 // 36 // This package integrates with [context.Context], user may add contextual fields 37 // to a Context (see [AddFields], [CtxHandler]), or add a pre-built logger 38 // to a Context (see [WithLogger]), or set a Context to use dynamic level 39 // (see [CtxHandler]), either smaller or greater than the base logger. 40 // Functions [Logger.Ctx], [SugaredLogger.Ctx] and [WithCtx] 41 // create child loggers with contextual information retrieved from a Context. 42 // 43 // # Multi-files Support 44 // 45 // [Config].PerLoggerFiles optionally set different file destination for different 46 // loggers specified by logger name. 47 // If a destination is configured for a parent logger, but not configured 48 // for a child logger, the child logger derives from its parent. 49 // 50 // # "logfmt" Encoder 51 // 52 // NewLogfmtEncoder creates a [zapcore.Encoder] which encodes log in the "logfmt" format. 53 // The returned encoder does not support [zapcore.ObjectMarshaler]. 54 // 55 // # "logr" Adapter 56 // 57 // This package provides an adapter implementation of [logr.LogSink] 58 // to send logs from a [logr.Logger] to an underlying [zap.Logger]. 59 // 60 // NewLogrLogger accepts optional options and creates a new logr.Logger 61 // using a [zap.Logger] as the underlying LogSink. 62 // 63 // # "slog" Adapter 64 // 65 // This package provides an adapter implementation of [slog.Handler] 66 // to send logs from a [slog.Logger] to an underlying [zap.Logger]. 67 // 68 // NewSlogLogger accepts optional options and creates a new slog.Logger 69 // using a [zap.Logger] as the underlying Handler. 70 // 71 // # Example 72 // 73 // func main() { 74 // // Simple shortcut for development. 75 // zlog.SetDevelopment() 76 // 77 // // Or, provide complete config and options to configure it. 78 // logCfg := &zlog.Config{ /* ... */ } 79 // logOpts := []zap.Option{ /* ... */ } 80 // zlog.SetupGlobals(logCfg, logOpts...) 81 // 82 // // Use the loggers ... 83 // zlog.L() /* ... */ 84 // zlog.S() /* ... */ 85 // zlog.With( ... ) /* ... */ 86 // zlog.SugaredWith( ... ) /* ... */ 87 // 88 // // Use with context integration. 89 // ctx = zlog.AddFields(ctx, ... ) // or ctx = zlog.WithLogger( ... ) 90 // zlog.L().Ctx(ctx) /* ... */ 91 // zlog.S().Ctx(ctx) /* ... */ 92 // zlog.WithCtx(ctx) /* ... */ 93 // 94 // // logr 95 // logger := zlog.NewLogrLogger( /* ... */ ) 96 // 97 // // slog 98 // logger := zlog.NewSlogLogger( /* ... */ ) 99 // 100 // // ...... 101 // } 102 package zlog 103 104 import ( 105 "runtime" 106 107 "go.uber.org/zap" 108 ) 109 110 // Logger is a simple wrapper of *zap.Logger. 111 // It provides fast, leveled, structured logging. All methods are safe 112 // for concurrent use. 113 // A zero Logger is not ready to use, don't construct Logger instances 114 // outside this package. 115 type Logger struct { 116 *zap.Logger 117 118 // Note, this type is designed to be a simple wrapper, 119 // don't add more fields to it. 120 _ struct{} 121 } 122 123 // Named adds a new path segment to the logger's name. Segments are joined by 124 // periods. By default, Loggers are unnamed. 125 func (l Logger) Named(name string) Logger { 126 return Logger{Logger: l.Logger.Named(name)} 127 } 128 129 // With creates a child logger and adds structured context to it. 130 // Fields added to the child don't affect the parent, and vice versa. 131 // Any fields that require evaluation (such as Objects) are evaluated 132 // upon invocation of With. 133 func (l Logger) With(fields ...zap.Field) Logger { 134 return Logger{Logger: l.Logger.With(fields...)} 135 } 136 137 // WithLazy creates a child logger and adds structured context to it lazily. 138 // 139 // The fields are evaluated only if the logger is further chained with [With] 140 // or is written to with any of the log level methods. 141 // Until that occurs, the logger may retain references to objects inside the fields, 142 // and logging will reflect the state of an object at the time of logging, 143 // not the time of WithLazy(). 144 // 145 // WithLazy provides a worthwhile performance optimization for contextual loggers 146 // when the likelihood of using the child logger is low, 147 // such as error paths and rarely taken branches. 148 // 149 // Similar to [With], fields added to the child don't affect the parent, and vice versa. 150 func (l Logger) WithLazy(fields ...zap.Field) Logger { 151 return Logger{Logger: l.Logger.WithLazy(fields...)} 152 } 153 154 // WithMethod adds the caller's method name as a context field, 155 // using the globally configured GlobalConfig.MethodNameKey as key. 156 func (l Logger) WithMethod() Logger { 157 methodName, _, _, _, ok := getCaller(1) 158 if !ok { 159 return l 160 } 161 methodNameKey := globals.Props.cfg.MethodNameKey 162 return l.With(zap.String(methodNameKey, methodName)) 163 } 164 165 // WithOptions clones the current Logger, applies the supplied Options, 166 // and returns the resulting Logger. It's safe to use concurrently. 167 func (l Logger) WithOptions(opts ...zap.Option) Logger { 168 return Logger{Logger: l.Logger.WithOptions(opts...)} 169 } 170 171 // Sugar clones the current Logger, returns a SugaredLogger. 172 // 173 // Sugaring a Logger is quite inexpensive, so it's reasonable for a 174 // single application to use both Loggers and SugaredLoggers, converting 175 // between them on the boundaries of performance-sensitive code. 176 func (l Logger) Sugar() SugaredLogger { 177 return SugaredLogger{SugaredLogger: l.Logger.Sugar()} 178 } 179 180 // SugaredLogger is a simple wrapper of *zap.SugaredLogger. 181 // It provides a slower, but less verbose, API. 182 // A zero SugaredLogger is not ready to use, don't construct 183 // SugaredLogger instances outside this package. 184 // 185 // Any Logger can be converted to a SugaredLogger with its Sugar method. 186 type SugaredLogger struct { 187 *zap.SugaredLogger 188 189 // Note, this type is designed to be a simple wrapper, 190 // don't add more fields to it. 191 _ struct{} 192 } 193 194 // Named adds a sub-scope to the logger's name. See Logger.Named for details. 195 func (s SugaredLogger) Named(name string) SugaredLogger { 196 return SugaredLogger{SugaredLogger: s.SugaredLogger.Named(name)} 197 } 198 199 // With adds a variadic number of fields to the logging context. 200 // It accepts a mix of strongly-typed Field objects and loosely-typed 201 // key-value pairs. When processing pairs, the first element of the pair 202 // is used as the field key and the second as the field value. 203 // 204 // Note that the keys in key-value pairs should be strings. 205 // In development, passing a non-string key panics. 206 // In production, the logger is more forgiving: a separate error is logged, 207 // but the key-value pair is skipped and execution continues. 208 // Passing an orphaned key triggers similar behavior: 209 // panics in development and errors in production. 210 func (s SugaredLogger) With(args ...any) SugaredLogger { 211 return SugaredLogger{SugaredLogger: s.SugaredLogger.With(args...)} 212 } 213 214 // WithMethod adds the caller's method name as a context field, 215 // using the globally configured GlobalConfig.MethodNameKey as key. 216 func (s SugaredLogger) WithMethod() SugaredLogger { 217 methodName, _, _, _, ok := getCaller(1) 218 if !ok { 219 return s 220 } 221 methodNameKey := globals.Props.cfg.MethodNameKey 222 return s.With(zap.String(methodNameKey, methodName)) 223 } 224 225 // WithOptions clones the current SugaredLogger, applies the supplied Options, 226 // and returns the result. It's safe to use concurrently. 227 func (s SugaredLogger) WithOptions(opts ...zap.Option) SugaredLogger { 228 return SugaredLogger{SugaredLogger: s.SugaredLogger.WithOptions(opts...)} 229 } 230 231 // Desugar unwraps a SugaredLogger, returning the original Logger. 232 // 233 // Desugaring is quite inexpensive, so it's reasonable for a single 234 // application to use both Loggers and SugaredLoggers, converting 235 // between them on the boundaries of performance-sensitive code. 236 func (s SugaredLogger) Desugar() Logger { 237 return Logger{Logger: s.SugaredLogger.Desugar()} 238 } 239 240 func getCaller(skip int) (funcName, fullFileName, simpleFileName string, line int, ok bool) { 241 pc, fullFileName, line, ok := runtime.Caller(skip + 1) 242 if !ok { 243 return 244 } 245 funcName = runtime.FuncForPC(pc).Name() 246 for i := len(funcName) - 1; i >= 0; i-- { 247 if funcName[i] == '/' { 248 funcName = funcName[i+1:] 249 break 250 } 251 } 252 simpleFileName = fullFileName 253 pathSepCnt := 0 254 for i := len(simpleFileName) - 1; i >= 0; i-- { 255 if simpleFileName[i] == '/' { 256 pathSepCnt++ 257 if pathSepCnt == 2 { 258 simpleFileName = simpleFileName[i+1:] 259 break 260 } 261 } 262 } 263 return 264 } 265 266 // With creates a child logger and adds structured context to it. 267 // Fields added to the child don't affect the parent, and vice versa. 268 func With(fields ...zap.Field) Logger { 269 return L().With(fields...) 270 } 271 272 // SugaredWith creates a child logger and adds a variadic number of fields 273 // to the logging context. 274 // Fields added to the child don't affect the parent, and vice versa. 275 // 276 // It accepts a mix of strongly-typed Field objects and loosely-typed 277 // key-value pairs. When processing pairs, the first element of the pair 278 // is used as the field key and the second as the field value. 279 func SugaredWith(args ...any) SugaredLogger { 280 return S().With(args...) 281 }