Swift.assert()
в вашем коде? Я, честно, ставлю довольно часто (Если это плохая практика, то, пожалуйста, напишите в комментариях — почему это плохо? Опыт разработки у меня достаточно небольшой). В моем коде часто можно встретить такой вызов:
Swift.assert(Thread.isMainThread)
Не так давно я решил, что неплохо бы продолжить наблюдать результаты от этих вызовов не только в рамках запуска приложения в симуляторе / на девайсе, но и от действий реальных пользователей. Кстати, здесь речь может идти и про Swift.precondition()
, про Swift.fatalError()
и т.п, хотя их я стараюсь избегать. Более подробно про Unrecoverable Errors in Swift я читал в этой публикации и она оказалось очень даже познавательной.
Ближе к делу: В коде у меня нашлось около 300 подобных вызовов. Ни один из них не срабатывал при обычном локальном тестировании — и это меня радовало. Но я предположил, что действия пользователя могут все-таки привести к срабатыванию некоторых из вызовов.
С точки зрения пользователя — вылетов происходить не должно (Опять же мое мнение). В крайнем случае — пользователь должен понять, что какой-то сценарий пошел не по плану и команда разработчиков уже работает над починкой. Подобные исключения всегда обрабатывались мной и для пользователя это могло сказаться в самой безобидной форме: например, одна из сотен ячеек таблицы просто была невидима.
С пользователем более-менее все ясно. Осталось разобраться с доставкой логов до разработчика. Во-первых, требовалось минимальными усилиями заменить в коде текущие вызовы на вызовы, отправляющие логи куда-то за пределы приложения. Во-вторых, требовалось точно локализовать место происшествия, иначе соотнести исключение с реальным кодом было бы практически невозможно. В-третьих, следовало учесть, что видоизмененные вызовы могут сработать при Unit-тестировании, где Thread.isMainThread
уже должен игнорироваться т.к. я использую RxText фреймворк для определенных видов тестрирования (здесь я тоже готов выслушать советы и критику). Главным пунктом осталось то, что локально все исключения должны срабатывать как и раньше т.е. Loggin.assert()
должен срабатывать тогда же, когда бы срабатывал Swift.assert()
Отличный способ отправки событий предоставляет Fabric (Crashlytics). Выглядит это следующим образом:
Crashlytics.sharedInstance().recordCustomExceptionName("", reason: ""...
Осталось упаковать Crashlytics в какой-нибудь фреймворк, который можно подгружать в полноценном виде в приложение и в урезанном виде (без зависимости Crashlytics) в тестовые таргеты.
«Упаковку» я решил сделать через CocoaPods. Код для «боевого таргета» стал выглядеть следующим образом:
import Crashlytics public enum Logging { public static func send(_ reason: String? = nil, __file: String = #file, __line: Int = #line) { let file = __file.components(separatedBy: "/").last ?? __file let line = "\(__line)" let name = [line, file].joined(separator: "_") Crashlytics.sharedInstance().recordCustomExceptionName(name, reason: reason ?? "no reason", frameArray: []) } public static func assert(_ assertion: @escaping @autoclosure () -> Bool, reason: String? = nil, __file: String = #file, __line: Int = #line) { if assertion() == false { self.assertionFailure(reason, __file: __file, __line: __line) } } public static func assert(_ assertion: @escaping () -> Bool, reason: String? = nil, __file: String = #file, __line: Int = #line) { if assertion() == false { self.assertionFailure(reason, __file: __file, __line: __line) } } public static func assertionFailure(_ reason: String? = nil, __file: String = #file, __line: Int = #line) { Swift.assertionFailure(reason ?? "") self.send(reason, __file: __file, __line: __line) } }
Для «тестового таргета» т.е. без зависимости Crashlytics:
import Foundation public enum Logging { public static func send(_ reason: String? = nil, __file: String = #file, __line: Int = #line) { // } public static func assert(_ assertion: @escaping @autoclosure () -> Bool, reason: String? = nil, __file: String = #file, __line: Int = #line) { // } public static func assert(_ assertion: @escaping () -> Bool, reason: String? = nil, __file: String = #file, __line: Int = #line) { // } public static func assertionFailure(_ reason: String? = nil, __file: String = #file, __line: Int = #line) { // } }
Результаты:
Исключения действительно начали срабатывать. Большая часть уведомляла о некорректном формате получаемых данных: Decodable
иногда получал данные с несоответствующим типом. Иногда срабатывали логи для Thread.isMainThread
, которые очень оперативно исправлялись в следующих релизах. Самыми интересными ошибками стали чудом выловленные NSException.
Спасибо за внимание.
P.S. Если вдруг вы будете слишком часто слать подобные логи в Crashlytics, то «их» система может распознать ваши действия как спам. И вы увидите следующее сообщение:
Due to improper usage, non-fatal reporting has been disabled for multiple builds. Learn how to re-enable reporting in our documentation.
Поэтому, стоит заранее продумать частоту отправки логов. Иначе все логи сборки могут оказаться под угрозой игнорирования сервисом Crashlytics
ссылка на оригинал статьи https://habr.com/ru/post/476792/
Добавить комментарий