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  }