DevOps

Мониторинг и логирование production-приложений

Введение

После публикации релиза важно знать, как приложение реально себя ведёт у пользователей — в статье разберём мониторинг и логирование production-приложений, связывая spdlog-цикл с практикой реального, развёрнутого приложения, а не только локальной разработки.

Концепция

Для desktop/мобильных приложений традиционный, серверный мониторинг (постоянно работающие дашборды состояния сервиса) неприменим напрямую — вместо этого используется сбор отчётов о крашах (crash reporting, автоматическая отправка диагностической информации при неожиданном завершении приложения у пользователя) и, опционально, телеметрии использования (анонимизированные данные о том, какие функции реально используются, для приоритизации дальнейшей разработки). Логирование, настроенное согласно spdlog-циклу, должно учитывать, что логи production-приложения у конечного пользователя обычно недоступны разработчику напрямую (в отличие от логов сервера, CAN-цикла, физически доступных администратору) — для реальной диагностики проблем у пользователей нужен явный механизм передачи логов разработчику (либо автоматический, с согласия пользователя, либо вручную, при обращении пользователя в поддержку).

Пример кода

// Интеграция простого crash-reporting через перехват необработанных исключений
// и установку handler для критичных системных сигналов (концептуальный пример)
#include <csignal>
#include <spdlog/spdlog.h>

void crashSignalHandler(int signal)
{
    spdlog::critical("Приложение завершилось аварийно, сигнал: {}", signal);
    spdlog::shutdown(); // гарантированный сброс накопленных логов перед завершением

    // В реальном production-приложении здесь была бы также попытка
    // сохранить минимальный дамп состояния и/или отправить отчёт о краше
    // (через специализированную библиотеку, например Sentry SDK, Crashpad)

    std::exit(signal);
}

void setupCrashHandling()
{
    std::signal(SIGSEGV, crashSignalHandler);
    std::signal(SIGABRT, crashSignalHandler);
}
// Опциональная, согласованная с пользователем отправка диагностических логов
class DiagnosticsUploader : public QObject
{
    Q_OBJECT
public:
    void uploadLogsIfUserConsented()
    {
        QSettings settings;
        if (!settings.value("diagnostics/userConsent", false).toBool()) {
            return; // пользователь явно не дал согласие — логи НЕ отправляются никуда
        }

        QFile logFile("logs/app.log");
        // ... отправка содержимого файла лога на сервер диагностики приложения ...
    }
};
// Простая, анонимизированная телеметрия использования функций (с явным согласием пользователя)
void trackFeatureUsage(const QString &featureName)
{
    if (!isAnonymousTelemetryEnabled()) {
        return;
    }
    // Отправка только названия использованной функции — БЕЗ каких-либо персональных
    // или идентифицирующих конкретного пользователя данных
    sendTelemetryEvent(featureName);
}

Пояснения к коду

setupCrashHandling() показывает базовый, низкоуровневый подход к перехвату критичных сбоев — обработчики сигналов SIGSEGV/SIGABRT дают последний шанс зафиксировать факт краша через spdlog::critical() (с обязательным spdlog::shutdown() для гарантированного сброса накопленных в асинхронном логгере сообщений, статья 336) перед фактическим завершением процесса, хотя для production-качественного решения стоит использовать специализированные, значительно более развитые библиотеки crash-reporting (Sentry, Crashpad), способные собрать значительно более полную диагностическую информацию (стек вызовов на момент краша), чем простой перехват сигнала. uploadLogsIfUserConsented()/trackFeatureUsage() явно демонстрируют принцип, критично важный для desktop-приложений, распространяемых конечным пользователям — любой сбор и передача диагностических данных должен быть явно обусловлен согласием пользователя (diagnostics/userConsent), а не происходить автоматически, без его осведомлённости и явного разрешения, что важно как с этической, так и с юридической точки зрения (законодательство о защите персональных данных, упоминавшееся в основном цикле статей про безопасность).

Подводные камни

  • Сбор и отправка диагностических данных/телеметрии без явного, осознанного согласия пользователя — это не просто практика, вызывающая недовольство пользователей при обнаружении, но и потенциальное юридическое нарушение применимого законодательства о защите персональных данных (в зависимости от юрисдикции и характера собираемых данных), и явный, понятный пользователю механизм согласия (как показано через проверку userConsent в примере) — не опциональная «вежливость», а часто прямое юридическое требование.
  • Сбор чрезмерного объёма диагностических данных «на всякий случай», включая потенциально чувствительную информацию (содержимое открытых пользователем файлов, личные данные, видимые в логах в виде значений переменных) без явной необходимости для реальной диагностики технических проблем — диагностические логи и crash-репорты должны быть спроектированы с явным минимизирующим принципом, собирая лишь действительно необходимую для технической диагностики информацию, а не максимально возможный объём данных без разбора.
  • Отсутствие у разработчика реального способа получить диагностическую информацию при проблеме у конкретного пользователя, если пользователь не дал согласие на автоматическую отправку (что является его законным правом) и при этом сам не может предоставить логи вручную (не знает, где они находятся, как их найти и отправить) — стоит предусмотреть удобный, понятный для обычного, не технически продвинутого пользователя способ найти и при необходимости вручную отправить файл лога (например, через простую кнопку «Отправить отчёт о проблеме» в интерфейсе приложения, формирующую и отправляющую архив с логами в одно действие).
  • Перехват сигналов краша без понимания, что обработчик сигнала выполняется в крайне ограниченном, потенциально нестабильном контексте (память процесса может быть уже частично повреждена тем самым событием, вызвавшим сигнал) — попытка выполнить сложную, ресурсоёмкую логику (как сложная сетевая отправка отчёта) непосредственно внутри обработчика сигнала может сама по себе привести к дальнейшим проблемам или зависанию; специализированные библиотеки crash-reporting (упомянутые как предпочтительная альтернатива простому самостоятельному перехвату сигналов) спроектированы именно с учётом этих тонких, но важных ограничений безопасного поведения внутри обработчика сигнала о краше.