وبلاگ

لاگ‌گیری مثل حرفه‌ای‌ها


رقیه اباذری رقیه اباذری

لاگ‌ها مانند شواهد در صحنه‌های جرم هستند و دولوپرها مثل کارشناس صحنه جرم هستند. لاگ‌ها نقش مهمی در پیدا کردن یک ارور و یا قطعی اپلیکیشن هستند. همانطور که کمبود شواهد باعث سردرگمی و نرسیدن به نتیجه درست می‌شود، کمبود لاگ‌های با معنی هم باعث وقت‌گیر یا غیرممکن شدن عیب‌یابی می‌شود.

همه دولوپرها باید یاد بگیرند که به طور موثر و حرفه‌ای از لاگ استفاده کنند. هدف این مقاله این است که نه تنها لیستی از روش‌های مفید لاگ‌گیری اپلیکیشن را ارائه دهد بلکه نظریه‌هایی مانند چه چیزی، چه زمانی و چه کسی را در لاگ‌گیری، توضیح دهد.

چگونه دیباگ کنیم؟

قبل از اینکه راجع به لاگ صحبت کنیم، بهتر است راجع به برنامه و چگونی عیب‌یابی دولوپرها صحبت کنیم. چون لاگ‌ها به دیباگ کمک می‌کنند.

 

در حالت تراکنش ها برنامه نویسی کنید.

توجه

 یک برنامه مجموعه ای از تراکنش ها در وضعیت های مختلف یا یک سری انتقال در بین وضعیت‌ها است.

وضعیت‌ها همان چیزی است که یک برنامه در یک زمان خاص در حافظه خود ذخیره می‌کند و کد یک برنامه، نحوه انتقال برنامه از یک وضعیت به وضعیت دیگر را تعریف می‌کند. دولوپرها با استفاده از زبان‌های برنامه‌نویسی مانند جاوا اغلب بیشتر از وضعیت‌ها روی رویه (کد) تمرکز می‌کنند. با این حال در نظر گرفتن یک برنامه به عنوان یک سری وضعیت‌ها یک ذهنیت اساسی برای اتخاذ است، زیرا وضعیت‌ها بیشتر از اینکه برنامه چگونه کار را انجام دهد، به آنچه برنامه باید انجام دهد علاقه مند هستند.

به عنوان مثال فرض کنید روباتی در حال پر کردن مخزن بنزین ماشین من است. برای انتقال وضعیت توصیف زیر را داریم.

باید از وضعیت (مخزن=خالی ، پول=50 هزار تومان) به وضعیت (مخزن=پر ، پول= 15هزار تومان) برود. هنگامی که این پروسه تعریف می‌شود، ربات باید پمپ بنزین پیدا کند، ماشین را به آن‌جا منتقل کند و هزینه پرداخت کند. روش کار بسیار مهم است، اما وضعیت ها در اندازه‌گیری صحت برنامه تاثیر مستقیم دارند.

دیباگ

دیباگ بازسازی ذهنی انتقال حالت است. دولوپرها نحوه دریافت ورودی توسط برنامه، ایجاد یک سری تغییرات وضعیت و تولید خروجی را بازسازی می‌کنند. بعد می‌فهمند که کجا چه اشتباهی اتفاق می‌افتد. هنگام توسعه، دولوپرها از دیباگر استفاده می‌کنند. اما در مرحله تولید استفاده از دیباگر بسیار دشوار است به همین دلیل دولوپرها از لاگ‌ها استفاده می‌کنند.

چه چیزی باید لاگ شود؟

با تعریف دیباگ‌کردن، پاسخ به این سوال ساده‌تر می شود:

توجه

لاگ‌ها باید حاوی اطلاعات کافی برای کمک به بازسازی انتقال وضعیت باشند.

غیرممکن و البته غیرضروری است که همه موقعیت‌ها را در هر زمان بگیرید. پلیس ها برای گرفتن مجرمان به فیلم‌های هولوگرام نیاز ندارند اما چند طرح و پیش‌زمینه خوب دارند. این موضوع برای لاگ‌ها هم صدق می‌کند، برنامه نویسان فقط هنگام بوجود آمدن وضعیت بحرانی از لاگ‌ها استفاده می‌کنند و لاگ‌ها باید حاوی مشخصات اصلی وضعیت فعلی و دلیل انتقال باشند.

انتقال حالت بحرانی

همه انتقال وضعیت‌ها، ارزش لاگ‌گیری را ندارند. نکته اصلی این است که در مورد اجرای یک برنامه به آن به عنوان مجموعه‌ای از  تغییر وضعیت‌ها فکر کنید، آن‌ها را به چند مرحله تقسیم کنید سپس بر زمانی که اجرا از یک مرحله به مرحله دیگر می‌رود تمرکز کنید.

