Skip to main content

Удобный асинхронный логгер

Project description

Polog - удобный асинхронный логгер

Упростите логирование в ваших проектах, выкинув ненужный код. Вот список некоторых преимуществ логгера Polog:

  • Минималистичный синтаксис без визуального мусора. Сделать логирование еще проще уже вряд ли возможно. Вы можете залогировать целый класс всего одним декоратором. Имена функций короткие, насколько это позволяет здравый смысл.
  • Поддержка асинхронности. Декораторы для автоматического логирования работают как на обычных функциях, так и на корутинах.
  • Высокая производительность. Записи делаются из отдельных потоков и ввод-вывод не блокирует основной поток исполнения вашей программы.
  • Автоматическое логирование. Просто повесьте декоратор на вашу функцию или класс, и каждый вызов будет логироваться автоматически (или только ошибки - это легко настроить).
  • Удобное профилирование. Время работы функций записывается. Вы можете накопить статистику производительности вашего кода и легко ее анализировать.
  • Учтено, что может быть несколько сервисов, которые пишут в одно место. Их можно будет различить.
  • Вы можете писать собственные обработчики или пользоваться уже существующими. К примеру, вы можете настроить отправку уведомлений об ошибках по электронной почте или их запись в реляционную БД.

Оглавление

Быстрый старт

Установите Polog через pip:

$ pip install polog

Прежде, чем вызывать логгер, необходимо зарегистрировать обработчик для записей. Для примера это будет простейшая функция, выводящая сообщение в консоль без какого-либо форматирования:

from polog import config


def print_log(function_args, **kwargs):
  print({**kwargs})

config.add_handlers(print_log)

Теперь вы можете импортировать декоратор @flog и применить его к вашей функции:

from polog import flog


@flog
def sum(a, b):
  return a + b

print(sum(2, 2))

В консоли вы увидите информацию о том, какая функция была вызвана, из какого она модуля, с какими аргументами, сколько времени заняла ее работа и какой результат она вернула.

Теперь попробуем залогировать ошибку:

from polog import flog


@flog
def division(a, b):
  return a / b

print(division(2, 0))

Делим число на 0. Что вывелось на этот раз? Очевидно, что результат работы функции выведен не будет, т.к. она не успела ничего вернуть. Зато там появится подробная информация об ошибке: название поднятого исключения, текст его сообщения, трейсбек и даже локальные переменные. Кроме того, появится отметка о неуспешности выполненной операции - они проставляются ко всем автоматическим логам, чтобы их легче было фильтровать.

Еще небольшой пример кода:

from polog import flog


@flog
def division(a, b):
  return a / b

@flog
def operation(a, b):
  return division(a, b)

print(operation(2, 0))

Чего в нем примечательного? В данном случае ошибка происходит в функции division(), а затем, поднимаясь по стеку вызовов, она проходит через функцию operation(). Однако логгер записал сообщение об ошибке только один раз! Встретив исключение в первый раз, он его записывает и подменяет другим, специальным, которое игнорирует в дальнейшем. В результате ваше хранилище логов не засоряется бесконечным дублированием информации об ошибках.

На случай, если ваш код специфически реагирует на конкретные типы исключений и вы не хотите, чтобы логгер исключал дублирование логов таким образом, его поведение можно изменить, об этом вы можете прочитать в более подробной части документации ниже. Однако имейте ввиду, что, возможно, существуют лучшие способы писать код, чем прокидывать исключения через много уровней стека вызовов функций, после чего ловить их там, ожидая конкретный тип.

Что, если мы хотим залогировать все методы целого класса? Обязательно ли проходиться по всем его методам и на каждый вешать декоратор @flog? Нет! Для классов существует декоратор @clog:

from polog import clog


@clog
class OneOperation(object):
  def division(self, a, b):
    return a / b

  def operation(self, a, b):
    return self.division(a, b)

print(OneOperation().operation(2, 0))

Что он делает? Он за вас проходится по методам класса и вешает на каждый из них декоратор @flog. Если вы не хотите логировать ВСЕ методы класса, передайте в @clog имена методов, которые вам нужно залогировать, например: @clog('division').

Если вам все же не хватило автоматического логирования, вы можете писать логи вручную, вызывая функцию log() из своего кода:

from polog import log


log("All right!")
log("It's bad.", exception=ValueError("Example of an exception."))

На этом введение закончено. Если вам интересны тонкости настройки логгера и его более мощные функции, можете почитать более подробную документацию.

