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