blob: dd0ed101563a2e780c64944e739d21d6d032f047 [file] [log] [blame]
// 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...)
}