Как это все работает?

Процесс выполнения любой программы состоит из событий: вызываются функции, поднимаются исключения и т. д. Эти события, в свою очередь, состоят из других событий, масштабом поменьше. Задача логгера - записать максимально подробный отчет обо всем этом, чтобы программист в случае сбоя мог быстро обнаружить место ошибки и устранить ее. Записать все события до мельчайших деталей невозможно - данных было бы слишком много. Поэтому обычно человек непосредственно указывает места в программном коде, записи из которых его интересуют. На этом принципе построен модуль логирования из стандартной библиотеки.

Обычно вызовы стандартного логгера в коде выглядят как-то так:

import logging


logging.debug('Skip this message!')
logging.info("Sometimes it's interesting.")
logging.warning('This is serious.')
logging.error('PANIC')
logging.critical("I'm quitting.")

В разных ситуациях нам нужно получать разное количество информации из программы: от максимальной подробности на этапе разработки, до редких записей об ошибках и иных важных событиях при реальной эксплуатации. Чтобы не лазить по всему коду и не исправлять / удалять каждый вызов логгера, мы манипулируем лишь общим уровнем логгирования. Необходимый уровень важности записи является фильтром, который устанавливается глобально по всей программе.

В чем отличие Polog от описанной схемы? Больших отличий по принципу действия тут нет. Программа все так же производит события, а мы их записываем или нет, в зависимости от выбранного уровня логирования. Задача библиотеки - по возможности, максимально очистить код ваших функций от визуального мусора, создаваемого вызовами логгеров. Дело в том, что большинство событий, записываемых логгерами, вполне стандартны. Программисту обычно интересно знать, что программа "зашла" в какой-то блок кода, или что в каком-то блоке было поднято исключение. Но если программа написана хорошо и ее логические части разбиты на независимые функции, нам нет нужды залезать в них, чтобы записать все эти вещи. Достаточно обернуть их вызов в декоратор, который сделает все сам.

С появлением декоратора возникают новые вопросы. Если у меня большой класс с кучей методов, мне что, на каждый метод навешивать декоратор? Если навесить его на одну функцию несколько раз, он при каждом вызове будет несколько записей делать? Что, если исключение пройдет через несколько таких задекорированных функций по стеку вызовов - оно много раз запишется? Как быть, если я точно не хочу, чтобы информация из какой-то функции вылезала наружу, например если там важные клиентские данные? Это все работает только на обычных функциях, или на корутинах тоже?

Для решения каждого из этих вопросов у Polog есть свой инструмент, об этом вы можете подробнее прочитать далее.

Помимо интерфейса регистрации событий, Polog немного отличается и по внутреннему устройству. Схема его работы выглядит примерно так:

  • События "ловятся" через декораторы или функции ручного логирования.
  • У каждого события определяется важность. Если важность выше или равна текущему уровню логирования, мы работаем с событием дальше. Если нет, логгер не делает с ним больше ничего.
  • Из события извлекаются данные. Когда оно произошло? В каком месте кода? Было ли исключение? И еще несколько вопросов, ответы на которые записываются в некое промежуточное представление.
  • Промежуточное представление кладется в очередь на запись.
  • В эту очередь смотрят несколько "воркеров" из отдельных потоков. Как только один из них освобождается, он может взять следующий лог из очереди и записать его. Числом потоков с воркерами вы можете управлять, по умолчанию их 2.
  • Получив объект промежуточного представления события, воркер последовательно передает его в каждый из имеющихся у него обработчиков.
  • Обработчики делают с событием кто что умеет: записывают в файл, отправляют по почте, пишут в базу данных, отправляют на внешние сервисы и т. д.

Вы как пользователь библиотеки можете использовать готовые обработчики или писать свои. Кроме того, вы можете гибко настраивать критерии, по которым определяется, записывать событие или нет.

Обратите внимание, что когда событие попадает в очередь, ваша программа уже приступает к дальнейшему выполнению. То есть ввод-вывод логгера практически не влияет на ее производительность, в отличие от большинства прочих решений.

Уровни логирования

Как было сказано выше, по умолчанию автоматические логи имеют 2 уровня: 1 и 2. 1 - это рядовое событие, 2 - исключение. Однако это легко поменять.

В декораторах вы можете указать желаемый уровень логирования:

from polog import flog


@flog(level=5)
def sum(a, b):
  return a + b

