Add structured tracing fields for debuggability - #83
Conversation
8958693 to
b46d326
Compare
|
Done, you can review now |
kkovaacs
left a comment
There was a problem hiding this comment.
Looks good to me!
Just a small nit related to one of the error events.
| ); | ||
| if let Err(e) = self.streamer_tx.try_send(StreamerMessage::RemoveSub((self.id, self.tag))) { | ||
| tracing::error!("Streamer remove sub control message sending error: {e}"); | ||
| tracing::error!(subscription_id = %self.id, tag = self.tag.as_u32(), "Streamer remove sub control message sending error: {e}"); |
There was a problem hiding this comment.
Would be cool to have the error as a field instead of formatting into the message.
| tracing::error!(subscription_id = %self.id, tag = self.tag.as_u32(), "Streamer remove sub control message sending error: {e}"); | |
| tracing::error!(subscription_id = %self.id, tag = self.tag.as_u32(), error=%e, "Streamer remove sub control message sending error"); |
WiktorStarczewski
left a comment
There was a problem hiding this comment.
Nice cleanup. Bonus: switching to skip(self, request) also kills the old full-request Debug dump that was landing in the spans, so it's a net privacy/cost win on top.
Two things worth surfacing up here (rest inline):
- Privacy: the subscription lifecycle logs leak
tagat info/warn/error, which goes against the PR's own "identifiers at debug only" rule. Lines flagged inline. - Merge order vs #84: you both touch
fetch_notes/fetch_notes_by_tagsinsqlite/mod.rs, and #84 makeslimita mandatory 3rd arg whilestreaming.rshere still callsfetch_noteswith 2 args. Whoever lands second has to reconcile by hand — keep both the tracing fields and thelimitwiring, and passNoneat the streaming call site. Not a bug here, just don't let the merge silently drop one side.
(Also confirmed cleanup_old_notes already returns u64 on the base branch, so notes_deleted is legit — no signature change.)
| // Remove non-responding subs | ||
| // Remove non-responding subs (backpressure) | ||
| for (sub_id, tag) in remove_subs { | ||
| tracing::warn!(subscription_id = %sub_id, tag = tag.as_u32(), reason = "backpressure", "Dropping subscription"); |
There was a problem hiding this comment.
tag at warn = leaked at the default log level, but the PR says identifiers are debug-only for privacy. Same thing in add_sub/remove_sub/Drop below. Drop tag here (keep subscription_id + reason + count) or move these to debug.
| let entry = self.tags.entry(sub.tag).or_insert_with(TagData::new); | ||
| entry.subs.insert(sub.id, sub.tx); | ||
| let active = self.tags.values().map(|td| td.subs.len()).sum::<usize>(); | ||
| tracing::info!(subscription_id = %sub.id, tag = sub.tag.as_u32(), active_subscriptions = active, "Subscription added"); |
There was a problem hiding this comment.
Same tag-at-info leak (also remove_sub L168 and Drop L288). subscription_id + active_subscriptions are fine at info — it's just tag that needs to go or drop to debug.
| self.tags.remove(&tag); | ||
| } | ||
| let active = self.tags.values().map(|td| td.subs.len()).sum::<usize>(); | ||
| tracing::info!(subscription_id = %sub_id, tag = tag.as_u32(), active_subscriptions = active, "Subscription removed"); |
There was a problem hiding this comment.
Minor: this "Subscription removed" plus Drop's "Subscription dropped" means every removal logs twice, and only one line carries the count while the other carries the reason. Probably cleaner as a single removal event.
| tag = self.tag.as_u32(), | ||
| duration_secs = duration_secs, | ||
| reason = "client_disconnect", | ||
| "Subscription dropped" |
There was a problem hiding this comment.
reason = "client_disconnect" fires unconditionally, but Drop also runs on backpressure/server eviction where the client didn't disconnect. Either drop the reason field or thread an evicted flag. (tag here is the same privacy nit.)
| let pnote = request_data.note.ok_or_else(|| Status::invalid_argument("Missing note"))?; | ||
|
|
||
| let timer = self.metrics.grpc_send_note_request((pnote.header.len() + pnote.details.len()) as u64); | ||
| let note_size = pnote.header.len() + pnote.details.len(); |
There was a problem hiding this comment.
Heads up: note_size here is header+details, but the rejection log below uses size = payload_size = details+metadata. Same handler, two different "size" numbers — mildly confusing when correlating accept vs reject.
b46d326 to
3522bb6
Compare
gRPC handlers: - send_note: note_size (info), note_id/tag (debug), warn on rejection - fetch_notes: tag_count/cursor/notes_returned/response_cursor (info), warn on too-many-tags rejection - stream_notes: subscription_id (info), tag/cursor (debug) Database layer: - store_note: note_id/tag (debug) - fetch_notes_by_tags: tag_count/cursor/notes_returned (info) - Legacy cursor reset promoted to info log - Removed redundant instrument from fetch_notes wrapper Maintenance: - Log notes_deleted count and retention_days per cleanup cycle Streaming lifecycle: - Subscription add/remove with active count - Backpressure drops with warn + reason - Client disconnect with subscription duration Design decisions: - Identifiers (note_id, tag) at debug level for privacy - Operational data (counts, cursors) at info level - Structured reason fields for rejections (filterable) Closes #82 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per review feedback (Wiktor, kkovaacs): - Privacy: stop logging note tag above debug level. Removed tag from the add_sub/remove_sub/backpressure/Drop lifecycle logs, per the PR's own 'identifiers at debug only' rule. subscription_id + active_subscriptions + reason remain at info. - Single removal event: consolidate the duplicate 'Subscription dropped' (Drop) and 'Subscription removed' (remove_sub) into one canonical event in remove_sub, carrying active count, duration, and a threaded reason. It only logs when a sub was actually present, so a backpressure eviction followed by a client-side Drop no longer double-logs or mislabels the reason. created_at moved into the manager's sub map so duration survives on that single event for every removal reason. - Error as field: the Drop send-failure log now records error=%e instead of interpolating it into the message.
3522bb6 to
c802419
Compare
* Release v0.4.1 Bump workspace version 0.4.0 -> 0.4.1 and add CHANGELOG entries for the changes merged since v0.4.0 (#80, #81, #83). Also dates the stale 'v0.4.0 (unreleased)' header (0.4.0 shipped 2026-06-08). * Drop [BREAKING] tag on OTel changelog entry The OTel change is a new way to configure the node, not an API break. Keep the note that the old env vars are no longer read. --------- Co-authored-by: Claude (Opus) <noreply@anthropic.com>
Summary
note_id,tag) at debug level for privacy (opt-in viaRUST_LOG)reasonfields for filteringWhat operators see at
RUST_LOG=info(default)send_note: note_sizefetch_notes: tag_count, cursor, notes_returned, response_cursorstream_notes: subscription_idreasonfield (note_too_large,too_many_tags,invalid_header)What operators see at
RUST_LOG=miden_note_transport=debugTest plan
make lintpassesmake testpasses (13/13)RUST_LOG=infoandRUST_LOG=miden_note_transport=debug🤖 Generated with Claude Code
Closes #82