What is Observability and Why Does a Swift Developer Need It?
\n\nHave you ever tried to figure out why your Swift iOS app lags specifically on the profile screen, or why your server-side Vapor application responds with a 3-second delay only for users from Europe? If so, you've already encountered the problem that observability solves.
\n\nObservability is not just "logging." It is the ability to ask your system any questions about its state without needing to write new code. The three pillars of observability are: tracing, metrics, and logs. Together, they provide a complete picture of what's happening: from the user's request to execution deep within the database.
\n\nIn this article, we'll explore how to implement observability in Swift projects — both on the client (iOS) and on the server (Vapor / Hummingbird). You'll learn which libraries to use, how to write effective traces and metrics, and how not to drown in logs.
\n\nWhy Swift is an Ideal Candidate for Observability?
\n\nSwift is a language with strong typing, C-level performance, and a growing ecosystem for server-side development. But there's a nuance: the Swift community is only beginning to actively adopt observability. Unlike Java or Go, where OpenTelemetry has already become a standard, observability in the Swift world is a fresh trend that gives early adopters a huge advantage.
\n\nKey challenges for a Swift developer:
\n- \n
- Asynchrony: async/await and Structured Concurrency change the rules for tracing. \n
- Multi-platform: Swift code can run on iOS, macOS, servers, and even embedded systems. \n
- Lack of a unified standard: there is no "silver bullet" yet, unlike in the Java world. \n
But the good news is that Swift provides all the tools for building reliable observability — you just need to know where to look.
\n\nTracing in Swift: From Request to Response
\n\nWhat is Tracing and Why is it Needed?
\nTracing (or distributed tracing) allows you to track the path of a single request through all services and functions of an application. In the world of microservices, this is indispensable. But even in a monolithic iOS application, tracing helps to understand which specific part of the code is slowing things down.
\n\nOpenTelemetry Swift: Your Main Tool
\nOpenTelemetry is an open standard for observability. For Swift, there is the opentelemetry-swift package. It supports tracing, metrics, and logs, as well as exporting data to any backend (Jaeger, Prometheus, Grafana).
Installation via Swift Package Manager:
\n// Package.swift\n.package(url: "https://github.com/open-telemetry/opentelemetry-swift.git", from: "1.10.0")\n\nWriting Your First Trace in Swift
\nImagine you have a function for loading a user profile. Without tracing, you only see the total execution time. With tracing, you see every step:
\n\nimport OpenTelemetryApi\nimport OpenTelemetrySdk\n\nlet tracer = OpenTelemetry.instance.tracerProvider.get(\n instrumentationName: "ProfileModule",\n instrumentationVersion: "1.0.0"\n)\n\nfunc loadUserProfile(userId: String) async throws -> UserProfile {\n let span = tracer.spanBuilder(spanName: "loadUserProfile")\n .setAttribute(key: "user.id", value: userId)\n .startSpan()\n \n defer {\n span.end()\n }\n \n // Step 1: check cache\n let cacheSpan = tracer.spanBuilder(spanName: "checkCache").startSpan()\n let cached = checkCache(for: userId)\n cacheSpan.end()\n \n if let cachedProfile = cached {\n span.setAttribute(key: "cache.hit", value: true)\n return cachedProfile\n }\n \n // Step 2: API request\n let apiSpan = tracer.spanBuilder(spanName: "fetchFromAPI").startSpan()\n let profile = try await api.fetchPro