راهنمای گزارش‌گیری

نویسنده:

Vinay Sajip <vinay_sajip at red-dove dot com>

این صفحه شامل اطلاعات آموزشی است. برای پیوندهایی به اطلاعات مرجع و یک کتابچه‌ی آشپزی برای گزارش‌گیری (logging cookbook)، لطفاً منابع دیگر را ببینید.

آموزش مقدماتی گزارش‌گیری

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

چه زمانی از گزارش‌گیری استفاده کنیم

شما می‌توانید با ایجاد یک گزارش‌گیر از طریق logger = logging.getLogger(__name__) و سپس فراخوانی متدهای debug()، info()، warning()، error() و critical() آن، به قابلیت گزارش‌گیری دسترسی پیدا کنید. برای تشخیص اینکه چه زمانی از گزارش‌گیری استفاده کنید و ببینید چه زمانی از کدام متدهای گزارش‌گیر استفاده کنید، به جدول زیر مراجعه کنید. این جدول برای هر یک از مجموعه‌ای از وظایف رایج، بهترین ابزار برای استفاده در آن وظیفه را بیان می‌کند.

کاری که می‌خواهید انجام دهید

بهترین ابزار برای این کار

نمایش خروجی کنسول برای استفاده معمول از یک اسکریپت یا برنامه خط فرمان

print()

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

متد info() یک گزارش‌گیر (یا متد debug() برای خروجی با جزئیات بسیار برای اهداف تشخیصی)

صدور یک هشدار در مورد یک رویداد ران‌تایم خاص

warnings.warn() در کد کتابخانه‌ای، اگر مشکل قابل‌اجتناب است و برنامه‌ی کلاینت باید برای حذف هشدار اصلاح شود

متد warning() گزارش‌گیر، اگر برنامه کلاینت نتواند هیچ کاری درباره آن موقعیت انجام دهد، اما رویداد همچنان باید ثبت شود

گزارش یک خطا در مورد یک رویداد ران‌تایم خاص

پرتاب یک استثنا

گزارش فرونشانی یک خطا بدون پرتاب استثنا (مثلاً هندلر خطا در یک فرآیند سرور طولانی‌مدت)

متد error()، exception() یا critical() یک گزارش‌گیر، متناسب با خطای مشخص و حوزه‌ی کاربرد

متدهای گزارش‌گیر بر اساس سطح یا شدت رویدادهایی که برای پیگیری آن‌ها استفاده می‌شوند، نام‌گذاری شده‌اند. سطح‌های استاندارد و موارد کاربرد آن‌ها در زیر شرح داده شده‌اند (به‌ترتیب افزایشی شدت):

سطح

هنگامی که استفاده می‌شود

DEBUG

اطلاعات دقیق، معمولاً فقط هنگام اشکال‌زدایی مشکلات مورد توجه است.

INFO

تأیید اینکه همه‌چیز مطابق انتظار کار می‌کند.

WARNING

نشانه‌ای از اینکه اتفاق غیرمنتظره‌ای رخ داده است، یا نشان‌دهنده‌ی مشکلی در آینده‌ی نزدیک (مثلاً «فضای دیسک کم است»). نرم‌افزار همچنان مطابق انتظار کار می‌کند.

ERROR

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

CRITICAL

یک خطای جدی، که نشان می‌دهد خود برنامه ممکن است نتواند به اجرا ادامه دهد.

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

رویدادهایی که پیگیری می‌شوند را می‌توان به روش‌های مختلفی مدیریت کرد. ساده‌ترین روش برای مدیریت رویدادهای پیگیری‌شده، چاپ آن‌ها در کنسول است. یک روش رایج دیگر، نوشتن آن‌ها در یک پرونده روی دیسک است.

یک مثال ساده

یک مثال بسیار ساده:

import logging
logging.warning('Watch out!')  # will print a message to the console
logging.info('I told you so')  # will not print anything

اگر این سطرها را در یک اسکریپت وارد کنید و آن را اجرا کنید، خواهید دید:

WARNING:root:مراقب باشید!

در کنسول چاپ می‌شود. پیام INFO نمایش داده نمی‌شود زیرا سطح پیش‌فرض WARNING است. پیام چاپ‌شده شامل نشانگر سطح و شرح رویداد ارائه‌شده در فراخوانی logging است، یعنی 'Watch out!'. در صورت نیاز، می‌توان خروجی واقعی را با انعطاف‌پذیری بالایی قالب‌بندی کرد؛ گزینه‌های قالب‌بندی نیز بعداً توضیح داده خواهند شد.

توجه داشته باشید که در این مثال، به‌جای ایجاد یک گزارش‌گیر و فراخوانی توابع آن، مستقیماً از توابع ماژول logging، مانند logging.debug، استفاده می‌کنیم. این توابع بر روی گزارش‌گیر ریشه عمل می‌کنند، اما می‌توانند مفید باشند، زیرا اگر basicConfig() هنوز فراخوانی نشده باشد، آن را برای شما فراخوانی می‌کنند، همان‌طور که در این مثال دیده می‌شود. با این حال، در برنامه‌های بزرگ‌تر معمولاً می‌خواهید پیکربندی گزارش‌گیری را به‌صراحت کنترل کنید؛ بنابراین به همین دلیل و دلایل دیگر، بهتر است گزارش‌گیرها را ایجاد کنید و متدهای آن‌ها را فراخوانی کنید.

گزارش‌گیری در یک پرونده

یک حالت بسیار رایج، ثبت رویدادهای گزارش در یک پرونده است، بنابراین در ادامه به بررسی آن می‌پردازیم. حتماً موارد زیر را در یک مفسر پایتون که به‌تازگی آغاز شده است آزمایش کنید و صرفاً از نشست توصیف‌شده در بالا ادامه ندهید:

import logging
logger = logging.getLogger(__name__)
logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG)
logger.debug('This message should go to the log file')
logger.info('So should this')
logger.warning('And this, too')
logger.error('And non-ASCII stuff, too, like Øresund and Malmö')

