Skip to content

feat(logging): add HTTP request instrumentation and correlation ID - #140

Open
Razano26 wants to merge 1 commit into
mainfrom
130-logging-add-http-request-instrumentation-and-correlation-id-propagation
Open

Razano26 wants to merge 1 commit into
mainfrom
130-logging-add-http-request-instrumentation-and-correlation-id-propagation

Conversation

@Razano26

@Razano26 Razano26 commented Feb 18, 2026 •

Copy link
Copy Markdown
Contributor

propagation

  • Add tower-http trace and request-id features to api/Cargo.toml
  • Wire SetRequestIdLayer, PropagateRequestIdLayer, and TraceLayer in app.rs
    • Each request gets a UUID x-request-id (generated if not provided by upstream)
    • http_request span carries request_id, method, uri
    • Response logs status and latency at info level
    • Failures log error and latency at error level
  • Add #[tracing::instrument] to all handlers in servers, channels, role, server_members, server_invitations, and friend modules
    • skip(state) on every handler to avoid logging service internals
    • fields(user_id = ...) on every authenticated handler for Loki correlation

Closes #130

Summary by CodeRabbit

  • Chores
    • Enhanced request tracing infrastructure with automatic request ID generation and propagation across the API.
    • Added structured logging to handler functions to record user context and request metadata.
    • Improved observability by capturing request method, URI, response status, and latency in logs.

propagation

- Add tower-http trace and request-id features to api/Cargo.toml
- Wire SetRequestIdLayer, PropagateRequestIdLayer, and TraceLayer in
  app.rs
  - Each request gets a UUID x-request-id (generated if not provided by
    upstream)
  - http_request span carries request_id, method, uri
  - Response logs status and latency at info level
  - Failures log error and latency at error level
- Add #[tracing::instrument] to all handlers in servers, channels, role,
  server_members, server_invitations, and friend modules
  - skip(state) on every handler to avoid logging service internals
  - fields(user_id = ...) on every authenticated handler for Loki
    correlation

Closes #130
Copilot AI review requested due to automatic review settings February 18, 2026 15:53
@coderabbitai

coderabbitai Bot commented Feb 18, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request implements HTTP request instrumentation and correlation ID propagation across the API. It adds tower-http dependencies for request ID generation and distributed tracing, wires middleware into the application router, and decorates handler functions with tracing instrumentation to capture user context and operation metadata.

Changes

Cohort / File(s) Summary
Dependencies
api/Cargo.toml
Added tower with "util" feature; extended tower-http features to include "trace" and "request-id" for HTTP instrumentation.
Middleware & Routing Setup
api/src/app.rs
Integrated SetRequestIdLayer, PropagateRequestIdLayer, and TraceLayer middleware; creates request-scoped spans with request_id, method, and URI; logs response status/latency and errors on failure.
Handler Instrumentation (Channels & Friends)
api/src/http/channels/handlers.rs, api/src/http/friend/handlers.rs, api/src/http/server_invitations/handlers.rs, api/src/http/server_members/handlers.rs, api/src/http/servers/handlers.rs
Added #[tracing::instrument] attributes to handler functions to capture user_id and enable distributed tracing; no changes to control flow or signatures.
Role Handler Enhancement
api/src/http/role/handlers.rs
Added Extension<UserIdentity> parameter to nine handler functions and paired each with #[tracing::instrument] attributes for user context and request tracing.

Sequence Diagram

sequenceDiagram
    actor Client
    participant Router as API Router
    participant RequestID as RequestID Middleware
    participant Trace as TraceLayer
    participant Handler as HTTP Handler
    participant Service as Service Layer
    
    Client->>Router: HTTP Request
    Router->>RequestID: Pass Request
    RequestID->>RequestID: Generate x-request-id
    RequestID->>Trace: Propagate x-request-id
    Trace->>Trace: Create Span<br/>(request_id, method, uri)
    Trace->>Handler: Invoke Handler
    Handler->>Handler: Emit #[instrument]<br/>(add user_id field)
    Handler->>Service: Process Business Logic
    Service-->>Handler: Result
    Handler-->>Trace: Return Response
    Trace->>Trace: Log Response<br/>(status, latency)
    Trace-->>Client: HTTP Response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Feat/role routing #108: Adds role route handlers and signatures that are now instrumented and receive UserIdentity context in this PR.
  • feat: list role from user and server #128: Introduces role endpoints (e.g., get_user_roles_in_server) that are extended with tracing and user identity wiring here.
  • Feat/implement authz #113: Modifies app.rs router and middleware setup; this PR layers additional tracing/request-id middleware on top of existing routing configuration.

Poem

