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 }