تغییر یافته در نسخه‌ی 3.9: آرگومان encoding افزوده شد. در نسخه‌های پیشین پایتون، یا اگر مشخص نشده باشد، کدگذاری استفاده‌شده مقدار پیش‌فرضی است که توسط open() استفاده می‌شود. اگرچه در مثال بالا نشان داده نشده است، اکنون می‌توان آرگومان errors را نیز ارسال کرد که تعیین می‌کند خطاهای کدگذاری چگونه مدیریت شوند. برای مقادیر موجود و مقدار پیش‌فرض، مستندات open() را ببینید.

و اکنون اگر پرونده را باز کنیم و به آنچه داریم نگاه کنیم، باید پیام‌های گزارش را پیدا کنیم:

DEBUG:__main__:این پیام باید به پرونده گزارش برود
INFO:__main__:این پیام هم همین‌طور
WARNING:__main__:و این هم همین‌طور
ERROR:__main__:و موارد غیر اسکی (non-ASCII) نیز، مانند Øresund و Malmö

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

اگر می‌خواهید سطح گزارش را از طریق یک گزینه‌ی خط فرمان تنظیم کنید، مانند:

--log=INFO

و مقدار پارامتر داده‌شده برای --log را در متغیری به نام loglevel دارید، می‌توانید به این صورت استفاده کنید:

getattr(logging, loglevel.upper())

برای دریافت مقداری که آن را از طریق آرگومان level به basicConfig() می‌دهید. ممکن است بخواهید هر مقدار ورودی کاربر را از نظر خطا بررسی کنید، شاید مانند مثال زیر:

# assuming loglevel is bound to the string value obtained from the
# command line argument. Convert to upper case to allow the user to
# specify --log=DEBUG or --log=debug
numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int):
    raise ValueError('Invalid log level: %s' % loglevel)
logging.basicConfig(level=numeric_level, ...)

فراخوانی basicConfig() باید پیش از هر فراخوانی از متدهای گزارش‌گیر مانند debug()، info() و غیره انجام شود. در غیر این صورت، ممکن است آن رویداد گزارش به روش دلخواه مدیریت نشود.

اگر اسکریپت بالا را چند بار اجرا کنید، پیام‌های اجراهای پیاپی به پرونده example.log افزوده می‌شوند. اگر می‌خواهید هر اجرا از نو آغاز شود و پیام‌های اجراهای پیشین را به خاطر نداشته باشد، می‌توانید با تغییر فراخوانی در مثال بالا به شکل زیر، آرگومان filemode را مشخص کنید:

logging.basicConfig(filename='example.log', filemode='w', level=logging.DEBUG)

خروجی مانند قبل خواهد بود، اما دیگر به پرونده گزارش الحاق نمی‌شود، بنابراین پیام‌های اجراهای پیشین از دست می‌روند.

ثبت داده‌های متغیر

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

import logging
logging.warning('%s before you %s', 'Look', 'leap!')

نمایش خواهد داد:

WARNING:root:پیش از اقدام، تأمل کنید!

همان‌طور که می‌بینید، ادغام داده‌های متغیر در پیام توصیف رویداد از سبک قدیمی قالب‌بندی رشته با %-style استفاده می‌کند. این برای سازگاری با نسخه‌های قدیمی است: بسته‌ی logging پیش از گزینه‌های قالب‌بندی جدیدتری مانند str.format() و string.Template وجود داشته است. این گزینه‌های قالب‌بندی جدیدتر پشتیبانی می‌شوند، اما بررسی آن‌ها خارج از محدوده این آموزش است: برای اطلاعات بیشتر استفاده از سبک‌های قالب‌بندی خاص در سراسر برنامه شما را ببینید.

تغییر قالب پیام‌های نمایش‌داده‌شده

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

import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
logging.info('So should this')
logging.warning('And this, too')

که این را چاپ خواهد کرد:

DEBUG:این پیام باید در کنسول ظاهر شود
INFO:این پیام نیز همین‌طور
WARNING:و این پیام نیز همین‌طور

توجه داشته باشید که 'root' که در مثال‌های پیشین ظاهر شده بود، ناپدید شده است. برای مشاهده مجموعه کاملی از مواردی که می‌توانند در رشته‌های قالب ظاهر شوند، می‌توانید به مستندات ویژگی‌های LogRecord مراجعه کنید، اما برای استفاده ساده، فقط به levelname (شدت)، message (توصیف رویداد، شامل داده‌های متغیر) و احتمالاً به نمایش زمان وقوع رویداد نیاز دارید. این موضوع در بخش بعدی توضیح داده شده است.

نمایش تاریخ/زمان در پیام‌ها

برای نمایش تاریخ و زمان یک رویداد، '%(asctime)s' را در رشته قالب خود قرار می‌دهید:

import logging
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')

که باید چیزی شبیه به این چاپ کند:

2010-12-12 11:41:42,612 زمانی است که این رویداد ثبت شد.

قالب پیش‌فرض برای نمایش تاریخ/زمان (که در بالا نشان داده شد) مانند ISO8601 یا RFC 3339 است. اگر به کنترل بیشتری بر قالب‌بندی تاریخ/زمان نیاز دارید، یک آرگومان datefmt به basicConfig ارائه دهید، همان‌طور که در این مثال آمده است:

import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.warning('is when this event was logged.')

که چیزی شبیه به این را نمایش می‌دهد:

این رویداد در ۱۲/۱۲/۲۰۱۰ ۱۱:۴۶:۳۶ ق.ظ ثبت شده است.

قالب آرگومان datefmt همان قالبی است که time.strftime() از آن پشتیبانی می‌کند.

گام‌های بعدی

این آموزش مقدماتی به پایان رسید. این باید برای شروع کار شما با logging کافی باشد. بسته‌ی logging امکانات بسیار بیشتری ارائه می‌دهد، اما برای بهره‌مندی هرچه بهتر از آن، لازم است کمی بیشتر زمان خود را صرف خواندن بخش‌های بعدی کنید. اگر برای این کار آماده‌اید، نوشیدنی دلخواه‌تان را بردارید و ادامه دهید.

