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