print(sum(2, 2))
# Запишется лог с меткой 5 уровня.

Это доступно как для @flog, так и для @clog, работает одинаково.

Также вы можете присвоить уровням логирования имена и в дальнейшем использовать их вместо чисел:

from polog import flog, config


# Присваиваем уровню 5 имя 'ERROR', а уровню 1 - 'ALL'.
config.levels(ERROR=5, ALL=1)

# Используем присвоенное имя вместо номера уровня.
@flog(level='ERROR')
def sum(a, b):
  return a + b

print(sum(2, 2))
# Запишется лог с меткой 5 уровня.

При этом указание уровней числами вам по-прежнему доступно, имена и числа взаимозаменяемы.

Если вы привыкли пользоваться стандартным модулем logging, вы можете присвоить уровням логирования стандартные имена оттуда:

from polog import flog, config


# Имена уровням логирования проставляются автоматически, в соответствии со стандартной схемой.
config.standart_levels()

@flog(level='ERROR')
def sum(a, b):
  return a + b

print(sum(2, 2))
# Запишется лог с меткой 40 уровня.

Также вы можете установить текущий уровень логирования:

from polog import flog, config


# Имена уровням логирования проставляются автоматически, в соответствии со стандартной схемой.
config.standart_levels()

# Устанавливаем текущий уровень логирования - 'CRITICAL'.
config.set(level='CRITICAL')

@flog(level='ERROR')
def sum(a, b):
  return a + b

print(sum(2, 2))
# Запись произведена не будет, т. к. уровень сообщения 'ERROR' ниже текущего уровня логирования 'CRITICAL'.

Все события уровнем ниже игнорируются. По умолчанию уровень равен 1.

Используя декораторы, для ошибок вы можете установить отдельный уровень логирования:

# Работает одинаково в декораторе функций и декораторе классов.
@flog(level='DEBUG', error_level='ERROR')
@clog(level='DEBUG', error_level='ERROR')

Также вы можете установить уровень логирования для ошибок глобально через настройки:

from polog import config


config.set(error_level='CRITICAL')

Сделав это 1 раз, вы можете больше не указывать уровни логирования локально в каждом декораторе. Но иногда вам это может быть полезным. Уровень, указанный в декораторе, обладает более высоким приоритетом, чем глобальный. Поэтому вы можете, к примеру, для какого-то особо важного класса указать более высокий уровень логирования. Или наоборот, понизить его, если не хотите в данный момент записывать логи из конкретной функции или класса.

Общие настройки

Выше уже упоминалось, что общие настройки логирования можно делать через класс config. Давайте вспомним, откуда его нужно импортировать:

from polog import config

Класс config предоставляет несколько методов. Все они работают непосредственно от класса, без вызова __init__, например вот так:

config.set(pool_size=5)

Методы класса config:

  • set(): общие настройки логгера.

    Принимает следующие именованные параметры:

    pool_size (int) - количество потоков-воркеров, по умолчанию равное 2-м. Вы можете увеличить это число, если ваша программа пишет логи достаточно интенсивно. Но помните, что большое число потоков - это большая ответственность дополнительные потоки повышают накладные расходы интерпретатора и могут замедлить вашу программу.

    service_name (str) - имя сервиса. Указывается в каждой записи. По умолчанию 'base'.

    level (int, str) - общий уровень логирования. События уровнем ниже записываться не будут.

    errors_level (int, str) - уровень логирования для ошибок. По умолчанию он равен 2-м.

    original_exceptions (bool) - режим оригинальных исключений. По умолчанию False. True означает, что все исключения остаются как были и никак не видоизменяются логгером. Это может приводить к дублированию информации об одной ошибке в записях, т. к. исключение, поднимаясь по стеку вызовов функций, может пройти через несколько задекорированных логгером функций. В режиме False все исключения логируются 1 раз, после чего оригинальное исключение подменяется на LoggedError, которое не логируется никогда.

    delay_before_exit (int, float) - задержка перед завершением программы для записи оставшихся логов. При завершении работы программы может произойти небольшая пауза, в течение которой будут записаны оставшиеся логи из очереди. Максимальная продолжительность такой паузы указывается в данной настройке.

  • levels(): присвоение имен уровням логирования.

  • standart_levels(): присвоение стандартных имен уровням логирования.

Для правильной работы логгера необходимо, чтобы все настройки были установлены до первого вызова регистратора логов.

