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