github.com/turingchain2020/turingchain@v1.1.21/common/log/log15/format.go (about)

     1  // Copyright Turing Corp. 2018 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  package log15
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/json"
    10  	"fmt"
    11  	"reflect"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  )
    17  
    18  const (
    19  	timeFormat     = "2006-01-02T15:04:05-0700"
    20  	termTimeFormat = "01-02|15:04:05"
    21  	floatFormat    = 'f'
    22  	termMsgJust    = 40
    23  )
    24  
    25  // Format  is the interface implemented by StreamHandler formatters.
    26  type Format interface {
    27  	Format(r *Record) []byte
    28  }
    29  
    30  // FormatFunc returns a new Format object which uses
    31  // the given function to perform record formatting.
    32  func FormatFunc(f func(*Record) []byte) Format {
    33  	return formatFunc(f)
    34  }
    35  
    36  type formatFunc func(*Record) []byte
    37  
    38  func (f formatFunc) Format(r *Record) []byte {
    39  	return f(r)
    40  }
    41  
    42  // TerminalFormat formats log records optimized for human readability on
    43  // a terminal with color-coded level output and terser human friendly timestamp.
    44  // This format should only be used for interactive programs or while developing.
    45  //
    46  //     [TIME] [LEVEL] MESAGE key=value key=value ...
    47  //
    48  // Example:
    49  //
    50  //     [May 16 20:58:45] [DBUG] remove route ns=haproxy addr=127.0.0.1:50002
    51  //
    52  func TerminalFormat() Format {
    53  	return FormatFunc(func(r *Record) []byte {
    54  		var color = 0
    55  		switch r.Lvl {
    56  		case LvlCrit:
    57  			color = 35
    58  		case LvlError:
    59  			color = 31
    60  		case LvlWarn:
    61  			color = 33
    62  		case LvlInfo:
    63  			color = 32
    64  		case LvlDebug:
    65  			color = 36
    66  		}
    67  
    68  		b := &bytes.Buffer{}
    69  		lvl := strings.ToUpper(r.Lvl.String())
    70  		if color > 0 {
    71  			fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), r.Msg)
    72  		} else {
    73  			fmt.Fprintf(b, "[%s] [%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Msg)
    74  		}
    75  
    76  		// try to justify the log output for short messages
    77  		if len(r.Ctx) > 0 && len(r.Msg) < termMsgJust {
    78  			b.Write(bytes.Repeat([]byte{' '}, termMsgJust-len(r.Msg)))
    79  		}
    80  
    81  		// print the keys logfmt style
    82  		logfmt(b, r.Ctx, color)
    83  		return b.Bytes()
    84  	})
    85  }
    86  
    87  // LogfmtFormat prints records in logfmt format, an easy machine-parseable but human-readable
    88  // format for key/value pairs.
    89  //
    90  // For more details see: http://godoc.org/github.com/kr/logfmt
    91  //
    92  func LogfmtFormat() Format {
    93  	return FormatFunc(func(r *Record) []byte {
    94  		common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg}
    95  		buf := &bytes.Buffer{}
    96  		logfmt(buf, append(common, r.Ctx...), 0)
    97  		return buf.Bytes()
    98  	})
    99  }
   100  
   101  func logfmt(buf *bytes.Buffer, ctx []interface{}, color int) {
   102  	for i := 0; i < len(ctx); i += 2 {
   103  		if i != 0 {
   104  			buf.WriteByte(' ')
   105  		}
   106  
   107  		k, ok := ctx[i].(string)
   108  		v := formatLogfmtValue(ctx[i+1])
   109  		if !ok {
   110  			k, v = errorKey, formatLogfmtValue(k)
   111  		}
   112  
   113  		// XXX: we should probably check that all of your key bytes aren't invalid
   114  		if color > 0 {
   115  			fmt.Fprintf(buf, "\x1b[%dm%s\x1b[0m=%s", color, k, v)
   116  		} else {
   117  			buf.WriteString(k)
   118  			buf.WriteByte('=')
   119  			buf.WriteString(v)
   120  		}
   121  	}
   122  
   123  	buf.WriteByte('\n')
   124  }
   125  
   126  // JSONFormat formats log records as JSON objects separated by newlines.
   127  // It is the equivalent of JSONFormatEx(false, true).
   128  func JSONFormat() Format {
   129  	return JSONFormatEx(false, true)
   130  }
   131  
   132  // JSONFormatEx formats log records as JSON objects. If pretty is true,
   133  // records will be pretty-printed. If lineSeparated is true, records
   134  // will be logged with a new line between each record.
   135  func JSONFormatEx(pretty, lineSeparated bool) Format {
   136  	jsonMarshal := json.Marshal
   137  	if pretty {
   138  		jsonMarshal = func(v interface{}) ([]byte, error) {
   139  			return json.MarshalIndent(v, "", "    ")
   140  		}
   141  	}
   142  
   143  	return FormatFunc(func(r *Record) []byte {
   144  		props := make(map[string]interface{})
   145  
   146  		props[r.KeyNames.Time] = r.Time
   147  		props[r.KeyNames.Lvl] = r.Lvl.String()
   148  		props[r.KeyNames.Msg] = r.Msg
   149  
   150  		for i := 0; i < len(r.Ctx); i += 2 {
   151  			k, ok := r.Ctx[i].(string)
   152  			if !ok {
   153  				props[errorKey] = fmt.Sprintf("%+v is not a string key", r.Ctx[i])
   154  			}
   155  			props[k] = formatJSONValue(r.Ctx[i+1])
   156  		}
   157  
   158  		b, err := jsonMarshal(props)
   159  		if err != nil {
   160  			b, _ = jsonMarshal(map[string]string{
   161  				errorKey: err.Error(),
   162  			})
   163  			return b
   164  		}
   165  
   166  		if lineSeparated {
   167  			b = append(b, '\n')
   168  		}
   169  
   170  		return b
   171  	})
   172  }
   173  
   174  func formatShared(value interface{}) (result interface{}) {
   175  	defer func() {
   176  		if err := recover(); err != nil {
   177  			if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() {
   178  				result = "nil"
   179  			} else {
   180  				panic(err)
   181  			}
   182  		}
   183  	}()
   184  
   185  	switch v := value.(type) {
   186  	case time.Time:
   187  		return v.Format(timeFormat)
   188  
   189  	case error:
   190  		return v.Error()
   191  
   192  	case fmt.Stringer:
   193  		return v.String()
   194  
   195  	default:
   196  		return v
   197  	}
   198  }
   199  
   200  func formatJSONValue(value interface{}) interface{} {
   201  	value = formatShared(value)
   202  	switch value.(type) {
   203  	case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string:
   204  		return value
   205  	default:
   206  		return fmt.Sprintf("%+v", value)
   207  	}
   208  }
   209  
   210  // formatValue formats a value for serialization
   211  func formatLogfmtValue(value interface{}) string {
   212  	if value == nil {
   213  		return "nil"
   214  	}
   215  
   216  	if t, ok := value.(time.Time); ok {
   217  		// Performance optimization: No need for escaping since the provided
   218  		// timeFormat doesn't have any escape characters, and escaping is
   219  		// expensive.
   220  		return t.Format(timeFormat)
   221  	}
   222  	value = formatShared(value)
   223  	switch v := value.(type) {
   224  	case bool:
   225  		return strconv.FormatBool(v)
   226  	case float32:
   227  		return strconv.FormatFloat(float64(v), floatFormat, 3, 64)
   228  	case float64:
   229  		return strconv.FormatFloat(v, floatFormat, 3, 64)
   230  	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
   231  		return fmt.Sprintf("%d", value)
   232  	case string:
   233  		return escapeString(v)
   234  	default:
   235  		return escapeString(fmt.Sprintf("%+v", value))
   236  	}
   237  }
   238  
   239  var stringBufPool = sync.Pool{
   240  	New: func() interface{} { return new(bytes.Buffer) },
   241  }
   242  
   243  func escapeString(s string) string {
   244  	needsQuotes := false
   245  	needsEscape := false
   246  	for _, r := range s {
   247  		if r <= ' ' || r == '=' || r == '"' {
   248  			needsQuotes = true
   249  		}
   250  		if r == '\\' || r == '"' || r == '\n' || r == '\r' || r == '\t' {
   251  			needsEscape = true
   252  		}
   253  	}
   254  	if !needsEscape && !needsQuotes {
   255  		return s
   256  	}
   257  	e := stringBufPool.Get().(*bytes.Buffer)
   258  	e.WriteByte('"')
   259  	for _, r := range s {
   260  		switch r {
   261  		case '\\', '"':
   262  			e.WriteByte('\\')
   263  			e.WriteByte(byte(r))
   264  		case '\n':
   265  			e.WriteString("\\n")
   266  		case '\r':
   267  			e.WriteString("\\r")
   268  		case '\t':
   269  			e.WriteString("\\t")
   270  		default:
   271  			e.WriteRune(r)
   272  		}
   273  	}
   274  	e.WriteByte('"')
   275  	var ret string
   276  	if needsQuotes {
   277  		ret = e.String()
   278  	} else {
   279  		ret = string(e.Bytes()[1 : e.Len()-1])
   280  	}
   281  	e.Reset()
   282  	stringBufPool.Put(e)
   283  	return ret
   284  }