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