Skip to content

feat: add structured JSON logging and request observability#83

Open
woydarko wants to merge 4 commits intokodykebab:mainfrom
woydarko:feat/structured-logging
Open

feat: add structured JSON logging and request observability#83
woydarko wants to merge 4 commits intokodykebab:mainfrom
woydarko:feat/structured-logging

Conversation

@woydarko
Copy link
Copy Markdown
Contributor

Summary

Closes #60

Adds a structured JSON logging system with per-request correlation IDs, making it easy to trace issues across requests in production.

What was added

server/utils/logging_config.py (new)

JsonFormatter — formats every log record as single-line JSON:

{"timestamp": "2026-04-19T...", "level": "INFO", "logger": "calliope.request", "message": "Request completed", "request_id": "uuid", "status_code": 200, "duration_ms": 42.3}

Includes extra fields (user_id, duration_ms, etc.), exception tracebacks, and is thread-safe via ContextVar.

configure_logging(level, json_output) — call once at startup:

  • Sets root log level from LOG_LEVEL env var (default: INFO)
  • Switches between JSON (production) and plain text (development) via LOG_FORMAT
  • Silences noisy third-party loggers (werkzeug, sqlalchemy, urllib3)

register_request_logging(app) — Flask before/after request middleware:

  • Generates UUID request_id per request (or reads X-Request-ID header for distributed tracing)
  • Stores request_id in ContextVar for thread-safe access across loggers
  • Logs request start: method, path, remote_addr, content_length
  • Logs request completion: status_code, duration_ms
  • Warns on slow requests (>500ms) at WARNING level
  • Adds X-Request-ID header to all responses
  • Handles unhandled exceptions with structured error logs

server/start.py

Added configure_logging() and register_request_logging(app) at startup. Respects LOG_LEVEL and LOG_FORMAT env vars.

server/tests/conftest.py

Extended restore_stubs fixture to save/restore root logger state between tests so configure_logging() calls don't contaminate other tests.

server/tests/test_logging_config.py (new)

21 tests covering all components.

Results

Tests: 132 passed, 0 failed (21 new + all existing)

Closes kodykebab#48

## What was added

### server/utils/contract_templates.py (new)
Template registry and generator with 4 starter templates:
- hello_world: minimal greeting contract (beginner)
- token: fungible token with initialize/transfer/balance (intermediate)
- nft: non-fungible token with mint/transfer/owner_of/token_uri (intermediate)
- governance: DAO proposal + voting with quorum threshold (advanced)

Functions: list_templates(), get_template(id), generate_template(id, path, name)
Each template generates: Cargo.toml, src/lib.rs, README.md

### server/routes/template_routes.py (new)
Three endpoints registered under /api/templates/:

GET /api/templates
- Lists all 4 templates with id, name, description, difficulty, tags

GET /api/templates/<template_id>
- Returns metadata for one template, 404 with available list if not found

POST /api/templates/generate
- Validates session ownership and resolves instance_dir
- Validates project_name with regex (alphanumeric, hyphens, underscores)
- Blocks path traversal
- Generates template files into session workspace
- Returns files_created list and project_path

### server/start.py
Registered templates_bp blueprint

### server/tests/test_contract_templates.py (new)
28 tests covering all components:
- list_templates: count, required fields, expected IDs
- get_template: valid/invalid IDs, all templates retrievable
- generate_template: all 4 templates, Cargo.toml content, src/lib.rs has soroban_sdk,
  README mentions template name, raises for unknown template, raises if path exists,
  files_created list correct
- GET /api/templates: 200, returns 4, required fields
- GET /api/templates/<id>: 200 valid, 404 unknown, available list on 404
- POST /api/templates/generate: missing fields, session not found,
  path traversal blocked, invalid project_name, unknown template 404,
  successful generation with files on disk

## Results
Tests: 88 passed, 0 failed (full suite)
Closes kodykebab#60

## What was added

### server/utils/logging_config.py (new)
Structured logging system with 3 components:

JsonFormatter — formats every log record as single-line JSON with:
  timestamp (ISO-8601 UTC), level, logger, message, request_id,
  extra fields (user_id, duration_ms, etc.), exception tracebacks

configure_logging(level, json_output) — call once at startup to:
  - Set root log level from LOG_LEVEL env var
  - Switch between JSON (production) and plain text (development)
  - Silence noisy third-party loggers (werkzeug, sqlalchemy, urllib3)

register_request_logging(app) — Flask middleware that:
  - Generates UUID request_id per request (or reads X-Request-ID header)
  - Stores request_id in ContextVar for thread-safe access
  - Logs request start: method, path, remote_addr, content_length
  - Logs request completion: status_code, duration_ms
  - Warns on slow requests (>500ms)
  - Adds X-Request-ID header to all responses
  - Handles unhandled exceptions with structured error logs

### server/start.py
Added configure_logging() and register_request_logging() calls at startup.
Respects LOG_LEVEL (default: INFO) and LOG_FORMAT (default: json) env vars.

### server/tests/conftest.py
Extended restore_stubs fixture to also save/restore root logger state
so configure_logging() calls in tests don't contaminate other tests.

### server/tests/test_logging_config.py (new)
21 tests:
- get/set request_id: default, set, uuid
- JsonFormatter: valid JSON, required fields, message, level, logger,
  ISO timestamp, request_id injection, extra fields, exception traceback, single-line
- configure_logging: INFO/DEBUG levels, JSON vs plain formatter
- Flask middleware: X-Request-ID header, UUID format, custom ID propagated, 200 response

## Results
Tests: 132 passed, 0 failed (21 new + all existing)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DevOps] Add structured logging and system observability

1 participant