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