به عنوان مثال، فرض کنید 3 مرحله در راه‌اندازی یک برنامه وجود دارد.

لود کردن تنظیمات

اتصال وابستگی‌ها

راه‌اندازی سرور

منطقی است که در ابتدا و انتهای تمام این مراحل لاگ کنید. در صورت بروز خطا در اتصال به وابستگی‌ها و قطع برنامه، لاگ‌ها به وضوح نشان می‌دهند که اپلیکیشن تنظیمات خود را بارگیری کرده است، وارد مرحله اتصال وابستگی شده است اما کامل نشده است. با این کار دولوپرها می‌توانند به سرعت مشکل را مشخص کنند.

خصوصیات اصلی

لاگ‌گیری مانند طراحی برنامه شماست، فقط ویژگی‌های اصلی هسته اصلی تجارت باید ضبط و ثبت شود. در صورتی که اطلاعات حساس مانند PII وجود داشتند هم باید ثبت شوند اما به صورت غیرمستقیم پوشانده یا توصیف شوند.

PII

منظور از PII اطلاعات مهم و شخصی هست.

به عنوان مثال، هنگامی که یک سرور HTTP از حالت "در انتظار ریکوئست" به حالت "ریکوئست دریافت شده" تغییر می‌کند، باید متد HTTP و URL را لاگ کند زیرا آن‌ها اصول اولیه درخواست HTTP را توصیف می‌کنند. اگر مقادیر آن‌ها بر منطق بیزنس تأثیر بگذارد، سایر المان‌های ریکوئست HTTP مانند هدر یا بخشی از ody باید ثبت شوند.

به عنوان مثال اگر رفتار سرور بین Content-Type:application/json و Content-Type:multipart/form-data تفاوت قابل توجهی داشته باشد، هدر باید ثبت شود.

دلیل انتقال وضعیت

ثبت دلیل انتقال وضعیت برای دیباگ بسیار مفید است. لاگ‌ها باید به طور خلاصه مفاهیم وضعیت‌های قبلی و بعدی را پوشش دهند و دلیل آن را توضیح دهند. این کار به دولوپرها کمک می‌کند تا نقاط را بهم وصل کرده و از بازسازی (دیباگ‌کردن) اجرای برنامه استفاده کنند.

مثال:

یک مثال ساده برای جمع کردن همه این توضیحات: با فرض اینکه یک سرور یک شماره SSN نامناسب دریافت کند و دولوپر می‌خواهد در مورد این مشکل، از لاگ استفاده کند.

 

چند لاگ آنتی پترن ضد الگو که فاقد مشخصات و دلایل اصلی هستند:

  • [2020–04–20T03:36:57+00:00] server.go: Error processing request
  • [2020–04–20T03:36:57+00:00] server.go: SSN rejected
  • [2020–04–20T03:36:57+00:00] server.go: SSN rejected for user UUID “123e4567-e89b-12d3-a456–426655440000”
  • همه این لاگ‌ها حاوی برخی از اطلاعات هستند اما برای پاسخگویی به سوالاتی که ممکن است دولوپر هنگام عیب‌یابی داشته باشند، کافی نیستند: چرا SSN ریجکت شد؟ کدام کاربر تحت تأثیر قرار گرفت؟
  • گزارش خوبی که به اشکال زدایی کمک می کند:
  • [2020–04–20T03:36:57+00:00] server.go: Received a SSN request(track id: “e4a49a27–1063–4ab3–9075-cf5faec22a16”) from user uuid “123e4567-e89b-12d3-a456–426655440000”(previous state), rejecting it(next state) because the server is expecting SSN format AAA-GG-SSSS but got **-***(why)
  •  
  • چه کسی باید لاگ‌ها را بنویسد

  • تله ای که بسیاری از آن غافل می‌شوند "کسی" است که باید لاگ‌ها را بنویسد. نوشتن لاگ‌های مربوط به فانکشن‌های اشتباه باعث می‌شود که لاگ‌ها دارای اطلاعات تکراری و یا ناکافی باشند.
  •  
  • در نظر گرفتن برنامه‌ها به عنوان لایه‌های انتزاعی

  • بیشتر برنامه های مدرن که به خوبی ساخته شده‌اند مانند هرمی با لایه‌های انتزاع هستند. کلاس‌ها/فانکشن‌های لایه فوقانی یک کار پیچیده را به وظایف کوچک‌تری تقسیم می‌کنند.
  • در حالی که کلاس‌ها/فانکشن‌های لایه پایین پیاده‌سازی‌های وظایف کوچک انتزاعی مانند جعبه‌های سیاه را تشکیل می‌دهند و رابط‌هایی برای فراخوانی لایه فوقانی فراهم می‌کنند. این پارادایم برنامه‌نویسی را آسان‌تر می‌کند زیرا هر لایه فقط باید روی منطق خودش تمرکز کند. بدون اینکه نگران جزئیات کامل برنامه باشد.
  • به عنوان مثال، یک وب‌سایت می‌تواند از لایه منطق کسب‌و‌کار، لایه HTTP و لایه TCP/IP تشکیل شود. هنگام پاسخ به ریکوئست به برخی از URLها، لایه منطق کسب‌وکار بر تصمیم‌گیری در مورد اینکه کدام صفحه وب نشان داده شود تمرکز  می‌کند و محتوای صفحه وب را به لایه HTTP منتقل می‌کند. لایه HTTP محتوا را به یک پاسخ HTTP تبدیل می‌کند، سپس از لایه TCP/IP می‌خواهیم پاسخ HTTP را به بسته‌های TCP تبدیل کند و آن‌ها را ارسال کند.
  •  
  • در لایه اشتباه لاگ نکنید

  • به عنوان یک نتیجه از انتزاع، لایه‌های مختلف دارای دانش مختلفی از یک کار در حال انجام هستند. در مثال قبلی، لایه HTTP ایده‌های زیادی در مورد تعداد بسته‌های TCP ارسال شده و همچنین قصد کاربران هنگام درخواست این URL ندارد. هنگام تلاش برای لاگ، دولوپرها باید لایه مناسبی را انتخاب کنند که نمای کاملی از انتقال‌ها و دلایل وضعیت داشته باشد.
  • در مثال اعتبار سنجی SSN ما با فرض منطق اعتبار سنجی SSN در یک کلاس Validator مانند:
  • public class Validator {
        // other validation functions ...
     
      public static void validateSSN(String ssn) throws ValidationException {
        // do the validation
        String regex = "^(?!000|666)[0-8][0-9]{2}-(?!00)[0-9]{2}-(?!0000)[0-9]{4}$";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(ssn);
     
        if (!matcher.matches()) {
          // --> Log Location A <--
          logger.info("Bad SSN blah, blah, blah...");
          throw new ValidationException(String.format("expecting SSN format AAA-GG-SSSS but got %s", ssn.replaceAll("\\d", "*")));
        }
      }
    }

