Мониторинг и логирование Swift приложений: отладка без боли

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

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

Начать курс

Ваше приложение работает идеально? Давайте проверим

Вы когда-нибудь запускали Swift-приложение в продакшн и молились, чтобы оно не упало? Я — да. И каждый раз, когда пользователь писал «оно зависло», я чувствовал себя слепым котёнком. Логирование и мониторинг — это те самые глаза и уши, которые превращают хаос в порядок. Без них вы гадаете, что пошло не так. С ними — видите каждый шаг.

В этой статье мы разберём, как настроить мониторинг Swift приложений так, чтобы вы спали спокойно. Никакой сухой теории — только живой код, реальные кейсы и лайфхаки, которые я собирал годами. Погнали!

Почему логирование в Swift — это не просто print()

Первый порыв любого разработчика — воткнуть print("здесь всё сломалось") в каждом закруглении. И это работает… пока вы не выпустили приложение. В продакшене print исчезает в пустоте, а пользователь остаётся один на один с багом. Нам нужно нечто большее.

Современное логирование Swift приложений — это структурированные данные, уровни важности и возможность удалённого сбора. Apple даёт нам мощный инструмент — OSLog. Он лёгкий, быстрый и встроен прямо в систему. Давайте посмотрим, как его приручить.

OSLog: базовый уровень

Подключаем фреймворк и создаём логгер:

import OSLog

let logger = Logger(
    subsystem: "com.pythonlib.monitoring",
    category: "network"
)

func fetchUserData() {
    logger.info("Начинаем загрузку данных пользователя")
    // ... код запроса ...
    logger.debug("Получен ответ: \(response.statusCode)")
}

Обратите внимание на subsystem и category. Это ваши теги для фильтрации. Когда у вас 50 модулей, вы сможете отсеять только логи сети или только логи базы данных — без лишнего шума.

Уровни логирования: когда кричать, а когда шептать

OSLog поддерживает шесть уровней. Вот как их использовать:

  • debug — для разработки, отключается в релизе (если не включить explicitly).
  • info — полезная информация, которая не засоряет консоль.
  • notice — важные события, например, успешный запуск.
  • error — ошибки, которые можно обработать.
  • fault — критические сбои, после которых приложение может упасть.

Пример с реальной ошибкой:

func handleApiError(_ error: Error) {
    logger.error("Ошибка API: \(error.localizedDescription, privacy: .public)")
    // privacy: .public — чтобы видеть текст в логах, иначе будет скрыто
}

Мониторинг Swift приложений: метрики и производительность

Логи — это прошлое. Мониторинг Swift приложений — это настоящее. Вы хотите знать, что происходит прямо сейчас: сколько памяти жрёт ваш код, не тормозит ли анимация, не падает ли частота кадров.

Apple предоставляет MetricKit — фреймворк для сбора диагностики на устройствах пользователей. Он собирает краш-репорты, данные о батарее и метрики запуска. Но для полноценного мониторинга часто подключают сторонние сервисы.

Подключаем Crashlytics (Firebase) — быстро и больно

Crashlytics остаётся стандартом индустрии. Вот минимальная настройка:

import FirebaseCrashlytics

// Где-то при старте приложения
Crashlytics.crashlytics().setUserID("user_123")
Crashlytics.crashlytics().setCustomValue(true, forKey: "is_premium")

// Логируем нефатальные ошибки
do {
    try riskyOperation()
} catch {
    Crashlytics.crashlytics().record(error: error)
    logger.error("Нефатальная ошибка: \(error)")
}

Важный момент: не логируйте персональные данные пользователя без согласия. Используйте setUserID только если у вас есть анонимизированный идентификатор.

Отправляем метрики вручную

Иногда хочется знать, сколько раз пользователь нажал на кнопку «Купить», но не завершил покупку. Для этого заводим кастомные события:

Analytics.logEvent("purchase_attempt", parameters: [
    "product_id": productId,
    "price": price
])

Это может быть Firebase Analytics, Amplitude или ваш собственный сервер. Главное — единый формат и консистентность.

Расширенное логирование Swift приложений: пишем свой менеджер

Стандартный OSLog хорош, но когда проектов становится много, хочется иметь единую точку входа. Давайте напишем простой LogManager, который объединит OSLog и удалённую отправку.

import OSLog
import Foundation

enum LogLevel: String {
    case debug, info, notice, error, fault
}

final class LogManager {
    static let shared = LogManager()
    private let osLogger: Logger
    
    private init() {
        osLogger = Logger(subsystem: "com.pythonlib.app", category: "general")
    }
    
    func log(
        _ message: String,
        level: LogLevel = .info,
        file: String = #file,
        function: String = #function,
        line: Int = #line
    ) {
        let fileName = (file as NSString).lastPathComponent
        let formatted = "[\(fileName):\(line)] \(function) -> \(message)"
        
        switch level {
        case .debug:   osLogger.debug("\(formatted)")
        case .info:    osLogger.info("\(formatted)")
        case .notice:  osLogger.notice("\(formatted)")
        case .error:   osLogger.error("\(formatted)")
        case .fault:   osLogger.fault("\(formatted)")
        }
        
        // Отправляем на сервер только ошибки и фатальные сбои
        if level == .error || level == .fault {
            sendToRemote(formatted)
        }
    }
    
    private func sendToRemote(_ message: String) {
        // Здесь ваш код отправки на сервер или в Crashlytics
        // Например, URLSession POST запрос
    }
}

Теперь в любом месте приложения пишем:

LogManager.shared.log("Пользователь вышел из аккаунта", level: .info)

И всё — и в консоль, и на сервер (если уровень серьёзный). Удобно, единообразно, масштабируемо.

Логирование Swift приложений в фоне: не потеряйте ни строчки

Одна из частых проблем — логи теряются, когда приложение уходит в фон. iOS может прибить процесс в любой момент. Решение — использовать os_log с типом .persistent или писать в файл.

Вот пример записи в файл с обработкой фонового режима:

func writeLogToFile(_ message: String) {
    guard let documentsDir = FileManager.default.urls(
        for: .documentDirectory, in: .userDomainMask
    ).first else { return }
    
    let logFile = documentsDir.appendingPath                

Блоги

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