github.com/core-coin/go-core/v2@v2.1.9/log/handler_glog.go (about)

     1  // Copyright 2017 The go-core Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core 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-core 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-core 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  // 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  	atomic.StoreUint32(&h.level, 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  	atomic.StoreUint32(&h.override, uint32(len(filter)))
   142  
   143  	return nil
   144  }
   145  
   146  // BacktraceAt sets the glog backtrace location. When set to a file and line
   147  // number holding a logging statement, a stack trace will be written to the Info
   148  // log whenever execution hits that statement.
   149  //
   150  // Unlike with Vmodule, the ".go" must be present.
   151  func (h *GlogHandler) BacktraceAt(location string) error {
   152  	// Ensure the backtrace location contains two non-empty elements
   153  	parts := strings.Split(location, ":")
   154  	if len(parts) != 2 {
   155  		return errTraceSyntax
   156  	}
   157  	parts[0] = strings.TrimSpace(parts[0])
   158  	parts[1] = strings.TrimSpace(parts[1])
   159  	if len(parts[0]) == 0 || len(parts[1]) == 0 {
   160  		return errTraceSyntax
   161  	}
   162  	// Ensure the .go prefix is present and the line is valid
   163  	if !strings.HasSuffix(parts[0], ".go") {
   164  		return errTraceSyntax
   165  	}
   166  	if _, err := strconv.Atoi(parts[1]); err != nil {
   167  		return errTraceSyntax
   168  	}
   169  	// All seems valid
   170  	h.lock.Lock()
   171  	defer h.lock.Unlock()
   172  
   173  	h.location = location
   174  	atomic.StoreUint32(&h.backtrace, uint32(len(location)))
   175  
   176  	return nil
   177  }
   178  
   179  // Log implements Handler.Log, filtering a log record through the global, local
   180  // and backtrace filters, finally emitting it if either allow it through.
   181  func (h *GlogHandler) Log(r *Record) error {
   182  	// If backtracing is requested, check whether this is the callsite
   183  	if atomic.LoadUint32(&h.backtrace) > 0 {
   184  		// Everything below here is slow. Although we could cache the call sites the
   185  		// same way as for vmodule, backtracing is so rare it's not worth the extra
   186  		// complexity.
   187  		h.lock.RLock()
   188  		match := h.location == r.Call.String()
   189  		h.lock.RUnlock()
   190  
   191  		if match {
   192  			// Callsite matched, raise the log level to info and gather the stacks
   193  			r.Lvl = LvlInfo
   194  
   195  			buf := make([]byte, 1024*1024)
   196  			buf = buf[:runtime.Stack(buf, true)]
   197  			r.Msg += "\n\n" + string(buf)
   198  		}
   199  	}
   200  	// If the global log level allows, fast track logging
   201  	if atomic.LoadUint32(&h.level) >= uint32(r.Lvl) {
   202  		return h.origin.Log(r)
   203  	}
   204  	// If no local overrides are present, fast track skipping
   205  	if atomic.LoadUint32(&h.override) == 0 {
   206  		return nil
   207  	}
   208  	// Check callsite cache for previously calculated log levels
   209  	h.lock.RLock()
   210  	lvl, ok := h.siteCache[r.Call.Frame().PC]
   211  	h.lock.RUnlock()
   212  
   213  	// If we didn't cache the callsite yet, calculate it
   214  	if !ok {
   215  		h.lock.Lock()
   216  		for _, rule := range h.patterns {
   217  			if rule.pattern.MatchString(fmt.Sprintf("%+s", r.Call)) {
   218  				h.siteCache[r.Call.Frame().PC], lvl, ok = rule.level, rule.level, true
   219  				break
   220  			}
   221  		}
   222  		// If no rule matched, remember to drop log the next time
   223  		if !ok {
   224  			h.siteCache[r.Call.Frame().PC] = 0
   225  		}
   226  		h.lock.Unlock()
   227  	}
   228  	if lvl >= r.Lvl {
   229  		return h.origin.Log(r)
   230  	}
   231  	return nil
   232  }