github.com/calmw/ethereum@v0.1.1/log/format.go (about)

     1  package log
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"math/big"
     8  	"reflect"
     9  	"strconv"
    10  	"strings"
    11  	"sync"
    12  	"sync/atomic"
    13  	"time"
    14  	"unicode/utf8"
    15  
    16  	"github.com/holiman/uint256"
    17  )
    18  
    19  const (
    20  	timeFormat        = "2006-01-02T15:04:05-0700"
    21  	termTimeFormat    = "01-02|15:04:05.000"
    22  	floatFormat       = 'f'
    23  	termMsgJust       = 40
    24  	termCtxMaxPadding = 40
    25  )
    26  
    27  // locationTrims are trimmed for display to avoid unwieldy log lines.
    28  var locationTrims = []string{
    29  	"github.com/calmw/ethereum/",
    30  }
    31  
    32  // PrintOrigins sets or unsets log location (file:line) printing for terminal
    33  // format output.
    34  func PrintOrigins(print bool) {
    35  	if print {
    36  		atomic.StoreUint32(&locationEnabled, 1)
    37  	} else {
    38  		atomic.StoreUint32(&locationEnabled, 0)
    39  	}
    40  }
    41  
    42  // locationEnabled is an atomic flag controlling whether the terminal formatter
    43  // should append the log locations too when printing entries.
    44  var locationEnabled uint32
    45  
    46  // locationLength is the maxmimum path length encountered, which all logs are
    47  // padded to to aid in alignment.
    48  var locationLength uint32
    49  
    50  // fieldPadding is a global map with maximum field value lengths seen until now
    51  // to allow padding log contexts in a bit smarter way.
    52  var fieldPadding = make(map[string]int)
    53  
    54  // fieldPaddingLock is a global mutex protecting the field padding map.
    55  var fieldPaddingLock sync.RWMutex
    56  
    57  type Format interface {
    58  	Format(r *Record) []byte
    59  }
    60  
    61  // FormatFunc returns a new Format object which uses
    62  // the given function to perform record formatting.
    63  func FormatFunc(f func(*Record) []byte) Format {
    64  	return formatFunc(f)
    65  }
    66  
    67  type formatFunc func(*Record) []byte
    68  
    69  func (f formatFunc) Format(r *Record) []byte {
    70  	return f(r)
    71  }
    72  
    73  // TerminalStringer is an analogous interface to the stdlib stringer, allowing
    74  // own types to have custom shortened serialization formats when printed to the
    75  // screen.
    76  type TerminalStringer interface {
    77  	TerminalString() string
    78  }
    79  
    80  // TerminalFormat formats log records optimized for human readability on
    81  // a terminal with color-coded level output and terser human friendly timestamp.
    82  // This format should only be used for interactive programs or while developing.
    83  //
    84  //	[LEVEL] [TIME] MESSAGE key=value key=value ...
    85  //
    86  // Example:
    87  //
    88  //	[DBUG] [May 16 20:58:45] remove route ns=haproxy addr=127.0.0.1:50002
    89  func TerminalFormat(usecolor bool) Format {
    90  	return FormatFunc(func(r *Record) []byte {
    91  		msg := escapeMessage(r.Msg)
    92  		var color = 0
    93  		if usecolor {
    94  			switch r.Lvl {
    95  			case LvlCrit:
    96  				color = 35
    97  			case LvlError:
    98  				color = 31
    99  			case LvlWarn:
   100  				color = 33
   101  			case LvlInfo:
   102  				color = 32
   103  			case LvlDebug:
   104  				color = 36
   105  			case LvlTrace:
   106  				color = 34
   107  			}
   108  		}
   109  
   110  		b := &bytes.Buffer{}
   111  		lvl := r.Lvl.AlignedString()
   112  		if atomic.LoadUint32(&locationEnabled) != 0 {
   113  			// Log origin printing was requested, format the location path and line number
   114  			location := fmt.Sprintf("%+v", r.Call)
   115  			for _, prefix := range locationTrims {
   116  				location = strings.TrimPrefix(location, prefix)
   117  			}
   118  			// Maintain the maximum location length for fancyer alignment
   119  			align := int(atomic.LoadUint32(&locationLength))
   120  			if align < len(location) {
   121  				align = len(location)
   122  				atomic.StoreUint32(&locationLength, uint32(align))
   123  			}
   124  			padding := strings.Repeat(" ", align-len(location))
   125  
   126  			// Assemble and print the log heading
   127  			if color > 0 {
   128  				fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s|%s]%s %s ", color, lvl, r.Time.Format(termTimeFormat), location, padding, msg)
   129  			} else {
   130  				fmt.Fprintf(b, "%s[%s|%s]%s %s ", lvl, r.Time.Format(termTimeFormat), location, padding, msg)
   131  			}
   132  		} else {
   133  			if color > 0 {
   134  				fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), msg)
   135  			} else {
   136  				fmt.Fprintf(b, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), msg)
   137  			}
   138  		}
   139  		// try to justify the log output for short messages
   140  		length := utf8.RuneCountInString(msg)
   141  		if len(r.Ctx) > 0 && length < termMsgJust {
   142  			b.Write(bytes.Repeat([]byte{' '}, termMsgJust-length))
   143  		}
   144  		// print the keys logfmt style
   145  		logfmt(b, r.Ctx, color, true)
   146  		return b.Bytes()
   147  	})
   148  }
   149  
   150  // LogfmtFormat prints records in logfmt format, an easy machine-parseable but human-readable
   151  // format for key/value pairs.
   152  //
   153  // For more details see: http://godoc.org/github.com/kr/logfmt
   154  func LogfmtFormat() Format {
   155  	return FormatFunc(func(r *Record) []byte {
   156  		common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg}
   157  		buf := &bytes.Buffer{}
   158  		logfmt(buf, append(common, r.Ctx...), 0, false)
   159  		return buf.Bytes()
   160  	})
   161  }
   162  
   163  func logfmt(buf *bytes.Buffer, ctx []interface{}, color int, term bool) {
   164  	for i := 0; i < len(ctx); i += 2 {
   165  		if i != 0 {
   166  			buf.WriteByte(' ')
   167  		}
   168  
   169  		k, ok := ctx[i].(string)
   170  		v := formatLogfmtValue(ctx[i+1], term)
   171  		if !ok {
   172  			k, v = errorKey, fmt.Sprintf("%+T is not a string key", ctx[i])
   173  		} else {
   174  			k = escapeString(k)
   175  		}
   176  
   177  		// XXX: we should probably check that all of your key bytes aren't invalid
   178  		fieldPaddingLock.RLock()
   179  		padding := fieldPadding[k]
   180  		fieldPaddingLock.RUnlock()
   181  
   182  		length := utf8.RuneCountInString(v)
   183  		if padding < length && length <= termCtxMaxPadding {
   184  			padding = length
   185  
   186  			fieldPaddingLock.Lock()
   187  			fieldPadding[k] = padding
   188  			fieldPaddingLock.Unlock()
   189  		}
   190  		if color > 0 {
   191  			fmt.Fprintf(buf, "\x1b[%dm%s\x1b[0m=", color, k)
   192  		} else {
   193  			buf.WriteString(k)
   194  			buf.WriteByte('=')
   195  		}
   196  		buf.WriteString(v)
   197  		if i < len(ctx)-2 && padding > length {
   198  			buf.Write(bytes.Repeat([]byte{' '}, padding-length))
   199  		}
   200  	}
   201  	buf.WriteByte('\n')
   202  }
   203  
   204  // JSONFormat formats log records as JSON objects separated by newlines.
   205  // It is the equivalent of JSONFormatEx(false, true).
   206  func JSONFormat() Format {
   207  	return JSONFormatEx(false, true)
   208  }
   209  
   210  // JSONFormatOrderedEx formats log records as JSON arrays. If pretty is true,
   211  // records will be pretty-printed. If lineSeparated is true, records
   212  // will be logged with a new line between each record.
   213  func JSONFormatOrderedEx(pretty, lineSeparated bool) Format {
   214  	jsonMarshal := json.Marshal
   215  	if pretty {
   216  		jsonMarshal = func(v interface{}) ([]byte, error) {
   217  			return json.MarshalIndent(v, "", "    ")
   218  		}
   219  	}
   220  	return FormatFunc(func(r *Record) []byte {
   221  		props := map[string]interface{}{
   222  			r.KeyNames.Time: r.Time,
   223  			r.KeyNames.Lvl:  r.Lvl.String(),
   224  			r.KeyNames.Msg:  r.Msg,
   225  		}
   226  
   227  		ctx := make([]string, len(r.Ctx))
   228  		for i := 0; i < len(r.Ctx); i += 2 {
   229  			if k, ok := r.Ctx[i].(string); ok {
   230  				ctx[i] = k
   231  				ctx[i+1] = formatLogfmtValue(r.Ctx[i+1], true)
   232  			} else {
   233  				props[errorKey] = fmt.Sprintf("%+T is not a string key,", r.Ctx[i])
   234  			}
   235  		}
   236  		props[r.KeyNames.Ctx] = ctx
   237  
   238  		b, err := jsonMarshal(props)
   239  		if err != nil {
   240  			b, _ = jsonMarshal(map[string]string{
   241  				errorKey: err.Error(),
   242  			})
   243  			return b
   244  		}
   245  		if lineSeparated {
   246  			b = append(b, '\n')
   247  		}
   248  		return b
   249  	})
   250  }
   251  
   252  // JSONFormatEx formats log records as JSON objects. If pretty is true,
   253  // records will be pretty-printed. If lineSeparated is true, records
   254  // will be logged with a new line between each record.
   255  func JSONFormatEx(pretty, lineSeparated bool) Format {
   256  	jsonMarshal := json.Marshal
   257  	if pretty {
   258  		jsonMarshal = func(v interface{}) ([]byte, error) {
   259  			return json.MarshalIndent(v, "", "    ")
   260  		}
   261  	}
   262  
   263  	return FormatFunc(func(r *Record) []byte {
   264  		props := map[string]interface{}{
   265  			r.KeyNames.Time: r.Time,
   266  			r.KeyNames.Lvl:  r.Lvl.String(),
   267  			r.KeyNames.Msg:  r.Msg,
   268  		}
   269  
   270  		for i := 0; i < len(r.Ctx); i += 2 {
   271  			k, ok := r.Ctx[i].(string)
   272  			if !ok {
   273  				props[errorKey] = fmt.Sprintf("%+T is not a string key", r.Ctx[i])
   274  			} else {
   275  				props[k] = formatJSONValue(r.Ctx[i+1])
   276  			}
   277  		}
   278  
   279  		b, err := jsonMarshal(props)
   280  		if err != nil {
   281  			b, _ = jsonMarshal(map[string]string{
   282  				errorKey: err.Error(),
   283  			})
   284  			return b
   285  		}
   286  
   287  		if lineSeparated {
   288  			b = append(b, '\n')
   289  		}
   290  
   291  		return b
   292  	})
   293  }
   294  
   295  func formatShared(value interface{}) (result interface{}) {
   296  	defer func() {
   297  		if err := recover(); err != nil {
   298  			if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() {
   299  				result = "nil"
   300  			} else {
   301  				panic(err)
   302  			}
   303  		}
   304  	}()
   305  
   306  	switch v := value.(type) {
   307  	case time.Time:
   308  		return v.Format(timeFormat)
   309  
   310  	case error:
   311  		return v.Error()
   312  
   313  	case fmt.Stringer:
   314  		return v.String()
   315  
   316  	default:
   317  		return v
   318  	}
   319  }
   320  
   321  func formatJSONValue(value interface{}) interface{} {
   322  	value = formatShared(value)
   323  	switch value.(type) {
   324  	case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string:
   325  		return value
   326  	default:
   327  		return fmt.Sprintf("%+v", value)
   328  	}
   329  }
   330  
   331  // formatValue formats a value for serialization
   332  func formatLogfmtValue(value interface{}, term bool) string {
   333  	if value == nil {
   334  		return "nil"
   335  	}
   336  
   337  	switch v := value.(type) {
   338  	case time.Time:
   339  		// Performance optimization: No need for escaping since the provided
   340  		// timeFormat doesn't have any escape characters, and escaping is
   341  		// expensive.
   342  		return v.Format(timeFormat)
   343  
   344  	case *big.Int:
   345  		// Big ints get consumed by the Stringer clause, so we need to handle
   346  		// them earlier on.
   347  		if v == nil {
   348  			return "<nil>"
   349  		}
   350  		return formatLogfmtBigInt(v)
   351  
   352  	case *uint256.Int:
   353  		// Uint256s get consumed by the Stringer clause, so we need to handle
   354  		// them earlier on.
   355  		if v == nil {
   356  			return "<nil>"
   357  		}
   358  		return formatLogfmtUint256(v)
   359  	}
   360  	if term {
   361  		if s, ok := value.(TerminalStringer); ok {
   362  			// Custom terminal stringer provided, use that
   363  			return escapeString(s.TerminalString())
   364  		}
   365  	}
   366  	value = formatShared(value)
   367  	switch v := value.(type) {
   368  	case bool:
   369  		return strconv.FormatBool(v)
   370  	case float32:
   371  		return strconv.FormatFloat(float64(v), floatFormat, 3, 64)
   372  	case float64:
   373  		return strconv.FormatFloat(v, floatFormat, 3, 64)
   374  	case int8:
   375  		return strconv.FormatInt(int64(v), 10)
   376  	case uint8:
   377  		return strconv.FormatInt(int64(v), 10)
   378  	case int16:
   379  		return strconv.FormatInt(int64(v), 10)
   380  	case uint16:
   381  		return strconv.FormatInt(int64(v), 10)
   382  	// Larger integers get thousands separators.
   383  	case int:
   384  		return FormatLogfmtInt64(int64(v))
   385  	case int32:
   386  		return FormatLogfmtInt64(int64(v))
   387  	case int64:
   388  		return FormatLogfmtInt64(v)
   389  	case uint:
   390  		return FormatLogfmtUint64(uint64(v))
   391  	case uint32:
   392  		return FormatLogfmtUint64(uint64(v))
   393  	case uint64:
   394  		return FormatLogfmtUint64(v)
   395  	case string:
   396  		return escapeString(v)
   397  	default:
   398  		return escapeString(fmt.Sprintf("%+v", value))
   399  	}
   400  }
   401  
   402  // FormatLogfmtInt64 formats n with thousand separators.
   403  func FormatLogfmtInt64(n int64) string {
   404  	if n < 0 {
   405  		return formatLogfmtUint64(uint64(-n), true)
   406  	}
   407  	return formatLogfmtUint64(uint64(n), false)
   408  }
   409  
   410  // FormatLogfmtUint64 formats n with thousand separators.
   411  func FormatLogfmtUint64(n uint64) string {
   412  	return formatLogfmtUint64(n, false)
   413  }
   414  
   415  func formatLogfmtUint64(n uint64, neg bool) string {
   416  	// Small numbers are fine as is
   417  	if n < 100000 {
   418  		if neg {
   419  			return strconv.Itoa(-int(n))
   420  		} else {
   421  			return strconv.Itoa(int(n))
   422  		}
   423  	}
   424  	// Large numbers should be split
   425  	const maxLength = 26
   426  
   427  	var (
   428  		out   = make([]byte, maxLength)
   429  		i     = maxLength - 1
   430  		comma = 0
   431  	)
   432  	for ; n > 0; i-- {
   433  		if comma == 3 {
   434  			comma = 0
   435  			out[i] = ','
   436  		} else {
   437  			comma++
   438  			out[i] = '0' + byte(n%10)
   439  			n /= 10
   440  		}
   441  	}
   442  	if neg {
   443  		out[i] = '-'
   444  		i--
   445  	}
   446  	return string(out[i+1:])
   447  }
   448  
   449  // formatLogfmtBigInt formats n with thousand separators.
   450  func formatLogfmtBigInt(n *big.Int) string {
   451  	if n.IsUint64() {
   452  		return FormatLogfmtUint64(n.Uint64())
   453  	}
   454  	if n.IsInt64() {
   455  		return FormatLogfmtInt64(n.Int64())
   456  	}
   457  
   458  	var (
   459  		text  = n.String()
   460  		buf   = make([]byte, len(text)+len(text)/3)
   461  		comma = 0
   462  		i     = len(buf) - 1
   463  	)
   464  	for j := len(text) - 1; j >= 0; j, i = j-1, i-1 {
   465  		c := text[j]
   466  
   467  		switch {
   468  		case c == '-':
   469  			buf[i] = c
   470  		case comma == 3:
   471  			buf[i] = ','
   472  			i--
   473  			comma = 0
   474  			fallthrough
   475  		default:
   476  			buf[i] = c
   477  			comma++
   478  		}
   479  	}
   480  	return string(buf[i+1:])
   481  }
   482  
   483  // formatLogfmtUint256 formats n with thousand separators.
   484  func formatLogfmtUint256(n *uint256.Int) string {
   485  	if n.IsUint64() {
   486  		return FormatLogfmtUint64(n.Uint64())
   487  	}
   488  	var (
   489  		text  = n.Dec()
   490  		buf   = make([]byte, len(text)+len(text)/3)
   491  		comma = 0
   492  		i     = len(buf) - 1
   493  	)
   494  	for j := len(text) - 1; j >= 0; j, i = j-1, i-1 {
   495  		c := text[j]
   496  
   497  		switch {
   498  		case c == '-':
   499  			buf[i] = c
   500  		case comma == 3:
   501  			buf[i] = ','
   502  			i--
   503  			comma = 0
   504  			fallthrough
   505  		default:
   506  			buf[i] = c
   507  			comma++
   508  		}
   509  	}
   510  	return string(buf[i+1:])
   511  }
   512  
   513  // escapeString checks if the provided string needs escaping/quoting, and
   514  // calls strconv.Quote if needed
   515  func escapeString(s string) string {
   516  	needsQuoting := false
   517  	for _, r := range s {
   518  		// We quote everything below " (0x22) and above~ (0x7E), plus equal-sign
   519  		if r <= '"' || r > '~' || r == '=' {
   520  			needsQuoting = true
   521  			break
   522  		}
   523  	}
   524  	if !needsQuoting {
   525  		return s
   526  	}
   527  	return strconv.Quote(s)
   528  }
   529  
   530  // escapeMessage checks if the provided string needs escaping/quoting, similarly
   531  // to escapeString. The difference is that this method is more lenient: it allows
   532  // for spaces and linebreaks to occur without needing quoting.
   533  func escapeMessage(s string) string {
   534  	needsQuoting := false
   535  	for _, r := range s {
   536  		// Allow CR/LF/TAB. This is to make multi-line messages work.
   537  		if r == '\r' || r == '\n' || r == '\t' {
   538  			continue
   539  		}
   540  		// We quote everything below <space> (0x20) and above~ (0x7E),
   541  		// plus equal-sign
   542  		if r < ' ' || r > '~' || r == '=' {
   543  			needsQuoting = true
   544  			break
   545  		}
   546  	}
   547  	if !needsQuoting {
   548  		return s
   549  	}
   550  	return strconv.Quote(s)
   551  }