Structured Logging رویکردی برای ثبت لاگها است که در آن هر پیام در قالبی ماشینخوان با جفتهای کلید-مقدار ارائه میشود، نه به صورت متن غیرساختاریافته. برخلاف رشتههای مسطح، لاگهای ساختاریافته شامل فراداده هستند: timestamp، سطح، ماژول، شناسه درخواست — و میتوانند توسط سیستمهای تحلیلی ایندکس شوند. بر اساس دادههای O'Reilly Effective Logging، انتقال به فرمتهای ساختاریافته زمان جستجوی حادثه را از ساعتها به دقیقه کاهش میدهد به لطف امکان فیلتر کردن بر اساس فیلدها. این استاندارد دوفاکتو در توسعه مدرن موبایل و سرور است: JSON و logfmt امکان پردازش لاگها توسط برنامهها را فراهم میکنند، نه با چشم.
نکات اصلی
Structured Logging روشی برای ثبت لاگها است که در آن هر پیام شامل فیلدهای نامگذاری شده با مقادیر تایپشده است. به جای رشتهای مانند User 42 logged in from device ABC لاگ ساختاریافته به صورت مجموعهای از فیلدها به نظر میرسد: user_id=42, event=login, device_id=ABC, timestamp=2026-07-04T10:30:00Z.
مزیت اصلی لاگهای ساختاریافته نسبت به متنی، امکان پردازش برنامهنویسی است. تجزیه لاگهای متنی نیازمند عبارات منظم و فرضیاتی درباره قالب رشته است. لاگهای ساختاریافته بدون اتلاف تجزیه میشوند: هر فیلد دارای نوع و نام شناختهشده است که امکان ساخت پرسوجوهایی مانند همه خطاهای مجوز در ساعت آخر برای کاربر 42 را پیدا کن بدون پردازش اضافی را فراهم میکند.
بر اساس دادههای Honeycomb.io (2023)، تیمهایی که از structured logging در تولید استفاده میکنند، حوادث را به طور متوسط ۴ برابر سریعتر از تیمهایی که به لاگهای متنی و grep تکیه میکنند، تشخیص میدهند.
Structured Logging از چندین فرمت سریالسازی پشتیبانی میکند. انتخاب فرمت به زیرساخت بستگی دارد: JSON برای یکپارچهسازی با Elasticsearch و سیستمهای ابری مناسب است، logfmt — برای مشاهده کنسولی از طریق tail و grep، Protocol Buffers — برای سیستمهای با کارایی بالا با محدودیت پهنای باند.
| فرمت | مثال | چه زمانی استفاده شود |
|---|---|---|
| JSON | {"event":"login","user_id":42} | ELK Stack، جمعآوریکنندههای ابری، میکروسرویسها |
| Logfmt | event=login user_id=42 duration_ms=150 | کنسول، tail، heroku logs |
| MessagePack | معادل باینری JSON | سیستمهای با بار بالا، IoT |
JSON رایجترین فرمت برای لاگهای ساختاریافته است. توسط تمام سیستمهای جمعآوری به صورت بومی پشتیبانی میشود: Logstash، Fluentd، Amazon CloudWatch، Google Cloud Logging. لاگهای JSON به راحتی توسط انسان خوانده میشوند و توسط هر زبان برنامهنویسی بدون کتابخانههای اضافی تجزیه میشوند. عیب اصلی — افزونگی: هر جفت کلید-مقدار نیاز به گیومه و دو نقطه دارد که حجم دادههای ذخیرهشده را ۳۰–۵۰٪ نسبت به logfmt افزایش میدهد.
Logfmt در Heroku برای دید کنسولی توسعه یافته است. از JSON فشردهتر است، خوانایی برای انسان را حفظ میکند و به راحتی با cut و awk بریده میشود. مثال: ts=2026-07-04T10:30:00Z level=error module=api status=500. Logfmt نیاز به escape کردن بیشتر کاراکترها ندارد و برای لاگنویسی stdout در کانتینرها مناسب است.
در برنامههای موبایل Structured Logging سه وظیفه کلیدی را حل میکند: یافتن دلایل crashes بدون بازتولید روی دستگاه، ردیابی نشستهای کاربر و تحلیل عملکرد بر اساس نسخههای برنامه.
لاگهای متنی روی دستگاههای موبایل تقریباً بیفایده هستند — توسعهدهنده نمیتواند لاگها را روی دستگاه کاربر grep کند. لاگهای ساختاریافته به سیستمهای ابری (Firebase، Sentry، Datadog) ارسال میشوند و در آنجا ایندکس میشوند. میتوان پرسوجویی ساخت: نمایش همه crashهای iOS 17.4، نسخه برنامه 3.2، در ماژول checkouts — و در چند ثانیه انتخاب دقیق دریافت کرد.
بر اساس دادههای Sentry (2024)، برنامههایی که از structured breadcrumbs استفاده میکنند، ۶۰٪ زمینه بیشتر در هر گزارش crash نسبت به برنامههایی دارند که فقط متن خطا را لاگ میکنند. این مستقیماً بر سرعت رفع باگ تأثیر میگذارد.
ELK Stack — Elasticsearch، Logstash، Kibana — زیرساخت استاندارد برای کار با لاگهای ساختاریافته باقی میماند. Logstash لاگها را در JSON دریافت میکند، تبدیل میکند و برای ایندکس به Elasticsearch میفرستد، Kibana رابط بصری برای پرسوجوها و داشبوردها فراهم میکند.
برای برنامههای موبایل راهحلهای ابری محبوب هستند: Firebase Crashlytics با لاگهای سفارشی، Sentry با breadcrumbs، Datadog با ردیابی APM. آنها لاگهای ساختاریافته را مستقیماً از SDK موبایل دریافت میکنند و نیاز به استقرار بکاند اختصاصی ندارند. Firebase بسته رایگان برای گزارش crash ارائه میدهد، Sentry ردیابی توزیعشده اضافه میکند و Datadog با APM برای ردیابی همزمان عملکرد درخواستها روی کلاینت و سرور یکپارچه میشود.
Grafana Loki — جایگزین Elasticsearch، بهینهشده برای لاگها. Loki به طور پیشفرض محتوای پیامها را ایندکس نمیکند، بلکه از برچسبها (labels) برای فیلتر کردن استفاده میکند. این کار ذخیرهسازی را بسیار ارزانتر و پرسوجوها را بر اساس مجموعه ثابت فیلدها سریعتر میکند.
// Structured logging از طریق Swift Logger به JSON
struct StructuredLog {
let event: String
let attributes: [String: Any]
let level: String
func serialize() -> String {
var base = "event=\(event) level=\(level)"
for (key, value) in attributes {
base += " \(key)=\(value)"
}
return base
}
}
اولین قانون لاگنویسی ساختاریافته: هر پیام باید حاوی شناسه درخواست یا نشست باشد. بدون زمینه، یک لاگ تکی بیفایده است — نمیتوان فهمید به کدام کاربر یا درخواست مربوط است. correlation ID را در شروع نشست اضافه کنید و آن را از تمام لایههای برنامه عبور دهید.
دومین قانون: تایپبندی فیلدها. فیلدهای عددی (duration_ms، status_code، retry_count) باید به صورت عدد ارسال شوند نه به صورت رشته. Elasticsearch و سیستمهای مشابه اعداد و رشتهها را متفاوت ایندکس میکنند: به اعداد میتوان تجمیع (میانگین، میانه، درصدک) اعمال کرد، به رشتهها — جستجوی متن کامل. تایپبندی نادرست امکان ساخت داشبوردهای تحلیلی را از بین میبرد.
سومین قانون: از اشیاء تو در تو خودداری کنید. لاگهای JSON با عمق تو در توی بیش از ۲ سطح فیلتر و بصریسازی دشوار هستند. به جای {"user": {"name": "Alice", "role": "admin"}} از کلیدهای مسطح استفاده کنید: user_name=Alice user_role=admin.
// Structured logging در Android از طریق Timber + logfmt
class StructuredTree : Timber.Tree() {
override fun log(priority: Int, tag: String?,
message: String?, t: Throwable?) {
val level = priorityToLevel(priority)
val logfmt = "level=$level tag=$tag message=$message"
sendToRemote(logfmt)
}
}
حداقل مجموعه فیلدها برای هر پیام ساختاریافته: timestamp در ISO 8601، level (debug/info/warn/error/fatal)، logger (نام ماژول یا کلاس)، message (توضیح رویداد قابل خواندن برای انسان). اضافی: correlation_id، user_id (اگر مشخص باشد)، version (نسخه برنامه)، platform (iOS/Android)، environment (dev/staging/prod).
بدون correlation_id لاگهای ساختاریافته به مجموعهای از رکوردهای پراکنده تبدیل میشوند که نمیتوان آنها را در یک سناریوی کاربر متصل کرد. UUID را در هر راهاندازی برنامه تولید کنید و آن را به تمام لاگهای نشست اضافه کنید. در عمل correlation_id باید از تمام لایهها عبور کند: از رویدادهای UI تا درخواستهای شبکه و وظایف پسزمینه — در غیر این صورت بخشی از لاگها بدون زمینه باقی میمانند و در تحلیل شرکت نمیکنند. در ردیابی سرتاسری، یک UUID امکان جمعآوری تصویر کامل مسیر کاربر را فراهم میکند.
در iOS لاگنویسی ساختاریافته را میتوان از طریق یک پوشش بر روی os_log پیادهسازی کرد که فیلدها را به فرمت logfmt سریالسازی میکند. در Android — از طریق Timber با Tree سفارشی که پیامها را قبل از ارسال به سرور به JSON یا logfmt تبدیل میکند.
import OSLog
struct StructuredLogger {
let subsystem: String
let category: String
func log(level: OSLogType,
event: String,
context: [String: Any]) {
let oslogger = Logger(
subsystem: subsystem,
category: category
)
let fields = context.map {
"\($0.key)=\($0.value)"
}.joined(separator: " ")
oslogger.log(level: level,
"\(event) \(fields)")
}
}
انتخاب بین لاگهای ساختاریافته و متنی به مرحله پروژه بستگی دارد. در مراحل اولیه توسعه، لاگهای متنی سادهتر و سریعتر هستند — توسعهدهنده پیام را مستقیماً بدون پوششهای اضافی مینویسد. اما به محض اینکه پروژه از مرز یک تیم یا یک سرور فراتر میرود، لاگهای ساختاریافته اجباری میشوند.
| معیار | لاگهای متنی | لاگهای ساختاریافته |
|---|---|---|
| خوانایی | بالا در کنسول | متوسط (نیاز به pretty-print) |
| جستجو | grep بر زیررشته | پرسوجو بر اساس فیلدها و مقادیر |
| تجمیع | پشتیبانی نمیشود | میانگین، میانه، درصدکها |
| یکپارچهسازی | نیاز به تجزیه دارد | بومی در ELK/Loki/Datadog |
| حجم ذخیرهسازی | کمتر (بدون فراداده) | بیشتر (فیلدها + مقادیر) |
سوالات متداول
برای ارسال به سرور از JSON استفاده کنید — به صورت بومی توسط Firebase Crashlytics، Sentry و Datadog پشتیبانی میشود. برای مشاهده محلی در لاگهای Xcode یا Android Studio از logfmt استفاده کنید — فشردهتر است و بدون فرمتبندی خوانده میشود.
بله، لاگهای ساختاریافته در سمت کلاینت امکان اضافه کردن زمینه به هر گزارش crash را فراهم میکنند: نسخه سیستمعامل، وضعیت شبکه، آخرین اقدامات کاربر. بدون breadcrumbs ساختاریافته، گزارش crash فقط شامل پشته فراخوانی بدون سناریوی کاربر است.
Logfmt فشردهتر است (حجم ۳۰–۵۰٪ کمتر) و در ترمینال راحتتر خوانده میشود. JSON از اشیاء تو در تو و آرایهها پشتیبانی میکند، اما نیاز به escape کردن گیومه دارد. انتخاب بستگی به زیرساخت دارد: برای ELK — JSON، برای مشاهده کنسولی — logfmt.
یک نمونه UUID در راهاندازی برنامه ایجاد کنید، آن را در singleton یا کانتینر DI ذخیره کنید و از طریق سازنده به تمام loggerها ارسال کنید. جایگزین — استفاده از threading-local یا Continuation Local Storage در کوروتینهای Kotlin.
میتوان، اما توصیه نمیشود — مخلوط کردن امکان ایندکس خودکار را از بین میبرد. اگر بخشی از لاگها متنی هستند، باید با عبارات منظم تجزیه شوند که عملکرد و قابلیت اطمینان جستجو را کاهش میدهد. بهتر است همه لاگها را به فرمت ساختاریافته مهاجرت دهید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید