The Honeycomb OpenTelemetry Swift SDK is Honeycomb’s distribution of OpenTelemetry Swift. It simplifies adding instrumentation to your iOS applications and sending telemetry to Honeycomb.
This page briefly covers usage of the SDK. If you just want to see some code, check out the examples on GitHub.
Before you can add instrumentation to your iOS application, you will need to do a few things.
To send data to Honeycomb, you need to:
Sign up for a Honeycomb account. To sign up, decide whether you would like Honeycomb to store your data in a US-based or EU-based location, then create a Honeycomb account in the US or create a Honeycomb account in the EU.
Create a Honeycomb Ingest API Key. To get started, you can create a key that you expect to swap out when you deploy to production. Name it something helpful, perhaps noting that it’s a Getting Started key. Make note of your API key; for security reasons, you will not be able to see the key again, and you will need it later!
Add Honeycomb OpenTelemetry Swift to your application’s dependencies. The Honeycomb Swift SDK is compatible with applications targeting iOS 13+.
If you manage dependencies in Xcode:
https://github.com/honeycombio/honeycomb-opentelemetry-swift
as the repository URL.Honeycomb
package to your application’s target dependencies.If you manage dependencies with Package.swift
:
Add Honeycomb OpenTelemetry Swift as a package dependency:
dependencies: [
.package(url: "https://github.com/honeycombio/honeycomb-opentelemetry-swift.git",
from: "0.0.10")
],
Then add Honeycomb
as a target dependency:
dependencies: [
.product(name: "Honeycomb", package: "honeycomb-opentelemetry-swift"),
],
Option | Description |
---|---|
APIKey | String Your Honeycomb Ingest API Key. Required if sending telemetry directly to Honeycomb. |
tracesAPIKey | String Dedicated Ingest API Key to use when sending traces. Overrides APIKey for traces. |
metricsAPIKey | String Dedicated Ingest API Key to use when sending metrics. Overrides APIKey for metrics. |
logsAPIKey | String Dedicated Ingest API Key to use when sending logs. Overrides APIKey for logs. |
dataset | String Name of the dataset to send telemetry data to. Required if using Honeycomb Classic. |
metricsDataset | String Name of the dataset to send metrics to. Overrides dataset for metrics. |
APIEndpoint | String Telemetry is sent to this URL. For Honeycomb EU instances set this as https://api.eu1.honeycomb.io:443 . If you’re using an OpenTelemetry Collector, provide your collector URL instead.Default: https://api.honeycomb.io:443 (US instance) |
tracesEndpoint | String API endpoint to send traces to. |
metricsEndpoint | String API endpoint to send metrics to. |
logsEndpoint | String API endpoint to send logs to. |
sampleRate | Int Sample rate to apply. For example, a sampleRate of 40 means 1 in 40 traces will be exported. Default: 1 |
sessionTimeout | TimeInterval Maximum length of time, in seconds, for a single user session. Used to generate session.id span attribute.Default: TimeInterval(60 * 60 * 4) (4 hours) |
serviceName | String The name of your application. Used as the value for service.name resource attribute. |
serviceVersion | String Current version of your application. Used as the value for service.version resource attribute. |
resourceAttributes | Dictionary<String, String> Attributes to attach to outgoing resources. |
headers | Dictionary<String, String> Headers to add to exported telemetry data. |
tracesHeaders | Dictionary<String, String> Headers to add to exported trace data. |
metricsHeaders | Dictionary<String, String> Headers to add to exported metrics data. |
logsHeaders | Dictionary<String, String> Headers to add to exported logs data. |
timeout | TimeInterval Timeout used by exporter when sending data. |
tracesTimeout | TimeInterval Timeout used by traces exporter. Overrides timeout for trace data. |
metricsTimeout | TimeInterval Timeout used by metrics exporter. Overrides timeout for metrics data. |
logsTimeout | TimeInterval Timeout used by logs exporter. Overrides timeout for logs data. |
protocol | enum HoneycombOptions.OTLPProtocol Protocol to use when sending data. Default: .httpProtobuf |
tracesProtocol | enum HoneycombOptions.OTLPProtocol Overrides protocol for trace data. |
metricsProtocol | enum HoneycombOptions.OTLPProtocol Overrides protocol for metrics data. |
logsProtocol | enum HoneycombOptions.OTLPProtocol Overrides protocol for logs data. |
spanProcessor | OpenTelemetryApi.SpanProcessor Additional span processor to use. |
metricKitInstrumentationEnabled | Bool Enable MetricKit instrumentation. Default: true |
urlSessionInstrumentationEnabled | Bool Enable URLSession instrumentation. Default: true |
uiKitInstrumentationEnabled | Bool Enable UIKit view instrumentation. Default: true |
touchInstrumentationEnabled | Bool Enable UIKit touch instrumentation. Default: false |
unhandledExceptionInstrumentationEnabled | Bool Enable unhandled exception instrumentation. Default: true |
offlineCachingEnabled | Bool Enable offline caching for telemetry. When offline caching is enabled, telemetry is cached during network failures. The SDK will retry exporting telemetry for up to 18 hours. Offline caching also adds a minimum delay of 5 seconds to telemetry exports. Offline caching is an alpha feature and may be unstable. Default: false |
Automatic instrumentation packages are enabled or disabled in your configuration.
import Honeycomb
import SwiftUI
@main
struct ExampleApp: App {
init() {
do {
let options = try HoneycombOptions.Builder()
.setAPIKey("YOUR-API-KEY")
.setServiceName("YOUR-SERVICE-NAME")
// Enable or disable auto-instrumentation packages
.setMetricKitInstrumentationEnabled(true)
.setURLSessionInstrumentationEnabled(true)
.setUIKitInstrumentationEnabled(true)
.setTouchInstrumentationEnabled(false)
.setUnhandledExceptionInstrumentationEnabled(true)
.setDebug(true)
.build()
try Honeycomb.configure(options: options)
} catch {
NSException(name: NSExceptionName("HoneycombOptionsError"), reason: "\(error)").raise()
}
}
var body: some Scene {
Text("Hello world!")
}
}
Resource attributes are available on every span your instrumentation emits. Adding custom, application-specific attributes makes it easier to correlate your data to important business information.
You can add extra resource attributes during SDK configuration with the .setResourceAttributes()
method.
import Honeycomb
import SwiftUI
@main
struct ExampleApp: App {
init() {
do {
let options = try HoneycombOptions.Builder()
.setAPIKey("YOUR-API-KEY")
.setServiceName("YOUR-SERVICE-NAME")
.setDebug(true)
.setResourceAttributes(["app.ab_test": "test c"])
.build()
try Honeycomb.configure(options: options)
} catch {
NSException(name: NSExceptionName("HoneycombOptionsError"), reason: "\(error)").raise()
}
}
var body: some Scene {
Text("Hello world!")
}
}
The Honeycomb Swift SDK includes optional deterministic head sampling.
To enable sampling, call .setSampleRate()
with your desired sample rate as an Int
value.
The sample rate is 1
by default, meaning every trace is exported.
The example below sets a sampleRate
of 40
, meaning 1 in 40 traces will be exported.
// ...
let options = try HoneycombOptions.Builder()
.setAPIKey("YOUR-API-KEY")
.setServiceName("YOUR-SERVICE-NAME")
.setServiceVersion("0.0.1")
.sampleRate(40)
.setDebug(true)
.build()
try Honeycomb.configure(options: options)
Automatic instrumentation is a fast way to instrument your code, but you get more insight into your application by adding custom, or manual, instrumentation.
To add your own custom instrumentation, include the OpenTelemetryApi as a dependency in your application.
You can retrieve the currently active span in a trace and add attributes to it. This lets you add more context to traces and gives you more ways to group or filter traces in your queries:
import OpenTelemetryApi
func applyDiscountCode(discountCode: String) {
let currentSpan = OpenTelemetry.instance.contextProvider.activeSpan
currentSpan.setAttribute("app.cart.discount_code", discountCode)
}
In the above example, we add an app.cart.discount_code
attribute to the current span.
This lets us use the app.cart.discount_code
field in WHERE
or GROUP BY
clauses in the Honeycomb query builder.
For manual tracing, you need to acquire a tracer:
import OpenTelemetryApi
let tracer = OpenTelemetry.instance.tracerProvider.get(
instrumentationName: "my-application-tracer",
instrumentationVersion: "1.0.0"
)
Create custom spans to get a clear view of the critical parts in your application.
import OpenTelemetryApi
let tracer = OpenTelemetry.instance.tracerProvider.get(
instrumentationName: "my-application-tracer",
instrumentationVersion: "1.0.0"
)
func generateNewLevel() {
let span = tracer.spanBuilder(spanName: "newLevel").startSpan()
// do some work
span.end()
}
Span processors provide hooks for when a span starts and when it ends. This lets you mutate spans after they have been created by automatic or manual instrumentation.
Here’s a basic example of a span processor that adds an attribute to spans when they start:
import Foundation
import OpenTelemetryApi
import OpenTelemetrySdk
internal class BasicSpanProcessor: SpanProcessor {
public let isStartRequired = true
public let isEndRequired = false
public func onStart(
parentContext: SpanContext?,
span: any ReadableSpan
) {
span.setAttribute(
key: "app.metadata",
value: "extra metadata"
)
}
func onEnd(span: any OpenTelemetrySdk.ReadableSpan) {}
func shutdown(explicitTimeout: TimeInterval?) {}
func forceFlush(timeout: TimeInterval?) {}
}
Add the span processor to your SDK configuration to enable it:
import Honeycomb
import SwiftUI
@main
struct ExampleApp: App {
init() {
do {
let options = try HoneycombOptions.Builder()
.setAPIKey("YOUR-API-KEY")
.setServiceName("YOUR-SERVICE-NAME")
.setSpanProcessor(BasicSpanProcessor())
.setDebug(true)
.build()
try Honeycomb.configure(options: options)
} catch {
NSException(name: NSExceptionName("HoneycombOptionsError"), reason: "\(error)").raise()
}
}
var body: some Scene {
Text("Hello world!")
}
}
The Honeycomb Swift SDK provides utilities for manually instrumenting SwiftUI views, SwiftUI navigation, and logging errors or exceptions.
Trace render timings of your views by wrapping them with HoneycombInstrumentedView(name: String)
.
var body: some View {
HoneycombInstrumentedView(name: "main view") {
VStack {
Text("Hello main view!")
}
}
}
The SDK provides a view modifier for manually tracing a NavigationStack when you are managing navigation state externally.
The instrumentNavigation(path: String)
view modifier creates spans on path
changes (NavigationTo
, NavigationFrom
) with attributes for the full navigation path and what triggered the navigation.
import Honeycomb
import SwiftUI
struct Fruit: Identifiable, Equatable, Hashable, Codable {
let name: String
let color: String
var id: String {
name
}
}
let fruits = [
Fruit(name: "Apple", color: "Red"),
Fruit(name: "Banana", color: "Yellow"),
]
func fruit(from id: Fruit.ID?) -> Fruit? {
if let fruitId = id {
if let index = fruits.firstIndex(where: { $0.id == fruitId }) {
return fruits[index]
}
}
return nil
}
struct FruitDetails: View {
let fruit: Fruit
var body: some View {
Text("\(fruit.name) is \(fruit.color)")
}
}
struct ExampleNavigationStackView: View {
@State private var presentedFruits: [Fruit] = []
var body: some View {
NavigationStack(path: $presentedFruits) {
List(fruits) { fruit in
NavigationLink(fruit.name, value: fruit)
}
.navigationDestination(for: Fruit.self) { fruit in
FruitDetails(fruit: fruit)
}
}
.instrumentNavigation(path: presentedFruits) // View Modifier
}
}
For other navigation components, such as TabView
or NavigationSplitView
, you can use the Honeycomb.setCurrentScreen(path: Any)
function to trace navigation.
import Honeycomb
import SwiftUI
struct ExampleTabView: View {
var body: some View {
TabView {
ViewA()
.padding()
.tabItem { Label("View A") }
.onAppear {
Honeycomb.setCurrentScreen(path: "View A")
}
ViewB()
.padding()
.tabItem { Label("View B") }
.onAppear {
Honeycomb.setCurrentScreen(path: "View B")
}
ViewC()
.padding()
.tabItem { Label("View C") }
.onAppear {
Honeycomb.setCurrentScreen(path: "View C")
}
}
}
}
The Honeycomb.log()
method records any Error
, NSError
, or NSException
as a log record.
You can use Honeycomb.log()
for logging exceptions you catch in your own code that are not logged by the SDK.
do {
try thisFunctionMayThrow()
}
catch let error {
Honeycomb.log(
error: error,
attributes: [
"user.name": AttributeValue.string(currentUser.name),
"user.id": AttributeValue.int(currentUser.id)
],
thread: Thread.current
);
}
If you are connecting your app to a backend service that you wish to view as a unified trace with your app, you will need to manually add headers to all your outgoing requests. You must also create a span and set it as the active span. The span’s context will be used to generate the headers needed for trace propagation.
import OpenTelemetryApi
private struct HttpTextMapSetter: Setter {
func set(carrier: inout [String: String], key: String, value: String) {
carrier[key] = value
}
}
private let textMapSetter = HttpTextMapSetter()
func makeBackendRequest(data: Data) async throws {
let url = URL(string: "https://mybackendservice")
var request = URLRequest(url: url!)
request.httpMethod = "POST"
request.httpBody = data
let allHeaders: [String: String] = []
let span = OpenTelemetry.instance.tracerProvider.get(
instrumentationName: "mybackendservice.network",
instrumentationVersion: getCurrentAppVersion()
)
.spanBuilder(spanName: "backendRequest")
// The span must be made the active span or else the network autoinstrumentation
// will not be attached to the trace.
.setActive(true)
.startSpan()
defer {
span.end()
}
// Add the required headers to the `allHeaders` Dictionary
OpenTelemetry.instance.propagators.textMapPropagator.inject(
spanContext: span.context,
carrier: &allHeaders,
setter: textMapSetter
)
allHeaders.forEach({ (key: String, value: String) in
request.setValue(value, forHTTPHeaderField: key)
})
let session = URLSession(configuration: URLSessionConfiguration.default)
let (data, response) = try await session.data(for: request)
// process your response data as normal
}
To explore common issues when sending data, visit Common Issues with Sending Data in Honeycomb.