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  }