github.com/klaytn/klaytn@v1.12.1/log/handler_glog.go (about)

     1  // Modifications Copyright 2018 The klaytn Authors
     2  // Copyright 2017 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from log/handler_glog.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package log
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"regexp"
    27  	"runtime"
    28  	"strconv"
    29  	"strings"
    30  	"sync"
    31  	"sync/atomic"
    32  )
    33  
    34  // errVmoduleSyntax is returned when a user vmodule pattern is invalid.
    35  var errVmoduleSyntax = errors.New("expect comma-separated list of filename=N")
    36  
    37  // errTraceSyntax is returned when a user backtrace pattern is invalid.
    38  var errTraceSyntax = errors.New("expect file.go:234")
    39  
    40  // GlogHandler is a log handler that mimics the filtering features of Google's
    41  // glog logger: setting global log levels; overriding with callsite pattern
    42  // matches; and requesting backtraces at certain positions.
    43  type GlogHandler struct {
    44  	origin Handler // The origin handler this wraps
    45  
    46  	level     uint32 // Current log level, atomically accessible
    47  	override  uint32 // Flag whether overrides are used, atomically accessible
    48  	backtrace uint32 // Flag whether backtrace location is set
    49  
    50  	patterns  []pattern       // Current list of patterns to override with
    51  	siteCache map[uintptr]Lvl // Cache of callsite pattern evaluations
    52  	location  string          // file:line location where to do a stackdump at
    53  	lock      sync.RWMutex    // Lock protecting the override pattern list
    54  }
    55  
    56  // NewGlogHandler creates a new log handler with filtering functionality similar
    57  // to Google's glog logger. The returned handler implements Handler.
    58  func NewGlogHandler(h Handler) *GlogHandler {
    59  	return &GlogHandler{
    60  		origin: h,
    61  	}
    62  }
    63  
    64  // pattern contains a filter for the Vmodule option, holding a verbosity level
    65  // and a file pattern to match.
    66  type pattern struct {
    67  	pattern *regexp.Regexp
    68  	level   Lvl
    69  }
    70  
    71  // Verbosity sets the glog verbosity ceiling. The verbosity of individual packages
    72  // and source files can be raised using Vmodule.
    73  func (h *GlogHandler) Verbosity(level Lvl) {
    74  	atomic.StoreUint32(&h.level, uint32(level))
    75  }
    76  
    77  // Vmodule sets the glog verbosity pattern.
    78  //
    79  // The syntax of the argument is a comma-separated list of pattern=N, where the
    80  // pattern is a literal file name or "glob" pattern matching and N is a V level.
    81  //
    82  // For instance:
    83  //
    84  //  pattern="gopher.go=3"
    85  //   sets the V level to 3 in all Go files named "gopher.go"
    86  //
    87  //  pattern="foo=3"
    88  //   sets V to 3 in all files of any packages whose import path ends in "foo"
    89  //
    90  //  pattern="foo/*=3"
    91  //   sets V to 3 in all files of any packages whose import path contains "foo"
    92  func (h *GlogHandler) Vmodule(ruleset string) error {
    93  	var filter []pattern
    94  	for _, rule := range strings.Split(ruleset, ",") {
    95  		// Empty strings such as from a trailing comma can be ignored
    96  		if len(rule) == 0 {
    97  			continue
    98  		}
    99  		// Ensure we have a pattern = level filter rule
   100  		parts := strings.Split(rule, "=")
   101  		if len(parts) != 2 {
   102  			return errVmoduleSyntax
   103  		}
   104  		parts[0] = strings.TrimSpace(parts[0])
   105  		parts[1] = strings.TrimSpace(parts[1])
   106  		if len(parts[0]) == 0 || len(parts[1]) == 0 {
   107  			return errVmoduleSyntax
   108  		}
   109  		// Parse the level and if correct, assemble the filter rule
   110  		level, err := strconv.Atoi(parts[1])
   111  		if err != nil {
   112  			return errVmoduleSyntax
   113  		}
   114  		if level <= 0 {
   115  			continue // Ignore. It's harmless but no point in paying the overhead.
   116  		}
   117  		// Compile the rule pattern into a regular expression
   118  		matcher := ".*"
   119  		for _, comp := range strings.Split(parts[0], "/") {
   120  			if comp == "*" {
   121  				matcher += "(/.*)?"
   122  			} else if comp != "" {
   123  				matcher += "/" + regexp.QuoteMeta(comp)
   124  			}
   125  		}
   126  		if !strings.HasSuffix(parts[0], ".go") {
   127  			matcher += "/[^/]+\\.go"
   128  		}
   129  		matcher = matcher + "$"
   130  
   131  		re, _ := regexp.Compile(matcher)
   132  		filter = append(filter, pattern{re, Lvl(level)})
   133  	}
   134  	// Swap out the vmodule pattern for the new filter system
   135  	h.lock.Lock()
   136  	defer h.lock.Unlock()
   137  
   138  	h.patterns = filter
   139  	h.siteCache = make(map[uintptr]Lvl)
   140  	atomic.StoreUint32(&h.override, uint32(len(filter)))
   141  
   142  	return nil
   143  }
   144  
   145  // BacktraceAt sets the glog backtrace location. When set to a file and line
   146  // number holding a logging statement, a stack trace will be written to the Info
   147  // log whenever execution hits that statement.
   148  //
   149  // Unlike with Vmodule, the ".go" must be present.
   150  func (h *GlogHandler) BacktraceAt(location string) error {
   151  	// Ensure the backtrace location contains two non-empty elements
   152  	parts := strings.Split(location, ":")
   153  	if len(parts) != 2 {
   154  		return errTraceSyntax
   155  	}
   156  	parts[0] = strings.TrimSpace(parts[0])
   157  	parts[1] = strings.TrimSpace(parts[1])
   158  	if len(parts[0]) == 0 || len(parts[1]) == 0 {
   159  		return errTraceSyntax
   160  	}
   161  	// Ensure the .go prefix is present and the line is valid
   162  	if !strings.HasSuffix(parts[0], ".go") {
   163  		return errTraceSyntax
   164  	}
   165  	if _, err := strconv.Atoi(parts[1]); err != nil {
   166  		return errTraceSyntax
   167  	}
   168  	// All seems valid
   169  	h.lock.Lock()
   170  	defer h.lock.Unlock()
   171  
   172  	h.location = location
   173  	atomic.StoreUint32(&h.backtrace, uint32(len(location)))
   174  
   175  	return nil
   176  }
   177  
   178  // Log implements Handler.Log, filtering a log record through the global, local
   179  // and backtrace filters, finally emitting it if either allow it through.
   180  func (h *GlogHandler) Log(r *Record) error {
   181  	// If backtracing is requested, check whether this is the callsite
   182  	if atomic.LoadUint32(&h.backtrace) > 0 {
   183  		// Everything below here is slow. Although we could cache the call sites the
   184  		// same way as for vmodule, backtracing is so rare it's not worth the extra
   185  		// complexity.
   186  		h.lock.RLock()
   187  		match := h.location == r.Call.String()
   188  		h.lock.RUnlock()
   189  
   190  		if match {
   191  			// Callsite matched, raise the log level to info and gather the stacks
   192  			r.Lvl = LvlInfo
   193  
   194  			buf := make([]byte, 1024*1024)
   195  			buf = buf[:runtime.Stack(buf, true)]
   196  			r.Msg += "\n\n" + string(buf)
   197  		}
   198  	}
   199  	// If the global log level allows, fast track logging
   200  	if atomic.LoadUint32(&h.level) >= uint32(r.Lvl) {
   201  		return h.origin.Log(r)
   202  	}
   203  	// If no local overrides are present, fast track skipping
   204  	if atomic.LoadUint32(&h.override) == 0 {
   205  		return nil
   206  	}
   207  	// Check callsite cache for previously calculated log levels
   208  	h.lock.RLock()
   209  	lvl, ok := h.siteCache[r.Call.Frame().PC]
   210  	h.lock.RUnlock()
   211  
   212  	// If we didn't cache the callsite yet, calculate it
   213  	if !ok {
   214  		h.lock.Lock()
   215  		for _, rule := range h.patterns {
   216  			if rule.pattern.MatchString(fmt.Sprintf("%+s", r.Call)) {
   217  				h.siteCache[r.Call.PC()], lvl, ok = rule.level, rule.level, true
   218  				break
   219  			}
   220  		}
   221  		// If no rule matched, remember to drop log the next time
   222  		if !ok {
   223  			h.siteCache[r.Call.PC()] = 0
   224  		}
   225  		h.lock.Unlock()
   226  	}
   227  	if lvl >= r.Lvl {
   228  		return h.origin.Log(r)
   229  	}
   230  	return nil
   231  }