github.com/jxskiss/gopkg@v0.17.3/easy/log.go (about)

     1  package easy
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"log"
     9  	"os"
    10  	"reflect"
    11  	"runtime"
    12  	"sort"
    13  	"strings"
    14  	"sync"
    15  	"unicode/utf8"
    16  
    17  	"github.com/jxskiss/gopkg/internal/linkname"
    18  	"github.com/jxskiss/gopkg/internal/unsafeheader"
    19  	"github.com/jxskiss/gopkg/json"
    20  	"github.com/jxskiss/gopkg/reflectx"
    21  	"github.com/jxskiss/gopkg/strutil"
    22  )
    23  
    24  func ConfigLog(cfg LogCfg) {
    25  	_logcfg = cfg
    26  }
    27  
    28  var _logcfg LogCfg
    29  
    30  type LogCfg struct {
    31  	EnableDebug func() bool
    32  	Logger      func() ErrDebugLogger
    33  	CtxLogger   func(context.Context) ErrDebugLogger
    34  }
    35  
    36  func (p LogCfg) getLogger(ctxp *context.Context) ErrDebugLogger {
    37  	if p.CtxLogger != nil && ctxp != nil {
    38  		if lg := p.CtxLogger(*ctxp); lg != nil {
    39  			return lg
    40  		}
    41  	}
    42  	if p.Logger != nil {
    43  		if lg := p.Logger(); lg != nil {
    44  			return lg
    45  		}
    46  	}
    47  	return stdLogger{}
    48  }
    49  
    50  var log_std = linkname.LogStd
    51  
    52  type stdLogger struct{}
    53  
    54  const _stdLogDepth = 2
    55  
    56  func (_ stdLogger) Debugf(format string, args ...interface{}) {
    57  	log_std.Output(_stdLogDepth, fmt.Sprintf("[DEBUG]: "+format, args...))
    58  }
    59  
    60  func (_ stdLogger) Errorf(format string, args ...interface{}) {
    61  	log_std.Output(_stdLogDepth, fmt.Sprintf("[ERROR]: "+format, args...))
    62  }
    63  
    64  // ErrLogger is an interface which log an message at ERROR level.
    65  // It's implemented by *logrus.Logger, *logrus.Entry, *zap.SugaredLogger,
    66  // and many other logging packages.
    67  type ErrLogger interface {
    68  	Errorf(format string, args ...interface{})
    69  }
    70  
    71  // DebugLogger is an interface which log an message at DEBUG level.
    72  // It's implemented by *logrus.Logger, *logrus.Entry, *zap.SugaredLogger,
    73  // and many other logging packages.
    74  type DebugLogger interface {
    75  	Debugf(format string, args ...interface{})
    76  }
    77  
    78  // ErrDebugLogger is an interface which log messages at ERROR and DEBUG level.
    79  // It's implemented by *logrus.Logger, *logrus.Entry, *zap.SugaredLogger,
    80  // and many other logging packages.
    81  type ErrDebugLogger interface {
    82  	ErrLogger
    83  	DebugLogger
    84  }
    85  
    86  // PrintFunc is a function to print the given arguments in format to somewhere.
    87  // It implements the interface `ErrDebugLogger`.
    88  type PrintFunc func(format string, args ...interface{})
    89  
    90  func (f PrintFunc) Errorf(format string, args ...interface{}) { f(format, args...) }
    91  
    92  func (f PrintFunc) Debugf(format string, args ...interface{}) { f(format, args...) }
    93  
    94  // JSON converts given object to a json string, it never returns error.
    95  // The marshalling method used here does not escape HTML characters,
    96  // and map keys are sorted, which helps human reading.
    97  func JSON(v interface{}) string {
    98  	b, err := json.MarshalNoHTMLEscape(v, "", "")
    99  	if err != nil {
   100  		return fmt.Sprintf("<error: %v>", err)
   101  	}
   102  	b = bytes.TrimSpace(b)
   103  	return unsafeheader.BytesToString(b)
   104  }
   105  
   106  // Logfmt converts given object to a string in logfmt format, it never
   107  // returns error. Note that only struct and map of basic types are
   108  // supported, non-basic types are simply ignored.
   109  func Logfmt(v interface{}) string {
   110  	if reflectx.IsNilInterface(v) {
   111  		return "null"
   112  	}
   113  	var src []byte
   114  	switch v := v.(type) {
   115  	case []byte:
   116  		src = v
   117  	case string:
   118  		src = unsafeheader.StringToBytes(v)
   119  	}
   120  	if src != nil && utf8.Valid(src) {
   121  		srcstr := string(src)
   122  		if bytes.IndexFunc(src, needsQuoteValueRune) != -1 {
   123  			return JSON(srcstr)
   124  		}
   125  		return srcstr
   126  	}
   127  
   128  	// simple values
   129  	val := reflect.Indirect(reflect.ValueOf(v))
   130  	if !val.IsValid() {
   131  		return "null"
   132  	}
   133  	if isBasicType(val.Type()) {
   134  		return fmt.Sprint(val)
   135  	}
   136  	if val.Kind() != reflect.Struct && val.Kind() != reflect.Map {
   137  		return "<error: unsupported logfmt type>"
   138  	}
   139  
   140  	keyValues := make([]interface{}, 0)
   141  	if val.Kind() == reflect.Map {
   142  		keys := make([]string, 0, val.Len())
   143  		values := make(map[string]interface{}, val.Len())
   144  		for iter := val.MapRange(); iter.Next(); {
   145  			k, v := iter.Key(), reflect.Indirect(iter.Value())
   146  			if !isBasicType(k.Type()) || !v.IsValid() {
   147  				continue
   148  			}
   149  			v = reflect.ValueOf(v.Interface())
   150  			if !v.IsValid() {
   151  				continue
   152  			}
   153  			kstr := fmt.Sprint(k.Interface())
   154  			if isBasicType(v.Type()) {
   155  				keys = append(keys, kstr)
   156  				values[kstr] = v.Interface()
   157  				continue
   158  			}
   159  			if bv, ok := v.Interface().([]byte); ok {
   160  				if len(bv) > 0 && utf8.Valid(bv) {
   161  					keys = append(keys, kstr)
   162  					values[kstr] = string(bv)
   163  				}
   164  				continue
   165  			}
   166  			if v.Kind() == reflect.Slice && isBasicType(v.Elem().Type()) {
   167  				keys = append(keys, kstr)
   168  				values[kstr] = JSON(v.Interface())
   169  				continue
   170  			}
   171  		}
   172  		sort.Strings(keys)
   173  		for _, k := range keys {
   174  			v := values[k]
   175  			keyValues = append(keyValues, k, v)
   176  		}
   177  	} else { // reflect.Struct
   178  		typ := val.Type()
   179  		fieldNum := val.NumField()
   180  		for i := 0; i < fieldNum; i++ {
   181  			field := typ.Field(i)
   182  			// ignore unexported fields which we can't take interface
   183  			if len(field.PkgPath) != 0 {
   184  				continue
   185  			}
   186  			fk := strutil.ToSnakeCase(field.Name)
   187  			fv := reflect.Indirect(val.Field(i))
   188  			if !(fv.IsValid() && fv.CanInterface()) {
   189  				continue
   190  			}
   191  			if isBasicType(fv.Type()) {
   192  				keyValues = append(keyValues, fk, fv.Interface())
   193  				continue
   194  			}
   195  			if bv, ok := fv.Interface().([]byte); ok {
   196  				if len(bv) > 0 && utf8.Valid(bv) {
   197  					keyValues = append(keyValues, fk, string(bv))
   198  				}
   199  				continue
   200  			}
   201  			if fv.Kind() == reflect.Slice && isBasicType(fv.Elem().Type()) {
   202  				keyValues = append(keyValues, fk, JSON(fv.Interface()))
   203  				continue
   204  			}
   205  		}
   206  	}
   207  	if len(keyValues) == 0 {
   208  		return ""
   209  	}
   210  
   211  	buf := &strings.Builder{}
   212  	needSpace := false
   213  	for i := 0; i < len(keyValues); i += 2 {
   214  		k, v := keyValues[i], keyValues[i+1]
   215  		if needSpace {
   216  			buf.WriteByte(' ')
   217  		}
   218  		addLogfmtString(buf, k)
   219  		buf.WriteByte('=')
   220  		addLogfmtString(buf, v)
   221  		needSpace = true
   222  	}
   223  	return buf.String()
   224  }
   225  
   226  func addLogfmtString(buf *strings.Builder, val interface{}) {
   227  	str, ok := val.(string)
   228  	if !ok {
   229  		str = fmt.Sprint(val)
   230  	}
   231  	if strings.IndexFunc(str, needsQuoteValueRune) != -1 {
   232  		str = JSON(str)
   233  	}
   234  	buf.WriteString(str)
   235  }
   236  
   237  func needsQuoteValueRune(r rune) bool {
   238  	switch r {
   239  	case '\\', '"', '=', '\n', '\r', '\t':
   240  		return true
   241  	default:
   242  		return r <= ' '
   243  	}
   244  }
   245  
   246  // Pretty converts given object to a pretty formatted json string.
   247  // If the input is a json string, it will be formatted using json.Indent
   248  // with four space characters as indent.
   249  func Pretty(v interface{}) string {
   250  	return prettyIndent(v, "    ")
   251  }
   252  
   253  // Pretty2 is like Pretty, but it uses two space characters as indent,
   254  // instead of four.
   255  func Pretty2(v interface{}) string {
   256  	return prettyIndent(v, "  ")
   257  }
   258  
   259  func prettyIndent(v interface{}, indent string) string {
   260  	var src []byte
   261  	switch v := v.(type) {
   262  	case []byte:
   263  		src = v
   264  	case string:
   265  		src = unsafeheader.StringToBytes(v)
   266  	}
   267  	if src != nil {
   268  		if json.Valid(src) {
   269  			buf := bytes.NewBuffer(nil)
   270  			_ = json.Indent(buf, src, "", indent)
   271  			return unsafeheader.BytesToString(buf.Bytes())
   272  		}
   273  		if utf8.Valid(src) {
   274  			return string(src)
   275  		}
   276  		return "<pretty: non-printable bytes>"
   277  	}
   278  	buf, err := json.MarshalNoHTMLEscape(v, "", indent)
   279  	if err != nil {
   280  		return fmt.Sprintf("<error: %v>", err)
   281  	}
   282  	buf = bytes.TrimSpace(buf)
   283  	return unsafeheader.BytesToString(buf)
   284  }
   285  
   286  // Caller returns function name, filename, and the line number of the caller.
   287  // The argument skip is the number of stack frames to ascend, with 0
   288  // identifying the caller of Caller.
   289  func Caller(skip int) (name, file string, line int) {
   290  	pc, file, line, _ := runtime.Caller(skip + 1)
   291  	name = runtime.FuncForPC(pc).Name()
   292  	for i := len(name) - 1; i >= 0; i-- {
   293  		if name[i] == '/' {
   294  			name = name[i+1:]
   295  			break
   296  		}
   297  	}
   298  	pathSepCnt := 0
   299  	for i := len(file) - 1; i >= 0; i-- {
   300  		if file[i] == '/' {
   301  			pathSepCnt++
   302  			if pathSepCnt == 2 {
   303  				file = file[i+1:]
   304  				break
   305  			}
   306  		}
   307  	}
   308  	return
   309  }
   310  
   311  var (
   312  	stdoutMu sync.Mutex
   313  	stdlogMu sync.Mutex
   314  )
   315  
   316  // CopyStdout replaces os.Stdout with a file created by `os.Pipe()`, and
   317  // copies the content written to os.Stdout.
   318  // This is not safe and most likely problematic, it's mainly to help intercepting
   319  // output in testing.
   320  func CopyStdout(f func()) ([]byte, error) {
   321  	stdoutMu.Lock()
   322  	defer stdoutMu.Unlock()
   323  	old := os.Stdout
   324  	defer func() { os.Stdout = old }()
   325  
   326  	r, w, err := os.Pipe()
   327  	// just to make sure the error didn't happen
   328  	// in case of unfortunate, we should still do the specified work
   329  	if err != nil {
   330  		f()
   331  		return nil, err
   332  	}
   333  
   334  	// copy the output in a separate goroutine, so printing can't block indefinitely
   335  	outCh := make(chan []byte)
   336  	go func() {
   337  		var buf bytes.Buffer
   338  		multi := io.MultiWriter(&buf, old)
   339  		io.Copy(multi, r)
   340  		outCh <- buf.Bytes()
   341  	}()
   342  
   343  	// do the work, write the stdout to pipe
   344  	os.Stdout = w
   345  	f()
   346  	w.Close()
   347  
   348  	out := <-outCh
   349  	return out, nil
   350  }
   351  
   352  // CopyStdLog replaces the out Writer of the default logger of `log` package,
   353  // and copies the content written to it.
   354  // This is unsafe and most likely problematic, it's mainly to help intercepting
   355  // log output in testing.
   356  //
   357  // Also NOTE if the out Writer of the default logger has already been replaced
   358  // with another writer, we won't know anything about that writer and will
   359  // restore the out Writer to os.Stderr before it returns.
   360  // It will be a real mess.
   361  func CopyStdLog(f func()) []byte {
   362  	stdlogMu.Lock()
   363  	defer stdlogMu.Unlock()
   364  	defer log.SetOutput(os.Stderr)
   365  
   366  	var buf bytes.Buffer
   367  	multi := io.MultiWriter(&buf, os.Stderr)
   368  	log.SetOutput(multi)
   369  	f()
   370  	return buf.Bytes()
   371  }
   372  
   373  func formatArgs(stringer stringerFunc, args []interface{}) []interface{} {
   374  	retArgs := make([]interface{}, 0, len(args))
   375  	for _, v := range args {
   376  		x := v
   377  		if v != nil {
   378  			typ := reflect.TypeOf(v)
   379  			for typ.Kind() == reflect.Ptr && isBasicType(typ.Elem()) {
   380  				typ = typ.Elem()
   381  				v = reflect.ValueOf(v).Elem().Interface()
   382  			}
   383  			if isBasicType(typ) {
   384  				x = v
   385  			} else if bv, ok := v.([]byte); ok && utf8.Valid(bv) {
   386  				x = string(bv)
   387  			} else {
   388  				x = stringer(v)
   389  			}
   390  		}
   391  		retArgs = append(retArgs, x)
   392  	}
   393  	return retArgs
   394  }
   395  
   396  func isBasicType(typ reflect.Type) bool {
   397  	switch typ.Kind() {
   398  	case reflect.Bool, reflect.String,
   399  		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
   400  		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
   401  		reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
   402  		return true
   403  	}
   404  	return false
   405  }