github.com/klaytn/klaytn@v1.12.1/api/debug/flags.go (about)

     1  // Modifications Copyright 2018 The klaytn Authors
     2  // Copyright 2016 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from internal/debug/flags.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package debug
    22  
    23  import (
    24  	"fmt"
    25  	"io"
    26  	_ "net/http/pprof"
    27  	"os"
    28  	"path/filepath"
    29  	"runtime"
    30  
    31  	"github.com/fjl/memsize/memsizeui"
    32  	"github.com/klaytn/klaytn/log"
    33  	"github.com/mattn/go-colorable"
    34  	"github.com/mattn/go-isatty"
    35  	"github.com/urfave/cli/v2"
    36  	"github.com/urfave/cli/v2/altsrc"
    37  	"gopkg.in/natefinch/lumberjack.v2"
    38  )
    39  
    40  var Memsize memsizeui.Handler
    41  
    42  var (
    43  	verbosityFlag = &cli.IntFlag{
    44  		Name:     "verbosity",
    45  		Usage:    "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail",
    46  		Value:    3,
    47  		Aliases:  []string{"debug-profile.verbosity"},
    48  		EnvVars:  []string{"KLAYTN_VERBOSITY"},
    49  		Category: "LOGGING AND DEBUGGING",
    50  	}
    51  	vmoduleFlag = &cli.StringFlag{
    52  		Name:     "vmodule",
    53  		Usage:    "Per-module verbosity: comma-separated list of <pattern>=<level> (e.g. klay/*=5,p2p=4)",
    54  		Value:    "",
    55  		Aliases:  []string{"debug-profile.vmodule"},
    56  		EnvVars:  []string{"KLAYTN_VMODULE"},
    57  		Category: "LOGGING AND DEBUGGING",
    58  	}
    59  	backtraceAtFlag = &cli.StringFlag{
    60  		Name:     "backtrace",
    61  		Usage:    "Request a stack trace at a specific logging statement (e.g. \"block.go:271\")",
    62  		Value:    "",
    63  		Aliases:  []string{"debug-profile.backtrace"},
    64  		EnvVars:  []string{"KLAYTN_BACKTRACE"},
    65  		Category: "LOGGING AND DEBUGGING",
    66  	}
    67  	debugFlag = &cli.BoolFlag{
    68  		Name:     "debug",
    69  		Usage:    "Prepends log messages with call-site location (file and line number)",
    70  		Aliases:  []string{"debug-profile.print-site"},
    71  		EnvVars:  []string{"KLAYTN_DEBUG"},
    72  		Category: "LOGGING AND DEBUGGING",
    73  	}
    74  	logFormatFlag = &cli.StringFlag{
    75  		Name:     "log.format",
    76  		Usage:    "Log format to use (json|logfmt|terminal)",
    77  		Value:    "terminal",
    78  		Aliases:  []string{},
    79  		EnvVars:  []string{"KLAYTN_LOGFORMAT"},
    80  		Category: "LOGGING AND DEBUGGING",
    81  	}
    82  	logFileFlag = &cli.StringFlag{
    83  		Name:     "log.file",
    84  		Usage:    "Write logs to a file",
    85  		Aliases:  []string{},
    86  		EnvVars:  []string{"KLAYTN_LOGFILE"},
    87  		Category: "LOGGING AND DEBUGGING",
    88  	}
    89  	logRotateFlag = &cli.BoolFlag{
    90  		Name:     "log.rotate",
    91  		Usage:    "Enables log file rotation",
    92  		Aliases:  []string{},
    93  		EnvVars:  []string{"KLAYTN_LOGROTATE"},
    94  		Category: "LOGGING AND DEBUGGING",
    95  	}
    96  	logMaxSizeMBsFlag = &cli.IntFlag{
    97  		Name:     "log.maxsize",
    98  		Usage:    "Maximum size in MBs of a single log file (use with --log.rotate flag)",
    99  		Value:    100,
   100  		Aliases:  []string{},
   101  		EnvVars:  []string{"KLAYTN_LOGMAXSIZE"},
   102  		Category: "LOGGING AND DEBUGGING",
   103  	}
   104  	logMaxBackupsFlag = &cli.IntFlag{
   105  		Name:     "log.maxbackups",
   106  		Usage:    "Maximum number of log files to retain (use with --log.rotate flag)",
   107  		Value:    10,
   108  		Aliases:  []string{},
   109  		EnvVars:  []string{"KLAYTN_LOGMAXBACKUPS"},
   110  		Category: "LOGGING AND DEBUGGING",
   111  	}
   112  	logMaxAgeFlag = &cli.IntFlag{
   113  		Name:     "log.maxage",
   114  		Usage:    "Maximum number of days to retain a log file (use with --log.rotate flag)",
   115  		Value:    30,
   116  		Aliases:  []string{},
   117  		EnvVars:  []string{"KLAYTN_LOGMAXAGE"},
   118  		Category: "LOGGING AND DEBUGGING",
   119  	}
   120  	logCompressFlag = &cli.BoolFlag{
   121  		Name:     "log.compress",
   122  		Usage:    "Compress the log files (use with --log.rotate flag)",
   123  		Aliases:  []string{},
   124  		EnvVars:  []string{"KLAYTN_LOGCOMPRESS"},
   125  		Category: "LOGGING AND DEBUGGING",
   126  	}
   127  	pprofFlag = &cli.BoolFlag{
   128  		Name:     "pprof",
   129  		Usage:    "Enable the pprof HTTP server",
   130  		Aliases:  []string{"debug-profile.pprof.enable"},
   131  		EnvVars:  []string{"KLAYTN_PPROF"},
   132  		Category: "LOGGING AND DEBUGGING",
   133  	}
   134  	pprofPortFlag = &cli.IntFlag{
   135  		Name:     "pprofport",
   136  		Usage:    "pprof HTTP server listening port",
   137  		Value:    6060,
   138  		Aliases:  []string{"debug-profile.pprof.port"},
   139  		EnvVars:  []string{"KLAYTN_PPROFPORT"},
   140  		Category: "LOGGING AND DEBUGGING",
   141  	}
   142  	pprofAddrFlag = &cli.StringFlag{
   143  		Name:     "pprofaddr",
   144  		Usage:    "pprof HTTP server listening interface",
   145  		Value:    "127.0.0.1",
   146  		Aliases:  []string{"debug-profile.pprof.addr"},
   147  		EnvVars:  []string{"KLAYTN_PPROFADDR"},
   148  		Category: "LOGGING AND DEBUGGING",
   149  	}
   150  	memprofileFlag = &cli.StringFlag{
   151  		Name:     "memprofile",
   152  		Usage:    "Write memory profile to the given file",
   153  		Aliases:  []string{"debug-profile.mem-profile.file-name"},
   154  		EnvVars:  []string{"KLAYTN_MEMPROFILE"},
   155  		Category: "LOGGING AND DEBUGGING",
   156  	}
   157  	memprofilerateFlag = &cli.IntFlag{
   158  		Name:     "memprofilerate",
   159  		Usage:    "Turn on memory profiling with the given rate",
   160  		Value:    runtime.MemProfileRate,
   161  		Aliases:  []string{"debug-profile.mem-profile.rate"},
   162  		EnvVars:  []string{"KLAYTN_MEMPROFILERATE"},
   163  		Category: "LOGGING AND DEBUGGING",
   164  	}
   165  	blockprofilerateFlag = &cli.IntFlag{
   166  		Name:     "blockprofilerate",
   167  		Usage:    "Turn on block profiling with the given rate",
   168  		Aliases:  []string{"debug-profile.block-profile.rate"},
   169  		EnvVars:  []string{"KLAYTN_BLOCKPROFILERATE"},
   170  		Category: "LOGGING AND DEBUGGING",
   171  	}
   172  	cpuprofileFlag = &cli.StringFlag{
   173  		Name:     "cpuprofile",
   174  		Usage:    "Write CPU profile to the given file",
   175  		Aliases:  []string{"debug-profile.cpu-profile.file-name"},
   176  		EnvVars:  []string{"KLAYTN_CPUPROFILE"},
   177  		Category: "LOGGING AND DEBUGGING",
   178  	}
   179  	traceFlag = &cli.StringFlag{
   180  		Name:     "trace",
   181  		Usage:    "Write execution trace to the given file",
   182  		Aliases:  []string{"debug-profile.trace.file-name"},
   183  		EnvVars:  []string{"KLAYTN_TRACE"},
   184  		Category: "LOGGING AND DEBUGGING",
   185  	}
   186  )
   187  
   188  // Flags holds all command-line flags required for debugging.
   189  var Flags = []cli.Flag{
   190  	altsrc.NewIntFlag(verbosityFlag),
   191  	altsrc.NewStringFlag(vmoduleFlag),
   192  	altsrc.NewStringFlag(backtraceAtFlag),
   193  	altsrc.NewBoolFlag(debugFlag),
   194  	altsrc.NewStringFlag(logFormatFlag),
   195  	altsrc.NewStringFlag(logFileFlag),
   196  	altsrc.NewBoolFlag(logRotateFlag),
   197  	altsrc.NewIntFlag(logMaxSizeMBsFlag),
   198  	altsrc.NewIntFlag(logMaxBackupsFlag),
   199  	altsrc.NewIntFlag(logMaxAgeFlag),
   200  	altsrc.NewBoolFlag(logCompressFlag),
   201  	altsrc.NewBoolFlag(pprofFlag),
   202  	altsrc.NewStringFlag(pprofAddrFlag),
   203  	altsrc.NewIntFlag(pprofPortFlag),
   204  	altsrc.NewStringFlag(memprofileFlag),
   205  	altsrc.NewIntFlag(memprofilerateFlag),
   206  	altsrc.NewIntFlag(blockprofilerateFlag),
   207  	altsrc.NewStringFlag(cpuprofileFlag),
   208  	altsrc.NewStringFlag(traceFlag),
   209  }
   210  
   211  var glogger *log.GlogHandler
   212  
   213  func init() {
   214  	glogger = log.NewGlogHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false)))
   215  	glogger.Verbosity(log.LvlInfo)
   216  	log.Root().SetHandler(glogger)
   217  }
   218  
   219  func GetGlogger() (*log.GlogHandler, error) {
   220  	if glogger != nil {
   221  		return glogger, nil
   222  	}
   223  	return nil, fmt.Errorf("glogger is nil")
   224  }
   225  
   226  // CreateLogDir creates a directory whose path is logdir as well as empty log files.
   227  func CreateLogDir(logDir string) {
   228  	if logDir == "" {
   229  		return
   230  	}
   231  	Handler.logDir = logDir
   232  
   233  	// Currently failures on directory or file creation is treated as a warning.
   234  	if err := os.MkdirAll(logDir, 0o700); err != nil {
   235  		logger.Warn("Failed to create a directory", "logDir", logDir, "err", err)
   236  	}
   237  }
   238  
   239  // Setup initializes profiling and logging based on the CLI flags.
   240  // It should be called as early as possible in the program.
   241  func Setup(ctx *cli.Context) error {
   242  	var (
   243  		output     = io.Writer(os.Stderr)
   244  		logFmtFlag = ctx.String(logFormatFlag.Name)
   245  		logfmt     log.Format
   246  	)
   247  
   248  	switch logFmtFlag {
   249  	case "json":
   250  		logfmt = log.JsonFormat()
   251  	case "logfmt":
   252  		logfmt = log.LogfmtFormat()
   253  	case "terminal":
   254  		useColor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb"
   255  		if useColor {
   256  			output = colorable.NewColorableStdout()
   257  		}
   258  		logfmt = log.TerminalFormat(useColor)
   259  	default:
   260  		// Unknown log format specified
   261  		return fmt.Errorf("unknown log format: %v", ctx.String(logFormatFlag.Name))
   262  	}
   263  
   264  	var (
   265  		ostream  = log.StreamHandler(output, logfmt)
   266  		logFile  = ctx.String(logFileFlag.Name)
   267  		rotation = ctx.Bool(logRotateFlag.Name)
   268  		context  = []interface{}{"rotate", rotation}
   269  	)
   270  
   271  	// if logFile is not set when rotation, set default log name
   272  	if rotation && len(logFile) == 0 {
   273  		logFile = filepath.Join(os.TempDir(), "klaytn-lumberjack.log")
   274  	}
   275  	if len(logFile) > 0 {
   276  		context = append(context, "format", logFmtFlag, "location", logFile)
   277  		if err := validateLogLocation(logFile); err != nil {
   278  			return fmt.Errorf("tried to create a temporary file to verify that the log path is writable, but it failed: %v", err)
   279  		}
   280  		if rotation {
   281  			ostream = log.StreamHandler(&lumberjack.Logger{
   282  				Filename:   logFile,
   283  				MaxSize:    ctx.Int(logMaxSizeMBsFlag.Name),
   284  				MaxBackups: ctx.Int(logMaxBackupsFlag.Name),
   285  				MaxAge:     ctx.Int(logMaxAgeFlag.Name),
   286  				Compress:   ctx.Bool(logCompressFlag.Name),
   287  			}, logfmt)
   288  		} else {
   289  			logOutputStream, err := log.FileHandler(logFile, logfmt)
   290  			if err != nil {
   291  				return err
   292  			}
   293  			ostream = logOutputStream
   294  		}
   295  	}
   296  	glogger = log.NewGlogHandler(ostream)
   297  
   298  	// logging
   299  	log.PrintOrigins(ctx.Bool(debugFlag.Name))
   300  	if err := log.ChangeGlobalLogLevel(glogger, log.Lvl(ctx.Int(verbosityFlag.Name))); err != nil {
   301  		return err
   302  	}
   303  	if err := glogger.Vmodule(ctx.String(vmoduleFlag.Name)); err != nil {
   304  		return err
   305  	}
   306  	if len(ctx.String(backtraceAtFlag.Name)) != 0 {
   307  		if err := glogger.BacktraceAt(ctx.String(backtraceAtFlag.Name)); err != nil {
   308  			return err
   309  		}
   310  	}
   311  	log.Root().SetHandler(glogger)
   312  
   313  	// profiling, tracing
   314  	runtime.MemProfileRate = ctx.Int(memprofilerateFlag.Name)
   315  	Handler.SetBlockProfileRate(ctx.Int(blockprofilerateFlag.Name))
   316  	if traceFile := ctx.String(traceFlag.Name); traceFile != "" {
   317  		if err := Handler.StartGoTrace(traceFile); err != nil {
   318  			return err
   319  		}
   320  	}
   321  	if cpuFile := ctx.String(cpuprofileFlag.Name); cpuFile != "" {
   322  		if err := Handler.StartCPUProfile(cpuFile); err != nil {
   323  			return err
   324  		}
   325  	}
   326  	Handler.memFile = ctx.String(memprofileFlag.Name)
   327  
   328  	// pprof server
   329  	if ctx.Bool(pprofFlag.Name) {
   330  		addr := ctx.String(pprofAddrFlag.Name)
   331  		port := ctx.Int(pprofPortFlag.Name)
   332  		Handler.StartPProf(&addr, &port)
   333  	}
   334  	if len(logFile) > 0 {
   335  		logger.Info("Logging configured", context...)
   336  	}
   337  	return nil
   338  }
   339  
   340  // Exit stops all running profiles, flushing their output to the
   341  // respective file.
   342  func Exit() {
   343  	if Handler.vmLogFile != nil {
   344  		Handler.vmLogFile.Close()
   345  		Handler.vmLogFile = nil
   346  	}
   347  	if Handler.memFile != "" {
   348  		Handler.WriteMemProfile(Handler.memFile)
   349  	}
   350  	Handler.StopCPUProfile()
   351  	Handler.StopGoTrace()
   352  	Handler.StopPProf()
   353  }
   354  
   355  func validateLogLocation(path string) error {
   356  	if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
   357  		return fmt.Errorf("error creating the directory: %w", err)
   358  	}
   359  	// Check if the path is writable by trying to create a temporary file
   360  	tmp := path + ".temp"
   361  	if f, err := os.Create(tmp); err != nil {
   362  		return err
   363  	} else {
   364  		f.Close()
   365  	}
   366  	return os.Remove(tmp)
   367  }