@@ -20,8 +20,9 @@ import (
2020 "fmt"
2121 "io"
2222 "net/http"
23- _ "net/http/pprof" // nolint: gosec
23+ _ "net/http/pprof"
2424 "os"
25+ "path/filepath"
2526 "runtime"
2627
2728 "github.com/ethereum/go-ethereum/internal/flags"
@@ -32,6 +33,7 @@ import (
3233 "github.com/mattn/go-colorable"
3334 "github.com/mattn/go-isatty"
3435 "github.com/urfave/cli/v2"
36+ "gopkg.in/natefinch/lumberjack.v2"
3537)
3638
3739var Memsize memsizeui.Handler
7678 Usage : "Prepends log messages with call-site location (file and line number)" ,
7779 Category : flags .LoggingCategory ,
7880 }
81+ logRotateFlag = & cli.BoolFlag {
82+ Name : "log.rotate" ,
83+ Usage : "Enables log file rotation" ,
84+ }
85+ logMaxSizeMBsFlag = & cli.IntFlag {
86+ Name : "log.maxsize" ,
87+ Usage : "Maximum size in MBs of a single log file" ,
88+ Value : 100 ,
89+ Category : flags .LoggingCategory ,
90+ }
91+ logMaxBackupsFlag = & cli.IntFlag {
92+ Name : "log.maxbackups" ,
93+ Usage : "Maximum number of log files to retain" ,
94+ Value : 10 ,
95+ Category : flags .LoggingCategory ,
96+ }
97+ logMaxAgeFlag = & cli.IntFlag {
98+ Name : "log.maxage" ,
99+ Usage : "Maximum number of days to retain a log file" ,
100+ Value : 30 ,
101+ Category : flags .LoggingCategory ,
102+ }
103+ logCompressFlag = & cli.BoolFlag {
104+ Name : "log.compress" ,
105+ Usage : "Compress the log files" ,
106+ Value : false ,
107+ Category : flags .LoggingCategory ,
108+ }
79109 pprofFlag = & cli.BoolFlag {
80110 Name : "pprof" ,
81111 Usage : "Enable the pprof HTTP server" ,
@@ -120,11 +150,16 @@ var (
120150var Flags = []cli.Flag {
121151 verbosityFlag ,
122152 vmoduleFlag ,
153+ backtraceAtFlag ,
154+ debugFlag ,
123155 logjsonFlag ,
124156 logFormatFlag ,
125157 logFileFlag ,
126- backtraceAtFlag ,
127- debugFlag ,
158+ logRotateFlag ,
159+ logMaxSizeMBsFlag ,
160+ logMaxBackupsFlag ,
161+ logMaxAgeFlag ,
162+ logCompressFlag ,
128163 pprofFlag ,
129164 pprofAddrFlag ,
130165 pprofPortFlag ,
@@ -148,44 +183,71 @@ func init() {
148183// Setup initializes profiling and logging based on the CLI flags.
149184// It should be called as early as possible in the program.
150185func Setup (ctx * cli.Context ) error {
151- logFile := ctx .String (logFileFlag .Name )
152- useColor := logFile == "" && os .Getenv ("TERM" ) != "dumb" && (isatty .IsTerminal (os .Stderr .Fd ()) || isatty .IsCygwinTerminal (os .Stderr .Fd ()))
153-
154- var logfmt log.Format
155- switch ctx .String (logFormatFlag .Name ) {
156- case "json" :
186+ var (
187+ logfmt log.Format
188+ output = io .Writer (os .Stderr )
189+ logFmtFlag = ctx .String (logFormatFlag .Name )
190+ )
191+ switch {
192+ case ctx .Bool (logjsonFlag .Name ):
193+ // Retain backwards compatibility with `--log.json` flag if `--log.format` not set
194+ defer log .Warn ("The flag '--log.json' is deprecated, please use '--log.format=json' instead" )
157195 logfmt = log .JSONFormat ()
158- case "logfmt" :
196+ case logFmtFlag == "json" :
197+ logfmt = log .JSONFormat ()
198+ case logFmtFlag == "logfmt" :
159199 logfmt = log .LogfmtFormat ()
160- case "terminal" :
161- logfmt = log .TerminalFormat (useColor )
162- case "" :
163- // Retain backwards compatibility with `--log.json` flag if `--log.format` not set
164- if ctx .Bool (logjsonFlag .Name ) {
165- defer log .Warn ("The flag '--log.json' is deprecated, please use '--log.format=json' instead" )
166- logfmt = log .JSONFormat ()
167- } else {
168- logfmt = log .TerminalFormat (useColor )
200+ case logFmtFlag == "" , logFmtFlag == "terminal" :
201+ useColor := (isatty .IsTerminal (os .Stderr .Fd ()) || isatty .IsCygwinTerminal (os .Stderr .Fd ())) && os .Getenv ("TERM" ) != "dumb"
202+ if useColor {
203+ output = colorable .NewColorableStderr ()
169204 }
205+ logfmt = log .TerminalFormat (useColor )
170206 default :
171207 // Unknown log format specified
172208 return fmt .Errorf ("unknown log format: %v" , ctx .String (logFormatFlag .Name ))
173209 }
174-
175- if logFile != "" {
176- var err error
177- logOutputStream , err = log .FileHandler (logFile , logfmt )
178- if err != nil {
179- return err
210+ var (
211+ stdHandler = log .StreamHandler (output , logfmt )
212+ ostream = stdHandler
213+ logFile = ctx .String (logFileFlag .Name )
214+ rotation = ctx .Bool (logRotateFlag .Name )
215+ )
216+ if len (logFile ) > 0 {
217+ if err := validateLogLocation (filepath .Dir (logFile )); err != nil {
218+ return fmt .Errorf ("failed to initiatilize file logger: %v" , err )
180219 }
220+ }
221+ context := []interface {}{"rotate" , rotation }
222+ if len (logFmtFlag ) > 0 {
223+ context = append (context , "format" , logFmtFlag )
181224 } else {
182- output := io .Writer (os .Stderr )
183- if useColor {
184- output = colorable .NewColorableStderr ()
225+ context = append (context , "format" , "terminal" )
226+ }
227+ if rotation {
228+ // Lumberjack uses <processname>-lumberjack.log in is.TempDir() if empty.
229+ // so typically /tmp/geth-lumberjack.log on linux
230+ if len (logFile ) > 0 {
231+ context = append (context , "location" , logFile )
232+ } else {
233+ context = append (context , "location" , filepath .Join (os .TempDir (), "geth-lumberjack.log" ))
234+ }
235+ ostream = log .MultiHandler (log .StreamHandler (& lumberjack.Logger {
236+ Filename : logFile ,
237+ MaxSize : ctx .Int (logMaxSizeMBsFlag .Name ),
238+ MaxBackups : ctx .Int (logMaxBackupsFlag .Name ),
239+ MaxAge : ctx .Int (logMaxAgeFlag .Name ),
240+ Compress : ctx .Bool (logCompressFlag .Name ),
241+ }, logfmt ), stdHandler )
242+ } else if logFile != "" {
243+ if logOutputStream , err := log .FileHandler (logFile , logfmt ); err != nil {
244+ return err
245+ } else {
246+ ostream = log .MultiHandler (logOutputStream , stdHandler )
247+ context = append (context , "location" , logFile )
185248 }
186- logOutputStream = log .StreamHandler (output , logfmt )
187249 }
188- glogger .SetHandler (logOutputStream )
250+ glogger .SetHandler (ostream )
189251
190252 // logging
191253 verbosity := ctx .Int (verbosityFlag .Name )
@@ -236,6 +298,9 @@ func Setup(ctx *cli.Context) error {
236298 // It cannot be imported because it will cause a cyclical dependency.
237299 StartPProf (address , ! ctx .IsSet ("metrics.addr" ))
238300 }
301+ if len (logFile ) > 0 || rotation {
302+ log .Info ("Logging configured" , context ... )
303+ }
239304 return nil
240305}
241306
@@ -263,3 +328,17 @@ func Exit() {
263328 closer .Close ()
264329 }
265330}
331+
332+ func validateLogLocation (path string ) error {
333+ if err := os .MkdirAll (path , os .ModePerm ); err != nil {
334+ return fmt .Errorf ("error creating the directory: %w" , err )
335+ }
336+ // Check if the path is writable by trying to create a temporary file
337+ tmp := filepath .Join (path , "tmp" )
338+ if f , err := os .Create (tmp ); err != nil {
339+ return err
340+ } else {
341+ f .Close ()
342+ }
343+ return os .Remove (tmp )
344+ }
0 commit comments