package main import ( "context" "fmt" "net/http" "os" "os/signal" "strings" "time" "git.t-juice.club/torjus/gpaste" "git.t-juice.club/torjus/gpaste/api" "github.com/urfave/cli/v2" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var ( version = "dev" commit = "none" date = "unknown" ) func main() { cli.VersionFlag = &cli.BoolFlag{Name: "version"} app := cli.App{ Name: "gpaste-server", Version: fmt.Sprintf("gpaste-server %s-%s (%s)", version, commit, date), Flags: []cli.Flag{ &cli.StringFlag{ Name: "config", Usage: "Path to config-file.", }, }, Action: ActionServe, } app.Run(os.Args) } func ActionServe(c *cli.Context) error { configPath := "gpaste-server.toml" if c.IsSet("config") { configPath = c.String("config") } f, err := os.Open(configPath) if err != nil { return cli.Exit(err, 1) } defer f.Close() cfg, err := gpaste.ServerConfigFromReader(f) if err != nil { return cli.Exit(err, 1) } // Setup loggers rootLogger := getRootLogger(cfg.LogLevel) serverLogger := rootLogger.Named("SERV") accessLogger := rootLogger.Named("ACCS") // Setup contexts for clean shutdown rootCtx, rootCancel := signal.NotifyContext(context.Background(), os.Interrupt) defer rootCancel() httpCtx, httpCancel := context.WithCancel(rootCtx) defer httpCancel() httpShutdownCtx, httpShutdownCancel := context.WithCancel(context.Background()) defer httpShutdownCancel() go func() { srv := api.NewHTTPServer(cfg) srv.Addr = cfg.ListenAddr srv.Logger = serverLogger srv.AccessLogger = accessLogger // Wait for cancel go func() { <-httpCtx.Done() timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() srv.Shutdown(timeoutCtx) }() serverLogger.Infow("Starting HTTP server.", "addr", cfg.ListenAddr) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { serverLogger.Errorw("Error during shutdown.", "error", err) } serverLogger.Infow("HTTP server shutdown complete.", "addr", cfg.ListenAddr) httpShutdownCancel() }() <-httpShutdownCtx.Done() return nil } func getRootLogger(level string) *zap.SugaredLogger { logEncoderConfig := zap.NewProductionEncoderConfig() logEncoderConfig.EncodeCaller = zapcore.ShortCallerEncoder logEncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder logEncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder logEncoderConfig.EncodeDuration = zapcore.StringDurationEncoder rootLoggerConfig := &zap.Config{ Level: zap.NewAtomicLevelAt(zap.DebugLevel), OutputPaths: []string{"stdout"}, ErrorOutputPaths: []string{"stdout"}, Encoding: "console", EncoderConfig: logEncoderConfig, DisableCaller: true, } switch strings.ToUpper(level) { case "DEBUG": rootLoggerConfig.DisableCaller = false rootLoggerConfig.Level = zap.NewAtomicLevelAt(zap.DebugLevel) case "INFO": rootLoggerConfig.Level = zap.NewAtomicLevelAt(zap.InfoLevel) case "WARN", "WARNING": rootLoggerConfig.Level = zap.NewAtomicLevelAt(zap.WarnLevel) case "ERR", "ERROR": rootLoggerConfig.Level = zap.NewAtomicLevelAt(zap.ErrorLevel) } rootLogger, err := rootLoggerConfig.Build() if err != nil { panic(err) } return rootLogger.Sugar() }