os_log — to unified logging API od Apple dla iOS i macOS, który zastąpił NSLog i os_trace. W przeciwieństwie do starych mechanizmów, os_log działa na poziomie jądra: komunikaty są buforowane w buforze cyklicznym i zapisywane na dysk dopiero po osiągnięciu progu aktywności. Według danych Apple WWDC 2016, os_log zmniejsza obciążenie dysku 10-krotnie w porównaniu z NSLog i daje kontrolę nad poziomem szczegółowości poprzez kategorie i typy. Jest to podstawowe narzędzie diagnostyczne dla dewelopera iOS: przez Console.app można filtrować komunikaty według procesu, kategorii i poziomu krytyczności w czasie rzeczywistym.
Najważniejsze
os_log — to unified logging API, przedstawiony przez Apple w iOS 10 i macOS Sierra. Połączył rozproszone mechanizmy logowania NSLog, os_trace i syslog w jeden system z buforowaniem na poziomie jądra XNU.
W przeciwieństwie do NSLog, które synchronicznie zapisuje każdy komunikat na dysk i blokuje wątek, os_log używa asynchronicznego bufora cyklicznego w pamięci. Komunikaty są zrzucane na dysk tylko wtedy, gdy aktywność przekroczy zadany próg lub na polecenie log collect. To radykalnie zmniejsza wpływ logowania na wydajność aplikacji.
os_log obsługuje sześć poziomów krytyczności, rozgraniczenie według subsystem i category, a także wbudowany mechanizm prywatności: dane oznaczone jako private są automatycznie maskowane w logach produkcyjnych i dostępne tylko dla dewelopera po podłączeniu Xcode.
Przed iOS 10 deweloperzy używali NSLog do debugowania i syslog do komunikatów systemowych. NSLog pisał do stderr i do konsoli, ale był skrajnie nieefektywny: każdy komunikat był synchronicznie zapisywany na dysk, powodując opóźnienia w UI przy częstym logowaniu. os_log rozwiązał ten problem, przenosząc buforowanie na poziom BSD części jądra XNU i czyniąc zapis na dysk asynchronicznym.
os_log jest używany we wszystkich aplikacjach Apple i zalecany przez Apple jako jedyne API logowania dla iOS, macOS, tvOS i watchOS. System i aplikacje innych firm piszą przez niego komunikaty do jednej bazy danych — przechowywanej w pamięci i okresowo zrzucanej na dysk. Analizować te logi można przez Console.app na Macu lub przez polecenie log w terminalu.
Architektura os_log składa się z trzech warstw: klienckiego API w przestrzeni użytkownika (libsystem_trace.dylib), bufora cyklicznego w jądrze XNU i demona logd, który asynchronicznie zrzuca bufor na dysk.
Gdy aplikacja wywołuje os_log, komunikat jest kopiowany do bufora cyklicznego jądra o rozmiarze kilku megabajtów. Bufor działa na zasadzie FIFO: jeśli jest pełny, stare komunikaty są nadpisywane nowymi. Demon logd okresowo sprawdza bufor i zapisuje komunikaty do plików .tracev3 w chronionym obszarze systemu plików.
Według danych Apple Engineering, typowe opóźnienie od wywołania os_log do pojawienia się komunikatu w Console.app wynosi 1–5 sekund na urządzeniu i do 60 sekund przy zrzucie na dysk w trybie wsadowym. To świadomy kompromis: wydajność aplikacji nie cierpi z powodu logowania, ale deweloper widzi komunikaty z niewielkim opóźnieniem.
// Deklaracja os_log przez OSLog
import OSLog
let logger = Logger(
subsystem: "com.example.app",
category: "network"
)
Bufor cykliczny os_log ma stałą pojemność i nie można go zmienić z przestrzeni użytkownika. Rozmiar bufora waha się od 256 KB na Apple Watch do 4 MB na Macu. Gdy aplikacja generuje więcej komunikatów, niż mieści bufor, stare komunikaty są tracone — to oczekiwane zachowanie przy logowaniu o dużej objętości.
Do długoterminowego zbierania wszystkich komunikatów używa się polecenia log collect, które uruchamia demona zbierania na urządzeniu i eksportuje .logarchive na komputer dewelopera. W tym trybie bufor nie jest nadpisywany — komunikaty są zapisywane bezpośrednio do archiwum.
os_log obsługuje pięć poziomów krytyczności, z których każdy odpowiada za inny typ komunikatów i jest inaczej przetwarzany przez system. Poziom Default — podstawowy dla komunikatów, które zawsze trafiają do bufora. Info i Debug są wyłączane w kompilacjach produkcyjnych bez profilu zbierania. Error i Fault są zawsze aktywne i oznaczone specjalną flagą w bazie danych.
| Poziom | Znaczenie | Domyslne trafianie do bufora |
|---|---|---|
| Default | Zwykłe komunikaty ważne dla diagnostyki | Tak |
| Info | Komunikaty informacyjne do szczegółowej analizy | Nie (tylko z profilem) |
| Debug | Komunikaty debugowe do tworzenia aplikacji | Nie (tylko z profilem) |
| Error | Błędy wymagające uwagi | Tak |
| Fault | Krytyczne awarie prowadzące do crasha | Tak |
Wybór odpowiedniego poziomu krytyczności jest ważny dla wydajności: Info i Debug nie są zapisywane na dysk w trybie normalnym, więc można ich używać obficie bez ryzyka spowolnienia aplikacji. Error i Fault są zawsze zapisywane, ale ich liczba powinna być minimalna — każdy taki komunikat wydłuża czas zapisu z powodu dodatkowych metadanych.
Subsystem — to identyfikator aplikacji lub modułu w formacie reverse-DNS (com.example.app). Category — etykieta tekstowa w ramach subsystem, która grupuje logi według obszarów funkcjonalnych: network, ui, database, auth. Taka hierarchia pozwala filtrować logi bez czytania każdego komunikatu i zbierać statystyki dla każdego modułu osobno.
Apple zaleca definiowanie jednego OSLog na moduł i używanie go we wszystkich plikach tego modułu. Dla różnych warstw aplikacji — networking, UI, persistence — należy tworzyć osobne kategorie. Wtedy w Console.app można włączyć logi tylko dla network i wyłączyć dla pozostałych, bez przekompilowywania aplikacji.
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 zapewnia wbudowany mechanizm kontroli prywatności: każda wartość w stringu formatującym może być oznaczona jako public, private lub auto (zachowanie domyślne). Domyślnie os_log uważa wszystkie dynamiczne stringi i obiekty za potencjalnie poufne i zastępuje je maską <private> w logach produkcyjnych.
Jest to krytycznie ważne dla zgodności z wymogami GDPR i HIPAA: jeśli aplikacja loguje email użytkownika lub numer karty przez os_log w trybie auto, prawdziwe dane nigdy nie trafią na dysk. Deweloper widzi pełny komunikat tylko po podłączeniu przez Xcode lub przy użyciu profilu zbierania z urządzenia podłączonego do tego samego Maca.
let email = "user@example.com"
logger.log("User login: \(email, privacy: .public)")
// W logach produkcyjnych: "User login: <private>"
// W debugowaniu przez Xcode: "User login: user@example.com"
logger.log("Payment token: \(token)")
Liczby (Int, Double, Float) domyślnie są uważane za public — można je bezpiecznie logować bez oznaczania. Stringi (String, NSString, StaticString) i obiekty (NSObject, CFType) domyślnie są private — maskowane w produkcji. Statyczne stringi (literały stringowe w cudzysłowie wewnątrz stringa formatującego) są zawsze widoczne — to część samego komunikatu, a nie dane.
To zachowanie różni się od NSLog, gdzie wszystkie dane były logowane w jawnej postaci. Przejście na os_log znacząco zmniejsza ryzyko wycieku wrażliwych danych użytkownika przez logi.
os_log jest o 90–95% szybszy od NSLog przy logowaniu o wysokiej częstotliwości. W teście z 10 000 wywołań w pętli NSLog tworzy opóźnienie około 2.8 sekundy, podczas gdy os_log wykonuje te same wywołania w 0.3 sekundy. Różnica wynika z synchronicznego zapisu na dysk w NSLog w porównaniu z asynchronicznym buforowaniem w os_log.
Według danych Apple Performance Lab (2016), aplikacja na iOS z 20 wywołaniami logowania na sekundę przez NSLog traci 5–8 klatek animacji na sekundę z powodu blokowania głównego wątku. Z os_log utrata klatek nie występuje, ponieważ buforowanie odbywa się w osobnym wątku jądra.
| Parametr | NSLog | os_log |
|---|---|---|
| Mechanizm zapisu | Synchroniczny zapis na dysk | Asynchroniczne buforowanie w jądrze |
| Czas na 10 000 wywołań | ~2.8 s | ~0.3 s |
| Wpływ na FPS | Strata 5–8 klatek | 0 klatek |
| Poziomy krytyczności | Brak | 5 poziomów |
| Prywatność | Wszystkie dane jawne | Auto-masking |
| Filtrowanie | Nieobsługiwane | Według subsystem / category / level |
os_log ma dwa API: klasyczne C-owskie os_log_create i nowoczesne opakowanie Swift Logger, wprowadzone w iOS 14. Swift Logger używa systemu ResultBuilder do formatowania — argumenty są interpolowane przez literały stringowe z jawnym oznaczaniem prywatności.
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 — narzędzie wiersza poleceń do wyeksportowania zebranych logów z urządzenia. Uruchamiane z Terminala po podłączeniu urządzenia do Maca przez USB.
// Zbieranie logów do .logarchive
// W terminalu: log collect --device --output ./app_logs.logarchive
// Podgląd logów subsystem: log show --subsystem com.example.app
// Logowanie z wartościami dynamicznymi
logger.log("User \(userId) opened screen \(screenName)")
Przy użyciu Logger należy pamiętać, że argumenty są interpolowane przez String Interpolation, a nie przez stringi formatujące, jak w C-owej wersji os_log. Jest to bezpieczniejsze, ale wymaga jawnego określenia privacy dla każdego argumentu, jeśli domyślne zachowanie nie odpowiada deweloperowi.
Często zadawane pytania
os_log asynchronicznie buforuje komunikaty w jądrze i nie blokuje głównego wątku, a NSLog synchronicznie zapisuje na dysk. os_log jest 10 razy szybszy, daje 5 poziomów krytyczności i automatycznie maskuje prywatne dane — NSLog nie ma żadnej z tych właściwości.
Do tymczasowych komunikatów debugowych używaj .debug — są wyłączane w kompilacji produkcyjnej i nie wpływają na wydajność użytkowników. Do ważnych komunikatów, które powinny być zawsze zachowywane, używaj .default lub .info.
Przez Configure Profile w Xcode: Devices → wybierz urządzenie → Open Console → Actions → Configure Profile. Ustaw poziom zbierania dla potrzebnego subsystem na Include. Tworzy to profil, który jest aktywny do pierwszego restartu urządzenia.
Tak, os_log działa we wszystkich aplikacjach SwiftUI bez dodatkowej konfiguracji. Utwórz statyczny Logger w modelu lub w rozszerzeniu View i używaj go w onChange, task oraz handlerach gestów do śledzenia cyklu życia ekranów.
os_log domyślnie maskuje stringi i obiekty jako private. Aby zobaczyć wartość, jawnie określ privacy: .public w interpolacji. Bez tego oznaczenia wartości zostaną zastąpione maską w kompilacjach produkcyjnych, a w debugowaniu przez Xcode są wyświetlane normalnie.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również