Swift OTel 1.0.0 Released

I’m thrilled to announce that Swift OTel just hit 1.0.0 :rocket:

I started Swift OTel shortly after completing the Google Summer of Code project for Swift Distributed Tracing in 2020, and back then it was solely focused on being a tracer. More than four years and a ton of amazing community contributions later, I couldn’t be happier about how the library evolved.

Thank you :orange_heart:

:glowing_star: Highlights

Logging, Metrics, and Distributed Tracing

Swift OTel comes with support for all three Swift observability libraries:

Streamlined, one-line bootstrap

It's super easy to bootstrap observability using Swift OTel with a single line:

// Bootstrap observability backends.
let observability = try OTel.bootstrap()

Then, run Swift OTel's background services directly ...

// Run the observability background tasks, alongside your application logic.

await withThrowingTaskGroup { group in
    group.addTask { try await observability.run() }
    // Your application logic here...
}

... or as part of a ServiceLifecycle ServiceGroup.

// Run observability services in a service group with your services.
let service: Service = // ...
let serviceGroup = ServiceGroup(
    services: [observability, service],
    logger: .init(label: "ServiceGroup")
)
try await serviceGroup.run()

It's that easy. Logs, metrics, and traces from your application and all its dependencies implementing one or all of the observability libraries will be exported via OTLP/HTTP+Protobuf to http://localhost:4318.

:diving_mask: Deep-Dive

Check out OTel.bootstrap(configuration:) to learn more about the one-line bootstrap.

Simple, centralized configuration

Overriding configuration values is also easy. You can change the configuration in code, via environment variables, or using a mixture of both.

// Start with defaults.
var config = OTel.Configuration.default

// Configure traces with specific OTLP/gRPC endpoint, mTLS, compression, and custom timeout.
config.traces.exporter = .otlp
config.traces.otlpExporter.endpoint = "https://otel-collector.example.com:4317"
config.traces.otlpExporter.protocol = .grpc
config.traces.otlpExporter.compression = .gzip
config.traces.otlpExporter.certificateFilePath = "/path/to/cert"
config.traces.otlpExporter.clientCertificateFilePath = "/path/to/cert"
config.traces.otlpExporter.clientKeyFilePath = "/path/to/key"
config.traces.otlpExporter.timeout = .seconds(3)
# Use environment variables from the OpenTelemetry specification to set configuration at runtime.
OTEL_SERVICE_NAME=my-awesome-app swift run

:diving_mask: Deep-Dive

Check out OTel.Configuration to learn more about configuring Swift OTel.

APIs for manual bootstrap and advanced use cases

Need more control over individual bootstraps? Swift OTel has got you covered here too.

Instead of the one-liner OTel.bootstrap, you can also use makeLoggingBackend(configuration:), makeMetricsBackend(configuration:), and makeTracingBackend(configuration:).

For example, if you want to use multiple log handlers in parallel, pass Swift OTel's log handler into a MultiplexLogHandler, then bootstrap manually:

// Create the logging backend without bootstrapping.
let otelBackend = try OTel.makeLoggingBackend()

// Manually bootstrap the logging subsystem with a multiplex handler.
LoggingSystem.bootstrap({ label in
    MultiplexLogHandler([
        otelBackend.factory(label),
        AnotherAwesomeLogHandler(label)
    ])
})

// Run the background service alongside your application.
let server = MockService(name: "AdopterServer")
let serviceGroup = ServiceGroup(
    services: [otelBackend.service, server],
    logger: .init(label: "ServiceGroup")
)
try await serviceGroup.run()

:diving_mask: Deep-Dive

Check out the following documentation pages to learn more about manually bootstrapping observability systems with Swift OTel:

Conditional transitive dependencies using package traits

By default, Swift OTel exports telemetry using OTLP/HTTP but also allows switching to OTLP/gRPC via OTel.Configuration. and/or OTEL_EXPORTER_OTLP_PROTOCOL (and its signal-specific friends). However, if you already know at compile-time that you want to only use one of the two protocols, you can win back some compilation time by disabling one of Swift OTel's package traits:

  • OTLPHTTP
  • OTLPGRPC

:diving_mask: Deep-Dive

We have a handful of examples that disable one of these package traits:

Console backend for Swift Log, for local development

When Swift OTel's log handler is bootstrapped, you won't see your logs in the console anymore. While this is desirable for deployments, you may want to have them in the console during development (instead of spinning up an observability tool locally to view your logs).

This is where Swift OTel's console backend for Swift Log comes into play. You can turn it on in code ...

var configuration = OTelConfiguration.default
configuration.logs.exporter = .console

... and/or via an environment variable:

OTEL_LOGS_EXPORTER=console swift run

:diving_mask: Deep-Dive

Check out OTel.Configuration.LogsConfiguration to learn more about Swift OTel's logging configuration options.

Example projects

Swift OTel's GitHub repository contains a ton of example projects using both Hummingbird and Vapor.

