Introduction: Why Standard Monitoring Is Not Enough?
Basic monitoring of Swift applications (crashlytics, API response time) solves only part of the problems. At an advanced level, developers need to understand internal telemetry: delays in the UI thread, memory leaks on specific screens, the frequency of business logic execution, and the load on background queues. Without custom metrics and intelligent alerts, it is difficult to identify "silent" performance degradations before they affect users.
In this article, we will look at how to organize advanced monitoring of Swift applications using custom metrics, integration with Prometheus/Grafana, and setting up alerts based on percentiles and anomalies.
Metric Collection Architecture: From Application to Dashboard
Effective monitoring requires a three-tier architecture:
- Application Layer (Swift): collecting metrics via a custom MetricCollector module.
- Transport Layer: transmitting metrics to the server (HTTP, WebSocket, or gRPC).
- Visualization Layer: Prometheus + Grafana (or alternatives like Datadog).
Example of a basic metric collector in Swift:
import Foundation
final class MetricCollector { static let shared = MetricCollector() private var metrics: [String: Double] = [:] private let queue = DispatchQueue(label: "com.app.metrics", attributes: .concurrent) func record(_ name: String, value: Double) { queue.async(flags: .barrier) { self.metrics[name, default: 0] += value } } func snapshot() -> [String: Double] { queue.sync { return metrics } } func reset() { queue.async(flags: .barrier) { self.metrics.removeAll() } }}Integration with Prometheus: Exporting Metrics
To send data to Prometheus, use the OpenMetrics format. Create an HTTP endpoint that serves metrics on request:
import Vapor
func metricsRoute(req: Request) -> String { let snapshot = MetricCollector.shared.snapshot() var output = "" for (name, value) in snapshot { output += "# HELP \\(name) Custom metric" output += "# TYPE \\(name) gauge" output += "\\(name) \\(value)" } return output}
// Route registrationapp.get("metrics", use: metricsRoute)On the server side, configure Prometheus to collect metrics from this endpoint. In the Swift application, use a background queue to periodically send data (every 10-15 seconds).
Key Metrics for Advanced Monitoring
In addition to standard metrics (CPU, memory, network), add business and performance metrics:
- UI delays: time between CADisplayLink and rendering.
- Cache size: number of objects in NSCache on screens.
- API call frequency: number of requests per minute to a specific endpoint.
- Queue processing time: average task execution time in DispatchQueue.
Example of measuring block execution time:
func measure(_ name: String, block: () -> Void) { let start = CFAbsoluteTimeGetCurrent() block() let duration = CFAbsoluteTimeGetCurrent() - start MetricCollector.shared.record("\\(name)_duration_ms", value: duration * 1000)}Use this method to wrap critical code sections:
measure("data_processing") { // your data processing code}Configuring Intelligent Alerts
Alerts based on threshold values (e.g., CPU > 90%) often produce false positives. An advanced approach is alerts based on percentiles and anom