Files
dance-lessons-coach/AGENTS.md
Gabriel Radureau c17fb4f9b4
Some checks failed
CI/CD Pipeline / Build Docker Cache (push) Successful in 10s
CI/CD Pipeline / CI Pipeline (push) Failing after 4m14s
CI/CD Pipeline / Trigger Docker Push (push) Has been skipped
🐛 fix: emit all config-loading logs in correct JSON format from the start (#16)
## 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>
2026-04-12 23:28:35 +02:00

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
```