github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/zlog/trace.go (about) 1 package zlog 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "strings" 8 9 "go.uber.org/zap" 10 11 "github.com/jxskiss/gopkg/v2/internal/logfilter" 12 ) 13 14 const TraceFilterRuleEnvName = "ZLOG_TRACE_FILTER_RULE" 15 16 func (p *Properties) compileTraceFilter() { 17 if p.cfg.TraceFilterRule == "" { 18 envRule := os.Getenv(TraceFilterRuleEnvName) 19 if envRule != "" { 20 S().Infof("zlog: using trace filter rule from env: %q", envRule) 21 p.cfg.TraceFilterRule = envRule 22 } 23 } 24 if p.cfg.TraceFilterRule != "" { 25 var errs []error 26 p.traceFilter, errs = logfilter.NewFileNameFilter(p.cfg.TraceFilterRule) 27 for _, err := range errs { 28 S().Warnf("zlog: %v", err) 29 } 30 } 31 } 32 33 // Trace logs a message at TraceLevel if it's enabled. 34 // 35 // If trace messages are disabled globally, calling this function is 36 // a no-op. 37 func (l Logger) Trace(msg string, fields ...zap.Field) { 38 if globals.Level.Load() <= int32(TraceLevel) { 39 l.slowPathTrace(msg, fields) 40 } 41 } 42 43 func (l Logger) slowPathTrace(msg string, fields []zap.Field) { 44 logger := l.Logger.WithOptions(zap.AddCallerSkip(3)) 45 checkAndWriteTraceMessage(logger, msg, fields...) 46 } 47 48 // Tracef uses fmt.Sprintf to log a message at TraceLevel if it's enabled. 49 // 50 // If trace messages are disabled globally, calling this function is 51 // a no-op. 52 func (l Logger) Tracef(format string, args ...any) { 53 if globals.Level.Load() <= int32(TraceLevel) { 54 l.slowPathTracef(format, args) 55 } 56 } 57 58 func (l Logger) slowPathTracef(format string, args []any) { 59 logger := l.Logger.WithOptions(zap.AddCallerSkip(3)) 60 msg := formatMessage(format, args) 61 checkAndWriteTraceMessage(logger, msg) 62 } 63 64 // Tracef uses fmt.Sprintf to log a message at TraceLevel if it's enabled. 65 // 66 // If trace messages are disabled globally, calling this function is 67 // a no-op. 68 func (s SugaredLogger) Tracef(format string, args ...any) { 69 if globals.Level.Load() <= int32(TraceLevel) { 70 s.slowPathTracef(format, args) 71 } 72 } 73 74 func (s SugaredLogger) slowPathTracef(format string, args []any) { 75 logger := s.SugaredLogger.Desugar().WithOptions(zap.AddCallerSkip(3)) 76 msg := formatMessage(format, args) 77 checkAndWriteTraceMessage(logger, msg) 78 } 79 80 func checkAndWriteTraceMessage(l *zap.Logger, msg string, fields ...zap.Field) { 81 if ce := l.Check(TraceLevel, msg); ce != nil { 82 fileName := ce.Caller.File 83 if fileName != "" { 84 _, fileName, _, _, _ = getCaller(3) 85 } 86 if fileName != "" && globals.Props.traceFilter != nil && 87 !globals.Props.traceFilter.Allow(fileName) { 88 return 89 } 90 ce.Write(fields...) 91 } 92 } 93 94 // TRACE logs a message at TraceLevel if it's enabled. 95 // 96 // TRACE accepts flexible arguments to help development, it trys to get a 97 // logger from the first argument, if the first argument is a *zap.Logger or 98 // *zap.SugaredLogger, the logger will be used, else if the first argument 99 // is a context.Context, the context will be used to build a logger using 100 // Builder, else it uses the global logger. 101 // 102 // The other arguments may be of type zap.Field or any ordinary type, 103 // the type will be detected and the arguments will be formatted in a most 104 // reasonable way. See example code for detailed usage examples. 105 // 106 // If trace messages are disabled globally, calling this function is 107 // a no-op. 108 func TRACE(args ...any) { 109 if globals.Level.Load() <= int32(TraceLevel) { 110 _slowPathTrace(0, nil, args) 111 } 112 } 113 114 // TRACESkip is similar to TRACE, but it has an extra skip argument to get 115 // correct caller information. When you need to wrap TRACE, you will always 116 // want to use this function instead of TRACE. 117 // 118 // If trace messages are disabled globally, calling this function is 119 // a no-op. 120 func TRACESkip(skip int, args ...any) { 121 if globals.Level.Load() <= int32(TraceLevel) { 122 _slowPathTrace(skip, nil, args) 123 } 124 } 125 126 // TRACE1 is same with TRACE, but it accepts an extra arg0 before args. 127 func TRACE1(arg0 any, args ...any) { 128 if globals.Level.Load() <= int32(TraceLevel) { 129 _slowPathTrace(0, arg0, args) 130 } 131 } 132 133 // TRACESkip1 is same with TRACESkip, but it accepts an extra arg0 before args. 134 func TRACESkip1(skip int, arg0 any, args ...any) { 135 if globals.Level.Load() <= int32(TraceLevel) { 136 _slowPathTrace(skip, arg0, args) 137 } 138 } 139 140 func _slowPathTrace(skip int, a0 any, args []any) { 141 caller, fullFileName, simpleFileName, line, _ := getCaller(skip + 2) 142 if fullFileName != "" && globals.Props.traceFilter != nil && 143 !globals.Props.traceFilter.Allow(fullFileName) { 144 return 145 } 146 logger, msg, fields := parseLoggerAndParams(skip, a0, args) 147 if msg == "" { 148 msg = fmt.Sprintf("======== %s#L%d - %s ========", simpleFileName, line, caller) 149 } else { 150 msg = fmt.Sprintf("[%s] %s", caller, msg) 151 } 152 if ce := logger.Check(TraceLevel, msg); ce != nil { 153 ce.Write(fields...) 154 } 155 } 156 157 func parseLoggerAndParams(skip int, a0 any, args []any) (Logger, string, []zap.Field) { 158 isArgs0 := false 159 if a0 == nil && len(args) > 0 { 160 a0 = args[0] 161 isArgs0 = true 162 } 163 trimArg0 := func(args []any) []any { 164 if isArgs0 { 165 args = args[1:] 166 } 167 return args 168 } 169 var logger = L() 170 if a0 != nil { 171 switch a0 := a0.(type) { 172 case context.Context: 173 logger = WithCtx(a0) 174 args = trimArg0(args) 175 case Logger: 176 logger = a0 177 args = trimArg0(args) 178 case SugaredLogger: 179 logger = a0.Desugar() 180 args = trimArg0(args) 181 case *zap.Logger: 182 logger = Logger{Logger: a0} 183 args = trimArg0(args) 184 case *zap.SugaredLogger: 185 logger = Logger{Logger: a0.Desugar()} 186 args = trimArg0(args) 187 default: 188 if !isArgs0 { 189 args = append([]any{a0}, args...) 190 } 191 } 192 } 193 logger = logger.WithOptions(zap.AddCallerSkip(skip + 2)) 194 if len(args) == 0 { 195 return logger, "", nil 196 } 197 198 switch arg0 := args[0].(type) { 199 case string: 200 fields, ok := tryConvertFields(args[1:]) 201 if ok { 202 return logger, arg0, fields 203 } 204 case zap.Field: 205 fields, ok := tryConvertFields(args) 206 if ok { 207 return logger, "", fields 208 } 209 } 210 211 template := "" 212 if s, ok := args[0].(string); ok && strings.IndexByte(s, '%') >= 0 { 213 template = s 214 args = args[1:] 215 } 216 return logger, formatMessage(template, args), nil 217 } 218 219 func tryConvertFields(args []any) ([]zap.Field, bool) { 220 if len(args) == 0 { 221 return nil, true 222 } 223 for i := 0; i < len(args); i++ { 224 if _, ok := args[i].(zap.Field); !ok { 225 return nil, false 226 } 227 } 228 fields := make([]zap.Field, len(args)) 229 for i, f := range args { 230 fields[i] = f.(zap.Field) 231 } 232 return fields, true 233 } 234 235 func formatMessage(template string, fmtArgs []any) string { 236 if len(fmtArgs) == 0 { 237 return template 238 } 239 if template != "" { 240 return fmt.Sprintf(template, fmtArgs...) 241 } 242 if len(fmtArgs) == 1 { 243 if str, ok := fmtArgs[0].(string); ok { 244 return str 245 } 246 } 247 return fmt.Sprint(fmtArgs...) 248 }