و عملکرد دیگری وجود دارد که درخواستی را تأیید می‌کند که اطلاعات کاربر را به روز می‌کند و اعتبار SSN را فراخوانی می‌کند:

public class Validator {
    // other validation functions ...
 
  public static void validateUserUpdateRequest(UserUpdateRequest req) {
    // validate other attribute of req ...
 
    try {
      validateSSN(req.ssn);
    } catch (ValidationException e) {
      // --> Log Location B <--
      logger.info(String.format("Received a user update request(track id %s) from user uuid %s, rejecting it because %s", req.trackID, req.uid, e.getMessage()));
      // other error handling logic ...
    }
 
    // other logic ...
  }
}

دو مکان وجود دارد، مکان‌های A و B

برای نوشتن لاگ‌های مربوط به خرابی اعتبار SSN، فقط مکان B اطلاعات کافی برای لاگ را دارد.

در مکان A برنامه نمی‌داند که چه نوع درخواستی را اداره می‌کند و نه اینکه درخواست از کدام کاربر است. کار مناسب برای validateUserUpdateRequest این است که خطا را به کالر validateRequest که زمینه بیشتری برای لاگ دارد، منتقل کند.

البته این بدان معنا نیست که لاگ در لایه‌های پایین یک برنامه همیشه غیرضروری و بیهوده است. هنگامی که لایه‌های پایین ارورها را در معرض لایه‌های بالا قرار نمی‌دهند راه حل مناسب، لاگ در لایه‌های پایین است.

به عنوان مثال در لایه شبکه می‌توان منطقی را دوباره امتحان کرد که به این معنی است که لایه‌های زیاد متوجه برخی از مشکلات شبکه متناوب نمی شوند. به طور کلی لایه‌های پایین‌تر می‌توانند لاگ‌های مربوط را بیشتر در سطح DEBUG بنویسند تا در سطح INFO تا گزاف‌گویی را کاهش دهند. در صورت نیاز، دولوپرها می‌توانند سطح لاگ را برای دریافت جزئیات بیشتر تنظیم کنند.

توجه

در لاگ گیری 5 سطح داریم:

 DEBUG : اطلاعات این برای دیباگ استفاده می‌شود.
INFO: اطلاعات این سطح برای بررسی عملکرد مورد انتظار از اپلیکیشن استفاده می‌شود.
WARNING: این اطلاعات نشان‌دهنده بروز اتفاق غیرمنتطره حین اجرای اپلیکیشن است.

ERROR: اطلاعات این سطح برای نشان دادن وقوع مشکل جدی استفاده می‌شود.
CRITICAL: اطلاعات این سطح نشان‌دهنده وقوع مشکل حاد در روند اجرای اپلیکیشن است.

چند تا لاگ؟