اگر نیازهای شما به گزارش ساده هستند، از مثال‌های بالا برای گنجاندن گزارش در اسکریپت‌های خود استفاده کنید و اگر با مشکلی مواجه شدید یا چیزی را متوجه نشدید، لطفاً پرسش خود را در دسته‌ی Help در انجمن گفتگوی پایتون ارسال کنید؛ باید به‌زودی کمک دریافت کنید.

هنوز اینجا هستید؟ می‌توانید به خواندن چند بخش بعدی ادامه دهید، که آموزشی کمی پیشرفته‌تر/عمیق‌تر از آموزش پایه‌ی بالا ارائه می‌دهند. پس از آن، می‌توانید نگاهی به کتاب آشپزی گزارش‌گیری (Logging Cookbook) بیندازید.

آموزش پیشرفته گزارش‌گیری

کتابخانه‌ی logging رویکردی ماژولار دارد و چندین دسته از کامپوننت‌ها را ارائه می‌دهد: گزارش‌گیرها، هندلرها، فیلترها و قالب‌بندها (formatters).

  • گزارش‌گیرها رابطی را که کد برنامه به‌طور مستقیم از آن استفاده می‌کند، در دسترس قرار می‌دهند.

  • هندلرها رکوردهای گزارش را که توسط گزارش‌گیرها ایجاد شده‌اند، به مقصد مناسب ارسال می‌کنند.

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

  • قالب‌بندها چیدمان رکوردهای گزارش را در خروجی نهایی مشخص می‌کنند.

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

گزارش کردن با فراخوانی متدهای نمونه‌هایی از کلاس Logger (که از این پس loggers نامیده می‌شوند) انجام می‌شود. هر نمونه یک نام دارد، و آن‌ها به‌صورت مفهومی در یک سلسله‌مراتب فضای نام با استفاده از نقطه‌ها (periods) به‌عنوان جداکننده چیده شده‌اند. برای مثال، یک گزارش‌گیر با نام 'scan' والد گزارش‌گیرهای 'scan.text'، 'scan.html' و 'scan.pdf' است. نام‌های گزارش‌گیر می‌توانند هر چیزی باشند که شما بخواهید، و بخشی از یک برنامه را که یک پیام گزارش‌شده از آن سرچشمه می‌گیرد، نشان می‌دهند.

یک قرارداد خوب هنگام نام‌گذاری گزارش‌گیرها این است که در هر ماژولی که از گزارش‌گیری استفاده می‌کند، از یک گزارش‌گیر سطح ماژول با نام زیر استفاده کنید:

logger = logging.getLogger(__name__)

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

ریشه‌ی سلسله‌مراتب گزارش‌گیرها، گزارش‌گیر ریشه نامیده می‌شود. این همان گزارش‌گیری است که توابع debug()، info()، warning()، error() و critical() از آن استفاده می‌کنند و صرفاً متد هم‌نام گزارش‌گیر ریشه را فراخوانی می‌کنند. این توابع و متدها امضاهای یکسانی دارند. نام گزارش‌گیر ریشه در خروجی گزارش‌شده به‌صورت 'root' چاپ می‌شود.

البته، امکان ثبت پیام‌های گزارش در مقصدهای مختلف وجود دارد. پشتیبانی برای نوشتن پیام‌های گزارش در پرونده‌ها، مکان‌های HTTP GET/POST، ایمیل از طریق SMTP، سوکت‌های عام، صف‌ها، یا سازوکارهای گزارش مختص سیستم‌عامل مانند syslog یا گزارش رویداد ویندوز NT در این بسته گنجانده شده است. مقصدها توسط کلاس‌های handler سرویس‌دهی می‌شوند. اگر نیازهای خاصی دارید که توسط هیچ‌یک از کلاس‌های handler توکار برآورده نمی‌شوند، می‌توانید کلاس مقصد گزارش خود را بسازید.

به‌طور پیش‌فرض، هیچ مقصدی برای پیام‌های گزارش تنظیم نشده است. می‌توانید با استفاده از basicConfig() همان‌طور که در مثال‌های آموزش آمده است، یک مقصد (مانند کنسول یا پرونده) را مشخص کنید. اگر توابع debug()، info()، warning()، error() و critical() را فراخوانی کنید، آن‌ها بررسی می‌کنند که آیا هیچ مقصدی تنظیم نشده است؛ و اگر مقصدی تنظیم نشده باشد، پیش از واگذاری خروجی واقعی پیام به گزارش‌گیر ریشه، یک مقصد از نوع کنسول (sys.stderr) و یک قالب پیش‌فرض برای پیام نمایش‌داده‌شده تنظیم می‌کنند.

قالب پیش‌فرضی که basicConfig() برای پیام‌ها تنظیم می‌کند، عبارت است از:

severity:logger name:message

شما می‌توانید این را با فرستادن یک رشته‌ی قالب به basicConfig() از طریق آرگومان کلیدواژه‌ای format تغییر دهید. برای همه‌ی گزینه‌های مربوط به نحوه‌ی ساخت یک رشته‌ی قالب، اشیای قالب‌بند (Formatter) را ببینید.

جریان گزارش‌گیری

جریان اطلاعات رویداد گزارش در گزارش‌گیرها و هندلرها در نمودار زیر نشان داده شده است.

Logger flow Create LogRecord Logging call in user code, e.g. logger.info(...) Stop Does a filter attached to logger reject the record? Pass record to handlers of current logger Is propagate true for current logger? Is there a parent logger? Set current logger to parent At least one handler in hierarchy? Use lastResort handler Handler enabled for level of record? Does a filter attached to handler reject the record? Stop Emit (includes formatting) Handler flow Logger enabled for level of call? No Yes Yes No No Yes Yes No No Yes No Yes No Yes Record passed to handler

گزارش‌گیرها

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

پرکاربردترین متدهای اشیای گزارش‌گیر به دو دسته تقسیم می‌شوند: پیکربندی و ارسال پیام.

