github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/log/handler_glog.go (about)

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