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