123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- // Go support for leveled logs, analogous to https://github.com/google/glog.
- //
- // Copyright 2023 Google Inc. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package glog
- import (
- "bytes"
- "errors"
- "flag"
- "fmt"
- "path/filepath"
- "runtime"
- "strconv"
- "strings"
- "sync"
- "sync/atomic"
- "github.com/golang/glog/internal/logsink"
- )
- // modulePat contains a filter for the -vmodule flag.
- // It holds a verbosity level and a file pattern to match.
- type modulePat struct {
- pattern string
- literal bool // The pattern is a literal string
- full bool // The pattern wants to match the full path
- level Level
- }
- // match reports whether the file matches the pattern. It uses a string
- // comparison if the pattern contains no metacharacters.
- func (m *modulePat) match(full, file string) bool {
- if m.literal {
- if m.full {
- return full == m.pattern
- }
- return file == m.pattern
- }
- if m.full {
- match, _ := filepath.Match(m.pattern, full)
- return match
- }
- match, _ := filepath.Match(m.pattern, file)
- return match
- }
- // isLiteral reports whether the pattern is a literal string, that is, has no metacharacters
- // that require filepath.Match to be called to match the pattern.
- func isLiteral(pattern string) bool {
- return !strings.ContainsAny(pattern, `\*?[]`)
- }
- // isFull reports whether the pattern matches the full file path, that is,
- // whether it contains /.
- func isFull(pattern string) bool {
- return strings.ContainsRune(pattern, '/')
- }
- // verboseFlags represents the setting of the -v and -vmodule flags.
- type verboseFlags struct {
- // moduleLevelCache is a sync.Map storing the -vmodule Level for each V()
- // call site, identified by PC. If there is no matching -vmodule filter,
- // the cached value is exactly v. moduleLevelCache is replaced with a new
- // Map whenever the -vmodule or -v flag changes state.
- moduleLevelCache atomic.Value
- // mu guards all fields below.
- mu sync.Mutex
- // v stores the value of the -v flag. It may be read safely using
- // sync.LoadInt32, but is only modified under mu.
- v Level
- // module stores the parsed -vmodule flag.
- module []modulePat
- // moduleLength caches len(module). If greater than zero, it
- // means vmodule is enabled. It may be read safely using sync.LoadInt32, but
- // is only modified under mu.
- moduleLength int32
- }
- // NOTE: For compatibility with the open-sourced v1 version of this
- // package (github.com/golang/glog) we need to retain that flag.Level
- // implements the flag.Value interface. See also go/log-vs-glog.
- // String is part of the flag.Value interface.
- func (l *Level) String() string {
- return strconv.FormatInt(int64(l.Get().(Level)), 10)
- }
- // Get is part of the flag.Value interface.
- func (l *Level) Get() any {
- if l == &vflags.v {
- // l is the value registered for the -v flag.
- return Level(atomic.LoadInt32((*int32)(l)))
- }
- return *l
- }
- // Set is part of the flag.Value interface.
- func (l *Level) Set(value string) error {
- v, err := strconv.Atoi(value)
- if err != nil {
- return err
- }
- if l == &vflags.v {
- // l is the value registered for the -v flag.
- vflags.mu.Lock()
- defer vflags.mu.Unlock()
- vflags.moduleLevelCache.Store(&sync.Map{})
- atomic.StoreInt32((*int32)(l), int32(v))
- return nil
- }
- *l = Level(v)
- return nil
- }
- // vModuleFlag is the flag.Value for the --vmodule flag.
- type vModuleFlag struct{ *verboseFlags }
- func (f vModuleFlag) String() string {
- f.mu.Lock()
- defer f.mu.Unlock()
- var b bytes.Buffer
- for i, f := range f.module {
- if i > 0 {
- b.WriteRune(',')
- }
- fmt.Fprintf(&b, "%s=%d", f.pattern, f.level)
- }
- return b.String()
- }
- // Get returns nil for this flag type since the struct is not exported.
- func (f vModuleFlag) Get() any { return nil }
- var errVmoduleSyntax = errors.New("syntax error: expect comma-separated list of filename=N")
- // Syntax: -vmodule=recordio=2,foo/bar/baz=1,gfs*=3
- func (f vModuleFlag) Set(value string) error {
- var filter []modulePat
- for _, pat := range strings.Split(value, ",") {
- if len(pat) == 0 {
- // Empty strings such as from a trailing comma can be ignored.
- continue
- }
- patLev := strings.Split(pat, "=")
- if len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 {
- return errVmoduleSyntax
- }
- pattern := patLev[0]
- v, err := strconv.Atoi(patLev[1])
- if err != nil {
- return errors.New("syntax error: expect comma-separated list of filename=N")
- }
- // TODO: check syntax of filter?
- filter = append(filter, modulePat{pattern, isLiteral(pattern), isFull(pattern), Level(v)})
- }
- f.mu.Lock()
- defer f.mu.Unlock()
- f.module = filter
- atomic.StoreInt32((*int32)(&f.moduleLength), int32(len(f.module)))
- f.moduleLevelCache.Store(&sync.Map{})
- return nil
- }
- func (f *verboseFlags) levelForPC(pc uintptr) Level {
- if level, ok := f.moduleLevelCache.Load().(*sync.Map).Load(pc); ok {
- return level.(Level)
- }
- f.mu.Lock()
- defer f.mu.Unlock()
- level := Level(f.v)
- fn := runtime.FuncForPC(pc)
- file, _ := fn.FileLine(pc)
- // The file is something like /a/b/c/d.go. We want just the d for
- // regular matches, /a/b/c/d for full matches.
- if strings.HasSuffix(file, ".go") {
- file = file[:len(file)-3]
- }
- full := file
- if slash := strings.LastIndex(file, "/"); slash >= 0 {
- file = file[slash+1:]
- }
- for _, filter := range f.module {
- if filter.match(full, file) {
- level = filter.level
- break // Use the first matching level.
- }
- }
- f.moduleLevelCache.Load().(*sync.Map).Store(pc, level)
- return level
- }
- func (f *verboseFlags) enabled(callerDepth int, level Level) bool {
- if atomic.LoadInt32(&f.moduleLength) == 0 {
- // No vmodule values specified, so compare against v level.
- return Level(atomic.LoadInt32((*int32)(&f.v))) >= level
- }
- pcs := [1]uintptr{}
- if runtime.Callers(callerDepth+2, pcs[:]) < 1 {
- return false
- }
- frame, _ := runtime.CallersFrames(pcs[:]).Next()
- return f.levelForPC(frame.Entry) >= level
- }
- // traceLocation represents an entry in the -log_backtrace_at flag.
- type traceLocation struct {
- file string
- line int
- }
- var errTraceSyntax = errors.New("syntax error: expect file.go:234")
- func parseTraceLocation(value string) (traceLocation, error) {
- fields := strings.Split(value, ":")
- if len(fields) != 2 {
- return traceLocation{}, errTraceSyntax
- }
- file, lineStr := fields[0], fields[1]
- if !strings.Contains(file, ".") {
- return traceLocation{}, errTraceSyntax
- }
- line, err := strconv.Atoi(lineStr)
- if err != nil {
- return traceLocation{}, errTraceSyntax
- }
- if line < 0 {
- return traceLocation{}, errors.New("negative value for line")
- }
- return traceLocation{file, line}, nil
- }
- // match reports whether the specified file and line matches the trace location.
- // The argument file name is the full path, not the basename specified in the flag.
- func (t traceLocation) match(file string, line int) bool {
- if t.line != line {
- return false
- }
- if i := strings.LastIndex(file, "/"); i >= 0 {
- file = file[i+1:]
- }
- return t.file == file
- }
- func (t traceLocation) String() string {
- return fmt.Sprintf("%s:%d", t.file, t.line)
- }
- // traceLocations represents the -log_backtrace_at flag.
- // Syntax: -log_backtrace_at=recordio.go:234,sstable.go:456
- // Note that unlike vmodule the file extension is included here.
- type traceLocations struct {
- mu sync.Mutex
- locsLen int32 // Safe for atomic read without mu.
- locs []traceLocation
- }
- func (t *traceLocations) String() string {
- t.mu.Lock()
- defer t.mu.Unlock()
- var buf bytes.Buffer
- for i, tl := range t.locs {
- if i > 0 {
- buf.WriteString(",")
- }
- buf.WriteString(tl.String())
- }
- return buf.String()
- }
- // Get always returns nil for this flag type since the struct is not exported
- func (t *traceLocations) Get() any { return nil }
- func (t *traceLocations) Set(value string) error {
- var locs []traceLocation
- for _, s := range strings.Split(value, ",") {
- if s == "" {
- continue
- }
- loc, err := parseTraceLocation(s)
- if err != nil {
- return err
- }
- locs = append(locs, loc)
- }
- t.mu.Lock()
- defer t.mu.Unlock()
- atomic.StoreInt32(&t.locsLen, int32(len(locs)))
- t.locs = locs
- return nil
- }
- func (t *traceLocations) match(file string, line int) bool {
- if atomic.LoadInt32(&t.locsLen) == 0 {
- return false
- }
- t.mu.Lock()
- defer t.mu.Unlock()
- for _, tl := range t.locs {
- if tl.match(file, line) {
- return true
- }
- }
- return false
- }
- // severityFlag is an atomic flag.Value implementation for logsink.Severity.
- type severityFlag int32
- func (s *severityFlag) get() logsink.Severity {
- return logsink.Severity(atomic.LoadInt32((*int32)(s)))
- }
- func (s *severityFlag) String() string { return strconv.FormatInt(int64(*s), 10) }
- func (s *severityFlag) Get() any { return s.get() }
- func (s *severityFlag) Set(value string) error {
- threshold, err := logsink.ParseSeverity(value)
- if err != nil {
- // Not a severity name. Try a raw number.
- v, err := strconv.Atoi(value)
- if err != nil {
- return err
- }
- threshold = logsink.Severity(v)
- if threshold < logsink.Info || threshold > logsink.Fatal {
- return fmt.Errorf("Severity %d out of range (min %d, max %d).", v, logsink.Info, logsink.Fatal)
- }
- }
- atomic.StoreInt32((*int32)(s), int32(threshold))
- return nil
- }
- var (
- vflags verboseFlags // The -v and -vmodule flags.
- logBacktraceAt traceLocations // The -log_backtrace_at flag.
- // Boolean flags. Not handled atomically because the flag.Value interface
- // does not let us avoid the =true, and that shorthand is necessary for
- // compatibility. TODO: does this matter enough to fix? Seems unlikely.
- toStderr bool // The -logtostderr flag.
- alsoToStderr bool // The -alsologtostderr flag.
- stderrThreshold severityFlag // The -stderrthreshold flag.
- )
- // verboseEnabled returns whether the caller at the given depth should emit
- // verbose logs at the given level, with depth 0 identifying the caller of
- // verboseEnabled.
- func verboseEnabled(callerDepth int, level Level) bool {
- return vflags.enabled(callerDepth+1, level)
- }
- // backtraceAt returns whether the logging call at the given function and line
- // should also emit a backtrace of the current call stack.
- func backtraceAt(file string, line int) bool {
- return logBacktraceAt.match(file, line)
- }
- func init() {
- vflags.moduleLevelCache.Store(&sync.Map{})
- flag.Var(&vflags.v, "v", "log level for V logs")
- flag.Var(vModuleFlag{&vflags}, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging")
- flag.Var(&logBacktraceAt, "log_backtrace_at", "when logging hits line file:N, emit a stack trace")
- stderrThreshold = severityFlag(logsink.Error)
- flag.BoolVar(&toStderr, "logtostderr", false, "log to standard error instead of files")
- flag.BoolVar(&alsoToStderr, "alsologtostderr", false, "log to standard error as well as files")
- flag.Var(&stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr")
- }
|