Skip to content

3.14.0.2

Choose a tag to compare

@github-actions github-actions released this 23 Jul 12:03

What's changed

Binding-only release. The native SDK is unchanged — still
dd-sdk-ios 3.14.0 — and so are the
package IDs, namespaces and every existing signature. Upgrading is a version bump.

The headline is OTSpan.GetTraceId, and it is worth stating why a getter warrants one: reading a
span's trace id from C# was not merely inconvenient, it was a trap that a consumer of these packages
fell into and shipped. Details below.

Package versions are <dd-sdk-ios version>.<binding revision>. 3.14.0.2 is dd-sdk-ios
3.14.0, binding revision 2. The fourth component belongs to this repository and advances when
the bindings or packaging change while the native binaries stay put.

Added

Span identity — OTSpan.GetTraceId / GetSpanId

OTSpanContext declares nothing but forEachBaggageItem. There is no traceID or spanID on the
protocol or on any bound type, and 3.x did not change that — so the ids can only be recovered by
injecting into a Datadog-format headers writer and reading what comes back.

That much is merely awkward. The trap is what you find when you look:

x-datadog-trace-id: 6096355397431041644          ← decimal, and only the LOW 64 bits
x-datadog-tags:     _dd.p.tid=6a61e4ff00000000   ← the HIGH 64, somewhere else entirely

A caller who reads the obvious header gets a decimal string naming half of an id that, rendered the
way Datadog renders it, looks nothing like it. DatadogNet 3.14.0.1 did exactly that and shipped
with iOS reporting 6096355397431041644 where Android reported
6a61e4ff000000002e430f579ece9a6c for the same span — which quietly broke RUM-resource-to-APM-trace
correlation on iOS, because _dd.trace_id is the only thing that links them.

var traceId = span.GetTraceId (DDTracer.Shared ());   // 32 lowercase hex, always
var spanId  = span.GetSpanId (DDTracer.Shared ());    // decimal

The rendering matches dd-sdk-android's own DatadogInterceptor, which is the reference
implementation of this correlation: DatadogTraceId.toHexString() for the trace id and
String.valueOf(long) for the span id. The asymmetry is Datadog's wire format rather than a choice.
toHexString() is toHexStringPadded(…, 32) on both the 128-bit and 64-bit implementations, so the
result is always 32 characters — with _dd.p.tid absent, the high half is genuinely zero and is
padded, not omitted.

Header injection — OTSpan.InjectHeaders

foreach (var header in span.InjectHeaders (DDTracer.Shared ()))
    request.Headers.TryAddWithoutValidation (header.Key, header.Value);

The bound API needs a dance that is not visible in the signatures: construct a writer, hand it to
Inject as though it were the carrier, then read the headers back off the writer. There is also
one writer type per format — unlike Android, where the formats are a property of the tracer and one
call writes all of them — so several formats means several round trips.

The format selector is a new OTHeaderFormats flags enum belonging to this repository, because the
bound DDTracingHeaderType is an Objective-C class of static singletons rather than an enum (Swift's
enum did not survive the projection) and so cannot be combined or compared. Defaults to Datadog plus
W3C trace context.

Span outcome — OTSpan.SetError / Log

span.SetError (exception);
span.Log (new Dictionary<string, object?> { ["event"] = "retry", ["attempt"] = 2 });

SetErrorWithKind takes kind, message and stack separately, and a caller who passes only the message
gets a span marked as an error with nothing in the APM error panel to act on.

RUM — single-value attributes and the session id

monitor.AddAttribute ("cart.id", cartId);
monitor.AddViewAttribute ("cart.items", 3);
monitor.AddFeatureFlagEvaluation ("new-checkout", true);

var sessionId = await monitor.GetCurrentSessionIdAsync ();

DatadogAttributes.ToNSObject (value, key) is now public, which is what these are built on.
AddAttributeForKey, AddViewAttributeForKey, AddFeatureFlagEvaluationWithName and
DDLogger.AddAttributeForKey all take a bare NSObject while DatadogAttributes only exposed the
dictionary form — so a caller either hand-wrapped the value, which is exactly what
DatadogAttributes exists to avoid, or round-tripped a one-element dictionary.

GetCurrentSessionIdAsync wraps CurrentSessionIDWithCompletion, which answers through a block on
the SDK's own queue.

Logs

DDLogger.AddAttribute (key, value), taking a plain value rather than an NSObject.

Documentation

The README gains an API coverage section, measured by diffing each framework's generated
-Swift.h against ApiDefinitions.cs rather than asserted:

Framework ObjC types Bound
DatadogRUM 377 377
DatadogLogs 16 16
DatadogCore 12 12
DatadogTrace 12 12
DatadogSessionReplay 4 4
DatadogInternal 2 2
DatadogCrashReporting 1 1
DatadogWebViewTracking 1 1

Member coverage is the same story — of 61 selectors and properties on DatadogCore, 59 are exported,
and the two that are not are init and new, removed on purpose by [DisableDefaultCtor].

What is missing is missing upstream, and the section now says so precisely rather than vaguely:

Swift types ObjC types
DatadogFlags 15 0
DatadogProfiling 2 0
OpenTelemetryApi no -Swift.h at all

Feature Flags leans on generics (FlagDetails<T>) and enums with associated values (AnyValue),
neither of which Swift projects into Objective-C. DatadogTrace is additionally 24 public Swift
types projected down to 12, and the casualty is OTelTracerProvider — so OpenTelemetry tracing is
unreachable even though OpenTracing is not.

