Loguru کتابخانه ای است که هدف آن لاگگیری لذت بخش در پایتون است.
آیا تا به حال در مورد پیکربندی یک لاگر احساس تنبلی کرده اید و آیا به جای آن از ()print استفاده می کنید؟ با استفاده از Loguru شما هیچ بهانه ای برای عدم استفاده از لاگر از ابتدا ندارید، این کار به سادگی from loguru import logger است.
همچنین، این کتابخانه این را نیز در نظر گرفته است تا با افزودن دسته ای از توابع مفید که اخطارهای لاگ کردن های استاندارد را حل می کند، لاگینگ پایتون را کمتر دردسرساز کند. استفاده از لاگ در برنامه شما باید خودکار باشد، Loguru سعی می کند آن را هم دلپذیر و هم قدرتمند کند.
به همان راحتی که فکر می کنید.
pip install loguru- آماده باز کردن بسته Loguru هستی؟
- بدون نگه دارنده، قالب ساز و حتی فیلتر: یک تابع برای کنترل همه آنها
- واقعه نگاری آسان تر درون یک فایل با rotation / retention / compression
- قالب بندی مدرن رشته ها با استفاده از سبک آکولاد
- گرفتن و ثبت استثنائات داخل کد اصلی یا ریسه ها (threads)
- یه لاگینگ قشنگ رنگی
- ناهمزمان، ایمن برای ریسه ها(Theard)، ایمن برای چند فرآیندی
- استثناهای کاملاً توصیف شده
- لاگینگ ساختار یافته لازم دارید؟
- ارزیابی سست کارکرد های پرخرج
- سطوح قابل تنظیم
- مدیریت بهتر زمان و تاریخ
- مناسب برای اسکریپت ها و کتابخانه ها
- کاملا به لاگینگ استاندارد سازگار است
- پیش فرض های قابل شخصی سازی از طریق متغیرهای محیطی
- یه تجزیه کننده راحت
- یه اطلاع دهنده کامل و جامع
10 برابر سریعتر از لاگینگ داخلی
مفهوم اصلی Loguru این است که یک و تنها یک لاگر وجود دارد.
برای راحتی بیشتر، از پیش پیکربندی شده است و برای شروع به صورت stderr خروجی میدهد (اما این کاملاً قابل تنظیم است).
from loguru import logger
logger.debug("خود خودشه، یه لاگینگ ساده و زیبا")لاگر فقط یک رابط است که پیام های لاگ را به کنترل کننده های پیکربندی شده ارسال می کند. سادست، مگه نه؟
چگونه یک نگه دارنده اضافه کنیم؟ چگونه قالب بندی گزارش ها را تنظیم کنیم؟ چگونه پیام ها را فیلتر کنیم؟ چگونه سطح را تنظیم کنیم؟
جواب سادست با تابع ()add .
logger.add(sys.stderr, format="{time} {level} {message}", filter="my_module", level="INFO")این تابع برای ثبت سینک ها استفاده می شود که آنها مسئول مدیریت پیام های لاگ ها به شکل گزارشی از نوع دیکشنری هستند. یک سینک می تواند اشکال مختلفی داشته باشد: یک تابع ساده، یک رشته آدرس، یک شی فایل مانند، یک تابع موازی (coroutine) یا یک نگه دارنده داخلی.
توجه داشته باشید که شما میتوانید با استفاده از شناسهای که هنگام اضافه کردن آن بازگردانده شده است، یک نگه دارنده که قبلاً اضافه شده را حذف کنید. این به ویژه در صورتی مفید است که میخواهید نگه دارنده پیشفرض stderr را جایگزین کنید: کافیست برای شروعی تازه، ()logger.remove را فراخوانی کنید.
اگر شما می خواهید که پیام ها را داخل فایلی لاگ نمایید فقط کافیست از رشته آدرس فایل به عنوان یک سینک استفاده نمایید. همچنین برای راحتی می توان آن را به طور خودکار زمان بندی کرد:
logger.add("file_{time}.log")همچنین اگر به لاگر چرخشی نیاز دارید، اگر میخواهید گزارشهای قدیمیتر را حذف کنید، یا اگر میخواهید فایلهای خود را در زمان بسته شدن فشرده کنید، به راحتی قابل تنظیم است.
logger.add("file_1.log", rotation="500 MB") # به طور خودکار فایل های بزرگ را میچرخونه
logger.add("file_2.log", rotation="12:00") # هر روز ظهر یه فایل جدید ایجاد میکنه
logger.add("file_3.log", rotation="1 week") # هر یه هفته فایل جدید ایجاد میکنه
logger.add("file_X.log", retention="10 days") # بعد از یه مدت فایل پاک میشه
logger.add("file_Y.log", compression="zip") # فضای دوست داشتنیمون رو بر می گردونهLoguru از قالب بندی بسیار زیباتر و قدرتمندتر {} بیش از`` % `` استفاده می کند، توابع ورود به سیستم در واقع معادل ()str.format هستند.
logger.info("If you're using Python {}, prefer {feature} of course!", 3.6, feature="f-strings")آیا تا به حال دیده اید که برنامه خود به طور غیرمنتظره ای از کار بیفتد بدون اینکه چیزی در فایل لاگ مشاهده کنید؟ آیا تا به حال متوجه شده اید که استثناهای رخ داده در رشته ها ثبت نشده اند؟ این را می توان با استفاده از ()catch (به صورت دکوریتور یا داخل کد) حل کرد که تضمین می کند هر گونه خطا به درستی در لاگر منتشر می شود.
@logger.catch
def my_function(x, y, z):
# An error? It's caught anyway!
return 1 / (x + y + z)Loguru به طور خودکار رنگ ها را به لاگ های شما اضافه می کند. شما می توانید با استفاده از برچسب های نشانه گذاری در قالب سینک، سبک مورد علاقه خود را تعریف کنید. البته اگر ترمینال شما سازگار باشد.
logger.add(sys.stdout, colorize=True, format="<green>{time}</green> <level>{message}</level>")تمام سینک های اضافه شده به لاگر به طور پیش فرض در برابر ریسه ها (Theard) امن هستند. آنها برای چند فرآیندی ایمن نیستند، اما میتوانید پیامها را در صف قرار دهید (enqueue) تا از یکپارچگی لاگ ها اطمینان حاصل کنید. اگر میخواهید گزارش غیرهمگام داشته باشید، میتوانید از همین آرگومان استفاده کنید.
logger.add("somefile.log", enqueue=True)توابع کوروتین که به عنوان سینک استفاده میشوند نیز پشتیبانی میشوند و باید با ()complete در انتظار آنها باشیم.
ثبت استثناهایی که در کد شما اتفاق میافتد برای ردیابی باگها مهم است، اما اگر ندانید چرا شکست خورده است، کاملاً بیفایده است. Loguru به شما کمک می کند تا با اجازه دادن به نمایش کل ردیابی پشته، از جمله مقادیر متغیرها، مشکلات را شناسایی کنید(از better_exceptions برای این قابلیت تشکر می کنیم!).
کد:
logger.add("out.log", backtrace=True, diagnose=True) # Caution, may leak sensitive data in prod
def func(a, b):
return a / b
def nested(c):
try:
func(5, c)
except ZeroDivisionError:
logger.exception("What?!")
nested(0)منجر به این میشه:
2018-07-17 01:38:43.975 | ERROR | __main__:nested:10 - What?!
Traceback (most recent call last):
File "test.py", line 12, in <module>
nested(0)
└ <function nested at 0x7f5c755322f0>
> File "test.py", line 8, in nested
func(5, c)
│ └ 0
└ <function func at 0x7f5c79fc2e18>
File "test.py", line 4, in func
return a / b
│ └ 0
└ 5
ZeroDivisionError: division by zero
آیا میخواهید لاگهای شما برای تجزیه آسانتر سریالی شوند یا آنها را منتقل کنید؟ با استفاده از آرگومان serialize، هر پیام لاگ قبل از ارسال به سینک پیکربندی شده به یک رشته JSON تبدیل می شود.
logger.add(custom_sink_function, serialize=True)با استفاده از ()bind میتوانید پیامهای لاگر خود را با تغییر ویژگی رکورد اضافی، به نمایش در آورید.
logger.add("file.log", format="{extra[ip]} {extra[user]} {message}")
context_logger = logger.bind(ip="192.168.0.1", user="someone")
context_logger.info("Contextualize your logger easily")
context_logger.bind(user="someone_else").info("Inline binding of extra attribute")
context_logger.info("Use kwargs to add context during formatting: {user}", user="anybody")حتی میشه یه حالت موقتی با محتوای محلی با ()contextualize ایجاد کرد:
with logger.contextualize(task=task_id):
do_something()
logger.info("End of task")شما همچنین می توانید با ترکیب ()bind و filter، کنترل دقیق تری بر لاگ های خود داشته باشید:
logger.add("special.log", filter=lambda record: "special" in record["extra"])
logger.debug("This message is not logged to the file")
logger.bind(special=True).info("This message, though, is logged to the file!")در نهایت، متد ()patch اجازه میدهد که مقادیر پویا به رکورد دیکشنری هر پیام جدید متصل شوند:
logger.add(sys.stderr, format="{extra[utc]} {message}")
logger = logger.patch(lambda record: record["extra"].update(utc=datetime.utcnow()))گاهی اوقات می خواهید اطلاعات پرمخاطب را بدون جریمه عملکرد در تولید ثبت کنید، می توانید از روش ()opt برای رسیدن به این هدف استفاده کنید.
logger.opt(lazy=True).debug("If sink level <= DEBUG: {x}", x=lambda: expensive_function(2**64))
# By the way, "opt()" serves many usages
logger.opt(exception=True).info("Error stacktrace added to the log message (tuple accepted too)")
logger.opt(colors=True).info("Per message <blue>colors</blue>")
logger.opt(record=True).info("Display values from the record (eg. {record[thread]})")
logger.opt(raw=True).info("Bypass sink formatting\n")
logger.opt(depth=1).info("Use parent stack context (useful within wrapped functions)")
logger.opt(capture=False).info("Keyword arguments not added to {dest} dict", dest="extra")Loguru با تمام |سطوح لاگینگ|_ استاندارد ارائه می شود که ()trace و ()success به آنها اضافه شده است. آیا بیشتر نیاز دارید؟ کافیه با استفاده از تابع ()level آن را ایجاد کنید.
new_level = logger.level("SNAKY", no=38, color="<yellow>", icon="🐍")
logger.log("SNAKY", "Here we go!")لاگینگ استاندارد با آرگومانهایی مانند datefmt یا msecs، %(asctime)s و %(created)s، زمانهای تاریخ ساده بدون اطلاعات منطقه زمانی، قالببندی بصری و غیره پر شده است. Loguru آن را بر طرف می کند:
logger.add("file.log", format="{time:YYYY-MM-DD at HH:mm:ss} | {level} | {message}")استفاده از لاگر در اسکریپت ها آسان است، و می توانید آن را در ابتدا پیکربندی(()configure) کنید. برای استفاده از Loguru در داخل یک کتابخانه، به یاد داشته باشید که هرگز ()add را فراخوانی نکنید، اما به جای آن از ()disable استفاده کنید تا توابع لاگینگ بدون عملیات باشد. اگر توسعهدهندهای بخواهد لاگ های کتابخانه شما را ببیند، میتواند آن را دوباره ()enable کند.
# For scripts
config = {
"handlers": [
{"sink": sys.stdout, "format": "{time} - {message}"},
{"sink": "file.log", "serialize": True},
],
"extra": {"user": "someone"}
}
logger.configure(**config)
# For libraries
logger.disable("my_library")
logger.info("No matter added sinks, this message is not displayed")
logger.enable("my_library")
logger.info("This message however is propagated to the sinks")آیا می خواهید از Handler داخلی به عنوان سینک Loguru استفاده کنید؟
handler = logging.handlers.SysLogHandler(address=('localhost', 514))
logger.add(handler)آیا نیاز دارید که Loguru را به لاگینگ استاندارد متصل کنید؟
class PropagateHandler(logging.Handler):
def emit(self, record):
logging.getLogger(record.name).handle(record)
logger.add(PropagateHandler(), format="{message}")آیا می خواهید پیام های لاگینگ استاندارد را از سینک های Loguru خود جدا کنید؟
class InterceptHandler(logging.Handler):
def emit(self, record):
# Get corresponding Loguru level if it exists
try:
level = logger.level(record.levelname).name
except ValueError:
level = record.levelno
# Find caller from where originated the logged message
frame, depth = logging.currentframe(), 2
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
depth += 1
logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
logging.basicConfig(handlers=[InterceptHandler()], level=0)قالب پیش فرض لاگر را دوست ندارید؟ آیا رنگ DEBUG دیگری را ترجیح می دهید؟ مشکلی نیست:
# Linux / OSX
export LOGURU_FORMAT="{time} | <lvl>{message}</lvl>"
# Windows
setx LOGURU_DEBUG_COLOR "<green>"استخراج اطلاعات خاص از لاگ های تولید شده اغلب مفید است، به همین دلیل است که Loguru یک روش ()parse ارائه می دهد که به مقابله با گزارش ها و regexes کمک می کند.
pattern = r"(?P<time>.*) - (?P<level>[0-9]+) - (?P<message>.*)" # Regex with named groups
caster_dict = dict(time=dateutil.parser.parse, level=int) # Transform matching groups
for groups in logger.parse("file.log", pattern, cast=caster_dict):
print("Parsed:", groups)
# {"level": 30, "message": "Log example", "time": datetime(2018, 12, 09, 11, 23, 55)}Loguru را میتوان به راحتی با کتابخانه notifiers عالی ترکیب کرد (باید به طور جداگانه نصب شود) تا زمانی که برنامه شما بهطور غیرمنتظرهای از کار میافتد، ایمیل دریافت کند یا بسیاری از انواع دیگر اعلانها را ارسال کند.
import notifiers
params = {
"username": "you@gmail.com",
"password": "abc123",
"to": "dest@gmail.com"
}
# Send a single notification
notifier = notifiers.get_notifier("gmail")
notifier.notify(message="The application is running!", **params)
# Be alerted on each error message
from notifiers.logging import NotificationHandler
handler = NotificationHandler("gmail", defaults=params)
logger.add(handler, level="ERROR")اگرچه تأثیر لاگ بر روی عملکردها در بیشتر موارد ناچیز است، یک لاگر با هزینه صفر اجازه می دهد تا بدون نگرانی زیاد از آن در هر مکانی استفاده کنید. در نسخه آینده، توابع حیاتی Loguru در C برای حداکثر سرعت پیاده سازی خواهند شد.