> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bronto.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Send Swift logs and traces to Bronto via OpenTelemetry

> Instrument iOS, macOS, and server-side Swift apps with the OpenTelemetry Swift SDK to send logs and traces to Bronto over OTLP via a Collector.

This page covers instrumenting a Swift application with the OpenTelemetry SDK to send **logs and traces** to Bronto over OTLP/HTTP via a local OTel Collector.

<Tip>
  If you don't have a Collector and want to export directly from your application to Bronto, see [Direct export to Bronto](#direct-export-to-bronto) at the bottom of this page.
</Tip>

## Prerequisites

* Swift 5.7 or later
* Xcode 14 or later, or Swift Package Manager on Linux
* A running OTel Collector configured to forward logs and traces to Bronto — see [Connect Open Telemetry to Bronto](/agent-setup/open-telemetry)
* [OpenTelemetry Swift SDK repository](https://github.com/open-telemetry/opentelemetry-swift)

## Install dependencies

Add the `opentelemetry-swift` package to your `Package.swift`:

```swift Package.swift theme={"dark"}
// swift-tools-version: 5.7
import PackageDescription

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

| Product             | Purpose                            |
| ------------------- | ---------------------------------- |
| `OpenTelemetryApi`  | Core OTel API                      |
| `OpenTelemetrySdk`  | SDK — `LoggerProvider`, processors |
| `OtlpHttpExporter`  | OTLP/HTTP exporter                 |
| `ResourceExtension` | Helpers for building `Resource`    |

## Configure the log bridge

Set up a `LoggerProvider` with an OTLP exporter and register it as the global OTel logs provider.

```swift OtelLogging.swift theme={"dark"}
import Foundation
import OpenTelemetryApi
import OpenTelemetrySdk
import OtlpHttpExporter
import ResourceExtension

func configureOtelLogging() {
    let resource = DefaultResources().get()
        .merging(other: Resource(attributes: [
            ResourceAttributes.serviceName.rawValue:
                AttributeValue.string("my-service"),
            ResourceAttributes.serviceNamespace.rawValue:
                AttributeValue.string("my-team"),
            "deployment.environment":
                AttributeValue.string("production"),
        ]))

    let exporterEndpoint = URL(string: "http://localhost:4318/v1/logs")!
    let exporter = OtlpHttpLogExporter(endpoint: exporterEndpoint)

    let processor = BatchLogRecordProcessor(
        logRecordExporter: exporter,
        scheduleDelay: 5.0
    )

    let loggerProvider = LoggerProviderBuilder()
        .with(resource: resource)
        .with(processors: [processor])
        .build()

    OpenTelemetry.registerLoggerProvider(loggerProvider: loggerProvider)
}
```

Call `configureOtelLogging()` once at application startup, before the first log emission.

## Configure the OTLP exporter

The `exporterEndpoint` in the snippet above connects to the OTel Collector on `localhost:4318`. If your Collector runs on a different host or port, update the URL accordingly.

## Set resource attributes

Resource attributes are attached to every log record exported from this process. Two attributes drive how Bronto organises incoming logs:

| OTel attribute      | Bronto concept | Description                                  |
| ------------------- | -------------- | -------------------------------------------- |
| `service.name`      | Dataset        | Groups logs from one service                 |
| `service.namespace` | Collection     | Groups related services or a team's services |

These are set via `Resource(attributes:)` in the setup above.

## Complete example

```swift main.swift theme={"dark"}
import OpenTelemetryApi

configureOtelLogging()

let logger = OpenTelemetry.instance.loggerProvider
    .loggerBuilder(instrumentationScopeName: "my-service")
    .build()

var record = ReadableLogRecord()
record.body = AttributeValue.string("Application started")
record.severity = .info
logger.emit(logRecord: record)

var warnRecord = ReadableLogRecord()
warnRecord.body = AttributeValue.string("Low disk space")
warnRecord.severity = .warn
warnRecord.attributes = ["disk_free_gb": AttributeValue.double(2.1)]
logger.emit(logRecord: warnRecord)
```

<Note>
  A direct bridge to Apple's `os.Logger` (Unified Logging) is not yet available in the official SDK. The example above emits log records via the OTel Logs API directly. For apps targeting Apple platforms, you can emit to both `os.Logger` and OTel from a thin wrapper function.
</Note>

## Verify delivery

After running your application, check both signals in Bronto:

* **Logs**: open the [Search](https://app.bronto.io/search) page and filter by the dataset name you set in `service.name` — your log records should appear within a few seconds.
* **Traces**: open the Explore Traces page and filter by the same `service.name` — your spans should appear within a few seconds.

If no logs appear, check:

* The OTel Collector is running and reachable at the configured endpoint.
* The Collector's pipeline includes a `logs` pipeline with an `otlp` receiver and the Bronto exporter — see [Connect Open Telemetry to Bronto](/agent-setup/open-telemetry).
* `configureOtelLogging()` is called before the first log emission.
* The `BatchLogRecordProcessor` exports on a background timer — ensure your process does not exit before the first flush interval.

## Traces

<Tip>
  Auto-instrumentation for Swift is currently limited. Check the [OpenTelemetry Swift registry](https://opentelemetry.io/ecosystem/registry/?language=swift\&component=instrumentation) for available community instrumentation packages. For URLSession and networking, community wrappers exist that create spans automatically.
</Tip>

### Install tracing dependencies

Add `StdoutExporter` or `OtlpHttpTraceExporter` from the same `opentelemetry-swift` package — no additional Swift Package Manager entries are needed. Add `OpenTracingShim` if you need OpenTracing compatibility:

```swift Package.swift theme={"dark"}
.product(name: "OpenTelemetrySdk",   package: "opentelemetry-swift"),
.product(name: "OtlpHttpExporter",   package: "opentelemetry-swift"),
```

### Configure the tracer provider

```swift OtelTracing.swift theme={"dark"}
import OpenTelemetryApi
import OpenTelemetrySdk
import OtlpHttpExporter

func configureOtelTracing(resource: Resource) {
    let traceEndpoint = URL(string: "http://localhost:4318/v1/traces")!
    let traceExporter = OtlpHttpTraceExporter(endpoint: traceEndpoint)

    let spanProcessor = BatchSpanProcessor(spanExporter: traceExporter)

    let tracerProvider = TracerProviderBuilder()
        .with(resource: resource)
        .with(processors: [spanProcessor])
        .build()

    OpenTelemetry.registerTracerProvider(tracerProvider: tracerProvider)
}
```

Pass the same `resource` used for logging so both signals share `service.name` and `service.namespace`.

### Creating spans

```swift theme={"dark"}
let tracer = OpenTelemetry.instance.tracerProvider
    .get(instrumentationName: "my-service", instrumentationVersion: nil)

let span = tracer.spanBuilder(spanName: "process-payment").startSpan()
span.setAttribute(key: "payment.amount", value: AttributeValue.double(99.99))
// your code here
span.end()
```

## GenAI semantic conventions

If your application calls an LLM, OpenTelemetry defines [GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md). There is no first-party GenAI auto-instrumentation package for Swift yet — set the attributes yourself around each LLM call:

<Note>
  This page was verified on **July 17, 2026**. GenAI libraries and semantic conventions are evolving rapidly, so package configuration and emitted attributes can change between releases. Keep your instrumentation current and verify the fields emitted by the version you deploy.
</Note>

```swift theme={"dark"}
let tracer = OpenTelemetry.instance.tracerProvider
    .get(instrumentationName: "my-service", instrumentationVersion: nil)

let span = tracer.spanBuilder(spanName: "chat gpt-4o-mini").startSpan()
span.setAttribute(key: "gen_ai.provider.name", value: AttributeValue.string("openai"))
span.setAttribute(key: "gen_ai.request.model", value: AttributeValue.string("gpt-4o-mini"))
span.setAttribute(key: "gen_ai.usage.input_tokens", value: AttributeValue.int(33))
span.setAttribute(key: "gen_ai.usage.output_tokens", value: AttributeValue.int(74))
span.setAttribute(key: "gen_ai.response.finish_reasons", value: AttributeValue.stringArray(["stop"]))
span.end()
```

<Note>
  Message content (`gen_ai.input.messages` / `gen_ai.output.messages`) is opt-in by convention in languages with auto-instrumentation, off by default. Since you're setting attributes by hand here, apply the same discipline — gate prompt/response content behind your own config flag rather than always sending it.
</Note>

See [LLM Observability](/ai-features/llm-observability) for the full recommended attribute set and how to search and aggregate GenAI fields in Bronto.

## Direct export to Bronto

If you are not using an OTel Collector, export directly to Bronto by replacing the exporter configurations with the Bronto OTLP endpoints and your API key:

```swift theme={"dark"}
let config = OtlpConfiguration(
    timeout: TimeInterval(10),
    headers: [("x-bronto-api-key", "<YOUR_API_KEY>")]
)

let logExporter = OtlpHttpLogExporter(
    endpoint: URL(string: "https://ingestion.eu.bronto.io/v1/logs")!, // or ingestion.us.bronto.io
    config: config
)

let traceExporter = OtlpHttpTraceExporter(
    endpoint: URL(string: "https://ingestion.eu.bronto.io/v1/traces")!, // or ingestion.us.bronto.io
    config: config
)
```

| Region | Logs endpoint                            | Traces endpoint                            |
| ------ | ---------------------------------------- | ------------------------------------------ |
| EU     | `https://ingestion.eu.bronto.io/v1/logs` | `https://ingestion.eu.bronto.io/v1/traces` |
| US     | `https://ingestion.us.bronto.io/v1/logs` | `https://ingestion.us.bronto.io/v1/traces` |

See [API Keys](/Account-Management/API-Keys) for how to create a key with ingestion permissions. No other changes to the rest of the setup are required.
