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 }