mirror of
https://github.com/ferdzo/fs.git
synced 2026-04-04 20:36:25 +00:00
162 lines
3.5 KiB
Go
162 lines
3.5 KiB
Go
package logging
|
|
|
|
import (
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Config struct {
|
|
Level slog.Level
|
|
LevelName string
|
|
Format string
|
|
Audit bool
|
|
AddSource bool
|
|
DebugMode bool
|
|
}
|
|
|
|
func ConfigFromEnv() Config {
|
|
levelName := strings.ToLower(strings.TrimSpace(os.Getenv("LOG_LEVEL")))
|
|
format := strings.ToLower(strings.TrimSpace(os.Getenv("LOG_FORMAT")))
|
|
return ConfigFromValues(levelName, format, envBool("AUDIT_LOG", true))
|
|
}
|
|
|
|
func ConfigFromValues(levelName, format string, audit bool) Config {
|
|
levelName = strings.ToLower(strings.TrimSpace(levelName))
|
|
if levelName == "" {
|
|
levelName = "info"
|
|
}
|
|
level := parseLevel(levelName)
|
|
levelName = strings.ToUpper(level.String())
|
|
|
|
format = strings.ToLower(strings.TrimSpace(format))
|
|
if format == "" {
|
|
format = "text"
|
|
}
|
|
if format != "json" && format != "text" {
|
|
format = "text"
|
|
}
|
|
|
|
debugMode := level <= slog.LevelDebug
|
|
return Config{
|
|
Level: level,
|
|
LevelName: levelName,
|
|
Format: format,
|
|
Audit: audit,
|
|
AddSource: debugMode,
|
|
DebugMode: debugMode,
|
|
}
|
|
}
|
|
|
|
func NewLogger(cfg Config) *slog.Logger {
|
|
opts := &slog.HandlerOptions{
|
|
Level: cfg.Level,
|
|
AddSource: cfg.AddSource,
|
|
}
|
|
opts.ReplaceAttr = func(_ []string, attr slog.Attr) slog.Attr {
|
|
if attr.Key == slog.SourceKey {
|
|
if src, ok := attr.Value.Any().(*slog.Source); ok && src != nil {
|
|
attr.Key = "src"
|
|
attr.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line))
|
|
}
|
|
}
|
|
return attr
|
|
}
|
|
|
|
var handler slog.Handler
|
|
if cfg.Format == "json" {
|
|
handler = slog.NewJSONHandler(os.Stdout, opts)
|
|
} else {
|
|
handler = slog.NewTextHandler(os.Stdout, opts)
|
|
}
|
|
|
|
logger := slog.New(handler)
|
|
slog.SetDefault(logger)
|
|
return logger
|
|
}
|
|
|
|
func HTTPMiddleware(logger *slog.Logger, cfg Config) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
start := time.Now()
|
|
ww := &responseWriter{ResponseWriter: w, status: http.StatusOK}
|
|
|
|
next.ServeHTTP(ww, r)
|
|
|
|
if !cfg.Audit && !cfg.DebugMode {
|
|
return
|
|
}
|
|
|
|
elapsed := time.Since(start)
|
|
attrs := []any{
|
|
"method", r.Method,
|
|
"path", r.URL.Path,
|
|
"status", ww.status,
|
|
"bytes", ww.bytes,
|
|
"duration_ms", float64(elapsed.Nanoseconds()) / 1_000_000.0,
|
|
"remote_addr", r.RemoteAddr,
|
|
}
|
|
|
|
if cfg.DebugMode {
|
|
attrs = append(attrs,
|
|
"query", r.URL.RawQuery,
|
|
"user_agent", r.UserAgent(),
|
|
"content_length", r.ContentLength,
|
|
"content_type", r.Header.Get("Content-Type"),
|
|
"x_amz_sha256", r.Header.Get("x-amz-content-sha256"),
|
|
)
|
|
logger.Debug("http_request", attrs...)
|
|
return
|
|
}
|
|
|
|
logger.Info("http_request", attrs...)
|
|
})
|
|
}
|
|
}
|
|
|
|
type responseWriter struct {
|
|
http.ResponseWriter
|
|
status int
|
|
bytes int
|
|
}
|
|
|
|
func (w *responseWriter) WriteHeader(statusCode int) {
|
|
w.status = statusCode
|
|
w.ResponseWriter.WriteHeader(statusCode)
|
|
}
|
|
|
|
func (w *responseWriter) Write(p []byte) (int, error) {
|
|
n, err := w.ResponseWriter.Write(p)
|
|
w.bytes += n
|
|
return n, err
|
|
}
|
|
|
|
func envBool(key string, defaultValue bool) bool {
|
|
raw := os.Getenv(key)
|
|
if raw == "" {
|
|
return defaultValue
|
|
}
|
|
value, err := strconv.ParseBool(raw)
|
|
if err != nil {
|
|
return defaultValue
|
|
}
|
|
return value
|
|
}
|
|
|
|
func parseLevel(levelName string) slog.Level {
|
|
switch levelName {
|
|
case "debug":
|
|
return slog.LevelDebug
|
|
case "warn", "warning":
|
|
return slog.LevelWarn
|
|
case "error":
|
|
return slog.LevelError
|
|
default:
|
|
return slog.LevelInfo
|
|
}
|
|
}
|