github.com/ethereum/go-ethereum@v1.14.3/log/handler_glog.go (about)

     1  // Copyright 2017 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 log
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"log/slog"
    24  	"maps"
    25  	"regexp"
    26  	"runtime"
    27  	"strconv"
    28  	"strings"
    29  	"sync"
    30  	"sync/atomic"
    31  )
    32  
    33  // errVmoduleSyntax is returned when a user vmodule pattern is invalid.
    34  var errVmoduleSyntax = errors.New("expect comma-separated list of filename=N")
    35  
    36  // GlogHandler is a log handler that mimics the filtering features of Google's
    37  // glog logger: setting global log levels; overriding with callsite pattern
    38  // matches; and requesting backtraces at certain positions.
    39  type GlogHandler struct {
    40  	origin slog.Handler // The origin handler this wraps
    41  
    42  	level    atomic.Int32 // Current log level, atomically accessible
    43  	override atomic.Bool  // Flag whether overrides are used, atomically accessible
    44  
    45  	patterns  []pattern              // Current list of patterns to override with
    46  	siteCache map[uintptr]slog.Level // Cache of callsite pattern evaluations
    47  	location  string                 // file:line location where to do a stackdump at
    48  	lock      sync.RWMutex           // Lock protecting the override pattern list
    49  }
    50  
    51  // NewGlogHandler creates a new log handler with filtering functionality similar
    52  // to Google's glog logger. The returned handler implements Handler.
    53  func NewGlogHandler(h slog.Handler) *GlogHandler {
    54  	return &GlogHandler{
    55  		origin: h,
    56  	}
    57  }
    58  
    59  // pattern contains a filter for the Vmodule option, holding a verbosity level
    60  // and a file pattern to match.
    61  type pattern struct {
    62  	pattern *regexp.Regexp
    63  	level   slog.Level
    64  }
    65  
    66  // Verbosity sets the glog verbosity ceiling. The verbosity of individual packages
    67  // and source files can be raised using Vmodule.
    68  func (h *GlogHandler) Verbosity(level slog.Level) {
    69  	h.level.Store(int32(level))
    70  }
    71  
    72  // Vmodule sets the glog verbosity pattern.
    73  //
    74  // The syntax of the argument is a comma-separated list of pattern=N, where the
    75  // pattern is a literal file name or "glob" pattern matching and N is a V level.
    76  //
    77  // For instance:
    78  //
    79  //	pattern="gopher.go=3"
    80  //	 sets the V level to 3 in all Go files named "gopher.go"
    81  //
    82  //	pattern="foo=3"
    83  //	 sets V to 3 in all files of any packages whose import path ends in "foo"
    84  //
    85  //	pattern="foo/*=3"
    86  //	 sets V to 3 in all files of any packages whose import path contains "foo"
    87  func (h *GlogHandler) Vmodule(ruleset string) error {
    88  	var filter []pattern
    89  	for _, rule := range strings.Split(ruleset, ",") {
    90  		// Empty strings such as from a trailing comma can be ignored
    91  		if len(rule) == 0 {
    92  			continue
    93  		}
    94  		// Ensure we have a pattern = level filter rule
    95  		parts := strings.Split(rule, "=")
    96  		if len(parts) != 2 {
    97  			return errVmoduleSyntax
    98  		}
    99  		parts[0] = strings.TrimSpace(parts[0])
   100  		parts[1] = strings.TrimSpace(parts[1])
   101  		if len(parts[0]) == 0 || len(parts[1]) == 0 {
   102  			return errVmoduleSyntax
   103  		}
   104  		// Parse the level and if correct, assemble the filter rule
   105  		l, err := strconv.Atoi(parts[1])
   106  		if err != nil {
   107  			return errVmoduleSyntax
   108  		}
   109  		level := FromLegacyLevel(l)
   110  
   111  		if level == LevelCrit {
   112  			continue // Ignore. It's harmless but no point in paying the overhead.
   113  		}
   114  		// Compile the rule pattern into a regular expression
   115  		matcher := ".*"
   116  		for _, comp := range strings.Split(parts[0], "/") {
   117  			if comp == "*" {
   118  				matcher += "(/.*)?"
   119  			} else if comp != "" {
   120  				matcher += "/" + regexp.QuoteMeta(comp)
   121  			}
   122  		}
   123  		if !strings.HasSuffix(parts[0], ".go") {
   124  			matcher += "/[^/]+\\.go"
   125  		}
   126  		matcher = matcher + "$"
   127  
   128  		re, _ := regexp.Compile(matcher)
   129  		filter = append(filter, pattern{re, level})
   130  	}
   131  	// Swap out the vmodule pattern for the new filter system
   132  	h.lock.Lock()
   133  	defer h.lock.Unlock()
   134  
   135  	h.patterns = filter
   136  	h.siteCache = make(map[uintptr]slog.Level)
   137  	h.override.Store(len(filter) != 0)
   138  
   139  	return nil
   140  }
   141  
   142  func (h *GlogHandler) Enabled(ctx context.Context, lvl slog.Level) bool {
   143  	// fast-track skipping logging if override not enabled and the provided verbosity is above configured
   144  	return h.override.Load() || slog.Level(h.level.Load()) <= lvl
   145  }
   146  
   147  func (h *GlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
   148  	h.lock.RLock()
   149  	siteCache := maps.Clone(h.siteCache)
   150  	h.lock.RUnlock()
   151  
   152  	patterns := []pattern{}
   153  	patterns = append(patterns, h.patterns...)
   154  
   155  	res := GlogHandler{
   156  		origin:    h.origin.WithAttrs(attrs),
   157  		patterns:  patterns,
   158  		siteCache: siteCache,
   159  		location:  h.location,
   160  	}
   161  
   162  	res.level.Store(h.level.Load())
   163  	res.override.Store(h.override.Load())
   164  	return &res
   165  }
   166  
   167  func (h *GlogHandler) WithGroup(name string) slog.Handler {
   168  	panic("not implemented")
   169  }
   170  
   171  // Log implements Handler.Log, filtering a log record through the global, local
   172  // and backtrace filters, finally emitting it if either allow it through.
   173  func (h *GlogHandler) Handle(_ context.Context, r slog.Record) error {
   174  	// If the global log level allows, fast track logging
   175  	if slog.Level(h.level.Load()) <= r.Level {
   176  		return h.origin.Handle(context.Background(), r)
   177  	}
   178  
   179  	// Check callsite cache for previously calculated log levels
   180  	h.lock.RLock()
   181  	lvl, ok := h.siteCache[r.PC]
   182  	h.lock.RUnlock()
   183  
   184  	// If we didn't cache the callsite yet, calculate it
   185  	if !ok {
   186  		h.lock.Lock()
   187  
   188  		fs := runtime.CallersFrames([]uintptr{r.PC})
   189  		frame, _ := fs.Next()
   190  
   191  		for _, rule := range h.patterns {
   192  			if rule.pattern.MatchString(fmt.Sprintf("+%s", frame.File)) {
   193  				h.siteCache[r.PC], lvl, ok = rule.level, rule.level, true
   194  			}
   195  		}
   196  		// If no rule matched, remember to drop log the next time
   197  		if !ok {
   198  			h.siteCache[r.PC] = 0
   199  		}
   200  		h.lock.Unlock()
   201  	}
   202  	if lvl <= r.Level {
   203  		return h.origin.Handle(context.Background(), r)
   204  	}
   205  	return nil
   206  }