Skip to content

feat(backend): log one wide event per request with the trace id - #153

Merged
lewisjared merged 8 commits into
mainfrom
feat/json-trace-logs
Oct 24, 2026
Merged

lewisjared merged 8 commits into
mainfrom
feat/json-trace-logs

Conversation

@lewisjared

@lewisjared lewisjared commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Adds one wide event per request and JSON logs carrying the trace id, following on from #150.

  • Outside ENVIRONMENT=local the API logs one compact JSON object per line, in the cr-service shape (ts, level, logger, message). Grafana links a Loki line to its Tempo trace and back by matching "trace_id":"…", so the separators stay compact. Local runs keep loguru's text line, with the bound fields appended as key=value pairs.
  • Every line logged while a request runs carries trace_id, span_id, request_id and route.
  • The cr-service WideEventMiddleware logs one http_request event per request with method, path, route, status, duration_ms, response size, client and user agent. The SQLAlchemy monitor adds db_queries and db_time_ms to it. route is the same template as the handler label in /metrics, bound before routing so it is set for 405s, docs and static files too, and null when nothing matches.
  • The wide event replaces uvicorn's access line and the per-request statistics dump, which printed every SQL statement.
  • LOG_LEVEL now applies to loguru's own logs. Deployed pods previously logged debug from climate_ref and the providers whatever it was set to. Two requests on the fixture database went from 223 lines to 45.
  • Every field is passed through cr_service.redact.redact_fields, as the cr-service formatter does, so secret-looking keys are masked.
  • scripts/perf-compare.sh keeps each run's benchmark storage inside the checkout. pytest-benchmark crashed formatting its machine_info warning for storage outside the working directory, which failed backend-perf on this branch with "No endpoint ran".
  • The stdlib intercept reports the real caller module, where it used to report logging, and keeps the record's extra fields.

Example wide event from the fixture database:

{"ts":"2026-10-24T21:31:05.991Z","level":"info","logger":"cr_service.middleware","message":"http_request","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736","span_id":"c2ec892fa406ca4a","request_id":"8fdd1766e25c449bb68d1a7b8bff6579","route":"/api/v1/executions/{group_id}","db_queries":1,"db_time_ms":0.04,"event":"http_request","method":"GET","path":"/api/v1/executions/999999","query":{},"status":404,"duration_ms":29.89,"response_bytes":32,"client_ip":"testclient","user_agent":"testclient","referer":null,"sentry_trace_id":"202a3b1f860c4f9e8f37d540ae5d0c7c"}

Kubernetes still probes /api/v1/utils/health-check/, so those hits log an info event until the probes move to /livez and /readyz, which log at debug.

Grafana links a Loki line to its Tempo trace by matching "trace_id" in a JSON body.
Uses the cr-service WideEventMiddleware, with the route template and query statistics bound onto it.
This replaces the uvicorn access line and the per-request statistics dump, and applies LOG_LEVEL to loguru.
@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 51 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 8ea864c0-2005-4f71-9bda-4282b4c6ea32
Commits

Reviewing files that changed from the base of the PR and between 98f043d and 7152f0c.

Files selected for processing: 3
  • backend/scripts/perf-compare.sh
  • backend/src/ref_backend/log.py
  • backend/tests/test_log.py
Walkthrough

The backend now emits structured API request logs with route, status, duration, database query and trace fields. It supports JSON output outside the local environment and formatted text output locally. Performance benchmark files now use a backend-local directory that the script removes during cleanup.

Changes: Structured request logging

Layer / File(s) Summary
Log context and output
backend/src/ref_backend/log.py, backend/src/ref_backend/main.py, backend/tests/test_log.py, changelog/153.improvement.md
Logging transfers standard record fields and request and trace context into Loguru records. It supports JSON output with exception details and formatted text output. The application enables JSON logs outside the local environment. Tests cover trace fields, exceptions and bound text fields.
Request and database context
backend/src/ref_backend/builder.py, backend/pyproject.toml, backend/tests/test_tracing.py
Middleware binds matched route names and database query statistics to request context. The app uses wide-event middleware, and request query actions run after successful or failed requests. Tests check request events for successful, rejected and failed requests. The backend adds prometheus-fastapi-instrumentator as a dependency.

