فهرست مطالب
تقریباً هر تیم بکاندی این آیین را میشناسد: کسی میگوید «سفارش فلان مشتری ثبت نشد»، یک نفر با SSH وارد سرور اول میشود، در پوشهٔ لاگها grep میزند، چیزی پیدا نمیکند، سراغ سرور دوم میرود، و بعد یادش میافتد که لاگ دیروز فشرده شده و باید zgrep بزند. نیم ساعت بعد، خطا پیدا شده ولی هنوز معلوم نیست چند بار تکرار شده و از کِی.
تا وقتی یک سرور و یک برنامه دارید، این روش کار میکند. ولی با اضافه شدن هر سرور، هر سرویس و هر کانتینر، هزینهاش چند برابر میشود. در این نوشته میگوییم دقیقاً کجا grep کم میآورد، لاگ متمرکز چیست، و چهار تصمیم مهمی که در راهاندازی آن باید گرفت: ساختار، سطح، مدت نگهداری و دادهٔ حساس.
grep کجا کم میآورد
چرخش فایلها
فایل لاگ باید بچرخد، وگرنه دیسک را پر میکند. logrotate روی لینوکس یا تنظیم rolling در Serilog و NLog فایلها را روزانه یا بر اساس حجم جدا میکنند، فشرده میکنند و بعد از چند روز پاک میکنند. نتیجه اینکه لاگ سه روز پیش ممکن است دیگر وجود نداشته باشد، و لاگ دیروز در فایلی فشرده با نامی است که باید حدسش بزنید. باگی که کاربر هفتهٔ پیش گزارش داد، عملاً قابل بررسی نیست.
چند سرور و کانتینرهای موقت
پشت load balancer، هر درخواست ممکن است به هر کدام از سرورها برود. پس باید روی همه grep بزنید. در کانتینرها وضع بدتر است: وقتی کانتینر دوباره ساخته میشود، لاگ داخل آن هم از بین میرود؛ درست همان کانتینری که بهخاطر خطا ریاستارت شده.
دسترسی و امنیت
برای خواندن لاگ با SSH، برنامهنویس باید به سرور تولید دسترسی داشته باشد. این یعنی یا همه دسترسی دارند، که خطرناک است، یا فقط یک نفر دارد و گلوگاه همهٔ عیبیابیها میشود.
شمارش و روند
grep میگوید یک خط وجود دارد. نمیگوید این خطا از انتشار دیروز شروع شده، روزی چند بار رخ میدهد، و آیا با آن خطای دیگری که پیامش کمی فرق دارد یکی است یا نه. سؤالهایی که در عیبیابی واقعاً مهماند، با grep جواب سادهای ندارند.
لاگ متمرکز چیست
ایدهٔ اصلی ساده است: لاگ هر برنامه، بهجای اینکه فقط روی دیسک همان سرور بماند، در همان لحظه به یک مقصد مشترک فرستاده میشود که آن را ذخیره، نمایه و قابل جستوجو میکند. اجزایش معمولاً اینهاست:
- ارسال: یا خود برنامه مستقیم میفرستد (مثلاً با exporter در OpenTelemetry یا sink در Serilog)، یا یک عامل روی سرور فایلها را میخواند و میفرستد.
- دریافت و نمایه: لاگها با سرویس، محیط، سطح و ویژگیهایشان ذخیره میشوند.
- جستوجو و گروهبندی: فیلتر روی هر ویژگی، شمارش، نمودار حجم در زمان و جمع کردن خطاهای همریشه.
نوشتن در فایل محلی را هم لازم نیست کنار بگذارید. خیلی از تیمها هر دو را نگه میدارند: فایل محلی برای روز مبادا و مقصد متمرکز برای کار روزانه.
لاگ ساختیافته: متن آزاد کافی نیست
لاگ متمرکز وقتی واقعاً به کار میآید که لاگها ساختیافته باشند. فرق را در این مثال Serilog ببینید:
// متن آزاد: فقط با جستوجوی متنی پیدا میشود
_logger.LogInformation("Order " + orderId + " paid by " + customerId);
// ساختیافته: OrderId و CustomerId ویژگی جدا هستند
_logger.LogInformation("Order {OrderId} paid by {CustomerId}", orderId, customerId);
در حالت دوم، پیام هنوز برای انسان خواناست، ولی OrderId یک فیلد مستقل است. میتوانید دقیقاً بگویید «همهٔ لاگهای سفارش 6177» بدون اینکه نگران باشید عدد 6177 در جای دیگری از متن هم آمده باشد. قالب پیام هم ثابت میماند، پس شمردن تکرارهای یک رخداد ساده است. در Java با SLF4J همین کار با {} و MDC انجام میشود و در پایتون با extra. اگر هیچ کتابخانهای ندارید، حداقل هر خط را JSON بنویسید.
یک ویژگی را هم از روز اول به همهٔ لاگها اضافه کنید: شناسهٔ درخواست. بدون آن، کنار هم گذاشتن خطوط یک درخواست در چند سرویس تقریباً ناممکن است. این موضوع را مفصل در نوشتهٔ trace_id آوردهایم.
سطحهای لاگ را جدی بگیرید
سطحها فقط برچسب نیستند؛ تعیین میکنند چه چیزی ارسال و ذخیره شود و هزینهاش چقدر باشد. یک قرارداد ساده که برای بیشتر تیمها جواب میدهد:
| سطح | کی | در تولید |
|---|---|---|
| Debug / Trace | جزئیات برای توسعه | خاموش، یا موقت برای یک سرویس |
| Information | رخدادهای مهم کسبوکار: سفارش ثبت شد، کاربر وارد شد | روشن |
| Warning | چیزی غیرعادی که برنامه از پسش برآمد | روشن |
| Error | عملیاتی که شکست خورد | روشن، همیشه با استثنا |
| Fatal / Critical | برنامه نمیتواند ادامه دهد | روشن |
دو اشتباه رایج: لاگ کردن هر خطای مورد انتظار (مثل رمز اشتباه کاربر) بهعنوان Error، که داشبورد را پر از نویز میکند؛ و ثبت Error بدون شیء استثنا، که stack trace را از دست میدهد. همچنین سطح را با پیکربندی تنظیم کنید نه با کد، تا بتوانید برای عیبیابی یک سرویس را موقتاً روی Debug ببرید.
حجم و مدت نگهداری
لاگ متمرکز هزینه دارد و هزینهاش با حجم بالا میرود. پیش از راهاندازی، این سؤالها را جواب دهید:
- عیبیابی معمولاً چند روز به عقب برمیگردد؟ برای بیشتر تیمها هفت تا سی روز کافی است.
- آیا الزام قانونی یا قراردادی برای نگهداری طولانیتر دارید؟ آن لاگها شاید جای دیگری با هزینهٔ کمتر بایگانی شوند.
- کدام لاگها پرحجم و کمارزشاند؟ لاگ health check که هر ده ثانیه تکرار میشود، معمولاً اولین نامزد حذف است.
- اگر روزی حجم ناگهان ده برابر شد چه میشود؟ روز حادثه درست همان روزی است که بیشترین لاگ را لازم دارید.
دادهٔ حساس در لاگ
متمرکز کردن لاگ یعنی همهٔ دادهٔ حساسی که در لاگها بوده، حالا در یک جا و برای افراد بیشتری قابل جستوجوست. رمز عبور، توکن، شمارهٔ کارت و کد ملی نباید هیچوقت در لاگ نوشته شوند. ولی واقعیت این است که روزی کسی کل شیء درخواست را لاگ میکند و این قاعده میشکند. برای همین دو لایه لازم است: بازبینی کد برای لاگ کردن کل اشیا، و پاکسازی خودکار الگوهای حساس پیش از ذخیره. ایمیل و شمارهٔ موبایل هم بسته به نوع کسبوکار ممکن است لازم باشد حذف یا ماسک شوند.
لاگماگ: لاگ متمرکز بدون تغییر کد
لاگماگ برای همین مسئله ساخته شده است. برنامههایی که OpenTelemetry دارند، از C# و Java تا Go، Python، Node.js و PHP، با کتابخانهٔ استاندارد وصل میشوند. برنامهای که Serilog دارد، حتی روی .NET Framework 4.6.2، با همان sink استاندارد Seq و یک خط پیکربندی وصل میشود:
Log.Logger = new LoggerConfiguration()
.Enrich.WithProperty("Application", "billing")
.WriteTo.Seq("https://ingest.logmug.ir", apiKey: "lm_ingest_…")
.CreateLogger();
برای بقیه هم ارسال JSON ساده با HTTP هست. در داشبورد متن آزاد و هر ویژگی ساختیافته را جستوجو میکنید، خطوطی که trace_id یکسان دارند کنار هم میآیند و خطاهای همریشه با اثرانگشتی از نوع خطا و stack کد خودتان یک گروه میشوند. شمارهٔ کارت، توکن و رمز عبور پیش از ذخیره حذف میشوند و حذف ایمیل و موبایل برای هر پروژه قابل روشن کردن است. داده در مرکز دادهٔ داخل ایران است. صریح بگوییم: لاگماگ هنوز هشدار ندارد و APM هم نیست. راهنمای اتصال هر زبان در سایت لاگماگ آمده است.
پرسشهای پرتکرار
اگر ارتباط با مقصد لاگ قطع شود، برنامه کند میشود؟
کتابخانههای استاندارد مثل OpenTelemetry و sinkهای Serilog لاگ را در پسزمینه و دستهای میفرستند و صف محدودی دارند. قطع ارتباط معمولاً به از دست رفتن بخشی از لاگ منجر میشود، نه کند شدن درخواستها. اگر آن لاگها برایتان حیاتی است، فایل محلی را هم نگه دارید.
لاگ متمرکز جای پایش سرور را میگیرد؟
نه. لاگ میگوید برنامه چه کرد؛ پایش سرور میگوید ماشین در چه وضعی است. دیسکی که در حال پر شدن است تا روزی که برنامه خطا بدهد در لاگ دیده نمیشود. فهرست چیزهایی که روی سرور باید زیر نظر باشد در چکلیست پایش سرور آمده است.
از کجا شروع کنیم؟
از سرویسی که بیشترین دردسر را دارد. اول آن را وصل کنید، trace_id را اضافه کنید و یک هفته با آن کار کنید. وقتی تیم یک بار خطایی را بهجای نیم ساعت در یک دقیقه پیدا کرد، بقیهٔ سرویسها خودبهخود دنبالش میآیند.
جمعبندی
SSH و grep برای یک سرور کافی بود. با چند سرور، کانتینر، چرخش فایل و تیمی که همه نباید به تولید دسترسی داشته باشند، لاگ باید به یک جای مشترک برود. ولی متمرکز کردن بهتنهایی کافی نیست: لاگ ساختیافته، سطحهای درست، مدت نگهداری معقول و حذف دادهٔ حساس است که آن را واقعاً مفید میکند. برای اینکه ببینید لاگ بکاند در عیبیابی یک حادثهٔ واقعی کجا مینشیند، چرخهٔ عمر یک خطا را بخوانید.