// Package logging builds the single *slog.Logger instance shared across the // whole application. Every log line the app writes goes through this // logger, formatted as JSON to stdout. // // Why JSON to stdout specifically? This is the standard "12-factor app" // approach to logging: the application doesn't know or care where its logs // end up (a file, Loki, Elasticsearch, ...) - it just writes structured // lines to stdout, and an external agent (in your case, Grafana Alloy) // takes care of collecting, parsing, and shipping them. Because every line // is valid JSON with consistent keys, Alloy/Loki can index and query on // fields like "status", "path", or "request_id" without any custom parsing // rules or regexes. package logging import ( "log/slog" "os" ) // New builds and returns the application's structured logger. // // The minimum log level is controlled by the LOG_LEVEL environment // variable: set LOG_LEVEL=debug to see verbose Debug()-level output; // anything else (or unset) defaults to Info level, which hides Debug logs. func New() *slog.Logger { level := slog.LevelInfo if os.Getenv("LOG_LEVEL") == "debug" { level = slog.LevelDebug } // slog.NewJSONHandler formats every log record as a single-line JSON // object and writes it to the given io.Writer (here, os.Stdout). // Swapping this for slog.NewTextHandler would switch to human-readable // text output instead - useful for local dev if you ever want it - but // every other part of the app that calls logger.Info/Error/etc would // stay completely unchanged, since they only depend on *slog.Logger, // not on which Handler is behind it. handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: level, }) return slog.New(handler) }