اصول Logging در پروژههای نرمافزاری
تقریبا اکثر توسعهدهندگان بر روی این که Logging یکی از مهمترین ابزارهای یک توسعهدهنده در پروژههای نرمافزاری است اتفاق نظر دارند ولی متاسفانه خیلی از توسعهدهندگان به خوبی از این ابزار ساده ولی قدرتمندی که در اختیار دارند استفاده نمیکنند. در حقیقت لاگها نحوه ارتباط توسعهدهنده با سرویسی هستند که طراحی میکنه پس یک استراتژی Logging مناسب میتونه توسعه نرمافزار شما رو سریعتر کنه، به پیدا کردن سریعتر باگها در لول پروداکشن کمک کنه و نگهداری محصول رو براتون ساده کنه. من سعی کردم برخی اصولی که خودم به طور معمول رعایت میکنم (و یا مهم هستند و من از روی تنبلی رعایت نمیکنم :)) رو در ادامه مطلب بیارم. اصول و تکنیکهایی که در ادامه نوشته شدهاند به صورت کلی در پروژههای نرمافزاری در پلتفرمهای مختلف قابل استفاده هستند ولی تمرکز اصلی مطلب بر روی پروژههای بک اند وب است.
خودتون رو به Logger های پیشفرض پلتفرم محدود نکنید
تقریبا اکثر پلتفرمها و زبانهای برنامهنویسی یک کلاس، تابع، پکیج و یا ابزار مشابهی برای چاپ کردن در اختیار توسعهدهندگان میگذارند، به چند تا مثال تو زبونهای مختلف توجه کنید
console.log('Hello World!'); // Javascript
print 'Hello World!' # Python
fmt.Print('Hello World!') // Go
با وجود این که استفاده از ابزارهای پیشفرض زبان میتونه تا حدی مشکل رو حل کنه ولی باعث میشه دست شما بسته بشه. پیشنهاد میکنم برای لاگکردن رویدادها و دیباگینگ از یک لاگر سفارشی استفاده کنید، لاگرهای سفارشی به شما امکان لاگ کردن در درجات مختلف(خطاها، هشدارها، پیغامهای مخصوص دیباگینگ و …)، تعیین خروجیهای مختلف(فایل، سرور لاگینگ، کنسول و …)، تعیین فرمت خروجی(متن ساده، json و …) و در مجموع کنترل کاملتر بر روی خروجی رو میدهند. من از winston
در نود، logrus
در گو و پکیج استاندارد logging
در پایتون برای لاگ کردن استفاده میکنم.
در چند درجه مختلف لاگ کنید
موارد زیادی در یک اپلیکیشن هستند که میتوانند لاگ شوند با این وجود برخی از موارد صرفا در پروسه توسعه کاربرد دارند در حالی که برخی موارد هم در پروسه توسعه و هم در هنگامی که محصول در لول پروداکشن مشغول به کار است با اهمیت هستند(به طور مثال فقط خطاها و هشدارها)، در صورتی که از یک لاگر سفارشی استفاده کرده باشید می تونید به راحتی خروجیهای سیستم را در چند درجه مختلف دستهبندی کنید و در در محیطهای مختلف، فقط خروجیهای مورد نیاز را تولید کنید، این استراتژی از افزایش بیدلیل حجم خروجیهای شما بهخصوص در لول پروداکشن کمک فوقالعادهای میکنه و دیباگینگ رو سادهتر خواهد کرد. در یک نرمافزار که به درستی طراحی شده باشه، درجه لاگکردن در هنگام راهاندازی نرمافزار قابل تنظیمه(در داخل کد هارد کد نشده است) و حتی پس از راهاندازی نرمافزار نیز به صورت داینامیک قابل تغییر است تا در هنگام برخورد با مشکلات احتمالی در لول پروداکشن بتوان بدون از لحظهای از دسترس خارج کردن سیستم، جزییات بیشتری از مشکل پیش اومده رو مشاهده کرد و راحتتر منشا اون رو پیدا کرد.
در لاگکردن افراط و تفریط نکنید
مثل تمام بخشهای دیگه زندگی، افراط و تفریط در لاگ کردن هم میتونه باعث ایجاد مشکل در کارها بشه. سعی کنید اکثر رویدادهای مهم سیستم که موفقیتآمیز بودن یا نبودنشون میتونه باعث ایجاد تغییر در رویه کاری سیستم بشه رو لاگ کنید. برقرار شدن ارتباط با دیتابیس، راهاندازی موفق سرور، موفقیتآمیز بودن کوئریهای ارسالی برای دیتابیس و امثالهم از مواردی هستند که میبایست لاگ شوند با این وجود مشخصا اختصاص دادن مقدار یه یک متغیر نیازی به لاگ کردن ندارد، قبل از لاگکردن یک مورد برای چند مسئله به این فکر کنید که آیا واقعا میشود درآینده از دیتای خروجی این مورد استفاده شود و یا دیتای خروجی صرفا موجب شلوغ شدن خروجی لاگها میشود؟
بدیهی است که لاگکردن در درجات مختلف میتونه تا حد قابلقبولی به این مورد هم کمک کنه.
اطلاعاتی که مورد نیاز است رو لاگ کنید، نه کمتر و نه بیشتر
لاگها باید علاوه بر ذکرکردن وقوع یک رویداد، اطلاعات مربوط به اون رویداد رو همراه خود داشته باشند. پیامهای زیر رو مقایسه کنید:
[1] order
[2] Order creation failed
[3] Order creation failed. price=۲۱۴۰۰ userId=oQYZQxCAKN8J
درباره حالت ۱ که توضیحی ندارم و به نظرم کاملا مردوده :) ولی در مورد حالات دیگه در صورتی که اطلاعات مربوط به رویداد رو ذکر نکرده باشید(حالت ۲)، احتمالا هیچ وقت نخواهید فهمید که عدم موفقیتآمیز بودن ایجاد سفارش مربوط به فارسی بودن اعداد قیمت سفارش است ولی در حالت ۳ به راحتی علت بروز مشکل قابل پیگیری است. سعی کنید ماکسیمم اطلاعاتی که به دیباگینگ کمک میکنند رو ذکر کنید، آیدیها و دادههایی که نیاز به اعتبارسنجی دارند در بالاترین درجه اهمیت هستند. همچنین دادههای بیش از حد و از لاگ کردن مواردی که ذکر شدن یا نشدنشون تغییری در پروسه ایجاد نمیکنه پرهیز کنید به طور مثال لازم نیست تا رویدادی تکراری که در یک حلقه انجام میشود را به ازای هر بار انجام لاگ کنید، کافیست عملیات انجام شده به همراه تعداد دفعات انجام حلقه را لاگ کنید. در نهایت از لاگ کردن دیتاهای امنیتی مثل پسوردها، توکنها و امثالهم هم اکیدا بپرهیزید.
لاگها باید توسط انسان و ماشین قابل خواندن باشند
لاگهای خروجی سیستم میبایست بدون parse کردن برای انسانها قابل خواندن باشند در غیر این صورت مهمترین قابلیت خود رو از دست میدهند، علاوه بر این باید توسط ماشین هم قابل خواندن باشند تا بتوان اونها رو ساده تر تحلیل کرد پس سعی کنید پیامهاتون ساده باشه، یک رویداد در چند خط متفاوت لاگ نشه، ترجیحا هیچگونه سریالیزیشنی حتی تبدیل به json ساده بر روشون انجام نشه(البته این مورد در شرایط خاص قابل چشمپوشی است) و مهمتر از همه متن ساده باشند و به هیچ عنوان به یک فرمت باینری تبدیل نشوند تا انسان بتواند به راحتی آنها رو بخواند. در عین حال برای این که ماشین هم توانایی خواندن سریع دادهها رو داشته باشه باید تمام پیامهای تولیدی توسط یک سیستم از یک فرمت مشخص و البته قابل parse کردن پیروی کنند. پیشنهاد میکنم پیامهاتون شامل درجه لاگ، زمان تولید، یک متن پیام نوشته شده توسط خودتون(یک جمله واقعی و واضح، نه صرفا یک کلمه کلیدی) به همراه تعدادی جفت key/value شامل دیتاهای مربوط به رویداد باشه با چنین ساختاری:
time="2015-12-01T01:27:38+3:30" level=debug msg="Something useful" key1=value1 foo=bar
و یا
[debug][2015-12-01T01:27:38+3:30][Something useful][(key1:value1)(foo:bar)]
در مواردی که سیستم و یا لایبرری مورد استفادهتون پیغام خطایی تولید کرده، اون رو به عنوان یکی از جفت key/value ها چاپ کنید. چنین ساختاری علاوه بر این که خوانایی مناسبی برای انسان داره میتونه به راحتی توسط ماشین parse بشه و در عین حال میشه به راحتی بر روی دادهها کوئری زد. لطفا به این توجه کنید که زمان لاگشدن اهمیت بالایی و کاربردهای مختلفی داره و به هیچ عنوان نباید در هنگام لاگ کردن فراموش بشه، همچنین بهتره زمان در پیامها با فرمتی لاگ بشه که تاریخ و ساعت رو به همراه منطقه زمانی به طور کامل داشته باشه و استفاده از تاریخ و یا ساعت و یا عدم چاپ کردن منطقه زمانی به هیچعنوان توصیه نمیشه. (استفاده از Unix Timestamp هم به دلیل ناخوانایی برای انسان توصیه نمیشه)
خروجی لاگها رو به مقصد مناسب هدایت کنید
لاگکردن دادهها می تونه مقصدهای مختلفی داشته باشه، شما میتونید توی کنسول سیستم لاگ کنید، از یک فایل استفاده کنید، دادههاتون رو به یک سرویس HTTP بفرستید، توی دیتابیس ذخیره کنید و یا از خیلی از روشهای دیگه استفاده کنید. هر کدوم از مقصدهایی که ذکر شد مزایا و معایب مختص خودشون رو دارند و استفاده از هرکدوم از روشها میتونه با در نظر گرفتن شرایط پروژه توجیهپذیر باشه.
پیشنهاد من اینه که تا سر حد امکان روی کنسول سیستم لاگ نکنید. از مقصدهای HTTP بپرهیزید(به خصوص اگه شرایط شبکهتون حتی در لول پروداکشن استیبل نیست، مثلا توی یک دیتاسنتر ایرانی مستقر هستید) و لاگکردن توی دیتابیس رو هم فراموش کنید چون با وجود این که به شما امکانات خوبی برای تحلیل دادهها میده ولی باعث ایجاد تاخیر در عملکرد نرمافزار میشه(به خصوص در پلتفرمهایی که از کانکارنسی به طور مناسب پشتیبانی نمیکنند). سعی کنید پیامها رو روی فایل لاگ کنید، فایلها برخلاف مقصدهای تحت شبکه بلاک نمیشوند، نوشتن بر روی آنها فوقالعاده کمهزینه و سریعه و کمترین تاثیر رو بر روی عملکرد سیستم دارند. علاوه بر این برای تحلیل اونها هم گزینههای مختلفی در اختیار دارید، میتونید از یک ادیتور ساده متنی استفاده کنید، از grep
برای سرچ کردن توشون استفاده کنید و یا با راهاندازی یک نود Logstash، خروجیها رو از منابع مختلف جمعآوری کنید و به راحتی و با حداقل تاثیر بر روی پرفورمنس سیستم روشون کوئریهای پیچیده بزنید.
لاگها تنها روش گرفتن خروجی از سیستم شما نیستند
در نظر داشته باشید که لاگها با وجود این که کمک شایانی به شما در پروسعه توسعه میکنند تنها ابزار شما نیستند و باید برخی اوقات منابع دیگهای برای تحیل سرویستونه داشته باشید. مثلا سعی کنید ارورها سیستم و اکسپشنهای ایجاد شده در سیستم رو بر روی یک سرویس بیرونی که شما رو از به وجود اومدن اونها مطلع میکنه هدایت کنید، Sentry برای وبسرویسها و Crashlytics بر موبایلاپها گزینههای پیشنهادی من برای ارور هندلینگ هستند، اکثر لاگرهای سفارشی را میتوان طوری تنظیم کرد که لاگهای مربوط به یک درجه خاص(مثل خطاها) را علاوه بر لاگکردن در فایل به مقصدهای دیگه هم ارسال کنند. همچنین به طور مثال میتوان برخی دادههای عددی که تحلیلشون سادهتره رو مثل تعداد ریکوئستها، تعداد ریکوئستهای ناموفق، زمان مورد نیاز برای انجام کوئریها و … به سرویسهای جمعاوری متریکز مثل Graphite ارسال کرد و تحلیل اونها رو چندین مرتبه سادهتر کرد. پس فکر نکنید لاگکردن تنها راه ارتباط شما با سرویسی هستش که طراحی کردید.
یک استراتژی مناسب برای آرشیو کردن لاگهاتون داشته باشید
به هر میزانی که حرفهای عمل کنید و فقط موارد مورد نیازتون رو لاگ کنید، با توسعه سیستم و گذر زمان به مرحلهای میرسید که حجم فایلهای لاگهاتون به طرز قابل توجهی رشد میکنه و حجم زیادی از فایل مربوط خواهد بود به دیتاهایی که مدتها پیش تولید شدند و شما نیازی به استفاده ازشون ندارید. راهحل ساده در برخورد با این مشکل پاککردن لاگهاست :) شاید بعد یک سال از تولید یک پیام، دیگه نیازی به اون پیام نباشه ولی پاک کردن لاگ سیستم مربوط به یک ماه پیش یک اشتباه واقعیه، در نتیجه سعی کنید یک راهکار مناسب برای آرشیو کردن لاگهاتون داشته باشید، مثلا پیشنهاد میکنم تا در بازههای زمانی مشخص لاگهاتون فشردهسازی کنید و فایلهای اصلی رو حذف کنید.(سرویسهایی هستند که این مراحل رو به طور خودکار براتون انجام میدن، همچنین اگه فرمت لاگتون منطقی طراحی شده باشه و زمان هم درش در نظر گرفته شده باشه، خودتون هم میتونید به راحتی یه ابزار ساده برای انجام این عملیات بنویسید) فقط توجه داشته باشید که بازه زمانی مربوط به این عملیات باید به نحوی انتخاب بشه که عملا لاگهای شما رو غیر قابل استفاده نکنه.
امیدوارم مواردی که توضیح دادم به پروسه توسعه نرمافزارهاتون کمک کنه. خوشحال میشم اگه مورد دیگهای به ذهنتون رسید از طریق توییتر برام ارسال کنید تا به این لیست اضافه کنم. سال نو هم مبارک باشه :)