aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGibheer <gibheer+git@zero-knowledge.org>2023-08-17 22:02:52 +0200
committerGibheer <gibheer+git@zero-knowledge.org>2023-08-17 22:02:52 +0200
commit10f7eb53f4370cab6c1ff390773772389d500e59 (patch)
tree8c7d81f6428f182bec3162274d88fe4e486be9e1
parenta4a8c642296560ddb88ee43199d43520250b9828 (diff)
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.
-rw-r--r--cmd/monfront/main.go53
-rw-r--r--cmd/monfront/server.go45
-rw-r--r--monfront.conf.example13
3 files changed, 109 insertions, 2 deletions
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
+}
diff --git a/monfront.conf.example b/monfront.conf.example
index 26a6422..57da37f 100644
--- a/monfront.conf.example
+++ b/monfront.conf.example
@@ -64,3 +64,16 @@ mode = "all"
# The list defines the usernames allowed to change data in the frontend. They
# must be authenticated to get the permission.
#list = ["user1", "user2"]
+
+[log]
+# With format the log output format can be switched between `text`
+# and `json` output.
+#format = "text"
+
+# With level the amount of logs can be reduced when necessary. The supported
+# levels are `error`, `warn`, `info` and `debug`.
+#level = "info"
+
+# Output decides where to send all generated log output. It can either be a path
+# or one of the special outputs `stdout` or `stderr`.
+#output = "stderr"