From 10f7eb53f4370cab6c1ff390773772389d500e59 Mon Sep 17 00:00:00 2001 From: Gibheer Date: Thu, 17 Aug 2023 22:02:52 +0200 Subject: prepare switch to log/slog This commit prepares the switch from log to log/slog, which was introduced in Go 1.21. slog provides some useful facilities to add metadata to log entries, which should be helpful for debugging problems. This commit also adds a small transaction ID generator. It provides a common identifier between log messages, so that multiple errors can be viewed together in their order. --- cmd/monfront/main.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++- cmd/monfront/server.go | 45 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 2 deletions(-) (limited to 'cmd/monfront') diff --git a/cmd/monfront/main.go b/cmd/monfront/main.go index a318e75..19be6e0 100644 --- a/cmd/monfront/main.go +++ b/cmd/monfront/main.go @@ -6,8 +6,10 @@ import ( "flag" "fmt" "html/template" + "io" "io/ioutil" "log" + "log/slog" "net" "net/http" "os" @@ -49,6 +51,11 @@ type ( Mode string `toml:"mode"` List []string `toml:"list"` } + Log struct { + Format string `toml:"format"` + Level string `toml:"level"` + Output string `toml:"output"` + } } MapEntry struct { @@ -99,6 +106,8 @@ func main() { log.Fatalf("could not parse config: %s", err) } + logger := parseLogger(config) + db, err := sql.Open("postgres", config.DB) if err != nil { log.Fatalf("could not open database connection: %s", err) @@ -168,7 +177,7 @@ func main() { l = tls.NewListener(l, tlsConf) } - s := newServer(l, db, tmpl, auth, autho) + s := newServer(l, db, logger, tmpl, auth, autho) s.Handle("/", showChecks) s.Handle("/create", showCreate) s.Handle("/check", showCheck) @@ -179,6 +188,48 @@ func main() { log.Fatalf("http server stopped: %s", s.ListenAndServe()) } +func parseLogger(config Config) *slog.Logger { + var output io.Writer + switch config.Log.Output { + case "", "stderr": + output = os.Stderr + case "stdout": + output = os.Stdout + default: + var err error + output, err = os.OpenFile(config.Log.Output, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0640) + if err != nil { + log.Fatalf("could not open log file handler: %s", err) + } + } + + var level slog.Level + switch config.Log.Level { + case "debug": + level = slog.LevelDebug + case "", "info": + level = slog.LevelInfo + case "warn": + level = slog.LevelWarn + case "error": + level = slog.LevelError + default: + log.Fatalf("unknown log level '%s', only 'debug', 'info', 'warn' and 'error' are supported", config.Log.Level) + } + + var handler slog.Handler + switch config.Log.Format { + case "", "text": + handler = slog.NewTextHandler(output, &slog.HandlerOptions{Level: level}) + case "json": + handler = slog.NewJSONHandler(output, &slog.HandlerOptions{Level: level}) + default: + log.Fatalf("unknown log format '%s', only 'text' and 'json' are supported", config.Log.Format) + } + + return slog.New(handler) +} + func checkAction(con *Context) { if con.r.Method != "POST" { con.w.WriteHeader(http.StatusMethodNotAllowed) diff --git a/cmd/monfront/server.go b/cmd/monfront/server.go index 8e53c64..06afe11 100644 --- a/cmd/monfront/server.go +++ b/cmd/monfront/server.go @@ -2,12 +2,15 @@ package main import ( "compress/gzip" + "crypto/rand" "database/sql" "encoding/json" "fmt" "html/template" "io" "log" + "log/slog" + "math/big" "net" "net/http" "strings" @@ -19,6 +22,7 @@ type ( listen net.Listener db *sql.DB h *http.ServeMux + log *slog.Logger tmpl *template.Template auth func(c *Context) error // authentication autho func(c *Context) error // authorization @@ -32,6 +36,7 @@ type ( r *http.Request tmpl *template.Template db *sql.DB + log *slog.Logger User string `json:"-"` Filter *filter `json:"-"` @@ -51,7 +56,7 @@ type ( } ) -func newServer(l net.Listener, db *sql.DB, tmpl *template.Template, auth func(c *Context) error, autho func(c *Context) error) *server { +func newServer(l net.Listener, db *sql.DB, log *slog.Logger, tmpl *template.Template, auth func(c *Context) error, autho func(c *Context) error) *server { s := &server{ listen: l, db: db, @@ -59,6 +64,7 @@ func newServer(l net.Listener, db *sql.DB, tmpl *template.Template, auth func(c h: http.NewServeMux(), auth: auth, autho: autho, + log: log, } return s } @@ -70,11 +76,19 @@ func (s *server) ListenAndServe() error { func (s *server) Handle(path string, fun handleFunc) { s.h.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { + txid, err := newTxId() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(`internal error occured`)) + s.log.Error("could not create txid", "error", err) + return + } c := &Context{ w: w, r: r, tmpl: s.tmpl, db: s.db, + log: s.log.With("path", path, "txid", txid), } if err := s.auth(c); err != nil { return @@ -153,3 +167,32 @@ func (c *Context) SetCookie(name, val string, expire time.Time) { http.SetCookie(c.w, &cook) return } + +// Alphabet are the available characters used to generate the txids. The more +// characters available the more bits can be represented with shorter IDs. +const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_" + +// alphalen is the number of available characters in the alphabet. +var alphalen = big.NewInt(int64(len(alphabet))) + +// txlen is the length of the generated transaction IDs +const txlen = 15 + +// generate a new transaction ID +// +// This function generates a unique ID using the above parameters. If the IDs +// generated are not unique enough to track a transaction, the txlen should +// be raised. +func newTxId() (string, error) { + s := make([]byte, txlen) + var err error + var num *big.Int + for i := 0; i < txlen; i++ { + num, err = rand.Int(rand.Reader, alphalen) + if err != nil { + return "", err + } + s[i] = alphabet[num.Int64()] + } + return string(s), nil +} -- cgit v1.2.3-70-g09d2