Resource Attributes in OpenTelemetry Swift

Resource attributes automatically detect and describe the environment in which your application runs. This information is attached to all telemetry data (traces and metrics) to provide context for monitoring and debugging.

Overview

Resources describe the entity producing telemetry. Common resource attributes include:

  • service.name - Logical name of the service
  • service.version - Version of the service
  • deployment.environment - Deployment environment (production, staging, etc.)
  • device.model.identifier - Device model (iPhone, Mac, etc.)
  • os.name - Operating system name

SDKResourceExtension

OpenTelemetry Swift provides the SDKResourceExtension package that automatically detects device, OS, and application information.

Installation

Add the ResourceExtension dependency:

swift
// Package.swift
dependencies: [
    .package(url: "https://github.com/open-telemetry/opentelemetry-swift", from: "1.0.0"),
],
targets: [
    .executableTarget(
        name: "MyApp",
        dependencies: [
            .product(name: "OpenTelemetrySdk", package: "opentelemetry-swift"),
            .product(name: "ResourceExtension", package: "opentelemetry-swift"),
        ]
    )
]

Basic usage

Use DefaultResources to get all available resource attributes:

swift
import OpenTelemetrySdk
import ResourceExtension

let resource = DefaultResources().get()

OpenTelemetry.registerTracerProvider(tracerProvider:
    TracerProviderBuilder()
        .with(resource: resource)
        .build()
)

Auto-detected attributes

The SDKResourceExtension automatically detects the following attributes:

Application Info

AttributeExampleDescription
service.nameMyAppCFBundleName from Info.plist
service.version1.0 (123)CFBundleShortVersion & CFBundleVersion
service.namespacecom.mycompany.myappCFBundleIdentifier

Device Info

AttributeExampleDescription
device.model.identifieriPhone14,2Device model from sysctl
device.id00000000-0000-0000-0000-000000000000identifierForVendor UUID

Operating System Info

AttributeExampleDescription
os.typedarwinOperating system type
os.nameiOS, macOS, watchOSPlatform name
os.version17.0.0OS version
os.descriptioniOS Version 17.0 (Build 21A5248v)Full OS description

Custom resource attributes

Add custom attributes by merging resources:

swift
import OpenTelemetrySdk
import ResourceExtension

func createResource() -> Resource {
    // Start with auto-detected resources
    let defaultResource = DefaultResources().get()

    // Add custom attributes
    let customResource = Resource(attributes: [
        ResourceAttributes.serviceName.rawValue: AttributeValue.string("my-service"),
        ResourceAttributes.serviceVersion.rawValue: AttributeValue.string("1.0.0"),
        ResourceAttributes.deploymentEnvironment.rawValue: AttributeValue.string("production"),
        "team": AttributeValue.string("mobile"),
        "app.build_type": AttributeValue.string("release")
    ])

    // Merge resources (custom attributes override defaults)
    return defaultResource.merging(other: customResource)
}

// Usage
let resource = createResource()
OpenTelemetry.registerTracerProvider(tracerProvider:
    TracerProviderBuilder()
        .with(resource: resource)
        .build()
)

Environment-based configuration

Configure resources based on the environment:

swift
import Foundation
import OpenTelemetrySdk
import ResourceExtension

func createResource() -> Resource {
    let defaultResource = DefaultResources().get()

    // Read from environment or use defaults
    let serviceName = ProcessInfo.processInfo.environment["OTEL_SERVICE_NAME"] ?? "my-service"
    let environment = ProcessInfo.processInfo.environment["DEPLOYMENT_ENVIRONMENT"] ?? "development"

    let customResource = Resource(attributes: [
        ResourceAttributes.serviceName.rawValue: AttributeValue.string(serviceName),
        ResourceAttributes.deploymentEnvironment.rawValue: AttributeValue.string(environment)
    ])

    return defaultResource.merging(other: customResource)
}

Resource attributes for different platforms

iOS App

