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  }