Skip to content

SNOW-4008939 sf_core: keep the telemetry raw-log message a JSON object so message:data stays readable - #1343

Closed
sfc-gh-merbel wants to merge 1 commit into
mainfrom
snow-4008939-telemetry-log-object-shape
Closed

SNOW-4008939 sf_core: keep the telemetry raw-log message a JSON object so message:data stays readable#1343
sfc-gh-merbel wants to merge 1 commit into
mainfrom
snow-4008939-telemetry-log-object-shape

Conversation

@sfc-gh-merbel

@sfc-gh-merbel sfc-gh-merbel commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

This changes the raw-log lane in sf_core/src/telemetry/session_telemetry.rs so that the message field of a /telemetry/send log entry is always a JSON object. RawLogEntry::into_log_entries previously parsed the caller's message_json into any serde_json::Value and embedded whatever came out; it now passes objects through untouched and nests anything else under a message key. No API, wire-envelope or proto change is involved, and for every caller that already sends an object — which is all of them today — the emitted payload is byte-identical.

The motivation is SNOW-4008939. TelemetrySendLog accepts message_json as a string, and nothing required the parsed value to be an object, so a caller sending a scalar or an array parsed successfully and produced an envelope with a non-object message. That matters because downstream consumers read payload fields as message:data:<field> against a VARIANT: against a scalar or an array, that expression yields NULL rather than an error. A misbehaving producer therefore creates rows that look present but carry no readable payload, and nothing on either side reports a problem. The severity is low — it takes a misbehaving caller, and the damage is confined to that caller's own rows — but the failure is silent in both directions, which makes it far cheaper to close now than to diagnose from a dashboard later.

Of the two fixes the ticket suggested, rejecting the entry or wrapping it, this takes the wrapping route. This lane already has a stated rule for bad input, in the doc comment on the function being changed: one bad entry must never sink the whole batch, but it also should not silently vanish. Rejecting would satisfy the first half and violate the second, and it would do so without any way to tell the caller, since telemetry_send_log has no error channel back to the wrapper — it returns an empty TelemetrySendResponse unconditionally. Wrapping keeps the payload, makes the envelope shape predictable, and leaves the well-behaved path allocation-for-allocation identical. Worth flagging for review: the malformed-JSON case, which the ticket treated as already handled, produced Value::String(raw) and so was affected by exactly the same problem — a JSON string is not an object either. It now takes the same wrapping path, and that is the one behaviour change an existing caller could observe. The two cases are logged distinguishably at debug level so a misbehaving producer can still be identified.

The risk is small and concentrated in that one behaviour change. There is no Java or JDBC caller of this RPC today; the wired consumers are the Rust core and Python, and neither sends a non-object message, so nothing shipping changes shape. Objects gain no extra nesting level, so existing field paths are untouched. Anyone who was relying on a malformed entry arriving as a bare string would now find it at message.message, which is a deliberate trade: that reading was already unusable through the documented message:data access path. The change adds no allocation on the object path and no new dependency.

Testing is at the unit level, alongside the existing cases in the same module. raw_log_entry_nests_non_object_json_under_message covers a number, an array, a string, a boolean and null, asserting both that message is an object and that the original value survives at message.message. raw_log_entry_does_not_nest_an_object_message pins the no-regression case, asserting that an object gains no wrapper key. The pre-existing malformed-JSON test was updated to the new expectation rather than deleted, so the fallback stays covered. All 12 unit tests in telemetry::session_telemetry pass, as do all 35 tests in the telemetry integration module, which exercise the real wire body through wiremock and would have caught an envelope-shape regression. cargo fmt and cargo clippy -p sf_core --lib are both clean, the only clippy output being a pre-existing deprecation warning elsewhere in the crate.

One note for the reviewer on provenance: the ticket cites origin/main at 4846f3466, which is not an ancestor of main and could not be resolved in this clone, so this work is based on aac98014b instead. The code in question is unchanged in substance between the two descriptions. Separately, the ticket records two adjacent behaviours that were investigated and deliberately not changed here — key reordering from serde_json's BTreeMap backing, which is harmless because VARIANT field access is order-insensitive, and integer precision, where an earlier claim of degradation above 2^53 was measured and found to be wrong. A related but independent issue in this lane, the absence of any per-call flush, is tracked separately as SNOW-4008940 and is not touched by this PR.

Made with Cursor

`TelemetrySendLog` takes `message_json` as a string, and the raw-log lane
parsed it into any `serde_json::Value` before embedding it as the envelope's
`message`. Nothing required an object, so a caller sending a scalar or an
array parsed successfully and produced an envelope with a non-object message.

Consumers read payload fields as `message:data:<field>` against a VARIANT.
Against a scalar or array that reads NULL rather than failing, so such a
caller creates rows that look present but carry no readable payload, and
nothing on either side reports a problem.

Normalise instead of rejecting: a non-object parse is nested under `message`
rather than dropped, which keeps the envelope shape predictable while honouring
this lane's existing rule that one bad entry must neither sink the batch nor
silently vanish. The already-handled malformed-JSON case produced a JSON string
and so was affected by the same problem; it now takes the same path, which is
the one behaviour change visible to an existing caller.

Objects — every real caller — are passed through untouched, so well-behaved
field paths are unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sfc-gh-merbel

Copy link
Copy Markdown
Contributor Author

Closing: this was opened against the wrong repository. Universal Driver work belongs in the EMU org, snowflake-eng/drivers, where active development happens. Reopened there as a draft: https://github.com/snowflake-eng/drivers/pull/1399

@sfc-gh-merbel
sfc-gh-merbel deleted the snow-4008939-telemetry-log-object-shape branch August 25, 2026 12:56
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