github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/zlog/context.go (about) 1 package zlog 2 3 import ( 4 "context" 5 6 "go.uber.org/zap" 7 "go.uber.org/zap/zapcore" 8 ) 9 10 type ctxKey int 11 12 const ( 13 fieldsKey ctxKey = iota 14 loggerKey 15 ) 16 17 // CtxHandler customizes a logger's behavior at runtime dynamically. 18 type CtxHandler struct { 19 20 // ChangeLevel returns a non-nil Level if it wants to change 21 // the logger's logging level according to ctx. 22 // It returns nil to keep the logger's logging level as-is. 23 ChangeLevel func(ctx context.Context) *Level 24 25 // WithCtx is called by Logger.Ctx, SugaredLogger.Ctx and the global 26 // function WithCtx to check ctx for context-aware information. 27 // It returns CtxResult to customize the logger's behavior. 28 WithCtx func(ctx context.Context) CtxResult 29 } 30 31 // CtxResult holds information get from a context. 32 type CtxResult struct { 33 // Non-nil Level changes the logger's logging level. 34 Level *Level 35 36 // Fields will be added to the logger as additional fields. 37 Fields []zap.Field 38 } 39 40 // AddFields add logging fields to ctx which can be retrieved by 41 // GetFields, WithCtx. 42 // Duplicate fields override the old ones in ctx. 43 func AddFields(ctx context.Context, fields ...zap.Field) context.Context { 44 if len(fields) == 0 { 45 return ctx 46 } 47 old, ok := ctx.Value(fieldsKey).([]zap.Field) 48 if ok { 49 fields = appendFields(old, fields) 50 } 51 return context.WithValue(ctx, fieldsKey, fields) 52 } 53 54 // GetFields returns the logging fields added to ctx by AddFields. 55 func GetFields(ctx context.Context) []zap.Field { 56 fs, ok := ctx.Value(fieldsKey).([]zap.Field) 57 if ok { 58 return fs[:len(fs):len(fs)] // clip 59 } 60 return nil 61 } 62 63 // WithLogger returns a new context.Context with logger attached, 64 // which can be retrieved by WithCtx. 65 func WithLogger[T Logger | SugaredLogger | *zap.Logger | *zap.SugaredLogger]( 66 ctx context.Context, logger T) context.Context { 67 ctx = context.WithValue(ctx, loggerKey, logger) 68 return ctx 69 } 70 71 func getLoggerFromCtx(ctx context.Context) any { 72 return ctx.Value(loggerKey) 73 } 74 75 // Ctx creates a child logger, it calls CtxHandler.WithCtx to get CtxResult 76 // from ctx, adds CtxResult.Fields to the child logger and changes 77 // the logger's level to CtxResult.Level, if it is not nil. 78 func (l Logger) Ctx(ctx context.Context, extra ...zap.Field) Logger { 79 if ctx == nil { 80 return l.With(extra...) 81 } 82 var fields []zap.Field 83 logger := l 84 ctxFunc := globals.Props.cfg.CtxHandler.WithCtx 85 if ctxFunc != nil { 86 ctxResult := ctxFunc(ctx) 87 fields = ctxResult.Fields 88 if ctxResult.Level != nil { 89 logger = logger.WithOptions(zap.WrapCore(changeLevel(*ctxResult.Level))) 90 } 91 } 92 fields = appendFields(fields, GetFields(ctx)) 93 fields = appendFields(fields, extra) 94 if len(fields) > 0 { 95 logger = logger.With(fields...) 96 } 97 return logger 98 } 99 100 // Ctx creates a child logger, it calls CtxHandler.WithCtx to get CtxResult 101 // from ctx, adds CtxResult.Fields to the child logger and changes 102 // the logger's level to CtxResult.Level, if it is not nil. 103 func (s SugaredLogger) Ctx(ctx context.Context, extra ...zap.Field) SugaredLogger { 104 ctxFunc := globals.Props.cfg.CtxHandler.WithCtx 105 if (ctx == nil || ctxFunc == nil) && len(extra) == 0 { 106 return s 107 } 108 return s.Desugar().Ctx(ctx, extra...).Sugar() 109 } 110 111 // WithCtx creates a child logger and customizes its behavior using context 112 // data (e.g. adding fields, dynamically changing level, etc.) 113 // 114 // If ctx is created by WithLogger, it carries a logger instance, 115 // this function uses that logger as a base to create the child logger, 116 // else it calls Logger.Ctx to build the child logger with contextual fields 117 // and optional dynamic level from ctx. 118 // 119 // Also see WithLogger, CtxHandler and CtxResult for more details. 120 func WithCtx(ctx context.Context, extra ...zap.Field) Logger { 121 if ctx == nil { 122 return With(extra...) 123 } 124 if lg := getLoggerFromCtx(ctx); lg != nil { 125 var logger Logger 126 switch x := lg.(type) { 127 case Logger: 128 logger = x 129 case SugaredLogger: 130 logger = x.Desugar() 131 case *zap.Logger: 132 logger = Logger{Logger: x} 133 case *zap.SugaredLogger: 134 logger = Logger{Logger: x.Desugar()} 135 } 136 if logger.Logger != nil { 137 return logger.With(extra...) 138 } 139 } 140 return L().Ctx(ctx, extra...) 141 } 142 143 //nolint:predeclared 144 func appendFields(old []zap.Field, new []zap.Field) []zap.Field { 145 if len(new) == 0 { 146 return old 147 } 148 result := make([]zap.Field, len(old), len(old)+len(new)) 149 copy(result, old) 150 151 // check namespace 152 nsIdx := 0 153 for i := len(result) - 1; i >= 0; i-- { 154 if result[i].Type == zapcore.NamespaceType { 155 nsIdx = i + 1 156 break 157 } 158 } 159 160 var hasNewNamespace bool 161 loop: 162 for _, f := range new { 163 if !hasNewNamespace { 164 if f.Type == zapcore.NamespaceType { 165 hasNewNamespace = true 166 result = append(result, f) 167 continue loop 168 } 169 for i := nsIdx; i < len(result); i++ { 170 if result[i].Key == f.Key { 171 result[i] = f 172 continue loop 173 } 174 } 175 } 176 result = append(result, f) 177 } 178 return result 179 }