## Summary Closes #15 When `logging.json: true` (or `DLC_LOGGING_JSON=true`), the logger was unconditionally initialised to console/text format at the top of `LoadConfig()`, so early log lines — most visibly **"Config file loaded"** — were always written as human-readable text regardless of configuration. ## Root cause Classic chicken-and-egg: the format flag lives inside the config that is being loaded. The format-switch block only ran *after* `v.Unmarshal()`, too late for the config-file log. ## Changes ### `pkg/config/config.go` - Add `peekJSONLogging()`: resolves the JSON flag **before** any log is emitted by (1) checking `DLC_LOGGING_JSON` directly via `os.Getenv`, then (2) doing a minimal throwaway Viper pre-read of the config file for the `logging.json` key. This mirrors Viper's own priority order without parsing the full config twice. - Apply the resolved format immediately and emit **"Logging configured"** as the very first log line. - Remove the now-redundant format-switch block that ran after `Unmarshal()`. ### `scripts/start-server.sh`, `test-graceful-shutdown.sh`, `test-opentelemetry.sh` - Replace hardcoded `PROJECT_DIR` path with a dynamic `SCRIPTS_DIR=$(dirname $(realpath ${BASH_SOURCE[0]}))` derivation so scripts work from any worktree or clone location. ## Test plan - [x] `go test ./pkg/...` — all pass - [x] `scripts/test-graceful-shutdown.sh` — all JSON valid, all startup logs present - [x] Manual smoke test: first line is `{"level":"info",...,"message":"Logging configured"}`, every line is valid JSON Reviewed-on: #16 Co-authored-by: Gabriel Radureau <arcodange@gmail.com> Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
192 lines
5.7 KiB
Markdown
192 lines
5.7 KiB
Markdown
# dance-lessons-coach — Agent Documentation
|
|
|
|
AI agent reference for developing, testing, and operating the dance-lessons-coach service.
|
|
|
|
## Tech Stack
|
|
|
|
| Component | Technology | Version |
|
|
|-----------|------------|---------|
|
|
| Language | Go | 1.26.1 |
|
|
| Router | Chi | v5.2.5 |
|
|
| Logging | Zerolog | v1.35.0 |
|
|
| Configuration | Viper | v1.21.0 |
|
|
| Telemetry | OpenTelemetry | v1.43.0 |
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
dance-lessons-coach/
|
|
├── adr/ # Architecture Decision Records
|
|
├── cmd/
|
|
│ ├── greet/ # CLI application
|
|
│ └── server/ # Web server entry point
|
|
├── pkg/
|
|
│ ├── config/ # Viper-based configuration
|
|
│ ├── greet/ # Core domain logic + API handlers
|
|
│ ├── server/ # HTTP server, routing, graceful shutdown
|
|
│ ├── telemetry/ # OpenTelemetry instrumentation
|
|
│ ├── user/ # User domain (auth, JWT, repository)
|
|
│ └── validation/ # Request validation
|
|
├── scripts/ # Server lifecycle, build, test scripts
|
|
├── config.yaml # Configuration file
|
|
└── config.example.yaml # Configuration template
|
|
```
|
|
|
|
## Server Management
|
|
|
|
```bash
|
|
# Start / stop / restart
|
|
./scripts/start-server.sh start
|
|
./scripts/start-server.sh stop
|
|
./scripts/start-server.sh restart
|
|
|
|
# Status and logs
|
|
./scripts/start-server.sh status
|
|
./scripts/start-server.sh logs
|
|
|
|
# Test all API endpoints
|
|
./scripts/start-server.sh test
|
|
```
|
|
|
|
## Configuration
|
|
|
|
All settings can be provided via `config.yaml` or environment variables (`DLC_` prefix).
|
|
|
|
| Option | Env var | Default | Description |
|
|
|--------|---------|---------|-------------|
|
|
| Host | `DLC_SERVER_HOST` | `0.0.0.0` | Bind address |
|
|
| Port | `DLC_SERVER_PORT` | `8080` | Listening port |
|
|
| Shutdown timeout | `DLC_SHUTDOWN_TIMEOUT` | `30s` | Graceful shutdown window |
|
|
| JSON logging | `DLC_LOGGING_JSON` | `false` | Structured JSON output |
|
|
| Log output | `DLC_LOGGING_OUTPUT` | `""` | File path (empty = stderr) |
|
|
| API v2 | `DLC_API_V2_ENABLED` | `false` | Enable `/api/v2` routes |
|
|
| Config file | `DLC_CONFIG_FILE` | `./config.yaml` | Override config path |
|
|
|
|
Minimal `config.yaml`:
|
|
```yaml
|
|
server:
|
|
host: "0.0.0.0"
|
|
port: 8080
|
|
shutdown:
|
|
timeout: 30s
|
|
logging:
|
|
json: false
|
|
```
|
|
|
|
**Priority**: env var > config file > default.
|
|
|
|
## API Endpoints
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| GET | `/api/health` | Liveness — always `{"status":"healthy"}` |
|
|
| GET | `/api/ready` | Readiness — 200 when ready, 503 during shutdown |
|
|
| GET | `/api/version` | Version info (`?format=plain\|full\|json`) |
|
|
| GET | `/api/v1/greet/` | Default greeting |
|
|
| GET | `/api/v1/greet/{name}` | Personalized greeting |
|
|
| POST | `/api/v2/greet` | V2 greeting with validation (feature-flagged) |
|
|
| GET | `/swagger/` | Swagger UI |
|
|
| GET | `/swagger/doc.json` | OpenAPI spec |
|
|
|
|
```bash
|
|
curl http://localhost:8080/api/health
|
|
curl http://localhost:8080/api/ready
|
|
curl http://localhost:8080/api/v1/greet/Alice
|
|
curl -X POST http://localhost:8080/api/v2/greet \
|
|
-H "Content-Type: application/json" -d '{"name":"Alice"}'
|
|
```
|
|
|
|
## Testing
|
|
|
|
```bash
|
|
# Unit + integration tests
|
|
go test ./...
|
|
go test -v ./...
|
|
|
|
# Graceful shutdown + JSON logging validation
|
|
./scripts/test-graceful-shutdown.sh
|
|
|
|
# OpenTelemetry end-to-end
|
|
./scripts/test-opentelemetry.sh
|
|
```
|
|
|
|
**Note:** Do not call `go generate` unless editing API endpoint annotations.
|
|
When needed: `go generate ./pkg/server/`
|
|
|
|
## Build
|
|
|
|
```bash
|
|
./scripts/build.sh
|
|
# Produces: ./bin/server ./bin/greet
|
|
./bin/server --version
|
|
```
|
|
|
|
Build injects version, commit, and date via `-ldflags`.
|
|
|
|
## Graceful Shutdown
|
|
|
|
On `SIGTERM` / `SIGINT`:
|
|
1. Readiness context is cancelled → `/api/ready` returns 503.
|
|
2. 1-second propagation window (load balancer drains).
|
|
3. `srv.Shutdown()` waits up to `shutdown.timeout` for active requests.
|
|
4. Process exits cleanly.
|
|
|
|
Health endpoint stays 200 throughout; readiness endpoint goes 503 immediately on signal.
|
|
|
|
## OpenTelemetry / Jaeger
|
|
|
|
Enable in config or via env:
|
|
```bash
|
|
export DLC_TELEMETRY_ENABLED=true
|
|
export DLC_TELEMETRY_OTLP_ENDPOINT="localhost:4317"
|
|
```
|
|
|
|
Quick Jaeger setup:
|
|
```bash
|
|
docker run -d --name jaeger \
|
|
-e COLLECTOR_OTLP_ENABLED=true \
|
|
-p 16686:16686 -p 4317:4317 \
|
|
jaegertracing/all-in-one:latest
|
|
```
|
|
|
|
## Architecture Decision Records
|
|
|
|
| ADR | Decision |
|
|
|-----|----------|
|
|
| [0001](adr/0001-go-1.26.1-standard.md) | Go 1.26.1 |
|
|
| [0002](adr/0002-chi-router.md) | Chi router |
|
|
| [0003](adr/0003-zerolog-logging.md) | Zerolog |
|
|
| [0004](adr/0004-interface-based-design.md) | Interface-based design |
|
|
| [0005](adr/0005-graceful-shutdown.md) | Graceful shutdown |
|
|
| [0006](adr/0006-configuration-management.md) | Viper configuration |
|
|
| [0007](adr/0007-opentelemetry-integration.md) | OpenTelemetry |
|
|
| [0008](adr/0008-bdd-testing.md) | BDD with Godog |
|
|
| [0009](adr/0009-hybrid-testing-approach.md) | Hybrid testing strategy |
|
|
|
|
Add a new ADR: copy an existing file, edit it, update `adr/README.md`.
|
|
|
|
## Commit Conventions
|
|
|
|
[Conventional Commits](https://www.conventionalcommits.org) with optional [gitmoji](https://gitmoji.dev):
|
|
|
|
| Emoji | Type | When |
|
|
|-------|------|------|
|
|
| ✨ | `feat` | New feature |
|
|
| 🐛 | `fix` | Bug fix |
|
|
| 📝 | `docs` | Documentation |
|
|
| 🎨 | `style` | Formatting only |
|
|
| ♻️ | `refactor` | Structural change |
|
|
| 🚀 | `perf` | Performance |
|
|
| 🔒 | `security` | Security fix |
|
|
| 📦 | `chore` | Dependencies / build |
|
|
| 🧪 | `test` | Tests |
|
|
| 🤖 | `ci` | CI/CD |
|
|
| 🔥 | `remove` | Delete code/files |
|
|
|
|
Examples:
|
|
```
|
|
feat: add JWT authentication middleware
|
|
fix: ensure first log line is JSON when json logging is enabled
|
|
docs: rewrite AGENTS.md for clarity
|
|
```
|