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  }