🐰 Hops through logs with glee,
Request IDs flow so free,
Spans of light on every call,
Tracing magic through it all,
Now we'll find where errors hide,
With correlation as our guide! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: adding HTTP request instrumentation and correlation ID support to the API.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #130: tower-http dependencies added [#130], middleware layers wired in app.rs [#130], and tracing instrumentation applied to all affected handlers [#130].
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #130: dependency additions, middleware setup, and handler instrumentation. No unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 130-logging-add-http-request-instrumentation-and-correlation-id-propagation

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 and usage tips.

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/src/http/channels/handlers.rs`:
- Line 68: The tracing span for create_private_channel omits user_id because
_user_identity is skipped and no fields(...) is declared; fix by not skipping
_user_identity (use skip(state) instead of skip(state, _user_identity)) and add
a fields(user_id = ...) entry that records the authenticated user's id from
_user_identity (e.g., user_id = _user_identity.user_id displayed), or if you
prefer the skip_all pattern keep skip_all but still declare fields(user_id =
...) that reads the id from the request identity; update the
#[tracing::instrument(...)] attribute on create_private_channel accordingly.

In `@api/src/http/server_invitations/handlers.rs`:
- Line 68: The span on get_invitation currently skips _user_identity and doesn't
set a user_id field, so add a fields entry referencing the request's user id
like in create_private_channel; update the attribute on the get_invitation
handler from #[tracing::instrument(skip(state, _user_identity))] to include
fields(user_id = %_user_identity.user_id) (or rename the parameter to
user_identity and use %user_identity.user_id) so the span contains the user_id
for correlation.

In `@api/src/http/servers/handlers.rs`:
- Line 34: The tracing::instrument attribute on the handler currently only skips
state but will record full Debug of user_identity, Json(request) and other
extractors; change the attribute to skip_all (e.g., replace
#[tracing::instrument(skip(state), fields(user_id = %user_identity.user_id))]
with a skip_all variant) and keep only the explicit fields(...) you want logged
(user_id) so UserIdentity, request bodies (Json(request)) and other extractors
are not serialized into spans; apply this same pattern to all handlers that use
tracing::instrument to avoid leaking sensitive or verbose data.

(status = 500, description = "Internal server error")
)
)]
#[tracing::instrument(skip(state, _user_identity))]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing user_id field on create_private_channel breaks Loki correlation for this endpoint.

This is the only authenticated handler in the PR that omits the user_id field. The _user_identity is skipped and no fields(user_id = ...) is declared, so this endpoint's spans will lack user context — inconsistent with every other instrumented handler.

🔧 Proposed fix
-#[tracing::instrument(skip(state, _user_identity))]
+#[tracing::instrument(skip(state), fields(user_id = %_user_identity.user_id))]

Or if adopting skip_all as suggested elsewhere:

-#[tracing::instrument(skip(state, _user_identity))]
+#[tracing::instrument(skip_all, fields(user_id = %_user_identity.user_id))]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[tracing::instrument(skip(state, _user_identity))]
#[tracing::instrument(skip(state), fields(user_id = %_user_identity.user_id))]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/src/http/channels/handlers.rs` at line 68, The tracing span for
create_private_channel omits user_id because _user_identity is skipped and no
fields(...) is declared; fix by not skipping _user_identity (use skip(state)
instead of skip(state, _user_identity)) and add a fields(user_id = ...) entry
that records the authenticated user's id from _user_identity (e.g., user_id =
_user_identity.user_id displayed), or if you prefer the skip_all pattern keep
skip_all but still declare fields(user_id = ...) that reads the id from the
request identity; update the #[tracing::instrument(...)] attribute on
create_private_channel accordingly.

(status = 500, description = "Internal server error")
)
)]
#[tracing::instrument(skip(state, _user_identity))]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing user_id field on get_invitation — same inconsistency as create_private_channel.

_user_identity is skipped and no fields(user_id = ...) is set, so this span lacks user correlation.

🔧 Proposed fix
-#[tracing::instrument(skip(state, _user_identity))]
+#[tracing::instrument(skip(state), fields(user_id = %_user_identity.user_id))]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/src/http/server_invitations/handlers.rs` at line 68, The span on
get_invitation currently skips _user_identity and doesn't set a user_id field,
so add a fields entry referencing the request's user id like in
create_private_channel; update the attribute on the get_invitation handler from
#[tracing::instrument(skip(state, _user_identity))] to include fields(user_id =
%_user_identity.user_id) (or rename the parameter to user_identity and use
%user_identity.user_id) so the span contains the user_id for correlation.

(status = 500, description = "Internal server error")
)
)]
#[tracing::instrument(skip(state), fields(user_id = %user_identity.user_id))]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Consider skipping additional params to avoid logging sensitive or verbose data.

