github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/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 "context" 21 "errors" 22 "fmt" 23 "log/slog" 24 "maps" 25 "regexp" 26 "runtime" 27 "strconv" 28 "strings" 29 "sync" 30 "sync/atomic" 31 ) 32 33 // errVmoduleSyntax is returned when a user vmodule pattern is invalid. 34 var errVmoduleSyntax = errors.New("expect comma-separated list of filename=N") 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 slog.Handler // The origin handler this wraps 41 42 level atomic.Int32 // Current log level, atomically accessible 43 override atomic.Bool // Flag whether overrides are used, atomically accessible 44 45 patterns []pattern // Current list of patterns to override with 46 siteCache map[uintptr]slog.Level // Cache of callsite pattern evaluations 47 location string // file:line location where to do a stackdump at 48 lock sync.RWMutex // Lock protecting the override pattern list 49 } 50 51 // NewGlogHandler creates a new log handler with filtering functionality similar 52 // to Google's glog logger. The returned handler implements Handler. 53 func NewGlogHandler(h slog.Handler) *GlogHandler { 54 return &GlogHandler{ 55 origin: h, 56 } 57 } 58 59 // pattern contains a filter for the Vmodule option, holding a verbosity level 60 // and a file pattern to match. 61 type pattern struct { 62 pattern *regexp.Regexp 63 level slog.Level 64 } 65 66 // Verbosity sets the glog verbosity ceiling. The verbosity of individual packages 67 // and source files can be raised using Vmodule. 68 func (h *GlogHandler) Verbosity(level slog.Level) { 69 h.level.Store(int32(level)) 70 } 71 72 // Vmodule sets the glog verbosity pattern. 73 // 74 // The syntax of the argument is a comma-separated list of pattern=N, where the 75 // pattern is a literal file name or "glob" pattern matching and N is a V level. 76 // 77 // For instance: 78 // 79 // pattern="gopher.go=3" 80 // sets the V level to 3 in all Go files named "gopher.go" 81 // 82 // pattern="foo=3" 83 // sets V to 3 in all files of any packages whose import path ends in "foo" 84 // 85 // pattern="foo/*=3" 86 // sets V to 3 in all files of any packages whose import path contains "foo" 87 func (h *GlogHandler) Vmodule(ruleset string) error { 88 var filter []pattern 89 for _, rule := range strings.Split(ruleset, ",") { 90 // Empty strings such as from a trailing comma can be ignored 91 if len(rule) == 0 { 92 continue 93 } 94 // Ensure we have a pattern = level filter rule 95 parts := strings.Split(rule, "=") 96 if len(parts) != 2 { 97 return errVmoduleSyntax 98 } 99 parts[0] = strings.TrimSpace(parts[0]) 100 parts[1] = strings.TrimSpace(parts[1]) 101 if len(parts[0]) == 0 || len(parts[1]) == 0 { 102 return errVmoduleSyntax 103 } 104 // Parse the level and if correct, assemble the filter rule 105 l, err := strconv.Atoi(parts[1]) 106 if err != nil { 107 return errVmoduleSyntax 108 } 109 level := FromLegacyLevel(l) 110 111 if level == LevelCrit { 112 continue // Ignore. It's harmless but no point in paying the overhead. 113 } 114 // Compile the rule pattern into a regular expression 115 matcher := ".*" 116 for _, comp := range strings.Split(parts[0], "/") { 117 if comp == "*" { 118 matcher += "(/.*)?" 119 } else if comp != "" { 120 matcher += "/" + regexp.QuoteMeta(comp) 121 } 122 } 123 if !strings.HasSuffix(parts[0], ".go") { 124 matcher += "/[^/]+\\.go" 125 } 126 matcher = matcher + "$" 127 128 re, _ := regexp.Compile(matcher) 129 filter = append(filter, pattern{re, level}) 130 } 131 // Swap out the vmodule pattern for the new filter system 132 h.lock.Lock() 133 defer h.lock.Unlock() 134 135 h.patterns = filter 136 h.siteCache = make(map[uintptr]slog.Level) 137 h.override.Store(len(filter) != 0) 138 139 return nil 140 } 141 142 func (h *GlogHandler) Enabled(ctx context.Context, lvl slog.Level) bool { 143 // fast-track skipping logging if override not enabled and the provided verbosity is above configured 144 return h.override.Load() || slog.Level(h.level.Load()) <= lvl 145 } 146 147 func (h *GlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { 148 h.lock.RLock() 149 siteCache := maps.Clone(h.siteCache) 150 h.lock.RUnlock() 151 152 patterns := []pattern{} 153 patterns = append(patterns, h.patterns...) 154 155 res := GlogHandler{ 156 origin: h.origin.WithAttrs(attrs), 157 patterns: patterns, 158 siteCache: siteCache, 159 location: h.location, 160 } 161 162 res.level.Store(h.level.Load()) 163 res.override.Store(h.override.Load()) 164 return &res 165 } 166 167 func (h *GlogHandler) WithGroup(name string) slog.Handler { 168 panic("not implemented") 169 } 170 171 // Log implements Handler.Log, filtering a log record through the global, local 172 // and backtrace filters, finally emitting it if either allow it through. 173 func (h *GlogHandler) Handle(_ context.Context, r slog.Record) error { 174 // If the global log level allows, fast track logging 175 if slog.Level(h.level.Load()) <= r.Level { 176 return h.origin.Handle(context.Background(), r) 177 } 178 179 // Check callsite cache for previously calculated log levels 180 h.lock.RLock() 181 lvl, ok := h.siteCache[r.PC] 182 h.lock.RUnlock() 183 184 // If we didn't cache the callsite yet, calculate it 185 if !ok { 186 h.lock.Lock() 187 188 fs := runtime.CallersFrames([]uintptr{r.PC}) 189 frame, _ := fs.Next() 190 191 for _, rule := range h.patterns { 192 if rule.pattern.MatchString(fmt.Sprintf("+%s", frame.File)) { 193 h.siteCache[r.PC], lvl, ok = rule.level, rule.level, true 194 } 195 } 196 // If no rule matched, remember to drop log the next time 197 if !ok { 198 h.siteCache[r.PC] = 0 199 } 200 h.lock.Unlock() 201 } 202 if lvl <= r.Level { 203 return h.origin.Handle(context.Background(), r) 204 } 205 return nil 206 }