mirror of
https://github.com/ferdzo/fs.git
synced 2026-04-05 01:56:25 +00:00
Added logging
This commit is contained in:
162
logging/logging.go
Normal file
162
logging/logging.go
Normal file
@@ -0,0 +1,162 @@
|
||||
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")))
|
||||
if levelName == "" {
|
||||
levelName = "info"
|
||||
}
|
||||
level := parseLevel(levelName)
|
||||
levelName = level.String()
|
||||
|
||||
format := strings.ToLower(strings.TrimSpace(os.Getenv("LOG_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: envBool("AUDIT_LOG", true),
|
||||
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,
|
||||
"remote_addr", r.RemoteAddr,
|
||||
}
|
||||
switch {
|
||||
case elapsed < time.Microsecond:
|
||||
attrs = append(attrs, "duration_ns", elapsed.Nanoseconds())
|
||||
case elapsed < time.Millisecond:
|
||||
attrs = append(attrs, "duration_us", elapsed.Microseconds())
|
||||
default:
|
||||
attrs = append(attrs, "duration_ms", elapsed.Milliseconds())
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user