این‌ها رایج‌ترین متدهای پیکربندی هستند:

  • Logger.setLevel() پایین‌ترین سطح شدت پیام گزارشی را مشخص می‌کند که یک گزارش‌گیر رسیدگی خواهد کرد، که در آن debug پایین‌ترین سطح شدت توکار و critical بالاترین شدت توکار است. برای مثال، اگر سطح شدت INFO باشد، گزارش‌گیر فقط پیام‌های INFO، WARNING، ERROR و CRITICAL را رسیدگی می‌کند و پیام‌های DEBUG را نادیده می‌گیرد.

  • Logger.addHandler() و Logger.removeHandler() اشیای handler را به شیء logger اضافه و از آن حذف می‌کنند. handlerها با جزئیات بیشتر در هندلرها پوشش داده شده‌اند.

  • Logger.addFilter() و Logger.removeFilter() اشیای فیلتر را به شیء گزارش‌گیر اضافه و از آن حذف می‌کنند. فیلترها با جزئیات بیشتر در اشیای فیلتر بررسی شده‌اند.

نیازی نیست همیشه این متدها را روی هر گزارش‌گیر که ایجاد می‌کنید فراخوانی کنید. دو پاراگراف آخر این بخش را ببینید.

با پیکربندی شیء logger، متدهای زیر پیام‌های گزارش ایجاد می‌کنند:

  • Logger.debug()، Logger.info()، Logger.warning()، Logger.error() و Logger.critical() همگی رکوردهای گزارش را ایجاد می‌کنند که دارای یک پیام و یک سطح هستند و سطح آن‌ها با نام متدهای مربوطه مطابقت دارد. پیام در واقع یک رشته‌ی قالب است که ممکن است حاوی سینتکس استاندارد جایگذاری رشته مانند %s، %d، %f و غیره باشد. سایر آرگومان‌های آن‌ها فهرستی از شیء‌ها است که با فیلدهای جایگذاری در پیام مطابقت دارند. در مورد **kwargs، متدهای گزارش تنها به کلیدواژه‌ای با نام exc_info توجه می‌کنند و از آن برای تعیین اینکه آیا اطلاعات استثنا در گزارش ثبت شود یا خیر استفاده می‌کنند.

  • Logger.exception() یک پیام گزارش مشابه Logger.error() ایجاد می‌کند. تفاوت این است که Logger.exception() یک ردگیری پشته را نیز همراه آن خروجی می‌دهد. این متد را فقط از داخل یک هندلر استثنا فراخوانی کنید.

  • Logger.log() یک سطح گزارش را به‌عنوان آرگومان صریح دریافت می‌کند. این روش برای گزارش کردن پیام‌ها کمی طولانی‌تر از استفاده از متدهای سهولت‌بخش سطح گزارش فهرست‌شده در بالا است، اما روش گزارش کردن در سطح‌های گزارش سفارشی همین است.

getLogger() در صورت ارائه شدن نام مشخص‌شده، ارجاعی به یک نمونه گزارش‌گیر با آن نام را برمی‌گرداند، و در غیر این صورت root را برمی‌گرداند. نام‌ها ساختارهای سلسله‌مراتبی هستند که با نقطه از هم جدا شده‌اند. فراخوانی‌های متعدد getLogger() با نام یکسان، ارجاعی به همان شیء گزارش‌گیر را برمی‌گرداند. گزارش‌گیرهای پایین‌تر در فهرست سلسله‌مراتبی، فرزندان گزارش‌گیرهای بالاتر در فهرست هستند. برای مثال، با فرض یک گزارش‌گیر با نام foo، گزارش‌گیرهای دارای نام‌های foo.bar، foo.bar.baz و foo.bam همگی از نوادگان foo هستند.

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

گزارش‌گیرهای فرزند پیام‌ها را به هندلرهای مرتبط با گزارش‌گیرهای اجدادی خود منتشر می‌کنند. به همین دلیل، نیازی به تعریف و پیکربندی هندلرها برای تمام گزارش‌گیرهایی که یک برنامه از آن‌ها استفاده می‌کند، نیست. کافی است هندلرها را برای یک گزارش‌گیر سطح بالا پیکربندی کنید و گزارش‌گیرهای فرزند را در صورت نیاز ایجاد کنید. (با این حال، می‌توانید با تنظیم ویژگی propagate یک گزارش‌گیر روی False، انتشار را غیرفعال کنید.)

هندلرها

اشیاء Handler مسئول ارسال پیام‌های گزارش مناسب (بر اساس شدت پیام‌های گزارش) به مقصد مشخص‌شده‌ی handler هستند. اشیاء Logger می‌توانند با متد addHandler() صفر یا چند شیء handler را به خود اضافه کنند. به‌عنوان یک سناریوی نمونه، یک برنامه ممکن است بخواهد تمام پیام‌های گزارش را به یک پرونده گزارش، تمام پیام‌های گزارش با سطح خطا یا بالاتر را به stdout و تمام پیام‌های سطح بحرانی را به یک نشانی ایمیل ارسال کند. این سناریو به سه handler جداگانه نیاز دارد که هر handler مسئول ارسال پیام‌هایی با شدتی مشخص به مکانی مشخص است.

کتابخانه استاندارد شامل تعداد نسبتاً زیادی از انواع هندلر است (به هندلرهای مفید مراجعه کنید)؛ آموزش‌ها در مثال‌های خود عمدتاً از StreamHandler و FileHandler استفاده می‌کنند.

در یک هندلر، متدهای بسیار کمی وجود دارد که توسعه‌دهندگان برنامه باید به آن‌ها بپردازند. تنها متدهای هندلر که به نظر می‌رسد برای توسعه‌دهندگان برنامه‌ای که از اشیای هندلر توکار استفاده می‌کنند (یعنی هندلرهای سفارشی ایجاد نمی‌کنند) مرتبط باشند، متدهای پیکربندی زیر هستند:

  • متد setLevel()، همان‌طور که در اشیای logger نیز چنین است، کمترین سطح شدتی را که به مقصد مناسب ارسال خواهد شد مشخص می‌کند. چرا دو متد setLevel() وجود دارد؟ سطح تنظیم‌شده در logger تعیین می‌کند که پیام‌ها با چه شدتی به handlerهای آن منتقل شوند. سطح تنظیم‌شده در هر handler تعیین می‌کند که آن handler کدام پیام‌ها را ارسال کند.

  • setFormatter() یک شیء Formatter را برای این هندلر انتخاب می‌کند تا از آن استفاده کند.

  • addFilter() و removeFilter() به‌ترتیب اشیای فیلتر را بر روی Handlerها پیکربندی و از پیکربندی خارج می‌کنند.

