Skip to content

Commit

Permalink
Merge pull request #115 from SiaFoundation/write-startup-errors-stderr
Browse files Browse the repository at this point in the history
Write startup errors to stderr
  • Loading branch information
n8maninger authored Oct 17, 2024
2 parents f2ab6cb + 562e0a0 commit c1648e0
Showing 1 changed file with 106 additions and 117 deletions.
223 changes: 106 additions & 117 deletions cmd/explored/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,12 @@ var cfg = config.Config{
},
}

// stdoutFatalError prints an error message to stdout and exits with a 1 exit code.
func stdoutFatalError(msg string) {
fmt.Println(msg)
// checkFatalError prints an error message to stderr and exits with a 1 exit code. If err is nil, this is a no-op.
func checkFatalError(context string, err error) {
if err == nil {
return
}
os.Stderr.WriteString(fmt.Sprintf("%s: %s\n", context, err))
os.Exit(1)
}

Expand All @@ -89,19 +92,13 @@ func tryLoadConfig() {
}

f, err := os.Open(configPath)
if err != nil {
stdoutFatalError("failed to open config file: " + err.Error())
return
}
checkFatalError("failed to open config file", err)
defer f.Close()

dec := yaml.NewDecoder(f)
dec.KnownFields(true)

if err := dec.Decode(&cfg); err != nil {
fmt.Println("failed to decode config file:", err)
os.Exit(1)
}
checkFatalError("failed to decode config file", dec.Decode(&cfg))
}

// jsonEncoder returns a zapcore.Encoder that encodes logs as JSON intended for
Expand Down Expand Up @@ -178,94 +175,7 @@ func forwardUPNP(ctx context.Context, addr string, log *zap.Logger) string {
return net.JoinHostPort(ip, portStr)
}

func main() {
tryLoadConfig()

flag.StringVar(&cfg.Directory, "dir", cfg.Directory, "directory to store node state in")
flag.StringVar(&cfg.HTTP.Address, "http", cfg.HTTP.Address, "address to serve API on")
flag.StringVar(&cfg.Consensus.Network, "network", cfg.Consensus.Network, "network to connect to")
flag.StringVar(&cfg.Syncer.Address, "addr", cfg.Syncer.Address, "p2p address to listen on")
flag.BoolVar(&cfg.Syncer.EnableUPNP, "upnp", cfg.Syncer.EnableUPNP, "attempt to forward ports and discover IP with UPnP")
flag.Parse()

if flag.Arg(0) == "version" {
fmt.Println("explored", build.Version())
fmt.Println("Commit:", build.Commit())
fmt.Println("Build Date:", build.Time())
return
}

if err := os.MkdirAll(cfg.Directory, 0700); err != nil {
stdoutFatalError("failed to open log file: " + err.Error())
return
}

var logCores []zapcore.Core
if cfg.Log.StdOut.Enabled {
// if no log level is set for stdout, use the global log level
if cfg.Log.StdOut.Level == "" {
cfg.Log.StdOut.Level = cfg.Log.Level
}

var encoder zapcore.Encoder
switch cfg.Log.StdOut.Format {
case "json":
encoder = jsonEncoder()
default: // stdout defaults to human
encoder = humanEncoder(cfg.Log.StdOut.EnableANSI)
}

// create the stdout logger
level := parseLogLevel(cfg.Log.StdOut.Level)
logCores = append(logCores, zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), level))
}

if cfg.Log.File.Enabled {
// if no log level is set for file, use the global log level
if cfg.Log.File.Level == "" {
cfg.Log.File.Level = cfg.Log.Level
}

// normalize log path
if cfg.Log.File.Path == "" {
cfg.Log.File.Path = filepath.Join(cfg.Directory, "explored.log")
}

// configure file logging
var encoder zapcore.Encoder
switch cfg.Log.File.Format {
case "human":
encoder = humanEncoder(false) // disable colors in file log
default: // log file defaults to JSON
encoder = jsonEncoder()
}

fileWriter, closeFn, err := zap.Open(cfg.Log.File.Path)
if err != nil {
stdoutFatalError("failed to open log file: " + err.Error())
return
}
defer closeFn()

// create the file logger
level := parseLogLevel(cfg.Log.File.Level)
logCores = append(logCores, zapcore.NewCore(encoder, zapcore.Lock(fileWriter), level))
}

var log *zap.Logger
if len(logCores) == 1 {
log = zap.New(logCores[0], zap.AddCaller())
} else {
log = zap.New(zapcore.NewTee(logCores...), zap.AddCaller())
}
defer log.Sync()

// redirect stdlib log to zap
zap.RedirectStdLog(log.Named("stdlib"))

ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