Декоратор @flog

Декоратор @flog используется для автоматического логирования вызовов функций. Поддерживает как обычные функции, так и корутинные.

@flog можно использовать как со скобками, так и без. Вызов без скобок эквивалентен вызову со скобками, но без аргументов.

Начнем с импорта:

from polog import flog

Параметр message можно использовать для добавления произвольного текста к каждому логу.

@flog(message='This function is very important!!!')
def very_important_function():
  ...

Про управление уровнями логирования через аргументы к данному декоратору читайте в разделе "уровни логирования".

Если в задекорированной функции возникло необработанное исключение, по умолчанию @flog записывает его, после чего подменяет внутренним исключением LoggedError. В дальнейшем, если логгеру встречается это исключение, оно игнорируется. Отловить его вы можете следующим образом:

from polog import flog, LoggedError


@flog
def error():
  return 4 / 0

try:
  error()
except LoggedError as e:
  # Поймал - выброси.
  pass

Будьте осторожны с этим! Если ваш стиль кодирования подразумевает проброс исключений на несколько уровней и их ловлю с указанием типа, данное поведение следует отключить. Делается это через настройки, вот так:

from polog import config


config.set(original_exceptions=True)

@clog - декоратор класса

Импортируется так:

from polog import clog

Может принимать все те же аргументы, что и @flog, либо использоваться без аргументов - как со скобками, так и без. Автоматически навешивает декоратор @flog на все методы задекорированного класса.

Игнорирует дандер-методы (методы, чьи названия начинаются с "__").

Если не хотите логировать все методы класса, можете перечислить нужные в качестве неименованных аргументов:

@clog('important_method', message='This class is also very important!!!')
class VeryImportantClass:
  def important_method(self):
    ...
  def not_important_method(self):
    ...
  ...

Перекрестное использование @сlog и @flog

При наложении на одну функцию нескольких декораторов логирования, срабатывает из них по итогу только один. Это достигается за счет наличия внутреннего реестра задекорированных функций. При каждом новом декорировании декорируется оригинальная функция, а не ее уже ранее задекорированная версия.

Пример:

@flog(level=6) # Сработает только этот декоратор.
@flog(level=5) #\
@flog(level=4) # |
@flog(level=3) #  > А эти нет. Они знают, что их несколько на одной функции, и уступают место последнему.
@flog(level=2) # |
@flog(level=1) #/
def some_function(): # При каждом вызове этой функции лог будет записан только 1 раз.
  ...

Мы наложили на одну функцию 6 декораторов @flog, однако реально сработает из них только тот, который выше всех. Это удобно в ситуациях, когда вам нужно временно изменить уровень логирования для какой-то функции. Не редактируйте старый декоратор, просто навесьте новый поверх него, и уберите, когда он перестанет быть нужен.

Также вы можете совместно использовать декораторы @сlog и @flog:

@clog(level=3)
class SomeClass:
  @flog(level=10)
  def some_method(self):
    ...

  def also_some_method(self):
    ...
  ...

У @flog приоритет всегда выше, чем у @сlog, поэтому в примере some_method() окажется задекорирован только через @flog, а остальные методы - через @сlog. Используйте это, когда вам нужно залогировать отдельные методы в классе как-то по-особенному.

Запрет логирования через декоратор @logging_is_forbidden

На любую функцию или метод вы можете навесить декоратор @logging_is_forbidden, чтобы быть уверенными, что тут не будут срабатывать декораторы @сlog и @flog. Это удобно, когда вы хотите, к примеру, временно приостановить логирование какой-то функции, не снимая логирующего декоратора.

Импортируется @logging_is_forbidden так:

from polog import logging_is_forbidden

@logging_is_forbidden сработает при любом расположении среди декораторов логирования:

@flog(level=5) # Этот декоратор не сработает.
@flog(level=4) # И этот.
@flog(level=3) # И этот.
@logging_is_forbidden
@flog(level=2) # И вот этот.
@flog(level=1) # И даже этот.
def some_function():
  ...

Также @logging_is_forbidden удобно использовать совместно с @сlog для отдельных методов класса.

@сlog
class VeryImportantClass:
  def important_method(self):
    ...

  @logging_is_forbidden
  def not_important_method(self):
    ...
  ...

Иногда это может быть удобнее, чем прописывать "разрешенные" методы в самом @сlog. Например, когда в вашем классе много методов и строка с их перечислением получилась бы слишком огромной.

