{"id":28542711,"url":"https://github.com/pomponchik/polog","last_synced_at":"2025-07-06T07:31:54.425Z","repository":{"id":39762096,"uuid":"277942389","full_name":"pomponchik/polog","owner":"pomponchik","description":"Логгер нового поколения","archived":false,"fork":false,"pushed_at":"2023-09-24T21:46:57.000Z","size":3255,"stargazers_count":66,"open_issues_count":3,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-06-09T21:07:19.420Z","etag":null,"topics":["async","contextvars","decorators","log","logger","logging","polog","pony","smtp","threading","traceback"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pomponchik.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2020-07-07T23:22:17.000Z","updated_at":"2025-05-28T07:31:25.000Z","dependencies_parsed_at":"2023-09-25T00:45:37.175Z","dependency_job_id":null,"html_url":"https://github.com/pomponchik/polog","commit_stats":{"total_commits":929,"total_committers":11,"mean_commits":84.45454545454545,"dds":0.2314316469321851,"last_synced_commit":"1b798d120302873569d4d78832740f8efd81c004"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/pomponchik/polog","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pomponchik%2Fpolog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pomponchik%2Fpolog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pomponchik%2Fpolog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pomponchik%2Fpolog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pomponchik","download_url":"https://codeload.github.com/pomponchik/polog/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pomponchik%2Fpolog/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263864310,"owners_count":23521770,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","contextvars","decorators","log","logger","logging","polog","pony","smtp","threading","traceback"],"created_at":"2025-06-09T21:07:21.197Z","updated_at":"2025-07-06T07:31:54.411Z","avatar_url":"https://github.com/pomponchik.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Polog: логгер нового поколения\n\n[![Downloads](https://pepy.tech/badge/polog/month)](https://pepy.tech/project/polog)\n[![Downloads](https://pepy.tech/badge/polog)](https://pepy.tech/project/polog)\n[![codecov](https://codecov.io/gh/pomponchik/polog/branch/master/graph/badge.svg)](https://codecov.io/gh/pomponchik/polog)\n[![Test-Package](https://github.com/pomponchik/polog/actions/workflows/coverage.yml/badge.svg)](https://github.com/pomponchik/polog/actions/workflows/coverage.yml)\n[![PyPI version](https://badge.fury.io/py/polog.svg)](https://badge.fury.io/py/polog)\n\nЛогируйте красиво, удобно и без бойлерплейта. Избавьтесь от \"мусора\" в коде. Зацените фичи:\n\n- **Продвинутая функция [log](#log---одна-функция-чтобы-править-всеми)**, которую можно использовать \"[как есть](#ручное-логирование)\", а можно как [контекстный менеджер](#контекстный-менеджер) или даже как декоратор для [функций](#декорируем-функции) (в том числе корутинных) и [классов](#декорируем-классы).\n- [**Просто настраивать**](#общие-настройки), причем любые настройки можно менять \"на лету\" во время работы программы.\n- **Записи об ошибках [не дублируются](#дедупликация-исключений)**, когда исключение проходит через несколько декораторов логирования.\n- Можно **[редактировать](#редактируем-автоматические-логи-из-задекорированных-функций) лог, записываемый декоратором, изнутри той функции, которую он оборачивает**.\n- Можно **запретить логирование** отдельных функций, навесив на них специальный [декоратор](#запрет-логирования-через-декоратор-unlog).\n- Ядро Polog [поддерживает **многопоточность**](#движки-синхронный-и-асинхронный), что иногда может заметно ускорить программу. В этом режиме запись лога становится **неблокирующей операцией**.\n- **Легко модифицировать**. Можно писать [собственные обработчики](#пишем-свой-обработчик) или добавлять собственную логику для [автоматического заполнения](#добавляем-извлекаемые-поля) полей лога.\n- У логгера **нет внешних зависимостей**.\n- Есть **более 580 тестов**, которые обеспечивают покрытие более чем на **96%**. И стремление эти цифры еще увеличить.\n\n\n## Оглавление\n\n- [**Быстрый старт**](#быстрый-старт)\n- [**О фреймворке**](#зачем-это-нужно-о-фреймворке)\n- [**```log()``` - одна функция, чтобы править всеми**](#log---одна-функция-чтобы-править-всеми)\n- [**\"Ручное\" логирование**](#ручное-логирование)\n- [**Контекстный менеджер**](#контекстный-менеджер)\n- [**Декорируем функции**](#декорируем-функции)\n- [**Дедупликация исключений**](#дедупликация-исключений)\n- [**Декорируем классы**](#декорируем-классы)\n- [**Перекрестное декорирование**](#перекрестное-декорирование)\n- [**Запрет логирования через декоратор ```@unlog```**](#запрет-логирования-через-декоратор-unlog)\n- [**Редактируем автоматические логи из задекорированных функций**](#редактируем-автоматические-логи-из-задекорированных-функций)\n- [**Умные ассерты**](#умные-ассерты)\n- [**Интеграция с модулем ```logging```**](#интеграция-с-logging)\n- [**Настройки логгера**](#общие-настройки)\n- [**Уровни логирования**](#уровни-логирования)\n- [**Движки: синхронный и асинхронный**](#движки-синхронный-и-асинхронный)\n- [**Об объекте лога**](#об-объекте-лога)\n- [**Добавляем извлекаемые поля**](#добавляем-извлекаемые-поля)\n- [**Обработчики**](#обработчики)\n- [**Пространства имен и иерархия обработчиков**](#пространства-имен-и-иерархия-обработчиков)\n- [**Вывод в файл или в консоль**](#выводим-логи-в-консоль-или-в-файл)\n- [**Ротация файла с логами**](#ротация-логов)\n- [**Как включить уведомления по электронной почте**](#включаем-оповещения-по-электронной-почте)\n- [**Пишем свой обработчик**](#пишем-свой-обработчик)\n- [**Общие советы про логирование**](#общие-советы-про-логирование)\n\n\n## Быстрый старт\n\nУстановите Polog через [pip](https://pypi.org/project/polog/):\n\n```\n$ pip install polog\n```\n\nПрежде, чем вызывать логгер, зарегистрируйте [обработчик](#обработчики):\n\n```python\nfrom polog import config, file_writer\n\n\nconfig.add_handlers(file_writer())\n```\n\nТеперь наши логи будут выводиться в консоль. При желании, в [```file_writer```](#выводим-логи-в-консоль-или-в-файл) можно передать имя файла и тогда вывод будет происходить туда.\n\nТеперь импортируем объект [```log```](#log---одна-функция-чтобы-править-всеми) и применим его к функции как декоратор:\n\n```python\nfrom polog import log\n\n\n@log\ndef sum(a, b):\n  return a + b\n\nsum(2, 2)\n```\n\nРезультат:\n\n```\n[2022-10-28 16:02:55.988837] |    1    | SUCCESS |  AUTO  | where: __main__.sum() | time of work: 0.00001478 sec. | input variables: 2 (int), 2 (int) | result: 4 (int)\n```\n\nТеперь попробуем залогировать ошибку:\n\n```python\n@log\ndef division(a, b):\n  return a / b\n\ndivision(2, 0)\n```\n\nРезультат:\n\n```\n[2022-10-29 00:23:20.699334] |    2    |  ERROR  |  AUTO  | where: __main__.division() | time of work: 0.00001097 sec. | input variables: 2 (int), 0 (int) | local variables: a = 2 (int), b = 0 (int) | exception: ZeroDivisionError(\"division by zero\") | traceback: return a / b (\".../main.py\", line 9, in division)\n```\n\nЧто, если мы хотим залогировать все методы целого класса? Обязательно ли проходиться ним вручную и на каждый вешать по декоратору? Нет! Классы тоже можно [декорировать](#декорируем-классы):\n\n```python\n@log\nclass OneOperation(object):\n  def division(self, a, b):\n    return a / b\n\n  def operation(self, a, b):\n    return self.division(a, b)\n\nOneOperation().operation(2, 0)\n```\n\nРезультат:\n\n```\n[2022-10-29 00:24:53.348855] |    2    |  ERROR  |  AUTO  | where: __main__.OneOperation.division() | time of work: 0.0000031 sec. | input variables: \u003c__main__.OneOperation object at 0x7fd1805a4100\u003e (OneOperation), 2 (int), 0 (int) | local variables: self = \u003c__main__.OneOperation object at 0x7fd1805a4100\u003e (OneOperation), a = 2 (int), b = 0 (int) | exception: ZeroDivisionError(\"division by zero\") | traceback: return a / b (\".../main.py\", line 10, in division)\n```\n\nЕсли вам все же не хватило автоматического логирования, вы можете писать логи вручную, вызывая ```log()``` [как функцию](#ручное-логирование) из своего кода:\n\n```python\nlog(\"All right!\")\nlog(\"It's bad.\", exception=ValueError(\"Example of an exception.\"))\n```\n\nВ консоли:\n\n```\n[2022-10-28 16:11:27.704617] |    1    | UNKNOWN | MANUAL | \"All right!\" | where: ?\n[2022-10-28 16:11:27.704997] |    2    |  ERROR  | MANUAL | \"It's bad.\" | where: ? | exception: ValueError(\"Example of an exception.\") | no traceback\n```\n\nНаконец, блок кода можно обернуть в логирующий контекстный менеджер:\n\n```python\nwith log(\"It's the context\"):\n  some_function()\n```\n\nРезультат:\n\n```\n[2022-10-28 16:14:21.993514] |    1    | SUCCESS | MANUAL | \"It's the context\" | where: ? | time of work: 0.00001192 sec.\n```\n\nНа этом введение закончено. Если вам интересны тонкости настройки логгера и его более мощные функции, можете почитать более подробную документацию.\n\n\n## Зачем это нужно? О фреймворке\n\nПроцесс выполнения любой программы состоит из событий: вызываются функции, поднимаются исключения и т. д. Эти события, в свою очередь, состоят из других событий, масштабом поменьше. Задача логгера - записать максимально подробный отчет обо всем этом, чтобы программист в случае сбоя мог быстро найти ошибку и устранить ее. Записать все события до мельчайших деталей невозможно - данных было бы слишком много. Поэтому обычно человек непосредственно указывает места в программном коде, записи из которых его интересуют.\n\nОбычно любой логгер, будь то Polog, [logging](https://docs.python.org/3/library/logging.html) или какой-то иной, содержит некий базовый набор компонентов:\n\n1. Прежде всего, регистраторы логов. Чаще всего это какие-то функции, которые вызываются посреди основного кода программы. В Polog регистратор один - функция [log](#log---одна-функция-чтобы-править-всеми), которую можно использовать одновременно и как обычную функцию, так и в виде [декоратора](#декорируем-функции) (в том числе [для классов](#декорируем-классы)) / [контекстного менеджера](#контекстный-менеджер).\n2. Далее, поток событий необходимо отфильтровать. Для этого используются [уровни логирования](#уровни-логирования). На каждое событие ставится метка об уровне его важности (число, к которому может быть привязано отдельное имя), а глобально на уровне программы устанавливается планка, ниже которой события отбрасываются как несущественные.\n3. На следующем шаге все оставшиеся события попадают в один компонент - [движок](#движки-синхронный-и-асинхронный). Задача движка - передать их на обработку специальным обработчикам и сделать немного другой, менее существенной работы.\n4. [Обработчики](#обработчики) что-то делают с полученными сведениями. Типичный обработчик, к примеру, [запишет](#выводим-логи-в-консоль-или-в-файл) информацию о событии в файл.\n\nКак уже сказано, описанная выше схема является типичной для логгеров, и Polog в этом плане вполне обычен. Так чем же он отличается? Давайте сравним его с [logging](https://docs.python.org/3/library/logging.html), вызовы которого обычно выглядят как-то так:\n\n```python\nimport logging\n\n\nlogging.debug('Skip this message!')\nlogging.info(\"Sometimes it's interesting.\")\nlogging.warning('This is serious.')\nlogging.error('PANIC')\nlogging.critical(\"I'm quitting.\")\n```\n\nЗаметили некоторую многословность? Для каждого события вы обязаны указать его уровень, вызвав по имени нужную функцию. Но в реальных программах большинство программистов крайне редко используют больше 2-х уровней: для обычных событий и для ошибок. В Polog вы можете [установить](#общие-настройки) дефолтные уровни логирования для этих двух видов событий, и тогда обычный вызов логгера будет выглядеть уже как-то так:\n\n```python\nlog('A normal event.')\n```\n\nЭто важно не только из соображений красоты. Есть другая причина, экономия ментальных усилий. Вроде кажется не существенным, но знаете, почему большинство людей предпочитает дебажить и логировать через [```print```](https://docs.python.org/3/library/functions.html#print)? Потому что это просто, а им есть, о чем еще подумать, пока они пишут свою программу. Так вот, Polog - это __даже проще__, чем ```print```.\n\nВторая важная особенность Polog: здесь отдается предпочтение логированию __целых блоков кода__, а не отдельных \"точек\" на линии потока событий в программе.\n\nПосмотрим опять на пример из ```logging```:\n\n```python\ndef function(arg_1, arg_2):\n  try:\n    logging.info(f\"begin of operations with {arg_1} and {arg_2}\")\n    operation(arg_1)\n    operation_2(arg_2)\n    logging.info(f\"end of operations with {arg} and {arg_2}\")\n  except Exception as e:\n    logging.error(f\"ERROR of operations with {arg_1} and {arg_2}, {e}\")\n    raise\n```\n\nМы видим здесь функцию, в которой \"настоящей\" логики всего пара строк. Остальное - логирование. Мы пишем логи когда операция начинается, когда кончается, и отдельно - в случае неудачи, для чего приходится еще городить блок try-except. Итого у нас на 2 строчки \"настоящей\" логики приходится еще 6 обеспечивающих логирование. Конечно, это утрированный пример и обычно в реальном коде все не так плохо. Чаще всего в функциях все же немного больше логики, обрамляемой логами, в результате чего соотношение строк \"настоящего\" кода и логирования становится чуть менее драматичным. Однако в целом неудобства все равно налицо:\n\n1. Логирование заставляет писать больше кода, причем шаблонного, в котором легко опечататься. К слову, в примере допущена опечатка, которая ломает логирование, попробуйте ее найти.\n2. Оно отвлекает от \"настоящей\" логики программы. Это происходит потому, что логи перемешаны с \"настоящим\" кодом, пишутся прямо посреди него. По сути с точки зрения логики программы логи являются не более чем визуальным мусором.\n\nЖить стало бы гораздо легче, будь у нас способ вынести логирование куда-то за пределы функции или блока кода, но когда это нужно - иметь возможность что-то подкорректировать и изнутри. Именно такой, гибридный способ логирования, и предоставляет Polog.\n\nС Polog пример кода выше превращается [в](#декорируем-функции):\n\n```python\n@log('Some message.')\ndef function(arg_1, arg_2):\n  operation(arg_1)\n  operation_2(arg_2)\n```\n\nУровень логирования здесь проставится автоматически, в зависимости от того, случится исключение или нет. Внутри функции нет ничего, кроме собственно логики, а все, что связано с логированием - вынесено за ее пределы.\n\nЕще с Polog можно вот [так](#контекстный-менеджер):\n\n```python\ndef function(arg_1, arg_2):\n  with log('Some message.'):\n    operation(arg_1)\n    operation_2(arg_2)\n```\n\nОпять же, логирование вынесено за пределы блока кода, выглядит компактно и не отвлекает от основной логики. Кстати, логирование кода блоками - еще и отличный способ улучшить навык декомпозиции. Если есть что-то, что вы хотели бы логировать отдельно, обычно это является блоком кода, который неплохо бы вынести в отдельную функцию.\n\nТретье важное отличие Polog - это отделение регистрирующей части от обработчиков. В [logging](https://docs.python.org/3/library/logging.html) регистраторы логов представляют из себя дерево, у которого есть свой ```root```. К регистраторам привязаны обработчики, а вы пишете логи в регистраторы. Дерево регистраторов по сути разбросано по разным файлам вашей программы, в результате чего вы вынуждены повсюду создавать экземпляры регистраторов, если хотите иметь возможность специфицировать набор обработчиков для разных участков. В Polog тоже есть [дерево](#пространства-имен-и-иерархия-обработчиков), но это дерево __обработчиков__, а не регистраторов. За счет того, что объект регистратора всего один, появляется возможность установить ```log``` как built-in функцию, которую не нужно даже импортировать (это можно включить через [настройки](#общие-настройки)) в разных файлах программы.\n\nПомимо перечисленного, Polog отличается от других логгеров еще множеством разных деталей, о них читайте ниже.\n\n\n## ```log()``` - одна функция, чтобы править всеми\n\nОдно из главных преимуществ Polog - фреймворк предоставляет несколько способов собирать логи через одну и ту же супер-функцию. Импортируется она так:\n\n```python\nfrom polog import log\n```\n\nДля удобства через [настройки](#общие-настройки) вы можете включить режим, при котором эта функция будет доступна по всей программе без дополнительных импортов, как [```print```](https://docs.python.org/3/library/functions.html#print):\n\n```python\nconfig.set(log_is_built_in=True)\n```\n\n```log``` можно использовать для обычного [ручного](#ручное-логирование) логирования:\n\n```python\nlog('plain text')\n```\n\nИли в качестве [контекстного менеджера](#контекстный-менеджер) / декоратора для [функций](#декорируем-функции) (в том числе корутинных) и [классов](#декорируем-классы):\n\n```python\n                        | @log('plain text')   | @log('plain text')     | @log('plain text')\nwith log('plain text'): | def function():      | async def function():  | class SimpleClass:\n   ...                  |    ...               |    ...                 |    ...\n```\n\nФункция **сама понимает контекст**, в котором она была вызвана! И подстраивает свое поведение соответственно.\n\nВо всех примерах выше сообщение лога передается первым неименованным аргументом. Если вам нечего сказать, можно использовать ```log``` и без скобок:\n\n```python\n                        | @log                 | @log                   | @log\nwith log:               | def function():      | async def function():  | class SimpleClass:\n   ...                  |    ...               |    ...                 |    ...\n```\n\nИмя уровня логирования можно указывать через точку, предварительно [зарегистрировав](#уровни-логирования) его через настройки. Посмотрим, как это работает, предварительно зарегистрировав новое имя:\n\n```python\nconfig.levels(some_level=5)\n```\n\nТеперь это имя можно использовать при любом способе логирования:\n\n```python\n# Обычная функция.\nlog.some_level('plain text')\n\n# Контекстный менеджер.\nwith log.some_level:                   | with log.some_level('plain text'):\n   ...                                 |    ...\n\n# Декоратор функций.\n@log.some_level                        | @log.some_level('plain text')\ndef function():                        | def function():\n   ...                                 |    ...\n\n# Декоратор корутинных функций.\n@log.some_level                        | @log.some_level('plain text')\nasync def function():                  | async def function():\n   ...                                 |    ...\n\n# Декоратор классов.\n@log.some_level                        | @log.some_level('plain text')\nclass SimpleClass:                     | class SimpleClass:\n   ...                                 |    ...\n```\n\nНо можно передать уровень логирования и в качестве аргумента, также будет работать при любом способе сбора логов:\n\n```python\nlog('plain text', level=5) # Будет работать в том числе через декораторы и контекстные менеджеры.\nlog('plain text', level='some_level')\n```\n\nЕсли уровень не указать, по умолчанию для обычных событий будет использоваться тот, что указан в [настройках](#общие-настройки) как ```default_level```, а в случае ошибок - ```default_error_level```.\n\nБолее подробно о специфических особенностях каждого из перечисленных выше способов регистрации логов читайте далее.\n\n\n## \"Ручное\" логирование\n\n\u003e Важно: все приведенные здесь примеры ручного логирования могут работать немного странно, если запускать их через [REPL](https://docs.python.org/3/tutorial/interpreter.html#interactive-mode). Это связано с особенностью реализации: при определении контекста, в котором вызывалась функция ```log```, используется отслеживание жизненного цикла возвращаемого значения функции. Запись происходит при обнулении счетчика ссылок на этот объект. В REPL же последнее возвращенное значение сохраняется в переменной ```_```, то есть счетчик ссылок обнулится и лог запишется, когда код в следующей строке перезапишет ```_```.\n\nМы начнем с самого привычного способа записывать логи, когда сообщение просто передается в функцию [```log```](#log---одна-функция-чтобы-править-всеми):\n\n```python\nlog('Very important message!!!')\n```\n\nСтрока с сообщением заполняет поле ```message``` в получившейся [записи](#об-объекте-лога).\n\n[Уровень логирования](#уровни-логирования) можно передать в качестве аргумента, по имени или числом:\n\n```python\n# Когда псевдонимы для уровней логирования прописаны по стандартной схеме.\nlog('Very important message!!!', level='ERROR')\n# Ну или просто в виде числа.\nlog('Very important message!!!', level=40)\n```\n\nНо есть и другой способ, можно писать названия уровней логирования через точку после ```log```:\n\n```python\nfrom polog import config\n\n\nconfig.levels(lol=100) # Присваиваем уровню 100 имя \"lol\".\n\nlog.lol('kek') # Регистрируем лог уровня \"lol\".\n```\n\nОбратите внимание, в примере выше Polog не знал заранее, что для уровня 100 будет использовано имя ```lol```, резолвинг имен происходит прямо на месте. По сути такой способ записи был бы полностью эквивалентен вот такой записи:\n\n```python\nlog('kek', level='lol')\n```\n\nВы можете передать в ```log()``` функцию, в которой исполняется код:\n\n```python\ndef foo():\n  log(function=foo)\n```\n\nПоля **function** и **module** в этом случае заполнятся автоматически.\n\nТакже вы можете передать в ```log()``` экземпляр исключения (но на самом деле в такой ситуации гораздо удобнее использовать [контекстный менеджер](#контекстный-менеджер)):\n\n```python\ntry:\n  var = 1 / 0\nexcept ZeroDivisionError as e:\n  log('I should probably stop dividing by zero.', exception=e)\n```\n\nПоля **exception_message** и **exception_type** тут тоже заполнятся автоматически. Флаг ```success``` будет установлен в значение False. Трейсбек и локальные переменные той функции, где произошла ошибка, заполнятся автоматически.\n\nПри желании, в качестве аргументов ```function``` и ```exception``` можно использовать и обычные строки, но тогда дополнительные поля не заполнятся сами как надо.\n\nЕще можно передать в логгер название или объект класса в виде аргумента ```class_``` (с нижним подчеркиванием, поскольку слово \"class\" [зарезервировано](https://docs.python.org/3/reference/lexical_analysis.html#keywords)):\n\n```python\nclass Car:\n  def wash(self):\n    log('The washing begins.', class_=type(self))\n    ...\n```\n\nИ наконец, вы можете передавать в ```log()``` произвольные переменные, которые считаете нужным залогировать.\n\n```python\ndef bar(a, b, c, other=None):\n  ...\n  log(':D', function=bar, other=other)\n  ...\n```\n\n\n## Контекстный менеджер\n\nЕще один удобный способ записывать логи - оборачивать блоки кода в контекстные менеджеры. В Polog эта возможность тоже встроена прямо в универсальную функцию [```log()```](#log---одна-функция-чтобы-править-всеми) - достаточно лишь использовать ее соответствующим образом:\n\n```python\nwith log('Go out from the context!'):\n  ...\n```\n\nИли даже так, это эквивалентно, просто записи будут без сообщений:\n\n```python\nwith log:\n  ...\n```\n\nКонтекстный менеджер засекает время работы обернутого им блока кода. Кроме того, он автоматически записывает информацию о произошедших внутри него исключениях.\n\nВ реальных программах очень часто встречаются случаи, когда логгер вызывается внутри блока except (или хуже того, ошибки просто [подавляются](https://en.wikipedia.org/wiki/Error_hiding) без всяких следов, даже в виде логов). С использованием стандартного ```logging``` это обычно выглядит примерно так:\n\n```python\ntry:\n  some_function()\nexcept SomeException:\n  logging.exception('Error in some operation!')\n```\n\nПри использовании контекстного менеджера Polog то же самое займет меньше строчек:\n\n```python\nwith log('Some operation!').suppress(SomeException):\n  some_function()\n```\n\nПродемонстрированный здесь метод ```.suppress()``` работает очень похоже на [```suppress```](https://docs.python.org/3/library/contextlib.html#contextlib.suppress) из стандартной библиотеки Python. Единственное отличие, если сюда не передать никаких аргументов, подавляться будут абсолютно все исключения:\n\n```python\nwith log('Some exception!').suppress():\n  raise ValueError # Подавлено будет абсолютно любое исключение, включая это.\n```\n\nЕсли данный паттерн подавления исключений вам приходится использовать достаточно часто, вы можете включить подавление исключений в контекстных менеджерах логирования по умолчанию, выставив [настройку](#общие-настройки) ```suppress_by_default``` в значение ```True```:\n\n```python\nconfig.set(suppress_by_default=True)\n```\n\nТакже контекстный менеджер позволяет редактировать создаваемую запись лога прямо изнутри контекста:\n\n```python\nwith log('The message before change.') as context:\n  context('The message after change.', some_variable='some text')\n```\n\nВ примере выше мы изменили сообщение лога, а также добавили в него новое поле - ```some_variable```.\n\nПри использовании ```log``` без скобок это тоже сработает:\n\n```python\nwith log as context:\n  context('The message.', some_variable='some text')\n```\n\nИзменение лога внутри контекста доступно и при использовании метода ```.suppress()```:\n\n```python\nwith log('The message before change.').suppress() as context:\n  context('The message after change.', some_variable='some text')\n  raise ValueError\n```\n\n\n## Декорируем функции\n\nОбъект [```log```](#log---одна-функция-чтобы-править-всеми) может быть использован как декоратор для автоматического логирования вызовов функций. Поддерживает как обычные функции, так и [корутинные](https://docs.python.org/3/library/asyncio-task.html). Интересная особенность: декоратор по умолчанию не влияет на трейсбек в случае возникновения иключений внутри функции: он выглядит ровно так, будто декоратора там нет.\n\n```@log``` можно использовать как со скобками, так и без. Вызов без скобок эквивалентен вызову со скобками, но без аргументов. То есть можно так:\n\n```python\n@log\ndef function():\n  ...\n```\n\n... а можно и так, разницы нет:\n\n```python\n@log()\ndef function():\n  ...\n```\n\nКак вы уже могли прочитать разделом выше, к каждой записи можно добавить произвольный текст, передав его первым неименованным аргументом:\n\n```python\n@log('This function is very important!!!')\ndef very_important_function():\n  ...\n```\n\nДальше в документации вы можете подробнее узнать, что такое [обработчики](#обработчики). Пока вкратце: обработчик - это функция, которая фактически производит какое-то финальное действие с логом, например [записывает его в файл](#выводим-логи-в-консоль-или-в-файл). Обработчики можно регистрировать глобально, а можно передать в конкретный декоратор списком:\n\n```python\ndef print_log(log_item): # Пример супер-примитивного обработчика. Не делайте так, придумайте что-то свое.\n  print(log_item)\n\n@log(handlers=[print_log])\ndef function():\n  ...\n```\n\nТакже далее в документации вы можете прочитать, как работают [извлекаемые поля](#добавляем-извлекаемые-поля). Их тоже можно указать как глобально (и тогда они будут срабатывать для всех регистрируемых логов), так и локально для декоратора:\n\n```python\nfrom polog import field\n\n@log(extra_fields={'kek': field(lambda log_item: 'kek')})\ndef function():\n  ...\n```\n\nА если вам позарез нужно, чтобы использовался как глобальный, так и локальный набор полей, возьмите словарь с полями в квадратные скобки и добавьте троеточие в конце, вот так:\n\n```python\n@log(extra_fields=[{'kek': field(lambda log_item: 'kek')}, ...])\ndef function():\n  ...\n```\n\nПо умолчанию данные для дополнительных полей извлекаются \"на месте\", то есть гарантированно в том же потоке, где выполняется основной код. Однако в некоторых случаях вы можете решить вынести извлечение полей внутрь движка, который может быть и [многопоточным](#движки-синхронный-и-асинхронный), для этого замените ```extra_fields``` на ```extra_engine_fields```:\n\n```python\n@log(extra_engine_fields=[{'kek': field(lambda log_item: 'kek')}, ...])\ndef function():\n  ...\n```\n\nНо имейте в виду, нужно это крайне редко.\n\n## Дедупликация исключений\n\nПри использовании для логирования [декораторов](#декорируем-функции), может возникнуть ситуация, когда одно и то же исключение пройдет через разные декораторы несколько раз:\n\n```python\n@log\ndef function_3():\n  raise ValueError\n\n@log\ndef function_2():\n  return function_3()\n\n@log\ndef function_1():\n  return function_2()\n\nfunction_1()\n```\n\nЧтобы избежать заполнения хранилища логов мусором, в Polog предусмотрен режим дедупликации исключений при проходе через декораторы. В этом режиме, когда декоратор Polog встречает исключение в первый раз - оно записывается, а дальше - игнорируется. Этот режим не нужно специально включать, он уже работает по умолчанию. Однако если вам это по каким-то причинам не нравится, можно его наоборот отключить, изменив [настройку](#общие-настройки) ```deduplicate_errors```:\n\n```python\nconfig.set(deduplicate_errors=False)\n```\n\n\n## Декорируем классы\n\nПомимо функций, объектом [```log```](#log---одна-функция-чтобы-править-всеми) вы можете декорировать и целые классы. Все работает точно так же: можно указывать или не указывать все те же аргументы, использовать декоратор как со скобками, так и без.\n\nПри этом игнорируются дандер-методы (это те, чьи названия начинаются и заканчиваются символами \"\\_\\_\").\n\nЕсли не хотите логировать все методы класса, можете передать в декоратор список или кортеж с названиями нужных:\n\n```python\n@log('This class is also very important!!!', methods=('important_method',))\nclass VeryImportantClass:\n  def important_method(self):\n    ...\n  def not_important_method(self):\n    ...\n  ...\n```\n\nНе забывайте, что при наследовании вы получаете класс __вместе с навешенным на его родителя логированием__, и это логирование не знает, что работает уже не в оригинальном классе, а в наследнике. Если на наследника вы тоже навесите ```@log```, логирование родителя у класса-ребенка заменится собственным. Но если вы этого не сделаете, логироваться он будет как родитель.\n\n\n## Перекрестное декорирование\n\nПри наложении на одну функцию нескольких декораторов логирования, срабатывает из них по итогу только один. Это достигается за счет наличия внутреннего реестра задекорированных функций. При каждом новом декорировании оборачивается оригинальная функция, а не ее уже ранее задекорированная версия.\n\nПример:\n\n```python\n@log(level=6) # Сработает только этот декоратор.\n@log(level=5) #\\\n@log(level=4) # |\n@log(level=3) #  \u003e А эти нет. Они знают, что их несколько на одной функции, и уступают место последнему.\n@log(level=2) # |\n@log(level=1) #/\ndef some_function(): # При каждом вызове этой функции лог будет записан только 1 раз.\n  ...\n```\n\nМы наложили на одну функцию 6 [декораторов](#декорируем-функции), однако реально сработает из них только тот, который выше всех. Это удобно в ситуациях, когда вам нужно временно изменить уровень логирования для какой-то функции. Не редактируйте старый декоратор, просто навесьте новый поверх него, и уберите, когда он перестанет быть нужен.\n\nТакже вы можете совмещать декорирование [класса](#декорируем-классы) и его [отдельных методов](#декорируем-функции):\n\n```python\n@log(level=3)\nclass SomeClass:\n  @log(level=10)\n  def some_method(self):\n    ...\n\n  def also_some_method(self):\n    ...\n  ...\n```\n\nУ [декоратора метода](#декорируем-функции) приоритет всегда выше, чем у [декоратора класса](#декорируем-классы), поэтому в примере some_method() окажется задекорирован только через [декоратор метода](#декорируем-функции), а остальные методы - через [декоратор класса](#декорируем-классы). Используйте это, когда вам нужно залогировать отдельные методы в классе как-то по-особенному.\n\n\n## Запрет логирования через декоратор ```@unlog```\n\nНекоторые вещи логировать опасно. К примеру, если вы пишете web-сервис, такими вещами могут быть пароли пользователей или токены для доступа к другим сервисам. Для таких случаев в Polog есть специальный декоратор - ```@unlog```. Он делает так, что на защищенной им функции больше не будут срабатывать [логирующие декораторы](#декорируем-функции). Кроме того, по умолчанию там не будет работать и [ручное логирование](#ручное-логирование). Еще данный декоратор можно навесить на целый класс, и тогда все перечисленное будет работать на всех его методах. Ниже будут примеры.\n\nИмпортируется ```@unlog``` так:\n\n```python\nfrom polog import unlog\n```\n\nНо если вам не хочется в лишний раз импортировать ```@unlog```, можно воспользоваться уже импортированным объектом [```log```](#log---одна-функция-чтобы-править-всеми), подставляя ```-``` перед ним:\n\n```python\n@(-log) # Работает точно так же, как @unlog.\ndef some_function():\n  ...\n```\n\nПростейший случай, \"отмена\" действия логирующего декоратора:\n\n```python\n@unlog\n@log(level=5) # Этот декоратор не сработает.\ndef some_function():\n  ...\n```\n\n\"Отмена\" действия ручного логирования:\n\n```python\n@unlog\ndef some_function():\n  log('hello, kitty!') # Эта функция тоже не будет работать.\n```\n\nНемного более сложный случай. Мы навешиваем ```@unlog``` на функцию, из которой вызывается вторая функция, и вот на той второй функции логирование тоже перестанет работать, когда она вызывается из первой:\n\n```python\n@log(level=5) # Это не сработает.\ndef some_function_2():\n  log('hello, kitty!') # И это тоже.\n\n@unlog\ndef some_function():\n  some_function_2()\n\nsome_function()\n```\n\nДело в том, что как только выполнение кода проходит через декоратор ```@unlog```, логирование выключается во всем \"вложенном\" коде выше по стеку вызовов функций. Это крайне полезная возможность, поскольку она позволяет гарантировать, что какая-то область кода, работающая с чувствительными данными, не только не запишет ничего лишнего в области вашей видимости, но и не сделает этого нигде из тех других функций, которые здесь вызываются (а там может быть довольно много кода, который вам иначе придется просмотреть \"глазками\").\n\nТакже ```@unlog``` можно использовать для методов класса:\n\n```python\n@log\nclass VeryImportantClass:\n  def important_method(self):\n    ...\n\n  @unlog\n  def not_important_method(self):\n    ...\n  ...\n```\n\nИногда это может быть удобнее, чем прописывать \"разрешенные\" методы в самом [декораторе класса](#декорируем-классы). Например, когда в вашем классе много методов и строка с их перечислением получилась бы слишком огромной.\n\nНу и на классы тоже можно навешивать ```@unlog```:\n\n```python\n@unlog\nclass NotImportantClass:\n  def some_method(self):\n    log('some boring message') # Не будет работать.\n```\n\nВыше было сказано, что по умолчанию ```@unlog``` отключает на обернутой им функции как [декораторы логирования](#декорируем-функции), так и [ручные](#ручное-логирование) логи. Но это поведение можно изменить при помощи [настройки](#общие-настройки) ```full_unlog```. По умолчанию она установлена в значение ```True```, однако если изменить ее на ```False```, то ```@unlog``` по-прежнему будет глушить декораторы логирования, но станет пропускать ручные вызовы логгера:\n\n```python\nfrom polog import config\n\n\nconfig.set(full_unlog=False)\n\n@unlog\n@log # Этот декоратор по-прежнему не сработает.\ndef some_function():\n  log('hello, kitty!') # А это сработает!\n\nsome_function()\n```\n\nКроме того, не обязательно изменять данную настройку глобально. ```@unlog``` можно вызвать со скобками, передав туда аргумент ```full```:\n\n```python\n@unlog(full=False)\n@log # Это не будет работать.\ndef some_function():\n  log('hello, kitty!') # А это будет.\n```\n\nЛокальные аргументы имеют приоритет над выставленной глобально настройкой ```full_unlog```.\n\nРасполагая ```@unlog``` среди других декораторов, вам нужно учитывать, что это будет работать при соблюдении хотя бы одного из двух условий:\n\n1. Декоратор ```@unlog``` расположен поверх всех прочих декораторов логирования на данной функции, например вот так:\n\n```python\n@unlog\n@log(level=5) # Этот декоратор не сработает.\n@log(level=4) # И этот.\n@log(level=3) # И этот.\n@log(level=2) # И вот этот.\n@log(level=1) # И даже этот.\ndef some_function():\n  ...\n```\n\n2. Он непосредственно прилегает к одному или нескольким логирующим декораторам, без прослойки в виде каких-то сторонних декораторов (не из Polog):\n\n```python\n@log(level=5) # Этот декоратор не сработает.\n@log(level=4) # И этот.\n@log(level=3) # И этот.\n@unlog\n@log(level=2) # И вот этот.\n@log(level=1) # И даже этот.\ndef some_function():\n  ...\n```\n\nОднако! Если ```@unlog``` находится ниже другого логирующего декоратора и при этом их разделяет какой-то сторонний декоратор, защита может перестать работать:\n\n```python\n@log(level=2) # Этот декоратор может сработать.\n@other_decorator # Какой-то сторонний декоратор.\n@unlog\n@log(level=1) # Этот декоратор не сработает, т.к. сообщается с @unlog.\ndef some_function():\n  ...\n```\n\nЭто связано с особенностями реализации ```@unlog```. Он запоминает, какие функции ранее уже попадались декораторам Polog. Если обернуть функцию сторонним декоратором, для Polog она уже воспринимается как \"новая\", ранее не известная. Поэтому декораторы Polog лучше всего располагать поверх прочих декораторов, которые вы используете. Исключение - регистрирующие декораторы, например роуты во фреймворках вроде Flask. Там синтаксис декораторов используется не для того, чтобы подменить оригинальную функцию, а для регистрации ее где-то. Для корректной работы регистрирующих декораторов, они должны быть размещены поверх всех прочих. То есть иерархия декораторов должны быть по следующей (чем больше номер - тем дальше от определения оригинальной функции): 1. обычные сторонние декораторы, 2. декораторы Polog, 3. регистрирующие декораторы.\n\n\n## Редактируем автоматические логи из задекорированных функций\n\nИспользуя декораторы Polog, иногда вы можете столкнуться с необходимостью добавить или изменить какую-то информацию, которая логируется автоматически. В этом вам поможет функция ```message()```.\n\nПример работы:\n\n```python\nfrom polog import message\n\n\n@log('original message')\ndef some_function():\n  message('new message')\n```\n\nВ полученном логе поле 'message' будет заполнено первым аргументом функции ```message()```.\n\nУ объекта [```log```](#log---одна-функция-чтобы-править-всеми) есть метод, который делает то же самое. Вы можете применять его, чтобы не импортировать ```message()``` отдельно:\n\n```python\n@log('original message')\ndef some_function():\n  log.message('new message')\n```\n\nТакже вы можете передавать в ```message()``` другие именованные аргументы:\n\n- ```e``` или ```exception``` (Exception) - экземпляр исключения, которое вы хотите залогировать. Название и сообщение из него будут извлечены автоматически, однако метка ```success``` затронута не будет.\n- ```success``` (bool) - метка успешности операции.\n- ```level``` (str, int) - [уровень](#уровни-логирования) лога.\n\n\n## Умные ассерты\n\nОдной из популярных техник программирования на Python является [рассеивание инструкций ```assert```](https://dbader.org/blog/python-assert-tutorial#:~:text=Python's%20assert%20statement%20is%20a,with%20an%20optional%20error%20message.) по всему коду. Пока программа запускается в тестовом режиме, это позволяет отловить часть ошибок и неожиданного поведения, а когда дело доходит до \"промышленного\" применения - ассерты просто выключаются при помощи [специального флага](https://docs.python.org/3/using/cmdline.html#cmdoption-O).\n\nВ Polog встроена собственная \"обертка\" над ассертами. На этапе отладки она работает по сути точно так же, как оригинал, а когда ассерты выключены - пишет логи вместо поднятия исключений. Таким образом вы даже в продакшене будете видеть, что в вашем коде пошло не так.\n\nВыглядит это так:\n\n```python\nfrom polog import ass\n\n\nass(False, \"It's bad.\") # Эквивалентно: assert False \"It's bad.\"\n```\n\nЕсли хотите, можно включить режим записи логов даже в режиме дебага. Для этого нужно изменить настройки:\n\n```python\nfrom polog import config\n\n\nconfig.set(smart_assert_politic='all')\n```\n\n## Интеграция с ```logging```\n\nPolog - самобытный фреймворк, местами расходящийся с [```logging```](https://docs.python.org/3/library/logging.html) на концептуальном уровне. Однако, поскольку множество легаси-проектов уже используют ```logging```, взять и начать использовать там Polog может быть не так просто и удобно, как если бы это произошло в самом начале. Кроме того, синтаксис ```logging``` настолько привычен для большинства Python-разработчиков, что даже сторонние библиотеки часто вынуждены ему подражать. Поэтому в Polog существуют средства, позволяющие в некоторой степени \"подружиться\" с ```logging```.\n\nПрежде всего, Polog поддерживает прямой перехват событий из ```logging```, причем это происходит по умолчанию:\n\n```python\nimport logging\nfrom polog import config, file_writer\n\nconfig.add_handlers(file_writer())\n\nlogging.warning('oops!')\n```\n\nПредставленный выше код должен вывести что-то вроде:\n\n```\n[2015-10-21 18:52:12.856087] |   30    |  ERROR  | MANUAL | \"oops!\" | where: main.? | line_number: 6 | path_to_code: \".../main.py\" | thread: \"MainThread (8628192768)\" | process: \"MainProcess (10281)\" | from_logging: True\n```\n\nКак видите, это не потребовало вообще никаких доработок или настроек на стороне ```logging```. Но как это работает? В процессе участвуют две пункта [настроек](#общие-настройки) Polog, которые по умолчанию обе возведены в положение ```True```: ```integration_with_logging``` и ```logging_off```. В них нет никакой магии. Первая в значении ```True``` добавляет фильтр в [```root logger```](https://docs.python.org/3/library/logging.html#logger-objects) библиотеки ```logging```, который перерегистрирует содержимое записи уже внутри Polog. То есть фильтр в данном случае используется по сути как [коллбек](https://en.wikipedia.org/wiki/Callback_(computer_programming)). Вторая - это то значение, которое данный фильтр возвращает: ```True``` или ```False```.\n\nЕсли вы знакомы с устройством ```logging```, вы можете предвидеть, что данный метод перехвата не сработает, если используется не ```root logger```. То есть если, к примеру, он был создан как-то вот [так](https://stackoverflow.com/questions/50714316/how-to-use-logging-getlogger-name-in-multiple-modules):\n\n```python\nlogger = logging.getLogger(__name__)\n```\n\nВ таком случае красиво уже не сделать, придется добавлять каждому логгеру фильтр самостоятельно:\n\n```python\nfrom polog.core.stores.settings.actions import from_logging_filter_to_polog\n\nlogger.addFilter(from_logging_filter_to_polog) # Не нравится, когда тычут в глаза камелкейсом? Переходите на Polog.\n```\n\nЕще один аспект синхронизации ```logging``` и Polog - имена [уровней логирования](#уровни-логирования). Дело в том, что Polog вам никак не предписывает, какие имена вы должны присваивать разным уровням. В ```logging``` же имена уже [придуманы](https://docs.python.org/3.8/library/logging.html#logging-levels) за вас:\n\n| Числовое значение | Имя  |\n|:------------- |:---------------:|\n| 50 | CRITICAL |\n| 40 | ERROR |\n| 30 | WARNING |\n| 20 | INFO |\n| 10 | DEBUG |\n| 0 | NOTSET |\n\nЕсли вы уже привыкли к таким именам, можно легко добавить их в Polog:\n\n```python\nfrom polog import log, config, file_writer\n\nconfig.add_handlers(file_writer())\nconfig.standard_levels()\n\nlog.error('My dog ate my homework :(') # Это сработает так, как вы ожидаете.\n```\n\n\n## Общие настройки\n\nОдно из основных преимуществ Polog - возможность изменить любую глобальную настройку [в любой момент](https://habr.com/ru/company/domclick/blog/589815/). При этом гарантируется, что не будет потеряна ни одна запись лога. Происходит это через класс ```config```, который импортируется вот так:\n\n```python\nfrom polog import config\n```\n\nВсе методы от него вызываются через точку, без вызова \\_\\_init\\_\\_, например вот так:\n\n```python\nconfig.set(pool_size=5)\n```\n\nМетоды класса ```config```:\n\n- **```set()```**: общие настройки логгера.\n\n  Принимает следующие именованные параметры:\n\n    **pool_size** (int) - количество потоков-воркеров. Его можно увеличивать, но при этом стоит помнить, что ~~большое число потоков - это большая ответственность~~ дополнительные потоки повышают накладные расходы интерпретатора и могут замедлить вашу программу. При значении 0 все логи будут обрабатываться [без использования дополнительных потоков](#движки-синхронный-и-асинхронный).\n\n    **max_queue_size** (int) - максимальный размер очереди. Бесконечен в случае значения 0. Данный пункт актуален только в случае не нулевого значения **pool_size**.\n\n    **service_name** (str) - имя сервиса. По умолчанию не задано, но вы можете передать сюда желаемое имя и оно будет отображаться во всех логах. Если вы используете [микросервисную архитектуру](https://en.wikipedia.org/wiki/Microservices), сюда рекомендуется пробрасывать уникальный идентификатор сервиса, к примеру, через переменные окружения.\n\n    **level** (int, str) - общий [уровень логирования](#уровни-логирования). События уровнем ниже записываться не будут.\n\n    **default_level** (int, str) - [уровень логирования](#уровни-логирования), с которым по умолчанию логируются обычные события. По умолчанию он равен 1.\n\n    **default_error_level** (int, str) - [уровень логирования](#уровни-логирования), с которым по умолчанию логируются ошибки. По умолчанию он равен 2-м.\n\n    **max_delay_before_exit** (int, float) - задержка в секундах перед завершением программы для записи оставшихся логов. При завершении работы программы может произойти небольшая пауза, в течение которой будут записаны оставшиеся логи из очереди. Настройка изменяет максимальную продолжительность такой паузы, однако если все логи успели записаться быстрее, лишнего ожидания не произойдет. Также стоит учитывать, что при значении **pool_size**, равном нулю, манипуляция данной настройкой становится бессмысленной, поскольку при этом помещение логов в очередь перед записью не происходит.\n\n    **silent_internal_exceptions** (bool) - \"лояльность\" при неправильных вызовах [ручного логирования](#ручное-логирование). В значении ```True``` при передаче неправильных аргументов или ином некорректном использовании исключения не поднимаются, и по возможности ваши данные все же будут записаны. В значении ```False``` при неправильном использовании лог записываться не будет, а также будет поднято исключение с сообщением об ошибке. При проектировании сервисов с использованием Polog рекомендуется устанавливать данную настройку в значение ```False``` на этапе отладки, и переходить на значение ```True``` при реальной эксплуатации.\n\n    **json_module** (module) - модуль для обработки данных в формате [json](https://en.wikipedia.org/wiki/JSON). Обязательно должен включать 2 функции: ```loads()``` и ```dumps()```. Для ускорения работы рекомендуется передать сюда модуль из библиотеки [ujson](https://pypi.org/project/ujson/).\n\n    **time_quant** (int, float) - продолжительность (в секундах) некоторых внутренних операций в Polog. Изменять не рекомендуется.\n\n    **fields_intersection** (bool) - поведение при пересечении различных [извлекаемых полей](#добавляем-извлекаемые-поля) под одинаковым именем. Если ```fields_intersection``` установлено в значение ```True```, то внутри движка может происходить перезапись полей, извлеченных ранее. В противном случае - нет.\n\n    **unknown_fields_in_handle_logs** (bool) - можно ли передавать при ручном логировании функцией [```log()```](#ручное-логирование) аргументы с неизвестными именами. По умолчанию можно (```True```).\n\n    **deduplicate_errors** (bool) - режим дедупликации автоматических логов об исключениях. По умолчанию - ```True```, то есть включен.\n\n    **log_is_built_in** (bool) - устанавливаем функцию [log()](#log---одна-функция-чтобы-править-всеми) в качестве встроенной. По умолчанию - ```False```, то есть данный режим выключен. Если включить, ```log()``` можно будет вызывать из любого места программы без дополнительного импорта, по аналогии, к примеру, со встроенной функцией [```print()```](https://docs.python.org/3/library/functions.html#print).\n\n    **full_unlog** (bool) - режим, в котором работает декоратор [```@unlog```](#запрет-логирования-через-декоратор-unlog). По умолчанию (```True```) при навешивании данного декоратора любой вид логирования внутри обернутой функции перестает работать, в том числе [ручной](#ручное-логирование). В значении ```False``` перестают работать прилегающие к ```@unlog``` декораторы логирования, однако ручное логирование работать продолжит.\n\n    **suppress_by_default** (bool) - режим подавления исключений в [контекстном менеджере](#контекстный-менеджер). По умолчанию (```False```) контекстный менеджер не влияет на любые поднимающиеся внутри него исключения, он лишь регистрирует их. Однако в режиме ```True``` он подавляет все исключения (чем напоминает [```suppress```](https://docs.python.org/3/library/contextlib.html#contextlib.suppress) из стандартной библиотеки), если только иное поведение не будет установлено для конкретного контекста локально.\n\n    **suppress_exception_subclasses** (bool) - позволяет немного изменить способ подавления исключений в [контекстном менеджере](#контекстный-менеджер). При значении ```True``` (по умолчанию), при передаче в метод ```.suppress()``` одного или нескольких исключений, подавляться будут не только они сами, но и все их подклассы. В режиме ```False``` - только сами переданные классы.\n\n    **integration_with_logging** (bool) - [интеграция](#интеграция-с-logging) с модулем [```logging```](https://docs.python.org/3/library/logging.html) из стандартной библиотеки. При значении ```True``` (по умолчанию) записи логов из ```logging``` копируются в Polog, а при ```False``` - нет. Привести данную настройку в ```False``` можно только при условии, что ```logging_off``` также ```False```.\n\n    **logging_off** (bool) - блокировка вывода всех логов через модуль [```logging```](https://docs.python.org/3/library/logging.html). В значении ```True``` (по умолчанию) все записи в ```logging``` игнорируются, не передаются обработчикам. Этот механизм может работать только при условии, что настройка ```integration_with_logging``` находится также в положении ```True``` (сочетание, при котором ```logging_off``` и ```integration_with_logging``` находятся в положениях, соответственно, ```True``` и ```False```, является невозможным). В значении ```False``` все записи будут игнорироваться в ```logging```, однако за счет того, что настройка ```integration_with_logging``` стоит в положении ```True```, они будут обрабатываться внутри Polog. То есть это работает по сути как чистый перехват всех сообщений. Они не доходят до адресатов (обработчиков) внутри системы ```logging```, но обрабатываются внутри Polog.\n\n    **traceback_cutting** (bool) - обрезание трейсбека при использовании [декоратора для функций](#декорируем-функции). В значении ```True``` (по умолчанию) из трейсбека вырезаются фрагменты, отражающие работу самого декоратора. То есть декоратор не должен как-либо воздействовать на трейсбек. Если отключить данный режим, трейсбек станет более длинным и менее информативным для большинства пользователей, однако более \"честным\".\n\n\n- **```levels()```**: присвоение имен [уровням логирования](#уровни-логирования).\n\n- **```standard_levels()```**: присвоение стандартных имен [уровням логирования](#уровни-логирования).\n\n- **```add_handlers()```**: регистрация новых [обработчиков](#обработчики).\n\n- **```get_handlers()```**: получение [дерева обработчиков](#пространства-имен-и-иерархия-обработчиков).\n\n- **```delete_handlers()```**: удаление [обработчиков](#обработчики).\n\n- **```add_fields()```**: регистрация новых [извлекаемых полей](#добавляем-извлекаемые-поля).\n\n- **```delete_fields()```**: удаление ранее зарегистрированных [извлекаемых полей](#добавляем-извлекаемые-поля). Стандартные поля удалить нельзя.\n\n- **```add_engine_fields()```**: регистрация новых [извлекаемых полей](#добавляем-извлекаемые-поля), обрабатываемых внутри движка.\n\n- **```delete_engine_fields()```**: удаление внутридвижковых [извлекаемых полей](#добавляем-извлекаемые-поля).\n\n- **```get_in_place_fields()```**: получение словаря с [извлекаемыми полями](#добавляем-извлекаемые-поля), которые отрабатывают до попадания лога внутрь движка. Если не передавать в метод ничего, вернет все такие поля, а если передать одно или несколько имен (в качестве неименованных аргументов), то вернет только их.\n\n- **```get_engine_fields()```**: получение словаря с [извлекаемыми полями](#добавляем-извлекаемые-поля), которые отрабатывают внутри движка. По аналогии с ```get_in_place_fields()```, если не передавать в метод ничего, вернет все такие поля, а если передать одно или несколько имен (в качестве неименованных аргументов), то вернет только их.\n\n- **```get_all_fields()```**: получить словарь с [извлекаемыми полями](#добавляем-извлекаемые-поля) всех видов (то есть как отрабатывающих внутри движка, так и до). По аналогии с ```get_in_place_fields()``` и ```get_engine_fields()```, если не передавать в метод ничего, вернет все такие поля, а если передать одно или несколько имен (в качестве неименованных аргументов), то вернет только их. В случае, если два разных экстрактора для извлечения вне движка и внутри него зарегистрированы под одинаковыми именами, данный метод учитывает значение настройки\n\n\n## Уровни логирования\n\nУровни логирования - это универсальный и удобный способ разделить все события на группы, и разбить эти группы на две части: одну мы логируем, а другую - нет. \"Разделителем\" служит глобальный уровень логирования, его мы устанавливаем для всей программы. Каждое событие также имеет пометку об уровне. Если уровень события больше, чем глобальный, или равен ему, оно будет записано, а иначе - проигнорировано. Вы можете не указывать при каждом вызове логгера уровень, которым будут помечаться отслеживаемые им события. В этом случае события будут помечаться уровнями по умолчанию. Для обычных событий уровнем по умолчанию является 1, для ошибок (например, когда в задекорированной логгером функции происходит исключение) - 2. Однако это можно изменить.\n\nВ декораторе вы можете указать уровень, которым будут помечаться все вызовы обернутой им функции:\n\n```python\nfrom polog import log\n\n\n@log(level=5)\ndef sum(a, b):\n  return a + b\n\nprint(sum(2, 2))\n# Запишется лог с меткой 5 уровня.\n```\n\nЭто доступно при декорировании как функций, так и классов, работает одинаково.\n\nВ декораторе вы также можете установить метку уровня, которой будут помечаться только ошибки:\n\n```python\n@log(level=5, error_level=10)\n```\n\nЭто может быть вам полезно, поскольку часто ошибки важнее прочих событий в программе, и вы можете сделать так, чтобы только они проходили через \"фильтр\" общего уровня логирования.\n\nТакже вы можете установить отдельный уровень логирования по умолчанию для ошибок глобально через настройки:\n\n```python\nfrom polog import config\n\n\nconfig.set(default_error_level=50)\n```\n\nПосле этого все ошибки, зарегистрированные декораторами, по умолчанию будут снабжаться соответствующей меткой уровня. Однако в декораторах вы можете продолжать его указывать, поскольку указанный там уровень имеет приоритет над глобальным.\n\n\nДля обычных событий тоже можно установить уровень по умолчанию:\n\n```python\nconfig.set(default_level=10)\n```\n\nУровням логирования можно присвоить имена и в дальнейшем использовать их вместо чисел:\n\n```python\nfrom polog import log, config\n\n\n# Присваиваем уровню 5 имя 'ERROR', а уровню 1 - 'ALL'.\nconfig.levels(ERROR=5, ALL=1)\n\n# Используем присвоенное имя вместо номера уровня.\n@log(level='ERROR')\ndef sum(a, b):\n  return a + b\n\nprint(sum(2, 2))\n# Запишется лог с меткой 5 уровня.\n```\n\nПри этом указание уровней числами вам по-прежнему доступно, имена и числа взаимозаменяемы.\n\nТакже, зарегистрировав имена уровней логирования, вы можете указывать их через точку, причем как при исьзовании объекта ```log``` в качестве декоратора, так и при \"ручном\" логировании:\n\n```python\nfrom polog import log, config\n\n\nconfig.levels(halloween_level=13)\n\n@log.halloween_level\ndef scary_function(a, b):\n  ...\n\nprint(scary_function('friday', 13))\n# Запишется лог 13-го уровня.\nlog.halloween_level('boo!')\n# Также запишется лог 13-го уровня.\n```\n\nЕсли вы привыкли пользоваться стандартным модулем [logging](https://docs.python.org/3.8/library/logging.html), вы можете присвоить уровням логирования [стандартные имена](https://docs.python.org/3.8/library/logging.html#logging-levels) оттуда:\n\n```python\nfrom polog import log, config\n\n\n# Имена уровням логирования проставляются автоматически, в соответствии со стандартной схемой.\nconfig.standard_levels()\n\n@log(level='ERROR')\ndef sum(a, b):\n  return a + b\n\nprint(sum(2, 2))\n# Запишется лог с меткой 40 уровня.\n```\n\nОбщим уровнем логирования вы можете управлять через настройки:\n\n```python\nfrom polog import log, config\n\n\n# Имена уровням логирования проставляются автоматически, в соответствии со стандартной схемой.\nconfig.standard_levels()\n\n# Устанавливаем текущий уровень логирования - 'CRITICAL'.\nconfig.set(level='CRITICAL')\n\n@log(level='ERROR')\ndef sum(a, b):\n  return a + b\n\nprint(sum(2, 2))\n# Запись произведена не будет, т. к. уровень сообщения 'ERROR' ниже текущего уровня логирования 'CRITICAL'.\n```\n\nВсе события уровнем ниже игнорируются.\n\n\n## Движки: синхронный и асинхронный\n\nВсе логи в Polog так или иначе проходят через движок. Задача движка - взять лог и передать его во все нужные обработчики по очереди.\n\nДвижок в Polog является сменяемым компонентом. Вы можете изменить некоторые пункты [настроек](#общие-настройки), которые подразумевают перезагрузку движка. В этом случае движок перезагрузится автоматически, причем в этом случае гарантируется, что не будет потеряна ни одна запись.\n\nПримерами настроек, подразумевающих перезагрузку движка, являются **pool_size** и **max_queue_size**. Чтобы объяснить, почему при их изменении происходит перезагрузка движка, нужно рассказать, какие вообще бывают движки и как в одном из них (асинхронном) задействуются эти параметры.\n\n**Синхронный движок** по своей сути очень примитивен. Это обычный объект, у которого есть метод, в который можно передать лог, и он передаст этот лог во все прикрепленные к нему обработчики. Вызов такого метода является блокирующей операцией, то есть ваша программа будет ждать, пока выполнится код всех обработчиков, и только после этого продолжит свое выполнение. За счет своей простоты такой обработчик порождает наименьший оверхед. Кроме того, в случае, к примеру, внезапного отключения питания компьютера, будет потеряна максимум одна запись лога - та, что на момент отключения была в работе. Какие тут минусы? При использовании обработчиков, которые, к примеру, передают логи куда-то по сети, сетевые задержки могут сильно тормозить вашу программу. Особенно это критично, если логгер используется внутри какого-нибудь нагруженного сервиса. Запросы на такой сервис могут приходить не равномерно, и сетевые операции обработчиков могут сильно снизить допустимую пиковую нагрузку.\n\n**Асинхронный движок** в некоторых ситуациях (но далеко не всегда) может ускорить работу программы в целом. Его устройство уже значительно сложнее, чем у синхронного, и включает в себя несколько компонентов: очередь логов; объект, который кладет в нее логи; и несколько потоков с воркерами. Воркер - это некоторая функция, внутри которой запущен бесконечный цикл ожидания лога из очереди. Как только лог там появляется, кто-то из воркеров первым его забирает и далее с ним работает точно так же, как это делал бы обычный синхронный движок. В случае пиковой нагрузки, при которой воркеры не успевают обрабатывать поступающие логи, очередь растет, как бы \"размазывая\" во времени дополнительную нагрузку на сервер, которую создает сам логгер. Однако за все приходится платить, в данном случае - памятью и вычислительным оверхедом (который еще усугубляется наличием [GIL](https://en.wikipedia.org/wiki/Global_interpreter_lock)). Чтобы выяснить, ускорят асинхронный движок и дополнительные потоки ваше приложение, или замедлят, нет другого способа, кроме нагрузочного тестирования, причем желательно проводить его в как можно более приближенных к реальности условиях. Реальное ускорение / замедление программы сильно зависит от характеристик железа, конкретного набора обработчиков, обрабатываемых данных и, возможно, от других факторов. Кроме того, вам нужно учитывать, что при работе асинхронного движка не гарантируется правильный порядок записи логов. Это связано с тем, что, хоть они и поступают в очередь практически в том же порядке, в каком происходили события вашей программы, время работы разных обработчиков может быть разным.\n\nНастройки **pool_size** и **max_queue_size** влияют на выбор и характеристики движков. Установка **pool_size** в значение 0 (по умолчанию) приведет к загрузке синхронного движка, а любое значение больше 0 - асинхронного с соответствующим количеством потоков с воркерами. Пункт **max_queue_size** - это лимит числа логов в очереди для асинхронного движка. При значении 0 (то есть по умолчанию) лимит полностью отключается, с чем нужно быть осторожнее, поскольку очередь может стать местом утечки памяти в случае хронической нехватки мощности обработчикам. Если установить сюда любое положительное значение, при попытке положить в очередь новый лог, программа заблокируется до момента, пока кто-то из воркеров не заберет один лог из очереди.\n\nПри изменении любой из этих настроек происходит следующее:\n\n1. Движок временно блокируется на запись логов. Функции, которые его вызывают, как бы подвиснут до момента, пока перезагрузка завершится.\n2. Работа движка прекращается. В случае синхронного движка это означает, что возможно, просто записывается последний лог. Для асинхронного все сложнее. У него логи могут быть в двух разных местах. Во-первых, у него может быть не пустая очередь. То есть нужно дождаться, пока она опустеет. Во-вторых, каждый из воркеров может еще продолжать работу с последним из взятых логов. Поэтому, после опустения очереди, программа передает сигналы каждому воркеру, что после отработки текущего лога нужно завершиться. Воркер после обработки каждого лога проверяет, не был ли ему передан такой сигнал, и, если да - разрывает бесконечный цикл, не приступая к ожиданию следующего лога. В случае же, когда воркер на момент получения сигнала не был занят обработкой лога, а ожидал его из очереди, все немного сложнее. Само по себе ожидание не бесконечно. На самом деле ожидание - это смесь бесконечного цикла и блокировки потока. Поток блокируется на некоторый промежуток времени (в настройках он фигурирует как **time_quant**), после чего \"просыпается\" и проверяет, не поступало ли сообщение о завершении. Если да - выходит, нет - снова засыпает в ожидании лога.\n3. Загружается новый движок. Причем решение, какой движок загружать и с какими параметрами, принимается уже на основе новых настроек.\n4. Все функции, которые планировали записать лог, разблокируются и продолжают свою работу, даже не заметив, что движок \"под капотом\" сменился, а логгер, возможно, из синхронного стал асинхронным, или наоборот.\n\nТак работает механизм защиты от потери логов при смене настроек.\n\nСуществует еще один сценарий, при котором происходит что-то похожее - это завершение работы программы. Polog при старте программы регистрирует через [atexit](https://docs.python.org/3/library/atexit.html) специальный обработчик выхода. Внутри него должны выполняться шаги 1 и 2 из перечисленных выше, но с лимитом времени. Этот лимит вы можете корректировать, изменяя настройку **max_delay_before_exit**. Рекомендуем при манипуляции данной настройкой учитывать также **max_queue_size**, иначе возможна ситуация, когда очередь окажется слишком длинной, чтобы успеть \"рассосаться\" за отведенное время.\n\n\n## Об объекте лога\n\nНиже вы можете прочитать о том, что такое [обработчики логов](#обработчики) и как они работают, а также как обогащать логи [извлекаемыми полями](#добавляем-извлекаемые-поля), но прежде, чем мы к этому перейдем, нужно коротко объяснить, что такое вообще отдельный лог с точки зрения фреймворка.\n\nЗадача логгера в принципе - перехватывать поток событий, которые создает программа. На каждое такое событие где-то внутри фреймворка создается специальный объект, в котором заключены извлеченные из события данные. Кроме того, он предоставляет некую базовую логику доступа к этим данным. Этот объект и есть лог. [Движок Polog](#движки-синхронный-и-асинхронный) берет каждый такой объект и передает его последовательно в каждый из привязанных к нему обработчиков.\n\nЭто была общая информация. Конкретно в Polog объект лога по синтаксису очень похож на словарь, с той лишь разницей, что из него можно только читать данные, но нельзя писать.\n\nДанные можно получать по ключу:\n\n```python\n\u003e\u003e\u003e log_item['time']\ndatetime.datetime(2021, 10, 21, 11, 24, 51, 20811)\n\u003e\u003e\u003e log_item.get('time')\ndatetime.datetime(2021, 10, 21, 11, 24, 51, 20811)\n```\n\nНабор полей, записанных для каждого отдельного события, может быть разным. Вот список возможных:\n\n- **level** (int, обязательное) - уровень важности лога.\n- **auto** (bool, обязательное) - метка, автоматический лог или ручной. Проставляется автоматически, вы не можете этим управлять. Автоматическими называются логи, записанные с помощью декораторов.\n- **time** (datetime.datetime, обязательное) - дата и время начала операции.\n- **service_name** (str, не обязательное) - название или идентификатор сервиса, из которого пишутся логи. Идея в том, что несколько разных сервисов могут отправлять логи в какое-то одно место, и вы должны иметь возможность их там различить. По умолчанию имя сервиса не задано, но вы можете это изменить через [настройки](#общие-настройки).\n- **success** (str, не обязательное) - метка успешного завершения операции. При автоматическом логировании проставляется в значение True, если в зад","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpomponchik%2Fpolog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpomponchik%2Fpolog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpomponchik%2Fpolog/lists"}