Replace zerolog with log/slog from stdlib
- Rewrite logger package to use slog with LevelVar for dynamic levels - Update all packages to use *slog.Logger instead of *zerolog.Logger - Use TextHandler for TTY (dev), JSONHandler for production - Add make check target (runs lint + test) - Add make test target
This commit is contained in:
@@ -2,10 +2,10 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"git.eeqj.de/sneak/gohttpserver/internal/globals"
|
||||
"git.eeqj.de/sneak/gohttpserver/internal/logger"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/fx"
|
||||
|
||||
@@ -37,7 +37,7 @@ type Config struct {
|
||||
Port int
|
||||
SentryDSN string
|
||||
params *ConfigParams
|
||||
log *zerolog.Logger
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) {
|
||||
@@ -69,9 +69,8 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) {
|
||||
// Config file not found; ignore error if desired
|
||||
} else {
|
||||
// Config file was found but another error was produced
|
||||
log.Panic().
|
||||
Err(err).
|
||||
Msg("config file malformed")
|
||||
log.Error("config file malformed", "error", err)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"git.eeqj.de/sneak/gohttpserver/internal/config"
|
||||
"git.eeqj.de/sneak/gohttpserver/internal/logger"
|
||||
"github.com/rs/zerolog"
|
||||
"go.uber.org/fx"
|
||||
|
||||
// spooky action at a distance!
|
||||
@@ -26,7 +26,7 @@ type DatabaseParams struct {
|
||||
|
||||
type Database struct {
|
||||
URL string
|
||||
log *zerolog.Logger
|
||||
log *slog.Logger
|
||||
params *DatabaseParams
|
||||
}
|
||||
|
||||
@@ -35,11 +35,11 @@ func New(lc fx.Lifecycle, params DatabaseParams) (*Database, error) {
|
||||
s.params = ¶ms
|
||||
s.log = params.Logger.Get()
|
||||
|
||||
s.log.Info().Msg("Database instantiated")
|
||||
s.log.Info("Database instantiated")
|
||||
|
||||
lc.Append(fx.Hook{
|
||||
OnStart: func(ctx context.Context) error {
|
||||
s.log.Info().Msg("Database OnStart Hook")
|
||||
s.log.Info("Database OnStart Hook")
|
||||
// FIXME connect to db
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -3,13 +3,13 @@ package handlers
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"git.eeqj.de/sneak/gohttpserver/internal/database"
|
||||
"git.eeqj.de/sneak/gohttpserver/internal/globals"
|
||||
"git.eeqj.de/sneak/gohttpserver/internal/healthcheck"
|
||||
"git.eeqj.de/sneak/gohttpserver/internal/logger"
|
||||
"github.com/rs/zerolog"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
@@ -23,7 +23,7 @@ type HandlersParams struct {
|
||||
|
||||
type Handlers struct {
|
||||
params *HandlersParams
|
||||
log *zerolog.Logger
|
||||
log *slog.Logger
|
||||
hc *healthcheck.Healthcheck
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func (s *Handlers) respondJSON(w http.ResponseWriter, r *http.Request, data inte
|
||||
if data != nil {
|
||||
err := json.NewEncoder(w).Encode(data)
|
||||
if err != nil {
|
||||
s.log.Error().Err(err).Msg("json encode error")
|
||||
s.log.Error("json encode error", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ func (s *Handlers) HandleIndex() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
err := t.ExecuteTemplate(w, "index.html", nil)
|
||||
if err != nil {
|
||||
s.log.Error().Err(err).Msg("")
|
||||
s.log.Error("template execution failed", "error", err)
|
||||
http.Error(w, http.StatusText(500), 500)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ func (s *Handlers) HandleLoginGET() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
err := t.ExecuteTemplate(w, "login.html", nil)
|
||||
if err != nil {
|
||||
s.log.Error().Err(err).Msg("")
|
||||
s.log.Error("template execution failed", "error", err)
|
||||
http.Error(w, http.StatusText(500), 500)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ func (s *Handlers) HandleSignupGET() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
err := t.ExecuteTemplate(w, "signup.html", nil)
|
||||
if err != nil {
|
||||
s.log.Error().Err(err).Msg("")
|
||||
s.log.Error("template execution failed", "error", err)
|
||||
http.Error(w, http.StatusText(500), 500)
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ func (s *Handlers) HandleSignupPOST() http.HandlerFunc {
|
||||
err := t.ExecuteTemplate(w, "signup.html", nil)
|
||||
|
||||
if err != nil {
|
||||
s.log.Error().Err(err).Msg("")
|
||||
s.log.Error("template execution failed", "error", err)
|
||||
http.Error(w, http.StatusText(500), 500)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ package healthcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"git.eeqj.de/sneak/gohttpserver/internal/config"
|
||||
"git.eeqj.de/sneak/gohttpserver/internal/database"
|
||||
"git.eeqj.de/sneak/gohttpserver/internal/globals"
|
||||
"git.eeqj.de/sneak/gohttpserver/internal/logger"
|
||||
"github.com/rs/zerolog"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
@@ -22,7 +22,7 @@ type HealthcheckParams struct {
|
||||
|
||||
type Healthcheck struct {
|
||||
StartupTime time.Time
|
||||
log *zerolog.Logger
|
||||
log *slog.Logger
|
||||
params *HealthcheckParams
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.eeqj.de/sneak/gohttpserver/internal/globals"
|
||||
"github.com/rs/zerolog"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
@@ -16,82 +14,56 @@ type LoggerParams struct {
|
||||
}
|
||||
|
||||
type Logger struct {
|
||||
log *zerolog.Logger
|
||||
log *slog.Logger
|
||||
level *slog.LevelVar
|
||||
params LoggerParams
|
||||
}
|
||||
|
||||
func New(lc fx.Lifecycle, params LoggerParams) (*Logger, error) {
|
||||
l := new(Logger)
|
||||
l.level = new(slog.LevelVar)
|
||||
l.level.Set(slog.LevelInfo)
|
||||
|
||||
// always log in UTC
|
||||
zerolog.TimestampFunc = func() time.Time {
|
||||
return time.Now().UTC()
|
||||
}
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
|
||||
// TTY detection for dev vs prod output
|
||||
tty := false
|
||||
if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) != 0 {
|
||||
tty = true
|
||||
}
|
||||
|
||||
var writers []io.Writer
|
||||
|
||||
var handler slog.Handler
|
||||
if tty {
|
||||
// this does cool colorization for console/dev
|
||||
consoleWriter := zerolog.NewConsoleWriter(
|
||||
func(w *zerolog.ConsoleWriter) {
|
||||
// Customize time format
|
||||
w.TimeFormat = time.RFC3339Nano
|
||||
},
|
||||
)
|
||||
|
||||
writers = append(writers, consoleWriter)
|
||||
// Text output for development
|
||||
handler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: l.level,
|
||||
AddSource: true,
|
||||
})
|
||||
} else {
|
||||
// log json in prod for the machines
|
||||
writers = append(writers, os.Stdout)
|
||||
// JSON output for production
|
||||
handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: l.level,
|
||||
AddSource: true,
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
// this is how you log to a file, if you do that
|
||||
// sort of thing still
|
||||
logfile := viper.GetString("Logfile")
|
||||
if logfile != "" {
|
||||
logfileDir := filepath.Dir(logfile)
|
||||
err := goutil.Mkdirp(logfileDir)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("unable to create log dir")
|
||||
}
|
||||
|
||||
hp.logfh, err = os.OpenFile(logfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
panic("unable to open logfile: " + err.Error())
|
||||
}
|
||||
|
||||
writers = append(writers, hp.logfh)
|
||||
*/
|
||||
|
||||
multi := zerolog.MultiLevelWriter(writers...)
|
||||
logger := zerolog.New(multi).With().Timestamp().Logger().With().Caller().Logger()
|
||||
|
||||
l.log = &logger
|
||||
// log.Logger = logger
|
||||
l.log = slog.New(handler)
|
||||
l.params = params
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *Logger) EnableDebugLogging() {
|
||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||
l.log.Debug().Bool("debug", true).Send()
|
||||
l.level.Set(slog.LevelDebug)
|
||||
l.log.Debug("debug logging enabled", "debug", true)
|
||||
}
|
||||
|
||||
func (l *Logger) Get() *zerolog.Logger {
|
||||
func (l *Logger) Get() *slog.Logger {
|
||||
return l.log
|
||||
}
|
||||
|
||||
func (l *Logger) Identify() {
|
||||
l.log.Info().
|
||||
Str("appname", l.params.Globals.Appname).
|
||||
Str("version", l.params.Globals.Version).
|
||||
Str("buildarch", l.params.Globals.Buildarch).
|
||||
Msg("starting")
|
||||
l.log.Info("starting",
|
||||
"appname", l.params.Globals.Appname,
|
||||
"version", l.params.Globals.Version,
|
||||
"buildarch", l.params.Globals.Buildarch,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -11,7 +12,6 @@ import (
|
||||
basicauth "github.com/99designs/basicauth-go"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/rs/zerolog"
|
||||
metrics "github.com/slok/go-http-metrics/metrics/prometheus"
|
||||
ghmm "github.com/slok/go-http-metrics/middleware"
|
||||
"github.com/slok/go-http-metrics/middleware/std"
|
||||
@@ -27,7 +27,7 @@ type MiddlewareParams struct {
|
||||
}
|
||||
|
||||
type Middleware struct {
|
||||
log *zerolog.Logger
|
||||
log *slog.Logger
|
||||
params *MiddlewareParams
|
||||
}
|
||||
|
||||
@@ -38,9 +38,6 @@ func New(lc fx.Lifecycle, params MiddlewareParams) (*Middleware, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// the following is from
|
||||
// https://learning-cloud-native-go.github.io/docs/a6.adding_zerolog_logger/
|
||||
|
||||
func ipFromHostPort(hp string) string {
|
||||
h, _, err := net.SplitHostPort(hp)
|
||||
if err != nil {
|
||||
@@ -70,7 +67,6 @@ func (lrw *loggingResponseWriter) WriteHeader(code int) {
|
||||
// this returns a Middleware that is designed to do every request through the
|
||||
// mux, note the signature:
|
||||
func (s *Middleware) Logging() func(http.Handler) http.Handler {
|
||||
// FIXME this should use https://github.com/google/go-cloud/blob/master/server/requestlog/requestlog.go
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
@@ -78,18 +74,18 @@ func (s *Middleware) Logging() func(http.Handler) http.Handler {
|
||||
ctx := r.Context()
|
||||
defer func() {
|
||||
latency := time.Since(start)
|
||||
s.log.Info().
|
||||
Time("request_start", start).
|
||||
Str("method", r.Method).
|
||||
Str("url", r.URL.String()).
|
||||
Str("useragent", r.UserAgent()).
|
||||
Str("request_id", ctx.Value(middleware.RequestIDKey).(string)).
|
||||
Str("referer", r.Referer()).
|
||||
Str("proto", r.Proto).
|
||||
Str("remoteIP", ipFromHostPort(r.RemoteAddr)).
|
||||
Int("status", lrw.statusCode).
|
||||
Int("latency_ms", int(latency.Milliseconds())).
|
||||
Send()
|
||||
s.log.InfoContext(ctx, "request",
|
||||
"request_start", start,
|
||||
"method", r.Method,
|
||||
"url", r.URL.String(),
|
||||
"useragent", r.UserAgent(),
|
||||
"request_id", ctx.Value(middleware.RequestIDKey).(string),
|
||||
"referer", r.Referer(),
|
||||
"proto", r.Proto,
|
||||
"remoteIP", ipFromHostPort(r.RemoteAddr),
|
||||
"status", lrw.statusCode,
|
||||
"latency_ms", latency.Milliseconds(),
|
||||
)
|
||||
}()
|
||||
|
||||
next.ServeHTTP(lrw, r)
|
||||
@@ -116,7 +112,7 @@ func (s *Middleware) Auth() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// CHANGEME you'll want to change this to do stuff.
|
||||
s.log.Info().Msg("AUTH: before request")
|
||||
s.log.Info("AUTH: before request")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ func (s *Server) serveUntilShutdown() {
|
||||
// this does any necessary setup in each handler
|
||||
s.SetupRoutes()
|
||||
|
||||
s.log.Info().Str("listenaddr", listenAddr).Msg("http begin listen")
|
||||
s.log.Info("http begin listen", "listenaddr", listenAddr)
|
||||
if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
s.log.Error().Msgf("listen:%+s\n", err)
|
||||
s.log.Error("listen error", "error", err)
|
||||
if s.cancelFunc != nil {
|
||||
s.cancelFunc()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -14,7 +15,6 @@ import (
|
||||
"git.eeqj.de/sneak/gohttpserver/internal/handlers"
|
||||
"git.eeqj.de/sneak/gohttpserver/internal/logger"
|
||||
"git.eeqj.de/sneak/gohttpserver/internal/middleware"
|
||||
"github.com/rs/zerolog"
|
||||
"go.uber.org/fx"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
@@ -43,7 +43,7 @@ type Server struct {
|
||||
startupTime time.Time
|
||||
exitCode int
|
||||
sentryEnabled bool
|
||||
log *zerolog.Logger
|
||||
log *slog.Logger
|
||||
ctx context.Context
|
||||
cancelFunc context.CancelFunc
|
||||
httpServer *http.Server
|
||||
@@ -103,10 +103,10 @@ func (s *Server) enableSentry() {
|
||||
Release: fmt.Sprintf("%s-%s", s.params.Globals.Appname, s.params.Globals.Version),
|
||||
})
|
||||
if err != nil {
|
||||
s.log.Fatal().Err(err).Msg("sentry init failure")
|
||||
return
|
||||
s.log.Error("sentry init failure", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
s.log.Info().Msg("sentry error reporting activated")
|
||||
s.log.Info("sentry error reporting activated")
|
||||
s.sentryEnabled = true
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ func (s *Server) serve() int {
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
// block and wait for signal
|
||||
sig := <-c
|
||||
s.log.Info().Msgf("signal received: %+v", sig)
|
||||
s.log.Info("signal received", "signal", sig)
|
||||
if s.cancelFunc != nil {
|
||||
// cancelling the main context will trigger a clean
|
||||
// shutdown.
|
||||
@@ -140,7 +140,7 @@ func (s *Server) serve() int {
|
||||
}
|
||||
|
||||
func (s *Server) cleanupForExit() {
|
||||
s.log.Info().Msg("cleaning up")
|
||||
s.log.Info("cleaning up")
|
||||
// FIXME unimplemented
|
||||
// close database connections or whatever
|
||||
}
|
||||
@@ -150,9 +150,7 @@ func (s *Server) cleanShutdown() {
|
||||
s.exitCode = 0
|
||||
ctxShutdown, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
if err := s.httpServer.Shutdown(ctxShutdown); err != nil {
|
||||
s.log.Error().
|
||||
Err(err).
|
||||
Msg("server clean shutdown failed")
|
||||
s.log.Error("server clean shutdown failed", "error", err)
|
||||
}
|
||||
if shutdownCancel != nil {
|
||||
shutdownCancel()
|
||||
|
||||
Reference in New Issue
Block a user