Type trace and span id signatures as Integer - #6124
Conversation
The trace/span id family was declared untyped in the RBS signatures even though SpanOperation's constructor already typed its id params Integer. Type the readers/ivars concretely: - SpanOperation#id / #parent_id / #trace_id: Integer - TraceOperation#id: Integer; #parent_span_id: Integer? - TraceSegment#id / #root_span_id: Integer? - TraceDigest#span_id / #trace_id: Integer? Signature-only change; no runtime code changes. Full steep check clean. SpanLink's ids are left untyped here: typing them makes steep infer its to_hash payload hash as Hash[Symbol, Integer], which conflicts with the heterogeneous values it also assigns; that needs a companion to_hash typing change and is deferred.
Typing analysisNote: Ignored files are excluded from the next sections. Untyped methodsThis PR introduces 2 untyped methods and 10 partially typed methods, and clears 14 untyped methods and 7 partially typed methods. It increases the percentage of typed methods from 69.55% to 69.93% (+0.38%). Untyped methods (+2-14)❌ Introduced:Partially typed methods (+10-7)❌ Introduced:Untyped other declarationsThis PR clears 13 untyped other declarations. It increases the percentage of typed other declarations from 84.88% to 85.51% (+0.63%). Untyped other declarations (+0-13)✅ Cleared:If you believe a method or an attribute is rightfully untyped or partially typed, you can add |
Corrects @trace_id ivar from String to Integer (it holds trace_id || 0), types Identifier#initialize span_id/trace_id params, the trace_id reader, and the format_trace_id/format_trace_id_128/identifier_from_digest signatures.
🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: c3d4cd8 | Docs | Datadog PR Page | Give us feedback! |
Types inject! digest params as TraceDigest? and extract returns as TraceDigest?, matching the _Propagator interface and the Datadog propagator, so digest.trace_id/span_id reads see the concrete Integer? id fields.
Reads parent.id/parent.trace_id (both Integer) when setting @parent_id/@trace_id.
Makes digest.span_id/trace_id reads resolve to the concrete Integer? id fields; SpanLink's own span_id/trace_id readers stay untyped (deferred by the base PR).
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep them coming! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
Pull request overview
This PR tightens the RBS signatures around trace/span identifier handling in Datadog::Tracing, making IDs consistently typed as Integer/Integer? across tracing utilities, core trace/span data structures, and distributed propagation helpers.
Changes:
- Types trace/span ID constants and generators in
Datadog::Tracing::Utils/TraceIdasInteger. - Types trace/span IDs on key tracing structures (
TraceOperation,TraceSegment,TraceDigest,SpanOperation) asInteger/Integer?. - Narrows distributed propagation and linking interfaces to return/accept
TraceDigestand parse IDs asInteger?.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| sig/datadog/tracing/utils.rbs | Types ID constants and ID generator methods as Integer and Random. |
| sig/datadog/tracing/trace_segment.rbs | Types trace segment id / root_span_id as Integer? and updates initializer accordingly. |
| sig/datadog/tracing/trace_operation.rbs | Types trace operation id as Integer and parent_span_id as Integer?. |
| sig/datadog/tracing/trace_digest.rbs | Types trace_id / span_id as Integer? and updates initializer accordingly. |
| sig/datadog/tracing/span_operation.rbs | Types span operation IDs as Integer and tightens the parent relationship types. |
| sig/datadog/tracing/span_link.rbs | Narrows SpanLink#initialize to accept a TraceDigest. |
| sig/datadog/tracing/distributed/trace_context.rbs | Types inject!/extract to accept/return TraceDigest? consistently. |
| sig/datadog/tracing/distributed/helpers.rbs | Types parse_decimal_id / parse_hex_id to return Integer?. |
| sig/datadog/tracing/distributed/b3_single.rbs | Types propagation inject!/extract around TraceDigest?. |
| sig/datadog/tracing/distributed/b3_multi.rbs | Types propagation inject!/extract around TraceDigest?. |
| sig/datadog/tracing/correlation.rbs | Types correlation identifier trace/span id-related fields (with one signature narrowness issue noted). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| attr_reader version: String | ||
| def initialize: (?env: untyped?, ?service: untyped?, ?span_id: untyped?, ?trace_id: untyped?, ?version: untyped?) -> void | ||
| def initialize: (?env: untyped?, ?service: untyped?, ?span_id: Integer?, ?trace_id: Integer?, ?version: untyped?) -> void |
There was a problem hiding this comment.
Good point on the runtime — #initialize did stringify through (span_id || 0).to_s, so it tolerated any #to_s-able value. That was incidental permissiveness rather than a supported contract, though: #initialize is @!visibility private, every construction site passes an Integer (TraceOperation#to_correlation → @active_span&.id; identifier_from_digest → TraceDigest#span_id), and a span id is an Integer everywhere else it's held (Span#id, SpanOperation#id, TraceDigest#span_id, Utils.next_id). No caller — and no spec — passes a String.
It's also the cross-tracer convention: the span id is modeled numerically, with strings appearing only at the log-output boundary.
| Tracer | span_id | trace_id | Surface |
|---|---|---|---|
| Java | String |
String |
CorrelationIdentifier.getSpanId()/getTraceId() |
| Python | str (from int) |
str |
get_log_correlation_context() -> dict[str, str] |
| JS | string |
string |
spanContext.toSpanId()/toTraceId() |
| .NET | ulong |
ulong |
CorrelationIdentifier.SpanId/TraceId |
| PHP | string (hex) |
string (hex) |
current_context(): array{span_id: string, …} |
| Go | uint64 |
string / uint64 |
SpanContext.SpanID()/TraceID()/TraceIDLower() |
| Ruby | Integer (rendered String for logs) |
Integer (rendered String for logs) |
Correlation::Identifier |
So rather than widen the type, I removed the storage-time .to_s so the value has one type end to end: @span_id is now Integer (matching @trace_id), and the String is produced only when rendering for logs — the #span_id/#trace_id readers, #to_h, and #to_log_format. The public #span_id reader still returns a String, so there's no observable change and the specs pass unchanged. Fixed in c3d4cd8.
The span id is an Integer everywhere it is held (Utils.next_id, Span#id, SpanOperation#id, TraceDigest#span_id) and every construction site of Correlation::Identifier passes an Integer (or nil); #initialize is @!visibility private, so no caller supplies a String. Storing it via (span_id || 0).to_s gave the one value two types (Integer at the param, String in the ivar/reader). Store @span_id as Integer (mirroring @trace_id) and render the String only at the log-output boundary: the #span_id reader now formats with #to_s, the same way #trace_id formats via format_trace_id. The public #span_id reader still returns a String, so there is no observable change; the existing specs pass unchanged.
BenchmarksBenchmark execution time: 2026-07-30 00:08:09 Comparing candidate commit c3d4cd8 in PR branch Found 0 performance improvements and 1 performance regressions! Performance is the same for 47 metrics, 1 unstable metrics.
|
Strech
left a comment
There was a problem hiding this comment.
I think it's a good step forward, but the method extraction altered the behavior in case of a nil input
| @env = env || Datadog.configuration.env | ||
| @service = service || Datadog.configuration.service | ||
| @span_id = (span_id || 0).to_s | ||
| @span_id = span_id || 0 |
There was a problem hiding this comment.
With that method, span_id will always return string, even with nil it will be "", so that leads to 0 fallback never happening and this is altering existing behavior.
There was a problem hiding this comment.
span_id here is the parameter, not the instance attribute, which I think is what you are referring to.
There was a problem hiding this comment.
Yeah, it's confusing because now we have a method that will be shadowed by this argument
def span_id
@span_id.to_s
endThere was a problem hiding this comment.
What is confusing about it? attr_reader does the exact same thing.
There was a problem hiding this comment.
Confusing part is that reader is not going to perform to_s on every read, now it will be
What does this PR do?
Types the trace/span id family in the RBS signatures.
The type is Integer. After LLM back and forth, the actual values appear to always be integers, and are converted to strings only when sent to agent. Hence, the type is integer and not string.
Motivation:
MOAR TYPES
Change log entry
None.
How to test the change?
Existing CI