Observability в Swift: трейсинг, метрики и логи для профи

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое Observability и зачем она нужна Swift-разработчику?

Вы когда-нибудь пытались понять, почему ваше Swift-приложение на iOS тормозит именно на экране профиля, а серверная часть на Vapor отвечает с задержкой в 3 секунды только для пользователей из Европы? Если да — вы уже сталкивались с проблемой, которую решает observability (наблюдаемость).

Observability — это не просто «логирование». Это способность задавать системе любые вопросы о её состоянии без необходимости писать новый код. Три столпа observability: трейсинг (трассировка), метрики и логи. Вместе они дают полную картину происходящего: от запроса пользователя до выполнения в глубине базы данных.

В этой статье мы разберём, как внедрить observability в Swift-проектах — как на клиенте (iOS), так и на сервере (Vapor / Hummingbird). Вы узнаете, какие библиотеки использовать, как писать эффективные трейсы и метрики, и как не утонуть в логах.

Почему Swift — идеальный кандидат для Observability?

Swift — язык с сильной типизацией, производительностью на уровне C и растущей экосистемой для серверной разработки. Но есть нюанс: сообщество Swift только начинает активно внедрять observability. В отличие от Java или Go, где OpenTelemetry уже стал стандартом, в Swift мир observability — это свежий тренд, который даёт ранним последователям огромное преимущество.

Основные вызовы для Swift-разработчика:

  • Асинхронность: async/await и Structured Concurrency меняют правила игры для трейсинга.
  • Многоплатформенность: код на Swift может работать на iOS, macOS, сервере и даже embedded.
  • Отсутствие единого стандарта: пока нет «золотой пули», как в мире Java.

Но хорошая новость в том, что Swift предоставляет все инструменты для построения надёжной observability — нужно лишь знать, куда смотреть.

Трейсинг (Tracing) в Swift: от запроса до ответа

Что такое трейсинг и зачем он нужен?

Трейсинг (или распределённая трассировка) позволяет отследить путь одного запроса через все сервисы и функции приложения. В мире микросервисов это незаменимо. Но даже в монолитном iOS-приложении трейсинг помогает понять, какая именно часть кода тормозит.

OpenTelemetry Swift: ваш главный инструмент

OpenTelemetry — это открытый стандарт для observability. Для Swift существует пакет opentelemetry-swift. Он поддерживает трейсинг, метрики и логи, а также экспорт данных в любой бэкенд (Jaeger, Prometheus, Grafana).

Установка через Swift Package Manager:

// Package.swift
.package(url: "https://github.com/open-telemetry/opentelemetry-swift.git", from: "1.10.0")

Пишем первый трейс на Swift

Представьте, что у вас есть функция загрузки профиля пользователя. Без трейсинга вы видите только общее время выполнения. С трейсингом — каждый шаг:

import OpenTelemetryApi
import OpenTelemetrySdk

let tracer = OpenTelemetry.instance.tracerProvider.get(
    instrumentationName: "ProfileModule",
    instrumentationVersion: "1.0.0"
)

func loadUserProfile(userId: String) async throws -> UserProfile {
    let span = tracer.spanBuilder(spanName: "loadUserProfile")
        .setAttribute(key: "user.id", value: userId)
        .startSpan()
    
    defer {
        span.end()
    }
    
    // Шаг 1: проверка кэша
    let cacheSpan = tracer.spanBuilder(spanName: "checkCache").startSpan()
    let cached = checkCache(for: userId)
    cacheSpan.end()
    
    if let cachedProfile = cached {
        span.setAttribute(key: "cache.hit", value: true)
        return cachedProfile
    }
    
    // Шаг 2: запрос к API
    let apiSpan = tracer.spanBuilder(spanName: "fetchFromAPI").startSpan()
    let profile = try await api.fetchProfile(userId: userId)
    apiSpan.end()
    
    // Шаг 3: обработка данных
    let processSpan = tracer.spanBuilder(spanName: "processProfile").startSpan()
    let processed = processProfile(profile)
    processSpan.end()
    
    span.setAttribute(key: "cache.hit", value: false)
    return processed
}

Обратите внимание на defer { span.end() } — это гарантирует, что трейс закроется даже при ошибке. В Swift это особенно удобно благодаря строгой обработке ошибок.

Structured Concurrency и трейсинг

С появлением async/await и Task Groups трейсинг стал ещё мощнее. Вы можете автоматически создавать дочерние трейсы для каждой параллельной задачи:

await withTaskGroup(of: Data.self) { group in
    let parentSpan = tracer.spanBuilder(spanName: "parallelFetch").startSpan()
    
    for url in urls {
        group.addTask {
            let childSpan = tracer.spanBuilder(spanName: "fetchURL")
                .setParent(parentSpan)
                .startSpan()
            defer { childSpan.end() }
            return try await downloadData(from: url)
        }
    }
    
    for await result in group {
        // обработка
    }
    parentSpan.end()
}

Это даёт вам полную картину: какой URL тормозит, сколько времени заняла каждая подзадача.

Метрики (Metrics) в Swift: считаем всё, что движется

Зачем нужны метрики, если есть трейсинг?

Трейсинг отвечает на вопрос «почему этот конкретный запрос медленный?». Метрики — на вопрос «как много запросов медленные в целом?». Это два разных уровня абстракции. Метрики позволяют увидеть тренды: рост времени ответа, увеличение числа ошибок, падение производительности после релиза.

OpenTelemetry Metrics API

OpenTelemetry Swift предоставляет три типа метрик: Counter, Histogram и Gauge. Рассмотрим каждый.

Counter — счётчик событий

let requestCounter = meter.createCounter(name: "api.requests.total")
requestCounter.add(value: 1, attributes: ["endpoint": "profile", "status": "200"])

Histogram — распределение значений

Идеально для измерения времени выполнения:

let latencyHistogram = meter.createHistogram(
    name: "api.latency",
    unit: "ms",
    description: "Response time in milliseconds"
)

let startTime = Date()
// ... вызов API ...
let duration = Date().timeIntervalSince(start) * 1000
latencyHistogram.record(value: duration, attributes: ["endpoint": "profile"])

Gauge — текущее значение

Например, количество активных пользователей в памяти:

let activeUsersGauge = meter.createGauge(name: "active.users")
activeUsersGauge.record(value: Double(activeUsersCount))

Продвинутый пример: метрики для iOS-приложения

На клиенте метрики помогают отслеживать производительность UI. Используем SwiftUI и Combine для автоматического сбора:

class PerformanceMonitor: ObservableObject {
    private let meter = OpenTelemetry.instance.meterProvider.get(
        instrumentationName: "iOSApp",
        instrumentationVersion: "1.0"
    )
    
    private let frameDropCounter: Counter
    
    init() {
        frameDropCounter = meter.createCounter(name: "ui.frame                

Блоги

Рекомендаций по книгам