Datadog SDK for iOS and tvOS

repository·develop·Indexed 43 days ago

https://github.com/datadog/dd-sdk-ios

A Swift and Objective-C SDK for iOS and tvOS that enables observability through Log, Trace, and Real User Monitoring (RUM) collection. It includes automatic instrumentation for libraries such as Alamofire, Apollo GraphQL, SDWebImage, and OpenAPI Generator, and provides capabilities for tracking WebViews in RUM.

Tokens
29.9K
Snippets
67
Records
124
Agent score
77%

What's inside Datadog

  1. Understand Datadog iOS SDK performance impact

    develop

    The Datadog iOS SDK is designed to have minimal impact on host application performance. Benchmarks conducted using the open-source application Beam show that the SDK has no significant impact on the following metrics:

    • CPU Utilization: No significant difference in Max CPU (%) or High CPU Utilization periods.
    • Memory (RAM): No significant difference in Max RAM usage.
    • Battery: No significant increase in battery overhead or energy use due to networking.
    • Disk Usage: No significant impact on disk reads or writes.
    • App Launch Time: No significant difference in cold start times.

    Bundle Size Impact: Including the Datadog SDK increases the .ipa file size by approximately 1.4 MB (e.g., from 22.2 MB to 23.6 MB in benchmark tests).

  2. Datadog SDK for iOS and tvOS Overview

    develop
    The Datadog SDK for iOS and tvOS provides Swift and Objective-C libraries that allow developers to interact with Datadog services. It is primarily used for collecting observability data from mobile applications, including Logs, Traces, and Real User Monitoring (RUM) events.
  3. Understand what constitutes an API surface

    develop

    The API surface of a Swift module is the complete list of all symbols explicitly marked with the public access modifier. This includes classes, enums, cases, initializers, functions, and extensions that are visible to external modules.

    Symbols marked as internal or private are excluded from the API surface.

    import Foundation
    
    public class Car {
        public enum Manufacturer: String {
            case manufacturer1
            case manufacturer2
            case manufacturer3
        }
    
        private let engine = Engine()
    
        public init(
            manufacturer: Manufacturer
        ) {}
    
        public func startEngine() -> Bool { engine.start() }
        public func stopEngine() -> Bool { engine.stop() }
    }
    
    internal struct Engine {
        func start() -> Bool { true }
        func stop() -> Bool { true }
    }
    
    public extension Car {
        var price: Int { 100 }
    }

    Resulting API Surface:

    public class Car
     public enum Manufacturer: String
      case manufacturer1
      case manufacturer2
      case manufacturer3
     public init(manufacturer: Manufacturer)
     public func startEngine() -> Bool
     public func stopEngine() -> Bool
    public extension Car
     var price: Int
  4. How Logs, RUM, and Traces work together

    develop

    The Logs feature can automatically enrich log events with context from other Datadog features:

    • RUM Correlation: If bundleWithRumEnabled is true and a RUM session is active and sampled, logs will include application_id, session_id, view.id, and user_action.id.
    • Trace Correlation: If bundleWithTraceEnabled is true and an active span exists (via Tracer.shared() or OTelTracerProvider), logs will include dd.trace_id and dd.span_id.
    • Span Logs: Logs written via OTSpan.log(...) also flow through the Logs feature.
  5. Manage Proxy Lifetime by attaching to the value

    develop

    When a swizzled setter installs a proxy (e.g., for a weak delegate), attach the proxy to the value being set, not to the receiver.

    Why? If you attach the proxy to the receiver (e.g., the UIScrollView), the proxy stays alive as long as the receiver is alive. If the value (e.g., the delegate) is deallocated, the proxy outlives it. UIKit might then dispatch cached selectors to the still-alive proxy whose originalDelegate is now nil, causing an "unrecognized selector" crash.

    By associating the proxy with the value, the proxy's lifetime matches the value's lifetime. When the value is released, the proxy is released, and the receiver's weak reference naturally becomes nil.

    // WRONG — proxy outlives the delegate when delegate is released
    objc_setAssociatedObject(scrollView, &Self.proxyKey, proxy, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    
    // CORRECT — proxy lifetime = delegate lifetime
    objc_setAssociatedObject(delegate, &Self.proxyKey, proxy, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  6. How the Profiling Pipeline Works

    develop

    The Profiling pipeline enables continuous profiling by sampling application threads and correlating them with RUM data.

    1. Initialization: Call Profiling.enable(with:in:) after Datadog.initialize().
    2. Registration: ProfilerFeature registers as a remote feature and configures native app-launch flags.
    3. Sampling: The native Mach profiler samples threads and aggregates stack traces into pprof data.
    4. RUM Correlation: Profiling uses RUM context (session IDs) for deterministic sampling. RUM payloads provide metadata like TTID, operations, app hangs, and long tasks to correlate profiles with specific user actions.
    5. Upload: ProfilingHandler serializes a ProfileEvent along with wall.pprof and optional rum-mobile-events.json attachments, sending them via a multipart upload to /api/v2/profile.
  7. RFC Process for major changes

    develop

    Before making significant changes, you must inform an engineer and undergo an internal RFC (Request for Comments) process. This is required if your change:

    • Modifies the public API significantly.
    • Changes data collection behavior.
    • Affects initialization or lifecycle.
    • Introduces new configuration options.
    • Changes network request format or frequency.
  8. How to use multiple SDK instances in 2.0

    develop

    Version 2.0 allows multiple independent SDK instances within the same application process. This is useful for 3rd party libraries that need their own Datadog context.

    Workflow:

    1. Initialize a named instance using Datadog.initialize(..., instanceName: "<name>").
    2. Enable products (RUM, Logs, Trace) by passing the specific instance to the in: parameter.
    3. Retrieve the instance later using Datadog.sdkInstance(named: "<name>") to access its products.

    Note: The instanceName must remain consistent across application runs as storage paths are tied to the name.

    import DatadogCore
    import DatadogRUM
    import DatadogLogs
    import DatadogTrace
    
    // 1. Initialize a named core instance
    let core = Datadog.initialize(
        with: configuration, 
        trackingConsent: trackingConsent, 
        instanceName: "my-instance"
    )
    
    // 2. Enable products within that specific instance
    RUM.enable(with: RUM.Configuration(applicationID: "<ID>"), in: core)
    Logs.enable(in: core)
    Trace.enable(in: core)
    
    // 3. Retrieve the instance later
    let retrievedCore = Datadog.sdkInstance(named: "my-instance")
    let logger = Logger.create(in: retrievedCore)
    let monitor = RUMMonitor.shared(in: retrievedCore)
  9. Structure of feature documentation (`*_FEATURE.md`)

    develop

    The Datadog iOS SDK uses *_FEATURE.md files as LLM-optimized entry points for specific feature modules (e.g., RUM, Logs, Traces). These files provide quick navigation to source files, documented configuration options with working examples, troubleshooting patterns, and feature interactions.

    Each feature module contains its own file at its root, such as:

    • DatadogRUM/RUM_FEATURE.md
    • DatadogSessionReplay/SESSION_REPLAY_FEATURE.md
    • DatadogTrace/TRACE_FEATURE.md
    • DatadogProfiling/PROFILING_FEATURE.md
    • DatadogLogs/LOGS_FEATURE.md
    • DatadogWebViewTracking/WEBVIEW_FEATURE.md
  10. Understand the Datadog SDK Module Structure

    develop

    The SDK is organized as a modular monorepo. This structure ensures that feature modules (like Logs, Trace, or RUM) remain decoupled while sharing common logic through a central core.

    Core Modules:

    • DatadogInternal: Contains shared protocols and types (Foundation only, no external dependencies).
    • DatadogCore: Handles initialization, storage, and upload orchestration.

    Feature Modules:

    • DatadogLogs: Log collection.
    • DatadogTrace: Distributed tracing.
    • DatadogRUM: Real User Monitoring.
    • DatadogSessionReplay: Session replay capabilities.
    • DatadogCrashReporting: Crash reporting.
    • DatadogWebViewTracking: WebView telemetry.
    • DatadogFlags: Feature flags.
    • DatadogProfiling: Continuous profiling.

    Platform Support:

    • iOS 15.0+, tvOS 15.0+, macOS 12.6+, watchOS 9.0+ (limited), visionOS 1.0+ (limited).
  11. Migrate Logs and APM Trace configurations

    develop

    Logs

    • The Logs product no longer reports fatal errors. To enable Error Tracking for crashes, you must enable Crash Reporting in conjunction with RUM.
    • All Objective-C Logs APIs are now included in DatadogLogs.

    APM Trace

    • Trace sampling is now deterministic when used alongside RUM, utilizing the RUM session.id for consistency.
    • Trace.Configuration.URLSessionTracking.FirstPartyHostsTracing sets sampling for all requests by default, and the trace context is injected only into sampled requests. (A similar configuration exists in RUM.Configuration.URLSessionTracking.FirstPartyHostsTracing).
    • All Objective-C Trace APIs are now included in DatadogTrace.
  12. Required sections in feature documentation

    develop

    Feature documentation files must follow a specific structure to ensure consistency and usability. The core sections, in order, are:

    1. Overview: Purpose of the feature, platform availability (e.g., **Platform**: iOS, tvOS), and key dependencies.
    2. Quick Start Example: A complete, compilable Swift code snippet including initialization, all configuration options with working values, and inline comments.
    3. Architecture Overview (optional): High-level explanation of runtime components or message flows.
    4. Key Files: Relative paths to source files grouped by role (Entry Point, Configuration, Public API, Implementation).
    5. Configuration Categories: Grouped configuration options (e.g., Sampling, Privacy) with defaults and interaction explanations.
    6. Common Troubleshooting Patterns: Structured as Symptom → Causes → Solutions.
    7. Feature Interactions: Dependencies and integration points with other features.
    8. Additional Context: Behavioral notes, platform differences, and limitations.