> ## 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, metrics, and traces to Bronto

> Instrument iOS, macOS, and server-side Swift apps with the OpenTelemetry Swift SDK to send logs, metrics, and traces to Bronto over OTLP/HTTP, via an OTel Collector or directly.

One configuration function sends **logs, metrics, and traces** from your Swift application to Bronto. All three providers share a single `Resource`, so telemetry from the process carries one identity.

The application exports to an [OpenTelemetry Collector](/agent-setup/open-telemetry), which forwards to Bronto. If you don't run a Collector, [export directly to Bronto](#direct-export-to-bronto) instead — only the endpoint and an API key header change.

<Note>
  The official SDK has no bridge to Apple's `os.Logger` (Unified Logging), and auto-instrumentation coverage is thinner than in other languages. Log records are emitted through the OTel Logs API directly, as shown below. Check the [OpenTelemetry Swift registry](https://opentelemetry.io/ecosystem/registry/?language=swift) for community bridges and `URLSession` instrumentation.
</Note>

## Prerequisites

* Swift 5.7 or later; Xcode 14 or later, or Swift Package Manager on Linux
* An OTel Collector reachable from your application, with `logs`, `metrics`, and `traces` pipelines forwarding to Bronto — see [Connect OpenTelemetry Collector to Bronto](/agent-setup/open-telemetry)

## Install dependencies

```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"),
            ]
        ),
    ]
)
```

## Initialise the SDK

Call `configureOtel()` once at startup, before the first span or log emission:

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

let otlpEndpoint = "http://localhost:4318"

func configureOtel() {
    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"),
        ]))

    // Logs
    let logExporter = OtlpHttpLogExporter(
        endpoint: URL(string: "\(otlpEndpoint)/v1/logs")!)
    let loggerProvider = LoggerProviderBuilder()
        .with(resource: resource)
        .with(processors: [BatchLogRecordProcessor(logRecordExporter: logExporter)])
        .build()
    OpenTelemetry.registerLoggerProvider(loggerProvider: loggerProvider)

    // Metrics
    let metricExporter = OtlpHttpMetricExporter(
        endpoint: URL(string: "\(otlpEndpoint)/v1/metrics")!)
    let meterProvider = MeterProviderBuilder()
        .with(resource: resource)
        .with(reader: PeriodicMetricReaderBuilder(exporter: metricExporter).build())
        .build()
    OpenTelemetry.registerMeterProvider(meterProvider: meterProvider)

    // Traces
    let spanExporter = OtlpHttpTraceExporter(
        endpoint: URL(string: "\(otlpEndpoint)/v1/traces")!)
    let tracerProvider = TracerProviderBuilder()
        .with(resource: resource)
        .add(spanProcessor: BatchSpanProcessor(spanExporter: spanExporter))
        .build()
    OpenTelemetry.registerTracerProvider(tracerProvider: tracerProvider)
}
```

<Note>
  The metrics builder names have changed across `opentelemetry-swift` releases. If the metrics block doesn't compile against your version, check the [examples in the opentelemetry-swift repository](https://github.com/open-telemetry/opentelemetry-swift/tree/main/Examples).
</Note>

Two resource attributes determine how Bronto organises your data:

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

<Note>
  `http://localhost:4318` is the standard OTLP/HTTP address for a Collector on the same host. Use the address reachable from your application if the Collector runs in another container, pod, or host. No authentication is needed between the application and the Collector — the Collector holds the Bronto API key.
</Note>

<Warning>
  On iOS and macOS apps shipped to users, do not embed a Bronto API key in the binary — it can be extracted. Send telemetry to a Collector or gateway you control, and let it hold the key.
</Warning>

## Instrument your application

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

configureOtel()

let logger = OpenTelemetry.instance.loggerProvider
    .loggerBuilder(instrumentationScopeName: "my-service")
    .build()
let tracer = OpenTelemetry.instance.tracerProvider
    .get(instrumentationName: "my-service")
let payments = OpenTelemetry.instance.meterProvider
    .get(name: "my-service")
    .createIntCounter(name: "app.payments")

let span = tracer.spanBuilder(spanName: "process-payment").startSpan()
span.setAttribute(key: "payment.amount", value: 99.99)
payments.add(value: 1)

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

span.end()
```

A log record emitted inside an active span picks up that span's `trace_id` and `span_id` from the current context, so you can jump from a log line to its trace in Bronto.

## Verify

Run your application, then check each signal in Bronto, filtering by the `service.name` you set:

* **Logs** — the [Search](https://app.bronto.io/search) page, in the dataset named after your service
* **Metrics** — the Metric Explorer
* **Traces** — the Explore Traces page

If nothing arrives:

* Confirm the Collector is running and reachable at the configured endpoint, and that its pipelines include an `otlp` receiver and the Bronto exporters.
* Confirm `configureOtel()` runs before the first log or span.
* The batch processors export on a background timer — make sure the process does not exit, or the app is not suspended, before the first flush.
* On iOS, App Transport Security blocks plaintext HTTP by default. Use an `https://` endpoint, or add a development ATS exception for your local Collector.

For signal-specific reference material, see [Send Metrics to Bronto](/metrics/send-metrics) and [Send Traces to Bronto](/tracing/send-traces).

## Direct export to Bronto

Without a Collector, the application exports straight to Bronto over OTLP/HTTP. Point the endpoint at your Bronto region and add your API key:

```swift theme={"dark"}
let otlpEndpoint = "https://ingestion.eu.bronto.io" // or ingestion.us.bronto.io

let logExporter = OtlpHttpLogExporter(
    endpoint: URL(string: "\(otlpEndpoint)/v1/logs")!,
    config: OtlpConfiguration(headers: [("x-bronto-api-key", "<YOUR_API_KEY>")]))
```

Apply the same configuration to the metric and span exporters.

| Region | Base endpoint                    |
| ------ | -------------------------------- |
| EU     | `https://ingestion.eu.bronto.io` |
| US     | `https://ingestion.us.bronto.io` |

See [API Keys](/Account-Management/API-Keys) for how to create a key with ingestion permissions.

<Tip>
  Prefer the Collector — it batches, filters, and enriches telemetry, and keeps the API key out of the application. For distributed mobile apps that is a security requirement, not just a preference.
</Tip>

## GenAI semantic conventions

If your application calls an LLM, OpenTelemetry's [GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md) define `gen_ai.*` span attributes for model, token usage, and prompt and response content.

### Manual spans

There is no first-party GenAI instrumentation for Swift, so set the attributes yourself around each model call:

```swift theme={"dark"}
let span = tracer.spanBuilder(spanName: "chat gpt-4o-mini").startSpan()

span.setAttribute(key: "gen_ai.provider.name", value: "openai")
span.setAttribute(key: "gen_ai.request.model", value: "gpt-4o-mini")
span.setAttribute(key: "gen_ai.usage.input_tokens", value: 33)
span.setAttribute(key: "gen_ai.usage.output_tokens", value: 74)
// Bronto flattens array attributes — this is searchable as finish_reasons.0
span.setAttribute(key: "gen_ai.response.finish_reasons",
                  value: AttributeValue.array(AttributeArray(values: [.string("stop")])))

span.end()
```

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

See the [GenAI span conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md) for the full attribute list.

For the recommended attribute set and Bronto search queries, see [LLM Observability](/ai-features/llm-observability).
