Instrumentation

Instrumentation for OpenTelemetry Swift

You are viewing the English version of this page because it has not yet been fully translated. Interested in helping out? See Contributing.

插桩(Instrumentation) 是指向应用中添加可观测性代码的行为。

如果你正在对一个应用进行插桩,需要使用适合你语言的 OpenTelemetry SDK。然后,你可以使用 SDK 初始化 OpenTelemetry,并使用 API 对代码进行插桩。这将从你的应用及其安装的任何带有插桩的库中导出遥测数据。

如果你正在对一个库进行插桩,只需安装适合你语言的 OpenTelemetry API 包。你的库不会自行导出遥测数据。只有当该库作为使用 OpenTelemetry SDK 的应用的一部分时,它才会导出遥测数据。有关如何对库进行插桩的更多信息,请参见 Libraries

有关 OpenTelemetry API 和 SDK 的更多信息,请参见 specification

Setup

OpenTelemetry Swift provides limited functionality in its default configuration. For more useful functionality, some configuration is required.

The default registered TracerProvider and MetricProvider are not configured with an exporter. There are several exporters available depending on your needs. Below we will explore configuring the OTLP exporter, which can be used for sending data to the collector.

import GRPC
import OpenTelemetryApi
import OpenTelemetrySdk
import OpenTelemetryProtocolExporter


// initialize the OtlpTraceExporter
let otlpConfiguration = OtlpConfiguration(timeout: OtlpConfiguration.DefaultTimeoutInterval)

let grpcChannel = ClientConnection.usingPlatformAppropriateTLS(for: MultiThreadedEventLoopGroup(numberOfThreads:1))
                                                  .connect(host: <collector host>, port: <collector port>)

let traceExporter = OtlpTraceExporter(channel: grpcChannel,
                                      config: otlpConfiguration)

// build & register the Tracer Provider using the built otlp trace exporter
OpenTelemetry.registerTracerProvider(tracerProvider: TracerProviderBuilder()
                                                      .add(spanProcessor:SimpleSpanProcessor(spanExporter: traceExporter))
                                                      .with(resource: Resource())
                                                      .build())

A similar pattern is used for the OtlpMetricExporter:

// otlpConfiguration & grpcChannel can be reused
OpenTelemetry.registerMeterProvider(meterProvider: MeterProviderBuilder()
            .with(processor: MetricProcessorSdk())
            .with(exporter: OtlpMetricExporter(channel: channel, config: otlpConfiguration))
            .with(resource: Resource())
            .build())

After configuring the MeterProvider & TracerProvider all subsequently initialized instrumentation will be exporting using this OTLP exporter.

Traces

Acquiring a Tracer

To do tracing, you will need a tracer. A tracer is acquired through the tracer provider and is responsible for creating spans. The OpenTelemetry manages the tracer provider as we defined and registered above. A tracer requires an instrumentation name, and an optional version to be created:

let  tracer = OpenTelemetry.instance.tracerProvider.get(instrumentationName: "instrumentation-library-name", instrumentationVersion: "1.0.0")

Creating Spans

A span represents a unit of work or operation. Spans are the building blocks of Traces. To create a span use the span builder associated with the tracer:

let span = tracer.spanBuilder(spanName: "\(name)").startSpan()
...
span.end()

It is required to call end() to end the span.

Creating Nested Spans

Spans are used to build relationship between operations. Below is an example of how we can manually build relationship between spans.

Below we have parent() calling child() and how to manually link spans of each of these methods.

func parent() {
  let parentSpan = someTracer.spanBuilder(spanName: "parent span").startSpan()
  child(span: parentSpan)
  parentSpan.end()
}

func child(parentSpan: Span) {
let childSpan = someTracer.spanBuilder(spanName: "child span")
                             .setParent(parentSpan)
                             .startSpan()
  // do work
  childSpan.end()
}

The parent-child relationship will be automatically linked if activeSpan is used:

func parent() {
  let parentSpan = someTracer.spanBuilder(spanName: "parent span")
                      .setActive(true) // automatically sets context
                      .startSpan()
  child()
  parentSpan.end()
}

func child() {
  let childSpan = someTracer.spanBuilder(spanName: "child span")
                             .startSpan() //automatically captures `active span` as parent
  // do work
  childSpan.end()
}

Getting the Current Span

Sometimes it’s useful to do something with the current/active span. Here’s how to access the current span from an arbitrary point in your code.

