Type to search.

Manual instrumentation using EDOT iOS

Serverless Observability

Elastic Cloud Serverless projects are autoscaled environments, fully managed by Elastic and available on Elastic Cloud.

Availability

  • Generally available

    This functionality is generally available and ready for production usage.

Stack

The Elastic Stack includes Elastic's core products such as Elasticsearch, Kibana, Logstash, and Beats.

Availability

  • Generally available

    This functionality is generally available and ready for production usage.

Unless stated otherwise on the page, this functionality is available when your Elastic Stack is deployed on Elastic Cloud Hosted, Elastic Cloud Enterprise, Elastic Cloud on Kubernetes, and self-managed environments.

This documentation corresponds to the latest patch available for each minor version. If you're not using the latest patch, check the release notes for changes.

EDOT iOS

The Elastic Distribution of OpenTelemetry (EDOT) iOS SDK collects performance metrics, traces, and logs in OpenTelemetry format, and sends them to Elastic Observability.

Availability

  • Generally available

    This functionality is generally available and ready for production usage.

This documentation corresponds to the latest patch available for each minor version. If you're not using the latest patch, check the release notes for changes.

Learn how to manually instrument your app using the OpenTelemetry-Swift APIs configured by EDOT iOS.

Manual instrumentation allows you to capture app-specific operations that automatic instrumentation can't infer, such as:

  • A user completing a checkout flow
  • A local database query
  • The number of items added to a cart
  • A log record that explains why an operation failed

After completing the setup, EDOT iOS registers global tracer, meter, and logger providers. Access them through the OpenTelemetry instance:

Swift
import OpenTelemetryApi

let tracer = OpenTelemetry.instance.tracerProvider.get(
  instrumentationName: "com.example.my-app"
)

let meter = OpenTelemetry.instance.meterProvider
  .meterBuilder(name: "com.example.my-app")
  .build()

let logger = OpenTelemetry.instance.loggerProvider
  .loggerBuilder(instrumentationScopeName: "com.example.my-app")
  .build()

Use a stable instrumentation scope name that identifies the library or app component creating the telemetry.

A span represents an operation with a start and end time.

The following example creates a span around an operation:

Swift
import OpenTelemetryApi

func loadProducts() {
  let tracer = OpenTelemetry.instance.tracerProvider.get(
    instrumentationName: "com.example.my-app"
  )

  let span = tracer.spanBuilder(spanName: "load-products").startSpan()
  defer {
    span.end()
  }

  // Run the operation you want to measure.
}

Always end spans. A defer block is useful when an operation can return from multiple code paths.

Use parent and child spans to represent work performed as part of a larger operation:

Swift
import OpenTelemetryApi

func refreshProducts() {
  let tracer = OpenTelemetry.instance.tracerProvider.get(
    instrumentationName: "com.example.my-app"
  )

  let parentSpan = tracer.spanBuilder(spanName: "refresh-products").startSpan()
  defer {
    parentSpan.end()
  }

  let childSpan = tracer.spanBuilder(spanName: "read-product-cache")
    .setParent(parentSpan)
    .startSpan()

  // Read the local cache.

  childSpan.end()
}

Setting the parent explicitly keeps the relationship clear when work crosses queues or asynchronous boundaries.

Attributes add queryable context to a span. Events identify a meaningful point in time during the operation:

Swift
import OpenTelemetryApi

func loadProducts() {
  let tracer = OpenTelemetry.instance.tracerProvider.get(
    instrumentationName: "com.example.my-app"
  )
  let span = tracer.spanBuilder(spanName: "load-products").startSpan()
  defer {
    span.end()
  }

  span.setAttribute(key: "app.screen", value: "products")
  span.setAttribute(key: "product.count", value: 42)

  span.addEvent(
    name: "cache.miss",
    attributes: [
      "cache.name": .string("products"),
    ]
  )
}

Avoid putting secrets, personal data, or values with unbounded cardinality in attributes.

Record an exception event and set the span status when an operation fails:

Swift
import OpenTelemetryApi

func refreshProducts() async throws {
  let tracer = OpenTelemetry.instance.tracerProvider.get(
    instrumentationName: "com.example.my-app"
  )
  let span = tracer.spanBuilder(spanName: "refresh-products").startSpan()
  defer {
    span.end()
  }

  do {
    try await loadProducts()
    span.status = .ok
  } catch {
    span.recordException(error)
    span.status = .error(description: error.localizedDescription)
    throw error
  }
}

Recording an exception does not end the span. End it after all work and error handling is complete.

A metric measures a value over time. The following example records the number of products added to a cart:

Swift
import OpenTelemetryApi

func recordProductAdded() {
  let meter = OpenTelemetry.instance.meterProvider
    .meterBuilder(name: "com.example.my-app")
    .build()

  var productsAdded = meter.counterBuilder(name: "app.cart.products_added")
    .build()

  productsAdded.add(
    value: 1,
    attributes: [
      "product.category": .string("accessories"),
    ]
  )
}

Choose the instrument type that matches the value you are recording. Refer to the OpenTelemetry metrics API for counters, histograms, gauges, and other instruments.

A log record describes an event that occurs in your app:

Swift
import OpenTelemetryApi

func logProductsRefreshed() {
  let logger = OpenTelemetry.instance.loggerProvider
    .loggerBuilder(instrumentationScopeName: "com.example.my-app")
    .build()

  logger.logRecordBuilder()
    .setSeverity(.info)
    .setBody(.string("Products refreshed"))
    .setAttributes([
      "product.count": .int(42),
    ])
    .emit()
}

EDOT iOS adds the current session.id to exported log records. If a log is related to a span, set its span context so you can correlate both signals:

Swift
import OpenTelemetryApi

func logRefreshFailure() {
  let tracer = OpenTelemetry.instance.tracerProvider.get(
    instrumentationName: "com.example.my-app"
  )
  let logger = OpenTelemetry.instance.loggerProvider
    .loggerBuilder(instrumentationScopeName: "com.example.my-app")
    .build()
  let span = tracer.spanBuilder(spanName: "refresh-products").startSpan()

  logger.logRecordBuilder()
    .setSpanContext(span.context)
    .setSeverity(.error)
    .setBody(.string("Failed to refresh products"))
    .emit()

  span.end()
}

Use manual instrumentation for custom or proprietary code, closed-source components without instrumentation support, and application-specific business logic.

Before adding custom instrumentation for a framework or Apple API, review Automatic instrumentation. EDOT iOS might already generate the telemetry you need.

For more details about the upstream APIs, refer to: