github.com/ethereum/go-ethereum@v1.16.1/internal/debug/flags.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package debug 18 19 import ( 20 "fmt" 21 "io" 22 "log/slog" 23 "net" 24 "net/http" 25 _ "net/http/pprof" 26 "os" 27 "path/filepath" 28 "runtime" 29 30 "github.com/ethereum/go-ethereum/internal/flags" 31 "github.com/ethereum/go-ethereum/log" 32 "github.com/ethereum/go-ethereum/metrics" 33 "github.com/ethereum/go-ethereum/metrics/exp" 34 "github.com/mattn/go-colorable" 35 "github.com/mattn/go-isatty" 36 "github.com/urfave/cli/v2" 37 "gopkg.in/natefinch/lumberjack.v2" 38 ) 39 40 var ( 41 verbosityFlag = &cli.IntFlag{ 42 Name: "verbosity", 43 Usage: "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail", 44 Value: 3, 45 Category: flags.LoggingCategory, 46 } 47 logVmoduleFlag = &cli.StringFlag{ 48 Name: "log.vmodule", 49 Usage: "Per-module verbosity: comma-separated list of <pattern>=<level> (e.g. eth/*=5,p2p=4)", 50 Value: "", 51 Category: flags.LoggingCategory, 52 } 53 vmoduleFlag = &cli.StringFlag{ 54 Name: "vmodule", 55 Usage: "Per-module verbosity: comma-separated list of <pattern>=<level> (e.g. eth/*=5,p2p=4)", 56 Value: "", 57 Hidden: true, 58 Category: flags.LoggingCategory, 59 } 60 logjsonFlag = &cli.BoolFlag{ 61 Name: "log.json", 62 Usage: "Format logs with JSON", 63 Hidden: true, 64 Category: flags.LoggingCategory, 65 } 66 logFormatFlag = &cli.StringFlag{ 67 Name: "log.format", 68 Usage: "Log format to use (json|logfmt|terminal)", 69 Category: flags.LoggingCategory, 70 } 71 logFileFlag = &cli.StringFlag{ 72 Name: "log.file", 73 Usage: "Write logs to a file", 74 Category: flags.LoggingCategory, 75 } 76 logRotateFlag = &cli.BoolFlag{ 77 Name: "log.rotate", 78 Usage: "Enables log file rotation", 79 Category: flags.LoggingCategory, 80 } 81 logMaxSizeMBsFlag = &cli.IntFlag{ 82 Name: "log.maxsize", 83 Usage: "Maximum size in MBs of a single log file", 84 Value: 100, 85 Category: flags.LoggingCategory, 86 } 87 logMaxBackupsFlag = &cli.IntFlag{ 88 Name: "log.maxbackups", 89 Usage: "Maximum number of log files to retain", 90 Value: 10, 91 Category: flags.LoggingCategory, 92 } 93 logMaxAgeFlag = &cli.IntFlag{ 94 Name: "log.maxage", 95 Usage: "Maximum number of days to retain a log file", 96 Value: 30, 97 Category: flags.LoggingCategory, 98 } 99 logCompressFlag = &cli.BoolFlag{ 100 Name: "log.compress", 101 Usage: "Compress the log files", 102 Value: false, 103 Category: flags.LoggingCategory, 104 } 105 pprofFlag = &cli.BoolFlag{ 106 Name: "pprof", 107 Usage: "Enable the pprof HTTP server", 108 Category: flags.LoggingCategory, 109 } 110 pprofPortFlag = &cli.IntFlag{ 111 Name: "pprof.port", 112 Usage: "pprof HTTP server listening port", 113 Value: 6060, 114 Category: flags.LoggingCategory, 115 } 116 pprofAddrFlag = &cli.StringFlag{ 117 Name: "pprof.addr", 118 Usage: "pprof HTTP server listening interface", 119 Value: "127.0.0.1", 120 Category: flags.LoggingCategory, 121 } 122 memprofilerateFlag = &cli.IntFlag{ 123 Name: "pprof.memprofilerate", 124 Usage: "Turn on memory profiling with the given rate", 125 Value: runtime.MemProfileRate, 126 Category: flags.LoggingCategory, 127 } 128 blockprofilerateFlag = &cli.IntFlag{ 129 Name: "pprof.blockprofilerate", 130 Usage: "Turn on block profiling with the given rate", 131 Category: flags.LoggingCategory, 132 } 133 cpuprofileFlag = &cli.StringFlag{ 134 Name: "pprof.cpuprofile", 135 Usage: "Write CPU profile to the given file", 136 Category: flags.LoggingCategory, 137 } 138 traceFlag = &cli.StringFlag{ 139 Name: "go-execution-trace", 140 Usage: "Write Go execution trace to the given file", 141 Category: flags.LoggingCategory, 142 } 143 ) 144 145 // Flags holds all command-line flags required for debugging. 146 var Flags = []cli.Flag{ 147 verbosityFlag, 148 logVmoduleFlag, 149 vmoduleFlag, 150 logjsonFlag, 151 logFormatFlag, 152 logFileFlag, 153 logRotateFlag, 154 logMaxSizeMBsFlag, 155 logMaxBackupsFlag, 156 logMaxAgeFlag, 157 logCompressFlag, 158 pprofFlag, 159 pprofAddrFlag, 160 pprofPortFlag, 161 memprofilerateFlag, 162 blockprofilerateFlag, 163 cpuprofileFlag, 164 traceFlag, 165 } 166 167 var ( 168 glogger *log.GlogHandler 169 logOutputFile io.WriteCloser 170 ) 171 172 func init() { 173 glogger = log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false)) 174 } 175 176 // Setup initializes profiling and logging based on the CLI flags. 177 // It should be called as early as possible in the program. 178 func Setup(ctx *cli.Context) error { 179 var ( 180 handler slog.Handler 181 terminalOutput = io.Writer(os.Stderr) 182 output io.Writer 183 logFmtFlag = ctx.String(logFormatFlag.Name) 184 ) 185 var ( 186 logFile = ctx.String(logFileFlag.Name) 187 rotation = ctx.Bool(logRotateFlag.Name) 188 ) 189 if len(logFile) > 0 { 190 if err := validateLogLocation(filepath.Dir(logFile)); err != nil { 191 return fmt.Errorf("failed to initiatilize file logger: %v", err) 192 } 193 } 194 context := []interface{}{"rotate", rotation} 195 if len(logFmtFlag) > 0 { 196 context = append(context, "format", logFmtFlag) 197 } else { 198 context = append(context, "format", "terminal") 199 } 200 if rotation { 201 // Lumberjack uses <processname>-lumberjack.log in is.TempDir() if empty. 202 // so typically /tmp/geth-lumberjack.log on linux 203 if len(logFile) > 0 { 204 context = append(context, "location", logFile) 205 } else { 206 context = append(context, "location", filepath.Join(os.TempDir(), "geth-lumberjack.log")) 207 } 208 logOutputFile = &lumberjack.Logger{ 209 Filename: logFile, 210 MaxSize: ctx.Int(logMaxSizeMBsFlag.Name), 211 MaxBackups: ctx.Int(logMaxBackupsFlag.Name), 212 MaxAge: ctx.Int(logMaxAgeFlag.Name), 213 Compress: ctx.Bool(logCompressFlag.Name), 214 } 215 output = io.MultiWriter(terminalOutput, logOutputFile) 216 } else if logFile != "" { 217 var err error 218 if logOutputFile, err = os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644); err != nil { 219 return err 220 } 221 output = io.MultiWriter(logOutputFile, terminalOutput) 222 context = append(context, "location", logFile) 223 } else { 224 output = terminalOutput 225 } 226 227 switch { 228 case ctx.Bool(logjsonFlag.Name): 229 // Retain backwards compatibility with `--log.json` flag if `--log.format` not set 230 defer log.Warn("The flag '--log.json' is deprecated, please use '--log.format=json' instead") 231 handler = log.JSONHandler(output) 232 case logFmtFlag == "json": 233 handler = log.JSONHandler(output) 234 case logFmtFlag == "logfmt": 235 handler = log.LogfmtHandler(output) 236 case logFmtFlag == "", logFmtFlag == "terminal": 237 useColor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb" 238 if useColor { 239 terminalOutput = colorable.NewColorableStderr() 240 if logOutputFile != nil { 241 output = io.MultiWriter(logOutputFile, terminalOutput) 242 } else { 243 output = terminalOutput 244 } 245 } 246 handler = log.NewTerminalHandler(output, useColor) 247 default: 248 // Unknown log format specified 249 return fmt.Errorf("unknown log format: %v", ctx.String(logFormatFlag.Name)) 250 } 251 252 glogger = log.NewGlogHandler(handler) 253 254 // logging 255 verbosity := log.FromLegacyLevel(ctx.Int(verbosityFlag.Name)) 256 glogger.Verbosity(verbosity) 257 vmodule := ctx.String(logVmoduleFlag.Name) 258 if vmodule == "" { 259 // Retain backwards compatibility with `--vmodule` flag if `--log.vmodule` not set 260 vmodule = ctx.String(vmoduleFlag.Name) 261 if vmodule != "" { 262 defer log.Warn("The flag '--vmodule' is deprecated, please use '--log.vmodule' instead") 263 } 264 } 265 glogger.Vmodule(vmodule) 266 267 log.SetDefault(log.NewLogger(glogger)) 268 269 // profiling, tracing 270 runtime.MemProfileRate = memprofilerateFlag.Value 271 if ctx.IsSet(memprofilerateFlag.Name) { 272 runtime.MemProfileRate = ctx.Int(memprofilerateFlag.Name) 273 } 274 275 blockProfileRate := ctx.Int(blockprofilerateFlag.Name) 276 Handler.SetBlockProfileRate(blockProfileRate) 277 278 if traceFile := ctx.String(traceFlag.Name); traceFile != "" { 279 if err := Handler.StartGoTrace(traceFile); err != nil { 280 return err 281 } 282 } 283 284 if cpuFile := ctx.String(cpuprofileFlag.Name); cpuFile != "" { 285 if err := Handler.StartCPUProfile(cpuFile); err != nil { 286 return err 287 } 288 } 289 290 // pprof server 291 if ctx.Bool(pprofFlag.Name) { 292 listenHost := ctx.String(pprofAddrFlag.Name) 293 294 port := ctx.Int(pprofPortFlag.Name) 295 296 address := net.JoinHostPort(listenHost, fmt.Sprintf("%d", port)) 297 // This context value ("metrics.addr") represents the utils.MetricsHTTPFlag.Name. 298 // It cannot be imported because it will cause a cyclical dependency. 299 StartPProf(address, !ctx.IsSet("metrics.addr")) 300 } 301 if len(logFile) > 0 || rotation { 302 log.Info("Logging configured", context...) 303 } 304 return nil 305 } 306 307 func StartPProf(address string, withMetrics bool) { 308 // Hook go-metrics into expvar on any /debug/metrics request, load all vars 309 // from the registry into expvar, and execute regular expvar handler. 310 if withMetrics { 311 exp.Exp(metrics.DefaultRegistry) 312 } 313 log.Info("Starting pprof server", "addr", fmt.Sprintf("http://%s/debug/pprof", address)) 314 go func() { 315 if err := http.ListenAndServe(address, nil); err != nil { 316 log.Error("Failure in running pprof server", "err", err) 317 } 318 }() 319 } 320 321 // Exit stops all running profiles, flushing their output to the 322 // respective file. 323 func Exit() { 324 Handler.StopCPUProfile() 325 Handler.StopGoTrace() 326 if logOutputFile != nil { 327 logOutputFile.Close() 328 } 329 } 330 331 func validateLogLocation(path string) error { 332 if err := os.MkdirAll(path, os.ModePerm); err != nil { 333 return fmt.Errorf("error creating the directory: %w", err) 334 } 335 // Check if the path is writable by trying to create a temporary file 336 tmp := filepath.Join(path, "tmp") 337 if f, err := os.Create(tmp); err != nil { 338 return err 339 } else { 340 f.Close() 341 } 342 return os.Remove(tmp) 343 }