Имейте ввиду, что @logging_is_forbidden "узнает" функции по их id. Это значит, что, если вы задекорируете конкретную функцию после того, как она помечена в качестве нелогируемой, декораторы Polog будут относиться к ней как к незнакомой:

@flog(level=2) # Этот декоратор сработает, так как не знает, что some_function() запрещено логировать, поскольку функция, вокруг которой он обернут, имеет другой id.
@other_decorator # Какой-то сторонний декоратор. Из-за него изменится первоначальный id функции some_function() и теперь для декораторов Polog это совершенно новая функция.
@logging_is_forbidden
@flog(level=1) # Этот декоратор не сработает, т.к. сообщается с @logging_is_forbidden.
def some_function():
  ...

Поэтому декораторы Polog лучше всего располагать поверх всех прочих декораторов, которые вы используете.

Редактируем автоматические логи из задекорированных функций

Используя декораторы Polog, иногда вы можете столкнуться с необходимостью добавить или изменить какую-то информацию, которая логируется автоматически. В этом вам поможет функция message().

Пример работы:

from polog import flog, message


@flog(message='original message')
def some_function():
  message('new message')

В полученном логе поле 'message' будет заполнено первым аргументом функции message().

Также вы можете передавать в message() другие именованные аргументы:

  • e или exception (Exception) - экземпляр исключения, которое вы хотите залогировать. Название и сообщение из него будут извлечены автоматически, однако метка success затронута не будет.
  • exception_type и exception_message (str) - название и сообщение исключения, которые вы указываете вручную. Вряд ли вам понадобится это делать вручную, но на всякий случай такая возможность есть. Если заполнен аргумент e или exception, содержимое данных полей игнорируется.
  • success (bool) - метка успешности операции.
  • level (str, int) - уровень лога.
  • local_variables (str) - ожидается json с локальными переменными.

"Ручное" логирование через log()

Отдельные важные события в вашем коде вы можете регистрировать вручную при помощи функции log(). log() отличается от message() главным образом тем, что последняя редактирует лог, записываемый через декоратор, а log() инициирует новую запись лога.

Пример использования:

from polog import log


log('Very important message!!!')

Уровень логирования указывается так же, как в декораторах @flog и @сlog:

# Когда псевдонимы для уровней логирования прописаны по стандартной схеме.
log('Very important message!!!', level='ERROR')
# Ну или просто в виде числа.
log('Very important message!!!', level=40)

Вы можете передать в log() функцию, в которой исполняется код:

def foo():
  log(function=foo)

Колонки function и module в этом случае заполнятся автоматически.

Также вы можете передать в log() экземпляр исключения:

try:
  var = 1 / 0
except ZeroDivisionError as e:
  log('I should probably stop dividing by zero.', exception=e)

Колонки exception_message и exception_type тогда тоже заполнятся автоматически. Флаг success будет установлен в значение False. Трейсбек и локальные переменные той функции, где произошла ошибка, заполнятся автоматически.

При желании, в качестве аргументов function и exception можно использовать и обычные строки, но тогда дополнительные поля не заполнятся сами как надо.

Также вы можете передавать в log() произвольные переменные, которые считаете нужным залогировать. Для этого нужно использовать функцию json_vars(), которая принимает любые аргументы и переводит их в стандартный json-формат:

from polog import log, json_vars


def bar(a, b, c, other=None):
  ...
  log(':D', function=bar, vars=json_vars(a, b, c, other=other))
  ...

Также вы можете автоматически получить все переменные в функции при помощи locals():

def bar(a, b, c, other=None):
  ...
  log(':D', function=bar, vars=json_vars(**locals()))
  ...

Добавляем собственные поля

Существует легкий способ расширить функциональность Polog - добавить в него собственные поля. Поле - это некая именованная сущность, которая извлекается из "сырых" данных при каждом логируемом событии. "Сырые" данные - это все то, что передается в обработчик.

Заполнение поля происходит в 3 этапа:

  1. Извлечение данных. Из аргументов задекорированной функции и извлеченных переменных создается некое промежуточное представление. Этот этап выполняется в основном потоке.
  2. Передача аргумента вместе со всеми прочими для записи в дочерние потоки.
  3. Перед записью происходит получение финального строкового представления, которое используется всеми обработчиками.

Рассмотрим пример дополнительного поля, в которое будет извлекаться ip-адрес клиента из обработчика запроса Django. Сам обработчик запросов выглядит примерно вот так:

