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.
This commit is contained in:
parent
a4a8c64229
commit
10f7eb53f4
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue