Logging¶
Orion uses structured logging across all services for consistent, queryable log output.
Logging Stack¶
| Service Type | Library | Format |
|---|---|---|
| Go (Gateway, CLI) | slog (stdlib) |
JSON |
| Python (all services) | structlog |
JSON |
Go Logging (slog)¶
The gateway uses Go's built-in slog package for structured logging.
Request Logging Middleware¶
Every HTTP request is logged with:
| Field | Type | Description |
|---|---|---|
method |
string | HTTP method |
path |
string | Request path |
status |
int | Response status code |
duration_ms |
float | Request duration in milliseconds |
request_id |
string | Unique request ID (X-Request-ID) |
Example log entry:
{
"time": "2024-03-12T10:30:00Z",
"level": "INFO",
"msg": "request completed",
"method": "GET",
"path": "/api/v1/scout/api/v1/trends",
"status": 200,
"duration_ms": 45.2,
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
Log Levels¶
| Level | Usage |
|---|---|
DEBUG |
Development diagnostics |
INFO |
Normal operations (request completed, service started) |
WARN |
Recoverable issues (rate limit hit, retry) |
ERROR |
Failures requiring attention |
Python Logging (structlog)¶
All Python services use structlog for structured logging.
Configuration¶
Logging is configured in libs/orion-common/:
import structlog
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.StackInfoRenderer(),
structlog.dev.set_exc_info,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.JSONRenderer(),
],
)
Usage in Services¶
import structlog
logger = structlog.get_logger(__name__)
async def handle_trend(payload: dict) -> None:
await logger.ainfo(
"trend_detected",
trend_id=payload["trend_id"],
topic=payload["topic"],
score=payload["score"],
)
Example log entry:
{
"event": "trend_detected",
"trend_id": "550e8400-e29b-41d4-a716-446655440000",
"topic": "AI agents in production",
"score": 0.87,
"level": "info",
"timestamp": "2024-03-12T10:30:00.000000Z",
"logger": "src.service.trend_service"
}
Contextual Logging¶
Bind context variables for the duration of a request:
structlog.contextvars.clear_contextvars()
structlog.contextvars.bind_contextvars(
request_id=request.headers.get("X-Request-ID"),
user_id=current_user.id,
)
All subsequent log calls in the request will include request_id and user_id.
Viewing Logs¶
Docker Compose¶
# All services
docker compose -f deploy/docker-compose.yml logs -f
# Specific service
docker compose -f deploy/docker-compose.yml logs -f scout
# Filter by level (using jq)
docker compose -f deploy/docker-compose.yml logs scout 2>&1 \
| jq -r 'select(.level == "error")'
Correlating Requests¶
Use the X-Request-ID header to trace a request across services:
# Find all logs for a specific request
docker compose -f deploy/docker-compose.yml logs 2>&1 \
| jq -r 'select(.request_id == "550e8400-e29b-41d4-a716-446655440000")'
Log Aggregation¶
For production, consider forwarding logs to a centralized system:
| Solution | Integration |
|---|---|
| Loki + Grafana | Docker logging driver or Promtail sidecar |
| ELK Stack | Filebeat sidecar |
| CloudWatch | AWS logging driver |
| Datadog | Datadog agent |
Structured JSON is key
Both slog and structlog output JSON by default, making logs easily parseable by any log aggregation system.