This episode covers production support for Swift apps: crash reporting and analytics, structured logging with OSLog along with telemetry and performance monitoring, health checks and user feedback loops, plus incident response and release rollback.

After an app is released, the work changes shape: from adding features to understanding how the app behaves in the hands of thousands of users. Episode 21 covers observability and production support — crash reporting, structured logging, telemetry, health checks, and the process of responding to incidents.
Observability answers three questions that determine production quality: what is happening, why it's happening, and how to prevent it from recurring. Without observability, every incident is an expensive puzzle.
Crash reporters like Firebase Crashlytics collect crash stack traces from user devices and present them in a dashboard. Key information for every crash:
Analytics records what users do — not just when they crash. A compact integration uses an SDK or custom telemetry:
struct Analytics {
static func catat(_ nama: String, data: [String: String] = [:]) {
var event = data
event["event"] = nama
event["app_version"] = Bundle.main.object(forInfoDictionaryKey:
"CFBundleShortVersionString") as? String ?? "?"
kirimEvent(event)
}
}
Analytics.catat("login_berhasil", data: ["metode": "apple"])Analytics.catat("login_berhasil", ...) records an event with app version context. Consistent event schema — stable names, documented fields — keeps analytics data answerable years later.
OSLog is Apple's official logging system, replacing print, which shouldn't be used in production. Logs are grouped by subsystem and category, and have severity levels:
import OSLog
let logger = Logger(subsystem: "com.example.Aplikasi",
category: "jaringan")
func ambilData() async throws -> Data {
logger.info("Memulai request")
do {
let (data, response) = try await URLSession.shared.data(from: url)
logger.debug("Respons: \(response)")
return data
} catch {
logger.error("Request gagal: \(error.localizedDescription)")
throw error
}
}let logger = Logger(subsystem: "com.example.Aplikasi", category: "jaringan") creates a logger per category. Levels like info, debug, and error help filter while debugging. OSLog logs can be accessed from the terminal:
log stream --predicate 'subsystem == "com.example.Aplikasi"'log stream --predicate 'subsystem == "..."' shows app logs in real time — the primary tool when verifying production behavior on a simulator or device.
Telemetry measures app health continuously: startup time, crash-free rate, request latency, and custom metrics. Tools like performance monitoring SDKs, or your own implementation recording the duration of critical operations:
let mulai = Date()
let data = try await ambilData()
let durasi = Date().timeIntervalSince(mulai)
print("Durasi request: \(durasi)s")let durasi = Date().timeIntervalSince(mulai) measures the operation time. Send this metric in aggregate — for example p95 — to detect subtle performance degradation before users complain.
For a Swift server (episode 19), a health check tells the orchestration platform whether an instance is healthy. Vapor provides it directly:
import Vapor
let healthRoute = app.get("health") { req in
return HTTPStatus.ok
}app.get("health") { req in return HTTPStatus.ok } returns status 200 while the server is alive. Platforms like Kubernetes and load balancers call this endpoint periodically to detect unresponsive instances.
User complaints are irreplaceable observability data. Build clear feedback channels: a report-a-problem button in the app, support email, and forums. When an incident happens, information from users often provides context that telemetry doesn't capture.
A recommended incident handling process:
Rollback returns the release to the previous stable version. A kill switch disables a specific feature remotely without a new release — invaluable for risky features:
struct Konfigurasi {
static let fiturRisikoAktif = ProcessInfo.processInfo
.environment["FITUR_RISIKO"] != "off"
}ProcessInfo.processInfo.environment["FITUR_RISIKO"] reads a flag from the environment — the server can disable a feature without redeploying. On client apps, a similar pattern uses remote config updated server-side.
Info
A well-handled incident isn't a failure — it's an investment. Each post-mortem produces runbooks, new dashboards, and alerts that make the next similar incident end far faster.
Key takeaways:
In the next episode, episode 22, we'll cover stable modern features and future trends — the latest stable Swift features like concurrency, macros, and result builders, Swift Package Manager improvements and ABI stability, Swift's strength in the Apple ecosystem and cross-platform, plus strategies to keep your Swift skills relevant. Your long journey comes to a close!