func runRootCmd(ctx context.Context, log *zap.Logger) error {
var network *consensus.Network
var genesisBlock types.Block

Expand All @@ -285,36 +195,31 @@ func main() {

bdb, err := coreutils.OpenBoltChainDB(filepath.Join(cfg.Directory, "consensus.db"))
if err != nil {
log.Error("failed to open bolt database", zap.Error(err))
return
return fmt.Errorf("failed to open bolt database: %w", err)
}
defer bdb.Close()

dbstore, tipState, err := chain.NewDBStore(bdb, network, genesisBlock)
if err != nil {
log.Error("failed to create chain store", zap.Error(err))
return
return fmt.Errorf("failed to create chain store: %w", err)
}
cm := chain.NewManager(dbstore, tipState)

store, err := sqlite.OpenDatabase(filepath.Join(cfg.Directory, "explored.sqlite3"), log.Named("sqlite3"))
if err != nil {
log.Error("failed to open sqlite database", zap.Error(err))
return
return fmt.Errorf("failed to open sqlite database: %w", err)
}
defer store.Close()

syncerListener, err := net.Listen("tcp", cfg.Syncer.Address)
if err != nil {
log.Error("failed to create listener", zap.Error(err))
return
return fmt.Errorf("failed to create listener: %w", err)
}
defer syncerListener.Close()

httpListener, err := net.Listen("tcp", cfg.HTTP.Address)
if err != nil {
log.Error("failed to create listener", zap.Error(err))
return
return fmt.Errorf("failed to create listener: %w", err)
}
defer httpListener.Close()

Expand All @@ -334,8 +239,7 @@ func main() {

ps, err := syncerutil.NewJSONPeerStore(filepath.Join(cfg.Directory, "peers.json"))
if err != nil {
log.Error("failed to open peer store", zap.Error(err))
return
return fmt.Errorf("failed to open peer store: %w", err)
}
for _, peer := range cfg.Syncer.Peers {
ps.AddPeer(peer)
Expand All @@ -352,8 +256,7 @@ func main() {

e, err := explorer.NewExplorer(cm, store, cfg.Index.BatchSize, cfg.Scanner, log.Named("explorer"))
if err != nil {
log.Error("failed to create explorer", zap.Error(err))
return
return fmt.Errorf("failed to create explorer: %w", err)
}
timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), 60*time.Second)
defer timeoutCancel()
Expand Down Expand Up @@ -383,8 +286,94 @@ func main() {

<-ctx.Done()
log.Info("shutting down")
go func() {
<-time.After(time.Minute)
log.Fatal("shutdown timed out")
}()
time.AfterFunc(3*time.Minute, func() {
log.Fatal("failed to shut down within 3 minutes")
})

return nil
}

func main() {
tryLoadConfig()

flag.StringVar(&cfg.Directory, "dir", cfg.Directory, "directory to store node state in")
flag.StringVar(&cfg.HTTP.Address, "http", cfg.HTTP.Address, "address to serve API on")
flag.StringVar(&cfg.Consensus.Network, "network", cfg.Consensus.Network, "network to connect to")
flag.StringVar(&cfg.Syncer.Address, "addr", cfg.Syncer.Address, "p2p address to listen on")
flag.BoolVar(&cfg.Syncer.EnableUPNP, "upnp", cfg.Syncer.EnableUPNP, "attempt to forward ports and discover IP with UPnP")
flag.Parse()

if flag.Arg(0) == "version" {
fmt.Println("explored", build.Version())
fmt.Println("Commit:", build.Commit())
fmt.Println("Build Date:", build.Time())
return
}

checkFatalError("failed to open log file", os.MkdirAll(cfg.Directory, 0700))

var logCores []zapcore.Core
if cfg.Log.StdOut.Enabled {
// if no log level is set for stdout, use the global log level
if cfg.Log.StdOut.Level == "" {
cfg.Log.StdOut.Level = cfg.Log.Level
}

var encoder zapcore.Encoder
switch cfg.Log.StdOut.Format {
case "json":
encoder = jsonEncoder()
default: // stdout defaults to human
encoder = humanEncoder(cfg.Log.StdOut.EnableANSI)
}

// create the stdout logger
level := parseLogLevel(cfg.Log.StdOut.Level)
logCores = append(logCores, zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), level))
}

if cfg.Log.File.Enabled {
// if no log level is set for file, use the global log level
if cfg.Log.File.Level == "" {
cfg.Log.File.Level = cfg.Log.Level
}

// normalize log path
if cfg.Log.File.Path == "" {
cfg.Log.File.Path = filepath.Join(cfg.Directory, "explored.log")
}

// configure file logging
var encoder zapcore.Encoder
switch cfg.Log.File.Format {
case "human":
encoder = humanEncoder(false) // disable colors in file log
default: // log file defaults to JSON
encoder = jsonEncoder()
}

fileWriter, closeFn, err := zap.Open(cfg.Log.File.Path)
checkFatalError("failed to open log file", err)
defer closeFn()

// create the file logger
level := parseLogLevel(cfg.Log.File.Level)
logCores = append(logCores, zapcore.NewCore(encoder, zapcore.Lock(fileWriter), level))
}

var log *zap.Logger
if len(logCores) == 1 {
log = zap.New(logCores[0], zap.AddCaller())
} else {
log = zap.New(zapcore.NewTee(logCores...), zap.AddCaller())
}
defer log.Sync()

// redirect stdlib log to zap
zap.RedirectStdLog(log.Named("stdlib"))

ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

checkFatalError("daemon startup failed", runRootCmd(ctx, log))
}

0 comments on commit c1648e0

Please sign in to comment.