github.com/ethersphere/bee/v2@v2.2.0/pkg/log/formatter.go (about)

     1  // Copyright 2022 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Note: the following code is derived (borrows) from: github.com/go-logr/logr
     6  
     7  package log
     8  
     9  import (
    10  	"bytes"
    11  	"encoding"
    12  	"fmt"
    13  	"path/filepath"
    14  	"reflect"
    15  	"runtime"
    16  	"strconv"
    17  	"strings"
    18  )
    19  
    20  // MessageCategory indicates which category or categories
    21  // of messages should include the caller in the log lines.
    22  type MessageCategory int
    23  
    24  const (
    25  	CategoryNone MessageCategory = iota
    26  	CategoryAll
    27  	CategoryError
    28  	CategoryWarning
    29  	CategoryInfo
    30  	CategoryDebug
    31  )
    32  
    33  // Marshaler is an optional interface that logged values may choose to
    34  // implement. Loggers with structured output, such as JSON, should
    35  // log the object return by the MarshalLog method instead of the
    36  // original value.
    37  type Marshaler interface {
    38  	// MarshalLog can be used to:
    39  	//   - ensure that structs are not logged as strings when the original
    40  	//     value has a String method: return a different type without a
    41  	//     String method
    42  	//   - select which fields of a complex type should get logged:
    43  	//     return a simpler struct with fewer fields
    44  	//   - log unexported fields: return a different struct
    45  	//     with exported fields
    46  	//
    47  	// It may return any value of any type.
    48  	MarshalLog() interface{}
    49  }
    50  
    51  // PseudoStruct is a list of key-value pairs that gets logged as a struct.
    52  // E.g.: PseudoStruct{"f1", 1, "f2", true, "f3", []int{}}.
    53  type PseudoStruct []interface{}
    54  
    55  // fmtOptions carries parameters which influence the way logs are generated/formatted.
    56  type fmtOptions struct {
    57  	caller          MessageCategory
    58  	logCallerFunc   bool
    59  	logTimestamp    bool
    60  	timestampLayout string
    61  	maxLogDepth     int
    62  	jsonOutput      bool
    63  	callerDepth     int
    64  }
    65  
    66  const (
    67  	null    = "null"       // null is a placeholder for nil values.
    68  	noValue = "<no-value>" // noValue is a placeholder for missing values.
    69  
    70  	// maxLogDepthExceeded is printed as the last value in
    71  	// the recursive chain when the depth limit is exceeded.
    72  	maxLogDepthExceeded = `"<max-log-depth-exceeded>"`
    73  )
    74  
    75  // caller represents the original call site for a log line. The File and
    76  // Line fields will always be provided, while the Func field is optional.
    77  type caller struct {
    78  	// File is the basename of the file for this call site.
    79  	File string `json:"file"`
    80  	// Line is the line number in the file for this call site.
    81  	Line int `json:"line"`
    82  	// Func is the function name for this call site, or empty if
    83  	// fmtOptions.logCallerFunc is not enabled.
    84  	Func string `json:"function,omitempty"`
    85  }
    86  
    87  // newFormatter constructs a formatter which
    88  // behavior is influenced by the given options.
    89  func newFormatter(opts fmtOptions) *formatter {
    90  	return &formatter{opts: opts}
    91  }
    92  
    93  // formatter is responsible for formatting the output of the log messages.
    94  type formatter struct {
    95  	opts fmtOptions
    96  }
    97  
    98  // render produces a log line where the base is
    99  // never escaped; the opposite is true for args.
   100  func (f *formatter) render(base, args []interface{}) []byte {
   101  	buf := bytes.NewBuffer(make([]byte, 0, 1024))
   102  	if f.opts.jsonOutput {
   103  		buf.WriteByte('{')
   104  	}
   105  	f.flatten(buf, base, false, false)
   106  	f.flatten(buf, args, len(base) > 0, true)
   107  	if f.opts.jsonOutput {
   108  		buf.WriteByte('}')
   109  	}
   110  	buf.WriteByte('\n')
   111  	return buf.Bytes()
   112  }
   113  
   114  // flatten renders a list of key-value pairs into a buffer. If continuing is
   115  // true, it assumes that the buffer has previous values and will emit a
   116  // separator (which depends on the output format) before the first pair is
   117  // written. If escapeKeys is true, the keys are assumed to have
   118  // non-JSON-compatible characters in them and must be evaluated for escapes.
   119  func (f *formatter) flatten(buf *bytes.Buffer, kvList []interface{}, continuing bool, escapeKeys bool) {
   120  	// This logic overlaps with sanitize() but saves one type-cast per key,
   121  	// which can be measurable.
   122  	if len(kvList)%2 != 0 {
   123  		kvList = append(kvList, noValue)
   124  	}
   125  	for i := 0; i < len(kvList); i += 2 {
   126  		k, ok := kvList[i].(string)
   127  		if !ok {
   128  			k = f.nonStringKey(kvList[i])
   129  			kvList[i] = k
   130  		}
   131  		v := kvList[i+1]
   132  
   133  		if i > 0 || continuing {
   134  			if f.opts.jsonOutput {
   135  				buf.WriteByte(',')
   136  			} else {
   137  				// In theory the format could be something we don't understand.
   138  				// In practice, we control it, so it won't be.
   139  				buf.WriteByte(' ')
   140  			}
   141  		}
   142  
   143  		if escapeKeys {
   144  			buf.WriteString(prettyString(k))
   145  		} else {
   146  			// The following is faster.
   147  			buf.WriteByte('"')
   148  			buf.WriteString(k)
   149  			buf.WriteByte('"')
   150  		}
   151  		if f.opts.jsonOutput {
   152  			buf.WriteByte(':')
   153  		} else {
   154  			buf.WriteByte('=')
   155  		}
   156  		buf.WriteString(f.prettyWithFlags(v, 0, 0))
   157  	}
   158  }
   159  
   160  // prettyWithFlags prettifies the given value.
   161  // TODO: This is not fast. Most of the overhead goes here.
   162  func (f *formatter) prettyWithFlags(value interface{}, flags uint32, depth int) string {
   163  	const flagRawStruct = 0x1 // Do not print braces on structs.
   164  
   165  	if depth > f.opts.maxLogDepth {
   166  		return maxLogDepthExceeded
   167  	}
   168  
   169  	// Handle types that take full control of logging.
   170  	if v, ok := value.(Marshaler); ok {
   171  		// Replace the value with what the type wants to get logged.
   172  		// That then gets handled below via reflection.
   173  		value = invokeMarshaler(v)
   174  	}
   175  
   176  	// Handle types that want to format themselves.
   177  	switch v := value.(type) {
   178  	case fmt.Stringer:
   179  		value = invokeStringer(v)
   180  	case error:
   181  		value = invokeError(v)
   182  	}
   183  
   184  	// Handling the most common types without reflect is a small perf win.
   185  	switch v := value.(type) {
   186  	case bool:
   187  		return strconv.FormatBool(v)
   188  	case string:
   189  		return prettyString(v)
   190  	case int:
   191  		return strconv.FormatInt(int64(v), 10)
   192  	case int8:
   193  		return strconv.FormatInt(int64(v), 10)
   194  	case int16:
   195  		return strconv.FormatInt(int64(v), 10)
   196  	case int32:
   197  		return strconv.FormatInt(int64(v), 10)
   198  	case int64:
   199  		return strconv.FormatInt(v, 10)
   200  	case uint:
   201  		return strconv.FormatUint(uint64(v), 10)
   202  	case uint8:
   203  		return strconv.FormatUint(uint64(v), 10)
   204  	case uint16:
   205  		return strconv.FormatUint(uint64(v), 10)
   206  	case uint32:
   207  		return strconv.FormatUint(uint64(v), 10)
   208  	case uint64:
   209  		return strconv.FormatUint(v, 10)
   210  	case uintptr:
   211  		return strconv.FormatUint(uint64(v), 10)
   212  	case float32:
   213  		return strconv.FormatFloat(float64(v), 'f', -1, 32)
   214  	case float64:
   215  		return strconv.FormatFloat(v, 'f', -1, 64)
   216  	case complex64:
   217  		return `"` + strconv.FormatComplex(complex128(v), 'f', -1, 64) + `"`
   218  	case complex128:
   219  		return `"` + strconv.FormatComplex(v, 'f', -1, 128) + `"`
   220  	case PseudoStruct:
   221  		buf := bytes.NewBuffer(make([]byte, 0, 1024))
   222  		v = f.sanitize(v)
   223  		if flags&flagRawStruct == 0 {
   224  			buf.WriteByte('{')
   225  		}
   226  		for i := 0; i < len(v); i += 2 {
   227  			if i > 0 {
   228  				buf.WriteByte(',')
   229  			}
   230  			k, _ := v[i].(string) // sanitize() above means no need to check success arbitrary keys might need escaping.
   231  			buf.WriteString(prettyString(k))
   232  			buf.WriteByte(':')
   233  			buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1))
   234  		}
   235  		if flags&flagRawStruct == 0 {
   236  			buf.WriteByte('}')
   237  		}
   238  		return buf.String()
   239  	}
   240  
   241  	buf := bytes.NewBuffer(make([]byte, 0, 256))
   242  	t := reflect.TypeOf(value)
   243  	if t == nil {
   244  		return null
   245  	}
   246  	v := reflect.ValueOf(value)
   247  	switch t.Kind() {
   248  	case reflect.Bool:
   249  		return strconv.FormatBool(v.Bool())
   250  	case reflect.String:
   251  		return prettyString(v.String())
   252  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   253  		return strconv.FormatInt(v.Int(), 10)
   254  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   255  		return strconv.FormatUint(v.Uint(), 10)
   256  	case reflect.Float32:
   257  		return strconv.FormatFloat(v.Float(), 'f', -1, 32)
   258  	case reflect.Float64:
   259  		return strconv.FormatFloat(v.Float(), 'f', -1, 64)
   260  	case reflect.Complex64:
   261  		return `"` + strconv.FormatComplex(v.Complex(), 'f', -1, 64) + `"`
   262  	case reflect.Complex128:
   263  		return `"` + strconv.FormatComplex(v.Complex(), 'f', -1, 128) + `"`
   264  	case reflect.Struct:
   265  		if flags&flagRawStruct == 0 {
   266  			buf.WriteByte('{')
   267  		}
   268  		for i := 0; i < t.NumField(); i++ {
   269  			fld := t.Field(i)
   270  			if fld.PkgPath != "" {
   271  				// Reflect says this field is only defined for non-exported fields.
   272  				continue
   273  			}
   274  			if !v.Field(i).CanInterface() {
   275  				// Reflect isn't clear exactly what this means, but we can't use it.
   276  				continue
   277  			}
   278  			name := ""
   279  			omitempty := false
   280  			if tag, found := fld.Tag.Lookup("json"); found {
   281  				if tag == "-" {
   282  					continue
   283  				}
   284  				if comma := strings.Index(tag, ","); comma != -1 {
   285  					if n := tag[:comma]; n != "" {
   286  						name = n
   287  					}
   288  					rest := tag[comma:]
   289  					if strings.Contains(rest, ",omitempty,") || strings.HasSuffix(rest, ",omitempty") {
   290  						omitempty = true
   291  					}
   292  				} else {
   293  					name = tag
   294  				}
   295  			}
   296  			if omitempty && isEmpty(v.Field(i)) {
   297  				continue
   298  			}
   299  			if i > 0 {
   300  				buf.WriteByte(',')
   301  			}
   302  			if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" {
   303  				buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1))
   304  				continue
   305  			}
   306  			if name == "" {
   307  				name = fld.Name
   308  			}
   309  			// Field names can't contain characters which need escaping.
   310  			buf.WriteByte('"')
   311  			buf.WriteString(name)
   312  			buf.WriteByte('"')
   313  			buf.WriteByte(':')
   314  			buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1))
   315  		}
   316  		if flags&flagRawStruct == 0 {
   317  			buf.WriteByte('}')
   318  		}
   319  		return buf.String()
   320  	case reflect.Slice, reflect.Array:
   321  		buf.WriteByte('[')
   322  		for i := 0; i < v.Len(); i++ {
   323  			if i > 0 {
   324  				buf.WriteByte(',')
   325  			}
   326  			e := v.Index(i)
   327  			buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1))
   328  		}
   329  		buf.WriteByte(']')
   330  		return buf.String()
   331  	case reflect.Map:
   332  		buf.WriteByte('{')
   333  		// This does not sort the map keys, for best perf.
   334  		it := v.MapRange()
   335  		i := 0
   336  		for it.Next() {
   337  			if i > 0 {
   338  				buf.WriteByte(',')
   339  			}
   340  			// If a map key supports TextMarshaler, use it.
   341  			var keystr string
   342  			if m, ok := it.Key().Interface().(encoding.TextMarshaler); ok {
   343  				txt, err := m.MarshalText()
   344  				if err != nil {
   345  					keystr = fmt.Sprintf("<error-MarshalText: %s>", err.Error())
   346  				} else {
   347  					keystr = string(txt)
   348  				}
   349  				keystr = prettyString(keystr)
   350  			} else {
   351  				// prettyWithFlags will produce already-escaped values.
   352  				keystr = f.prettyWithFlags(it.Key().Interface(), 0, depth+1)
   353  				if t.Key().Kind() != reflect.String {
   354  					// JSON only does string keys. Unlike Go's standard JSON, we'll convert just about anything to a string.
   355  					keystr = prettyString(keystr)
   356  				}
   357  			}
   358  			buf.WriteString(keystr)
   359  			buf.WriteByte(':')
   360  			buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1))
   361  			i++
   362  		}
   363  		buf.WriteByte('}')
   364  		return buf.String()
   365  	case reflect.Ptr, reflect.Interface:
   366  		if v.IsNil() {
   367  			return null
   368  		}
   369  		return f.prettyWithFlags(v.Elem().Interface(), 0, depth)
   370  	}
   371  	return fmt.Sprintf(`"<unhandled-%s>"`, t.Kind().String())
   372  }
   373  
   374  // caller captures basic information about the caller (filename, line, function).
   375  func (f *formatter) caller() caller {
   376  	pc, file, line, ok := runtime.Caller(f.opts.callerDepth + 3)
   377  	if !ok {
   378  		return caller{"<unknown>", 0, ""}
   379  	}
   380  	caller := caller{File: filepath.Base(file), Line: line}
   381  	if f.opts.logCallerFunc {
   382  		if fp := runtime.FuncForPC(pc); fp != nil {
   383  			caller.Func = fp.Name()
   384  		}
   385  	}
   386  	return caller
   387  }
   388  
   389  // nonStringKey converts non-string value v to string.
   390  func (f *formatter) nonStringKey(v interface{}) string {
   391  	return fmt.Sprintf("<non-string-key: %s>", f.snippet(v))
   392  }
   393  
   394  // snippet produces a short snippet string of an arbitrary value.
   395  func (f *formatter) snippet(v interface{}) string {
   396  	const snipLen = 16
   397  
   398  	snip := f.prettyWithFlags(v, 0, 0)
   399  	if len(snip) > snipLen {
   400  		snip = snip[:snipLen]
   401  	}
   402  	return snip
   403  }
   404  
   405  // sanitize ensures that a list of key-value pairs has a value for every key
   406  // (adding a value if needed) and that each key is a string (substituting a key
   407  // if needed).
   408  func (f *formatter) sanitize(kvList []interface{}) []interface{} {
   409  	if len(kvList)%2 != 0 {
   410  		kvList = append(kvList, noValue)
   411  	}
   412  	for i := 0; i < len(kvList); i += 2 {
   413  		_, ok := kvList[i].(string)
   414  		if !ok {
   415  			kvList[i] = f.nonStringKey(kvList[i])
   416  		}
   417  	}
   418  	return kvList
   419  }
   420  
   421  // isEmpty is similar to the IsZero() reflect.Value method, except that
   422  // in the case of Array and Struct it does not go into depth.
   423  func isEmpty(v reflect.Value) bool {
   424  	switch v.Kind() {
   425  	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
   426  		return v.Len() == 0
   427  	case reflect.Bool:
   428  		return !v.Bool()
   429  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   430  		return v.Int() == 0
   431  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   432  		return v.Uint() == 0
   433  	case reflect.Float32, reflect.Float64:
   434  		return v.Float() == 0
   435  	case reflect.Complex64, reflect.Complex128:
   436  		return v.Complex() == 0
   437  	case reflect.Interface, reflect.Ptr:
   438  		return v.IsNil()
   439  	}
   440  	return false
   441  }
   442  
   443  // prettyString prettify the given string.
   444  // TODO: try to avoid allocations.
   445  func prettyString(s string) string {
   446  	// Avoid escaping (which does allocations) if we can.
   447  	if needsEscape(s) {
   448  		return strconv.Quote(s)
   449  	}
   450  	b := bytes.NewBuffer(make([]byte, 0, 1024))
   451  	b.WriteByte('"')
   452  	b.WriteString(s)
   453  	b.WriteByte('"')
   454  	return b.String()
   455  }
   456  
   457  // needsEscape determines whether the input string needs
   458  // to be escaped or not, without doing any allocations.
   459  func needsEscape(s string) bool {
   460  	for _, r := range s {
   461  		if !strconv.IsPrint(r) || r == '\\' || r == '"' {
   462  			return true
   463  		}
   464  	}
   465  	return false
   466  }
   467  
   468  // invokeMarshaler returns panic-safe output from the Marshaler.MarshalLog() method.
   469  func invokeMarshaler(m Marshaler) (ret interface{}) {
   470  	defer func() {
   471  		if r := recover(); r != nil {
   472  			ret = fmt.Sprintf("<panic: %s>", r)
   473  		}
   474  	}()
   475  	return m.MarshalLog()
   476  }
   477  
   478  // invokeStringer returns panic-safe output from the fmt.Stringer.String() method.
   479  func invokeStringer(s fmt.Stringer) (ret string) {
   480  	defer func() {
   481  		if r := recover(); r != nil {
   482  			ret = fmt.Sprintf("<panic: %s>", r)
   483  		}
   484  	}()
   485  	return s.String()
   486  }
   487  
   488  // invokeError returns panic-safe output from the error. Error() method.
   489  func invokeError(e error) (ret string) {
   490  	defer func() {
   491  		if r := recover(); r != nil {
   492  			ret = fmt.Sprintf("<panic: %s>", r)
   493  		}
   494  	}()
   495  	return e.Error()
   496  }