@ -20,8 +20,9 @@ import (
"fmt"
"io"
"net/http"
_ "net/http/pprof" // nolint: gosec
_ "net/http/pprof"
"os"
"path/filepath"
"runtime"
"github.com/ethereum/go-ethereum/internal/flags"
@ -32,6 +33,7 @@ import (
"github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
"github.com/urfave/cli/v2"
"gopkg.in/natefinch/lumberjack.v2"
)
var Memsize memsizeui . Handler
@ -76,6 +78,34 @@ var (
Usage : "Prepends log messages with call-site location (file and line number)" ,
Category : flags . LoggingCategory ,
}
logRotateFlag = & cli . BoolFlag {
Name : "log.rotate" ,
Usage : "Enables log file rotation" ,
}
logMaxSizeMBsFlag = & cli . IntFlag {
Name : "log.maxsize" ,
Usage : "Maximum size in MBs of a single log file" ,
Value : 100 ,
Category : flags . LoggingCategory ,
}
logMaxBackupsFlag = & cli . IntFlag {
Name : "log.maxbackups" ,
Usage : "Maximum number of log files to retain" ,
Value : 10 ,
Category : flags . LoggingCategory ,
}
logMaxAgeFlag = & cli . IntFlag {
Name : "log.maxage" ,
Usage : "Maximum number of days to retain a log file" ,
Value : 30 ,
Category : flags . LoggingCategory ,
}
logCompressFlag = & cli . BoolFlag {
Name : "log.compress" ,
Usage : "Compress the log files" ,
Value : false ,
Category : flags . LoggingCategory ,
}
pprofFlag = & cli . BoolFlag {
Name : "pprof" ,
Usage : "Enable the pprof HTTP server" ,
@ -120,11 +150,16 @@ var (
var Flags = [ ] cli . Flag {
verbosityFlag ,
vmoduleFlag ,
backtraceAtFlag ,
debugFlag ,
logjsonFlag ,
logFormatFlag ,
logFileFlag ,
backtraceAtFlag ,
debugFlag ,
logRotateFlag ,
logMaxSizeMBsFlag ,
logMaxBackupsFlag ,
logMaxAgeFlag ,
logCompressFlag ,
pprofFlag ,
pprofAddrFlag ,
pprofPortFlag ,
@ -148,44 +183,71 @@ func init() {
// Setup initializes profiling and logging based on the CLI flags.
// It should be called as early as possible in the program.
func Setup ( ctx * cli . Context ) error {
logFile := ctx . String ( logFileFlag . Name )
useColor := logFile == "" && os . Getenv ( "TERM" ) != "dumb" && ( isatty . IsTerminal ( os . Stderr . Fd ( ) ) || isatty . IsCygwinTerminal ( os . Stderr . Fd ( ) ) )
var logfmt log . Format
switch ctx . String ( logFormatFlag . Name ) {
case "json" :
logfmt = log . JSONFormat ( )
case "logfmt" :
logfmt = log . LogfmtFormat ( )
case "terminal" :
logfmt = log . TerminalFormat ( useColor )
case "" :
var (
logfmt log . Format
output = io . Writer ( os . Stderr )
logFmtFlag = ctx . String ( logFormatFlag . Name )
)
switch {
case ctx . Bool ( logjsonFlag . Name ) :
// Retain backwards compatibility with `--log.json` flag if `--log.format` not set
if ctx . Bool ( logjsonFlag . Name ) {
defer log . Warn ( "The flag '--log.json' is deprecated, please use '--log.format=json' instead" )
logfmt = log . JSONFormat ( )
} else {
logfmt = log . TerminalFormat ( useColor )
case logFmtFlag == "json" :
logfmt = log . JSONFormat ( )
case logFmtFlag == "logfmt" :
logfmt = log . LogfmtFormat ( )
case logFmtFlag == "" , logFmtFlag == "terminal" :
useColor := ( isatty . IsTerminal ( os . Stderr . Fd ( ) ) || isatty . IsCygwinTerminal ( os . Stderr . Fd ( ) ) ) && os . Getenv ( "TERM" ) != "dumb"
if useColor {
output = colorable . NewColorableStderr ( )
}
logfmt = log . TerminalFormat ( useColor )
default :
// Unknown log format specified
return fmt . Errorf ( "unknown log format: %v" , ctx . String ( logFormatFlag . Name ) )
}
if logFile != "" {
var err error
logOutputStream , err = log . FileHandler ( logFile , logfmt )
if err != nil {
return err
var (
stdHandler = log . StreamHandler ( output , logfmt )
ostream = stdHandler
logFile = ctx . String ( logFileFlag . Name )
rotation = ctx . Bool ( logRotateFlag . Name )
)
if len ( logFile ) > 0 {
if err := validateLogLocation ( filepath . Dir ( logFile ) ) ; err != nil {
return fmt . Errorf ( "failed to initiatilize file logger: %v" , err )
}
}
context := [ ] interface { } { "rotate" , rotation }
if len ( logFmtFlag ) > 0 {
context = append ( context , "format" , logFmtFlag )
} else {
output := io . Writer ( os . Stderr )
if useColor {
output = colorable . NewColorableStderr ( )
context = append ( context , "format" , "terminal" )
}
if rotation {
// Lumberjack uses <processname>-lumberjack.log in is.TempDir() if empty.
// so typically /tmp/geth-lumberjack.log on linux
if len ( logFile ) > 0 {
context = append ( context , "location" , logFile )
} else {
context = append ( context , "location" , filepath . Join ( os . TempDir ( ) , "geth-lumberjack.log" ) )
}
ostream = log . MultiHandler ( log . StreamHandler ( & lumberjack . Logger {
Filename : logFile ,
MaxSize : ctx . Int ( logMaxSizeMBsFlag . Name ) ,
MaxBackups : ctx . Int ( logMaxBackupsFlag . Name ) ,
MaxAge : ctx . Int ( logMaxAgeFlag . Name ) ,
Compress : ctx . Bool ( logCompressFlag . Name ) ,
} , logfmt ) , stdHandler )
} else if logFile != "" {
if logOutputStream , err := log . FileHandler ( logFile , logfmt ) ; err != nil {
return err
} else {
ostream = log . MultiHandler ( logOutputStream , stdHandler )
context = append ( context , "location" , logFile )
}
logOutputStream = log . StreamHandler ( output , logfmt )
}
glogger . SetHandler ( logOutputStream )
glogger . SetHandler ( os tream)
// logging
verbosity := ctx . Int ( verbosityFlag . Name )
@ -236,6 +298,9 @@ func Setup(ctx *cli.Context) error {
// It cannot be imported because it will cause a cyclical dependency.
StartPProf ( address , ! ctx . IsSet ( "metrics.addr" ) )
}
if len ( logFile ) > 0 || rotation {
log . Info ( "Logging configured" , context ... )
}
return nil
}
@ -263,3 +328,17 @@ func Exit() {
closer . Close ( )
}
}
func validateLogLocation ( path string ) error {
if err := os . MkdirAll ( path , os . ModePerm ) ; err != nil {
return fmt . Errorf ( "error creating the directory: %w" , err )
}
// Check if the path is writable by trying to create a temporary file
tmp := filepath . Join ( path , "tmp" )
if f , err := os . Create ( tmp ) ; err != nil {
return err
} else {
f . Close ( )
}
return os . Remove ( tmp )
}