That is also why DatadogNet.Flags.iOS and DatadogNet.Profiling.iOS ship frameworks with no
callable API.

New: a prototype for reaching the Swift-only frameworks

shims/DatadogFlagsObjc/ is a hand-written Swift @objc wrapper
around DatadogFlags. It compiles against the real 3.14.0 framework with zero warnings and emits
exactly the header Objective Sharpie consumes, so the rest of the path to a NuGet is the ordinary one
this repository already runs.

Two things make Flags tractable: Datadog already flattened the generics at the convenience layer
(getBooleanValue, getStringValue, getIntegerValue, getDoubleValue, getObjectValue and the
matching …Details), and FlagsClient.shared(named:in:) returns a real object to wrap.
AnyValue maps onto the Foundation object graph, which is what a C# caller wants anyway.

Nothing ships. It is not built, packaged, bound or referenced — see its README for what remains.
The standing cost is worth naming: the wrapper is ours, so when Datadog changes
FlagsClientProtocol it stops compiling, at build time rather than someone's runtime.

Tests

127 package-layout tests pass, and the on-simulator suite grows from 17 checks to 21, all
running against the packed packages.

Until now this suite enabled Trace and stopped there — EnablesTrace constructed the three header
writers and never started a span — so the tracing path was configured and never driven. That is the
gap the trace-id defect slipped through, and it is now closed:

  • A span is started, tagged, errored, logged and finished. Its ids are asserted by shape
    rather than for non-emptiness: 32 lowercase hex characters and not all zeros for the trace, decimal
    for the span. "Not empty" is what a caller checked before, and it is why the wrong rendering
    shipped.
  • Headers are injected in all three formats and cross-checked two ways: the id must equal the
    traceparent W3C value across all 128 bits — derived independently of GetTraceId, so a second
    opinion rather than a restatement — and must end with the low 64 bits the Datadog header carries in
    decimal. Selecting no formats must produce nothing rather than throwing.
  • The single-value attribute overloads are driven on RUM, view attributes, feature flags and a
    logger, and ToNSObject is asserted to map null to NSNull — because an explicitly empty
    attribute and an unset one are different things in a RUM event.
  • The session id is read through GetCurrentSessionIdAsync and asserted non-null; a null means
    the completion block never fired, which is the failure the Task wrapper exists to surface.

The harness gained async support to do the last of those, matching the shape the runner already
had on the façade side.

One thing the new checks turned up: Info(message, attributes) is generated without [NullAllowed]
on the dictionary, so passing null throws rather than reaching the SDK. Objective-C accepts nil
there. The single-argument overload and the ergonomic Log both handle it, so this is a papercut
rather than a gap — but it is real, and worth knowing before you hit it.

Sample

samples/DatadogNet.iOS.Example gains a Trace section, because it enabled Trace and never
demonstrated it. Two buttons: one traces an outgoing HttpRequestMessage end to end — start a span,
inject the headers a receiving service continues the trace from, report the status, finish — and one
records a failed span with SetError and Log. RUM gains a button showing the current session id.

Upgrading from 3.14.0.1

-<PackageReference Include="DatadogNet.RUM.iOS" Version="3.14.0.1" />
+<PackageReference Include="DatadogNet.RUM.iOS" Version="3.14.0.2" />

Nothing is removed or renamed, and the native xcframeworks are byte-for-byte the same build. All
packages move together, as they depend on each other at an exact version.

If you read span ids yourself, replace whatever you were doing with GetTraceId/GetSpanId and
check what you get: a decimal trace id, or one shorter than 32 characters, means the RUM-to-APM link
was not working.

Full changelog: v3.14.0.1...v3.14.0.2

Packages

Bound against dd-sdk-ios 3.14.0, targeting net8.0-ios18.0, net9.0-ios18.0 and net10.0-ios26.0.

The first three components of 3.14.0.2 are the dd-sdk-ios version; the fourth is this
repository's binding revision, which advances when the bindings or packaging change while
the native binaries stay put.

Package Wraps NuGet
DatadogNet.Core.iOS DatadogCore 3.14.0.2
DatadogNet.RUM.iOS DatadogRUM 3.14.0.2
DatadogNet.Logs.iOS DatadogLogs 3.14.0.2
DatadogNet.Trace.iOS DatadogTrace 3.14.0.2
DatadogNet.SessionReplay.iOS DatadogSessionReplay 3.14.0.2
DatadogNet.WebViewTracking.iOS DatadogWebViewTracking 3.14.0.2
DatadogNet.CrashReporting.iOS DatadogCrashReporting 3.14.0.2
DatadogNet.Flags.iOS DatadogFlags 3.14.0.2
DatadogNet.Profiling.iOS DatadogProfiling 3.14.0.2
DatadogNet.Internal.iOS DatadogInternal 3.14.0.2
DatadogNet.OpenTelemetryApi.iOS OpenTelemetryApi 3.14.0.2
DatadogNet.Objc.iOS (compatibility meta-package) 3.14.0.2
dotnet add package DatadogNet.Core.iOS --version 3.14.0.2
dotnet add package DatadogNet.RUM.iOS --version 3.14.0.2

Licensing

The binding code is MIT; the bundled native binaries are Apache-2.0, as built and
published by Datadog. Each package ships both texts under licenses/.