You can find them here: swift-otel/Examples at 1.0.0 · swift-otel/swift-otel · GitHub

:orange_heart: How to get involved

If you're an application developer using Swift we'd love to hear how Swift OTel works for you. Please file any issues you may experience on GitHub: Sign in to GitHub · GitHub

If you're a library developer we'd like to encourage you to add support for Swift's observability libraries if you don't already have it. Your users will greatly benefit from it, regardless of whether they're using Swift OTel or different observability backends.

18 Likes

Congrats! Such a nice piece of work and crucial piece in the ecosystem!

2 Likes

Thank you Tobias :folded_hands: And thanks for your numerous contributions to Swift OTel and specifically Swift W3C Trace Context :orange_heart:

2 Likes

As a complete noob in distributed tracing and open telemetry I’m a bit confused. How does this package relate to opentelemetry-swift ? I understand that Swift-Otel is a backend for Swift Log, Metrics and Distributed tracing. But does it use opentelemetry-swift under the hood or why does it not ?

It’s a good question. The short answer is captured in the opening paragraph of the README for Swift OTel:

An OpenTelemetry Protocol (OTLP) backend for Swift Log, Swift Metrics, and Swift Distributed Tracing.

Note: This package does not provide an OTel instrumentation API, or general-purpose OTel SDK.

A longer answer is that the Swift package ecosystem provides some packages that provide observability APIs that decouple the recording of telemetry from the collection and exporting of telemetry. We often refer to these as “API packages”, and, in this context, they are Swift Log, Swift Metrics, and Swift Distributed Tracing.

These dependencies can be widely adopted throughout the ecosystem, including in library packages, because they provide an abstraction layer. They are lightweight and do not impose any dependencies required for any specific export format.

It’s then down to the author of the executable to select and configure a backend package, which will then collect and export the telemetry for both the executable and all its instrumented dependencies. In this aspect, the system remains flexible and pluggable, and the choice of backend for logging, metrics, and traces remains with the application author.

We can consider these to be wire-protocol–agnostic APIs that are pluggable with a wire-protocol–specific backend package.

The OTel specification defines several things: it provides a specification, which defines an instrumentation API and guidelines for language-specific SDKs (similar to the API package pattern, but with its own, distinct API). However, it also defines a wire protocol, OTLP, for exporting logs, metrics, and traces.

Swift OTel is an OTLP backend for Swift Log, Metrics, and Distributed Tracing. It is the wire-protocol–specific backend for the wire-protocol–agnostic ecosystem observability API packages.

Concretely, it's not attempting to provide the API or SDK described by the OTel specification. Instead, it provides the necessary components to export telemetry recorded through the Swift Observability APIs, to an OTLP endpoint.

It’s a focussed and light-weight package.

For folks that prefer to use the OTel APIs for recording telemetry and/or to use an OTel SDK directly, then the opentelemetry-swift will be the right choice.

For folks that want a lightweight OTLP backend that only implements the Swift Log, Metrics, and Distributed Tracing APIs, then Swift OTel is an option.

Some of the above is explored in more detail in a recent talk at the ServerSide.swift 2025 conference: https://youtu.be/HSxIFLsoODc.

6 Likes

Waw thanks for the elaborate explanation!

If I understand you correctly, are you saying that both packages (opentelemetry-swift and Swift OTel) are implementing the wire protocol OTLP but that Swift OTel uses that to back the generic Swift Observability API’s and that opentelemetry-swift uses it to expose a custom OpenTelemetry API that fits their product best?

If that is correct I was wondering why that SDK from opentelemetry-swift was not used to back Swift OTel. Because how I understand, we now have two separate implementations of the same wire protocol. Is that correct? It would be nice to know what the decisioning behind that was. Is the implementation of the wire protocol done better or simpler inside Swift Otel?

I also see here that opentelemetry-swift has a bridging to use it with Swift-Log and Swift Metrics. How does that bridge differ from the one in Swift Otel?

While the landscape of both packages are both moving, Swift OTel currently has a very minimal set of dependencies, with a focus on being just enough for the Swift observability API backends. It also has a much reduced API surface since that's its primary focus.

The "wire protocol"—OTLP/gRPC or OTLP/HTTP—use protobuf types so there's not much duplication at that level.

As to how that's achieved, there are some differences, including the gRPC Swift v2 for OTLP/gRPC, the use of AsyncHTTPClient for OTLP/HTTP, the ability to only depend on what you need using package traits, and only offering an opinionated subset, which are aimed at a server-side audience.

Good spot and this is where there does start to be some overlap in scope. From a quick look there is no such bridge for Swift Distributed Tracing, but maybe I missed it. The bridges for Swift Metrics and Swift Log will likely provide adopters with an alternative route for an OTLP backend for these API packages.

With that said, I suspect these packages have a different focus and audience and that these have both evolved over time—they were created in 2020 and 2021, respectively. There's definitely room for this to continue to evolve, either by exploring how they might complement each other, or to make the distinct focus of each clearer.

4 Likes