Реализация сбора диагностических логов в мобильном приложении

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.
Разработка и поддержка любых видов мобильных приложений:
Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Предлагаемые услуги
Показано 1 из 1 услугВсе 1735 услуг
Реализация сбора диагностических логов в мобильном приложении
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1054
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    445

Реализация сбора диагностических логов в мобильном приложении

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

Архитектура логирования

In-memory кольцевой буфер

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

// Android: кольцевой буфер логов
class LogBuffer(private val capacity: Int = 500) {
    private val buffer = ArrayDeque<LogEntry>(capacity)

    @Synchronized
    fun add(level: LogLevel, tag: String, message: String) {
        if (buffer.size >= capacity) buffer.removeFirst()
        buffer.addLast(LogEntry(
            timestamp = System.currentTimeMillis(),
            level = level,
            tag = tag,
            message = message
        ))
    }

    @Synchronized
    fun getLast(count: Int): List<LogEntry> =
        buffer.takeLast(minOf(count, buffer.size))
}

500 записей — достаточно, чтобы восстановить последние несколько минут работы приложения. Больше — обычно избыточно и занимает заметный объём памяти.

Timber tree для Android

class DiagnosticTree(private val buffer: LogBuffer) : Timber.Tree() {
    override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
        val level = when (priority) {
            Log.DEBUG -> LogLevel.DEBUG
            Log.INFO -> LogLevel.INFO
            Log.WARN -> LogLevel.WARN
            Log.ERROR -> LogLevel.ERROR
            else -> LogLevel.VERBOSE
        }
        buffer.add(level, tag ?: "App", message)
        // В Debug-сборке дополнительно пишем в Android Logcat
        if (BuildConfig.DEBUG) super.log(priority, tag, message, t)
    }
}

// Application.onCreate()
Timber.plant(DiagnosticTree(logBuffer))
Timber.plant(if (BuildConfig.DEBUG) Timber.DebugTree() else SilentTree())

iOS — OSLog + in-memory buffer

// Кастомный Logger с буфером
class DiagnosticLogger {
    private let osLog = Logger(subsystem: "com.example.app", category: "diagnostic")
    private var buffer: [LogEntry] = []
    private let maxEntries = 500
    private let queue = DispatchQueue(label: "logger", qos: .utility)

    func log(_ message: String, level: LogLevel = .info, file: String = #file, line: Int = #line) {
        let entry = LogEntry(
            timestamp: Date(),
            level: level,
            message: message,
            location: "\(URL(fileURLWithPath: file).lastPathComponent):\(line)"
        )
        queue.async { [weak self] in
            guard let self else { return }
            if self.buffer.count >= self.maxEntries {
                self.buffer.removeFirst()
            }
            self.buffer.append(entry)
        }
        // OSLog для Instruments и Console.app
        osLog.log(level: level.osLogType, "\(message)")
    }
}

#file и #line — автоматическая привязка к месту вызова. В диагностическом отчёте видно не только «что произошло», но и «где в коде».

Сохранение в файл

При сборе диагностики сериализуем буфер в файл:

suspend fun exportDiagnosticReport(): File = withContext(Dispatchers.IO) {
    val file = File(context.cacheDir, "diagnostic_${System.currentTimeMillis()}.txt")
    file.bufferedWriter().use { writer ->
        writer.appendLine("=== App: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) ===")
        writer.appendLine("=== Device: ${Build.MANUFACTURER} ${Build.MODEL}, Android ${Build.VERSION.RELEASE} ===")
        writer.appendLine("=== Report generated: ${Date()} ===")
        writer.appendLine()
        logBuffer.getLast(500).forEach { entry ->
            writer.appendLine("[${entry.levelTag}] ${entry.formattedTime} ${entry.tag}: ${entry.message}")
        }
    }
    file
}

Обфускация чувствительных данных

Логи не должны содержать токены, пароли или данные карт. Фильтрация на уровне logger-tree:

private val sensitivePatterns = listOf(
    Regex("""Bearer\s+[\w\-._~+/]+=*"""),          // Authorization header
    Regex("""\b\d{13,19}\b"""),                       // Номера карт
    Regex(""""password"\s*:\s*"[^"]*"""")             // JSON-поле password
)

override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
    var sanitized = message
    sensitivePatterns.forEach { pattern ->
        sanitized = pattern.replace(sanitized, "[REDACTED]")
    }
    buffer.add(/* ... */, sanitized)
}

Отправка отчёта пользователем

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

// iOS: Share sheet для отправки файла
let diagnosticURL = try await DiagnosticLogger.shared.exportReport()
let activityVC = UIActivityViewController(
    activityItems: [diagnosticURL],
    applicationActivities: nil
)
present(activityVC, animated: true)

Дополнительно — возможность отправки напрямую в support-тикет через API Zendesk/Freshdesk как attachment.

Ориентиры по срокам

Реализация in-memory буфера, Timber tree / OSLog-обёртки и экспорта файла — 3–5 дней. С обфускацией чувствительных данных, интеграцией с helpdesk и UI для пользователя — до 1 недели.