У стандартній бібліотеці Python є чудовий пакет для логування – logging. У мережі існує думка, що він складний і налаштовувати його суцільний біль. У цій статті я спробую переконати вас у протилежному. Ми розберемо що являє собою цей пакет, вивчимо основні компоненти і закріпимо матеріал практичним прикладом. Логи є рентген знімок виконання вашої програми. Чим детальніше балка, тим простіше розібратися в нестандартних ситуаціях, які можуть статися з вашим скриптом. Найбільш популярним прикладом логів є access логи веб-сервера, наприклад, Apache httpd або nginx. Приклад шматка access лога мого блога: Крім access логів, веб-сервер також пише error лог, там зберігається інформація про всі помилки при обробці HTTP запитів. Також і у ваших скриптах, логи можуть ділитися на інформаційні - виведення поточного стану виконання, налагоджувальну інформацію, і на логи з помилками - висновок винятків, помилок з додатковою інформацією для налагодження, що містить контекст). Точкою входу в роботу з логуванням Python є бібліотека logging.На перший погляд може здатися, що бібліотека складна і заплутана, але витративши деякий час на її вивчення, можна переконатися у протилежному. Для мене logging – це класичний приклад дизайну ОВП, де композиція переважає над спадкуванням, тому у вихідному коді бібліотеки можна зустріти безліч функціональних класів. Мета цього туторіалу розібрати по кісточках кожен клас і об'єднати в єдиний механізм логування в Python. Почнемо-с. Щоб почати роботу з logging необхідно імпортувати бібліотеку logging і викликати функцію getLogger, передавши їй ім'я майбутнього логера. Функція поверне інстанс об'єкта Logger. Логер це важіль за який ми смикаємо щоразу, коли нам потрібно записати інформацію в балку. Зауважте, що функція getLogger приймає на вхід параметр - ім'я логера. Можна призначати будь-яке ім'я або __name__. Виклик getLogger з однаковою назвою поверне той самий інстанс логера. Я рекомендую використовувати як аргумент __name__ , в цьому випадку не потрібно турбуватися, що різні модулі можуть посилатися на той самий логер. Клас Logger надає назовні кілька способів для запису повідомлень різного рівня. Рівні необхідні розуміння контексту повідомлення/лога, що ми пишемо. У logging існує кілька рівнів: За замовчуванням у logging заданий рівень WARNING, це означає, що повідомлення рівня DEBUG і INFOігноруватимуться при записі в балку. Розробник може самостійно задати необхідний йому рівень логування через метод setLevel у інстансу Logger: Методи для запису повідомлень з різними рівнями називаються за тим самим принципом: Також є ще один метод - exception. Його бажано викликати у блоці except при обробці виключення. У цьому випадку він зможе вловити контекст виключення та записати його в лог: Завдання класу Handler та його нащадків обробляти запис повідомлень/логів. Тобто. Handler відповідає за те, куди будуть записані повідомлення. У базовому наборі logging надає ряд готових класів-обробників: Це далеко не повний перелік. Щоб переглянути все, перейдіть за посиланням вище. Для вказівки Handler необхідно в інстанса Logger викликати метод addHandler і передати туди інстанс класу Handler. В одного Logger інстансу може бути безліч обробників. Приклад запису лога в stdout: Якщо запустити цей скрипт, можна побачити повідомлення: Ми бачимо повідомлення рівня DEBUG тому що завдання цей рівень у налаштуваннях. Якщо змінити його на INFO, то повідомлення пропаде: Напевно ви звернули увагу, що ліг містить лише переданий рядок. Як зробити так, щоб у лозі була інформація про рівень лога, час запису? Formatter це ще один клас у сімействі logging, який відповідає за відображення лога. Якщо клас Handler відповідальний за те, куди відбуватиметься запис, клас Formatter відповідає на питання як буде записано повідомлення. За замовчуванням в лог пишеться голий рядок, який ми передаємо через методи debug, info і т.д. Давайте збагатимо наш лог додаткової метаінформації, наприклад, про час запису та рівень повідомлення. Formatter передається інстансу Handler через метод .setFormatter Запуск скрипта виведе на екран наступне повідомлення: Зверніть увагу на рядок, який я передав під час ініціалізації інстансу Formatter : Це шаблон, який буде заповнений під час запису повідомлення в балку. Набір таких готових шаблонів можна переглянути в розділі LogRecord attributes. Завдання класу фільтрувати повідомлення за заданою логікою розробником. Припустимо, що хочу записувати в лог повідомлення, що містять слово python . Щоб задати фільтр, необхідно викликати метод addFilter у інстансу Logger. Передати можна або інстанс класу, що реалізує метод filter або callable об'єкт (наприклад, функцію). На вхід прилетить інстанс класу LogRecord, це і є 1 повідомлення лога: Наочно та зрозуміло, хіба logging може бути складним? 😎 Адаптер необхідний передачі додаткової контекстної інформації при кожному запису лога через Logger. Наприклад, ви написали веб-додаток і вам необхідно в логи додатково передавати username користувача: Рядки в логах це добре, а що якщо я хочу, крім рядка, додатково передавати відповідь від веб-сервера? Для цього можна використовувати аргумент extra при виклику методів debug, info і т.д. Давайте напишемо приклад висновку Тепер висновок значення ключа response можна вказати через Formatter (за умови, що response передається завжди): Аргумент extra зручний при написанні своїх кастомних обробників ліг (наприклад, відсилання ліг в телеграм). Далі я покажу приклад кастомного Handler класу для надсилання логів у Telegram через бота. Офіційна документація рекомендує конфігурувати logging через словник python.Для цього необхідно викликати функцію logging.config.dictConfig та передати їй спеціальний словник. Схему словника описано тут. Я лише коротко пробіжуся за основними ключами: Раніше всі налаштування я задавав код через виклик методів. Це може бути зручно, якщо у вас один модуль, але коли таких модулів стає безліч, то в кожному з них задавати загальні налаштування здається зайвим заняттям. Давайте спробуємо всі налаштування поставити в одному місці: Чи неправда зручно? У реальних програмах налаштування виносять в окремий модуль, який обов'язково імпортується на старті, наприклад, модуль в settings.py як Django. Саме в ньому задаються глобальні налаштування для всіх логерів програми. Ще одним зручним механізмом у logging є "успадкування" налаштувань кореневого логера його нащадками. Спадкування задається через символ. у назві логера.Тобто логер під назвою my_package.logger1 успадкує всі налаштування, задані для my_package . Давайте оновимо приклад вище, додавши в LOGGING_CONFIG налаштування для my_package Якщо у вас є налаштування для конкретного логера і ви не хочете, щоб він був додатково оброблений батьківськими Handler класами, ключу propagate потрібно привласнити значення False . У цьому випадку передачу управління "вгору" до батька буде заборонено. А тепер давайте напишемо свій кастомний Handler для надсилання логів через бота в телеграм. Якщо ви ніколи не працювали з телеграм-ботами, почитайте мою статтю про створення телеграм-ботів. Я припускаю, що ви вже створили бота, отримали його токен і знаєте свій user-id/chat-id, щоб бот зміг надсилати повідомлення особисто вам. Для роботи з телеграмом я використовую бібліотеку pyTelegramBotAPI. Щоб створити свій обробник, необхідно наслідувати клас Handler і перезаписати метод emit : При ініціалізації інстансу класу TelegramBotHandler йому необхідно буде передати токен бота і chat_id. Зазначу, що ці налаштування можна встановити через конфігурування: Щоб обробник почав свою роботу, достатньо в налаштуваннях вашого логера прописати новий обробник: У цій статті я постарався коротко розповісти та показати основні сутності бібліотеки logging, а також продемонструвати гнучкий механізм логування у python. Сподіваюся мені це вдалося, і стаття виявилася для вас корисною. У міру того, як додатки змінюються і ускладнюються, наявність журналу буде корисним при налагодженні і для розуміння проблем, аналізу продуктивності додатків. Стандартна бібліотека логування в Python поставляється модулем logging, який пропонує більшість головних функцій для ведення лога. При правильному налаштуванні повідомлення лога ми отримаємо багато корисної інформації. Про те, коли і де запускається логування, про контекст лог-журналу, наприклад: запущений процес або потік. Незважаючи на переваги, модуль logging часто не береться до уваги, так як для правильного налаштування потрібен деякий час. На мій погляд, повна офіційна документація logging насправді не показує кращих методів ведення журналу і не виділяє деякі сюрпризи ведення журналу. Зауважте, що фрагменти коду в статті припускають, що ви вже імпортували модуль logging: У цьому розділі наведено огляд деяких концепцій, які часто зустрічаються в модулі logging. Рівень лога відповідає його важливості: лог ERROR важливіший, ніж WARNING. Тоді як лог DEBUG слід використовувати лише при налагодженні програми. Форматування лога доповнює повідомлення, додаючи до нього контекстну інформацію. Корисно знати, коли відправляється лог, де (файл Python, номер рядка, метод і т.д.), а також додатковий контекст, такий як потік і процес. Надзвичайно корисні дані при налагодженні багатопотокового додатка. Оброблювач лога — це компонент, який записує та відображає дані лога. Він відображає лог у консолі (через StreamHandler), у файлі (через FileHandler), за допомогою відправки електронного листа (через SMTPHandler) та інших методів. Стандартна бібліотека містить кілька обробників, яких достатньо для більшості випадків: https://docs.python.org/3/library/logging.handlers.html#module-logging.handlers. Найбільш поширені - StreamHandler і FileHandler: Логгер використовуватиметься найчастіше в коді і буде найскладнішим. Новий Logger отримаємо так: У логера три основні поля: Logger визначається по імені, це означає, що якщо створено лог з ім'ям foo , наступні виклики logging.getLogger ("foo") повертатимуть один і той самий об'єкт: Як ви могли здогадатися, у логерів є ієрархія.У верхній частині ієрархії кореневий лог, якого можна отримати доступ через logging.root . Цей лог викликається, коли використовуються методи, як logging.debug() . За замовчуванням рівень кореневого лога — WARNING, тому кожен лог з рівнем нижче ігнорується (наприклад, через logging.info("info") ). Проте логер використовує “точковий запис”, отже логер з ім'ям f.r буде нащадком логгера f. Однак це справедливо тільки в тому випадку, коли створено лог f, інакше батько fr, як і раніше, буде коренем. Коли логгер вирішує, чи лог виводиться відповідно до рівня важливості (наприклад, якщо рівень лога нижче рівня логгера, повідомлення буде проігноровано), він використовує свій “ефективний рівень” замість фактичного рівня. За замовчуванням рівень логера буде використовуватися для рішення про виведення лога. Якщо рівень лога нижче рівня логгера, лог не братиме до уваги. Модуль logging дійсно зручний, але зі своїми особливостями, які обертаються годинником головного болю навіть для досвідчених розробників Python. Ось рекомендації щодо використання цього модуля: Після цього, ви можете створити новий логер і користуватися ним: Логування в Python: Логування Python - це процес запису інформації про виконання програми під час її роботи. Це корисний інструмент для налагодження та відстеження проблем у програмах. Python надає вбудований модуль logging для створення та управління логами. Ось деякі основні концепції та приклади використання цього модуля: У наведеному вище прикладі, ми налаштовуємо логер для запису в файл 'app.log'. Потім ми використовуємо різні рівні логування для запису повідомлень різного типу. відображення лише повідомлень певного рівня або вище. Логи можна використовувати для відстеження виконання програми, виявлення помилок та розуміння того, що відбувається у вашому коді під час виконання. Логування є важливою частиною розробки програмного забезпечення, яка дозволяє записувати та відстежувати інформацію про події та повідомлення, що відбуваються під час виконання програми. , який включає вбудований модуль logging. Цей модуль забезпечує гнучкі засоби для створення та управління логами. додатків. Це створить логгер з ім'ям поточного модуля. Ви також маєте можливість призначити рівень логування, щоб вказати, які повідомлення повинні бути записані.Наприклад, можна задати рівень "DEBUG", щоб записати всі повідомлення, включаючи налагоджувальні: Для запису повідомлень в балку, необхідно використовувати різні методи, що надаються логером. Наприклад, можна використовувати методи debug, info, warning, error і critical. Кожен із цих методів записує повідомлення з певним рівнем у балку. Модуль logging також дозволяє настроювати форматування логів, щоб вони відображалися в зручному для вас вигляді. Для цього можна використати клас Formatter. Модуль logging дозволяє записувати логи у файли, а не лише виводити їх на консоль. Для цього можна використовувати клас FileHandler. Після створення обробника файлу та додавання його до логера, всі повідомлення будуть записуватись у вказаний файл. У цій статті ми розглянули основи логування Python. Логування – це важливий інструмент для налагодження та відстеження роботи вашої програми. Модуль logging надає потужний інструментарій для створення та управління логами у Python.Ми розглянули, як почати використовувати логування, як записувати повідомлення в лог, налаштувати форматування логів і як записувати логи в файли. Сподіваюся, ця стаття допомогла вам зрозуміти основи логування Python і як застосовувати його у своїх проектах.Введення у logging на Python
Для чого потрібні логи?
92.63.107.227 - - [04/Nov/2020:06:30:48 +0000] "GET /ru/hosted-open-vpn-server/ HTTP/1.1" 301 169 "-" "python-requests/2.11.1 " "-" 92.63.107.227 - - [04/Nov/2020:06:30:49 +0000] "GET /ru/data-engineering-course/ HTTP/1.1" 301 169 "-" "python-requests/2.11 .1" "-" 213.180.203.50 - - [04/Nov/2020:06:36:07 +0000] "GET/HTTP/1.1" 301 169 "-" "Mozilla/5.0 (compatible; YandexMetrika/2.0; +http://yandex.com/bots yabs01)" "-" 114.119.160.75 - - [04/Nov/2020:06:36:41 + 0000] "GET /robots.txt HTTP/1.1" 301 169 "-" "(compatible;PetalBot;+https://aspiegel.com/petalbot)" "10.179.80.67" 90.180.35.207 - - [04/ 2020:06:47:11 +0000] "GET/HTTP/1.0" 301 169 "-" "-" "-" 46.246.122.77 - - [04/Nov/2020:06:53:22 +0000] "GET / HTTP/1.1" 301 169 "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Geck Chrome/66.0.3359.181 Safari/537.36" "-" 66.249.76.16 - - [04/Nov/2020:06:53:30 +0000] "GET / HTTP/1.1" 301 169 "-" "Mozilla ; Googlebot/2.1; +http://www.google.com/bot.html)" "-" [04/Nov/2020:07:11:19 +0000] "GET/HTTP/1.1" 301169" -" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, як Gecko) Chrome/49.0.2623.75 Safari/537.36 Google Favicon" "46.159.204.234" 71.6.167.142 - - [04/N0/5 / HTTP/1.1" 301 169 "-" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, як Gecko) Chrome/41.0.2228.0 Safari/537.36" "-"logging та Python
Logger
import logging logger = logging.getLogger('logger')
logger.setLevel(logging.DEBUG)
logger.debug('debug info') logger.info('info') logger.warning('warning') logger.error('debug info') logger.critical('debug info')
try: 1/0 except : logger.exception('exception')Handler
import sys import logging from logging import StreamHandler logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) handler = StreamHandler(stream=sys.stdout) logger.addHandler(handler) logger.debug('debug
logger.setLevel(logging.INFO)
Formatter
import sys import logging from logging import StreamHandler, Formatter logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) handler = StreamHandler(stream=sys.stdout) handler.setFormatter(Formatter(fmt='[%( s: %(levelname)s] %(message)s')) logger.addHandler(handler) logger.debug('debug information')
[2020-11-22 13:00:08,751: DEBUG] debug information
[%(asctime)s: %(levelname)s] %(message)s
Filter
import sys import logging from logging import StreamHandler, Formatter, LogRecord def filter_python(record: LogRecord) -> bool: return record.getMessage().find('python') != -1 logger = logging.getLogger(__name__) logger. setLevel(logging.DEBUG) handler = StreamHandler(stream=sys.stdout) handler.setFormatter(Formatter(fmt='[%(asctime)s: %(levelname)s] %(message)s')) logger.addHandler( handler) logger.addFilter(filter_python) logger.debug('python debug information')
LoggerAdapter
class CustomLoggerAdapter(LoggerAdapter): def process(self, msg, kwargs): return f' from ', kwargs logger2 = logging.getLogger('adapter') logger2.setLevel(logging.DEBUG) handler = StreamHandler(stream ) handler.setFormatter(Formatter(fmt='[%(asctime)s: %(levelname)s] %(message)s')) adapter = CustomLoggerAdapter(logger2, ) logger2.addHandler(handler) adapter.error('failed to save')
extra і не тільки
logger.debug('debug info', extra=)Formatter(fmt='[%(asctime)s: %(levelname)s] %(message)s, response: %(response)s')
Конфігурація logging
import logging.config LOGGING_CONFIG = < 'version': 1, 'disable_existing_loggers': False, 'formatters': < 'default_formatter': < 'format': '[%(levelname)s:%(asctime)s] %(message )s' >, >, 'handlers': < 'stream_handler': < 'class': 'logging.StreamHandler', 'formatter': 'default_formatter', >, >, 'loggers': < 'my_logger': < ' handlers': ['stream_handler'], 'level': 'DEBUG', 'propagate': True >> > logging.config.dictConfig(LOGGING_CONFIG) logger = logging.getLogger('my_logger') logger.debug('debug log')
Спадкування у logging
LOGGING_CONFIG['loggers'].update( < 'my_package': < 'handlers': ['stream_handler'], 'level': 'DEBUG', 'propagate': False >>)
Відправляємо логи в Telegram
import telebot from logging import Handler, LogRecord class TelegramBotHandler(Handler): def __init__(self, token: str, chat_id: str): super().__init__() self.token = token self.chat_id = chat_id def emit(self, record: LogRecord): bot = telebot.TeleBot(self.token) bot.send_message( self.chat_id, self.format(record) )
'handlers': < 'telegram_handler': < 'class': 'handlers.TelegramBotHandler', 'chat_id': '', 'token': '', 'formatter': 'default_formatter', > >,
LOGGING_CONFIG['loggers'].update( < 'my_package': < 'handlers': ['stream_handler', 'telegram_handler'], 'level': 'DEBUG', 'propagate': False >>)Висновок
Цікаві записи:
Основи роботи бібліотеки Python Logging
Концепція Python Logging
Python Logging
Python пропонує шість рівнів лога; кожен рівень пов'язані з числом, що свідчить про важливість лога: NOTSET=0, DEBUG=10, INFO=20, WARNING=30, ERROR=40 і CRITICAL=50.
Ієрархія рівнів інтуїтивно зрозуміла: DEBUG < INFO < WARNING. Крім NOTSET, зі специфікою якого ознайомимося пізніше.Форматування лога у Python
"%(time)s — %(log_name)s — %(level)s — %(func_name)s:%(line_no)d — %(message)s"
2019-01-16 10:35:12,468 - keyboards - ERROR - module>:1 - привіт світ
Обробник ведення логу в Python
У кожному обробнику лога 2 важливі поля:
console_handler = logging.StreamHandler() file_handler = logging.FileHandler("MyLogFile.txt")
Python Logger
toto_logger = logging.getLogger("Privacy")
assert
id(logging.getLogger("foo"))
==
id(logging.getLogger("foo"))
Інша особливість кореневого логгера полягає в тому, що його обробник, за замовчуванням, створюється при першому вході в балку з рівнем вище WARNING.
Використання кореневого логера за допомогою таких методів, як logging.debug() , не рекомендуєтьсяlab = logging.getLogger("f.r")
assert lab.parent == logging.root # lab.parent справді кореневий логер
la = logging.getLogger("f")
assert lab.parent == la # Батько lab тепер la замість кореня
Ефективний рівень логера
Ефективний рівень збігається з рівнем логера, якщо рівень не дорівнює NOTSET. Однак, якщо рівень логера дорівнює NOTSET, ефективним рівнем буде перший рівень батька, який не має NOTSET.
За замовчуванням новий логер має рівень NOTSET. Оскільки кореневий логер з рівнем WARNING, ефективний рівень логеру буде WARNING.
Тому навіть якщо новий логер підключений до деяких обробників, ці обробники не будуть викликатися, якщо рівень лога не перевищить WARNING:foo_logger = logging.getLogger("foo")
assert foo_logger.level == logging.NOTSET # у нового логера рівень NOTSET
assert foo_logger.getEffectiveLevel()
== logging.WARNING # та її ефективний рівень перестав бути рівнем кореневого логгера
# Прикріпіть консольний обробник до foo_logger console_handler = logging.StreamHandler() foo_logger.addHandler(console_handler) foo_logger.debug("debug level")
# нічого не відображається, тому що рівень лога DEBUG менший ніж ефективний рівень foo foo_logger.setLevel(logging.DEBUG) foo_logger.debug("debug message")
# тепер ви побачите на екрані "debug message"
Рекомендації щодо роботи з Python Logging
import logging import sys from logging.handlers import TimedRotatingFileHandler FORMATTER = logging.Formatter("%(time)s - %(name)s - %(level)s - %(message)s") LOG_FILE =
"my_app.log"
def
get_console_handler(): console_handler = logging.StreamHandler(sys.stdout) console_handler.setFormatter(FORMATTER)
return console_handler def
get_file_handler(): file_handler = TimedRotatingFileHandler(LOG_FILE, when='Midnight') file_handler.setFormatter(FORMATTER)
return file_handler def
get_logger(logger_name): logger = logging.getLogger(logger_name) logger.setLevel(logging.DEBUG)
краще мати більше логів, ніж їх брак logger.addHandler(get_console_handler()) logger.addHandler(get_file_handler()) logger.propagate =
False
return logger
my_logger = get_logger("Ім'я модуля наприклад") my_logger.debug("debug повідомлення")
Що таке logging python: повне керівництво з прикладами та порадами
import logging # Налаштування логера logging.basicConfig(filename='app.log', level=logging.DEBUG) # Запис повідомлення в лог logging.debug('Це повідомлення налагодження') logging.info('Це інформаційне повідомлення') logging. warning('Це попереджувальне повідомлення') logging.error('Це повідомлення про помилку') logging.critical('Це критичне повідомлення')Детальна відповідь
Що таке логування у Python?
Як почати використовувати logging у Python?
Для початку використання логування в Python необхідно імпортувати модуль logging:
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
Як записати повідомлення у лог?
Після виконання цього коду кожне повідомлення буде записано в лог із зазначеним рівнем.
logger.debug("Це повідомлення налагодження") logger.info("Це інформаційне повідомлення") logger.warning("Це попереджувальне повідомлення") logger.error("Це повідомлення про помилку") logger.critical("Це критичне повідомлення" )
Як настроїти форматування логів?
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
Тепер кожне повідомлення, що записується в лог, буде відформатовано відповідно до заданого формату.
logger.setFormatter(formatter)
Як записувати логи у файл?
file_handler = logging.FileHandler('app.log') logger.addHandler(file_handler)
Висновок