from django.http import HttpResponse
import datetime
from polog import flog


@flog
def current_datetime(request):
    now = datetime.datetime.now()
    html = "<html><body>It is now %s.</body></html>" % now
    return HttpResponse(html)

Чтобы ip извлекался из запроса автоматически, необходимо зарегистрировать в Polog extractor - функцию, которая получит на вход "сырые" аргументы функции и уже успевшие извлечься на момент вызова extractor'а прочие аргументы. На выходе extractor должен дать некий объект, который будет помещен в очередь для записи лога. Делается это примерно так:

from polog import config, field


def ip_extractor(args, **kwargs):
  request = args[0][0]
  ip = request.META.get('REMOTE_ADDR')
  return ip

config.add_fields(ip=field(ip_extractor))

Теперь при каждом логировании в обработчик логов будет передаваться среди прочих аргумент под названием "ip", значением которого будет извлеченный из строки запроса ip-адрес. Как видите, в данном extractor'е нет никакой обработки ошибок. Их экранирование происходит в самом Polog. Если случится ошибка при извлечении конкретного поля - оно просто не извлечется, на запись прочих полей это никак не повлияет.

При необходимости, вы можете также указать функцию, ответственную за форматирование извлеченных данных в строку перед непосредственно записью:

def ip_converter(ip):
  return ip.replace('.', '-')

config.add_fields(ip=field(ip_extractor, converter=ip_converter))

Будьте осторожны при использовании изменяемых типов данных из аргументов функции при извлечении дополнительных полей. Вы рискуете зааффектить логгером поведение оригинальных функций, если будете как-то изменять в процессе исходные данные. Кроме того, изменяемые типы данных на момент вашего к ним обращения могут быть уже изменены относительно того состояния, в каком они были при вызове первоначальной функции.

Пишем свои обработчики

Обработчик логов - это некая функция, которую Polog вызывает для каждого логируемого события. Их может быть несколько. Вы можете написать свой обработчик и зарегистрировать его в логгере. К примеру, он может слать логи в вашу любимую NoSQL базу данных, писать их в файловую систему, выводить в консоль, или отправлять вам в мессенджерах / соцсетях.

Первым позиционным аргументом обработчик принимает кортеж из 2 элементов. Если событие зарегистрировано через декоратор, там будут соответственно *args и **kwargs задекорированной функции. Вы можете использовать в своем обработчике какие-то данные оттуда, однако будьте осторожны с изменяемыми типами данных. Кроме того, учитывайте многопоточность Polog: ваш обработчик вызывается не из того же потока, где отработала задекорированная функция. Некоторые объекты от этого будут вести себя иначе. В случае ручного логирования, обоими элементами кортежа будет None.

Помимо одного позиционного аргумента, в обработчик может передаваться разное количество именованных аргументов. Их полный состав определяется по ситуации. Скажем, в случае исключений передается их имя, сообщение и трейсбек. Вот полный список возможных именованных аргументов для обработчика:

  • level (int, обязательный) - уровень важности лога.
  • auto (bool, обязательный) - метка, автоматический лог или ручной. Проставляется автоматически, вы не можете этим управлять.
  • time (datetime.datetime, обязательный) - дата и время начала операции.
  • success (str, обязательный) - метка успешного завершения операции. При автоматическом логировании проставляется в значение True, если в задекорированной функции не произошло исключений. При ручном логировании вы можете проставить метку самостоятельно, либо она заполнится автоматически, если передадите в функцию log() объект исключения (False).
  • service (str, обязательный) - название или идентификатор сервиса, из которого пишутся логи. Идея в том, что несколько разных сервисов могут отправлять логи в какое-то одно место, и вы должны иметь возможность их там различить. Имя сервиса по умолчанию - 'base'. Изменить его вы можете через настройки.
  • function (str, не обязательный) - название функции, действие в которой мы логируем. При автоматическом логировании (которое происходит через декораторы), название функции извлекается из атрибута __name__ объекта функции. При ручном логировании вы можете передать в логгер как сам объект функции, чтобы из нее автоматически извлекся атрибут __name__, так и строку с названием функции. Рекомендуется предпочесть первый вариант, т.к. это снижает вероятность опечаток.
  • module (str, не обязательный) - название модуля, в котором произошло событие. Автоматически извлекается из атрибута __module__ объекта функции.
  • message (str, не обязательный) - произвольный текст, который вы можете приписать к каждой записи.
  • exception_type (str, не обязательный) - тип исключения. Автоматические логи заполняют эту колонку самостоятельно, вручную - вам нужно передать в логгер объект исключения.
  • exception_message (str, не обязательный) - сообщение, с которым вызывается исключение.
  • traceback (str, не обязательный) - json со списком строк трейсбека. При ручном логировании данное поле заполняется автоматически при передаче в функцию log() экземпляра исключения.
  • input_variables (str, не обязательный) - входные аргументы логируемой функции. Автоматически логируются в формате json. Стандартные для json типы данных указываются напрямую, остальные преобразуются в строку. Чтобы вы могли отличить преобразованный в строку объект от собственно строки, к каждой переменной указывается ее оригинальный тип данных из кода python. Для генерации подобных json'ов при ручном логировании рекомендуется использовать функцию json_vars(), куда можно передавать любый аргументы (позиционные и именные) и получать в результате стандартно оформленный json.
  • local_variables (str, не обязательный) - локальные переменные функции. Извлекаются автоматически при логировании через декораторы, либо если вы передадите в функцию log() экземпляр исключения. Также представлены в виде json с указанием типов данных.
  • result (str, не обязательный) - то, что вернула задекорированная логгером функция. Вы не можете заполнить это поле при ручном логировании.
  • time_of_work (float, не обязательный) - время работы задекорированной логгером функции, в секундах. Проставляется автоматически. При ручном логировании вы не можете указать этот параметр.
  • Прочие именованные поля, добавленные вручную. Вы можете дать им любые имена.