let currentSpan = OpenTelemetry.instance.contextProvider.activeSpan

Span Attributes

Spans can also be annotated with additional attributes. All spans will be automatically annotated with the Resource attributes attached to the tracer provider. The Opentelemetry-swift SDK already provides instrumentation of common attributes in the SDKResourceExtension instrumentation. In this example a span for a network request capturing details about that request using existing semantic conventions.

let span = tracer.spanBuilder("/resource/path").startSpan()
span.setAttribute("http.method", "GET");
span.setAttribute("http.url", url.toString());

Creating Span Events

A Span Event can be thought of as a structured log message (or annotation) on a Span, typically used to denote a meaningful, singular point in time during the Span’s duration.

let attributes = [
    "key" : AttributeValue.string("value"),
    "result" : AttributeValue.int(100)
]
span.addEvent(name: "computation complete", attributes: attributes)

Setting Span Status

可以在一个 Span 上设置一个 Status,通常用于指明某个 Span 没有成功完成 —— 即标记为 Error。默认情况下,所有的 Span 状态都是 Unset,意味着该操作完成但未明确表示是否出错。 如果你想显式地标记某个操作是成功的,而不是依赖默认的 Unset,就可以使用 Ok 状态。

状态可以在 Span 结束前的任何时候设置。

func myFunction() {
  let span = someTracer.spanBuilder(spanName: "my span").startSpan()
  defer {
    span.end()
  }
  guard let criticalData = get() else {
      span.status = .error(description: "something bad happened")
      return
  }
  // do something
}

Recording exceptions in Spans

Semantic conventions provide special demarcation for events that record exceptions:

let span = someTracer.spanBuilder(spanName: "my span").startSpan()
do {
  try throwingFunction()
} catch {
  span.addEvent(name: SemanticAttributes.exception.rawValue,
    attributes: [SemanticAttributes.exceptionType.rawValue: AttributeValue.string(String(describing: type(of: error))),
                 SemanticAttributes.exceptionEscaped.rawValue: AttributeValue.bool(false),
                 SemanticAttributes.exceptionMessage.rawValue: AttributeValue.string(error.localizedDescription)])
  })
  span.status = .error(description: error.localizedDescription)
}
span.end()

Metrics

The documentation for the metrics API & SDK is missing, you can help make it available by editing this page.

Logs

The logs API & SDK are currently under development.

SDK Configuration

Processors

Different Span processors are offered by OpenTelemetry-swift. The SimpleSpanProcessor immediately forwards ended spans to the exporter, while the BatchSpanProcessor batches them and sends them in bulk. Multiple Span processors can be configured to be active at the same time using the MultiSpanProcessor. For example, you may create a SimpleSpanProcessor that exports to a logger, and a BatchSpanProcessor that exports to a OpenTelemetry Collector:

let otlpConfiguration = OtlpConfiguration(timeout: OtlpConfiguration.DefaultTimeoutInterval)

let grpcChannel = ClientConnection.usingPlatformAppropriateTLS(for: MultiThreadedEventLoopGroup(numberOfThreads:1))
                                                  .connect(host: <collector host>, port: <collector port>)

let traceExporter = OtlpTraceExporter(channel: grpcChannel
                                      config: otlpConfiguration)

// build & register the Tracer Provider using the built otlp trace exporter
OpenTelemetry.registerTracerProvider(tracerProvider: TracerProviderBuilder()
                                                      .add(spanProcessor:BatchSpanProcessor(spanExporter: traceExporter))
                                                      .add(spanProcessor:SimpleSpanProcessor(spanExporter: StdoutExporter))
                                                      .with(resource: Resource())
                                                      .build())

The batch span processor allows for a variety of parameters for customization including.

Exporters

OpenTelemetry-Swift provides the following exporters:

  • InMemoryExporter: Keeps the span data in memory. This is useful for testing and debugging.
  • DatadogExporter: Converts OpenTelemetry span data to Datadog traces & span Events to Datadog logs.
  • JaegerExporter: Converts OpenTelemetry span data to Jaeger format and exports to a Jaeger endpoint.
  • Persistence exporter: An exporter decorator that provides data persistence to existing metric and trace exporters.
  • PrometheusExporter: Converts metric data to Prometheus format and exports to a Prometheus endpoint.
  • StdoutExporter: Exports span data to Stdout. Useful for debugging.
  • ZipkinTraceExporter: Exports span data to Zipkin format to a Zipkin endpoint.