Performance benchmark storage

Layer / File(s) Summary
Benchmark storage and cleanup
backend/scripts/perf-compare.sh, backend/.gitignore
The script stores benchmarks in backend/.perf-benchmarks and removes that directory during cleanup. Git ignores the directory.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant WideEventMiddleware
  participant BindRouteMiddleware
  participant RequestQueryMonitor
  participant API
  Client->>WideEventMiddleware: Send HTTP request
  WideEventMiddleware->>BindRouteMiddleware: Forward request
  BindRouteMiddleware->>RequestQueryMonitor: Forward request with matched route
  RequestQueryMonitor->>API: Handle request
  API-->>RequestQueryMonitor: Return response or raise error
  RequestQueryMonitor->>RequestQueryMonitor: Bind query statistics
  RequestQueryMonitor-->>WideEventMiddleware: Return response or propagate error
  WideEventMiddleware-->>Client: Emit request event and return response
Loading

Possibly related PRs

  • Climate-REF/ref-app#145: Introduced the performance comparison script that this change updates to use backend-local benchmark storage.

Priority: Low

Change: Feature

Merge Risk: Low · up to 98f04

Structured request logging works as described on the supported deployment path. However, extra fields attached to log records are no longer masked, so any credential-like value logged this way would appear in plain text. Adding redaction is a small fix worth making before merge. The benchmark script can also interfere with itself if two comparisons run at once in the same checkout.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 12.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 25 functions across 6 files. (3 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly describes the main change: one wide event per backend request that includes the trace ID. It is concise and specific.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 12.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 25 functions across 6 files. (3 skipped: 3 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lewisjared
lewisjared marked this pull request as ready for review October 24, 2026 21:41
* origin/main: (29 commits)
  chore(deps): update astral-sh/setup-uv action to v10.3.1
  fix: leave groups with a retracted latest execution out of the model pages
  fix: enable an execution's value tabs from its own values
  fix: keep obs-only groups in every era-scoped count
  docs: drop 'all' from the execution values value_type
  docs: add changelog for #147
  perf: fetch metric value facets apart from the values
  perf: count diagnostic groups without counting their executions
  refactor(api)!: build on climate-ref's query package with explicit filters
  refactor(api)!: remove the /diagnostics/facets endpoint
  refactor(api)!: drop the per-attempt execution counts from diagnostics
  refactor: build the last era filters from cmip_input_filter
  test: stop pytest collecting the test config helpers as tests
  fix: return an empty model for an unknown mip_era
  fix: treat a blank isolate_ids or exclude_ids as no filter
  refactor: read catalog resource usage from ResourcesReader.summaries
  refactor: scope values by era with or_no_model_input
  test: pin how mixed-era and obs-only executions are scoped
  refactor: resolve the mip_era query value in one place
  chore: use Session.get for execution outputs
  ...
pytest-benchmark crashes formatting its machine_info warning when the storage sits outside the working directory.
coderabbitai[bot]
coderabbitai Bot previously requested changes Oct 24, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


Review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 400b9bcf-6eb4-48eb-9394-93d84ecc7898
Commits

Reviewing files that changed from the base of the PR and between 2599c28 and 98f043d.

Files ignored due to path filters: 1
  • backend/uv.lock is excluded by !**/*.lock
Files selected for processing: 9
  • backend/.gitignore
  • backend/pyproject.toml
  • backend/scripts/perf-compare.sh
  • backend/src/ref_backend/builder.py
  • backend/src/ref_backend/log.py
  • backend/src/ref_backend/main.py
  • backend/tests/test_log.py
  • backend/tests/test_tracing.py
  • changelog/153.improvement.md

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread backend/scripts/perf-compare.sh Outdated
Comment thread backend/src/ref_backend/log.py Outdated
@lewisjared
lewisjared dismissed coderabbitai[bot]’s stale review October 24, 2026 22:49

Both CodeRabbit findings are fixed in 7152f0c (per-run benchmark storage, redacted log fields) and their threads are resolved.

@lewisjared
lewisjared merged commit e8ced97 into main Oct 24, 2026
6 checks passed
@lewisjared
lewisjared deleted the feat/json-trace-logs branch October 24, 2026 23:02
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.

1 participant