بین حجم لاگ و میزان سودمندی یک ارتباط مستقیم و آشکار وجود دارد. هرچه لاگ‌های مربوط با معنی بیشتری داشته باشند، بازسازی انتقال‌های وضعیت آسان‌تر است. برای کنترل حجم لاگ دو کار وجود دارد:

رابطه بین لاگ‌ها و حجم کار خود را تخمین بزنید
برای کنترل حجم لاگ، ابتدا باید برآورد عادلانه‌ای از آن انجام دهید. بیشتر برنامه‌ها دو نوع حجم‌کاری دارند:

دریافت آیتم‌های کار (ریکوئست‌ها) و پاسخ دادن

نمونه‌برداری آیتم‌های کار از یک جای دیگر و انجام اقدامات

بیشتر لاگ‌گیری با حجم‌کار ایجاد می‌شود. هرچه میزان حجم‌کاری برنامه بیشتر باشد لاگ‌های بیشتری نوشته می‌شود. لاگ‌های مربوط به غیرحجم‌کاری دیگری نیز وجود دارد اما با شروع حجم‌کاری برنامه، آن‌ها بی‌اهمیت می‌شوند و مثل یک عدد کوچک در مقابل عدد بزرگ قابل چشم‌پوشی هستند.

 

توسعه دهندگان باید رابطه بین لاگ‌های مربوط و آیتم‌های کار را حفظ کنند.

# of logs = X * # of work items + constants

که در آن می توان X را با بررسی کد تعیین کرد. توسعه‌دهندگان باید از فاکتورهای X ایده خوبی برای برنامه‌های خود داشته باشند و آن را با ظرفیت لاگ‌گیری و بودجه مطابقت دهند. چند مورد شایع X:

X بین 0 و 1: این بدان معنی است که از لاگ‌های مربوط نمونه‌برداری می‌شود و همه آیتم‌های‌ کار دارای لاگ‌های مربوط نیستند. به عنوان مثال. فقط ارور لاگ را وارد کنید یا از الگوریتم‌های نمونه‌برداری دیگر از log، استفاده کنید. اینکار به کاهش حجم لاگ کمک می‌کند اما می‌تواند عیب‌یابی را محدود کند.

X ~ 1: این بدان معناست که به طور متوسط، هر آیتم کاری تقریباً یک رکورد تولید می‌کند. این مورد معمولاً معقول است به شرط آنکه یک گزارش بااطلاعات کافی داشته باشیم.

X >> 1: باید دلیل خیلی خوبی برای داشتن X بزرگتر از 1 داشته باشیم. چون وقتی حجم كار افزایش یابد، سرور تعداد بالایی درخواست‌های HTTP دریافت می‌كند، X آن را تقویت می‌كند و بار زیادی را بهمراه می‌آورد دردسر ساز خواهد شد.

از سطوح لاگ استفاده کنید

اگر X حتی پس از بهینه‌سازی، خیلی بزرگ باشد، چه می‌کنید؟ سطوح لاگ می‌تواند کمک کند. اگر X بزرگتر از 1 باشد، شاید بتوان برخی از log ها را در سطح DEBUG قرار داد تا X سطح INFO را کاهش دهد. هنگام دیباگ، برنامه می‌تواند به طور موقت در سطح DEBUG اجرا شود تا اطلاعات بیشتری ارائه دهد.

نتیجه گیری

برای نوشتن لاگ مانند یک دولوپر حرفه‌ای، باید یک برنامه را به عنوان مجموعه‌ای از انتقال وضعیت‌ها با لایه‌های انتزاع در نظر گرفت. با در نظر داشتن این تئوری، می‌توان به این سوالات اصلی در لاگ‌گیری اپلیکیشن پاسخ داد:

زمان لاگ: هنگام تغییر وضعیت بحرانی 

چه چیزی باید در لاگ نوشته شود: ویژگی‌های اصلی وضعیت فعلی و دلیل انتقال وضعیت

چه کسی باید لاگ کند: در لایه صحیح انتزاع لاگ کنید که دارای زمینه کافی است.

چه تعداد لاگ: فاکتور X را تخمین بزنید و آن را با بودجه تنظیم کنید.

 

 

 

 


رقیه اباذری
رقیه اباذری

تو دانشگاه IT خوندم و اکثر منابع کتاب‌های ترجمه شده بودند و صدالبته مبهم :( مثلا element رو "عنصر" ترجمه می‌کردن و من همیشه می‌رفتم تو شیمی و جدول مندلیف. تو باورژن سعی کردم تا حد ممکن مطالب رو با زبان ساده و قابل درک بنویسم. باشد که کسانی که تازه پا به عرصه برنامه‌نویسی گذاشتن، راغب‌تر بشن و با نظرات و فیدبک‌های شما راه هموارتر بشه:)

مطالب مرتبط

دیدگاه‌ها