This document contains instructions on how to set up OpenTelemetry instrumentation in your Swift applications and view your application traces in SigNoz.
Prerequisites
- Swift 5.x (recommended by OpenTelemetry, see Adding the dependency section)
- An instance of SigNoz (either Cloud or Self-Hosted)
Setup
Step 1: Install dependencies
To configure your Swift application to send traces directly to SigNoz Cloud, install opentelemetry-swift and grpc-swift as dependencies.
Add the following to your Package.swift file:
platforms: [
.macOS(.v12)
],
dependencies: [
.package(url: "https://github.com/open-telemetry/opentelemetry-swift-core.git", .upToNextMajor(from: "2.3.0")),
.package(url: "https://github.com/open-telemetry/opentelemetry-swift.git", .upToNextMajor(from: "2.3.0")),
.package(url: "https://github.com/grpc/grpc-swift", from: "1.15.0"),
],
targets: [
.executableTarget(
name: "<service_name>",
dependencies: [
.product(name: "OpenTelemetryApi", package: "opentelemetry-swift-core"),
.product(name: "OpenTelemetrySdk", package: "opentelemetry-swift-core"),
.product(name: "OpenTelemetryProtocolExporter", package: "opentelemetry-swift"),
.product(name: "GRPC", package: "grpc-swift")
],
path: "."
)
]
Verify these values:
<service_name>: The name of your service.
Step 2: Initialize the Tracer
Import the necessary OpenTelemetry modules and initialize the tracer. Insert the following code snippet into your main.swift file:
import Foundation
import GRPC
import NIO
import NIOSSL
@preconcurrency import OpenTelemetryApi
import OpenTelemetryProtocolExporterCommon
import OpenTelemetryProtocolExporterGrpc
import OpenTelemetrySdk
@main
struct SwiftExample {
static func main() async throws {
let instrumentationScopeName = "SwiftExample"
let instrumentationScopeVersion = "semver:0.1.0"
// Replace <your-ingestion-key> with your SigNoz Ingestion Key
let otlpConfiguration = OtlpConfiguration(timeout: TimeInterval(10), headers: [("signoz-ingestion-key", "<your-ingestion-key>")])
// Replace <region> with the region of your SigNoz Cloud instance
let grpcChannel = ClientConnection.usingPlatformAppropriateTLS(
for: MultiThreadedEventLoopGroup(numberOfThreads: 1)
).connect(host: "ingest.<region>.signoz.cloud", port: 443)
let otlpTraceExporter = OtlpTraceExporter(channel: grpcChannel, config: otlpConfiguration)
let spanProcessor = BatchSpanProcessor(spanExporter: otlpTraceExporter)
// Replace <service_name> with the name of your service
let resource = Resource(attributes: [
"service.name": AttributeValue.string("<service_name>")
])
let tracerProvider = TracerProviderBuilder()
.add(spanProcessor: spanProcessor)
.with(resource: resource)
.build()
OpenTelemetry.registerTracerProvider(tracerProvider: tracerProvider)
let tracer = OpenTelemetry.instance.tracerProvider.get(instrumentationName: instrumentationScopeName, instrumentationVersion: instrumentationScopeVersion)
Verify these values:
<region>: Your SigNoz Cloud region<your-ingestion-key>: Your SigNoz ingestion key<service_name>: A name to identify your service in SigNoz (e.g.,my-swift-app)
Step 3: Instrument your application
OpenTelemetry Swift does not support automatic instrumentation. You must manually create spans for every operation you want to trace.
To send telemetry data to SigNoz, wrap operations in spans. Each span represents a unit of work and can carry attributes (key-value metadata) for context.
Create a basic span
Use the tracer's spanBuilder to start a span, optionally set its kind and attributes, then call .end() when the work is done:
func doWork() {
let span = tracer.spanBuilder(spanName: "doWork")
.setSpanKind(spanKind: .client)
.startSpan()
// Add key-value attributes to provide context
span.setAttribute(key: "operation.type", value: "example")
span.setAttribute(key: "operation.id", value: 42)
// ... perform work here ...
span.end()
}
Nest spans for detailed traces
Set a parent span as the active span using OpenTelemetry.instance.contextProvider so that any child spans created within its scope are automatically linked:
func handleRequest() {
let parentSpan = tracer.spanBuilder(spanName: "handleRequest")
.setSpanKind(spanKind: .server)
.startSpan()
OpenTelemetry.instance.contextProvider.setActiveSpan(parentSpan)
// This child span is automatically linked to parentSpan
fetchData()
parentSpan.end()
}
func fetchData() {
let childSpan = tracer.spanBuilder(spanName: "fetchData")
.setSpanKind(spanKind: .client)
.startSpan()
childSpan.setAttribute(key: "db.system", value: "sqlite")
// ... fetch data ...
childSpan.end()
}
Record errors
Mark a span's status as .error and record the exception when something fails:
func riskyOperation() {
let span = tracer.spanBuilder(spanName: "riskyOperation").startSpan()
do {
// ... work that might throw ...
} catch {
span.status = .error(description: error.localizedDescription)
}
span.end()
}
Once your spans are in place, flush and run the application:
// Call the function to generate a trace
handleRequest()
// Flush all pending spans before the process exits
tracerProvider.forceFlush(timeout: 15)
Step 4: Run the application
Execute your application by running the following command in your terminal:
swift run
If the application exits without errors, the traces have been sent to SigNoz. Navigate to the Traces tab in SigNoz to verify.
Validate
To ensure everything is working:
- Perform an action in your application that triggers the traced code.
- In SigNoz, navigate to the Traces tab.
- Filter by your
<service_name>(e.g.service.nameINmy-swift-app) to find your traces. - If you see your spans appearing in the list, the instrumentation is successfully sending data to SigNoz.
Troubleshooting
No data appearing in SigNoz
- Invalid Credentials: Ensure that
<your-ingestion-key>andingest.<region>.signoz.cloudare correctly populated without the angle brackets. - Network Issues: Ensure your application environment has outbound network access to the SigNoz ingestion endpoint on port
443. - Initialization Order: Make sure the
OpenTelemetry.registerTracerProvider()gets called before any spans are created.
Setup OpenTelemetry Collector (Optional)
What is the OpenTelemetry Collector?
Think of the OTel Collector as a middleman between your app and SigNoz. Instead of your application sending data directly to SigNoz, it sends everything to the Collector first, which then forwards it along.
Why use it?
- Cleaning up data — Filter out noisy traces you don't care about, or remove sensitive info before it leaves your servers.
- Keeping your app lightweight — Let the Collector handle batching, retries, and compression instead of your application code.
- Adding context automatically — The Collector can tag your data with useful info like which Kubernetes pod or cloud region it came from.
- Future flexibility — Want to send data to multiple backends later? The Collector makes that easy without changing your app.
See Switch from direct export to Collector for step-by-step instructions to convert your setup.
For more details, see Why use the OpenTelemetry Collector? and the Collector configuration guide.
Update connection logic
Change your tracer initialization to send data without TLS to localhost (or your collector host) on port 4317:
Use the same imports from Step 2, adding
import NIOSSL. Only the connection and provider setup changes:
import Foundation
import GRPC
import NIO
import NIOSSL
@preconcurrency import OpenTelemetryApi
import OpenTelemetryProtocolExporterCommon
import OpenTelemetryProtocolExporterGrpc
import OpenTelemetrySdk
let otlpConfiguration = OtlpConfiguration(timeout: TimeInterval(10))
let configuration = ClientConnection.Configuration.default(
target: .hostAndPort("localhost", 4317),
eventLoopGroup: MultiThreadedEventLoopGroup(numberOfThreads: 1)
)
let grpcChannel = ClientConnection(configuration: configuration)
let otlpTraceExporter = OtlpTraceExporter(channel: grpcChannel, config: otlpConfiguration)
let spanProcessor = BatchSpanProcessor(spanExporter: otlpTraceExporter)
// Replace <service_name> with the name of your service
let resource = Resource(attributes: [
"service.name": AttributeValue.string("<service_name>")
])
let tracerProvider = TracerProviderBuilder()
.add(spanProcessor: spanProcessor)
.with(resource: resource)
.build()
OpenTelemetry.registerTracerProvider(tracerProvider: tracerProvider)
let tracer = OpenTelemetry.instance.tracerProvider.get(instrumentationName: instrumentationScopeName, instrumentationVersion: instrumentationScopeVersion)
Next Steps
- View your Traces to find performance bottlenecks.
- Create Custom Dashboards to monitor application health.
- Set up Alerts to get notified when things go wrong.
Sample Swift Application:
Get Help
If you need help with the steps in this topic, please reach out to us on SigNoz Community Slack.
If you are a SigNoz Cloud user, please use in product chat support located at the bottom right corner of your SigNoz instance or contact us at cloud-support@signoz.io.