Custom Spans
Generates: Spans
Measure the duration of any operation — function calls, image processing, complex calculations — using OpenTelemetry spans.
Closure-Based (Recommended)
Wrap any synchronous or async operation. The span starts before the closure runs and ends automatically when it returns or throws.
// Synchronous
let result = Pulse.shared.trackSpan(
name: "parse_json",
params: ["format": "json", "payload.size_bytes": data.count]
) {
try JSONDecoder().decode(Response.self, from: data)
}
// Async
let users = await Pulse.shared.trackSpan(name: "fetch_users") {
try await api.getUsers()
}
Manual Start / End
For spans whose lifetime spans multiple functions or async boundaries:
let span = Pulse.shared.startSpan(
name: "image_upload",
params: [
"file.size_bytes": imageData.count,
"file.format": "jpeg",
]
)
defer { span.end() }
do {
let url = try await uploadService.upload(imageData)
span.setAttribute(key: "upload.url", value: AttributeValue.string(url.absoluteString))
} catch {
span.setAttribute(key: "error", value: AttributeValue.bool(true))
span.setAttribute(key: "error.message", value: AttributeValue.string(error.localizedDescription))
throw error
}
warning
Always call span.end() on manually started spans. Use defer { span.end() } to guarantee it runs even on thrown errors.
Adding Attributes During Execution
let span = Pulse.shared.startSpan(name: "database_query")
defer { span.end() }
let results = try database.query("SELECT * FROM products WHERE category = ?", args: [category])
// Add result metadata after the operation
span.setAttribute(key: "db.rows_returned", value: AttributeValue.int(results.count))
span.setAttribute(key: "db.query_plan", value: AttributeValue.string("index_scan"))
Nested Spans
Spans created inside trackSpan are automatically nested as child spans:
await Pulse.shared.trackSpan(name: "checkout_flow") {
await Pulse.shared.trackSpan(name: "validate_cart") {
try await cart.validate()
}
await Pulse.shared.trackSpan(name: "process_payment") {
try await payment.process()
}
await Pulse.shared.trackSpan(name: "send_confirmation") {
try await notifications.sendOrderConfirmation()
}
}
Direct OpenTelemetry Access
For advanced use cases, access the underlying tracer directly:
if let otel = Pulse.shared.getOpenTelemetry() {
let tracer = otel.tracerProvider.get(
instrumentationName: "my-library",
instrumentationVersion: "1.0.0"
)
let span = tracer
.spanBuilder(spanName: "custom_operation")
.setSpanKind(spanKind: .client)
.startSpan()
// ... do work ...
span.end()
}
Generated Telemetry
Type: Span
Span Kind: Internal (unless overridden via direct OTel access)
Attributes
| Attribute | Description | Always Present |
|---|---|---|
session.id | Current session identifier | ✅ Yes |
screen.name | Active UIViewController at span start | ⚠️ If available |
| (your params) | All key/value pairs from params | ⚠️ As provided |
Sample Payload
{
"name": "fetch_users",
"kind": "INTERNAL",
"duration_ms": 234,
"status": "OK",
"attributes": {
"session.id": "f40364c92b85ec0c19c35a65be42b97f",
"screen.name": "HomeViewController"
}
}