github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/log/handler.go (about)

     1  package log
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"log/slog"
     8  	"math/big"
     9  	"reflect"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/holiman/uint256"
    14  )
    15  
    16  type discardHandler struct{}
    17  
    18  // DiscardHandler returns a no-op handler
    19  func DiscardHandler() slog.Handler {
    20  	return &discardHandler{}
    21  }
    22  
    23  func (h *discardHandler) Handle(_ context.Context, r slog.Record) error {
    24  	return nil
    25  }
    26  
    27  func (h *discardHandler) Enabled(_ context.Context, level slog.Level) bool {
    28  	return false
    29  }
    30  
    31  func (h *discardHandler) WithGroup(name string) slog.Handler {
    32  	panic("not implemented")
    33  }
    34  
    35  func (h *discardHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
    36  	return &discardHandler{}
    37  }
    38  
    39  type TerminalHandler struct {
    40  	mu       sync.Mutex
    41  	wr       io.Writer
    42  	lvl      slog.Level
    43  	useColor bool
    44  	attrs    []slog.Attr
    45  	// fieldPadding is a map with maximum field value lengths seen until now
    46  	// to allow padding log contexts in a bit smarter way.
    47  	fieldPadding map[string]int
    48  
    49  	buf []byte
    50  }
    51  
    52  // NewTerminalHandler returns a handler which formats log records at all levels optimized for human readability on
    53  // a terminal with color-coded level output and terser human friendly timestamp.
    54  // This format should only be used for interactive programs or while developing.
    55  //
    56  //	[LEVEL] [TIME] MESSAGE key=value key=value ...
    57  //
    58  // Example:
    59  //
    60  //	[DBUG] [May 16 20:58:45] remove route ns=haproxy addr=127.0.0.1:50002
    61  func NewTerminalHandler(wr io.Writer, useColor bool) *TerminalHandler {
    62  	return NewTerminalHandlerWithLevel(wr, levelMaxVerbosity, useColor)
    63  }
    64  
    65  // NewTerminalHandlerWithLevel returns the same handler as NewTerminalHandler but only outputs
    66  // records which are less than or equal to the specified verbosity level.
    67  func NewTerminalHandlerWithLevel(wr io.Writer, lvl slog.Level, useColor bool) *TerminalHandler {
    68  	return &TerminalHandler{
    69  		wr:           wr,
    70  		lvl:          lvl,
    71  		useColor:     useColor,
    72  		fieldPadding: make(map[string]int),
    73  	}
    74  }
    75  
    76  func (h *TerminalHandler) Handle(_ context.Context, r slog.Record) error {
    77  	h.mu.Lock()
    78  	defer h.mu.Unlock()
    79  	buf := h.format(h.buf, r, h.useColor)
    80  	h.wr.Write(buf)
    81  	h.buf = buf[:0]
    82  	return nil
    83  }
    84  
    85  func (h *TerminalHandler) Enabled(_ context.Context, level slog.Level) bool {
    86  	return level >= h.lvl
    87  }
    88  
    89  func (h *TerminalHandler) WithGroup(name string) slog.Handler {
    90  	panic("not implemented")
    91  }
    92  
    93  func (h *TerminalHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
    94  	return &TerminalHandler{
    95  		wr:           h.wr,
    96  		lvl:          h.lvl,
    97  		useColor:     h.useColor,
    98  		attrs:        append(h.attrs, attrs...),
    99  		fieldPadding: make(map[string]int),
   100  	}
   101  }
   102  
   103  // ResetFieldPadding zeroes the field-padding for all attribute pairs.
   104  func (t *TerminalHandler) ResetFieldPadding() {
   105  	t.mu.Lock()
   106  	t.fieldPadding = make(map[string]int)
   107  	t.mu.Unlock()
   108  }
   109  
   110  type leveler struct{ minLevel slog.Level }
   111  
   112  func (l *leveler) Level() slog.Level {
   113  	return l.minLevel
   114  }
   115  
   116  // JSONHandler returns a handler which prints records in JSON format.
   117  func JSONHandler(wr io.Writer) slog.Handler {
   118  	return JSONHandlerWithLevel(wr, levelMaxVerbosity)
   119  }
   120  
   121  // JSONHandlerWithLevel returns a handler which prints records in JSON format that are less than or equal to
   122  // the specified verbosity level.
   123  func JSONHandlerWithLevel(wr io.Writer, level slog.Level) slog.Handler {
   124  	return slog.NewJSONHandler(wr, &slog.HandlerOptions{
   125  		ReplaceAttr: builtinReplaceJSON,
   126  		Level:       &leveler{level},
   127  	})
   128  }
   129  
   130  // LogfmtHandler returns a handler which prints records in logfmt format, an easy machine-parseable but human-readable
   131  // format for key/value pairs.
   132  //
   133  // For more details see: http://godoc.org/github.com/kr/logfmt
   134  func LogfmtHandler(wr io.Writer) slog.Handler {
   135  	return slog.NewTextHandler(wr, &slog.HandlerOptions{
   136  		ReplaceAttr: builtinReplaceLogfmt,
   137  	})
   138  }
   139  
   140  // LogfmtHandlerWithLevel returns the same handler as LogfmtHandler but it only outputs
   141  // records which are less than or equal to the specified verbosity level.
   142  func LogfmtHandlerWithLevel(wr io.Writer, level slog.Level) slog.Handler {
   143  	return slog.NewTextHandler(wr, &slog.HandlerOptions{
   144  		ReplaceAttr: builtinReplaceLogfmt,
   145  		Level:       &leveler{level},
   146  	})
   147  }
   148  
   149  func builtinReplaceLogfmt(_ []string, attr slog.Attr) slog.Attr {
   150  	return builtinReplace(nil, attr, true)
   151  }
   152  
   153  func builtinReplaceJSON(_ []string, attr slog.Attr) slog.Attr {
   154  	return builtinReplace(nil, attr, false)
   155  }
   156  
   157  func builtinReplace(_ []string, attr slog.Attr, logfmt bool) slog.Attr {
   158  	switch attr.Key {
   159  	case slog.TimeKey:
   160  		if attr.Value.Kind() == slog.KindTime {
   161  			if logfmt {
   162  				return slog.String("t", attr.Value.Time().Format(timeFormat))
   163  			} else {
   164  				return slog.Attr{Key: "t", Value: attr.Value}
   165  			}
   166  		}
   167  	case slog.LevelKey:
   168  		if l, ok := attr.Value.Any().(slog.Level); ok {
   169  			attr = slog.Any("lvl", LevelString(l))
   170  			return attr
   171  		}
   172  	}
   173  
   174  	switch v := attr.Value.Any().(type) {
   175  	case time.Time:
   176  		if logfmt {
   177  			attr = slog.String(attr.Key, v.Format(timeFormat))
   178  		}
   179  	case *big.Int:
   180  		if v == nil {
   181  			attr.Value = slog.StringValue("<nil>")
   182  		} else {
   183  			attr.Value = slog.StringValue(v.String())
   184  		}
   185  	case *uint256.Int:
   186  		if v == nil {
   187  			attr.Value = slog.StringValue("<nil>")
   188  		} else {
   189  			attr.Value = slog.StringValue(v.Dec())
   190  		}
   191  	case fmt.Stringer:
   192  		if v == nil || (reflect.ValueOf(v).Kind() == reflect.Pointer && reflect.ValueOf(v).IsNil()) {
   193  			attr.Value = slog.StringValue("<nil>")
   194  		} else {
   195  			attr.Value = slog.StringValue(v.String())
   196  		}
   197  	}
   198  	return attr
   199  }