diff --git a/cmd/texd/main.go b/cmd/texd/main.go index 5f87e3e..e388cf9 100644 --- a/cmd/texd/main.go +++ b/cmd/texd/main.go @@ -2,6 +2,8 @@ package main import ( "context" + "errors" + "flag" "fmt" "os" "os/signal" @@ -16,7 +18,7 @@ import ( "github.com/digineo/texd/service" "github.com/digineo/texd/tex" "github.com/docker/go-units" - flag "github.com/spf13/pflag" + "github.com/spf13/pflag" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) @@ -32,52 +34,60 @@ var opts = service.Options{ } var ( - engine = tex.DefaultEngine.Name() - jobdir = "" - pull = false - logLevel = zapcore.InfoLevel.String() - maxJobSize = units.BytesSize(float64(opts.MaxJobSize)) + engine = tex.DefaultEngine.Name() + jobdir = "" + pull = false + logLevel = zapcore.InfoLevel.String() + maxJobSize = units.BytesSize(float64(opts.MaxJobSize)) + showVersion = false ) -var log = zap.L() - -func main() { //nolint:funlen - texd.PrintBanner(os.Stdout) +func parseFlags() { + fs := pflag.NewFlagSet(os.Args[0], pflag.ContinueOnError) + fs.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + fs.PrintDefaults() + } - flag.StringVarP(&opts.Addr, "listen-address", "b", opts.Addr, + fs.StringVarP(&opts.Addr, "listen-address", "b", opts.Addr, "bind `address` for the HTTP API") - flag.StringVarP(&engine, "tex-engine", "X", engine, + fs.StringVarP(&engine, "tex-engine", "X", engine, fmt.Sprintf("`name` of default TeX engine, acceptable values are: %v", tex.SupportedEngines())) - flag.DurationVarP(&opts.CompileTimeout, "compile-timeout", "t", opts.CompileTimeout, + fs.DurationVarP(&opts.CompileTimeout, "compile-timeout", "t", opts.CompileTimeout, "maximum rendering time") - flag.IntVarP(&opts.QueueLength, "parallel-jobs", "P", opts.QueueLength, + fs.IntVarP(&opts.QueueLength, "parallel-jobs", "P", opts.QueueLength, "maximum `number` of parallel rendereing jobs") - flag.StringVar(&maxJobSize, "max-job-size", maxJobSize, + fs.StringVar(&maxJobSize, "max-job-size", maxJobSize, "maximum size of job, a value <= 0 disables check") - flag.DurationVarP(&opts.QueueTimeout, "queue-wait", "w", opts.QueueTimeout, + fs.DurationVarP(&opts.QueueTimeout, "queue-wait", "w", opts.QueueTimeout, "maximum wait time in full rendering queue") - flag.StringVarP(&jobdir, "job-directory", "D", jobdir, + fs.StringVarP(&jobdir, "job-directory", "D", jobdir, "`path` to base directory to place temporary jobs into (path must exist and it must be writable; defaults to the OS's temp directory)") - flag.BoolVar(&pull, "pull", pull, "always pull Docker images") - flag.StringVar(&logLevel, "log-level", logLevel, + fs.BoolVar(&pull, "pull", pull, "always pull Docker images") + fs.StringVar(&logLevel, "log-level", logLevel, "set logging verbosity, acceptable values are: [debug, info, warn, error, dpanic, panic, fatal]") - versionRequested := flag.BoolP("version", "v", false, `print version information and exit`) - flag.Parse() + fs.BoolVarP(&showVersion, "version", "v", showVersion, + `print version information and exit`) - if *versionRequested { - printVersion() + switch err := fs.Parse(os.Args[1:]); { + case errors.Is(err, pflag.ErrHelp): + // pflag already has called fs.Usage os.Exit(0) + case err != nil: + fmt.Fprintf(os.Stderr, "Error parsing flags:\n\t%v\n", err) + os.Exit(2) } +} - if lvl, err := zapcore.ParseLevel(logLevel); err != nil { - zap.L().Fatal("error parsing log level", - zap.String("flag", "--log-level"), - zap.Error(err)) - } else if log, err = newLogger(lvl); err != nil { - zap.L().Fatal("error constructing logger", - zap.Error(err)) - } else { - defer func() { _ = log.Sync() }() +func main() { + texd.PrintBanner(os.Stdout) + parseFlags() + log, sync := setupLogger() + defer sync() + + if showVersion { + printVersion() + os.Exit(0) } if err := tex.SetJobBaseDir(jobdir); err != nil { @@ -115,14 +125,14 @@ func main() { //nolint:funlen } stop := service.Start(opts, log) - onExit(stop) + onExit(log, stop) } const exitTimeout = 10 * time.Second type stopFun func(context.Context) error -func onExit(stopper ...stopFun) { +func onExit(log *zap.Logger, stopper ...stopFun) { exitCh := make(chan os.Signal, 2) signal.Notify(exitCh, syscall.SIGINT, syscall.SIGTERM) sig := <-exitCh @@ -182,7 +192,7 @@ func printVersion() { } } -func newLogger(level zapcore.Level) (*zap.Logger, error) { +func setupLogger() (*zap.Logger, func()) { var cfg zap.Config if texd.Development() { cfg = zap.NewDevelopmentConfig() @@ -190,6 +200,26 @@ func newLogger(level zapcore.Level) (*zap.Logger, error) { cfg = zap.NewProductionConfig() } - cfg.Level = zap.NewAtomicLevelAt(level) - return cfg.Build() + lvl, lvlErr := zapcore.ParseLevel(logLevel) + if lvlErr == nil { + cfg.Level = zap.NewAtomicLevelAt(lvl) + } + + log, err := cfg.Build() + if err != nil { + // we don't have a logger yet, so logging the error + // proves to be complicatet :) + panic(err) + } + + if lvlErr != nil { + log.Error("error parsing log level", + zap.String("flag", "--log-level"), + zap.Error(lvlErr)) + } + + zap.ReplaceGlobals(log) + return log, func() { + _ = log.Sync() + } } diff --git a/go.mod b/go.mod index 5511078..d3aeb14 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.18 require ( github.com/docker/docker v20.10.13+incompatible + github.com/docker/go-units v0.4.0 github.com/google/uuid v1.3.0 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 @@ -11,6 +12,7 @@ require ( github.com/spf13/afero v1.8.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.7.0 + go.uber.org/zap v1.21.0 ) require ( @@ -20,7 +22,6 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect - github.com/docker/go-units v0.4.0 // indirect github.com/felixge/httpsnoop v1.0.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect @@ -32,7 +33,6 @@ require ( github.com/sirupsen/logrus v1.8.1 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.8.0 // indirect - go.uber.org/zap v1.21.0 // indirect golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect golang.org/x/text v0.3.7 // indirect diff --git a/go.sum b/go.sum index 83835b0..e9b3efc 100644 --- a/go.sum +++ b/go.sum @@ -110,6 +110,7 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -876,6 +877,7 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= @@ -1369,6 +1371,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/service/middleware/requestid.go b/service/middleware/requestid.go index 719550f..19b1ced 100644 --- a/service/middleware/requestid.go +++ b/service/middleware/requestid.go @@ -2,9 +2,11 @@ package middleware import ( "context" + "crypto/rand" + "encoding/base64" + "fmt" "net/http" - "github.com/google/uuid" "go.uber.org/zap" ) @@ -14,9 +16,17 @@ type contextKey string const ContextKey = contextKey("request-id") +func generateRequestId() string { + b := make([]byte, 6) + if _, err := rand.Read(b); err != nil { + panic(fmt.Errorf("rand error: %w", err)) + } + return base64.RawURLEncoding.EncodeToString(b) +} + func RequestID(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - id := uuid.Must(uuid.NewRandom()).String() + id := generateRequestId() r = r.WithContext(context.WithValue(r.Context(), ContextKey, id)) w.Header().Set(HeaderKey, id) @@ -30,9 +40,8 @@ func GetRequestID(r *http.Request) (string, bool) { } func RequestIDField(ctx context.Context) zap.Field { - id, ok := ctx.Value(ContextKey).(string) - if !ok { - id = "-" + if id, ok := ctx.Value(ContextKey).(string); ok { + return zap.String("request-id", id) } - return zap.String("request-id", id) + return zap.Skip() }