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