Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/scripts/run-simulator-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ for package in objc core internal logs rum sessionreplay trace webviewtracking c
rm -rf "${HOME}/.nuget/packages/datadognet.${package}.ios/${VERSION}"
done

# The app's own intermediate output has to go too, not just the NuGet cache. The native payload
# is extracted out of the package into obj/ and copied into the .app, and neither step re-runs
# when the package version string is unchanged - so a rebuilt package of the same version leaves
# the *previous* build's xcframeworks embedded in the app. That produced a genuinely baffling
# failure once: the frameworks were present and loaded, but every Objective-C class lookup missed,
# because the app was running the previous release's binaries.
rm -rf "${REPO_ROOT}/tests/DatadogNet.iOS.DeviceTests/obj" \
"${REPO_ROOT}/tests/DatadogNet.iOS.DeviceTests/bin"

echo "==> building device tests (version=${VERSION}, tfm=${TARGET_FRAMEWORK}, sdk=${sdk_version})"
( cd "${SDK_DIR}" && dotnet build "${PROJECT}" \
--configuration Release \
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
DataDog-SDK-iOS-net repository versioned CrashReporter.iOS as 1.11.2.1 instead; that made it
impossible to tell which Datadog release a given CrashReporter package belonged to.
-->
<DatadogNativeVersion>2.17.0</DatadogNativeVersion>
<DatadogNativeVersion>2.30.2</DatadogNativeVersion>
<DatadogBindingRevision>1</DatadogBindingRevision>
<VersionPrefix>$(DatadogNativeVersion).$(DatadogBindingRevision)</VersionPrefix>

Expand Down
111 changes: 89 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ DDRUM.EnableWith(new DDRUMConfiguration(applicationID: "<RUM_APPLICATION_ID>"));
- [Installing](#installing)
- [Usage](#usage)
- [Convenience API](#convenience-api)
- [API coverage](#api-coverage)
- [Migrating from `DatadogCore.iOS` / `DatadogObjc.iOS`](#migrating-from-datadogcoreios--datadogobjcios)
- [How this repository works](#how-this-repository-works)
- [Building locally](#building-locally)
Expand All @@ -44,8 +45,8 @@ DDRUM.EnableWith(new DDRUMConfiguration(applicationID: "<RUM_APPLICATION_ID>"));
## Packages

Eleven packages, one per native framework in the Datadog release. Versions are
`<dd-sdk-ios version>.<binding revision>` — `2.17.0.1` is dd-sdk-ios **2.17.0**, binding revision
**1**. The fourth component belongs to this repository and advances when the bindings or packaging
`<dd-sdk-ios version>.<binding revision>` — `2.30.2.1` is dd-sdk-ios **2.30.2**, binding revision
**2**. The fourth component belongs to this repository and advances when the bindings or packaging
change while the native binaries stay put.

| Package | Wraps | Depends on | What it is for |
Expand All @@ -65,7 +66,7 @@ change while the native binaries stay put.
Most apps need one line:

```xml
<PackageReference Include="DatadogNet.Objc.iOS" Version="2.17.0.1" />
<PackageReference Include="DatadogNet.Objc.iOS" Version="2.30.2.1" />
```

Add `DatadogNet.CrashReporting.iOS` for crash reporting and `DatadogNet.WebViewTracking.iOS` for
Expand All @@ -87,7 +88,7 @@ OS-provided Swift runtime, which is only ABI-stable from 12.2.

```xml
<ItemGroup>
<PackageReference Include="DatadogNet.Objc.iOS" Version="2.17.0.1" />
<PackageReference Include="DatadogNet.Objc.iOS" Version="2.30.2.1" />
</ItemGroup>
```

Expand All @@ -100,7 +101,7 @@ Windows head does not try to restore them:

```xml
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
<PackageReference Include="DatadogNet.Objc.iOS" Version="2.17.0.1" />
<PackageReference Include="DatadogNet.Objc.iOS" Version="2.30.2.1" />
</ItemGroup>
```

Expand Down Expand Up @@ -206,18 +207,29 @@ helpers. Propagation style is chosen with `DDTracingHeaderType` — `Datadog`, `
### Session Replay

```csharp
DDSessionReplay.EnableWith(new DDSessionReplayConfiguration(replaySampleRate: 100)
{
DefaultPrivacyLevel = DDSessionReplayConfigurationPrivacyLevel.Mask,
});
using DatadogSessionReplay;

DDSessionReplay.EnableWith(new DDSessionReplayConfiguration(
replaySampleRate: 100,
textAndInputPrivacyLevel: DDTextAndInputPrivacyLevel.MaskAll,
imagePrivacyLevel: DDImagePrivacyLevel.MaskAll,
touchPrivacyLevel: DDTouchPrivacyLevel.Hide));
```

Requires RUM. Privacy levels are `Allow`, `MaskUserInput` and `Mask`; the level decides what is
redacted *on the device*, before anything is uploaded. Loosen it deliberately.
Requires RUM. The three levels decide what is redacted *on the device*, before anything is
uploaded, and the initializer requires all three so the choice is never left implicit. Loosen them
deliberately.

A single view can override the session-wide levels:

```csharp
var overrides = myView.GetDdSessionReplayPrivacyOverrides();
overrides.TextAndInputPrivacy = DDTextAndInputPrivacyLevelOverride.MaskAll;
overrides.Hide = new NSNumber(true);
```

> `DDSessionReplay` exists in two namespaces — `DatadogObjc` and `DatadogSessionReplay`. They are
> different native classes that do the same thing. Prefer the `DatadogObjc` one so a single
> `using DatadogObjc;` covers the whole SDK; importing both namespaces makes the name ambiguous.
Set `StartRecordingImmediately = false` to enable the feature without recording, then call
`DDSessionReplay.StartRecording()` once the user has consented, and `StopRecording()` to pause.

### Crash reporting

Expand Down Expand Up @@ -270,6 +282,7 @@ still there; nothing is hidden or renamed.
| six methods per level, plus an `NSError` you do not have | `logger.Log(level, message, exception?, attributes?)` |
| `DDDatadog.SetUserInfoWithId(id, null, null, empty)` | `DDDatadog.SetUserInfo(id)` |
| `DDTrackingConsent.Granted` (a class, not an enum) | `DDDatadog.SetTrackingConsent(TrackingConsent.Granted)` |
| `DDURLSessionInstrumentation.EnableWithConfiguration(...)` with a `Class` as a raw `IntPtr` | `DDURLSessionInstrumentation.Enable<MyDelegate>()` |

The view scope is the one worth adopting everywhere: the raw API is a `StartViewWithKey` /
`StopViewWithKey` pair matched by string key, and a view left open by an early return or an
Expand All @@ -279,6 +292,61 @@ Attribute values may be strings, any numeric type, `bool`, `DateTime`, `DateTime
enums, `NSObject`s, arrays, and nested dictionaries. Anything else throws `ArgumentException`
rather than being silently dropped.

### Redacting events before upload

Both RUM and Logs let you rewrite or drop events on the device, before anything is uploaded. This
is the supported way to keep PII out of Datadog.

```csharp
var logs = new DDLogsConfiguration(customEndpoint: null);
logs.SetEventMapper(logEvent =>
{
logEvent.Message = Redact(logEvent.Message);
return logEvent; // or return null to drop the event entirely
});
DDLogs.EnableWith(logs);
```

RUM has five equivalents — `SetViewEventMapper`, `SetActionEventMapper`, `SetResourceEventMapper`,
`SetErrorEventMapper` and `SetLongTaskEventMapper` — registered on `DDRUMConfiguration` before
`DDRUM.EnableWith`. Mappers can only be set at configuration time.

### Network instrumentation

```csharp
DDURLSessionInstrumentation.Enable<MySessionDelegate>();
```

`MySessionDelegate` must be an `NSObject` implementing `INSUrlSessionDataDelegate` and carrying a
`[Register]` attribute. The raw binding takes the delegate class as an `IntPtr`, which accepts
`IntPtr.Zero` happily and then instruments nothing; the generic form resolves the Objective-C class
and throws if it does not exist.

---

## API coverage

Every public Objective-C type Datadog declares for 2.30.2 is bound except one, and every documented
feature is reachable from C#. Measured by parsing the `-Swift.h` header each xcframework ships and
diffing it against the bound selectors — not by reading the documentation.

The unbound type is **`DDRUMLongTaskEventLongTaskScripts`**, with the `scripts` property that
returns it. It describes JavaScript long tasks, is reachable only from inside a RUM long-task event
mapper, and a native iOS app does not produce them.

Three further groups of *members* are deliberately not bound:

- **Four `DDTelemetryConfigurationEventTelemetryConfiguration` properties** named after the Session
Replay privacy levels. These are read-only fields on a *telemetry* event, describing what the SDK
reported about its own configuration; they are not knobs. The real ones are on
`DDSessionReplayConfiguration`, and all of those are bound.
- **`initWithSamplingRate:` and `initWithSamplingRate:injectEncoding:` on the tracing header
writers** — deprecated upstream, and differing from the bound `initWithSampleRate:` forms only by
argument label, which Objective Sharpie cannot project as two separate initializers.
- **Three `URLSession` overloads on `DatadogURLSessionDelegate`** — see
[the note in that binding](src/DatadogNet.Internal.iOS/ApiDefinitions.cs); binding them crashes
every consuming app at startup, and they remain callable under their inherited names.

---

## Migrating from `DatadogCore.iOS` / `DatadogObjc.iOS`
Expand All @@ -288,7 +356,7 @@ edit — no `using` directive and no call site changes.

```diff
-<PackageReference Include="DatadogObjc.iOS" Version="2.17.0.1" />
+<PackageReference Include="DatadogNet.Objc.iOS" Version="2.17.0.1" />
+<PackageReference Include="DatadogNet.Objc.iOS" Version="2.30.2.1" />
```

| Old | New |
Expand Down Expand Up @@ -317,7 +385,7 @@ selector is already registered on the member 'DidFinishCollectingMetrics'.
```

**`CrashReporter` is versioned with everything else.** It was `1.11.2.1`, tracking PLCrashReporter
upstream; it is now `2.17.0.1` like the rest, because it ships inside the same Datadog release and
upstream; it is now `2.30.2.1` like the rest, because it ships inside the same Datadog release and
the old numbering made it impossible to tell which Datadog build a given package belonged to.

**`net7.0-ios` is gone, `net9`/`net10` are new.** The old packages targeted `net7.0-ios16.1` and
Expand Down Expand Up @@ -382,15 +450,15 @@ sample directly, as the command in the previous section does.
Requires macOS, Xcode, and the .NET 9 and .NET 10 SDKs with the `ios` workload.

```bash
./build/FetchXcFrameworks.sh # ~116 MB, verified against build/checksums.txt
./build/FetchXcFrameworks.sh # ~100 MB, verified against build/checksums.txt
./build/BuildNugets.sh # packs all eleven into artifacts/
dotnet test tests/DatadogNet.iOS.PackageTests
```

Run the on-simulator smoke tests against the packed packages:

```bash
./.github/scripts/run-simulator-tests.sh 2.17.0.1 net9.0-ios18.0
./.github/scripts/run-simulator-tests.sh 2.30.2.1 net9.0-ios18.0
```

Build and run the sample:
Expand Down Expand Up @@ -436,7 +504,7 @@ dropping a package — update the `FRAMEWORKS` list, add or remove the binding p

## Releasing

Tag it. `v2.17.0.1` builds, tests, publishes all eleven packages to nuget.org via trusted
Tag it. `v2.30.2.1` builds, tests, publishes all eleven packages to nuget.org via trusted
publishing, and creates a GitHub release. The tag drives which native SDK is bound, so an older
line can be released by tagging it.

Expand All @@ -452,9 +520,8 @@ Curated notes in `docs/release-notes/<version>.md` replace the generated commit
common cause. Set `DDDatadog.VerbosityLevel = DDSDKVerbosityLevel.Debug` to see the SDK's own
diagnostics in the Xcode console.

**`DDSessionReplay` is an ambiguous reference.** You have imported both `DatadogObjc` and
`DatadogSessionReplay`. Use the `DatadogObjc` one, or alias:
`using SessionReplay = DatadogSessionReplay;`.
**`DDSessionReplay` could not be found.** It moved out of the `DatadogObjc` namespace in
dd-sdk-ios 2.19.0 and now lives only in `DatadogSessionReplay`. Add `using DatadogSessionReplay;`.

**`This version of .NET for iOS requires Xcode 26.0`.** Only affects `net10.0-ios26.0`. See
[Building locally](#building-locally).
Expand Down
28 changes: 24 additions & 4 deletions build/GenerateBindings.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,29 @@ set -e
# ./GenerateBindings.sh # every framework
# ./GenerateBindings.sh DatadogObjc # just one
#
# Requires Objective Sharpie, which is not on nuget.org and has to be installed separately:
# Requires Objective Sharpie, which is not on nuget.org and has to be obtained separately:
#
# https://aka.ms/objective-sharpie
#
# Set SHARPIE to point at the binary if it is not on PATH. The installer needs administrator
# rights, but the package can also just be expanded and run in place, which is enough:
#
# curl -fsSL -o sharpie.pkg https://aka.ms/objective-sharpie
# pkgutil --expand-full sharpie.pkg sharpie-x
# export SHARPIE="$PWD/sharpie-x/Framework.pkg/Payload/Library/Frameworks/ObjectiveSharpie.framework/Versions/*/bin/sharpie"
#
# KNOWN LIMITATION: Objective Sharpie 3.5.116 (the current release) bundles a clang that cannot
# parse the module maps in recent iOS SDKs. Against the 18.5 and 26.5 SDKs it fails with
#
# module '_stddef' requires feature 'found_incompatible_headers__check_search_paths'
# unknown type name 'size_t'
#
# before emitting anything. It also refuses to bind a framework whose recorded DTSDKName is not
# installed ("framework requires SDK 'iphoneos18.2'"), which is normal for a downloaded binary.
# Until sharpie ships a newer clang, the 2.30.2 binding delta was produced by reading the shipped
# -Swift.h headers directly and diffing them against the previous version's - see the release
# notes. Keep this script for the day it works again.
#
# IMPORTANT: the output is a starting point, not a drop-in replacement. It is written to
# ../Binding/ deliberately, *not* over the committed ApiDefinitions.cs files, because those carry
# fixes that regenerating would silently undo. Diff the two and port the real changes across.
Expand Down Expand Up @@ -42,8 +61,9 @@ ROOT="$(cd .. && pwd)"
LIBS="$ROOT/libs"
OUTPUT="$ROOT/Binding"

if ! command -v sharpie >/dev/null 2>&1; then
echo "error: sharpie is not installed - see https://aka.ms/objective-sharpie" >&2
SHARPIE="${SHARPIE:-sharpie}"
if ! command -v "$SHARPIE" >/dev/null 2>&1; then
echo "error: sharpie not found - set SHARPIE, or see https://aka.ms/objective-sharpie" >&2
exit 1
fi

Expand Down Expand Up @@ -86,7 +106,7 @@ for framework in $FRAMEWORKS; do
# -scope keeps the output to this framework's own headers: without it, a framework that
# imports another (all of them import DatadogInternal) has the imported API duplicated into
# its definitions, and the same type ends up bound in several packages.
sharpie bind \
"$SHARPIE" bind \
--output "$target" \
--namespace "$framework" \
--sdk "iphoneos$SDK" \
Expand Down
1 change: 1 addition & 0 deletions build/checksums.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
# Format: <dd-sdk-ios version> <sha256>

2.17.0 e948375d758527118550e5a5f89a332264309b19c96e3ec56e2bf84789b6f5f9
2.30.2 0f944182502801c7bf7e47ddc2a6a3be54fcc6067fb7f9ef8a495a86361baaa7
2 changes: 1 addition & 1 deletion docs/release-notes/2.17.0.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ there is no long-lived API key.

## Changed

**Packages are about 74 MB in total, down from what shipping every slice would cost.** The upstream
**The eleven packages come to about 80 MB in total, against roughly a gigabyte if every slice shipped.** The upstream
archive carries tvOS slices for every framework, macCatalyst/macOS/watchOS/visionOS for two of them,
and a full set of dSYMs — none of it reachable from a `net*-ios` binding, and all of it embedded
once per target framework. `FetchXcFrameworks.sh` strips to the two iOS slices and rewrites each
Expand Down
Loading
Loading