2022-07-07 20:11:50 +00:00
// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
//
// Copyright 2013 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 klog 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:
//
// klog.Info("Prepare to repel boarders")
//
// klog.Fatalf("Initialization failed: %s", err)
//
// See the documentation for the V function for an explanation of these examples:
//
// if klog.V(2) {
// klog.Info("Starting transaction...")
// }
//
// klog.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 standard error.
// This package provides several flags that modify this behavior.
// As a result, flag.Parse must be called before any logging is done.
//
2024-02-24 00:34:55 +00:00
// -logtostderr=true
// Logs are written to standard error instead of to files.
// This shortcuts most of the usual output routing:
// -alsologtostderr, -stderrthreshold and -log_dir have no
// effect and output redirection at runtime with SetOutput is
// ignored.
// -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.
2022-07-07 20:11:50 +00:00
//
2024-02-24 00:34:55 +00:00
// Other flags provide aids to debugging.
2022-07-07 20:11:50 +00:00
//
2024-02-24 00:34:55 +00:00
// -log_backtrace_at=""
// When set to a file and line number 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 that statement. (Unlike with -vmodule, the ".go" must be
// present.)
// -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 "gopher".
2022-07-07 20:11:50 +00:00
package klog
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"io"
stdLog "log"
"math"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
2024-02-24 00:34:55 +00:00
"k8s.io/klog/v2/internal/buffer"
"k8s.io/klog/v2/internal/clock"
"k8s.io/klog/v2/internal/dbg"
"k8s.io/klog/v2/internal/serialize"
"k8s.io/klog/v2/internal/severity"
2022-07-07 20:11:50 +00:00
)
2024-02-24 00:34:55 +00:00
// severityValue identifies the sort of log: info, warning etc. It also implements
2022-07-07 20:11:50 +00:00
// the flag.Value interface. The -stderrthreshold flag is of type severity and
// should be modified only through the flag.Value interface. The values match
// the corresponding constants in C++.
2024-02-24 00:34:55 +00:00
type severityValue struct {
severity . Severity
2022-07-07 20:11:50 +00:00
}
// get returns the value of the severity.
2024-02-24 00:34:55 +00:00
func ( s * severityValue ) get ( ) severity . Severity {
return severity . Severity ( atomic . LoadInt32 ( ( * int32 ) ( & s . Severity ) ) )
2022-07-07 20:11:50 +00:00
}
// set sets the value of the severity.
2024-02-24 00:34:55 +00:00
func ( s * severityValue ) set ( val severity . Severity ) {
atomic . StoreInt32 ( ( * int32 ) ( & s . Severity ) , int32 ( val ) )
2022-07-07 20:11:50 +00:00
}
// String is part of the flag.Value interface.
2024-02-24 00:34:55 +00:00
func ( s * severityValue ) String ( ) string {
return strconv . FormatInt ( int64 ( s . Severity ) , 10 )
2022-07-07 20:11:50 +00:00
}
// Get is part of the flag.Getter interface.
2024-02-24 00:34:55 +00:00
func ( s * severityValue ) Get ( ) interface { } {
return s . Severity
2022-07-07 20:11:50 +00:00
}
// Set is part of the flag.Value interface.
2024-02-24 00:34:55 +00:00
func ( s * severityValue ) Set ( value string ) error {
var threshold severity . Severity
2022-07-07 20:11:50 +00:00
// Is it a known name?
2024-02-24 00:34:55 +00:00
if v , ok := severity . ByName ( value ) ; ok {
2022-07-07 20:11:50 +00:00
threshold = v
} else {
v , err := strconv . ParseInt ( value , 10 , 32 )
if err != nil {
return err
}
2024-02-24 00:34:55 +00:00
threshold = severity . Severity ( v )
2022-07-07 20:11:50 +00:00
}
logging . stderrThreshold . set ( threshold )
return nil
}
// 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
}
2024-02-24 00:34:55 +00:00
var severityStats = [ severity . NumSeverity ] * OutputStats {
severity . InfoLog : & Stats . Info ,
severity . WarningLog : & Stats . Warning ,
severity . ErrorLog : & Stats . Error ,
2022-07-07 20:11:50 +00:00
}
// Level is exported because it appears in the arguments to V and is
// the type of the v flag, which can be set programmatically.
// It's a distinct type because we want to discriminate it from logType.
// Variables of type level are only changed under logging.mu.
// The -v flag is read only with atomic ops, so the state of the logging
// module is consistent.
// Level is treated as a sync/atomic int32.
// Level specifies a level of verbosity for V logs. *Level implements
// flag.Value; the -v flag is of type Level and should be modified
// only through the flag.Value interface.
type Level int32
// get returns the value of the Level.
func ( l * Level ) get ( ) Level {
return Level ( atomic . LoadInt32 ( ( * int32 ) ( l ) ) )
}
// set sets the value of the Level.
func ( l * Level ) set ( val Level ) {
atomic . StoreInt32 ( ( * int32 ) ( l ) , int32 ( val ) )
}
// String is part of the flag.Value interface.
func ( l * Level ) String ( ) string {
return strconv . FormatInt ( int64 ( * l ) , 10 )
}
// Get is part of the flag.Getter interface.
func ( l * Level ) Get ( ) interface { } {
return * l
}
// Set is part of the flag.Value interface.
func ( l * Level ) Set ( value string ) error {
v , err := strconv . ParseInt ( value , 10 , 32 )
if err != nil {
return err
}
logging . mu . Lock ( )
defer logging . mu . Unlock ( )
logging . setVState ( Level ( v ) , logging . vmodule . filter , false )
return nil
}
// moduleSpec represents the setting of the -vmodule flag.
type moduleSpec struct {
filter [ ] modulePat
}
// 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
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 ( file string ) bool {
if m . literal {
return file == m . pattern
}
match , _ := filepath . Match ( m . pattern , file )
return match
}
func ( m * moduleSpec ) String ( ) string {
// Lock because the type is not atomic. TODO: clean this up.
logging . mu . Lock ( )
defer logging . mu . Unlock ( )
2024-02-24 00:34:55 +00:00
return m . serialize ( )
}
func ( m * moduleSpec ) serialize ( ) string {
2022-07-07 20:11:50 +00:00
var b bytes . Buffer
for i , f := range m . filter {
if i > 0 {
b . WriteRune ( ',' )
}
fmt . Fprintf ( & b , "%s=%d" , f . pattern , f . level )
}
return b . String ( )
}
// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the
// struct is not exported.
func ( m * moduleSpec ) Get ( ) interface { } {
return nil
}
var errVmoduleSyntax = errors . New ( "syntax error: expect comma-separated list of filename=N" )
// Set will sets module value
// Syntax: -vmodule=recordio=2,file=1,gfs*=3
func ( m * moduleSpec ) Set ( value string ) error {
2024-02-24 00:34:55 +00:00
filter , err := parseModuleSpec ( value )
if err != nil {
return err
}
logging . mu . Lock ( )
defer logging . mu . Unlock ( )
logging . setVState ( logging . verbosity , filter , true )
return nil
}
func parseModuleSpec ( value string ) ( [ ] modulePat , error ) {
2022-07-07 20:11:50 +00:00
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 {
2024-02-24 00:34:55 +00:00
return nil , errVmoduleSyntax
2022-07-07 20:11:50 +00:00
}
pattern := patLev [ 0 ]
v , err := strconv . ParseInt ( patLev [ 1 ] , 10 , 32 )
if err != nil {
2024-02-24 00:34:55 +00:00
return nil , errors . New ( "syntax error: expect comma-separated list of filename=N" )
2022-07-07 20:11:50 +00:00
}
if v < 0 {
2024-02-24 00:34:55 +00:00
return nil , errors . New ( "negative value for vmodule level" )
2022-07-07 20:11:50 +00:00
}
if v == 0 {
continue // Ignore. It's harmless but no point in paying the overhead.
}
// TODO: check syntax of filter?
filter = append ( filter , modulePat { pattern , isLiteral ( pattern ) , Level ( v ) } )
}
2024-02-24 00:34:55 +00:00
return filter , nil
2022-07-07 20:11:50 +00:00
}
// 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 , ` \*?[] ` )
}
// traceLocation represents the setting of the -log_backtrace_at flag.
type traceLocation struct {
file string
line int
}
// isSet reports whether the trace location has been specified.
// logging.mu is held.
func ( t * traceLocation ) isSet ( ) bool {
return t . line > 0
}
// 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.
// logging.mu is held.
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 {
// Lock because the type is not atomic. TODO: clean this up.
logging . mu . Lock ( )
defer logging . mu . Unlock ( )
return fmt . Sprintf ( "%s:%d" , t . file , t . line )
}
// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the
// struct is not exported
func ( t * traceLocation ) Get ( ) interface { } {
return nil
}
var errTraceSyntax = errors . New ( "syntax error: expect file.go:234" )
// Set will sets backtrace value
// Syntax: -log_backtrace_at=gopherflakes.go:234
// Note that unlike vmodule the file extension is included here.
func ( t * traceLocation ) Set ( value string ) error {
if value == "" {
// Unset.
logging . mu . Lock ( )
defer logging . mu . Unlock ( )
t . line = 0
t . file = ""
return nil
}
fields := strings . Split ( value , ":" )
if len ( fields ) != 2 {
return errTraceSyntax
}
file , line := fields [ 0 ] , fields [ 1 ]
if ! strings . Contains ( file , "." ) {
return errTraceSyntax
}
v , err := strconv . Atoi ( line )
if err != nil {
return errTraceSyntax
}
if v <= 0 {
return errors . New ( "negative or zero value for level" )
}
logging . mu . Lock ( )
defer logging . mu . Unlock ( )
t . line = v
t . file = file
return nil
}
// flushSyncWriter is the interface satisfied by logging destinations.
type flushSyncWriter interface {
Flush ( ) error
Sync ( ) error
io . Writer
}
2024-02-24 00:34:55 +00:00
var logging loggingT
var commandLine flag . FlagSet
// init sets up the defaults and creates command line flags.
2022-07-07 20:11:50 +00:00
func init ( ) {
2024-02-24 00:34:55 +00:00
commandLine . StringVar ( & logging . logDir , "log_dir" , "" , "If non-empty, write log files in this directory (no effect when -logtostderr=true)" )
commandLine . StringVar ( & logging . logFile , "log_file" , "" , "If non-empty, use this log file (no effect when -logtostderr=true)" )
commandLine . Uint64Var ( & logging . logFileMaxSizeMB , "log_file_max_size" , 1800 ,
"Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. " +
"If the value is 0, the maximum file size is unlimited." )
commandLine . BoolVar ( & logging . toStderr , "logtostderr" , true , "log to standard error instead of files" )
commandLine . BoolVar ( & logging . alsoToStderr , "alsologtostderr" , false , "log to standard error as well as files (no effect when -logtostderr=true)" )
2022-07-07 20:11:50 +00:00
logging . setVState ( 0 , nil , false )
2024-02-24 00:34:55 +00:00
commandLine . Var ( & logging . verbosity , "v" , "number for the log level verbosity" )
commandLine . BoolVar ( & logging . addDirHeader , "add_dir_header" , false , "If true, adds the file directory to the header of the log messages" )
commandLine . BoolVar ( & logging . skipHeaders , "skip_headers" , false , "If true, avoid header prefixes in the log messages" )
commandLine . BoolVar ( & logging . oneOutput , "one_output" , false , "If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true)" )
commandLine . BoolVar ( & logging . skipLogHeaders , "skip_log_headers" , false , "If true, avoid headers when opening log files (no effect when -logtostderr=true)" )
logging . stderrThreshold = severityValue {
Severity : severity . ErrorLog , // Default stderrThreshold is ERROR.
}
commandLine . Var ( & logging . stderrThreshold , "stderrthreshold" , "logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=true)" )
commandLine . Var ( & logging . vmodule , "vmodule" , "comma-separated list of pattern=N settings for file-filtered logging" )
commandLine . Var ( & logging . traceLocation , "log_backtrace_at" , "when logging hits line file:N, emit a stack trace" )
logging . settings . contextualLoggingEnabled = true
logging . flushD = newFlushDaemon ( logging . lockAndFlushAll , nil )
2022-07-07 20:11:50 +00:00
}
// InitFlags is for explicitly initializing the flags.
2024-02-24 00:34:55 +00:00
// It may get called repeatedly for different flagsets, but not
// twice for the same one. May get called concurrently
// to other goroutines using klog. However, only some flags
// may get set concurrently (see implementation).
2022-07-07 20:11:50 +00:00
func InitFlags ( flagset * flag . FlagSet ) {
if flagset == nil {
flagset = flag . CommandLine
}
2024-02-24 00:34:55 +00:00
commandLine . VisitAll ( func ( f * flag . Flag ) {
flagset . Var ( f . Value , f . Name , f . Usage )
} )
2022-07-07 20:11:50 +00:00
}
// Flush flushes all pending log I/O.
func Flush ( ) {
logging . lockAndFlushAll ( )
}
2024-02-24 00:34:55 +00:00
// settings collects global settings.
type settings struct {
// contextualLoggingEnabled controls whether contextual logging is
// active. Disabling it may have some small performance benefit.
contextualLoggingEnabled bool
// logger is the global Logger chosen by users of klog, nil if
// none is available.
logger * logWriter
// loggerOptions contains the options that were supplied for
// globalLogger.
loggerOptions loggerOptions
2022-07-07 20:11:50 +00:00
// 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.
// Level flag. Handled atomically.
2024-02-24 00:34:55 +00:00
stderrThreshold severityValue // The -stderrthreshold flag.
2022-07-07 20:11:50 +00:00
2024-02-24 00:34:55 +00:00
// Access to all of the following fields must be protected via a mutex.
2022-07-07 20:11:50 +00:00
// file holds writer for each of the log types.
2024-02-24 00:34:55 +00:00
file [ severity . NumSeverity ] flushSyncWriter
// flushInterval is the interval for periodic flushing. If zero,
// the global default will be used.
flushInterval time . Duration
2022-07-07 20:11:50 +00:00
// filterLength stores the length of the vmodule filter chain. If greater
// than zero, it means vmodule is enabled. It may be read safely
// using sync.LoadInt32, but is only modified under mu.
filterLength int32
// traceLocation is the state of the -log_backtrace_at flag.
traceLocation traceLocation
// These flags are modified only under lock, although verbosity may be fetched
// safely using atomic.LoadInt32.
vmodule moduleSpec // The state of the -vmodule flag.
verbosity Level // V logging level, the value of the -v flag/
// If non-empty, overrides the choice of directory in which to write logs.
// See createLogDirs for the full list of possible destinations.
logDir string
// If non-empty, specifies the path of the file to write logs. mutually exclusive
// with the log_dir option.
logFile string
// When logFile is specified, this limiter makes sure the logFile won't exceeds a certain size. When exceeds, the
// logFile will be cleaned up. If this value is 0, no size limitation will be applied to logFile.
logFileMaxSizeMB uint64
// If true, do not add the prefix headers, useful when used with SetOutput
skipHeaders bool
// If true, do not add the headers to log files
skipLogHeaders bool
// If true, add the file directory to the header
addDirHeader bool
// If true, messages will not be propagated to lower severity log levels
oneOutput bool
// If set, all output will be filtered through the filter.
filter LogFilter
}
2024-02-24 00:34:55 +00:00
// deepCopy creates a copy that doesn't share anything with the original
// instance.
func ( s settings ) deepCopy ( ) settings {
// vmodule is a slice and would be shared, so we have copy it.
filter := make ( [ ] modulePat , len ( s . vmodule . filter ) )
copy ( filter , s . vmodule . filter )
s . vmodule . filter = filter
if s . logger != nil {
logger := * s . logger
s . logger = & logger
}
return s
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
// loggingT collects all the global state of the logging setup.
type loggingT struct {
settings
// flushD holds a flushDaemon that frequently flushes log file buffers.
// Uses its own mutex.
flushD * flushDaemon
// mu protects the remaining elements of this structure and the fields
// in settingsT which need a mutex lock.
mu sync . Mutex
// pcs is used in V to avoid an allocation when computing the caller's PC.
pcs [ 1 ] uintptr
// vmap is a cache of the V Level for each V() call site, identified by PC.
// It is wiped whenever the vmodule flag changes state.
vmap map [ uintptr ] Level
}
2022-07-07 20:11:50 +00:00
// setVState sets a consistent state for V logging.
// l.mu is held.
func ( l * loggingT ) setVState ( verbosity Level , filter [ ] modulePat , setFilter bool ) {
// Turn verbosity off so V will not fire while we are in transition.
l . verbosity . set ( 0 )
// Ditto for filter length.
atomic . StoreInt32 ( & l . filterLength , 0 )
// Set the new filters and wipe the pc->Level map if the filter has changed.
if setFilter {
l . vmodule . filter = filter
l . vmap = make ( map [ uintptr ] Level )
}
// Things are consistent now, so enable filtering and verbosity.
// They are enabled in order opposite to that in V.
atomic . StoreInt32 ( & l . filterLength , int32 ( len ( filter ) ) )
l . verbosity . set ( verbosity )
}
2024-02-24 00:34:55 +00:00
var timeNow = time . Now // Stubbed out for testing.
// CaptureState gathers information about all current klog settings.
// The result can be used to restore those settings.
func CaptureState ( ) State {
logging . mu . Lock ( )
defer logging . mu . Unlock ( )
return & state {
settings : logging . settings . deepCopy ( ) ,
flushDRunning : logging . flushD . isRunning ( ) ,
maxSize : MaxSize ,
2022-07-07 20:11:50 +00:00
}
}
2024-02-24 00:34:55 +00:00
// State stores a snapshot of klog settings. It gets created with CaptureState
// and can be used to restore the entire state. Modifying individual settings
// is supported via the command line flags.
type State interface {
// Restore restore the entire state. It may get called more than once.
Restore ( )
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
type state struct {
settings
flushDRunning bool
maxSize uint64
}
func ( s * state ) Restore ( ) {
// This needs to be done before mutex locking.
if s . flushDRunning && ! logging . flushD . isRunning ( ) {
// This is not quite accurate: StartFlushDaemon might
// have been called with some different interval.
interval := s . flushInterval
if interval == 0 {
interval = flushInterval
}
logging . flushD . run ( interval )
} else if ! s . flushDRunning && logging . flushD . isRunning ( ) {
logging . flushD . stop ( )
}
logging . mu . Lock ( )
defer logging . mu . Unlock ( )
logging . settings = s . settings
logging . setVState ( s . verbosity , s . vmodule . filter , true )
MaxSize = s . maxSize
}
2022-07-07 20:11:50 +00:00
/ *
header formats a log header as defined by the C ++ implementation .
It returns a buffer containing the formatted header and the user ' s file and line number .
The depth specifies how many stack frames above lives the source line to be identified in the log message .
Log lines have this form :
2024-02-24 00:34:55 +00:00
2022-07-07 20:11:50 +00:00
Lmmdd hh : mm : ss . uuuuuu threadid file : line ] msg ...
2024-02-24 00:34:55 +00:00
2022-07-07 20:11:50 +00:00
where the fields are defined as follows :
2024-02-24 00:34:55 +00:00
2022-07-07 20:11:50 +00:00
L A single character , representing the log level ( eg 'I' for INFO )
mm The month ( zero padded ; ie May is ' 05 ' )
dd The day ( zero padded )
hh : mm : ss . uuuuuu Time in hours , minutes and fractional seconds
threadid The space - padded thread ID as returned by GetTID ( )
file The file name
line The line number
msg The user - supplied message
* /
2024-02-24 00:34:55 +00:00
func ( l * loggingT ) header ( s severity . Severity , depth int ) ( * buffer . Buffer , string , int ) {
2022-07-07 20:11:50 +00:00
_ , file , line , ok := runtime . Caller ( 3 + depth )
if ! ok {
file = "???"
line = 1
} else {
if slash := strings . LastIndex ( file , "/" ) ; slash >= 0 {
path := file
file = path [ slash + 1 : ]
if l . addDirHeader {
if dirsep := strings . LastIndex ( path [ : slash ] , "/" ) ; dirsep >= 0 {
file = path [ dirsep + 1 : ]
}
}
}
}
2024-02-24 00:34:55 +00:00
return l . formatHeader ( s , file , line , timeNow ( ) ) , file , line
2022-07-07 20:11:50 +00:00
}
// formatHeader formats a log header using the provided file name and line number.
2024-02-24 00:34:55 +00:00
func ( l * loggingT ) formatHeader ( s severity . Severity , file string , line int , now time . Time ) * buffer . Buffer {
buf := buffer . GetBuffer ( )
2022-07-07 20:11:50 +00:00
if l . skipHeaders {
return buf
}
2024-02-24 00:34:55 +00:00
buf . FormatHeader ( s , file , line , now )
2022-07-07 20:11:50 +00:00
return buf
}
2024-02-24 00:34:55 +00:00
func ( l * loggingT ) println ( s severity . Severity , logger * logWriter , filter LogFilter , args ... interface { } ) {
l . printlnDepth ( s , logger , filter , 1 , args ... )
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
func ( l * loggingT ) printlnDepth ( s severity . Severity , logger * logWriter , filter LogFilter , depth int , args ... interface { } ) {
if false {
_ = fmt . Sprintln ( args ... ) // cause vet to treat this function like fmt.Println
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
buf , file , line := l . header ( s , depth )
// If a logger is set and doesn't support writing a formatted buffer,
// we clear the generated header as we rely on the backing
// logger implementation to print headers.
if logger != nil && logger . writeKlogBuffer == nil {
buffer . PutBuffer ( buf )
buf = buffer . GetBuffer ( )
2022-07-07 20:11:50 +00:00
}
if filter != nil {
args = filter . Filter ( args )
}
fmt . Fprintln ( buf , args ... )
2024-02-24 00:34:55 +00:00
l . output ( s , logger , buf , depth , file , line , false )
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
func ( l * loggingT ) print ( s severity . Severity , logger * logWriter , filter LogFilter , args ... interface { } ) {
l . printDepth ( s , logger , filter , 1 , args ... )
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
func ( l * loggingT ) printDepth ( s severity . Severity , logger * logWriter , filter LogFilter , depth int , args ... interface { } ) {
if false {
_ = fmt . Sprint ( args ... ) // // cause vet to treat this function like fmt.Print
}
2022-07-07 20:11:50 +00:00
buf , file , line := l . header ( s , depth )
2024-02-24 00:34:55 +00:00
l . printWithInfos ( buf , file , line , s , logger , filter , depth + 1 , args ... )
}
func ( l * loggingT ) printWithInfos ( buf * buffer . Buffer , file string , line int , s severity . Severity , logger * logWriter , filter LogFilter , depth int , args ... interface { } ) {
// If a logger is set and doesn't support writing a formatted buffer,
// we clear the generated header as we rely on the backing
// logger implementation to print headers.
if logger != nil && logger . writeKlogBuffer == nil {
buffer . PutBuffer ( buf )
buf = buffer . GetBuffer ( )
2022-07-07 20:11:50 +00:00
}
if filter != nil {
args = filter . Filter ( args )
}
fmt . Fprint ( buf , args ... )
2024-02-24 00:34:55 +00:00
if buf . Len ( ) == 0 || buf . Bytes ( ) [ buf . Len ( ) - 1 ] != '\n' {
2022-07-07 20:11:50 +00:00
buf . WriteByte ( '\n' )
}
2024-02-24 00:34:55 +00:00
l . output ( s , logger , buf , depth , file , line , false )
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
func ( l * loggingT ) printf ( s severity . Severity , logger * logWriter , filter LogFilter , format string , args ... interface { } ) {
l . printfDepth ( s , logger , filter , 1 , format , args ... )
}
func ( l * loggingT ) printfDepth ( s severity . Severity , logger * logWriter , filter LogFilter , depth int , format string , args ... interface { } ) {
if false {
_ = fmt . Sprintf ( format , args ... ) // cause vet to treat this function like fmt.Printf
}
buf , file , line := l . header ( s , depth )
// If a logger is set and doesn't support writing a formatted buffer,
// we clear the generated header as we rely on the backing
// logger implementation to print headers.
if logger != nil && logger . writeKlogBuffer == nil {
buffer . PutBuffer ( buf )
buf = buffer . GetBuffer ( )
2022-07-07 20:11:50 +00:00
}
if filter != nil {
format , args = filter . FilterF ( format , args )
}
fmt . Fprintf ( buf , format , args ... )
if buf . Bytes ( ) [ buf . Len ( ) - 1 ] != '\n' {
buf . WriteByte ( '\n' )
}
2024-02-24 00:34:55 +00:00
l . output ( s , logger , buf , depth , file , line , false )
2022-07-07 20:11:50 +00:00
}
// printWithFileLine behaves like print but uses the provided file and line number. If
// alsoLogToStderr is true, the log message always appears on standard error; it
// will also appear in the log file unless --logtostderr is set.
2024-02-24 00:34:55 +00:00
func ( l * loggingT ) printWithFileLine ( s severity . Severity , logger * logWriter , filter LogFilter , file string , line int , alsoToStderr bool , args ... interface { } ) {
buf := l . formatHeader ( s , file , line , timeNow ( ) )
// If a logger is set and doesn't support writing a formatted buffer,
// we clear the generated header as we rely on the backing
// logger implementation to print headers.
if logger != nil && logger . writeKlogBuffer == nil {
buffer . PutBuffer ( buf )
buf = buffer . GetBuffer ( )
2022-07-07 20:11:50 +00:00
}
if filter != nil {
args = filter . Filter ( args )
}
fmt . Fprint ( buf , args ... )
if buf . Bytes ( ) [ buf . Len ( ) - 1 ] != '\n' {
buf . WriteByte ( '\n' )
}
2024-02-24 00:34:55 +00:00
l . output ( s , logger , buf , 2 /* depth */ , file , line , alsoToStderr )
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
// if logger is specified, will call logger.Error, otherwise output with logging module.
func ( l * loggingT ) errorS ( err error , logger * logWriter , filter LogFilter , depth int , msg string , keysAndValues ... interface { } ) {
2022-07-07 20:11:50 +00:00
if filter != nil {
msg , keysAndValues = filter . FilterS ( msg , keysAndValues )
}
2024-02-24 00:34:55 +00:00
if logger != nil {
logger . WithCallDepth ( depth + 2 ) . Error ( err , msg , keysAndValues ... )
2022-07-07 20:11:50 +00:00
return
}
2024-02-24 00:34:55 +00:00
l . printS ( err , severity . ErrorLog , depth + 1 , msg , keysAndValues ... )
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
// if logger is specified, will call logger.Info, otherwise output with logging module.
func ( l * loggingT ) infoS ( logger * logWriter , filter LogFilter , depth int , msg string , keysAndValues ... interface { } ) {
2022-07-07 20:11:50 +00:00
if filter != nil {
msg , keysAndValues = filter . FilterS ( msg , keysAndValues )
}
2024-02-24 00:34:55 +00:00
if logger != nil {
logger . WithCallDepth ( depth + 2 ) . Info ( msg , keysAndValues ... )
2022-07-07 20:11:50 +00:00
return
}
2024-02-24 00:34:55 +00:00
l . printS ( nil , severity . InfoLog , depth + 1 , msg , keysAndValues ... )
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
// printS is called from infoS and errorS if logger is not specified.
2022-07-07 20:11:50 +00:00
// set log severity by s
2024-02-24 00:34:55 +00:00
func ( l * loggingT ) printS ( err error , s severity . Severity , depth int , msg string , keysAndValues ... interface { } ) {
// Only create a new buffer if we don't have one cached.
b := buffer . GetBuffer ( )
// The message is always quoted, even if it contains line breaks.
// If developers want multi-line output, they should use a small, fixed
// message and put the multi-line output into a value.
b . WriteString ( strconv . Quote ( msg ) )
2022-07-07 20:11:50 +00:00
if err != nil {
2024-02-24 00:34:55 +00:00
serialize . KVListFormat ( & b . Buffer , "err" , err )
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
serialize . KVListFormat ( & b . Buffer , keysAndValues ... )
l . printDepth ( s , nil , nil , depth + 1 , & b . Buffer )
// Make the buffer available for reuse.
buffer . PutBuffer ( b )
2022-07-07 20:11:50 +00:00
}
// redirectBuffer is used to set an alternate destination for the logs
type redirectBuffer struct {
w io . Writer
}
func ( rb * redirectBuffer ) Sync ( ) error {
return nil
}
func ( rb * redirectBuffer ) Flush ( ) error {
return nil
}
func ( rb * redirectBuffer ) Write ( bytes [ ] byte ) ( n int , err error ) {
return rb . w . Write ( bytes )
}
// SetOutput sets the output destination for all severities
func SetOutput ( w io . Writer ) {
logging . mu . Lock ( )
defer logging . mu . Unlock ( )
2024-02-24 00:34:55 +00:00
for s := severity . FatalLog ; s >= severity . InfoLog ; s -- {
2022-07-07 20:11:50 +00:00
rb := & redirectBuffer {
w : w ,
}
logging . file [ s ] = rb
}
}
// SetOutputBySeverity sets the output destination for specific severity
func SetOutputBySeverity ( name string , w io . Writer ) {
logging . mu . Lock ( )
defer logging . mu . Unlock ( )
2024-02-24 00:34:55 +00:00
sev , ok := severity . ByName ( name )
2022-07-07 20:11:50 +00:00
if ! ok {
panic ( fmt . Sprintf ( "SetOutputBySeverity(%q): unrecognized severity name" , name ) )
}
rb := & redirectBuffer {
w : w ,
}
logging . file [ sev ] = rb
}
// LogToStderr sets whether to log exclusively to stderr, bypassing outputs
func LogToStderr ( stderr bool ) {
logging . mu . Lock ( )
defer logging . mu . Unlock ( )
logging . toStderr = stderr
}
// output writes the data to the log files and releases the buffer.
2024-02-24 00:34:55 +00:00
func ( l * loggingT ) output ( s severity . Severity , logger * logWriter , buf * buffer . Buffer , depth int , file string , line int , alsoToStderr bool ) {
var isLocked = true
2022-07-07 20:11:50 +00:00
l . mu . Lock ( )
2024-02-24 00:34:55 +00:00
defer func ( ) {
if isLocked {
// Unlock before returning in case that it wasn't done already.
l . mu . Unlock ( )
}
} ( )
2022-07-07 20:11:50 +00:00
if l . traceLocation . isSet ( ) {
if l . traceLocation . match ( file , line ) {
2024-02-24 00:34:55 +00:00
buf . Write ( dbg . Stacks ( false ) )
2022-07-07 20:11:50 +00:00
}
}
data := buf . Bytes ( )
2024-02-24 00:34:55 +00:00
if logger != nil {
if logger . writeKlogBuffer != nil {
logger . writeKlogBuffer ( data )
2022-07-07 20:11:50 +00:00
} else {
2024-02-24 00:34:55 +00:00
if len ( data ) > 0 && data [ len ( data ) - 1 ] == '\n' {
data = data [ : len ( data ) - 1 ]
}
// TODO: set 'severity' and caller information as structured log info
// keysAndValues := []interface{}{"severity", severityName[s], "file", file, "line", line}
if s == severity . ErrorLog {
logger . WithCallDepth ( depth + 3 ) . Error ( nil , string ( data ) )
} else {
logger . WithCallDepth ( depth + 3 ) . Info ( string ( data ) )
}
2022-07-07 20:11:50 +00:00
}
} else if l . toStderr {
os . Stderr . Write ( data )
} else {
if alsoToStderr || l . alsoToStderr || s >= l . stderrThreshold . get ( ) {
os . Stderr . Write ( data )
}
if logging . logFile != "" {
// Since we are using a single log file, all of the items in l.file array
// will point to the same file, so just use one of them to write data.
2024-02-24 00:34:55 +00:00
if l . file [ severity . InfoLog ] == nil {
if err := l . createFiles ( severity . InfoLog ) ; err != nil {
2022-07-07 20:11:50 +00:00
os . Stderr . Write ( data ) // Make sure the message appears somewhere.
l . exit ( err )
}
}
2024-02-24 00:34:55 +00:00
_ , _ = l . file [ severity . InfoLog ] . Write ( data )
2022-07-07 20:11:50 +00:00
} else {
if l . file [ s ] == nil {
if err := l . createFiles ( s ) ; err != nil {
os . Stderr . Write ( data ) // Make sure the message appears somewhere.
l . exit ( err )
}
}
if l . oneOutput {
2024-02-24 00:34:55 +00:00
_ , _ = l . file [ s ] . Write ( data )
2022-07-07 20:11:50 +00:00
} else {
switch s {
2024-02-24 00:34:55 +00:00
case severity . FatalLog :
_ , _ = l . file [ severity . FatalLog ] . Write ( data )
2022-07-07 20:11:50 +00:00
fallthrough
2024-02-24 00:34:55 +00:00
case severity . ErrorLog :
_ , _ = l . file [ severity . ErrorLog ] . Write ( data )
2022-07-07 20:11:50 +00:00
fallthrough
2024-02-24 00:34:55 +00:00
case severity . WarningLog :
_ , _ = l . file [ severity . WarningLog ] . Write ( data )
2022-07-07 20:11:50 +00:00
fallthrough
2024-02-24 00:34:55 +00:00
case severity . InfoLog :
_ , _ = l . file [ severity . InfoLog ] . Write ( data )
2022-07-07 20:11:50 +00:00
}
}
}
}
2024-02-24 00:34:55 +00:00
if s == severity . FatalLog {
2022-07-07 20:11:50 +00:00
// If we got here via Exit rather than Fatal, print no stacks.
if atomic . LoadUint32 ( & fatalNoStacks ) > 0 {
l . mu . Unlock ( )
2024-02-24 00:34:55 +00:00
isLocked = false
timeoutFlush ( ExitFlushTimeout )
OsExit ( 1 )
2022-07-07 20:11:50 +00:00
}
// Dump all goroutine stacks before exiting.
2024-02-24 00:34:55 +00:00
// First, make sure we see the trace for the current goroutine on standard error.
// If -logtostderr has been specified, the loop below will do that anyway
// as the first stack in the full dump.
if ! l . toStderr {
os . Stderr . Write ( dbg . Stacks ( false ) )
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
2022-07-07 20:11:50 +00:00
// Write the stack trace for all goroutines to the files.
2024-02-24 00:34:55 +00:00
trace := dbg . Stacks ( true )
2022-07-07 20:11:50 +00:00
logExitFunc = func ( error ) { } // If we get a write error, we'll still exit below.
2024-02-24 00:34:55 +00:00
for log := severity . FatalLog ; log >= severity . InfoLog ; log -- {
2022-07-07 20:11:50 +00:00
if f := l . file [ log ] ; f != nil { // Can be nil if -logtostderr is set.
2024-02-24 00:34:55 +00:00
_ , _ = f . Write ( trace )
2022-07-07 20:11:50 +00:00
}
}
l . mu . Unlock ( )
2024-02-24 00:34:55 +00:00
isLocked = false
timeoutFlush ( ExitFlushTimeout )
OsExit ( 255 ) // C++ uses -1, which is silly because it's anded with 255 anyway.
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
buffer . PutBuffer ( buf )
2022-07-07 20:11:50 +00:00
if stats := severityStats [ s ] ; stats != nil {
atomic . AddInt64 ( & stats . lines , 1 )
atomic . AddInt64 ( & stats . bytes , int64 ( len ( data ) ) )
}
}
// logExitFunc provides a simple mechanism to override the default behavior
// of exiting on error. Used in testing and to guarantee we reach a required exit
// for fatal logs. Instead, exit could be a function rather than a method but that
// would make its use clumsier.
var logExitFunc func ( error )
// exit is called if there is trouble creating or writing log files.
// It flushes the logs and exits the program; there's no point in hanging around.
// l.mu is held.
func ( l * loggingT ) exit ( err error ) {
fmt . Fprintf ( os . Stderr , "log: exiting because of error: %s\n" , err )
// If logExitFunc is set, we do that instead of exiting.
if logExitFunc != nil {
logExitFunc ( err )
return
}
l . flushAll ( )
2024-02-24 00:34:55 +00:00
OsExit ( 2 )
2022-07-07 20:11:50 +00:00
}
// syncBuffer joins a bufio.Writer to its underlying file, providing access to the
// file's Sync method and providing a wrapper for the Write method that provides log
// file rotation. There are conflicting methods, so the file cannot be embedded.
// l.mu is held for all its methods.
type syncBuffer struct {
logger * loggingT
* bufio . Writer
file * os . File
2024-02-24 00:34:55 +00:00
sev severity . Severity
2022-07-07 20:11:50 +00:00
nbytes uint64 // The number of bytes written to this file
maxbytes uint64 // The max number of bytes this syncBuffer.file can hold before cleaning up.
}
func ( sb * syncBuffer ) Sync ( ) error {
return sb . file . Sync ( )
}
// CalculateMaxSize returns the real max size in bytes after considering the default max size and the flag options.
func CalculateMaxSize ( ) uint64 {
if logging . logFile != "" {
if logging . logFileMaxSizeMB == 0 {
// If logFileMaxSizeMB is zero, we don't have limitations on the log size.
return math . MaxUint64
}
// Flag logFileMaxSizeMB is in MB for user convenience.
return logging . logFileMaxSizeMB * 1024 * 1024
}
// If "log_file" flag is not specified, the target file (sb.file) will be cleaned up when reaches a fixed size.
return MaxSize
}
func ( sb * syncBuffer ) Write ( p [ ] byte ) ( n int , err error ) {
if sb . nbytes + uint64 ( len ( p ) ) >= sb . maxbytes {
if err := sb . rotateFile ( time . Now ( ) , false ) ; err != nil {
sb . logger . exit ( err )
}
}
n , err = sb . Writer . Write ( p )
sb . nbytes += uint64 ( n )
if err != nil {
sb . logger . exit ( err )
}
return
}
// rotateFile closes the syncBuffer's file and starts a new one.
// The startup argument indicates whether this is the initial startup of klog.
// If startup is true, existing files are opened for appending instead of truncated.
func ( sb * syncBuffer ) rotateFile ( now time . Time , startup bool ) error {
if sb . file != nil {
sb . Flush ( )
sb . file . Close ( )
}
var err error
2024-02-24 00:34:55 +00:00
sb . file , _ , err = create ( severity . Name [ sb . sev ] , now , startup )
2022-07-07 20:11:50 +00:00
if err != nil {
return err
}
if startup {
fileInfo , err := sb . file . Stat ( )
if err != nil {
return fmt . Errorf ( "file stat could not get fileinfo: %v" , err )
}
// init file size
sb . nbytes = uint64 ( fileInfo . Size ( ) )
} else {
sb . nbytes = 0
}
sb . Writer = bufio . NewWriterSize ( sb . file , bufferSize )
if sb . logger . skipLogHeaders {
return nil
}
// Write header.
var buf bytes . Buffer
fmt . Fprintf ( & buf , "Log file created at: %s\n" , now . Format ( "2006/01/02 15:04:05" ) )
fmt . Fprintf ( & buf , "Running on machine: %s\n" , host )
fmt . Fprintf ( & buf , "Binary: Built with %s %s for %s/%s\n" , runtime . Compiler , runtime . Version ( ) , runtime . GOOS , runtime . GOARCH )
fmt . Fprintf ( & buf , "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg\n" )
n , err := sb . file . Write ( buf . Bytes ( ) )
sb . nbytes += uint64 ( n )
return err
}
// bufferSize sizes the buffer associated with each log file. It's large
// so that log records can accumulate without the logging thread blocking
// on disk I/O. The flushDaemon will block instead.
const bufferSize = 256 * 1024
// createFiles creates all the log files for severity from sev down to infoLog.
// l.mu is held.
2024-02-24 00:34:55 +00:00
func ( l * loggingT ) createFiles ( sev severity . Severity ) error {
interval := l . flushInterval
if interval == 0 {
interval = flushInterval
}
l . flushD . run ( interval )
2022-07-07 20:11:50 +00:00
now := time . Now ( )
// Files are created in decreasing severity order, so as soon as we find one
// has already been created, we can stop.
2024-02-24 00:34:55 +00:00
for s := sev ; s >= severity . InfoLog && l . file [ s ] == nil ; s -- {
2022-07-07 20:11:50 +00:00
sb := & syncBuffer {
logger : l ,
sev : s ,
maxbytes : CalculateMaxSize ( ) ,
}
if err := sb . rotateFile ( now , true ) ; err != nil {
return err
}
l . file [ s ] = sb
}
return nil
}
const flushInterval = 5 * time . Second
// flushDaemon periodically flushes the log file buffers.
2024-02-24 00:34:55 +00:00
type flushDaemon struct {
mu sync . Mutex
clock clock . Clock
flush func ( )
stopC chan struct { }
stopDone chan struct { }
}
// newFlushDaemon returns a new flushDaemon. If the passed clock is nil, a
// clock.RealClock is used.
func newFlushDaemon ( flush func ( ) , tickClock clock . Clock ) * flushDaemon {
if tickClock == nil {
tickClock = clock . RealClock { }
}
return & flushDaemon {
flush : flush ,
clock : tickClock ,
}
}
// run starts a goroutine that periodically calls the daemons flush function.
// Calling run on an already running daemon will have no effect.
func ( f * flushDaemon ) run ( interval time . Duration ) {
f . mu . Lock ( )
defer f . mu . Unlock ( )
if f . stopC != nil { // daemon already running
return
}
f . stopC = make ( chan struct { } , 1 )
f . stopDone = make ( chan struct { } , 1 )
ticker := f . clock . NewTicker ( interval )
go func ( ) {
defer ticker . Stop ( )
defer func ( ) { f . stopDone <- struct { } { } } ( )
for {
select {
case <- ticker . C ( ) :
f . flush ( )
case <- f . stopC :
f . flush ( )
return
}
}
} ( )
}
// stop stops the running flushDaemon and waits until the daemon has shut down.
// Calling stop on a daemon that isn't running will have no effect.
func ( f * flushDaemon ) stop ( ) {
f . mu . Lock ( )
defer f . mu . Unlock ( )
if f . stopC == nil { // daemon not running
return
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
f . stopC <- struct { } { }
<- f . stopDone
f . stopC = nil
f . stopDone = nil
}
// isRunning returns true if the flush daemon is running.
func ( f * flushDaemon ) isRunning ( ) bool {
f . mu . Lock ( )
defer f . mu . Unlock ( )
return f . stopC != nil
}
// StopFlushDaemon stops the flush daemon, if running, and flushes once.
// This prevents klog from leaking goroutines on shutdown. After stopping
// the daemon, you can still manually flush buffers again by calling Flush().
func StopFlushDaemon ( ) {
logging . flushD . stop ( )
}
// StartFlushDaemon ensures that the flush daemon runs with the given delay
// between flush calls. If it is already running, it gets restarted.
func StartFlushDaemon ( interval time . Duration ) {
StopFlushDaemon ( )
logging . flushD . run ( interval )
2022-07-07 20:11:50 +00:00
}
// lockAndFlushAll is like flushAll but locks l.mu first.
func ( l * loggingT ) lockAndFlushAll ( ) {
l . mu . Lock ( )
l . flushAll ( )
l . mu . Unlock ( )
}
// flushAll flushes all the logs and attempts to "sync" their data to disk.
// l.mu is held.
func ( l * loggingT ) flushAll ( ) {
// Flush from fatal down, in case there's trouble flushing.
2024-02-24 00:34:55 +00:00
for s := severity . FatalLog ; s >= severity . InfoLog ; s -- {
2022-07-07 20:11:50 +00:00
file := l . file [ s ]
if file != nil {
2024-02-24 00:34:55 +00:00
_ = file . Flush ( ) // ignore error
_ = file . Sync ( ) // ignore error
2022-07-07 20:11:50 +00:00
}
}
2024-02-24 00:34:55 +00:00
if logging . loggerOptions . flush != nil {
logging . loggerOptions . flush ( )
}
2022-07-07 20:11:50 +00:00
}
// 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 ) {
2024-02-24 00:34:55 +00:00
sev , ok := severity . ByName ( name )
2022-07-07 20:11:50 +00:00
if ! ok {
panic ( fmt . Sprintf ( "log.CopyStandardLogTo(%q): unrecognized severity name" , name ) )
}
// Set a log format that captures the user's file and line:
// d.go:23: message
stdLog . SetFlags ( stdLog . Lshortfile )
stdLog . SetOutput ( logBridge ( sev ) )
}
2024-02-24 00:34:55 +00:00
// NewStandardLogger returns a Logger that writes to the klog 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 , ok := severity . ByName ( name )
if ! ok {
panic ( fmt . Sprintf ( "klog.NewStandardLogger(%q): unknown severity" , name ) )
}
return stdLog . New ( logBridge ( sev ) , "" , stdLog . Lshortfile )
}
2022-07-07 20:11:50 +00:00
// logBridge provides the Write method that enables CopyStandardLogTo to connect
// Go's standard logs to the logs provided by this package.
2024-02-24 00:34:55 +00:00
type logBridge severity . Severity
2022-07-07 20:11:50 +00:00
// 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
}
}
// printWithFileLine with alsoToStderr=true, so standard log messages
// always appear on standard error.
2024-02-24 00:34:55 +00:00
logging . printWithFileLine ( severity . Severity ( lb ) , logging . logger , logging . filter , file , line , true , text )
2022-07-07 20:11:50 +00:00
return len ( b ) , nil
}
// setV computes and remembers the V level for a given PC
// when vmodule is enabled.
// File pattern matching takes the basename of the file, stripped
// of its .go suffix, and uses filepath.Match, which is a little more
// general than the *? matching used in C++.
// l.mu is held.
func ( l * loggingT ) setV ( pc uintptr ) Level {
fn := runtime . FuncForPC ( pc )
file , _ := fn . FileLine ( pc )
// The file is something like /a/b/c/d.go. We want just the d.
2024-02-24 00:34:55 +00:00
file = strings . TrimSuffix ( file , ".go" )
2022-07-07 20:11:50 +00:00
if slash := strings . LastIndex ( file , "/" ) ; slash >= 0 {
file = file [ slash + 1 : ]
}
for _ , filter := range l . vmodule . filter {
if filter . match ( file ) {
l . vmap [ pc ] = filter . level
return filter . level
}
}
l . vmap [ pc ] = 0
return 0
}
// Verbose is a boolean type that implements Infof (like Printf) etc.
// See the documentation of V for more information.
type Verbose struct {
enabled bool
2024-02-24 00:34:55 +00:00
logger * logWriter
2022-07-07 20:11:50 +00:00
}
func newVerbose ( level Level , b bool ) Verbose {
2024-02-24 00:34:55 +00:00
if logging . logger == nil {
return Verbose { b , nil }
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
v := logging . logger . V ( int ( level ) )
return Verbose { b , & logWriter { Logger : v , writeKlogBuffer : logging . loggerOptions . writeKlogBuffer } }
2022-07-07 20:11:50 +00:00
}
// V reports whether verbosity at the call site is at least the requested level.
// The returned value is a struct of type Verbose, which implements Info, Infoln
// and Infof. These methods will write to the Info log if called.
// Thus, one may write either
2024-02-24 00:34:55 +00:00
//
// if klog.V(2).Enabled() { klog.Info("log this") }
//
2022-07-07 20:11:50 +00:00
// or
2024-02-24 00:34:55 +00:00
//
2022-07-07 20:11:50 +00:00
// klog.V(2).Info("log this")
2024-02-24 00:34:55 +00:00
//
2022-07-07 20:11:50 +00:00
// 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. The V call will log if its level
// is less than or equal to the value of the -v flag, or alternatively if its level is
// less than or equal to the value of the -vmodule pattern matching the source file
// containing the call.
func V ( level Level ) Verbose {
2024-02-24 00:34:55 +00:00
return VDepth ( 1 , level )
}
// VDepth is a variant of V that accepts a number of stack frames that will be
// skipped when checking the -vmodule patterns. VDepth(0) is equivalent to
// V().
func VDepth ( depth int , level Level ) Verbose {
2022-07-07 20:11:50 +00:00
// This function tries hard to be cheap unless there's work to do.
// The fast path is two atomic loads and compares.
// Here is a cheap but safe test to see if V logging is enabled globally.
if logging . verbosity . get ( ) >= level {
return newVerbose ( level , true )
}
// It's off globally but vmodule may still be set.
// Here is another cheap but safe test to see if vmodule is enabled.
if atomic . LoadInt32 ( & logging . filterLength ) > 0 {
// Now we need a proper lock to use the logging structure. The pcs field
// is shared so we must lock before accessing it. This is fairly expensive,
// but if V logging is enabled we're slow anyway.
logging . mu . Lock ( )
defer logging . mu . Unlock ( )
2024-02-24 00:34:55 +00:00
if runtime . Callers ( 2 + depth , logging . pcs [ : ] ) == 0 {
2022-07-07 20:11:50 +00:00
return newVerbose ( level , false )
}
2024-02-24 00:34:55 +00:00
// runtime.Callers returns "return PCs", but we want
// to look up the symbolic information for the call,
// so subtract 1 from the PC. runtime.CallersFrames
// would be cleaner, but allocates.
pc := logging . pcs [ 0 ] - 1
v , ok := logging . vmap [ pc ]
2022-07-07 20:11:50 +00:00
if ! ok {
2024-02-24 00:34:55 +00:00
v = logging . setV ( pc )
2022-07-07 20:11:50 +00:00
}
return newVerbose ( level , v >= level )
}
return newVerbose ( level , false )
}
// Enabled will return true if this log level is enabled, guarded by the value
// of v.
// See the documentation of V for usage.
func ( v Verbose ) Enabled ( ) bool {
return v . enabled
}
// 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 ... interface { } ) {
if v . enabled {
2024-02-24 00:34:55 +00:00
logging . print ( severity . InfoLog , v . logger , logging . filter , 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 ... interface { } ) {
if v . enabled {
logging . printDepth ( severity . InfoLog , v . logger , logging . filter , depth , args ... )
2022-07-07 20:11:50 +00:00
}
}
// 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 ... interface { } ) {
if v . enabled {
2024-02-24 00:34:55 +00:00
logging . println ( severity . InfoLog , v . logger , logging . filter , args ... )
}
}
// InfolnDepth is equivalent to the global InfolnDepth function, guarded by the value of v.
// See the documentation of V for usage.
func ( v Verbose ) InfolnDepth ( depth int , args ... interface { } ) {
if v . enabled {
logging . printlnDepth ( severity . InfoLog , v . logger , logging . filter , depth , args ... )
2022-07-07 20:11:50 +00:00
}
}
// 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 ... interface { } ) {
if v . enabled {
2024-02-24 00:34:55 +00:00
logging . printf ( severity . InfoLog , v . logger , logging . filter , format , args ... )
}
}
// InfofDepth is equivalent to the global InfofDepth function, guarded by the value of v.
// See the documentation of V for usage.
func ( v Verbose ) InfofDepth ( depth int , format string , args ... interface { } ) {
if v . enabled {
logging . printfDepth ( severity . InfoLog , v . logger , logging . filter , depth , format , args ... )
2022-07-07 20:11:50 +00:00
}
}
// InfoS is equivalent to the global InfoS function, guarded by the value of v.
// See the documentation of V for usage.
func ( v Verbose ) InfoS ( msg string , keysAndValues ... interface { } ) {
if v . enabled {
2024-02-24 00:34:55 +00:00
logging . infoS ( v . logger , logging . filter , 0 , msg , keysAndValues ... )
2022-07-07 20:11:50 +00:00
}
}
// InfoSDepth acts as InfoS but uses depth to determine which call frame to log.
// InfoSDepth(0, "msg") is the same as InfoS("msg").
func InfoSDepth ( depth int , msg string , keysAndValues ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . infoS ( logging . logger , logging . filter , depth , msg , keysAndValues ... )
}
// InfoSDepth is equivalent to the global InfoSDepth function, guarded by the value of v.
// See the documentation of V for usage.
func ( v Verbose ) InfoSDepth ( depth int , msg string , keysAndValues ... interface { } ) {
if v . enabled {
logging . infoS ( v . logger , logging . filter , depth , msg , keysAndValues ... )
}
2022-07-07 20:11:50 +00:00
}
// Deprecated: Use ErrorS instead.
func ( v Verbose ) Error ( err error , msg string , args ... interface { } ) {
if v . enabled {
2024-02-24 00:34:55 +00:00
logging . errorS ( err , v . logger , logging . filter , 0 , msg , args ... )
2022-07-07 20:11:50 +00:00
}
}
// ErrorS is equivalent to the global Error function, guarded by the value of v.
// See the documentation of V for usage.
func ( v Verbose ) ErrorS ( err error , msg string , keysAndValues ... interface { } ) {
if v . enabled {
2024-02-24 00:34:55 +00:00
logging . errorS ( err , v . logger , logging . filter , 0 , msg , keysAndValues ... )
2022-07-07 20:11:50 +00:00
}
}
// Info logs to the INFO log.
// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
func Info ( args ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . print ( severity . InfoLog , logging . logger , logging . filter , args ... )
2022-07-07 20:11:50 +00:00
}
// InfoDepth acts as Info but uses depth to determine which call frame to log.
// InfoDepth(0, "msg") is the same as Info("msg").
func InfoDepth ( depth int , args ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . printDepth ( severity . InfoLog , logging . logger , logging . filter , depth , args ... )
2022-07-07 20:11:50 +00:00
}
// Infoln logs to the INFO log.
// Arguments are handled in the manner of fmt.Println; a newline is always appended.
func Infoln ( args ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . println ( severity . InfoLog , logging . logger , logging . filter , args ... )
}
// InfolnDepth acts as Infoln but uses depth to determine which call frame to log.
// InfolnDepth(0, "msg") is the same as Infoln("msg").
func InfolnDepth ( depth int , args ... interface { } ) {
logging . printlnDepth ( severity . InfoLog , logging . logger , logging . filter , depth , args ... )
2022-07-07 20:11:50 +00:00
}
// 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 ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . printf ( severity . InfoLog , logging . logger , logging . filter , format , args ... )
}
// InfofDepth acts as Infof but uses depth to determine which call frame to log.
// InfofDepth(0, "msg", args...) is the same as Infof("msg", args...).
func InfofDepth ( depth int , format string , args ... interface { } ) {
logging . printfDepth ( severity . InfoLog , logging . logger , logging . filter , depth , format , args ... )
2022-07-07 20:11:50 +00:00
}
// InfoS structured logs to the INFO log.
// The msg argument used to add constant description to the log line.
// The key/value pairs would be join by "=" ; a newline is always appended.
//
// Basic examples:
// >> klog.InfoS("Pod status updated", "pod", "kubedns", "status", "ready")
// output:
// >> I1025 00:15:15.525108 1 controller_utils.go:116] "Pod status updated" pod="kubedns" status="ready"
func InfoS ( msg string , keysAndValues ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . infoS ( logging . logger , logging . filter , 0 , msg , keysAndValues ... )
2022-07-07 20:11:50 +00:00
}
// 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 ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . print ( severity . WarningLog , logging . logger , logging . filter , args ... )
2022-07-07 20:11:50 +00:00
}
// 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 ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . printDepth ( severity . WarningLog , logging . logger , logging . filter , depth , args ... )
2022-07-07 20:11:50 +00:00
}
// Warningln logs to the WARNING and INFO logs.
// Arguments are handled in the manner of fmt.Println; a newline is always appended.
func Warningln ( args ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . println ( severity . WarningLog , logging . logger , logging . filter , args ... )
}
// WarninglnDepth acts as Warningln but uses depth to determine which call frame to log.
// WarninglnDepth(0, "msg") is the same as Warningln("msg").
func WarninglnDepth ( depth int , args ... interface { } ) {
logging . printlnDepth ( severity . WarningLog , logging . logger , logging . filter , depth , args ... )
2022-07-07 20:11:50 +00:00
}
// 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 ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . printf ( severity . WarningLog , logging . logger , logging . filter , format , args ... )
}
// WarningfDepth acts as Warningf but uses depth to determine which call frame to log.
// WarningfDepth(0, "msg", args...) is the same as Warningf("msg", args...).
func WarningfDepth ( depth int , format string , args ... interface { } ) {
logging . printfDepth ( severity . WarningLog , logging . logger , logging . filter , depth , format , args ... )
2022-07-07 20:11:50 +00:00
}
// 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 ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . print ( severity . ErrorLog , logging . logger , logging . filter , args ... )
2022-07-07 20:11:50 +00:00
}
// 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 ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . printDepth ( severity . ErrorLog , logging . logger , logging . filter , depth , args ... )
2022-07-07 20:11:50 +00:00
}
// Errorln logs to the ERROR, WARNING, and INFO logs.
// Arguments are handled in the manner of fmt.Println; a newline is always appended.
func Errorln ( args ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . println ( severity . ErrorLog , logging . logger , logging . filter , args ... )
}
// ErrorlnDepth acts as Errorln but uses depth to determine which call frame to log.
// ErrorlnDepth(0, "msg") is the same as Errorln("msg").
func ErrorlnDepth ( depth int , args ... interface { } ) {
logging . printlnDepth ( severity . ErrorLog , logging . logger , logging . filter , depth , args ... )
2022-07-07 20:11:50 +00:00
}
// 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 ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . printf ( severity . ErrorLog , logging . logger , logging . filter , format , args ... )
}
// ErrorfDepth acts as Errorf but uses depth to determine which call frame to log.
// ErrorfDepth(0, "msg", args...) is the same as Errorf("msg", args...).
func ErrorfDepth ( depth int , format string , args ... interface { } ) {
logging . printfDepth ( severity . ErrorLog , logging . logger , logging . filter , depth , format , args ... )
2022-07-07 20:11:50 +00:00
}
// ErrorS structured logs to the ERROR, WARNING, and INFO logs.
// the err argument used as "err" field of log line.
// The msg argument used to add constant description to the log line.
// The key/value pairs would be join by "=" ; a newline is always appended.
//
// Basic examples:
// >> klog.ErrorS(err, "Failed to update pod status")
// output:
// >> E1025 00:15:15.525108 1 controller_utils.go:114] "Failed to update pod status" err="timeout"
func ErrorS ( err error , msg string , keysAndValues ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . errorS ( err , logging . logger , logging . filter , 0 , msg , keysAndValues ... )
2022-07-07 20:11:50 +00:00
}
// ErrorSDepth acts as ErrorS but uses depth to determine which call frame to log.
// ErrorSDepth(0, "msg") is the same as ErrorS("msg").
func ErrorSDepth ( depth int , err error , msg string , keysAndValues ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . errorS ( err , logging . logger , logging . filter , depth , msg , keysAndValues ... )
2022-07-07 20:11:50 +00:00
}
// Fatal logs to the FATAL, ERROR, WARNING, and INFO logs,
2024-02-24 00:34:55 +00:00
// prints stack trace(s), then calls OsExit(255).
//
// Stderr only receives a dump of the current goroutine's stack trace. Log files,
// if there are any, receive a dump of the stack traces in all goroutines.
//
// Callers who want more control over handling of fatal events may instead use a
// combination of different functions:
// - some info or error logging function, optionally with a stack trace
// value generated by github.com/go-logr/lib/dbg.Backtrace
// - Flush to flush pending log data
// - panic, os.Exit or returning to the caller with an error
//
2022-07-07 20:11:50 +00:00
// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
func Fatal ( args ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . print ( severity . FatalLog , logging . logger , logging . filter , args ... )
2022-07-07 20:11:50 +00:00
}
// 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 ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . printDepth ( severity . FatalLog , logging . logger , logging . filter , depth , args ... )
2022-07-07 20:11:50 +00:00
}
// Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs,
2024-02-24 00:34:55 +00:00
// including a stack trace of all running goroutines, then calls OsExit(255).
2022-07-07 20:11:50 +00:00
// Arguments are handled in the manner of fmt.Println; a newline is always appended.
func Fatalln ( args ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . println ( severity . FatalLog , logging . logger , logging . filter , args ... )
}
// FatallnDepth acts as Fatalln but uses depth to determine which call frame to log.
// FatallnDepth(0, "msg") is the same as Fatalln("msg").
func FatallnDepth ( depth int , args ... interface { } ) {
logging . printlnDepth ( severity . FatalLog , logging . logger , logging . filter , depth , args ... )
2022-07-07 20:11:50 +00:00
}
// Fatalf logs to the FATAL, ERROR, WARNING, and INFO logs,
2024-02-24 00:34:55 +00:00
// including a stack trace of all running goroutines, then calls OsExit(255).
2022-07-07 20:11:50 +00:00
// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
func Fatalf ( format string , args ... interface { } ) {
2024-02-24 00:34:55 +00:00
logging . printf ( severity . FatalLog , logging . logger , logging . filter , format , args ... )
}
// FatalfDepth acts as Fatalf but uses depth to determine which call frame to log.
// FatalfDepth(0, "msg", args...) is the same as Fatalf("msg", args...).
func FatalfDepth ( depth int , format string , args ... interface { } ) {
logging . printfDepth ( severity . FatalLog , logging . logger , logging . filter , depth , format , args ... )
2022-07-07 20:11:50 +00:00
}
// fatalNoStacks is non-zero if we are to exit without dumping goroutine stacks.
// It allows Exit and relatives to use the Fatal logs.
var fatalNoStacks uint32
2024-02-24 00:34:55 +00:00
// Exit logs to the FATAL, ERROR, WARNING, and INFO logs, then calls OsExit(1).
2022-07-07 20:11:50 +00:00
// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
func Exit ( args ... interface { } ) {
atomic . StoreUint32 ( & fatalNoStacks , 1 )
2024-02-24 00:34:55 +00:00
logging . print ( severity . FatalLog , logging . logger , logging . filter , args ... )
2022-07-07 20:11:50 +00:00
}
// 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 ... interface { } ) {
atomic . StoreUint32 ( & fatalNoStacks , 1 )
2024-02-24 00:34:55 +00:00
logging . printDepth ( severity . FatalLog , logging . logger , logging . filter , depth , args ... )
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
// Exitln logs to the FATAL, ERROR, WARNING, and INFO logs, then calls OsExit(1).
2022-07-07 20:11:50 +00:00
func Exitln ( args ... interface { } ) {
atomic . StoreUint32 ( & fatalNoStacks , 1 )
2024-02-24 00:34:55 +00:00
logging . println ( severity . FatalLog , logging . logger , logging . filter , args ... )
}
// ExitlnDepth acts as Exitln but uses depth to determine which call frame to log.
// ExitlnDepth(0, "msg") is the same as Exitln("msg").
func ExitlnDepth ( depth int , args ... interface { } ) {
atomic . StoreUint32 ( & fatalNoStacks , 1 )
logging . printlnDepth ( severity . FatalLog , logging . logger , logging . filter , depth , args ... )
2022-07-07 20:11:50 +00:00
}
2024-02-24 00:34:55 +00:00
// Exitf logs to the FATAL, ERROR, WARNING, and INFO logs, then calls OsExit(1).
2022-07-07 20:11:50 +00:00
// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
func Exitf ( format string , args ... interface { } ) {
atomic . StoreUint32 ( & fatalNoStacks , 1 )
2024-02-24 00:34:55 +00:00
logging . printf ( severity . FatalLog , logging . logger , logging . filter , format , args ... )
}
// ExitfDepth acts as Exitf but uses depth to determine which call frame to log.
// ExitfDepth(0, "msg", args...) is the same as Exitf("msg", args...).
func ExitfDepth ( depth int , format string , args ... interface { } ) {
atomic . StoreUint32 ( & fatalNoStacks , 1 )
logging . printfDepth ( severity . FatalLog , logging . logger , logging . filter , depth , format , args ... )
2022-07-07 20:11:50 +00:00
}
// LogFilter is a collection of functions that can filter all logging calls,
// e.g. for sanitization of arguments and prevent accidental leaking of secrets.
type LogFilter interface {
Filter ( args [ ] interface { } ) [ ] interface { }
FilterF ( format string , args [ ] interface { } ) ( string , [ ] interface { } )
FilterS ( msg string , keysAndValues [ ] interface { } ) ( string , [ ] interface { } )
}
2024-02-24 00:34:55 +00:00
// SetLogFilter installs a filter that is used for all log calls.
//
// Modifying the filter is not thread-safe and should be done while no other
// goroutines invoke log calls, usually during program initialization.
2022-07-07 20:11:50 +00:00
func SetLogFilter ( filter LogFilter ) {
logging . filter = filter
}