swift
func createiOSResource() -> Resource {
    let defaultResource = DefaultResources().get()

    var attributes: [String: AttributeValue] = [
        ResourceAttributes.serviceName.rawValue: .string("MyiOSApp"),
        ResourceAttributes.serviceVersion.rawValue: .string(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown"),
        "deployment.environment": .string(getEnvironment())
    ]

    // Add iOS-specific attributes
    #if os(iOS)
    attributes["device.screen_width"] = .int(Int(UIScreen.main.bounds.width))
    attributes["device.screen_height"] = .int(Int(UIScreen.main.bounds.height))
    #endif

    return defaultResource.merging(other: Resource(attributes: attributes))
}

func getEnvironment() -> String {
    #if DEBUG
    return "development"
    #else
    return "production"
    #endif
}

macOS App

swift
func createmacOSResource() -> Resource {
    let defaultResource = DefaultResources().get()

    var attributes: [String: AttributeValue] = [
        ResourceAttributes.serviceName.rawValue: .string("MyMacApp"),
        ResourceAttributes.serviceVersion.rawValue: .string(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown"),
        "deployment.environment": .string(getEnvironment())
    ]

    // Add macOS-specific attributes
    attributes["host.name"] = .string(Host.current().localizedName ?? "unknown")

    return defaultResource.merging(other: Resource(attributes: attributes))
}

Server-side Swift (Vapor)

swift
func createServerResource() -> Resource {
    let defaultResource = DefaultResources().get()

    let attributes: [String: AttributeValue] = [
        ResourceAttributes.serviceName.rawValue: .string("my-api-service"),
        ResourceAttributes.serviceVersion.rawValue: .string("1.0.0"),
        ResourceAttributes.deploymentEnvironment.rawValue: .string(Environment.get("ENVIRONMENT") ?? "development"),
        "host.name": .string(Host.current().localizedName ?? ProcessInfo.processInfo.hostName),
        "process.pid": .int(Int(ProcessInfo.processInfo.processIdentifier))
    ]

    return defaultResource.merging(other: Resource(attributes: attributes))
}

Complete example

swift
import Foundation
import OpenTelemetryApi
import OpenTelemetrySdk
import OpenTelemetryProtocolExporter
import ResourceExtension
import GRPC
import NIO

func configureOpenTelemetry() {
    let dsn = ProcessInfo.processInfo.environment["UPTRACE_DSN"] ?? ""

    // Create resource with auto-detection and custom attributes
    let resource = createResource()

    // Configure exporter
    let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
    let channel = ClientConnection
        .usingPlatformAppropriateTLS(for: group)
        .connect(host: "api.uptrace.dev", port: 4317)

    let otlpConfig = OtlpConfiguration(
        timeout: OtlpConfiguration.DefaultTimeoutInterval,
        headers: [("uptrace-dsn", dsn)]
    )

    let traceExporter = OtlpTraceExporter(channel: channel, config: otlpConfig)
    let spanProcessor = BatchSpanProcessor(spanExporter: traceExporter)

    // Register tracer provider with resource
    OpenTelemetry.registerTracerProvider(tracerProvider:
        TracerProviderBuilder()
            .add(spanProcessor: spanProcessor)
            .with(resource: resource)
            .build()
    )

    // Register meter provider with the same resource
    let metricExporter = OtlpMetricExporter(channel: channel, config: otlpConfig)
    OpenTelemetry.registerMeterProvider(meterProvider:
        MeterProviderBuilder()
            .with(processor: MetricProcessorSdk())
            .with(exporter: metricExporter)
            .with(resource: resource)
            .build()
    )
}

func createResource() -> Resource {
    let defaultResource = DefaultResources().get()

    let customAttributes: [String: AttributeValue] = [
        // Service identification
        ResourceAttributes.serviceName.rawValue: .string("my-swift-service"),
        ResourceAttributes.serviceVersion.rawValue: .string("1.0.0"),
        ResourceAttributes.serviceNamespace.rawValue: .string("com.mycompany"),

        // Deployment info
        "deployment.environment": .string(getEnvironment()),

        // Custom attributes
        "team": .string("platform"),
        "app.component": .string("api-gateway")
    ]

    return defaultResource.merging(other: Resource(attributes: customAttributes))
}

func getEnvironment() -> String {
    if let env = ProcessInfo.processInfo.environment["ENVIRONMENT"] {
        return env
    }
    #if DEBUG
    return "development"
    #else
    return "production"
    #endif
}

Debugging resources

Print detected resource attributes for debugging:

swift
func printResourceAttributes(_ resource: Resource) {
    print("Resource attributes:")
    for attribute in resource.attributes {
        print("  \(attribute.key): \(attribute.value.description)")
    }
}

// Usage
let resource = createResource()
printResourceAttributes(resource)

What's next?