os_log — یک unified logging API از Apple برای iOS و macOS است که جایگزین NSLog و os_trace شده است. برخلاف مکانیزمهای قدیمی، os_log در سطح هسته کار میکند: پیامها در یک بافر حلقوی بافر میشوند و تنها پس از رسیدن به آستانه فعالیت روی دیسک نوشته میشوند. طبق دادههای Apple WWDC 2016، os_log بار روی دیسک را در مقایسه با NSLog تا 10 برابر کاهش میدهد و از طریق دستهبندیها و انواع، کنترل سطح جزئیات را فراهم میکند. این ابزار اصلی تشخیصی برای توسعهدهنده iOS است: از طریق Console.app میتوان پیامها را بر اساس فرآیند، دستهبندی و سطح بحرانیت در زمان واقعی فیلتر کرد.
نکات کلیدی
os_log — یک unified logging API است که توسط Apple در iOS 10 و macOS Sierra معرفی شد. این API مکانیزمهای پراکنده لاگگیری 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 در مک یا از طریق دستور 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 کیلوبایت در Apple Watch تا 4 مگابایت در مک متغیر است. وقتی برنامه پیامهای بیشتری از ظرفیت بافر تولید میکند، پیامهای قدیمی از دست میروند — این رفتار مورد انتظار برای لاگگیری با حجم بالا است.
برای جمعآوری طولانیمدت همه پیامها از دستور 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 یا با استفاده از پروفایل جمعآوری از دستگاهی که به همان مک متصل است میبیند.
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 — ابزار خط فرمان برای خروجی گرفتن لاگهای جمعآوری شده از دستگاه. پس از اتصال دستگاه به مک از طریق 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. این ایمنتر است، اما برای هر آرگومان نیاز به تعیین صریح 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 و handlerهای حرکات برای ردیابی چرخه حیات صفحهها استفاده کنید.
os_log به طور پیشفرض رشتهها و اشیاء را به عنوان private ماسک میکند. برای دیدن مقدار، به صراحت privacy: .public را در درونیابی مشخص کنید. بدون این علامتگذاری، مقادیر در بیلدهای تولیدی با ماسک جایگزین میشوند، اما در اشکالزدایی از طریق Xcode به طور عادی نمایش داده میشوند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید