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