Introduction: Why Monitoring Swift Applications is a Must-Have for Production
\nWhen your Swift application goes into production, it's not enough to simply ensure it doesn't crash. Modern users demand responsiveness, stability, and speed. Without a monitoring system, you are flying blind: UI delays, memory leaks, slow network requests — all of this goes unnoticed until the first negative reviews. In this article, we will cover the advanced level of monitoring Swift applications: from collecting custom metrics to setting up real-time alerts. You will learn how to use os_log, Signposts, integration with Prometheus and Grafana, and how to automatically respond to anomalies.
1. Metrics Collection Architecture in Swift: From OSLog to Custom Providers
\nBasic monitoring in Swift is built on os_log and OSSignposter. But for advanced analysis, this is not enough. You need a unified data bus that aggregates metrics from different sources: network, UI, background processing, memory.
1.1. Using OSSignposter for Performance Measurement
\nOSSignposter is a powerful profiling tool. It allows you to create interval and discrete events that are then visible in Instruments and can be exported to custom dashboards.
import os\n\nlet signposter = OSSignposter(subsystem: "com.yourapp.network", category: "API")\n\n// Measuring request execution time\nlet state = signposter.beginInterval("FetchUserData", id: signposter.makeSignpostID())\nURLSession.shared.dataTask(with: url) { data, _, error in\n if let error = error {\n signposter.emitEvent("Error", "\\(error.localizedDescription)")\n }\n signposter.endInterval("FetchUserData", state)\n}.resume()\nTip: group intervals by operation type (network, database, rendering) to see the overall picture in the dashboard.
\n\n1.2. Custom MetricCollector
\nCreate a singleton class that collects metrics into a thread-safe buffer and sends them to the server every N seconds. This is the foundation for integration with any backend.
\nfinal class MetricCollector {\n static let shared = MetricCollector()\n private let queue = DispatchQueue(label: "com.yourapp.metrics", qos: .utility)\n private var buffer: [String: Double] = [:]\n \n func record(_ name: String, value: Double) {\n queue.async {\n self.buffer[name, default: 0] += value\n }\n }\n \n func flush() -> [String: Double] {\n queue.sync {\n let copy = self.buffer\n self.buffer.removeAll()\n return copy\n }\n }\n}\n\n2. Custom Metrics for Swift: What to Measure and How
\nStandard metrics (FPS, memory usage) are the basics. The advanced level requires business metrics and user experience metrics.
\n- \n
- Time to Interactive (TTI): Measurement from app launch to the moment the user can perform the first action. \n
- Business Logic Error Rate: For example, failed login attempts, payment failures. \n
- Cache and Disk Size: Control to ensure the app doesn't take up gigabytes. \n
- Animation Speed: Measuring frames per second (FPS) using CADisplayLink. \n
2.1. Example: FPS Monitoring with Low Performance Alert
\nimport UIKit\n\nfinal class FPSMonitor {\n private var displayLink: CADisplayLink?\n private var lastTimestamp: CFTimeInterval = 0\n private var frameCount = 0\n \n func start() {\n displayLink = CADisplayLink(target: self, selector: #selector(tick))\n displayLink?.add(to: .main, forMode: .common)\n }\n \n @objc private func tick() {\n frameCount += 1\n let delta = displayLink?.targetTimestamp ?? 0 - lastTimestamp\n if