کد برنامه نباید مستقیماً از Handler نمونه‌سازی کند و از نمونه‌های آن استفاده کند. در عوض، کلاس Handler یک کلاس پایه است که رابطی را تعریف می‌کند که همه‌ی handlerها باید داشته باشند و برخی رفتارهای پیش‌فرض را تعیین می‌کند که کلاس‌های فرزند می‌توانند از آن‌ها استفاده کنند (یا آن‌ها را بازنویسی کنند).

قالب‌بندها

اشیاء قالب‌بند (Formatter) ترتیب نهایی، ساختار و محتوای پیام گزارش را پیکربندی می‌کنند. برخلاف کلاس پایه‌ی logging.Handler، کد برنامه می‌تواند کلاس‌های قالب‌بند را نمونه‌سازی کند، هرچند اگر برنامه شما به رفتار خاصی نیاز داشته باشد، به احتمال زیاد می‌توانید یک زیرکلاس از قالب‌بند ایجاد کنید. سازنده سه آرگومان اختیاری دریافت می‌کند -- یک رشته‌ی قالب پیام، یک رشته‌ی قالب تاریخ و یک نشانگر سبک.

logging.Formatter.__init__(fmt=None, datefmt=None, style='%')

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

%Y-%m-%d %H:%M:%S

با میلی‌ثانیه‌هایی که در انتها اضافه شده‌اند. style یکی از '%'، '{' یا '$' است. اگر یکی از این موارد تعیین نشده باشد، از '%' استفاده خواهد شد.

اگر style برابر '%' باشد، رشته قالب پیام از جایگزینی رشته به‌سبک %(<dictionary key>)s استفاده می‌کند؛ کلیدهای ممکن در ویژگی‌های LogRecord مستند شده‌اند. اگر سبک برابر '{' باشد، فرض می‌شود رشته قالب پیام با str.format() (با استفاده از آرگومان‌های کلیدواژه‌ای) سازگار باشد، در حالی که اگر سبک برابر '$' باشد، رشته قالب پیام باید با آنچه string.Template.substitute() انتظار دارد مطابقت داشته باشد.

تغییر یافته در نسخه‌ی 3.2: پارامتر style اضافه شد.

رشته‌ی قالب پیام زیر، زمان را در قالبی خوانا برای انسان، شدت پیام و محتوای پیام را به همین ترتیب ثبت می‌کند:

'%(asctime)s - %(levelname)s - %(message)s'

قالب‌بندکننده‌ها از یک تابع قابل‌پیکربندی توسط کاربر برای تبدیل زمان ایجاد یک رکورد به یک تاپل استفاده می‌کنند. به‌طور پیش‌فرض، از time.localtime() استفاده می‌شود؛ برای تغییر این موضوع برای یک نمونه‌ی خاص از قالب‌بندکننده، ویژگی converter آن نمونه را روی تابعی با همان امضای time.localtime() یا time.gmtime() تنظیم کنید. برای تغییر آن برای همه‌ی قالب‌بندکننده‌ها، برای مثال اگر می‌خواهید همه‌ی زمان‌های گزارش‌گیری به‌صورت GMT نمایش داده شوند، ویژگی converter را در کلاس Formatter تنظیم کنید (برای نمایش GMT، روی time.gmtime).

پیکربندی گزارش‌گیری

برنامه‌نویسان می‌توانند گزارش‌گیری را به سه روش پیکربندی کنند:

  1. ایجاد ثبت‌کننده‌ها، هندلرها و قالب‌بندها به‌صورت صریح با استفاده از کد پایتونی که متدهای پیکربندی فهرست‌شده در بالا را فراخوانی می‌کند.

  2. ایجاد یک پرونده‌ی پیکربندی گزارش‌گیری و خواندن آن با استفاده از تابع fileConfig().

  3. ایجاد یک دیکشنری از اطلاعات پیکربندی و ارسال آن به تابع dictConfig().

برای مستندات مرجع در مورد دو گزینه‌ی آخر، به توابع پیکربندی مراجعه کنید. مثال زیر با استفاده از کد پایتون، یک گزارش‌گیر بسیار ساده، یک هندلر کنسول (console handler) و یک قالب‌بند (formatter) ساده را پیکربندی می‌کند:

import logging

# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)

# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# add formatter to ch
ch.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

اجرای این ماژول از خط فرمان، خروجی زیر را تولید می‌کند:

$ python simple_logging_module.py
2005-03-19 15:10:26,618 - simple_example - DEBUG - debug message
2005-03-19 15:10:26,620 - simple_example - INFO - info message
2005-03-19 15:10:26,695 - simple_example - WARNING - warn message
2005-03-19 15:10:26,697 - simple_example - ERROR - error message
2005-03-19 15:10:26,773 - simple_example - CRITICAL - critical message

ماژول پایتون زیر یک گزارش‌گیر، هندلر و قالب‌بند (formatter) تقریباً یکسان با موارد موجود در مثال ذکرشده در بالا ایجاد می‌کند، با این تفاوت که تنها نام اشیاء متفاوت است:

import logging
import logging.config

logging.config.fileConfig('logging.conf')

# create logger
logger = logging.getLogger('simpleExample')

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

در اینجا پرونده logging.conf آمده است:

[loggers]
keys=root,simpleExample

[handlers]
keys=consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

خروجی تقریباً با خروجی مثال غیرمبتنی بر پرونده پیکربندی یکسان است:

$ python simple_logging_config.py
2005-03-19 15:38:55,977 - simpleExample - DEBUG - debug message
2005-03-19 15:38:55,979 - simpleExample - INFO - info message
2005-03-19 15:38:56,054 - simpleExample - WARNING - warn message
2005-03-19 15:38:56,055 - simpleExample - ERROR - error message
2005-03-19 15:38:56,130 - simpleExample - CRITICAL - critical message