Рассмотрим пример простейшего обработчика:

from polog import config, log


def print_function_name(function_args, **kwargs):
  if 'function' in kwargs:
    print(kwargs['function'])
  else:
    print('is unknown!')

# Передаем ваш обработчик в Polog. В метод add_handlers() можно передать несколько функций через запятую.
config.add_handlers(print_function_name)
# В консоли появится сообщение из вашей функции-расширения.
log('hello!')

Включаем оповещения по электронной почте

В составе Polog есть ряд уже готовых обработчиков. Один из них позволяет вам настроить отправку электронных писем по SMTP-протоколу. Вам это может пригодиться для быстрого реагирования на какие-то особо критичные события в вашем коде.

Подключается так:

from polog import config
from polog.handlers.smtp.sender import SMTP_sender


# Адреса и пароль абсолютно случайны.
config.add_handlers(SMTP_sender('from_me42@yandex.com', 'JHjhhb87TY(*Ny08z)', 'smtp.yandex.ru', 'to_me@yandex.ru'))

SMTP_sender - это вызываемый класс. Обязательных аргументов для его инициализации 4: адрес, с которого мы посылаем письма, пароль от ящика, адрес сервера, к которому мы подключаемся, и адрес, куда мы посылаем письма.

Письма, которые будут сыпаться вам на почту, будут выглядеть примерно так:

Message from the Polog:

auto = True
module = __main__
function = do
time = 2020-09-22 20:31:45.712366
exception_message = division by zero
exception_type = ZeroDivisionError
success = False
traceback = [" File \"some_path\", line 46, in wrapper\n result = func(*args, **kwargs)\n"," File \"test.py\", line 23, in do\n return x \/ y\n"]
local_variables = {"x":{"value":1,"type":"int"},"y":{"value":0,"type":"int"}}
time_of_work = 2.86102294921875e-06
level = 2
input_variables = {"args":[{"value":1,"type":"int"},{"value":0,"type":"int"}]}
service_name = base

При необходимости, вы можете настроить отправку писем более тонко. Для этого в конструктор класса нужно передать дополнительные именованные параметры. Вот их список:

  • port (int) - номер порта в почтовом сервере, через который происходит отправка почты. По умолчанию 465 (обычно используется для шифрованного соединения).
  • text_assembler (function) - альтернативная функция для генерации текста сообщений. Должна принимать в себя те же аргументы, которые обычно передаются в пользовательские обработчики Polog, и возвращать строковый объект.
  • subject_assembler (function) - по аналогии с аргументом "text_assembler", альтернативная функция для генерации темы письма.
  • only_errors (bool) - фильтр на отправку писем. В режиме False (то есть по умолчанию) через него проходят все события. В режиме True - только ошибки, т. е., если это не ошибка, письмо гарантированно отправлено не будет.
  • filter (function) - дополнительный фильтр на отправку сообщений. По умолчанию он отсутствует, т. е. отправляются сообщения обо всех событиях, прошедших через фильтр "only_errors". Вы можете передать сюда свою функцию, которая должна принимать стандартный для расширений Polog набор аргументов, и возвращать bool. Возвращенное значение True из данной функции будет означать, что сообщение нужно отправлять, а False - что нет.
  • alt (function) - функция, которая будет вызвана в случае, если отправка сообщения не удалась или запрещена фильтрами. Набор принимаемых аргументов, опять же, стандартный для обработчиков.
  • is_html (bool) - флаг, является ли отправляемое содержимое HTML-документом. По умолчанию False. Влияет на заголовок письма.

