os_log — това е unified logging API на Apple за iOS и macOS, което замени NSLog и os_trace. За разлика от старите механизми, os_log работи на ниво ядро: съобщенията се буферират в кръгов буфер и се записват на диска само при достигане на прага на активност. По данни на Apple WWDC 2016, os_log намалява натоварването на диска 10 пъти в сравнение с NSLog и дава контрол над нивото на детайлност чрез категории и типове. Това е основният диагностичен инструмент за iOS разработчика: чрез Console.app могат да се филтрират съобщенията по процес, категория и ниво на критичност в реално време.
Основни точки
os_log — това е unified logging API, представен от Apple в iOS 10 и macOS Sierra. Той обедини разпръснатите механизми за логване NSLog, os_trace и syslog в единна система с буфериране на ниво ядро XNU.
За разлика от NSLog, което синхронно записва всяко съобщение на диска и блокира нишката, os_log използва асинхронен кръгов буфер в паметта. Съобщенията се записват на диска само когато активността надхвърли определен праг или по команда log collect. Това радикално намалява влиянието на логването върху производителността на приложението.
os_log поддържа шест нива на критичност, разграничение по subsystem и category, както и вграден механизъм за поверителност: данни, отбелязани като private, автоматично се маскират в продуктовите логове и са достъпни само за разработчика при свързване на Xcode.
Преди iOS 10 разработчиците използваха NSLog за отстраняване на грешки и syslog за системни съобщения. NSLog пишеше в stderr и конзолата, но беше изключително неефективен: всяко съобщение се записваше синхронно на диска, причинявайки закъснения в интерфейса при често логване. os_log реши този проблем, премествайки буферирането на ниво BSD част на ядрото XNU и правейки записа на диска асинхронен.
os_log се използва във всички приложения на Apple и се препоръчва от Apple като единствения API за логване за iOS, macOS, tvOS и watchOS. Системата и приложенията на трети страни пишат чрез него съобщения в единна база данни — съхранявана в паметта и периодично изхвърляна на диска. Тези логове могат да се анализират чрез Console.app на Mac или чрез командата log в терминала.
Архитектурата на os_log се състои от три слоя: клиентски API в потребителското пространство (libsystem_trace.dylib), кръгов буфер в ядрото XNU и демон logd, който асинхронно изхвърля буфера на диска.
Когато приложението извика os_log, съобщението се копира в кръговия буфер на ядрото с размер няколко мегабайта. Буферът работи на принципа FIFO: ако се напълни, старите съобщения се презаписват с нови. Демонът logd периодично проверява буфера и запазва съобщенията във файлове .tracev3 в защитена зона на файловата система.
Според данни на Apple Engineering, типичното закъснение от извикването на os_log до появата на съобщението в Console.app е 1–5 секунди на устройството и до 60 секунди при пакетно изхвърляне на диска. Това е съзнателен компромис: производителността на приложението не страда от логването, но разработчикът вижда съобщенията с малко закъснение.
// Деклариране на os_log чрез OSLog
import OSLog
let logger = Logger(
subsystem: "com.example.app",
category: "network"
)
Кръговият буфер на os_log има фиксиран обем и не може да бъде променян от потребителското пространство. Размерът на буфера варира от 256 KB на Apple Watch до 4 MB на Mac. Когато приложението генерира повече съобщения, отколкото буферът може да побере, старите съобщения се губят — това е очаквано поведение за логване с голям обем.
За дългосрочно събиране на всички съобщения се използва командата log collect, която стартира демона за събиране на устройството и експортира .logarchive на компютъра на разработчика. В този режим буферът не се презаписва — съобщенията се записват директно в архива.
os_log поддържа пет нива на критичност, всяко от които отговаря за отделен тип съобщения и се обработва различно от системата. Ниво Default — основно за съобщения, които винаги попадат в буфера. Info и Debug се изключват в продуктовите компилации без профил за събиране. Error и Fault винаги са активни и се отбелязват с особен флаг в базата данни.
| Ниво | Значение | По подразбиране в буфера |
|---|---|---|
| Default | Обикновени съобщения, важни за диагностика | Да |
| Info | Информационни съобщения за подробен анализ | Не (само с профил) |
| Debug | Съобщения за отстраняване на грешки за разработка | Не (само с профил) |
| Error | Грешки, които изискват внимание | Да |
| Fault | Критични повреди, водещи до срив | Да |
Изборът на правилното ниво на критичност е важен за производителността: Info и Debug не се записват на диска в нормален режим, така че могат да се използват изобилно без риск от забавяне на приложението. Error и Fault винаги се запазват, но броят им трябва да бъде минимален — всяко такова съобщение увеличава времето за запис поради допълнителни метаданни.
Subsystem — идентификатор на приложението или модула във формат reverse-DNS (com.example.app). Category — текстов етикет в рамките на subsystem, който групира логовете по функционални области: network, ui, database, auth. Такава йерархия позволява филтриране на логовете без четене на всяко съобщение и събиране на статистика за всеки модул поотделно.
Apple препоръчва дефиниране на един OSLog на модул и използването му във всички файлове на този модул. За различните слоеве на приложението — networking, UI, persistence — трябва да се създадат отделни категории. Тогава в Console.app могат да се включат логове само за network и да се изключат за останалите, без прекомпилиране на приложението.
import OSLog
extension Logger {
static let network = Logger(
subsystem: "com.example.app",
category: "network"
)
static let ui = Logger(
subsystem: "com.example.app",
category: "ui"
)
}
os_log предоставя вграден механизъм за контрол на поверителността: всяка стойност във форматиращия низ може да бъде отбелязана като public, private или auto (поведение по подразбиране). По подразбиране os_log счита всички динамични низове и обекти за потенциално поверителни и ги заменя с маска <private> в продуктовите логове.
Това е критично важно за съответствие с изискванията на GDPR и HIPAA: ако приложението логва имейла на потребителя или номера на карта чрез os_log в автоматичен режим, реалните данни никога не попадат на диска. Разработчикът вижда пълното съобщение само при свързване чрез Xcode или при използване на профил за събиране от устройство, свързано към същия Mac.
let email = "user@example.com"
logger.log("User login: \(email, privacy: .public)")
// В продуктови логове: "User login: <private>"
// При отстраняване на грешки чрез Xcode: "User login: user@example.com"
logger.log("Payment token: \(token)")
Числата (Int, Double, Float) по подразбиране се считат за public — могат безопасно да се логват без маркиране. Низовете (String, NSString, StaticString) и обектите (NSObject, CFType) по подразбиране са private — маскират се в продукция. Статичните низове (низови литерали в кавички във форматиращия низ) винаги са видими — това е част от самото съобщение, а не данни.
Това поведение се различава от NSLog, където всички данни се логваха в открит вид. Преминаването към os_log значително намалява риска от изтичане на чувствителни потребителски данни чрез логове.
os_log е с 90–95% по-бърз от NSLog при високочестотно логване. В тест с 10 000 извиквания в цикъл, NSLog създава закъснение от около 2.8 секунди, докато os_log изпълнява същите извиквания за 0.3 секунди. Разликата се обяснява със синхронния запис на диска в NSLog срещу асинхронното буфериране в os_log.
Според данни на Apple Performance Lab (2016), iOS приложение с 20 извиквания на логване в секунда чрез NSLog губи 5–8 кадъра анимация в секунда поради блокиране на главната нишка. С os_log не се получава загуба на кадри, тъй като буферирането се извършва в отделна нишка на ядрото.
| Параметър | NSLog | os_log |
|---|---|---|
| Механизъм на запис | Синхронен запис на диска | Асинхронно буфериране в ядрото |
| Време за 10 000 извиквания | ~2.8 с | ~0.3 с |
| Влияние върху FPS | Загуба на 5–8 кадъра | 0 кадъра |
| Нива на критичност | Няма | 5 нива |
| Поверителност | Всички данни отворени | Автоматично маскиране |
| Филтриране | Не се поддържа | По subsystem / category / level |
os_log има два API: класически C os_log_create и модерна Swift обвивка Logger, представена в iOS 14. Swift Logger използва системата ResultBuilder за форматиране — аргументите се интерполират чрез низови литерали с изрично маркиране на поверителност.
import OSLog
let logger = Logger(
subsystem: "com.example.app",
category: "network"
)
func handleResponse(statusCode: Int) {
if statusCode > 399 {
logger.error("HTTP error: \(statusCode, privacy: .public)")
} else {
logger.info("Response OK: \(statusCode)")
}
}
log collect — команден ред инструмент за експорт на събраните логове от устройството. Стартира се от терминала след свързване на устройството към Mac чрез USB.
// Събиране на логове в .logarchive
// В терминала: log collect --device --output ./app_logs.logarchive
// Преглед на логове subsystem: log show --subsystem com.example.app
// Логване с динамични стойности
logger.log("User \(userId) opened screen \(screenName)")
При използване на Logger е важно да запомните, че аргументите се интерполират чрез String Interpolation, а не чрез форматиращи низове, както в C версията на os_log. Това е по-безопасно, но изисква изрично посочване на privacy за всеки аргумент, ако поведението по подразбиране не устройва разработчика.
Често задавани въпроси
os_log асинхронно буферира съобщения в ядрото и не блокира главната нишка, а NSLog синхронно записва на диска. os_log е 10 пъти по-бърз, дава 5 нива на критичност и автоматично маскира частни данни — NSLog няма нито едно от тези свойства.
За временни съобщения за отстраняване на грешки използвайте .debug — те се изключват в продуктовата компилация и не влияят на производителността на потребителите. За важни съобщения, които трябва винаги да се запазват, използвайте .default или .info.
Чрез Configure Profile в Xcode: Devices → изберете устройство → Open Console → Actions → Configure Profile. Задайте нивото на събиране за желания subsystem на Include. Това създава профил, който е активен до първото рестартиране на устройството.
Да, os_log работи във всички SwiftUI приложения без допълнителни настройки. Създайте статичен Logger в модела или в разширение на View и го използвайте в onChange, task и манипулатори на жестове за проследяване на жизнения цикъл на екраните.
os_log по подразбиране маскира низовете и обектите като private. За да видите стойността, изрично посочете privacy: .public в интерполацията. Без това маркиране стойностите ще бъдат заменени с маска в продуктовите компилации, а при отстраняване на грешки чрез Xcode се показват нормално.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също