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