می‌توانید ببینید که روش پرونده پیکربندی چند مزیت نسبت به روش کد پایتون دارد، عمدتاً جداسازی پیکربندی و کد و توانایی افراد غیربرنامه‌نویس برای تغییر آسان ویژگی‌های گزارش‌گیری.

هشدار

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

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

توجه داشته باشید که نام کلاس‌های ارجاع‌شده در پرونده‌های پیکربندی باید یا نسبت به ماژول logging نسبی باشند، یا مقادیر مطلقی باشند که بتوان آن‌ها را با استفاده از سازوکارهای عادی ایمپورت حل کرد. بنابراین، می‌توانید از WatchedFileHandler (نسبت به ماژول logging) یا mypackage.mymodule.MyHandler (برای کلاسی که در بسته‌ی mypackage و ماژول mymodule تعریف شده باشد و mypackage در مسیر ایمپورت پایتون در دسترس باشد) استفاده کنید.

در پایتون 3.2، روش جدیدی برای پیکربندی گزارش‌گیری معرفی شده است که در آن از دیکشنری‌ها برای نگه‌داری اطلاعات پیکربندی استفاده می‌شود. این روش، ابرمجموعه‌ای از قابلیت‌های رویکرد مبتنی بر پرونده پیکربندیِ ذکرشده در بالا را فراهم می‌کند و روش پیکربندی توصیه‌شده برای برنامه‌ها و استقرارهای جدید است. از آنجا که از یک دیکشنری پایتون برای نگه‌داری اطلاعات پیکربندی استفاده می‌شود، و از آنجا که می‌توانید آن دیکشنری را با روش‌های مختلفی پر کنید، گزینه‌های بیشتری برای پیکربندی دارید. برای مثال، می‌توانید از یک پرونده پیکربندی در قالب JSON، یا در صورت دسترسی به قابلیت پردازش YAML، از یک پرونده در قالب YAML، برای پر کردن دیکشنری پیکربندی استفاده کنید. یا البته، می‌توانید دیکشنری را در کد پایتون بسازید، آن را به‌صورت pickled از طریق یک سوکت دریافت کنید، یا از هر رویکردی که برای برنامه شما مناسب است استفاده کنید.

در اینجا نمونه‌ای از همان پیکربندی بالا، در قالب YAML برای رویکرد جدید مبتنی بر دیکشنری آمده است:

version: 1
formatters:
  simple:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: simple
    stream: ext://sys.stdout
loggers:
  simpleExample:
    level: DEBUG
    handlers: [console]
    propagate: no
root:
  level: DEBUG
  handlers: [console]

برای اطلاعات بیشتر درباره‌ی گزارش‌کردن با استفاده از یک دیکشنری، توابع پیکربندی را ببینید.

اگر هیچ پیکربندی ارائه نشود، چه اتفاقی می‌افتد

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

این رویداد با استفاده از یک «هندلر آخرین چاره» (handler of last resort) که در lastResort ذخیره شده است، خروجی داده می‌شود. این هندلر داخلی به هیچ گزارش‌گیری مرتبط نیست و مانند یک StreamHandler عمل می‌کند که پیام شرح رویداد را در مقدار فعلی sys.stderr می‌نویسد (بنابراین هرگونه تغییرمسیری را که ممکن است فعال باشد رعایت می‌کند). هیچ قالب‌بندی روی پیام انجام نمی‌شود؛ فقط پیام شرح رویداد به‌صورت خام چاپ می‌شود. سطح این هندلر روی WARNING تنظیم شده است، بنابراین همه رویدادها با این شدت و شدت‌های بیشتر خروجی داده خواهند شد.

تغییر یافته در نسخه‌ی 3.2: در نسخه‌های پایتون پیش از 3.2، رفتار به شرح زیر است:

  • اگر raiseExceptions برابر False باشد (حالت تولید)، رویداد به‌صورت بی‌صدا حذف می‌شود.

  • اگر raiseExceptions برابر True باشد (حالت توسعه)، پیام 'No handlers could be found for logger X.Y.Z' یک بار چاپ می‌شود.

برای به دست آوردن رفتار پیش از 3.2، می‌توان lastResort را روی None تنظیم کرد.

پیکربندی گزارش برای یک کتابخانه

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

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

یک هندلر بدون عملیات در بسته‌ی logging گنجانده شده است: NullHandler (از پایتون 3.1). می‌توان یک نمونه از این هندلر را به گزارش‌گیر سطح بالای فضای نام گزارش‌گیری مورد استفاده‌ی کتابخانه اضافه کرد (اگر بخواهید در نبود پیکربندی گزارش‌گیری، از نوشته شدن رویدادهای گزارش‌شده‌ی کتابخانه‌تان در sys.stderr جلوگیری کنید). اگر تمام گزارش‌گیری کتابخانه‌ی foo با استفاده از گزارش‌گیرهایی با نام‌های مطابق با 'foo.x'، 'foo.x.y' و غیره انجام شود، آنگاه کد:

import logging
logging.getLogger('foo').addHandler(logging.NullHandler())

باید اثر مطلوب را داشته باشد. اگر سازمانی تعدادی کتابخانه تولید کند، آنگاه نام گزارش‌گیر مشخص‌شده می‌تواند به‌جای فقط 'foo'، 'orgname.foo' باشد.

توجه

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

توجه

اکیداً توصیه می‌شود که هیچ هندلری به‌جز NullHandler به گزارش‌گیرهای کتابخانه خود اضافه نکنید. دلیل این امر آن است که پیکربندی هندلرها در اختیار توسعه‌دهنده‌ی برنامه‌ای است که از کتابخانه شما استفاده می‌کند. توسعه‌دهنده‌ی برنامه مخاطبان هدف و مناسب‌ترین هندلرها برای برنامه‌اش را می‌داند: اگر «در پشت صحنه» هندلرهایی اضافه کنید، ممکن است در توانایی او برای اجرای آزمون واحد‌ها و ارائه گزارش‌هایی که نیازهایش را برآورده می‌کنند، اختلال ایجاد کنید.

