github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/cmd/geth/log_context.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  	"time"
     8  	"unicode"
     9  
    10  	"gopkg.in/urfave/cli.v1"
    11  
    12  	"os"
    13  	"path/filepath"
    14  
    15  	"github.com/ethereumproject/go-ethereum/logger"
    16  	"github.com/ethereumproject/go-ethereum/logger/glog"
    17  	"github.com/ethereumproject/go-ethereum/p2p/discover"
    18  	"net"
    19  )
    20  
    21  const defaultStatusLog = "sync=60s"
    22  
    23  var isToFileLoggingEnabled = true
    24  
    25  // setupLogging sets default
    26  func setupLogging(ctx *cli.Context) error {
    27  	glog.CopyStandardLogTo("INFO")
    28  
    29  	// Turn on only file logging, disabling logging(T).toStderr and logging(T).alsoToStdErr
    30  	glog.SetToStderr(glog.DefaultToStdErr)
    31  	glog.SetAlsoToStderr(glog.DefaultAlsoToStdErr)
    32  
    33  	glog.SetV(glog.DefaultVerbosity)
    34  
    35  	// Set up file logging.
    36  	logDir := ""
    37  	isToFileLoggingEnabled = toFileLoggingEnabled(ctx)
    38  
    39  	// If '--log-dir' flag is in use, override the default.
    40  	if ctx.GlobalIsSet(aliasableName(LogDirFlag.Name, ctx)) {
    41  		ld := ctx.GlobalString(aliasableName(LogDirFlag.Name, ctx))
    42  		if ld == "" {
    43  			return fmt.Errorf("--%s cannot be empty", LogDirFlag.Name)
    44  		}
    45  		if isToFileLoggingEnabled {
    46  			ld = expandPath(ld)
    47  			ldAbs, err := filepath.Abs(ld)
    48  			if err != nil {
    49  				return err
    50  			}
    51  			logDir = ldAbs
    52  		} else {
    53  			glog.SetD(0)
    54  			glog.SetToStderr(true)
    55  		}
    56  	} else {
    57  		logDir = filepath.Join(MustMakeChainDataDir(ctx), glog.DefaultLogDirName)
    58  	}
    59  
    60  	// Allow to-file logging to be disabled
    61  	if logDir != "" {
    62  		// Ensure log dir exists; mkdir -p <logdir>
    63  		if e := os.MkdirAll(logDir, os.ModePerm); e != nil {
    64  			return e
    65  		}
    66  
    67  		// Before glog.SetLogDir is called, logs are saved to system-default temporary directory.
    68  		// If logging is started before this call, the new logDir will be used after file rotation
    69  		// (by default after 1800MB of data per file).
    70  		glog.SetLogDir(logDir)
    71  	}
    72  
    73  	// Handle --neckbeard config overrides if set.
    74  	if ctx.GlobalBool(NeckbeardFlag.Name) {
    75  		glog.SetD(0)
    76  		glog.SetV(5)
    77  		glog.SetAlsoToStderr(true)
    78  	}
    79  
    80  	// Handle display level configuration.
    81  	if ctx.GlobalIsSet(DisplayFlag.Name) {
    82  		i := ctx.GlobalInt(DisplayFlag.Name)
    83  		if i > 5 {
    84  			return fmt.Errorf("--%s level must be 0 <= i <= 5, got: %d", DisplayFlag.Name, i)
    85  		}
    86  		glog.SetD(i)
    87  	}
    88  
    89  	// Manual context configs
    90  	// Global V verbosity
    91  	if ctx.GlobalIsSet(VerbosityFlag.Name) {
    92  		nint := ctx.GlobalInt(VerbosityFlag.Name)
    93  		if nint <= logger.Detail || nint == logger.Ridiculousness {
    94  			glog.SetV(nint)
    95  		}
    96  	}
    97  
    98  	// Global Vmodule
    99  	if ctx.GlobalIsSet(VModuleFlag.Name) {
   100  		v := ctx.GlobalString(VModuleFlag.Name)
   101  		glog.GetVModule().Set(v)
   102  	}
   103  
   104  	// If --log-status not set, set default 60s interval
   105  	if !ctx.GlobalIsSet(LogStatusFlag.Name) {
   106  		ctx.Set(LogStatusFlag.Name, defaultStatusLog)
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  func setupLogRotation(ctx *cli.Context) error {
   113  	var err error
   114  	glog.MaxSize, err = getSizeFlagValue(&LogMaxSizeFlag, ctx)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	glog.MinSize, err = getSizeFlagValue(&LogMinSizeFlag, ctx)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	glog.MaxTotalSize, err = getSizeFlagValue(&LogMaxTotalSizeFlag, ctx)
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	glog.Compress = ctx.GlobalBool(aliasableName(LogCompressFlag.Name, ctx))
   130  
   131  	interval, err := glog.ParseInterval(ctx.GlobalString(aliasableName(LogIntervalFlag.Name, ctx)))
   132  	if err != nil {
   133  		return fmt.Errorf("invalid log rotation interval: %v", err)
   134  	}
   135  	glog.RotationInterval = interval
   136  
   137  	maxAge, err := parseDuration(ctx.GlobalString(aliasableName(LogMaxAgeFlag.Name, ctx)))
   138  	if err != nil {
   139  		return fmt.Errorf("error parsing log max age: %v", err)
   140  	}
   141  	glog.MaxAge = maxAge
   142  
   143  	return nil
   144  }
   145  
   146  func getSizeFlagValue(flag *cli.StringFlag, ctx *cli.Context) (uint64, error) {
   147  	strVal := ctx.GlobalString(aliasableName(flag.Name, ctx))
   148  	size, err := parseSize(strVal)
   149  	if err != nil {
   150  		return 0, fmt.Errorf("%s: invalid value '%s': %v", flag.Name, strVal, err)
   151  	}
   152  	return size, nil
   153  }
   154  
   155  func parseDuration(str string) (time.Duration, error) {
   156  	mapping := map[rune]uint64{
   157  		0:   uint64(time.Second), // no-suffix means value in seconds
   158  		's': uint64(time.Second),
   159  		'm': uint64(time.Minute),
   160  		'h': uint64(time.Hour),
   161  		'd': uint64(24 * time.Hour),
   162  		'w': uint64(7 * 24 * time.Hour),
   163  	}
   164  	value, err := parseWithSuffix(str, mapping)
   165  	if err != nil {
   166  		return 0, err
   167  	}
   168  	return time.Duration(value), nil
   169  }
   170  
   171  // reinventing the wheel to avoid external dependency
   172  func parseSize(str string) (uint64, error) {
   173  	mapping := map[rune]uint64{
   174  		0:   1, // no-suffix means multiply by 1
   175  		'k': 1024,
   176  		'm': 1024 * 1024,
   177  		'g': 1024 * 1024 * 1024,
   178  	}
   179  	return parseWithSuffix(str, mapping)
   180  }
   181  
   182  func parseWithSuffix(str string, mapping map[rune]uint64) (uint64, error) {
   183  	number := strings.ToLower(strings.TrimLeftFunc(str, unicode.IsSpace))
   184  
   185  	trim := ""
   186  	for k := range mapping {
   187  		if k != 0 {
   188  			trim += string(k)
   189  		}
   190  	}
   191  	suffix := rune(0)
   192  	number = strings.TrimRightFunc(number, func(r rune) bool {
   193  		if unicode.IsSpace(r) {
   194  			return true
   195  		}
   196  		if unicode.IsDigit(r) {
   197  			return false
   198  		}
   199  		if suffix == 0 {
   200  			suffix = r
   201  			return true
   202  		}
   203  		return false
   204  	})
   205  
   206  	if suffix != 0 && !strings.ContainsRune(trim, suffix) {
   207  		return 0, fmt.Errorf("invalid suffix '%v', expected one of %v", string(suffix), strings.Split(trim, ""))
   208  	}
   209  
   210  	value, err := strconv.ParseUint(number, 10, 64)
   211  
   212  	if err != nil {
   213  		return 0, fmt.Errorf("invalid value '%v': natural number expected", number)
   214  	}
   215  
   216  	return value * mapping[suffix], nil
   217  }
   218  
   219  func toFileLoggingEnabled(ctx *cli.Context) bool {
   220  	if ctx.GlobalIsSet(aliasableName(LogDirFlag.Name, ctx)) {
   221  		ld := ctx.GlobalString(aliasableName(LogDirFlag.Name, ctx))
   222  		if ld == "off" || ld == "disable" || ld == "disabled" {
   223  			return false
   224  		}
   225  	}
   226  	return true
   227  }
   228  
   229  func mustMakeMLogDir(ctx *cli.Context) string {
   230  	if ctx.GlobalIsSet(MLogDirFlag.Name) {
   231  		p := ctx.GlobalString(MLogDirFlag.Name)
   232  		if p == "" {
   233  			glog.Fatalf("Flag %v requires a non-empty argument", MLogDirFlag.Name)
   234  			return ""
   235  		}
   236  		if filepath.IsAbs(p) {
   237  			return p
   238  		}
   239  		ap, e := filepath.Abs(p)
   240  		if e != nil {
   241  			glog.Fatalf("could not establish absolute path for mlog dir: %v", e)
   242  		}
   243  		return ap
   244  	}
   245  
   246  	return filepath.Join(MustMakeChainDataDir(ctx), "mlogs")
   247  }
   248  
   249  func makeMLogFileLogger(ctx *cli.Context) (string, error) {
   250  	now := time.Now()
   251  
   252  	mlogdir := mustMakeMLogDir(ctx)
   253  	logger.SetMLogDir(mlogdir)
   254  
   255  	_, filename, err := logger.CreateMLogFile(now)
   256  	if err != nil {
   257  		return "", err
   258  	}
   259  	// withTs toggles custom timestamp ISO8601 prefix
   260  	// logger print without timestamp header prefix if json
   261  	withTs := true
   262  	if f := ctx.GlobalString(MLogFlag.Name); logger.MLogStringToFormat[f] == logger.MLOGJSON {
   263  		withTs = false
   264  	}
   265  	logger.BuildNewMLogSystem(mlogdir, filename, 1, 0, withTs) // flags: 0 disables automatic log package time prefix
   266  	return filename, nil
   267  }
   268  
   269  func mustRegisterMLogsFromContext(ctx *cli.Context) {
   270  	if e := logger.MLogRegisterComponentsFromContext(ctx.GlobalString(MLogComponentsFlag.Name)); e != nil {
   271  		// print documentation if user enters unavailable mlog component
   272  		var components []string
   273  		for k := range logger.GetMLogRegistryAvailable() {
   274  			components = append(components, string(k))
   275  		}
   276  		glog.V(logger.Error).Errorf("Error: %s", e)
   277  		glog.V(logger.Error).Errorf("Available machine log components: %v", components)
   278  		os.Exit(1)
   279  	}
   280  	// Set the global logger mlog format from context
   281  	if e := logger.SetMLogFormatFromString(ctx.GlobalString(MLogFlag.Name)); e != nil {
   282  		glog.Fatalf("Error setting mlog format: %v, value was: %v", e, ctx.GlobalString(MLogFlag.Name))
   283  	}
   284  	_, e := makeMLogFileLogger(ctx)
   285  	if e != nil {
   286  		glog.Fatalf("Failed to start machine log: %v", e)
   287  	}
   288  	logger.SetMlogEnabled(true)
   289  }
   290  
   291  func logLoggingConfiguration(ctx *cli.Context) {
   292  	v := glog.GetVerbosity().String()
   293  	logdir := "off"
   294  	if isToFileLoggingEnabled {
   295  		logdir = glog.GetLogDir()
   296  	}
   297  	vmodule := glog.GetVModule().String()
   298  	// An empty string looks unused, so show * instead, which is equivalent.
   299  	if vmodule == "" {
   300  		vmodule = "*"
   301  	}
   302  	d := glog.GetDisplayable().String()
   303  
   304  	statusFeats := []string{}
   305  	for k, v := range availableLogStatusFeatures {
   306  		if v.Seconds() == 0 {
   307  			statusFeats = append(statusFeats, fmt.Sprintf("%s=%s", k, "off"))
   308  			continue
   309  		}
   310  		statusFeats = append(statusFeats, fmt.Sprintf("%s=%v", k, v))
   311  	}
   312  	statusLine := strings.Join(statusFeats, ",")
   313  
   314  	glog.V(logger.Warn).Infoln("Debug log configuration", "v=", v, "logdir=", logdir, "vmodule=", vmodule)
   315  	glog.D(logger.Warn).Infof("Debug log config: verbosity=%s log-dir=%s vmodule=%s",
   316  		logger.ColorGreen(v),
   317  		logger.ColorGreen(logdir),
   318  		logger.ColorGreen(vmodule),
   319  	)
   320  
   321  	glog.V(logger.Warn).Infoln("Display log configuration", "d=", d, "status=", statusLine)
   322  	glog.D(logger.Warn).Infof("Display log config: display=%s status=%s",
   323  		logger.ColorGreen(d),
   324  		logger.ColorGreen(statusLine),
   325  	)
   326  
   327  	if logger.MlogEnabled() {
   328  		glog.V(logger.Warn).Infof("Machine log config: mlog=%s mlog-dir=%s", logger.GetMLogFormat().String(), logger.GetMLogDir())
   329  		glog.D(logger.Warn).Infof("Machine log config: mlog=%s mlog-dir=%s", logger.ColorGreen(logger.GetMLogFormat().String()), logger.ColorGreen(logger.GetMLogDir()))
   330  	} else {
   331  		glog.V(logger.Warn).Infof("Machine log config: mlog=%s mlog-dir=%s", logger.GetMLogFormat().String(), logger.GetMLogDir())
   332  		glog.D(logger.Warn).Infof("Machine log config: mlog=%s", logger.ColorYellow("off"))
   333  	}
   334  
   335  }
   336  
   337  func logIfUnsafeConfiguration(ctx *cli.Context) {
   338  	// If RPC APIs include ANY of eth,personal,admin AND an account is unlocked, that's unsafe because
   339  	// anyone can use the unlocked account to sign and send transactions over the RPC API.
   340  	//
   341  	rpcapis := ctx.GlobalString(aliasableName(RPCApiFlag.Name, ctx))
   342  	unsafeRPCAPIs := []string{"eth", "personal", "admin"}
   343  	safeRPCListenAddrsWhitelist := [][2]net.IP{
   344  		discover.Ipv4ReservedRangeThis,
   345  		discover.Ipv4ReservedRangeLoopback,
   346  		discover.Ipv4ReservedRangePrivateNetwork,
   347  		discover.Ipv4ReservedRangeLocalPrivate2,
   348  		discover.Ipv6ReservedRangeLoopback,
   349  	}
   350  	stringContainsAny := func(s string, anyof []string) bool {
   351  		for _, ss := range anyof {
   352  			if strings.Contains(s, ss) {
   353  				return true
   354  			}
   355  		}
   356  		return false
   357  	}
   358  
   359  	possibleUnlockCondition := ctx.GlobalIsSet(UnlockedAccountFlag.Name) || ctx.GlobalIsSet(PasswordFileFlag.Name)
   360  	rpcEnabledCondition := ctx.GlobalBool(RPCEnabledFlag.Name)
   361  	rpcAPICondition := stringContainsAny(rpcapis, unsafeRPCAPIs)
   362  
   363  	// rpc listen addr is considered "safe", which is to be probably not exposed to the internet
   364  	rpcListenAddrCondition := func(configuredRPCListenAddr string) bool {
   365  		// listening on all interfaces, UNSAFE
   366  		if configuredRPCListenAddr == "*" {
   367  			return true
   368  		}
   369  		if strings.Contains(configuredRPCListenAddr, "localhost") {
   370  			return false
   371  		}
   372  		ip := net.ParseIP(configuredRPCListenAddr)
   373  		for _, n := range safeRPCListenAddrsWhitelist {
   374  			if ok, err := discover.IpBetween(n[0], n[1], ip); ok && err == nil {
   375  				return false
   376  			}
   377  		}
   378  		// parsed listen ip was NOT in the whitelist
   379  		return true
   380  	}(ctx.GlobalString(aliasableName(RPCListenAddrFlag.Name, ctx)))
   381  
   382  	unsafeCondition := possibleUnlockCondition && rpcEnabledCondition && rpcAPICondition && rpcListenAddrCondition
   383  
   384  	// check for EITHER --password or --unlock to be on the safe side, along with any of the sensitive RPC APIs enabled
   385  	if unsafeCondition {
   386  		func(vs []func(...interface{})) {
   387  			for _, v := range vs {
   388  				v(glog.Separator("-"))
   389  				v("*")
   390  				v(fmt.Sprintf(`
   391  
   392  >    !!!  WARNING: Unsafe use of --%s and exposed RPC API [ currently: %s ].  !!!  
   393  > 
   394  >    It's UNSAFE to unlock an account while exposing ANY of the following RPC APIs: %s to the internet.
   395  >    Anyone from the internet will be able to transfer funds from an unlocked account without any password.
   396  > 
   397  >    You can use the --%s flag to enable specific RPC API modules if necessary, 
   398  >    and/or restrict the exposed RPC listen address with the --%s flag.
   399  
   400  `,
   401  					UnlockedAccountFlag.Name,
   402  					rpcapis,
   403  					unsafeRPCAPIs,
   404  					aliasableName(RPCApiFlag.Name, ctx),
   405  					aliasableName(RPCListenAddrFlag.Name, ctx),
   406  				))
   407  				v("*")
   408  				v(glog.Separator("-"))
   409  			}
   410  		}([]func(...interface{}){
   411  			glog.V(logger.Warn).Warnln,
   412  			glog.D(logger.Warn).Warnln,
   413  		})
   414  		if !askForConfirmation("Do you really wish to proceed?") {
   415  			os.Exit(0)
   416  		}
   417  	}
   418  
   419  }