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