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.

11 Likes

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

1 Like

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

1 Like