rdev/internal/adapter/templates/templates/skeleton/pkg/logging/logger.go.tmpl
jordan 8282d60c69 feat: implement composable monorepo template system with component architecture
Adds the composable monorepo template system that generates project skeletons
with pluggable components (service, worker, app-react, app-astro, cli).

Key changes:
- Monorepo skeleton templates with shared pkg/, scripts/, and git hooks
- Component templates (service, worker, app-react, app-astro, cli) with
  Dockerfiles, CI steps, and component.yaml manifests
- Component domain model with validation and dependency resolution
- Component handler endpoints for CRUD and composition
- Template provider extended with BuildComposableProject and component assembly
- Deployer extended with composable project deployment support
- Handler timeout constants (TimeoutFastLookup through TimeoutLongRunning)
- envutil package for centralized env var reads with defaults
- api.DecodeJSON helper for standardized request body decoding
- Standardized response helpers (WriteBadRequest, WriteNotFound, etc.)
- Replaced fullstack-app cookbook with composable-app cookbook
- Hardened handler timeouts, logging, and error responses across all handlers

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:11:42 -07:00

246 lines
5.3 KiB
Cheetah

// Package logging provides slog-based structured logging with context integration.
//
// This package standardizes logging across all services with:
// - Environment-aware formatting (JSON for production, text for development)
// - Request-scoped loggers with context propagation
// - Convenience methods for common logging patterns
//
// Usage:
//
// // Create a logger based on environment
// logger := logging.New(logging.Config{
// Level: logging.LevelInfo,
// Format: logging.FormatJSON,
// Environment: "production",
// })
//
// // Or use convenience constructors
// logger := logging.NewDevelopment() // text format, debug level
// logger := logging.NewProduction() // JSON format, info level
package logging
import (
"io"
"log/slog"
"os"
"strings"
)
// Level represents the logging level.
type Level int
const (
LevelDebug Level = iota
LevelInfo
LevelWarn
LevelError
)
// String returns the string representation of the level.
func (l Level) String() string {
switch l {
case LevelDebug:
return "debug"
case LevelInfo:
return "info"
case LevelWarn:
return "warn"
case LevelError:
return "error"
default:
return "info"
}
}
// ParseLevel parses a string into a Level.
// Returns LevelInfo if the string is not recognized.
func ParseLevel(s string) Level {
switch strings.ToLower(strings.TrimSpace(s)) {
case "debug":
return LevelDebug
case "info":
return LevelInfo
case "warn", "warning":
return LevelWarn
case "error":
return LevelError
default:
return LevelInfo
}
}
func (l Level) toSlog() slog.Level {
switch l {
case LevelDebug:
return slog.LevelDebug
case LevelInfo:
return slog.LevelInfo
case LevelWarn:
return slog.LevelWarn
case LevelError:
return slog.LevelError
default:
return slog.LevelInfo
}
}
// Format represents the output format.
type Format int
const (
FormatJSON Format = iota
FormatText
)
// String returns the string representation of the format.
func (f Format) String() string {
switch f {
case FormatJSON:
return "json"
case FormatText:
return "text"
default:
return "json"
}
}
// ParseFormat parses a string into a Format.
// Returns FormatJSON if the string is not recognized.
func ParseFormat(s string) Format {
switch strings.ToLower(strings.TrimSpace(s)) {
case "text", "console":
return FormatText
case "json":
return FormatJSON
default:
return FormatJSON
}
}
// Config holds the logger configuration.
type Config struct {
// Level sets the minimum log level.
// Default: LevelInfo
Level Level
// Format sets the output format.
// Default: FormatJSON
Format Format
// Output sets the output writer.
// Default: os.Stdout
Output io.Writer
// AddSource adds source file and line number to log entries.
// Default: false
AddSource bool
// Environment determines default format if not specified.
// "development" uses text format, others use JSON.
Environment string
}
// Logger wraps slog.Logger with additional convenience methods.
type Logger struct {
*slog.Logger
}
// New creates a new Logger with the given configuration.
func New(cfg Config) *Logger {
if cfg.Output == nil {
cfg.Output = os.Stdout
}
// Auto-detect format based on environment if not explicitly set
format := cfg.Format
if cfg.Environment == "development" && format == FormatJSON {
format = FormatText
}
opts := &slog.HandlerOptions{
Level: cfg.Level.toSlog(),
AddSource: cfg.AddSource,
}
var handler slog.Handler
switch format {
case FormatText:
handler = slog.NewTextHandler(cfg.Output, opts)
default:
handler = slog.NewJSONHandler(cfg.Output, opts)
}
return &Logger{
Logger: slog.New(handler),
}
}
// NewDevelopment creates a logger configured for development.
// Uses text format, debug level, and includes source location.
func NewDevelopment() *Logger {
return New(Config{
Level: LevelDebug,
Format: FormatText,
AddSource: true,
})
}
// NewProduction creates a logger configured for production.
// Uses JSON format and info level.
func NewProduction() *Logger {
return New(Config{
Level: LevelInfo,
Format: FormatJSON,
})
}
// With returns a new Logger with the given attributes.
func (l *Logger) With(args ...any) *Logger {
return &Logger{
Logger: l.Logger.With(args...),
}
}
// WithGroup returns a new Logger with the given group name.
func (l *Logger) WithGroup(name string) *Logger {
return &Logger{
Logger: l.Logger.WithGroup(name),
}
}
// WithError returns a new Logger with the error attribute.
func (l *Logger) WithError(err error) *Logger {
if err == nil {
return l
}
return l.With("error", err.Error())
}
// WithComponent returns a new Logger with the component attribute.
func (l *Logger) WithComponent(name string) *Logger {
return l.With("component", name)
}
// WithService returns a new Logger with the service attribute.
func (l *Logger) WithService(name string) *Logger {
return l.With("service", name)
}
// Nop returns a logger that discards all output.
func Nop() *Logger {
return New(Config{
Output: io.Discard,
Level: LevelError,
})
}
// Default returns the default logger configured for the current environment.
// Uses APP_ENVIRONMENT env var to determine format.
func Default() *Logger {
env := os.Getenv("APP_ENVIRONMENT")
if env == "development" || env == "" {
return NewDevelopment()
}
return NewProduction()
}