github.com/theQRL/go-zond@v0.1.1/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  	"errors"
    21  	"fmt"
    22  	"regexp"
    23  	"runtime"
    24  	"strconv"
    25  	"strings"
    26  	"sync"
    27  	"sync/atomic"
    28  )
    29  
    30  // errVmoduleSyntax is returned when a user vmodule pattern is invalid.
    31  var errVmoduleSyntax = errors.New("expect comma-separated list of filename=N")
    32  
    33  // errTraceSyntax is returned when a user backtrace pattern is invalid.
    34  var errTraceSyntax = errors.New("expect file.go:234")
    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 Handler // The origin handler this wraps
    41  
    42  	level     atomic.Uint32 // Current log level, atomically accessible
    43  	override  atomic.Bool   // Flag whether overrides are used, atomically accessible
    44  	backtrace atomic.Bool   // Flag whether backtrace location is set
    45  
    46  	patterns  []pattern       // Current list of patterns to override with
    47  	siteCache map[uintptr]Lvl // Cache of callsite pattern evaluations
    48  	location  string          // file:line location where to do a stackdump at
    49  	lock      sync.RWMutex    // Lock protecting the override pattern list
    50  }
    51  
    52  // NewGlogHandler creates a new log handler with filtering functionality similar
    53  // to Google's glog logger. The returned handler implements Handler.
    54  func NewGlogHandler(h Handler) *GlogHandler {
    55  	return &GlogHandler{
    56  		origin: h,
    57  	}
    58  }
    59  
    60  // SetHandler updates the handler to write records to the specified sub-handler.
    61  func (h *GlogHandler) SetHandler(nh Handler) {
    62  	h.origin = nh
    63  }
    64  
    65  // pattern contains a filter for the Vmodule option, holding a verbosity level
    66  // and a file pattern to match.
    67  type pattern struct {
    68  	pattern *regexp.Regexp
    69  	level   Lvl
    70  }
    71  
    72  // Verbosity sets the glog verbosity ceiling. The verbosity of individual packages
    73  // and source files can be raised using Vmodule.
    74  func (h *GlogHandler) Verbosity(level Lvl) {
    75  	h.level.Store(uint32(level))
    76  }
    77  
    78  // Vmodule sets the glog verbosity pattern.
    79  //
    80  // The syntax of the argument is a comma-separated list of pattern=N, where the
    81  // pattern is a literal file name or "glob" pattern matching and N is a V level.
    82  //
    83  // For instance:
    84  //
    85  //	pattern="gopher.go=3"
    86  //	 sets the V level to 3 in all Go files named "gopher.go"
    87  //
    88  //	pattern="foo=3"
    89  //	 sets V to 3 in all files of any packages whose import path ends in "foo"
    90  //
    91  //	pattern="foo/*=3"
    92  //	 sets V to 3 in all files of any packages whose import path contains "foo"
    93  func (h *GlogHandler) Vmodule(ruleset string) error {
    94  	var filter []pattern
    95  	for _, rule := range strings.Split(ruleset, ",") {
    96  		// Empty strings such as from a trailing comma can be ignored
    97  		if len(rule) == 0 {
    98  			continue
    99  		}
   100  		// Ensure we have a pattern = level filter rule
   101  		parts := strings.Split(rule, "=")
   102  		if len(parts) != 2 {
   103  			return errVmoduleSyntax
   104  		}
   105  		parts[0] = strings.TrimSpace(parts[0])
   106  		parts[1] = strings.TrimSpace(parts[1])
   107  		if len(parts[0]) == 0 || len(parts[1]) == 0 {
   108  			return errVmoduleSyntax
   109  		}
   110  		// Parse the level and if correct, assemble the filter rule
   111  		level, err := strconv.Atoi(parts[1])
   112  		if err != nil {
   113  			return errVmoduleSyntax
   114  		}
   115  		if level <= 0 {
   116  			continue // Ignore. It's harmless but no point in paying the overhead.
   117  		}
   118  		// Compile the rule pattern into a regular expression
   119  		matcher := ".*"
   120  		for _, comp := range strings.Split(parts[0], "/") {
   121  			if comp == "*" {
   122  				matcher += "(/.*)?"
   123  			} else if comp != "" {
   124  				matcher += "/" + regexp.QuoteMeta(comp)
   125  			}
   126  		}
   127  		if !strings.HasSuffix(parts[0], ".go") {
   128  			matcher += "/[^/]+\\.go"
   129  		}
   130  		matcher = matcher + "$"
   131  
   132  		re, _ := regexp.Compile(matcher)
   133  		filter = append(filter, pattern{re, Lvl(level)})
   134  	}
   135  	// Swap out the vmodule pattern for the new filter system
   136  	h.lock.Lock()
   137  	defer h.lock.Unlock()
   138  
   139  	h.patterns = filter
   140  	h.siteCache = make(map[uintptr]Lvl)
   141  	h.override.Store(len(filter) != 0)
   142  	// Enable location storage (globally)
   143  	if len(h.patterns) > 0 {
   144  		stackEnabled.Store(true)
   145  	}
   146  	return nil
   147  }
   148  
   149  // BacktraceAt sets the glog backtrace location. When set to a file and line
   150  // number holding a logging statement, a stack trace will be written to the Info
   151  // log whenever execution hits that statement.
   152  //
   153  // Unlike with Vmodule, the ".go" must be present.
   154  func (h *GlogHandler) BacktraceAt(location string) error {
   155  	// Ensure the backtrace location contains two non-empty elements
   156  	parts := strings.Split(location, ":")
   157  	if len(parts) != 2 {
   158  		return errTraceSyntax
   159  	}
   160  	parts[0] = strings.TrimSpace(parts[0])
   161  	parts[1] = strings.TrimSpace(parts[1])
   162  	if len(parts[0]) == 0 || len(parts[1]) == 0 {
   163  		return errTraceSyntax
   164  	}
   165  	// Ensure the .go prefix is present and the line is valid
   166  	if !strings.HasSuffix(parts[0], ".go") {
   167  		return errTraceSyntax
   168  	}
   169  	if _, err := strconv.Atoi(parts[1]); err != nil {
   170  		return errTraceSyntax
   171  	}
   172  	// All seems valid
   173  	h.lock.Lock()
   174  	defer h.lock.Unlock()
   175  
   176  	h.location = location
   177  	h.backtrace.Store(len(location) > 0)
   178  	// Enable location storage (globally)
   179  	stackEnabled.Store(true)
   180  	return nil
   181  }
   182  
   183  // Log implements Handler.Log, filtering a log record through the global, local
   184  // and backtrace filters, finally emitting it if either allow it through.
   185  func (h *GlogHandler) Log(r *Record) error {
   186  	// If backtracing is requested, check whether this is the callsite
   187  	if h.backtrace.Load() {
   188  		// Everything below here is slow. Although we could cache the call sites the
   189  		// same way as for vmodule, backtracing is so rare it's not worth the extra
   190  		// complexity.
   191  		h.lock.RLock()
   192  		match := h.location == r.Call.String()
   193  		h.lock.RUnlock()
   194  
   195  		if match {
   196  			// Callsite matched, raise the log level to info and gather the stacks
   197  			r.Lvl = LvlInfo
   198  
   199  			buf := make([]byte, 1024*1024)
   200  			buf = buf[:runtime.Stack(buf, true)]
   201  			r.Msg += "\n\n" + string(buf)
   202  		}
   203  	}
   204  	// If the global log level allows, fast track logging
   205  	if h.level.Load() >= uint32(r.Lvl) {
   206  		return h.origin.Log(r)
   207  	}
   208  	// If no local overrides are present, fast track skipping
   209  	if !h.override.Load() {
   210  		return nil
   211  	}
   212  	// Check callsite cache for previously calculated log levels
   213  	h.lock.RLock()
   214  	lvl, ok := h.siteCache[r.Call.Frame().PC]
   215  	h.lock.RUnlock()
   216  
   217  	// If we didn't cache the callsite yet, calculate it
   218  	if !ok {
   219  		h.lock.Lock()
   220  		for _, rule := range h.patterns {
   221  			if rule.pattern.MatchString(fmt.Sprintf("%+s", r.Call)) {
   222  				h.siteCache[r.Call.Frame().PC], lvl, ok = rule.level, rule.level, true
   223  				break
   224  			}
   225  		}
   226  		// If no rule matched, remember to drop log the next time
   227  		if !ok {
   228  			h.siteCache[r.Call.Frame().PC] = 0
   229  		}
   230  		h.lock.Unlock()
   231  	}
   232  	if lvl >= r.Lvl {
   233  		return h.origin.Log(r)
   234  	}
   235  	return nil
   236  }