Structured Logging Standards
Requirements
- Use
log/slog exclusively
- Never use
log.Printf(), fmt.Print(), or visual formatting (===, empty lines)
- Use structured fields, not string interpolation
- Single format: JSON to stdout
// Wrong
logger.Printf("WARNING: Failed to fetch %s", url)
// Right
slog.Warn("upstream fetch failed", "upstream", url, "error", err)
Setup
cooked uses a single JSON handler writing to stdout:
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
slog.SetDefault(logger)
No log streams, no OTel/ECS formats, no object storage — just structured JSON to stdout.
Request Log Example
Every request is logged with structured fields:
{
"time": "2026-02-06T12:00:00Z",
"level": "INFO",
"msg": "request",
"method": "GET",
"path": "/https://cgit.internal/repo/plain/README.md",
"upstream": "https://cgit.internal/repo/plain/README.md",
"status": 200,
"cache": "hit",
"upstream_ms": 0,
"render_ms": 12,
"total_ms": 14,
"content_type": "markdown",
"bytes": 14832
}
Cache field values: hit, miss, revalidated, expired.
Log Message Style Guide
| Rule |
Guideline |
Example |
| Tense |
Past for events, present for conditions |
upstream fetched, cache full |
| Case |
lowercase start (no capital unless proper noun) |
config loaded, mermaid block found |
| Punctuation |
No trailing period (fragments, not sentences) |
request completed not Request completed. |
| Redundancy |
No severity prefix (level conveys this) |
upstream unreachable not Error: upstream unreachable |
| Specificity |
State what failed, not generic failure |
upstream fetch failed not operation failed |
| Brevity |
Omit articles and filler words |
file too large not The file was too large |
| Action |
Use verb-noun or noun-verb pattern |
config loaded, invalid upstream url |
Severity Levels
| Level |
When to use |
Examples |
| ERROR |
Unrecoverable failures requiring attention |
template parse failed, listen failed |
| WARN |
Degraded but recoverable situations |
upstream returned 5xx, cache eviction failed |
| INFO |
Normal business events |
request, server started, config loaded |
| DEBUG |
Internal details for troubleshooting |
cache hit, rewriting relative url, mdx import stripped |
Structured Fields
- Use snake_case for field names:
upstream_ms, content_type, cache_status
- Suffix units:
_ms, _bytes, _count
- Boolean fields:
has_mermaid, has_toc
- Avoid embedding data in message string; use fields instead
// Bad
slog.Info(fmt.Sprintf("fetched %s in %dms", url, dur))
// Good
slog.Info("upstream fetched", "upstream", url, "duration_ms", dur)
1---2name: logging-config3description: MANDATORY - All logging code must use slog with structured fields. Load before writing any logging.4---5
6# Structured Logging Standards
7
8## Requirements
9
10- Use `log/slog` exclusively
11- Never use `log.Printf()`, `fmt.Print()`, or visual formatting (`===`, empty lines)
12- Use structured fields, not string interpolation
13- Single format: JSON to stdout
14
15```go
16// Wrong
17logger.Printf("WARNING: Failed to fetch %s", url)
18
19// Right
20slog.Warn("upstream fetch failed", "upstream", url, "error", err)
21```
22
23---
24
25## Setup
26
27cooked uses a single JSON handler writing to stdout:
28
29```go
30logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
31 Level: slog.LevelInfo,
32}))
33slog.SetDefault(logger)
34```
35
36No log streams, no OTel/ECS formats, no object storage — just structured JSON to stdout.
37
38---
39
40## Request Log Example
41
42Every request is logged with structured fields:
43
44```json
45{
46 "time": "2026-02-06T12:00:00Z",
47 "level": "INFO",
48 "msg": "request",
49 "method": "GET",
50 "path": "/https://cgit.internal/repo/plain/README.md",
51 "upstream": "https://cgit.internal/repo/plain/README.md",
52 "status": 200,
53 "cache": "hit",
54 "upstream_ms": 0,
55 "render_ms": 12,
56 "total_ms": 14,
57 "content_type": "markdown",
58 "bytes": 14832
59}
60```
61
62Cache field values: `hit`, `miss`, `revalidated`, `expired`.
63
64---
65
66## Log Message Style Guide
67
68| Rule | Guideline | Example |
69|------|-----------|---------|
70| Tense | Past for events, present for conditions | `upstream fetched`, `cache full` |
71| Case | lowercase start (no capital unless proper noun) | `config loaded`, `mermaid block found` |
72| Punctuation | No trailing period (fragments, not sentences) | `request completed` not `Request completed.` |
73| Redundancy | No severity prefix (level conveys this) | `upstream unreachable` not `Error: upstream unreachable` |
74| Specificity | State what failed, not generic failure | `upstream fetch failed` not `operation failed` |
75| Brevity | Omit articles and filler words | `file too large` not `The file was too large` |
76| Action | Use verb-noun or noun-verb pattern | `config loaded`, `invalid upstream url` |
77
78## Severity Levels
79
80| Level | When to use | Examples |
81|-------|-------------|----------|
82| ERROR | Unrecoverable failures requiring attention | `template parse failed`, `listen failed` |
83| WARN | Degraded but recoverable situations | `upstream returned 5xx`, `cache eviction failed` |
84| INFO | Normal business events | `request`, `server started`, `config loaded` |
85| DEBUG | Internal details for troubleshooting | `cache hit`, `rewriting relative url`, `mdx import stripped` |
86
87## Structured Fields
88
89- Use snake_case for field names: `upstream_ms`, `content_type`, `cache_status`
90- Suffix units: `_ms`, `_bytes`, `_count`
91- Boolean fields: `has_mermaid`, `has_toc`
92- Avoid embedding data in message string; use fields instead
93
94```go
95// Bad
96slog.Info(fmt.Sprintf("fetched %s in %dms", url, dur))
97
98// Good
99slog.Info("upstream fetched", "upstream", url, "duration_ms", dur)
100```