github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/easy/ezdbg/debug.go (about) 1 package ezdbg 2 3 import ( 4 "context" 5 "reflect" 6 "runtime" 7 "strings" 8 "unicode/utf8" 9 10 "github.com/jxskiss/gopkg/v2/easy" 11 ) 12 13 type stringerFunc func(v any) string 14 15 /* 16 DEBUG is debug message logger which do nothing if debug level is not enabled (the default). 17 It has good performance for production deployment by eliminating unnecessary 18 parameter evaluation and control flows. 19 20 DEBUG accepts very flexible arguments to help development, see the following examples: 21 22 // func() 23 DEBUG(func() { 24 logrus.Debug(something) 25 for _, item := range someSlice { 26 logrus.Debug(item) 27 } 28 }) 29 30 // logger from context with or without format 31 DEBUG(ctx, 1, 2, 3) 32 DEBUG(ctx, "a=%v b=%v c=%v", 1, 2, 3) 33 34 // log variables with or without format 35 DEBUG(1, 2, 3) 36 DEBUG("a=%v b=%v c=%v", 1, 2, 3) 37 38 // optionally a DebugLogger can be provided as the first parameter 39 logger := logrus.WithField("key", "dummy") 40 DEBUG(logger, "aa", 2, 3) 41 DEBUG(logger, "a=%v b=%v c=%v", "aa", 2, 3) 42 43 // or a function which returns a logger implements DebugLogger 44 logger := func() *logrus.Entry { ... } 45 DEBUG(logger, "aa", 2, 3) 46 DEBUG(logger, "a=%v b=%v c=%v", "aa", 2, 3) 47 48 // or a print function to print the log message 49 logger := logrus.Debugf 50 DEBUG(logger, "aa", 2, 3) 51 DEBUG(logger, "a=%v b=%v c=%v", "aa", 2, 3) 52 53 // non-basic-type values will be formatted using json 54 obj := &SomeStructType{Field1: "blah", Field2: 1234567, Field3: true} 55 DEBUG(logger, "obj=%v", obj) 56 */ 57 func DEBUG(args ...any) { 58 stringer := easy.JSON 59 logdebug(1, stringer, args...) 60 } 61 62 // DEBUGSkip is similar to DEBUG, but it has an extra skip param to skip stacktrace 63 // to get correct caller information. 64 // When you wrap functions in this package, you always want to use the functions 65 // which end with "Skip". 66 func DEBUGSkip(skip int, args ...any) { 67 stringer := easy.JSON 68 logdebug(skip+1, stringer, args...) 69 } 70 71 // PRETTY is similar to DEBUG, but it calls Pretty to format non-basic-type data. 72 func PRETTY(args ...any) { 73 stringer := easy.Pretty 74 logdebug(1, stringer, args...) 75 } 76 77 // PRETTYSkip is similar to PRETTY, but it has an extra skip param to skip stacktrace 78 // to get correct caller information. 79 // When you wrap functions in this package, you always want to use the functions 80 // which end with "Skip". 81 func PRETTYSkip(skip int, args ...any) { 82 stringer := easy.Pretty 83 logdebug(skip+1, stringer, args...) 84 } 85 86 func logdebug(skip int, stringer stringerFunc, args ...any) { 87 ctx := context.Background() 88 if len(args) > 0 { 89 if _ctx, ok := args[0].(context.Context); ok && _ctx != nil { 90 ctx = _ctx 91 } 92 } 93 if _logcfg.EnableDebug == nil || !_logcfg.EnableDebug(ctx) { 94 return 95 } 96 97 // Check filter rules. 98 caller, fullFileName, simpleFileName, line := getCaller(skip + 1) 99 if _logcfg.filter != nil && !_logcfg.filter.Allow(fullFileName) { 100 return 101 } 102 103 var logger DebugLogger 104 if len(args) > 0 { 105 if arg0, ok := args[0].(func()); ok { 106 arg0() 107 return 108 } 109 logger, args = parseLogger(args) 110 } 111 if logger == nil { 112 logger = _logcfg.getLogger(nil) 113 } 114 callerPrefix := "[" + caller + "] " 115 if len(args) > 0 { 116 if format, ok := args[0].(string); ok && strings.IndexByte(format, '%') >= 0 { 117 logger.Debugf(callerPrefix+format, formatArgs(stringer, args[1:])...) 118 return 119 } 120 format := callerPrefix + "%v" + strings.Repeat(" %v", len(args)-1) 121 logger.Debugf(format, formatArgs(stringer, args)...) 122 } else { 123 logger.Debugf("======== %s#L%d - %s ========", simpleFileName, line, caller) 124 } 125 } 126 127 var debugLoggerTyp = reflect.TypeOf((*DebugLogger)(nil)).Elem() 128 129 func parseLogger(args []any) (DebugLogger, []any) { 130 var logger DebugLogger 131 if arg0, ok := args[0].(DebugLogger); ok { 132 logger = arg0 133 args = args[1:] 134 return logger, args 135 } 136 137 switch arg0 := args[0].(type) { 138 case context.Context: 139 logger = _logcfg.getLogger(&arg0) 140 args = args[1:] 141 case func(string, ...any): 142 logger = PrintFunc(arg0) 143 args = args[1:] 144 default: 145 arg0typ := reflect.TypeOf(arg0) 146 if arg0typ.Kind() == reflect.Func { 147 if arg0typ.NumIn() == 0 && arg0typ.NumOut() == 1 && 148 arg0typ.Out(0).Implements(debugLoggerTyp) { 149 out := reflect.ValueOf(arg0).Call(nil)[0] 150 logger = out.Interface().(DebugLogger) 151 } 152 args = args[1:] 153 } 154 } 155 return logger, args 156 } 157 158 func formatArgs(stringer stringerFunc, args []any) []any { 159 retArgs := make([]any, 0, len(args)) 160 for _, v := range args { 161 x := v 162 if v != nil { 163 typ := reflect.TypeOf(v) 164 if typ.Kind() == reflect.Ptr && !reflect.ValueOf(v).IsNil() && isBasicType(typ.Elem()) { 165 typ = typ.Elem() 166 v = reflect.ValueOf(v).Elem().Interface() 167 } 168 if isBasicType(typ) { 169 x = v 170 } else if bv, ok := v.([]byte); ok && utf8.Valid(bv) { 171 x = string(bv) 172 } else { 173 x = stringer(v) 174 } 175 } 176 retArgs = append(retArgs, x) 177 } 178 return retArgs 179 } 180 181 func isBasicType(typ reflect.Type) bool { 182 switch typ.Kind() { 183 case reflect.Bool, reflect.String, 184 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 185 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, 186 reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: 187 return true 188 } 189 return false 190 } 191 192 func getCaller(skip int) (funcName, fullFileName, simpleFileName string, line int) { 193 pc, fullFileName, line, _ := runtime.Caller(skip + 1) 194 funcName = runtime.FuncForPC(pc).Name() 195 for i := len(funcName) - 1; i >= 0; i-- { 196 if funcName[i] == '/' { 197 funcName = funcName[i+1:] 198 break 199 } 200 } 201 simpleFileName = fullFileName 202 pathSepCnt := 0 203 for i := len(simpleFileName) - 1; i >= 0; i-- { 204 if simpleFileName[i] == '/' { 205 pathSepCnt++ 206 if pathSepCnt == 2 { 207 simpleFileName = simpleFileName[i+1:] 208 break 209 } 210 } 211 } 212 return 213 }