#[tracing::instrument] records all non-skipped parameters via their Debug impl. Currently only state is skipped, so user_identity (which may contain auth context beyond just the user_id), Json(request) bodies, and other extractors will be fully serialized into every span. Since user_id is already captured explicitly via fields(...), the full UserIdentity is redundant and potentially leaks auth internals.

This applies uniformly across all handler files in this PR — suggest using skip_all and relying solely on fields(...) for the data you want:

♻️ Example fix (apply pattern to all handlers)
-#[tracing::instrument(skip(state), fields(user_id = %user_identity.user_id))]
+#[tracing::instrument(skip_all, fields(user_id = %user_identity.user_id))]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[tracing::instrument(skip(state), fields(user_id = %user_identity.user_id))]
#[tracing::instrument(skip_all, fields(user_id = %user_identity.user_id))]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/src/http/servers/handlers.rs` at line 34, The tracing::instrument
attribute on the handler currently only skips state but will record full Debug
of user_identity, Json(request) and other extractors; change the attribute to
skip_all (e.g., replace #[tracing::instrument(skip(state), fields(user_id =
%user_identity.user_id))] with a skip_all variant) and keep only the explicit
fields(...) you want logged (user_id) so UserIdentity, request bodies
(Json(request)) and other extractors are not serialized into spans; apply this
same pattern to all handlers that use tracing::instrument to avoid leaking
sensitive or verbose data.

Copilot AI 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.

Pull request overview

This pull request adds comprehensive HTTP request instrumentation and correlation ID propagation to enable better observability and incident reconstruction in Loki. The changes implement request-level tracing using tower-http middleware and handler-level spans using the tracing crate's instrument macro.

Changes:

  • Added tower-http middleware stack (SetRequestIdLayer, PropagateRequestIdLayer, TraceLayer) to generate and propagate x-request-id headers and create http_request spans with request_id, method, and uri fields
  • Instrumented all HTTP handlers across servers, channels, roles, server_members, server_invitations, and friends modules with #[tracing::instrument] attributes that capture user_id for authenticated endpoints
  • Updated dependencies to include tower 0.5 and enabled trace/request-id features on tower-http 0.6.6

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
api/Cargo.toml Added tower dependency and enabled trace/request-id features for tower-http
Cargo.lock Updated dependency resolution for tower and tower-http with new features
api/src/app.rs Added middleware stack with SetRequestIdLayer, PropagateRequestIdLayer, and TraceLayer to generate UUID request IDs, propagate them to responses, and create http_request spans with logging for responses and failures
api/src/http/servers/handlers.rs Added #[tracing::instrument] to all 7 handlers with skip(state) and user_id field extraction
api/src/http/channels/handlers.rs Added #[tracing::instrument] to all 6 handlers with skip(state) and user_id field extraction (or skip _user_identity where appropriate)
api/src/http/role/handlers.rs Added #[tracing::instrument] to all 9 handlers with skip(state) and user_id field extraction, including skipping request body in update_role
api/src/http/server_members/handlers.rs Added #[tracing::instrument] to all 4 handlers with skip(state) and user_id field extraction
api/src/http/server_invitations/handlers.rs Added #[tracing::instrument] to all 3 handlers with skip(state) and user_id field extraction (or skip _user_identity where appropriate)
api/src/http/friend/handlers.rs Added #[tracing::instrument] to all 8 handlers with skip(state) and user_id field extraction

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

(status = 500, description = "Internal server error")
)
)]
#[tracing::instrument(skip(state, request), fields(user_id = %user_identity.user_id))]

Copilot AI Feb 18, 2026

Copy link

Choose a reason for hiding this comment

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

The instrumentation here skips the request parameter to avoid logging potentially large or sensitive JSON payloads. However, this pattern is not consistently applied across all handlers that accept JSON bodies. For consistency and to prevent logging sensitive data, all handlers that accept JSON request bodies should skip them in their instrumentation.

Affected handlers that should also skip their JSON request parameters:

  • create_server in servers/handlers.rs (line 38)
  • update_server in servers/handlers.rs (line 159)
  • create_server_channel in channels/handlers.rs (line 41)
  • create_private_channel in channels/handlers.rs (line 72)
  • update_channel in channels/handlers.rs (line 160)
  • create_role in role/handlers.rs (line 48)
  • create_invitation in server_invitations/handlers.rs (line 42)
  • update_member in server_members/handlers.rs (line 142)
  • create_friend_request in friend/handlers.rs (line 173)
  • accept_friend_request in friend/handlers.rs (line 200)
  • decline_friend_request in friend/handlers.rs (line 226)

For example, these should use patterns like:

  • #[tracing::instrument(skip(state, request), fields(user_id = %user_identity.user_id))]
  • #[tracing::instrument(skip(state, input), fields(user_id = %user_identity.user_id))]

Copilot uses AI. Check for mistakes.
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.

logging: add HTTP request instrumentation and correlation ID propagation

2 participants