feat: add structured JSON logging and request observability#83
Open
woydarko wants to merge 4 commits intokodykebab:mainfrom
Open
feat: add structured JSON logging and request observability#83woydarko wants to merge 4 commits intokodykebab:mainfrom
woydarko wants to merge 4 commits intokodykebab:mainfrom
Conversation
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 viaContextVar.configure_logging(level, json_output)— call once at startup:LOG_LEVELenv var (default:INFO)LOG_FORMATregister_request_logging(app)— Flask before/after request middleware:request_idper request (or readsX-Request-IDheader for distributed tracing)request_idinContextVarfor thread-safe access across loggersmethod,path,remote_addr,content_lengthstatus_code,duration_msX-Request-IDheader to all responsesserver/start.pyAdded
configure_logging()andregister_request_logging(app)at startup. RespectsLOG_LEVELandLOG_FORMATenv vars.server/tests/conftest.pyExtended
restore_stubsfixture to save/restore root logger state between tests soconfigure_logging()calls don't contaminate other tests.server/tests/test_logging_config.py(new)21 tests covering all components.
Results