سطوح گزارش

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

سطح

مقدار عددی

CRITICAL

50

ERROR

40

WARNING

30

INFO

20

DEBUG

10

NOTSET

0

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

پیام‌های ثبت رویداد به‌صورت نمونه‌هایی از کلاس LogRecord کدگذاری می‌شوند. هنگامی که یک گزارش‌گیر تصمیم می‌گیرد واقعاً رویدادی را ثبت کند، نمونه‌ای از LogRecord بر اساس پیام ثبت رویداد ایجاد می‌شود.

پیام‌های گزارش با استفاده از handlers تحت یک سازوکار توزیع قرار می‌گیرند؛ هندلرها نمونه‌هایی از زیرکلاس‌های کلاس Handler هستند. هندلرها مسئول اطمینان از این هستند که پیام گزارش‌شده (در قالب LogRecord) در مکان خاصی (یا مجموعه‌ای از مکان‌ها) قرار گیرد که برای مخاطبان هدف آن پیام مفید باشد (مانند کاربران نهایی، کارکنان میز پشتیبانی، مدیران سیستم، توسعه‌دهندگان). نمونه‌های LogRecord که برای مقاصد خاصی در نظر گرفته شده‌اند، به هندلرها ارسال می‌شوند. هر گزارش‌گیر می‌تواند صفر، یک یا چند هندلر مرتبط با خود داشته باشد (از طریق متد addHandler() کلاس Logger). علاوه بر هر هندلری که مستقیماً با یک گزارش‌گیر مرتبط است، تمام هندلرهای مرتبط با تمام اجداد آن گزارش‌گیر برای توزیع پیام فراخوانی می‌شوند (مگر اینکه پرچم propagate برای یک گزارش‌گیر روی مقدار نادرست تنظیم شده باشد، که در آن حالت، ارسال به هندلرهای اجداد متوقف می‌شود).

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

سطوح سفارشی

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

هندلرهای مفید

علاوه بر کلاس پایه Handler، زیرکلاس‌های مفید زیادی ارائه شده‌اند:

  1. نمونه‌های StreamHandler پیام‌ها را به جریان‌ها (اشیاء شبه‌پرونده) ارسال می‌کنند.

  2. نمونه‌های FileHandler پیام‌ها را به پرونده‌های دیسک ارسال می‌کنند.

  3. BaseRotatingHandler کلاس پایه برای هندلرهایی است که پرونده‌های گزارش را در نقطه‌ای معین می‌چرخانند. این کلاس برای نمونه‌سازی مستقیم در نظر گرفته نشده است. در عوض، از RotatingFileHandler یا TimedRotatingFileHandler استفاده کنید.

  4. نمونه‌های RotatingFileHandler پیام‌ها را به پرونده‌های دیسک ارسال می‌کنند و از حداکثر اندازه پرونده‌های گزارش و چرخش پرونده گزارش پشتیبانی می‌کنند.

  5. نمونه‌های TimedRotatingFileHandler پیام‌ها را به پرونده‌های دیسک ارسال می‌کنند و پرونده گزارش را در بازه‌های زمانی مشخصی می‌چرخانند.

  6. نمونه‌های SocketHandler پیام‌ها را به سوکت‌های TCP/IP ارسال می‌کنند. از نسخه 3.4، از سوکت‌های دامنه یونیکس (Unix domain sockets) نیز پشتیبانی می‌شود.

  7. نمونه‌های DatagramHandler پیام‌ها را به سوکت‌های UDP ارسال می‌کنند. از 3.4، سوکت‌های دامنه یونیکس نیز پشتیبانی می‌شوند.

  8. نمونه‌های SMTPHandler پیام‌ها را به یک نشانی ایمیل تعیین‌شده ارسال می‌کنند.

  9. نمونه‌های SysLogHandler پیام‌ها را به یک دیمون syslog در یونیکس ارسال می‌کنند، احتمالاً روی یک ماشین راه دور.

  10. نمونه‌های NTEventLogHandler پیام‌ها را به گزارش رویداد ویندوز NT/2000/XP ارسال می‌کنند.

  11. نمونه‌های MemoryHandler پیام‌ها را به بافری در حافظه ارسال می‌کنند که هر زمان معیارهای خاصی برآورده شوند، تخلیه می‌شود.

  12. نمونه‌های HTTPHandler پیام‌ها را با استفاده از معنای GET یا POST به یک سرور HTTP ارسال می‌کنند.

  13. نمونه‌های WatchedFileHandler پرونده‌ای را که در آن گزارش می‌کنند، پایش می‌کنند. اگر پرونده تغییر کند، بسته می‌شود و با استفاده از نام پرونده دوباره باز می‌شود. این هندلر فقط در سیستم‌های شبه‌یونیکس مفید است؛ ویندوز از سازوکار زیربنایی مورد استفاده پشتیبانی نمی‌کند.

  14. نمونه‌های QueueHandler پیام‌ها را به یک صف ارسال می‌کنند، مانند صف‌هایی که در ماژول‌های queue یا multiprocessing پیاده‌سازی شده‌اند.

  15. نمونه‌های NullHandler هیچ کاری با پیام‌های خطا انجام نمی‌دهند. توسعه‌دهندگان کتابخانه‌ها که می‌خواهند از گزارش‌گیری استفاده کنند، اما می‌خواهند از پیام «No handlers could be found for logger XXX» اجتناب کنند، از این نمونه‌ها استفاده می‌کنند؛ پیامی که ممکن است در صورتی که کاربر کتابخانه گزارش‌گیری را پیکربندی نکرده باشد، نمایش داده شود. برای اطلاعات بیشتر پیکربندی گزارش برای یک کتابخانه را ببینید.

اضافه شده در نسخه‌ی 3.1: کلاس NullHandler.

اضافه شده در نسخه‌ی 3.2: کلاس QueueHandler.

کلاس‌های NullHandler، StreamHandler و FileHandler در بسته‌ی اصلی logging تعریف شده‌اند. سایر هندلرها در یک زیرماژول، logging.handlers تعریف شده‌اند. (همچنین زیرماژول دیگری، logging.config، برای قابلیت پیکربندی وجود دارد.)

