github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/internal/testlog/testlog.go (about)

     1  // Copyright 2019 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package testlog provides a log handler for unit tests.
    18  package testlog
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"fmt"
    24  	"log/slog"
    25  	"sync"
    26  	"testing"
    27  
    28  	"github.com/ethereum/go-ethereum/log"
    29  )
    30  
    31  const (
    32  	termTimeFormat = "01-02|15:04:05.000"
    33  )
    34  
    35  // logger implements log.Logger such that all output goes to the unit test log via
    36  // t.Logf(). All methods in between logger.Trace, logger.Debug, etc. are marked as test
    37  // helpers, so the file and line number in unit test output correspond to the call site
    38  // which emitted the log message.
    39  type logger struct {
    40  	t  *testing.T
    41  	l  log.Logger
    42  	mu *sync.Mutex
    43  	h  *bufHandler
    44  }
    45  
    46  type bufHandler struct {
    47  	buf   []slog.Record
    48  	attrs []slog.Attr
    49  	level slog.Level
    50  	mu    sync.Mutex
    51  }
    52  
    53  func (h *bufHandler) Handle(_ context.Context, r slog.Record) error {
    54  	h.mu.Lock()
    55  	defer h.mu.Unlock()
    56  	h.buf = append(h.buf, r)
    57  	return nil
    58  }
    59  
    60  func (h *bufHandler) Enabled(_ context.Context, lvl slog.Level) bool {
    61  	return lvl <= h.level
    62  }
    63  
    64  func (h *bufHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
    65  	h.mu.Lock()
    66  	defer h.mu.Unlock()
    67  	records := make([]slog.Record, len(h.buf))
    68  	copy(records[:], h.buf[:])
    69  	return &bufHandler{
    70  		buf:   records,
    71  		attrs: append(h.attrs, attrs...),
    72  		level: h.level,
    73  	}
    74  }
    75  
    76  func (h *bufHandler) WithGroup(_ string) slog.Handler {
    77  	panic("not implemented")
    78  }
    79  
    80  // Logger returns a logger which logs to the unit test log of t.
    81  func Logger(t *testing.T, level slog.Level) log.Logger {
    82  	handler := bufHandler{
    83  		buf:   []slog.Record{},
    84  		attrs: []slog.Attr{},
    85  		level: level,
    86  	}
    87  	return &logger{
    88  		t:  t,
    89  		l:  log.NewLogger(&handler),
    90  		mu: new(sync.Mutex),
    91  		h:  &handler,
    92  	}
    93  }
    94  
    95  // LoggerWithHandler returns
    96  func LoggerWithHandler(t *testing.T, handler slog.Handler) log.Logger {
    97  	var bh bufHandler
    98  	return &logger{
    99  		t:  t,
   100  		l:  log.NewLogger(handler),
   101  		mu: new(sync.Mutex),
   102  		h:  &bh,
   103  	}
   104  }
   105  
   106  func (l *logger) Handler() slog.Handler {
   107  	return l.l.Handler()
   108  }
   109  
   110  func (l *logger) Write(level slog.Level, msg string, ctx ...interface{}) {}
   111  
   112  func (l *logger) Enabled(ctx context.Context, level slog.Level) bool {
   113  	return l.l.Enabled(ctx, level)
   114  }
   115  
   116  func (l *logger) Trace(msg string, ctx ...interface{}) {
   117  	l.t.Helper()
   118  	l.mu.Lock()
   119  	defer l.mu.Unlock()
   120  	l.l.Trace(msg, ctx...)
   121  	l.flush()
   122  }
   123  
   124  func (l *logger) Log(level slog.Level, msg string, ctx ...interface{}) {
   125  	l.t.Helper()
   126  	l.mu.Lock()
   127  	defer l.mu.Unlock()
   128  	l.l.Log(level, msg, ctx...)
   129  	l.flush()
   130  }
   131  
   132  func (l *logger) Debug(msg string, ctx ...interface{}) {
   133  	l.t.Helper()
   134  	l.mu.Lock()
   135  	defer l.mu.Unlock()
   136  	l.l.Debug(msg, ctx...)
   137  	l.flush()
   138  }
   139  
   140  func (l *logger) Info(msg string, ctx ...interface{}) {
   141  	l.t.Helper()
   142  	l.mu.Lock()
   143  	defer l.mu.Unlock()
   144  	l.l.Info(msg, ctx...)
   145  	l.flush()
   146  }
   147  
   148  func (l *logger) Warn(msg string, ctx ...interface{}) {
   149  	l.t.Helper()
   150  	l.mu.Lock()
   151  	defer l.mu.Unlock()
   152  	l.l.Warn(msg, ctx...)
   153  	l.flush()
   154  }
   155  
   156  func (l *logger) Error(msg string, ctx ...interface{}) {
   157  	l.t.Helper()
   158  	l.mu.Lock()
   159  	defer l.mu.Unlock()
   160  	l.l.Error(msg, ctx...)
   161  	l.flush()
   162  }
   163  
   164  func (l *logger) Crit(msg string, ctx ...interface{}) {
   165  	l.t.Helper()
   166  	l.mu.Lock()
   167  	defer l.mu.Unlock()
   168  	l.l.Crit(msg, ctx...)
   169  	l.flush()
   170  }
   171  
   172  func (l *logger) With(ctx ...interface{}) log.Logger {
   173  	return &logger{l.t, l.l.With(ctx...), l.mu, l.h}
   174  }
   175  
   176  func (l *logger) New(ctx ...interface{}) log.Logger {
   177  	return l.With(ctx...)
   178  }
   179  
   180  // terminalFormat formats a message similarly to the NewTerminalHandler in the log package.
   181  // The difference is that terminalFormat does not escape messages/attributes and does not pad attributes.
   182  func (h *bufHandler) terminalFormat(r slog.Record) string {
   183  	buf := &bytes.Buffer{}
   184  	lvl := log.LevelAlignedString(r.Level)
   185  	attrs := []slog.Attr{}
   186  	r.Attrs(func(attr slog.Attr) bool {
   187  		attrs = append(attrs, attr)
   188  		return true
   189  	})
   190  
   191  	attrs = append(h.attrs, attrs...)
   192  
   193  	fmt.Fprintf(buf, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Message)
   194  	if length := len(r.Message); length < 40 {
   195  		buf.Write(bytes.Repeat([]byte{' '}, 40-length))
   196  	}
   197  
   198  	for _, attr := range attrs {
   199  		fmt.Fprintf(buf, " %s=%s", attr.Key, string(log.FormatSlogValue(attr.Value, nil)))
   200  	}
   201  	buf.WriteByte('\n')
   202  	return buf.String()
   203  }
   204  
   205  // flush writes all buffered messages and clears the buffer.
   206  func (l *logger) flush() {
   207  	l.t.Helper()
   208  	l.h.mu.Lock()
   209  	defer l.h.mu.Unlock()
   210  	for _, r := range l.h.buf {
   211  		l.t.Logf("%s", l.h.terminalFormat(r))
   212  	}
   213  	l.h.buf = nil
   214  }