123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592 |
- // 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 implements logging analogous to the Google-internal C++ INFO/ERROR/V setup.
- // It provides functions Info, Warning, Error, Fatal, plus formatting variants such as
- // Infof. It also provides V-style logging controlled by the -v and -vmodule=file=2 flags.
- //
- // Basic examples:
- //
- // glog.Info("Prepare to repel boarders")
- //
- // glog.Fatalf("Initialization failed: %s", err)
- //
- // See the documentation for the V function for an explanation of these examples:
- //
- // if glog.V(2) {
- // glog.Info("Starting transaction...")
- // }
- //
- // glog.V(2).Infoln("Processed", nItems, "elements")
- //
- // Log output is buffered and written periodically using Flush. Programs
- // should call Flush before exiting to guarantee all log output is written.
- //
- // By default, all log statements write to files in a temporary directory.
- // This package provides several flags that modify this behavior.
- // As a result, flag.Parse must be called before any logging is done.
- //
- // -logtostderr=false
- // Logs are written to standard error instead of to files.
- // -alsologtostderr=false
- // Logs are written to standard error as well as to files.
- // -stderrthreshold=ERROR
- // Log events at or above this severity are logged to standard
- // error as well as to files.
- // -log_dir=""
- // Log files will be written to this directory instead of the
- // default temporary directory.
- //
- // Other flags provide aids to debugging.
- //
- // -log_backtrace_at=""
- // A comma-separated list of file and line numbers holding a logging
- // statement, such as
- // -log_backtrace_at=gopherflakes.go:234
- // A stack trace will be written to the Info log whenever execution
- // hits one of these statements. (Unlike with -vmodule, the ".go"
- // must bepresent.)
- // -v=0
- // Enable V-leveled logging at the specified level.
- // -vmodule=""
- // The syntax of the argument is a comma-separated list of pattern=N,
- // where pattern is a literal file name (minus the ".go" suffix) or
- // "glob" pattern and N is a V level. For instance,
- // -vmodule=gopher*=3
- // sets the V level to 3 in all Go files whose names begin with "gopher",
- // and
- // -vmodule=/path/to/glog/glog_test=1
- // sets the V level to 1 in the Go file /path/to/glog/glog_test.go.
- // If a glob pattern contains a slash, it is matched against the full path,
- // and the file name. Otherwise, the pattern is
- // matched only against the file's basename. When both -vmodule and -v
- // are specified, the -vmodule values take precedence for the specified
- // modules.
- package glog
- // This file contains the parts of the log package that are shared among all
- // implementations (file, envelope, and appengine).
- import (
- "bytes"
- "errors"
- "fmt"
- stdLog "log"
- "os"
- "reflect"
- "runtime"
- "runtime/pprof"
- "strconv"
- "sync"
- "sync/atomic"
- "time"
- "github.com/golang/glog/internal/logsink"
- "github.com/golang/glog/internal/stackdump"
- )
- var timeNow = time.Now // Stubbed out for testing.
- // MaxSize is the maximum size of a log file in bytes.
- var MaxSize uint64 = 1024 * 1024 * 1800
- // ErrNoLog is the error we return if no log file has yet been created
- // for the specified log type.
- var ErrNoLog = errors.New("log file not yet created")
- // OutputStats tracks the number of output lines and bytes written.
- type OutputStats struct {
- lines int64
- bytes int64
- }
- // Lines returns the number of lines written.
- func (s *OutputStats) Lines() int64 {
- return atomic.LoadInt64(&s.lines)
- }
- // Bytes returns the number of bytes written.
- func (s *OutputStats) Bytes() int64 {
- return atomic.LoadInt64(&s.bytes)
- }
- // Stats tracks the number of lines of output and number of bytes
- // per severity level. Values must be read with atomic.LoadInt64.
- var Stats struct {
- Info, Warning, Error OutputStats
- }
- var severityStats = [...]*OutputStats{
- logsink.Info: &Stats.Info,
- logsink.Warning: &Stats.Warning,
- logsink.Error: &Stats.Error,
- logsink.Fatal: nil,
- }
- // Level specifies a level of verbosity for V logs. The -v flag is of type
- // Level and should be modified only through the flag.Value interface.
- type Level int32
- var metaPool sync.Pool // Pool of *logsink.Meta.
- // metaPoolGet returns a *logsink.Meta from metaPool as both an interface and a
- // pointer, allocating a new one if necessary. (Returning the interface value
- // directly avoids an allocation if there was an existing pointer in the pool.)
- func metaPoolGet() (any, *logsink.Meta) {
- if metai := metaPool.Get(); metai != nil {
- return metai, metai.(*logsink.Meta)
- }
- meta := new(logsink.Meta)
- return meta, meta
- }
- type stack bool
- const (
- noStack = stack(false)
- withStack = stack(true)
- )
- func appendBacktrace(depth int, format string, args []any) (string, []any) {
- // Capture a backtrace as a stackdump.Stack (both text and PC slice).
- // Structured log sinks can extract the backtrace in whichever format they
- // prefer (PCs or text), and Text sinks will include it as just another part
- // of the log message.
- //
- // Use depth instead of depth+1 so that the backtrace always includes the
- // log function itself - otherwise the reason for the trace appearing in the
- // log may not be obvious to the reader.
- dump := stackdump.Caller(depth)
- // Add an arg and an entry in the format string for the stack dump.
- //
- // Copy the "args" slice to avoid a rare but serious aliasing bug
- // (corrupting the caller's slice if they passed it to a non-Fatal call
- // using "...").
- format = format + "\n\n%v\n"
- args = append(append([]any(nil), args...), dump)
- return format, args
- }
- // logf writes a log message for a log function call (or log function wrapper)
- // at the given depth in the current goroutine's stack.
- func logf(depth int, severity logsink.Severity, verbose bool, stack stack, format string, args ...any) {
- now := timeNow()
- _, file, line, ok := runtime.Caller(depth + 1)
- if !ok {
- file = "???"
- line = 1
- }
- if stack == withStack || backtraceAt(file, line) {
- format, args = appendBacktrace(depth+1, format, args)
- }
- metai, meta := metaPoolGet()
- *meta = logsink.Meta{
- Time: now,
- File: file,
- Line: line,
- Depth: depth + 1,
- Severity: severity,
- Verbose: verbose,
- Thread: int64(pid),
- }
- sinkf(meta, format, args...)
- metaPool.Put(metai)
- }
- func sinkf(meta *logsink.Meta, format string, args ...any) {
- meta.Depth++
- n, err := logsink.Printf(meta, format, args...)
- if stats := severityStats[meta.Severity]; stats != nil {
- atomic.AddInt64(&stats.lines, 1)
- atomic.AddInt64(&stats.bytes, int64(n))
- }
- if err != nil {
- logsink.Printf(meta, "glog: exiting because of error: %s", err)
- sinks.file.Flush()
- os.Exit(2)
- }
- }
- // CopyStandardLogTo arranges for messages written to the Go "log" package's
- // default logs to also appear in the Google logs for the named and lower
- // severities. Subsequent changes to the standard log's default output location
- // or format may break this behavior.
- //
- // Valid names are "INFO", "WARNING", "ERROR", and "FATAL". If the name is not
- // recognized, CopyStandardLogTo panics.
- func CopyStandardLogTo(name string) {
- sev, err := logsink.ParseSeverity(name)
- if err != nil {
- panic(fmt.Sprintf("log.CopyStandardLogTo(%q): %v", name, err))
- }
- // Set a log format that captures the user's file and line:
- // d.go:23: message
- stdLog.SetFlags(stdLog.Lshortfile)
- stdLog.SetOutput(logBridge(sev))
- }
- // NewStandardLogger returns a Logger that writes to the Google logs for the
- // named and lower severities.
- //
- // Valid names are "INFO", "WARNING", "ERROR", and "FATAL". If the name is not
- // recognized, NewStandardLogger panics.
- func NewStandardLogger(name string) *stdLog.Logger {
- sev, err := logsink.ParseSeverity(name)
- if err != nil {
- panic(fmt.Sprintf("log.NewStandardLogger(%q): %v", name, err))
- }
- return stdLog.New(logBridge(sev), "", stdLog.Lshortfile)
- }
- // logBridge provides the Write method that enables CopyStandardLogTo to connect
- // Go's standard logs to the logs provided by this package.
- type logBridge logsink.Severity
- // Write parses the standard logging line and passes its components to the
- // logger for severity(lb).
- func (lb logBridge) Write(b []byte) (n int, err error) {
- var (
- file = "???"
- line = 1
- text string
- )
- // Split "d.go:23: message" into "d.go", "23", and "message".
- if parts := bytes.SplitN(b, []byte{':'}, 3); len(parts) != 3 || len(parts[0]) < 1 || len(parts[2]) < 1 {
- text = fmt.Sprintf("bad log format: %s", b)
- } else {
- file = string(parts[0])
- text = string(parts[2][1:]) // skip leading space
- line, err = strconv.Atoi(string(parts[1]))
- if err != nil {
- text = fmt.Sprintf("bad line number: %s", b)
- line = 1
- }
- }
- // The depth below hard-codes details of how stdlog gets here. The alternative would be to walk
- // up the stack looking for src/log/log.go but that seems like it would be
- // unfortunately slow.
- const stdLogDepth = 4
- metai, meta := metaPoolGet()
- *meta = logsink.Meta{
- Time: timeNow(),
- File: file,
- Line: line,
- Depth: stdLogDepth,
- Severity: logsink.Severity(lb),
- Thread: int64(pid),
- }
- format := "%s"
- args := []any{text}
- if backtraceAt(file, line) {
- format, args = appendBacktrace(meta.Depth, format, args)
- }
- sinkf(meta, format, args...)
- metaPool.Put(metai)
- return len(b), nil
- }
- // defaultFormat returns a fmt.Printf format specifier that formats its
- // arguments as if they were passed to fmt.Print.
- func defaultFormat(args []any) string {
- n := len(args)
- switch n {
- case 0:
- return ""
- case 1:
- return "%v"
- }
- b := make([]byte, 0, n*3-1)
- wasString := true // Suppress leading space.
- for _, arg := range args {
- isString := arg != nil && reflect.TypeOf(arg).Kind() == reflect.String
- if wasString || isString {
- b = append(b, "%v"...)
- } else {
- b = append(b, " %v"...)
- }
- wasString = isString
- }
- return string(b)
- }
- // lnFormat returns a fmt.Printf format specifier that formats its arguments
- // as if they were passed to fmt.Println.
- func lnFormat(args []any) string {
- if len(args) == 0 {
- return "\n"
- }
- b := make([]byte, 0, len(args)*3)
- for range args {
- b = append(b, "%v "...)
- }
- b[len(b)-1] = '\n' // Replace the last space with a newline.
- return string(b)
- }
- // Verbose is a boolean type that implements Infof (like Printf) etc.
- // See the documentation of V for more information.
- type Verbose bool
- // V reports whether verbosity at the call site is at least the requested level.
- // The returned value is a boolean of type Verbose, which implements Info, Infoln
- // and Infof. These methods will write to the Info log if called.
- // Thus, one may write either
- //
- // if glog.V(2) { glog.Info("log this") }
- //
- // or
- //
- // glog.V(2).Info("log this")
- //
- // The second form is shorter but the first is cheaper if logging is off because it does
- // not evaluate its arguments.
- //
- // Whether an individual call to V generates a log record depends on the setting of
- // the -v and --vmodule flags; both are off by default. If the level in the call to
- // V is at most the value of -v, or of -vmodule for the source file containing the
- // call, the V call will log.
- func V(level Level) Verbose {
- return VDepth(1, level)
- }
- // VDepth acts as V but uses depth to determine which call frame to check vmodule for.
- // VDepth(0, level) is the same as V(level).
- func VDepth(depth int, level Level) Verbose {
- return Verbose(verboseEnabled(depth+1, level))
- }
- // Info is equivalent to the global Info function, guarded by the value of v.
- // See the documentation of V for usage.
- func (v Verbose) Info(args ...any) {
- v.InfoDepth(1, args...)
- }
- // InfoDepth is equivalent to the global InfoDepth function, guarded by the value of v.
- // See the documentation of V for usage.
- func (v Verbose) InfoDepth(depth int, args ...any) {
- if v {
- logf(depth+1, logsink.Info, true, noStack, defaultFormat(args), args...)
- }
- }
- // InfoDepthf is equivalent to the global InfoDepthf function, guarded by the value of v.
- // See the documentation of V for usage.
- func (v Verbose) InfoDepthf(depth int, format string, args ...any) {
- if v {
- logf(depth+1, logsink.Info, true, noStack, format, args...)
- }
- }
- // Infoln is equivalent to the global Infoln function, guarded by the value of v.
- // See the documentation of V for usage.
- func (v Verbose) Infoln(args ...any) {
- if v {
- logf(1, logsink.Info, true, noStack, lnFormat(args), args...)
- }
- }
- // Infof is equivalent to the global Infof function, guarded by the value of v.
- // See the documentation of V for usage.
- func (v Verbose) Infof(format string, args ...any) {
- if v {
- logf(1, logsink.Info, true, noStack, format, args...)
- }
- }
- // Info logs to the INFO log.
- // Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
- func Info(args ...any) {
- InfoDepth(1, args...)
- }
- // InfoDepth calls Info from a different depth in the call stack.
- // This enables a callee to emit logs that use the callsite information of its caller
- // or any other callers in the stack. When depth == 0, the original callee's line
- // information is emitted. When depth > 0, depth frames are skipped in the call stack
- // and the final frame is treated like the original callee to Info.
- func InfoDepth(depth int, args ...any) {
- logf(depth+1, logsink.Info, false, noStack, defaultFormat(args), args...)
- }
- // InfoDepthf acts as InfoDepth but with format string.
- func InfoDepthf(depth int, format string, args ...any) {
- logf(depth+1, logsink.Info, false, noStack, format, args...)
- }
- // Infoln logs to the INFO log.
- // Arguments are handled in the manner of fmt.Println; a newline is appended if missing.
- func Infoln(args ...any) {
- logf(1, logsink.Info, false, noStack, lnFormat(args), args...)
- }
- // Infof logs to the INFO log.
- // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
- func Infof(format string, args ...any) {
- logf(1, logsink.Info, false, noStack, format, args...)
- }
- // Warning logs to the WARNING and INFO logs.
- // Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
- func Warning(args ...any) {
- WarningDepth(1, args...)
- }
- // WarningDepth acts as Warning but uses depth to determine which call frame to log.
- // WarningDepth(0, "msg") is the same as Warning("msg").
- func WarningDepth(depth int, args ...any) {
- logf(depth+1, logsink.Warning, false, noStack, defaultFormat(args), args...)
- }
- // WarningDepthf acts as Warningf but uses depth to determine which call frame to log.
- // WarningDepthf(0, "msg") is the same as Warningf("msg").
- func WarningDepthf(depth int, format string, args ...any) {
- logf(depth+1, logsink.Warning, false, noStack, format, args...)
- }
- // Warningln logs to the WARNING and INFO logs.
- // Arguments are handled in the manner of fmt.Println; a newline is appended if missing.
- func Warningln(args ...any) {
- logf(1, logsink.Warning, false, noStack, lnFormat(args), args...)
- }
- // Warningf logs to the WARNING and INFO logs.
- // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
- func Warningf(format string, args ...any) {
- logf(1, logsink.Warning, false, noStack, format, args...)
- }
- // Error logs to the ERROR, WARNING, and INFO logs.
- // Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
- func Error(args ...any) {
- ErrorDepth(1, args...)
- }
- // ErrorDepth acts as Error but uses depth to determine which call frame to log.
- // ErrorDepth(0, "msg") is the same as Error("msg").
- func ErrorDepth(depth int, args ...any) {
- logf(depth+1, logsink.Error, false, noStack, defaultFormat(args), args...)
- }
- // ErrorDepthf acts as Errorf but uses depth to determine which call frame to log.
- // ErrorDepthf(0, "msg") is the same as Errorf("msg").
- func ErrorDepthf(depth int, format string, args ...any) {
- logf(depth+1, logsink.Error, false, noStack, format, args...)
- }
- // Errorln logs to the ERROR, WARNING, and INFO logs.
- // Arguments are handled in the manner of fmt.Println; a newline is appended if missing.
- func Errorln(args ...any) {
- logf(1, logsink.Error, false, noStack, lnFormat(args), args...)
- }
- // Errorf logs to the ERROR, WARNING, and INFO logs.
- // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
- func Errorf(format string, args ...any) {
- logf(1, logsink.Error, false, noStack, format, args...)
- }
- func fatalf(depth int, format string, args ...any) {
- logf(depth+1, logsink.Fatal, false, withStack, format, args...)
- sinks.file.Flush()
- err := abortProcess() // Should not return.
- // Failed to abort the process using signals. Dump a stack trace and exit.
- Errorf("abortProcess returned unexpectedly: %v", err)
- sinks.file.Flush()
- pprof.Lookup("goroutine").WriteTo(os.Stderr, 1)
- os.Exit(2) // Exit with the same code as the default SIGABRT handler.
- }
- // Fatal logs to the FATAL, ERROR, WARNING, and INFO logs,
- // including a stack trace of all running goroutines, then calls os.Exit(2).
- // Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
- func Fatal(args ...any) {
- FatalDepth(1, args...)
- }
- // FatalDepth acts as Fatal but uses depth to determine which call frame to log.
- // FatalDepth(0, "msg") is the same as Fatal("msg").
- func FatalDepth(depth int, args ...any) {
- fatalf(depth+1, defaultFormat(args), args...)
- }
- // FatalDepthf acts as Fatalf but uses depth to determine which call frame to log.
- // FatalDepthf(0, "msg") is the same as Fatalf("msg").
- func FatalDepthf(depth int, format string, args ...any) {
- fatalf(depth+1, format, args...)
- }
- // Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs,
- // including a stack trace of all running goroutines, then calls os.Exit(2).
- // Arguments are handled in the manner of fmt.Println; a newline is appended if missing.
- func Fatalln(args ...any) {
- fatalf(1, lnFormat(args), args...)
- }
- // Fatalf logs to the FATAL, ERROR, WARNING, and INFO logs,
- // including a stack trace of all running goroutines, then calls os.Exit(2).
- // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
- func Fatalf(format string, args ...any) {
- fatalf(1, format, args...)
- }
- func exitf(depth int, format string, args ...any) {
- logf(depth+1, logsink.Fatal, false, noStack, format, args...)
- sinks.file.Flush()
- os.Exit(1)
- }
- // Exit logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1).
- // Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
- func Exit(args ...any) {
- ExitDepth(1, args...)
- }
- // ExitDepth acts as Exit but uses depth to determine which call frame to log.
- // ExitDepth(0, "msg") is the same as Exit("msg").
- func ExitDepth(depth int, args ...any) {
- exitf(depth+1, defaultFormat(args), args...)
- }
- // ExitDepthf acts as Exitf but uses depth to determine which call frame to log.
- // ExitDepthf(0, "msg") is the same as Exitf("msg").
- func ExitDepthf(depth int, format string, args ...any) {
- exitf(depth+1, format, args...)
- }
- // Exitln logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1).
- func Exitln(args ...any) {
- exitf(1, lnFormat(args), args...)
- }
- // Exitf logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1).
- // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
- func Exitf(format string, args ...any) {
- exitf(1, format, args...)
- }
|