feat(observability): configurable logging, request ids and error logs
- internal/logging: LOG_LEVEL/LOG_FORMAT parsing and slog handler - request_id middleware (X-Request-ID or generated, echoed) + debug access log - httpx registers errors; middleware logs internal causes with request_id - health excluded, objects/sse debug-only, no query/body/secrets in logs - new observability capability spec; README/.env.example docs
This commit is contained in:
@@ -5,6 +5,8 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"galo/internal/logging"
|
||||
)
|
||||
|
||||
type Postgres struct {
|
||||
@@ -63,6 +65,11 @@ type Database struct {
|
||||
SQLitePath string
|
||||
}
|
||||
|
||||
type Log struct {
|
||||
Level string
|
||||
Format string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Postgres Postgres
|
||||
S3 S3
|
||||
@@ -72,6 +79,7 @@ type Config struct {
|
||||
Server Server
|
||||
SSE SSE
|
||||
Worker Worker
|
||||
Log Log
|
||||
}
|
||||
|
||||
func Load() (*Config, error) {
|
||||
@@ -125,6 +133,15 @@ func Load() (*Config, error) {
|
||||
return nil, fmt.Errorf("invalid WORKER_PENDING_TTL: %w", err)
|
||||
}
|
||||
|
||||
logLevel := getEnv("LOG_LEVEL", "info")
|
||||
if _, err := logging.ParseLevel(logLevel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logFormat := getEnv("LOG_FORMAT", "json")
|
||||
if _, err := logging.ParseFormat(logFormat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Postgres: Postgres{
|
||||
Host: getEnv("PG_HOST", "pg.localnet"),
|
||||
@@ -169,6 +186,10 @@ func Load() (*Config, error) {
|
||||
Batch: workerBatch,
|
||||
PendingTTL: workerPendingTTL,
|
||||
},
|
||||
Log: Log{
|
||||
Level: logLevel,
|
||||
Format: logFormat,
|
||||
},
|
||||
}
|
||||
|
||||
switch cfg.Database.Backend {
|
||||
|
||||
@@ -132,3 +132,47 @@ func TestLoadDatabaseBackend(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadLog(t *testing.T) {
|
||||
t.Run("defaults", func(t *testing.T) {
|
||||
setRequiredEnv(t)
|
||||
t.Setenv("LOG_LEVEL", "")
|
||||
t.Setenv("LOG_FORMAT", "")
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("load: %v", err)
|
||||
}
|
||||
if cfg.Log.Level != "info" || cfg.Log.Format != "json" {
|
||||
t.Fatalf("log = %+v, want info/json", cfg.Log)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("custom values", func(t *testing.T) {
|
||||
setRequiredEnv(t)
|
||||
t.Setenv("LOG_LEVEL", "debug")
|
||||
t.Setenv("LOG_FORMAT", "text")
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("load: %v", err)
|
||||
}
|
||||
if cfg.Log.Level != "debug" || cfg.Log.Format != "text" {
|
||||
t.Fatalf("log = %+v, want debug/text", cfg.Log)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid level", func(t *testing.T) {
|
||||
setRequiredEnv(t)
|
||||
t.Setenv("LOG_LEVEL", "trace")
|
||||
if _, err := Load(); err == nil {
|
||||
t.Fatal("expected error for invalid LOG_LEVEL")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid format", func(t *testing.T) {
|
||||
setRequiredEnv(t)
|
||||
t.Setenv("LOG_FORMAT", "xml")
|
||||
if _, err := Load(); err == nil {
|
||||
t.Fatal("expected error for invalid LOG_FORMAT")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user