Имейте ввиду, что отправка письма - процесс довольно затратный, поэтому имеет смысл это делать только в исключительных ситуациях. Кроме того, если у вас не свой SMTP-сервер, а вы пользуетесь какими-то публичными сервисами, у них часто есть свои ограничения на отправку писем, так что злоупотреблять этим тоже не стоит. В некоторых случаях письма могут просто не отправляться из-за политики используемого вами сервиса.

Кроме того, опять же, из-за затратности процесса отправки, некоторые письма могут не успеть отправиться в случае экстренного завершения программы.

Пишем логи в реляционную базу данных

Для записи логов в реляционную БД необходимо установить дополнительный обработчик через pip:

$ pip install pony_polog_handler

Более подробно о данном обработчике читайте на соответствующей странице.

Общие советы про логирование

Чтобы получить наибольшую пользу от ведения логов, следуйте нескольким небольшим правилам для организации вашего проекта.

  • Заведите для хранения логов отдельную машину. Она может быть одна для нескольких разных проектов или сервисов - главное, чтобы хранение логов физически не могло никак аффектить ваше основное приложение.

  • Держите каждый класс в отдельном файле. Не держите "отдельно стоящих" функций в одном файле с классом. Помимо очевидного, что это делает вашу работу с проектом удобнее, это также устраняет возможность конфликта имен. Polog записывает название функции и модуля. Но если в модуле присутствуют 2 функции с одинаковыми названиями (например, в составе разных классов), вы не сможете их отличить, когда будете читать логи, и можете принять за одну функцию, которая почему-то ведет себя по-разному.

  • Следите за конфиденциальностью данных, которые вы логируете. Скажем, если функция принимает в качестве аргумента пароль пользователя, ее не стоит логировать. Polog предоставляет удобные возможности для экранирования функций от логирования, например декоратор @logging_is_forbidden.

  • Избегайте логирования функций, которые вызываются слишком часто. Обычно это функции с низким уровнем абстракции, лежащие в основе вашего проекта. Выберите уровень абстракции, на котором количество логов становится достаточно комфортным. Помните, что, поскольку запись логов в базу делается в отдельном потоке, то, что вы не чувствуете тормозов от записи логов, не означает, что логирование не ведется слишком интенсивно. Вы можете не замечать, пока Polog пишет по несколько гигабайт логов в минуту.

    Для удобства вы можете разделить граф вызова функций на слои, в зависимости их отдаленности от точки входа при запуске приложения. Каждому из уровней присвоить название, а каждому названию указать уровень логирования, который будет тем меньше, чем дальше соответствующий ему уровень от точки входа. Пока вы тестируете свое приложение, общий уровень логирования можно сделать равным уровню самого дальнего слоя, после чего его можно повысить, оставив логируемыми только 2-3 слоя вокруг точки входа.

    Как пример, если вы пишете веб-приложение, у вас наверняка там будут какие-то классы или функции-обработчики для отдельных URL. Из них наверняка будут вызываться некие функции с бизнес-логикой, а оттуда - функции для работы с базой данных. Запускаете вы приложение в условной функции main(). В данном случае функции main() можно присвоить уровень 4, обработчикам запросов - 3, слою бизнес-логики - 2, ну и слою работы с БД - 1.

  • Избегайте излишнего экранирования ошибок. Когда вы ловите исключения блоками try-except, в логирующие декораторы они могут не попасть. Поэтому полезно взять за правило каждое использование инструкции except сопровождать ручным логированием образовавшегося исключения.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

polog-0.0.9.tar.gz (68.4 kB view hashes)

Uploaded Source

Built Distribution

polog-0.0.9-py3-none-any.whl (47.2 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page