همانطور که میدانیم، ثبت لاگ مربوط به پروژههای نرمافزاری یکی از قابلیتهای مفید برای توسعه، دیباگ کردن و بررسی روند اجرای اپلیکیشنها است که بدین وسیله دولوپرها میتوانند روند اجرای برنامه را مانیتور کرده و در صورت بروز هر گونه اروری، با بررسی لاگهای مربوطه مشکل به وجود آمده را رفع نمایند (به منظور کسب اطلاعات بیشتر در رابطه با نحوۀ لاگگیری اصولی اپلیکیشن توصیه میکنیم به مقالۀ آشنایی با مفهوم Structured Logging در توسعهٔ نرمافزار مراجعه کنید.)
در واقع، لاگگیری امکان اِعمال کنترل بیشتر روی جریان برنامه را برای دولوپرها فراهم میآورد که بدین طریق میتوانند اطلاعات بیشتری از اشکالات به وجود آمده در پروژه به دست آورند به طوری که به جای رصد کردن برنامۀ مد نظر و بررسی خطبهخط آن، به لاگها مراجعه کرده و وضعیت برنامه را تا قبل از رسیدن به سطری بررسی میکنند که منجر به ارور شده است. به علاوه اینکه با ثبت اطلاعات مفید برنامه در فایلهای لاگگیری نه تنها میتوان ارور به وجود آمده را به راحتی دیباگ کرد، بلکه دیتای مربوطه به منظور آنالیز پرفورمنس اپلیکیشن نیز میتواند مورد استفاده قرار گیرد.
زبان برنامهنویسی پایتون ماژولی تحت عنوان logging
به منظور ارائۀ سیستم لاگگیری از پروژههای نرمافزاری فراهم کرده است که دولوپرها با بهکارگیری آن میتوانند قابلیت ثبت لاگ را به اپلیکیشن خود اضافه کنند که در همین راستا در این مقاله قصد داریم تا با این ماژول آشنا شده و برخی فیچرهای کاربردی آن به منظور ثبت اطلاعات مربوط به اجرای اپلیکیشن را معرفی کنیم.
آشنایی با ماژول لاگگیری استاندارد پایتونlogging
یک ماژول استاندارد در زبان برنامهنویسی پایتون است که امکان لاگگیری از اپلیکیشنهای کوچک و همچنین پروژههای اینترپرایز را دارا است و در اکثر لایبرریهای به اصطلاح Third-party در این زبان مورد استفاده قرار میگیرد که از همین روی امکان لاگگیری از چنین لایبرریهایی را در پروژه و در قالب یک فایل یکپارچه فراهم میکند.
به طور کلی، پنج سطح استاندارد لاگگیری به منظور نشان دادن شدت اهمیت رویدادها در جریان اجرای اپلیکیشن وجود دارد که در این ماژول متدهایی مجزا به منظور ثبت وقایع مربوط به هر یک از این سطوح استاندارد پیادهسازی شده است که این سطوح لاگگیری به ترتیب افزایش اهمیت عبارتند از:
- DEBUG: اطلاعات این سطح توسط دولوپرها و در حین تلاش برای دیباگ کردن برخی ارورها مورد استفاده قرار میگیرد.
- INFO: این اطلاعات توسط تیم توسعهدهنده به منظور بررسی عملکرد مورد انتظار از اپلیکیشن مورد استفاده قرار میگیرد.
- WARNING: این اطلاعات نشاندهندۀ بروز اتفاقی غیرمنتظره در جریان اجرای اپلیکیشن میباشد مضاف بر اینکه میتواند بیانگر بروز برخی مشکلات در آیندۀ نزدیک برای برنامه باشد.
- ERROR: دیتای این سطح به منظور نشان دادن وقوع مشکلی جدی در برنامه مورد استفاده قرار میگیرد به طوری که اجرای برخی از کامپوننتهای نرمافزار با مشکل مواجه شده است.
- CRITICAL: اطلاعات این سطح بیانگر وقوع مشکلی حاد در روند اجرای برنامه میباشد که منجر به از کار افتادن اپلیکیشن گردیده است.
برای استفاده از ویژگیهای ماژول logging
در برنامۀ خود لازم است تا در ابتدا این ماژول را با کامند زیر روی سیستم خود دانلود و نصب کنیم:
$ pip3 install logging
در گام بعدی ماژول مذکور را با اجرای دستور زیر ایمپورت میکنیم:
import logging
با ایمپورت کردن این ماژول، آبجکتی که اصطلاحاً Logger (لاگگیر) نامیده میشود در اختیار ما قرار میگیرد به طوری که این آبجکت امکان فراخوانی متدهای مد نظر به منظور ثبت لاگهای مربوط به اجرای اپلیکیشن و همچنین فیلترینگ و ثبت پیامهای متناسب با هر یک از سطوح استاندارد مد نظر در فایلهای لاگگیری را برای دولوپر فراهم میکند.
به صورت پیشفرض، سیستم لاگگیری در این ماژول روی آبجکتی تحت عنوان root
سِت شده است که پیامهایی با سطح استاندارد WARNING
به بالا را ثبت میکند. همچنین ماژول logging
یکسری فیچر پیشفرض به منظور لاگگیری فراهم میکند که بدون نیاز به اِعمال تغییر در تنظیمات اولیۀ این ماژول میتوان برای لاگگیری اپلیکیشن از آن استفاده کرد. در همین راستا، متدهای متناسب با هر یک از سطوح استاندارد به منظور ثبت لاگ مربوطه را در ادامه آوردهایم:
import logging
logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')
همانطور که میبینید، هر یک از دستورات به منظور فراخوانی متدهای مد نظر و برای ثبت لاگهای مربوط به هر یک از سطوح استاندارد فوقالذکر در روند اجرای اپلیکیشن فراخوانی شدهاند که خروجی حاصل از اجرای آنها بدین ترتیب خواهد بود:
WARNING:root:This is a warning message
ERROR:root:This is an error message
CRITICAL:root:This is a critical message
با توجه به خروجی فوق، میبینیم که در ابتدا نوع پیام ثبتشده نمایش داده میشود و در ادامه نام آبجکت پیشفرض به منظور ثبت لاگهای حاصل از اجرای برنامه، که در این مورد root
است، ذکر شده و به دنبال آن پیام مد نظر به منظور نمایش در فایل آمده که هر یک با علامت :
از یکدیگر جدا شدهاند. فرمت استاندارد خروجی برای لاگها در این ماژول بدین صورت تعریف شده است و امکان افزودن فیچرهای دیگری همچون زمان وقوع مشکل یا رویداد، شمارۀ خط مد نظر و مربوط به این پیام و سایر جزئیات برای دولوپرها فراهم شده است (در خروجی فوق پیامهای مربوط به سطوح استاندارد DEBUG
و INFO
در فایل لاگ ثبت نشدهاند چرا که تنظیمات پیشفرض این ماژول روی root
سِت شده و از همین روی آبجکت مذکور پیامهای با سطح اهمیت WARNING
به بالا را ثبت میکند. البته امکان اِعمال تغییرات مد نظر روی ماژول مذکور فراهم شده است تا بدین وسیله دولوپرها بتوانند پیامهای مربوط به رویدادهای حاصل از اجرای اپلیکیشن و در تمامی سطوح اهمیت را در لاگهای مد نظر خود ثبت کنند.)
نحوۀ پیکربندی ماژول
برای پیکربندی این ماژول از متدی تحت عنوان (basicConfig(**kwargs
استفاده میشود که برخی از پارامترهای ورودی رایج برای این متد را در ادامه معرفی میکنیم. پارامتر level
امکان تعریف سطح وقوع رویداد را برای لاگِر فراهم میکند که با استفاده از آن تمامی پیامهای مربوط به سطح مد نظر و سطوح بالاتر از آن در قالب لاگ ثبت خواهند شد، پارامتر filename
نام فایل مد نظر به منظور ثبت لاگ را نگهداری میکند، پارامتر filemode
نیز چنانچه نام فایلی به عنوان آرگومان ورودی به این متد داده شود سطح دسترسی به فایل مد نظر را تعریف میکند که به صورت پیشفرض روی حالت a
قرار دارد که برگرفته از کلمهٔ «Append» به معنای «افزودن» است که در هر مرتبه اجرای اپلیکیشن لاگ مربوطه به فایل مد نظر افزوده میشود و در نهایت پارامتر format
را داریم که فرمت پیامهای خروجی به منظور ثبت در فایل لاگ مربوطه را نشان میدهد. برای درک بهتر نحوۀ پیادهسازی متد ()basicConfig
مثال زیر را مد نظر قرار میدهیم:
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug('This will get logged')
در کد فوق، ابتدا ماژول logging
را ایمپورت کرده و در ادامه متد ()basicConfig
از این ماژول را فراخوانی کردهایم که پارامتر ورودی level
را به آن داده و گفتهایم تمامی رویدادهای مربوط به روند اجرای اپلیکیشن با سطح اهمیت DEBUG
به بالا را لاگگیری کند و همچنین رویداد مربوط به دیباگ نیز در قالب استرینگ «This will get logged» نمایش داده شود. خروجی حاصل از اجرای این کد بر اساس فرمت پیشفرض به صورت زیر خواهد بود:
DEBUG:root:This will get logged
همانطور که میبینید، پیام مربوط به فرآیند دیباگ در قالب استرینگ فوقالذکر نمایش داده میشود به علاوه اینکه لاگهای مربوط به روند اجرای اپلیکیشن را میتوانیم در فایلهای مد نظر خود ذخیره سازیم که این امر نیز با پارامترهای ورودی تحت عناوین filename
و filemode
برای دولوپرها فراهم شده است مضاف بر اینکه با بهکارگیری پارامتر دیگری به نام format
نیز میتوان ساختار پیامهای ذخیرهشده را مشخص کرد. مثال زیر کاربرد سه پارامتر مذکور را نشان میدهد:
import logging
logging.basicConfig(filename='app.log', filemode='w', format='%(name)s - %(levelname)s - %(message)s')
logging.warning('This will get logged to a file')
در کد فوق، پیام مد نظر در فایلی به نام app.log
و با سطح دسترسی w
معادل Write به معنای «نوشتن» ذخیره میشود بدین معنی که در هر بار فراخوانی متد ()basicConfig
فایل لاگ مذکور بازنویسی میشود و در سطر بعد گفتهایم استرینگ «This will get logged to a file» در قالب پیامی با سطح اهمیت WARNING
در فایل مربوطه ذخیره شود که خروجی حاصل از اجرای این کد به شرح زیر خواهد بود:
root - WARNING - This will get logged to a file
همانطور که ملاحظه میکنید، فرمت پیام خروجی بدین صورت است که در ابتدا نام آبجکت مربوط به سیستم نوشته شده است که به صورت پیشفرض root
میباشد و در ادامه سطح اهمیت رویداد مد نظر بیان شده که پیام مربوطه به عنوان یک به اصطلاح WARNING
در فایل مد نظر ثبت شده و به دنبال آن نیز پیام مربوط به هشدار در قالب استرینگ مد نظر نمایش داده شده است (همچنین لازم به یادآوری است که امکان دسترسی به پارامترهای ورودی بیشتری فراهم شده است تا دولوپرها بتوانند تغییرات مد نظر خود را روی متد ()basicConfig
و به منظور ثبت لاگهایی متناسب با نیازشان اِعمال کنند.)
نکتۀ قابلتوجه در مورد متد ()basicConfig
این است که فراخوانی متد مذکور به منظور پیکربندی لاگِر تنها در صورتی مؤثر میباشد که تنظیمات اولیه روی آبجکت root
اِعمال نشده باشد؛ به عبارت بهتر، آبجکت root
تنها یک بار قابلیت پیکربندی داشته و متد ()basicConfig
را تنها یک مرتبه میتوان برای آن فراخوانی کرد.
متدهایی همچون ()critical
و ()error
و همچنین متدهای مربوط به سطوح اهمیت پایینتر از جمله ()warning
و ()info
نیز قابلیت فراخوانی متد ()basicConfig
را بدون آرگومان ورودی دارند بدین معنی که پس از یک مرتبه فراخوانی، هر یک از متدهای فوق دیگر امکان پیکربندی لاگِر root
را نخواهند داشت چرا که هر یک از این متدها به صورت خودکار در پشت پرده متد ()basicConfig
را فراخوانی میکنند.
نحوۀ تعریف فرمت پیام خروجی
همانطور که در مثالهای قبل دیدیم، با استفاده از ماژول logging
میتوانیم استرینگ مد نظر خود به منظور نمایش در فایلِ لاگ مربوطه را به عنوان آرگومان ورودی به فانکشن ()basicConfig
پاس دهیم به علاوه اینکه فرمت نمایش پیام مذکور را میتوانیم با استفاده از برخی اِلِمانهای تعریفشده در این فانکشن شخصیسازی کنیم که برای مثال میتوان امکان اختصاص اصطلاحاً Process ID منحصربهفرد برای هر یک از پیامها را نام برد که برای این منظور کد زیر را مد نظر قرار میدهیم:
import logging
logging.basicConfig(format='%(process)d-%(levelname)s-%(message)s')
logging.warning('This is a Warning')
در کد فوق، متد ()basicConfig
از ماژول logging
را با یک پارامتر ورودی تحت عنوان format
فراخوانی کردهایم که در آن فرمت پیام خروجی را بدین صورت تعریف کردهایم که در ابتدا Process ID مربوط به رویداد مد نظر نمایش داده میشود سپس نوع پیام و در ادامه استرینگ تعریفشده برای پیام مد نظر در فایل مربوط به لاگگیری ثبت شود و در سطر بعد نیز گفتهایم پیام مذکور با سطح استاندارد WARNING
در قالب استرینگ «This is a Warning» نمایش داده شود. خروجی حاصل از اجرای کد فوق بدین صورت خواهد بود:
18472-WARNING-This is a Warning
به عنوان مثالی دیگر در رابطه با تعریف فرمت منحصربهفرد برای پیامهای خروجی، کد زیر را مد نظر قرار میدهیم:
import logging
logging.basicConfig(format='%(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S')
logging.warning('Admin logged out')
در کد فوق، متد ()basicConfig
را با دو پارامتر ورودی format
و datefmt
فراخوانی کردهایم که در آن فرمت پیام خروجی را بدین صورت تعریف کردهایم که در ابتدا تاریخ و ساعت وقوع رویداد مد نظر بر اساس فرمت تعیینشده در پارامتر ورودی ثبت میشود و به دنبال آن هم استرینگ تعریفشده در قالب یک هشدار (Warning) در فایل مربوطه ذکر میشود که خروجی حاصل از اجرای این کد به ترتیب زیر خواهد بود:
24-Jan-19 17:27:28 - Admin logged out
همچنین برای دسترسی به سایر اتربیوتهای تعریفشده در این ماژول به منظور شخصیسازی فرمت پیامهای خروجی میتوانید به مستندات لایبرری لاگینگ پایتون مراجعه نمایید.
ثبت دیتای مربوط به متغیرها در پیام خروجی
نیاز به توضیح نیست که ثبت لاگهای به اصطلاح دینامیک (پویا) از روند اجرای اپلیکشین مزایای بیشتری در جهت رفع مشکلات مربوطه خواهد داشت که در همین راستا ماژول logging
امکان ثبت دیتای مربوط به برخی متغیرها را نیز فراهم کرده است.
در مثالهای قبل دیدیم که استرینگهای مد نظر خود را میتوانیم در قالب پیامهایی با سطوح اهمیت متناظرشان در فایل مربوطه ثبت کنیم و از همین روی میتوانیم چنین استرینگهایی را به متغیرهای مد نظر خود منتسب کرده و نام متغیر مذکور را به عنوان آرگومان ورودی به فانکشن مربوطه پاس دهیم که برای این منظور مثال زیر را میتوان مد نظر داد:
import logging
name = 'User1'
logging.error('%s raised an error', name)
در کد فوق، استرینگ «User1» را به متغیری تحت عنوان name
منتسب کرده و در ادامه آن را به عنوان آرگومان ورودی به متد ()error
از این ماژول دادهایم سپس گفتهایم استرینگ منتسب به متغیر name
به جای علائم s%
قرار گرفته و به همراه استرینگ تعریفشده برای این متد در قالب پیام ارور و با فرمت خروجی پیشفرض در لاگفایل مربوطه ثبت شود که خروجی حاصل از اجرای این کد به صورت زیر خواهد بود:
ERROR:root:User1 raised an error
همانطور که میبینید استرینگ منتسب به متغیر name
با استرینگ تعریفشده در متد ()error
کانکت شده در معرض دید ما قرار گرفته است.
ثبت روند اجرای برنامه در لاگفایل
یکی دیگر از قابلیتهای ماژول logging
امکان ثبت به اصطلاح Stack Trace یا «روند کلی اجرای برنامه» در فایل مد نظر است به طوری که ثبت اطلاعات مربوط به اکسپشنهای برنامه نیز از طریق پارامتری تحت عنوان exc_info
امکانپذیر میباشد که برای آشنایی با نحوۀ کار آن مثال زیر را در ادامه آوردهایم:
import logging
a = 5
b = 0
try:
c = a / b
except Exception as e:
logging.error("Exception occurred", exc_info = True)
در کد فوق، دو متغیر تحت عناوین a
و b
تعریف کردهایم و مقدار اولیۀ آنها را به ترتیب برابر با اعداد پنج و صفر قرار دادهایم و در ادامه گفتهایم مقدار منتسب به متغیرهای a
و b
بر هم تقسیم شوند که در صورت بروز مشکل در انجام عملیات تقسیم، دستور سطر هفتم اجرا میشود که در آن فانکشن ()error
از ماژول logging
را فراخوانی کرده و مقدار پارامتر exc_info
برابر با True
قرار دادهایم تا علاوه بر نمایش استرینگ «Exception occurred» در قالب پیغام خطا، جزئیات مربوط به ارور اتفاق افتاده لاگگیری شود که در نهایت خروجی حاصل از اجرای کد فوق بدین صورت خواهد بود:
ERROR:root:Exception occurred
Traceback (most recent call last):
File "exceptions.py", line 6, in <module>
c = a / b
ZeroDivisionError: division by zero
[Finished in 0.2s]
همانطور که میبینید، پیغام خطایی در قالب استرینگ «Exception occurred» نمایش داده شده و همچنین جزئیات مربوط به ارور برنامه که در اینجا «division by zero» به معنای «تقسیم بر عدد صفر» میباشد، در ادامه ذکر شده است.
همچنین به خاطر داشته باشیم که چنانچه مقدار پارامتر exc_info
را برابر با True
قرار ندهیم، خروجی حاصل از اجرای برنامه هیچ پیامی مبنی بر جزئیات مربوط به ارورهای احتمالی را نخواهد داشت به علاوه اینکه فراخوانی متد ()exception
روشی سادهتر به منظور نمایش جزئیات اکسپشنها و ارورهای اپلیکیشن است که برای آشنایی با نحوۀ کار این متد مثال قبل را بدین صورت تغییر میدهیم:
import logging
a = 5
b = 0
try:
c = a / b
except Exception as e:
logging.exception("Exception occurred")
همانطور که میبینید، در کد فوق متدی تحت عنوان ()exception
را فراخوانی کردهایم که عملکردی مشابه متد ()error
با پارامتر ورودی exc_info
دارا است با این تفاوت که بهکارگیری آن سادهتر بوده و در صورت بروز مشکل در انجام عملیات تقسیمِ مقادیر منتسب به دو متغیر a
و b
فراخوانی میشود به طوری که علاوه بر نمایش پیام اکسپشن در قالب استرینگِ «Exception occurred»، جزئیات اکسپشن مربوطه را در لاگفایل ثبت میکند و خروجی حاصل از اجرای این کد مشابه مثال قبل بوده و جزئیات ارور مربوطه لاگگیری میشود که در اینجا تقسیم عدد صحیح بر عدد صفر است:
ERROR:root:Exception occurred
Traceback (most recent call last):
File "exceptions.py", line 6, in <module>
c = a / b
ZeroDivisionError: division by zero
[Finished in 0.2s]
متد ()exception
لاگهای مربوط به ارورهای احتمالی در جریان اجرای اپلیکیشن را ثبت میکند و به منظور ثبت لاگهای مربوط به سایر رویدادهای برنامه میتوانیم متدهایی همچون ()debug
و ()critical
و همچنین سایر متدهای تعریف شده در ماژول logging
را فراخوانی کنیم که برای ثبت پیامهای مد نظر لازم است تا متدهای مذکور با پارامتر ورودی exc_info
با مقدار معادل True
فراخوانی شوند.
جمعبندی
به طور کلی، میتوان گفت که ماژول logging
بسیار انعطافپذیر بوده و علاوه بر توانایی ثبت لاگهای مربوط به روند اجرای برنامه با تنظیمات پیشفرض، امکان پیادهسازی متدهای لاگینگ با قابلیتهای منحصربهفرد در آن برای دولوپرها فراهم شده است به طوری با اِعمال تغییرات مد نظر در این ماژول میتوان لاگهایی با سطوح اهمیت متفاوت و به اصطلاح کاستومایز از روند اجرای اپلیکیشن را ثبت کرد.