chore: add go config and logging layer

This commit is contained in:
Philipp
2026-06-10 14:56:03 +02:00
parent e4bfc62c18
commit 2112821f79
12 changed files with 265 additions and 30 deletions
+87
View File
@@ -0,0 +1,87 @@
package config
import (
"fmt"
"log/slog"
"os"
"strconv"
"strings"
)
type Config struct {
AppEnv string
LogLevel slog.Level
BackendAddr string
ConsoleProxyAddr string
WorkerConcurrency int
DatabaseURL string
DatabaseDirectURL string
SupabaseURL string
SupabaseJWKSURL string
SupabaseIssuer string
RedisAddr string
}
func Load() (Config, error) {
logLevel, err := parseLogLevel(getenv("LOG_LEVEL", "info"))
if err != nil {
return Config{}, err
}
workerConcurrency, err := intFromEnv("WORKER_CONCURRENCY", 5)
if err != nil {
return Config{}, err
}
if workerConcurrency < 1 {
return Config{}, fmt.Errorf("WORKER_CONCURRENCY must be greater than 0")
}
return Config{
AppEnv: getenv("APP_ENV", "development"),
LogLevel: logLevel,
BackendAddr: getenv("BACKEND_ADDR", ":8080"),
ConsoleProxyAddr: getenv("CONSOLE_PROXY_ADDR", ":8081"),
WorkerConcurrency: workerConcurrency,
DatabaseURL: os.Getenv("DATABASE_URL"),
DatabaseDirectURL: os.Getenv("DATABASE_DIRECT_URL"),
SupabaseURL: os.Getenv("SUPABASE_URL"),
SupabaseJWKSURL: os.Getenv("SUPABASE_JWKS_URL"),
SupabaseIssuer: os.Getenv("SUPABASE_ISSUER"),
RedisAddr: getenv("REDIS_ADDR", "localhost:6379"),
}, nil
}
func getenv(key, fallback string) string {
if value := strings.TrimSpace(os.Getenv(key)); value != "" {
return value
}
return fallback
}
func intFromEnv(key string, fallback int) (int, error) {
raw := strings.TrimSpace(os.Getenv(key))
if raw == "" {
return fallback, nil
}
value, err := strconv.Atoi(raw)
if err != nil {
return 0, fmt.Errorf("%s must be an integer: %w", key, err)
}
return value, nil
}
func parseLogLevel(raw string) (slog.Level, error) {
switch strings.ToLower(strings.TrimSpace(raw)) {
case "debug":
return slog.LevelDebug, nil
case "info":
return slog.LevelInfo, nil
case "warn", "warning":
return slog.LevelWarn, nil
case "error":
return slog.LevelError, nil
default:
return slog.LevelInfo, fmt.Errorf("LOG_LEVEL must be one of debug, info, warn, error")
}
}
+109
View File
@@ -0,0 +1,109 @@
package config
import (
"log/slog"
"testing"
)
func TestLoadDefaults(t *testing.T) {
clearConfigEnv(t)
cfg, err := Load()
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if cfg.AppEnv != "development" {
t.Fatalf("AppEnv = %q, want development", cfg.AppEnv)
}
if cfg.LogLevel != slog.LevelInfo {
t.Fatalf("LogLevel = %v, want info", cfg.LogLevel)
}
if cfg.BackendAddr != ":8080" {
t.Fatalf("BackendAddr = %q, want :8080", cfg.BackendAddr)
}
if cfg.ConsoleProxyAddr != ":8081" {
t.Fatalf("ConsoleProxyAddr = %q, want :8081", cfg.ConsoleProxyAddr)
}
if cfg.WorkerConcurrency != 5 {
t.Fatalf("WorkerConcurrency = %d, want 5", cfg.WorkerConcurrency)
}
if cfg.RedisAddr != "localhost:6379" {
t.Fatalf("RedisAddr = %q, want localhost:6379", cfg.RedisAddr)
}
}
func TestLoadOverrides(t *testing.T) {
clearConfigEnv(t)
t.Setenv("APP_ENV", "test")
t.Setenv("LOG_LEVEL", "debug")
t.Setenv("BACKEND_ADDR", ":9000")
t.Setenv("CONSOLE_PROXY_ADDR", ":9001")
t.Setenv("WORKER_CONCURRENCY", "12")
t.Setenv("REDIS_ADDR", "redis:6379")
cfg, err := Load()
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if cfg.AppEnv != "test" {
t.Fatalf("AppEnv = %q, want test", cfg.AppEnv)
}
if cfg.LogLevel != slog.LevelDebug {
t.Fatalf("LogLevel = %v, want debug", cfg.LogLevel)
}
if cfg.BackendAddr != ":9000" {
t.Fatalf("BackendAddr = %q, want :9000", cfg.BackendAddr)
}
if cfg.ConsoleProxyAddr != ":9001" {
t.Fatalf("ConsoleProxyAddr = %q, want :9001", cfg.ConsoleProxyAddr)
}
if cfg.WorkerConcurrency != 12 {
t.Fatalf("WorkerConcurrency = %d, want 12", cfg.WorkerConcurrency)
}
if cfg.RedisAddr != "redis:6379" {
t.Fatalf("RedisAddr = %q, want redis:6379", cfg.RedisAddr)
}
}
func TestLoadRejectsInvalidLogLevel(t *testing.T) {
clearConfigEnv(t)
t.Setenv("LOG_LEVEL", "trace")
if _, err := Load(); err == nil {
t.Fatal("Load() error = nil, want error")
}
}
func TestLoadRejectsInvalidWorkerConcurrency(t *testing.T) {
clearConfigEnv(t)
t.Setenv("WORKER_CONCURRENCY", "0")
if _, err := Load(); err == nil {
t.Fatal("Load() error = nil, want error")
}
}
func clearConfigEnv(t *testing.T) {
t.Helper()
for _, key := range []string{
"APP_ENV",
"LOG_LEVEL",
"BACKEND_ADDR",
"CONSOLE_PROXY_ADDR",
"WORKER_CONCURRENCY",
"DATABASE_URL",
"DATABASE_DIRECT_URL",
"SUPABASE_URL",
"SUPABASE_JWKS_URL",
"SUPABASE_ISSUER",
"REDIS_ADDR",
} {
t.Setenv(key, "")
}
}
+3
View File
@@ -0,0 +1,3 @@
module forgejo.digital-droplets.de/philschlo/proxui/platform
go 1.22
+17
View File
@@ -0,0 +1,17 @@
package logging
import (
"log/slog"
"os"
)
func New(service string, appEnv string, level slog.Level) *slog.Logger {
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: level,
})
return slog.New(handler).With(
"service", service,
"env", appEnv,
)
}