Files
member-console/internal/logging/logging.go
T
cgalo5758 8e7e0dd04d Harden container, OIDC auth, and error handling
Run Docker runtime stage as non-root user app (UID 65532).

Add styled full-page 404/500 error rendering for navigation requests
while preserving plain-text responses for HTMX partials.

Reuse recent unconsumed OIDC login state to avoid state mismatch on
parallel login hits, and merge resource_access in role extraction.

Re-level template headings, add autocomplete tokens, and resolve
catalog resource display names.

Self-label test-stack secrets and document CSRF secret rotation.
2026-07-31 23:27:15 -05:00

90 lines
2.4 KiB
Go

package logging
import (
"context"
"fmt"
"log/slog"
"os"
"time"
)
// loggerKey is the context key for logger values
type loggerKey struct{}
// AppLogger is the application-wide logger instance
var AppLogger *slog.Logger
// SetupLogger initializes the structured logger based on environment settings
func SetupLogger(env string) *slog.Logger {
// Determine log level
logLevel := slog.LevelInfo
if levelStr := os.Getenv("LOG_LEVEL"); levelStr != "" {
if err := logLevel.UnmarshalText([]byte(levelStr)); err != nil {
panic(fmt.Sprintf("invalid log level: %s", levelStr))
}
}
var handler slog.Handler
// Configure handler based on environment
if env == "development" {
// In development, use text output with source information if in debug mode
opts := &slog.HandlerOptions{
Level: logLevel,
AddSource: logLevel == slog.LevelDebug,
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
// Format time for better readability in dev mode
if a.Key == slog.TimeKey {
if t, ok := a.Value.Any().(time.Time); ok {
return slog.String(slog.TimeKey, t.Format(time.RFC3339))
}
}
return a
},
}
handler = slog.NewTextHandler(os.Stdout, opts)
} else {
// In production, use JSON output
handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: logLevel,
})
}
// Create and set the default logger
logger := slog.New(handler)
slog.SetDefault(logger)
AppLogger = logger
logger.Info("logger initialized",
slog.String("environment", env),
slog.String("level", logLevel.String()))
return logger
}
// FromContext retrieves a logger from the given context
// If no logger exists in the context, returns the default logger.
// Never returns nil: before Initialize has run (e.g. in tests), callers get
// slog's process default instead of a nil AppLogger that would SIGSEGV.
func FromContext(ctx context.Context) *slog.Logger {
if ctx != nil {
if logger, ok := ctx.Value(loggerKey{}).(*slog.Logger); ok {
return logger
}
}
if AppLogger != nil {
return AppLogger
}
return slog.Default()
}
// WithContext stores a logger in the given context
func WithContext(ctx context.Context, logger *slog.Logger) context.Context {
return context.WithValue(ctx, loggerKey{}, logger)
}
// WithValues returns a new logger with additional context values
func WithValues(logger *slog.Logger, attrs ...any) *slog.Logger {
return logger.With(attrs...)
}