پیام‌های ثبت‌شده برای نمایش از طریق نمونه‌هایی از کلاس Formatter قالب‌بندی می‌شوند. آن‌ها با یک رشته قالب مناسب برای استفاده با عملگر % و یک دیکشنری مقداردهی اولیه می‌شوند.

برای قالب‌بندی چندین پیام در یک دسته، می‌توان از نمونه‌های BufferingFormatter استفاده کرد. علاوه بر رشته‌ی قالب (که به هر پیام در دسته اعمال می‌شود)، امکانی برای رشته‌های قالب سرآیند و پایانی وجود دارد.

هنگامی که فیلتر کردن بر اساس سطح logger و/یا سطح handler کافی نباشد، می‌توان نمونه‌هایی از Filter را به هر دو نمونه‌ی Logger و Handler اضافه کرد (از طریق متد addFilter() آن‌ها). پیش از تصمیم به ادامه‌ی پردازش یک پیام، logger و handler هر دو برای کسب اجازه تمام فیلترهای خود را بررسی می‌کنند. اگر هر یک از فیلترها مقدار نادرستی برگرداند، پیام بیش از این پردازش نمی‌شود.

قابلیت پایه‌ای Filter امکان فیلتر کردن بر اساس نام گزارش‌گیر مشخص را فراهم می‌کند. اگر از این قابلیت استفاده شود، پیام‌های ارسال‌شده به گزارش‌گیر نام‌برده و فرزندان آن از فیلتر عبور داده می‌شوند و تمام پیام‌های دیگر حذف می‌شوند.

استثناهای پرتاب‌شده هنگام گزارش کردن

بسته‌ی گزارش‌گیری به‌گونه‌ای طراحی شده است که استثناهایی را که هنگام گزارش‌گیری در محیط عملیاتی رخ می‌دهند، ببلعد. این برای آن است که خطاهایی که هنگام مدیریت رویدادهای گزارش‌گیری رخ می‌دهند — مانند پیکربندی نادرست گزارش‌گیری، خطاهای شبکه یا دیگر خطاهای مشابه — باعث خاتمه‌ی زودهنگام برنامه‌ی کاربردی‌ای که از گزارش‌گیری استفاده می‌کند، نشوند.

استثناهای SystemExit و KeyboardInterrupt هرگز نادیده گرفته نمی‌شوند. سایر استثناهایی که در حین اجرای متد emit() یک کلاس فرعی از Handler رخ می‌دهند، به متد handleError() آن ارسال می‌شوند.

پیاده‌سازی پیش‌فرض handleError() در Handler بررسی می‌کند که آیا یک متغیر سطح ماژول به نام raiseExceptions تنظیم شده است یا خیر. اگر تنظیم شده باشد، یک ردگیری پشته در sys.stderr چاپ می‌شود. اگر تنظیم نشده باشد، استثنا نادیده گرفته می‌شود.

توجه

مقدار پیش‌فرض raiseExceptions برابر True است. این به این دلیل است که در طول توسعه، معمولاً می‌خواهید از هر استثنایی که رخ می‌دهد مطلع شوید. توصیه می‌شود برای استفاده در محیط عملیاتی، raiseExceptions را روی False تنظیم کنید.

استفاده از اشیاء دلخواه به‌عنوان پیام

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

بهینه‌سازی

قالب‌بندی آرگومان‌های پیام تا زمانی که نتوان از آن اجتناب کرد، به تعویق می‌افتد. با این حال، محاسبه آرگومان‌هایی که به متد گزارش‌کردن ارسال می‌شوند نیز می‌تواند پرهزینه باشد، و ممکن است بخواهید از انجام آن اجتناب کنید اگر گزارش‌گیر قرار باشد صرفاً رویداد شما را دور بیندازد. برای تصمیم‌گیری درباره اینکه چه کاری انجام دهید، می‌توانید متد isEnabledFor() را فراخوانی کنید که یک آرگومان سطح می‌گیرد و در صورتی مقدار true را برمی‌گرداند که رویداد برای آن سطح از فراخوانی توسط Logger ایجاد شود. می‌توانید کدی مانند زیر بنویسید:

if logger.isEnabledFor(logging.DEBUG):
    logger.debug('Message with %s, %s', expensive_func1(),
                                        expensive_func2())

به‌گونه‌ای که اگر آستانه‌ی گزارش‌گیر بالاتر از DEBUG تنظیم شده باشد، فراخوانی‌های expensive_func1 و expensive_func2 هرگز انجام نمی‌شوند.

توجه

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

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

آنچه نمی‌خواهید جمع‌آوری شود

چگونه از زباله‌روبی آن اجتناب کنیم

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

logging._srcfile را روی None تنظیم کنید. این کار از فراخوانی sys._getframe() جلوگیری می‌کند، که ممکن است به افزایش سرعت کد شما در محیط‌هایی مانند PyPy کمک کند (که نمی‌تواند سرعت کدی را که از sys._getframe() استفاده می‌کند افزایش دهد).

اطلاعات نخ‌بندی.

logging.logThreads را روی False تنظیم کنید.

شناسه فرایند جاری (os.getpid())

logging.logProcesses را روی False تنظیم کنید.

نام فرایند فعلی هنگام استفاده از multiprocessing برای مدیریت چندین فرایند.

logging.logMultiprocessing را روی False تنظیم کنید.

نام فعلی asyncio.Task هنگام استفاده از asyncio.

logging.logAsyncioTasks را روی False تنظیم کنید.

همچنین توجه داشته باشید که ماژول اصلی logging فقط شامل هندلرهای پایه است. اگر logging.handlers و logging.config را ایمپورت نکنید، آن‌ها هیچ حافظه‌ای را اشغال نخواهند کرد.

منابع دیگر

همچنین ملاحظه نمائید

ماژول logging

مرجع API برای ماژول logging.

ماژول logging.config

API پیکربندی برای ماژول logging.

ماژول logging.handlers

هندلرهای مفید موجود در ماژول logging.

کتابچه آشپزی ثبت رویداد