cmd/geth: enable log rotation (#26843)

This change enables log rotation, which can be activated using the flag --log.rotate. Additional parameters that can be given are: 

  - log.maxsize to set maximum size before files are rotated,
  - log.maxbackups to set how many files are retailed, 
  - log.maxage to configure max age of rotated files, 
  - log.compress whether to compress rotated files

The way to configure location of the logfile(s) is left unchanged, via the `log.logfile` parameter.  

---------

Co-authored-by: Martin Holst Swende <martin@swende.se>
This commit is contained in:
sudeep
2023-04-03 05:05:36 -04:00
committed by GitHub
co-authored by Martin Holst Swende
parent 2c5798464e
commit 7076ae00aa
4 changed files with 117 additions and 33 deletions
+112 -33
View File
@@ -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)
defer log.Warn("The flag '--log.json' is deprecated, please use '--log.format=json' instead")
logfmt = log.JSONFormat()
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)
}
} else {
output := io.Writer(os.Stderr)
if useColor {
output = colorable.NewColorableStderr()
}
logOutputStream = log.StreamHandler(output, logfmt)
}
glogger.SetHandler(logOutputStream)
context := []interface{}{"rotate", rotation}
if len(logFmtFlag) > 0 {
context = append(context, "format", logFmtFlag)
} else {
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)
}
}
glogger.SetHandler(ostream)
// 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)
}