Python вывести ошибку в файл
Обратите внимание, что параметры logging.basicConfig() должны передаваться до первого вызова функций ведения журнала. Если в консоли интерпретатора уже была вызвана хотя бы одна функция, то необходимо перезагрузить пакет logging или выйти и снова зайти в консоль интерпретатора. С версии Python-3.8 для этой цели можно использовать ключевой аргумент force=True
8.6. User-defined Exceptions¶
Programs may name their own exceptions by creating a new exception class (see Classes for more about Python classes). Exceptions should typically be derived from the Exception class, either directly or indirectly.
Exception classes can be defined which do anything any other class can do, but are usually kept simple, often only offering a number of attributes that allow information about the error to be extracted by handlers for the exception.
Most exceptions are defined with names that end in “Error”, similar to the naming of the standard exceptions.
Many standard modules define their own exceptions to report errors that may occur in functions they define. More information on classes is presented in chapter Classes .
Хороший пример логов
Понимание картины
Контекст
Connecting to AWS
Началась операция с AWS
Атрибуты лога должны позволить мне выяснить, кто его вызвал
Retrieved instances from all regions
Был достигнут существенный прогресс
Connection to AWS has been successful
Операция с AWS завершилась
Атрибуты лога должны позволить мне найти сущности, на которые операция произвела положительный эффект
8.3. Handling Exceptions¶
It is possible to write programs that handle selected exceptions. Look at the following example, which asks the user for input until a valid integer has been entered, but allows the user to interrupt the program (using Control - C or whatever the operating system supports); note that a user-generated interruption is signalled by raising the KeyboardInterrupt exception.
The try statement works as follows.
First, the try clause (the statement(s) between the try and except keywords) is executed.
If no exception occurs, the except clause is skipped and execution of the try statement is finished.
If an exception occurs during execution of the try clause, the rest of the clause is skipped. Then, if its type matches the exception named after the except keyword, the except clause is executed, and then execution continues after the try/except block.
If an exception occurs which does not match the exception named in the except clause, it is passed on to outer try statements; if no handler is found, it is an unhandled exception and execution stops with a message as shown above.
A try statement may have more than one except clause, to specify handlers for different exceptions. At most one handler will be executed. Handlers only handle exceptions that occur in the corresponding try clause, not in other handlers of the same try statement. An except clause may name multiple exceptions as a parenthesized tuple, for example:
A class in an except clause is compatible with an exception if it is the same class or a base class thereof (but not the other way around — an except clause listing a derived class is not compatible with a base class). For example, the following code will print B, C, D in that order:
Note that if the except clauses were reversed (with except B first), it would have printed B, B, B — the first matching except clause is triggered.
All exceptions inherit from BaseException , and so it can be used to serve as a wildcard. Use this with extreme caution, since it is easy to mask a real programming error in this way! It can also be used to print an error message and then re-raise the exception (allowing a caller to handle the exception as well):
Alternatively the last except clause may omit the exception name(s), however the exception value must then be retrieved from sys.exc_info()[1] .
The try … except statement has an optional else clause, which, when present, must follow all except clauses. It is useful for code that must be executed if the try clause does not raise an exception. For example:
The use of the else clause is better than adding additional code to the try clause because it avoids accidentally catching an exception that wasn’t raised by the code being protected by the try … except statement.
When an exception occurs, it may have an associated value, also known as the exception’s argument. The presence and type of the argument depend on the exception type.
The except clause may specify a variable after the exception name. The variable is bound to an exception instance with the arguments stored in instance.args . For convenience, the exception instance defines __str__() so the arguments can be printed directly without having to reference .args . One may also instantiate an exception first before raising it and add any attributes to it as desired.
If an exception has arguments, they are printed as the last part (‘detail’) of the message for unhandled exceptions.
Exception handlers don’t just handle exceptions if they occur immediately in the try clause, but also if they occur inside functions that are called (even indirectly) in the try clause. For example:
Логирование событий в файл.
Очень распространенная ситуация, когда необходимо записывать события в файл.
Если необходимо установить уровень ведения журнала из командной строки, а значение параметра для --log= было получено например в переменную loglevel , то можно использовать функцию getattr для получения значения, которое передается logging.basicConfig() в качестве аргумента level :
При условии, что loglevel привязан к строковому значению, полученному из командной строки, можно проверить любой вводимый пользователем уровень логирования, используя псевдокод из следующего примера:
Введение
Примеры облегчают визуальное восприятие, поэтому мы будем рассматривать следующую систему:
Пользователи могут подключать несколько интеграций к ресурсам (например, GitHub, Slack, AWS и т.д.)
Ресурсы уникальны в зависимости от интеграции (например, репозитории списков с GitHub, диалоги из Slack, экземпляры списков EC2 из AWS и т.д.)
Каждая интеграция уникальна, имеет свой набор сложностей, конечных точек, шагов и т.д.
Каждая интеграция может привести к различным ошибкам, которые могут возникнуть в любое время (например, неверная аутентификация, ресурс не существует и т.д.)
Я не буду сосредотачиваться на проблемах поддержки таких интеграций, просто пронаблюдаем за тем, как это работает.
Примеры использования вывода на печать исключений и трассировки стека:
Пример наглядно демонстрирует различные способы печати исключения и обратной трассировки:
Можно писать программы, которые обрабатывают выбранные исключения. Посмотрите на следующий пример, который запрашивает ввод у пользователя до тех пор, пока не будет введено правильное целое число, но позволяет пользователю прерывать программу, используя Ctrl-C или что-либо поддерживаемое операционной системой. Обратите внимание, что сгенерированное пользователем прерывание сигнализируется возбуждением исключения KeyboardInterrupt .
Оператор try/except работает следующим образом:
Оператор try может содержать несколько инструкций except , чтобы указать обработчики для различных исключений. В этом случае будет выполнен только один обработчик. Обработчики обрабатывают исключения, возникающие только в соответствующей конструкции try , а не в других обработчиках, например вложенных, того же оператора try . Инструкция except может иметь несколько исключений в виде кортежа, заключенного в скобки, например:
Класс в инструкции except совместим с исключением, если это тот же самый класс или его базовый класс, но не наоборот. Инструкция except , перечисляющая производный класс, не совместима с базовым классом. Например, следующий код будет печатать B , C , D в таком порядке:
Обратите внимание, что если бы инструкции except были отменены (с первым исключением B), то вывелось бы B , B , B - срабатывает первое совпадающее предложение except .
В примере выше, сведения об ошибке сохраняются в файл trace.txt с использованием встроенной функции open() . Лучшей практикой сохранения исключений для дальнейшего анализа в подобных ситуациях является применение модуля logging .
Конструкция try/except может содержать необязательную инструкцию else , которая при наличии должна следовать за всеми инструкциями except . Блок кода в инструкции else будет выполнен в том случае, если код в инструкции try не вызывает исключения. Например:
Использование инструкции else лучше, чем добавление дополнительного кода в инструкцию try , поскольку позволяет избежать случайного перехвата исключения, которое не было вызвано кодом, защищенным конструкцией try/except .
Когда возникает исключение, оно может иметь связанное значение, также известное как аргумент исключения. Наличие и тип аргумента зависят от типа исключения.
Инструкция except может указывать переменную после имени исключения. Переменная привязывается к экземпляру исключения с аргументами, хранящимися в экземпляре instance.args . Для удобства, экземпляр исключения определяет __str__() , так что аргументы могут быть напечатаны непосредственно без необходимости ссылаться instance.args . Кроме того, можно создать экземпляр исключения прежде, чем вызвать его и добавить любые атрибуты к нему по желанию.
Обработчики исключений обрабатывают не только исключения возникающие непосредственно в предложении try , но также если они возникают внутри функций, которые вызываются, даже косвенно, в коде оператора try . Например:
Форматируйте логи
Когда я работал в Zak (бывшем Mimic), и даже сегодня в Lumos мы форматировали логи как JSON. Он является хорошим стандартом для систем, работающих на продакшене, поскольку содержит множество атрибутов. Проще визуализировать JSON, чем обычную длинную строку, и для этого вам не нужно создавать свой собственный форматтер (ознакомьтесь с python-json-logger).
Для локальной разработки я рекомендую использовать форматирование по умолчанию для простоты.
Ваше решение будет зависеть от вида проекта. Для Tryceratops я решил использовать обычный форматтер, поскольку он проще и работает локально, там нет нужды в JSON.
Правильно настройте логгер
Еще я замечаю, что люди испытывают трудности при настройке логгера (или вообще его не настраивают). Конечно, документация в Python не очень дружелюбная, но это не оправдание, чтобы вообще ее не трогать.
Есть несколько способов настроить логгер. Вы можете использовать logging.config.dictConfig, logging.config.fileConfig или вообще сделать все вручную, вызывая такие команды как setLevel , AddHandler , addFilter .
Использование ручных команд непросто поддерживать и понимать;
fileConfig – негибкая история, у вас не бывает динамических значений (без дополнительных фокусов);
dictConfig – простая история в запуске и настройке.
Поэтому в качестве примера мы будем придерживаться dictConfig . Еще можно запустить basicConfig, но не думаю, что он вам понадобится, если вы все настроили правильно.
Я поделюсь несколькими советами и определениями, которые вам надо знать, а затем мы создадим окончательную конфигурацию на реальных примерах из проектов, над которыми я работаю.
Вот кусочек того, о чем мы будем говорить дальше:
Пример логов об ошибках
Допустим, что извлечь экземпляры из региона af-south-1 не удалось из-за какой-то внезапной ошибки в этом регионе.
Понимание картины
Контекст
Connecting to AWS
Началась операция с AWS
Атрибуты лога должны позволить мне выяснить, кто его вызвал
Failed to retrieve instances from regions af-south-1 when connecting to AWS for user X
Операция AWS не завершена, произошел сбой в регионе af-south-1, пострадал пользователь X
Я должен иметь возможность увидеть трассировку стека ошибки, чтобы понять, почему извлечение не удалось
В обоих случаях, я могу отследить, когда произошло какое-то событие (в логах есть отметки о времени), что именно произошло и кто от этого пострадал.
Я решил не указывать пользователя при начале и успешном завершении операции, потому что это не имеет значения (ведь это шум), поскольку:
Если я знаю, что что-то запустилось, но не знаю результата выполнения, то что я могу сделать?
Если все хорошо, то зачем беспокоиться?
Добавление таких данных делает логи шумными, потому что на них невозможно реагировать, делать-то с этим ничего не надо! Но я все еще должен быть в состоянии собрать детальную информацию из атрибутов (кто, когда, почему и т.д.). Если вы хотите что-то измерить, вам следует воспользоваться метриками, а не логами.
С другой стороны, логи об ошибках кажутся более подробными, и так и должно быть! Чтение таких логов дает достаточно уверенности, чтобы немедленно перейти к действиям:
Попросите разработчика проверить статус AWS в регионе af-south-1 , и по возможности сделайте sanity check.
Свяжитесь с пользователем Х и сообщите ему, что вам известно о проблеме в этом регионе.
Ключевой момент следующий: вы можете отреагировать сразу и для этого вам не требуется более глубокого изучения ситуации. Вы знаете все, что вам нужно, и можете немедленно принять меры для уменьшения ущерба. Разработчикам, возможно, потребуется углубиться в трассировку стека, чтобы собрать больше контекста (в случае с ошибкой), но общая картина уже становится ясна.
Всегда спрашивайте себя: Что я хочу уяснить для себя, после получения такого лога?
8.4. Raising Exceptions¶
The raise statement allows the programmer to force a specified exception to occur. For example:
The sole argument to raise indicates the exception to be raised. This must be either an exception instance or an exception class (a class that derives from Exception ). If an exception class is passed, it will be implicitly instantiated by calling its constructor with no arguments:
If you need to determine whether an exception was raised but don’t intend to handle it, a simpler form of the raise statement allows you to re-raise the exception:
Логирование исключений с трассировкой стека.
Для того, что бы регистрировать перехваченные исключения с полной трассировкой стека, используйте функцию logging.exception() . Она подходит для кода, который может вызвать ряд исключений, которые например нельзя ожидать. И вместо того, чтобы завершить программу, можно записать информацию об ошибке и продолжить ее работу.
По умолчанию функция logging.exception() использует уровень журнала ERROR . Также для этих целей, можно использовать обычное функции регистрации событий, такие как logging.debug() , logging.info() , logging.warn() и т. д., только устанавливать значение аргумента exc_info=True :
Часто вижу, что помимо обработки исключений, люди мучаются кое с чем еще, а именно с логированием.
Большинство людей не знают, что писать в логи, поэтому решают логировать все, что угодно, думая, что все подряд – это в любом случае лучше, чем ничего, и, в конечном итоге, просто создают шум. А шум – это информация, которая никак не помогает вашей команде понять, в чем дело и как решить проблему.
Более того, я не думаю, что эти люди могут уверенно пользоваться уровнями логирования, поэтому используют по умолчанию logger.info везде (если не пишут print ).
Наконец, люди, похоже, не знают, как сконфигурировать логирование в Python, понятия не имеют, что такое обработчики, фильтры, методы форматирования (форматтеры) и т.д.
Цель этой статьи – разъяснить, что такое логирование и как вы должны его реализовывать. Я постараюсь привести содержательные примеры и обеспечить вас гибкими эмпирическими приемами, которые следует использовать при логировании в любом приложении, которое вы когда-либо будете создавать.
Логирование событий в файл.
Очень распространенная ситуация, когда необходимо записывать события в файл.
Если необходимо установить уровень ведения журнала из командной строки, а значение параметра для --log= было получено например в переменную loglevel , то можно использовать функцию getattr для получения значения, которое передается logging.basicConfig() в качестве аргумента level :
При условии, что loglevel привязан к строковому значению, полученному из командной строки, можно проверить любой вводимый пользователем уровень логирования, используя псевдокод из следующего примера:
Обрабатывайте логи и то, как все связано
Обработчики представляют из себя комбинации форматтеров, выходных данных (потоков) и фильтров.
С ними вы можете создавать следующие комбинации:
Выводить все логи из info (фильтр), а потом выводить JSON в консоль.
Наконец логгеры указывают обработчикам.
Пример logging.dictConfig
Теперь, когда вы понимаете, что делают все эти объекты, давайте писать собственные! Как всегда, я постараюсь показать вам примеры из реальной жизни. Я буду использовать конфигурацию Tryceratops. Вы можете открыть ссылку и посмотреть самостоятельно окончательную конфигурацию.
8.8. Predefined Clean-up Actions¶
Some objects define standard clean-up actions to be undertaken when the object is no longer needed, regardless of whether or not the operation using the object succeeded or failed. Look at the following example, which tries to open a file and print its contents to the screen.
The problem with this code is that it leaves the file open for an indeterminate amount of time after this part of the code has finished executing. This is not an issue in simple scripts, but can be a problem for larger applications. The with statement allows objects like files to be used in a way that ensures they are always cleaned up promptly and correctly.
After the statement is executed, the file f is always closed, even if a problem was encountered while processing the lines. Objects which, like files, provide predefined clean-up actions will indicate this in their documentation.
Шаблон конфигурации логирования
Начнем с такого каркаса, создадим константу LOGGING_CONFIG :
Version всегда будет 1. Это плейсхолдер для возможных следующих релизов. На данный момент версия всего одна.
Я рекомендую оставить значение disable_existing_loggers в False, чтобы ваша система не поглощала другие неожиданные проблемы, которые могут возникнуть. Если вы хотите изменить другие логгеры, я бы порекомендовал их явно переписать (хоть это и скучно).
Внешний ключ root , как вы могли догадаться, определяет логгер верхнего уровня, от которого может наследоваться текущий.
Корневой логгер можно определить тремя разными способами, что сбивает с толку:
Выбирайте любой! Мне нравится оставлять его снаружи, поскольку так он выглядит очевиднее и подробнее говорит о том, чего я хочу, ведь корневой логгер влияет на все другие определенные логгеры.
Что такое логгеры?
Самое интересное, что логгеры образуют иерархию и все наследуются от root -логгера. Дальнейшее наследование определяется « . » (точками), например mymodule.this.that будет наследником mymodule.this .
Из-за этого в документации Python есть рекомендация по использованию logger.getLogger(name) , поскольку name , вернет лишь пространство имен текущего пакета.
В любом случае, придерживайтесь:
Внимание: Вы можете обратиться к корневому логгеру по имени root , пустой строке “” или вообще ни по чему. Да, это сбивает с толку, поэтому используйте root для многословности и ясности.
Содержание:
- traceback.print_tb() -печать трассировки стека,
- traceback.print_exception() - печать исключения и трассировку стека,
- traceback.print_exc() - сокращение для вызова traceback.print_exception() ,
- traceback.print_last() - еще одно сокращение для вызова print_exception() ,
- traceback.print_stack() - печатает записи трассировки, начиная с точки вызова,
- traceback.format_exc() - похожа на traceback.print_exc(limit) но возвращает строку вместо печати, .
traceback.print_tb(tb, limit=None, file=None) :
Функция traceback.print_tb() если аргумент limit положительный, то печатает записи трассировки стека из объекта трассировки tb , ограничивая количество записей значением limit (начиная с кадра вызывающего абонента). В противном случае выводит последние записи abs(limit) .
- Если аргумент limit опущен или отсутствует, то печатаются все записи.
- Если аргумент файла file опущен или отсутствует, то вывод идет в sys.stderr .
- Если аргумент файла file задан, то для получения вывода он должен быть открытым файлом или файлоподобным объектом.
traceback.print_exception(etype, value, tb, limit=None, file=None, chain=True) :
Функция traceback.print_exception() выводит информацию об исключении и записи трассировки стека из объекта трассировки tb в файл.
- Если tb не равно None , то выводится заголовок Traceback (most recent call last): ,
- Функция печатает etype исключения и значение после трассировки стека,
- Если type(value) - это SyntaxError , а значение имеет соответствующий формат, то функция печатает строку, в которой произошла синтаксическая ошибка с символом вставки, указывающим приблизительное положение ошибки.
Необязательный аргумент limit имеет то же значение, что и в функции traceback.print_tb() .
Если аргумент chain=True (по умолчанию), то связанные исключения (атрибуты исключения __cause__ или __context__ ) также будут выведены, как это делает сам интерпретатор при печати необработанного исключения.
traceback.print_exc(limit=None, file=None, chain=True) :
Функция traceback.print_exc() представляет собой сокращенное название для вызова traceback.print_exception() и вызывается с параметрами:
Обратите внимание, что значение аргументов etype , value и tb функции traceback.print_exception() уже подставлены в виде вызова sys.exc_info() .
traceback.print_last(limit=None, file=None, chain=True) :
Функция traceback.print_last() представляет собой сокращенный для вызова traceback.print_exception() и вызывается с параметрами:
Как правило, функция будет работать только после того, как исключение достигнет интерактивной подсказки (подробнее смотрите описание sys.last_type ).
Обратите внимание, что значение аргументов etype , value и tb функции traceback.print_exception() уже подставлены в виде значений sys.last_type , sys.last_value и sys.last_traceback соответственно.
traceback.print_stack(f=None, limit=None, file=None) :
Функция traceback.print_stack() если limit является положительным, то печатает записи трассировки стека, начиная с точки вызова, ограничивая количество значением limit . В противном случае выводит на печать последние записи abs(limit) .
- Если аргумент limit опущен или отсутствует, то печатаются все записи.
- Необязательный аргумент f можно использовать для указания альтернативного кадра стека для запуска.
- Необязательный аргумент file имеет то же значение, что и для функции traceback.print_tb() .
traceback.format_exc(limit=None, chain=True) :
Функция traceback.format_exc() похожа на traceback.print_exc(limit) но возвращает строку вместо печати или сохранения в файл.
8.2. Exceptions¶
The last line of the error message indicates what happened. Exceptions come in different types, and the type is printed as part of the message: the types in the example are ZeroDivisionError , NameError and TypeError . The string printed as the exception type is the name of the built-in exception that occurred. This is true for all built-in exceptions, but need not be true for user-defined exceptions (although it is a useful convention). Standard exception names are built-in identifiers (not reserved keywords).
The rest of the line provides detail based on the type of exception and what caused it.
The preceding part of the error message shows the context where the exception occurred, in the form of a stack traceback. In general it contains a stack traceback listing source lines; however, it will not display lines read from standard input.
Built-in Exceptions lists the built-in exceptions and their meanings.
Логирование событий из нескольких модулей.
Если программа состоит из нескольких модулей, вот пример того, как можно организовать код:
Если запустить файл app.py , то в myapp.log увидим следующее:
Добавление значений переменных в описание события использует форматирование строк в стиле printf . Такое поведение в модуле logging сохранено для обратной совместимости. С версии Python-3.2 класс logging.Formatter() получил ключевой аргумент style='%' , при помощи которого можно установить спецификации форматирования '
Этот запишет в файл example.log следующее:
Для простого использования модуля достаточно использования таких атрибутов как levelname (серьезность), message (описание события, включая данные переменных) и возможно понадобится отображение времени, когда произошло событие.
Чтобы отобразить дату и время события, необходимо поместить атрибут %(asctime)s в строку формата:
Код выше выведет в файл example.log следующее:
Формат отображения даты и времени по умолчанию подобен ISO8601 или RFC 3339. Если нужен больший контроль над форматированием даты и времени, то необходимо использовать аргумент datefmt для класса начальной настройки модуля logging.basicConfig() , как в этом примере:
Теперь в файл example.log можно увидеть запись:
Формат аргумента datefmt такой же, как поддерживается функцией time.strftime() .
Конфигурация логирования: форматтеры
Я дополню пример из Tryceratops примером с JSON из Lumos.
Обратите внимание, что любая конструкция %([name])[type] , как %(message) или %(created) , говорит форматтеру, что и как отображать.
Обратите внимание, что имена, которые мы задаем ( default , simple и JSON ), - произвольные, но актуальные. Вскоре мы к ним обратимся.
Природа логирования: хорошее логирование имеет значение
Для начала давайте проанализируем характеристики логов.
Логи должны быть:
«Наглядными» мы их называем потому, что они предоставляют вам какую-то информацию, «контекстными», потому что они дают вам общее представление о том, как обстоят дела на данный момент времени. И наконец, «реактивными» они являются потому, что они позволяют вам предпринимать действия только после того, как что-то произошло (даже если ваши логи отправляются/получаются в режиме реального времени, на самом деле вы не можете изменить то, что произошло только что).
Если вы не будете учитывать природу логов, то будете производить только шум, что снизит вашу производительность.
Дальше я приведу несколько примеров, основанных на системе, которую мы определили выше:
Если вы зададите описание, к примеру «operation connect failed», но не добавите контекст, трудно будет понять, какая из интеграций не отработала, кто пострадал, на каком этапе подключения произошел сбой, поэтому и среагировать вы не можете. В конечном итоге вы будете копаться в тонне логов без малейшего представления о том, где может быть проблема.
Логи – это конфиденциальная информация из вашего программного обеспечения, нужная чтобы вы оставались в курсе происходящего и могли реагировать на ситуации. Любые логи, которые не дают вам такой информации – это шум.
8.7. Defining Clean-up Actions¶
The try statement has another optional clause which is intended to define clean-up actions that must be executed under all circumstances. For example:
If a finally clause is present, the finally clause will execute as the last task before the try statement completes. The finally clause runs whether or not the try statement produces an exception. The following points discuss more complex cases when an exception occurs:
If an exception occurs during execution of the try clause, the exception may be handled by an except clause. If the exception is not handled by an except clause, the exception is re-raised after the finally clause has been executed.
An exception could occur during execution of an except or else clause. Again, the exception is re-raised after the finally clause has been executed.
If the finally clause executes a break , continue or return statement, exceptions are not re-raised.
If the try statement reaches a break , continue or return statement, the finally clause will execute just prior to the break , continue or return statement’s execution.
If a finally clause includes a return statement, the returned value will be the one from the finally clause’s return statement, not the value from the try clause’s return statement.
A more complicated example:
As you can see, the finally clause is executed in any event. The TypeError raised by dividing two strings is not handled by the except clause and therefore re-raised after the finally clause has been executed.
In real world applications, the finally clause is useful for releasing external resources (such as files or network connections), regardless of whether the use of the resource was successful.
Предоставление контекста с помощью Python
В Python атрибуты логов можно добавить с помощью дополнительного поля, например:
Нечто большее, чем logger.info и logger.error
Не так-то просто понять, что происходит, когда тысячи клиентов выдают логи «Connecting to Slack». Поскольку вы выдаете логи, а вашим приложением пользуются несколько клиентов, нужно иметь возможность фильтровать информацию по релевантности.
В логах бывает много уровней (т.е. уровней релевантности). В Python у вас есть DEBUG , INFO , WARNING , ERROR , CRITICAL . Я рекомендую использовать их все.
Чтобы упростить ситуацию, вот вам новое эмпирическое правило (будьте гибкими):
Уровень
Когда используется
Для какой-то действительно повторяющейся информации. Возможно, было бы полезно выдавать весь контекст информации, но порой этого не нужно.
Когда происходит что-то важное, достойное того, чтобы о нем было известно большую часть времени.
Случилось что-то странное (но не прервало поток/операцию). Если проблема возникнет на более поздних этапах, такой лог может дать вам подсказку.
Произошла ошибка, ее следует устранить как можно быстрее.
Произошла очень серьезная ошибка, она требует немедленного вмешательства. Если не уверены в критичности ошибки, применяйте ERROR .
Для описанной системы/потоков, я бы использовал уровни логов, как определено:
Что делать с logger.critical и logger.warning ?
Для примера хочу осветить случаи, когда я бы рассмотрел возможность использования WARNING и CRITICAL .
WARNING – недостаточно веская причина для остановки потока, однако это предупреждение на будущее, если возникнет какая-то проблема.
CRITICAL – самый тревожный предупреждающий лог, который вы когда-либо получите. По сути, он должен быть той самой причиной встать в три часа ночи и пойти что-то чинить.
Для этих случаев мы рассмотрим:
Для AWS: если какой-то регион AWS недоступен, мы можем предположить, что у пользователя там нет никаких ресурсов, поэтому можно двигаться дальше.
Для Slack: если OAuth завершится неудачно из-за невалидного id клиента, значит остальные пользователи столкнутся с той же проблемой, интеграция не отработает, пока мы вручную не сгенерируем новый id. Это дело кажется достаточно критичным.
Непопулярное мнение: использование DEBUG -уровня на продакшене
Да, я считаю, что логи DEBUG нужно использовать на продакшене.
Другой вариант – включить дебаг после того, как странная ошибка потребует более детального разбирательства.
Простите, но для меня такой вариант недопустим.
В реальном мире клиентам нужна быстрая реакция, командам нужно выполнять свою работу и постоянно поддерживать системы в рабочем состоянии. У меня нет времени и пропускной способности для нового деплоя или включения флага и ожидания повторения проблемы. Я должен реагировать на неожиданные проблемы в считанные секунды, а не минуты.
8.1. Syntax Errors¶
Syntax errors, also known as parsing errors, are perhaps the most common kind of complaint you get while you are still learning Python:
The parser repeats the offending line and displays a little ‘arrow’ pointing at the earliest point in the line where the error was detected. The error is caused by (or at least detected at) the token preceding the arrow: in the example, the error is detected at the function print() , since a colon ( ':' ) is missing before it. File name and line number are printed so you know where to look in case the input came from a script.
Фильтруйте логи
Фильтры можно использовать либо для фильтрации логов (внезапно), либо для добавления дополнительного контекста в запись лога. Рассматривайте фильтры, как хуки, вызываемые до обработки итогового лога.
Их можно определить следующим образом:
Или их можно добавить прямо в логгер или обработчик для упрощения фильтрации по уровням (скоро будут примеры).
Что логировать?
Чтобы логи были наглядными и контекстными, нужно предоставлять правильный набор информации, и я не могу сказать, какая информация будет являться таковой, не зная вашего случая. Давайте вместо этого воспользуемся нашим примером.
8.5. Exception Chaining¶
The raise statement allows an optional from which enables chaining exceptions. For example:
This can be useful when you are transforming exceptions. For example:
Exception chaining happens automatically when an exception is raised inside an except or finally section. This can be disabled by using from None idiom:
For more information about chaining mechanics, see Built-in Exceptions .
Печать или вывод в файл информации об исключениях и записей трассировки стека.
Конфигурация логирования: обработчики
Обратите внимание, что если вы используете logging.fileConfig , иметь хорошую константу, такую как ERROR_LOG_FILENAME , невозможно. Эту же информацию можно прочитать из переменных среды, если хотите.
Также обратите внимание, что классы/параметры, которые я использую для обработчиков, создавал не я. Они из библиотеки logging , и там есть не только они!
Конфигурация логирования: логгеры и root
Давайте разберемся, что происходит:
В root мы определяем все логи, кроме DEBUG , которые будут обрабатываться logfile и обработчиками JSON .
Обработчик logfile отфильтровывает только логи ERROR и CRITICAL , и выводит их в файл с форматтером по умолчанию.
JSON принимает все входящие логи (без фильтра) и выводит их в консоль с JSON -форматтером.
Tryceratops переопределяет некоторые конфигурации, унаследованные от root , такие как level (несмотря на то, что это то же самое), и handlers для использования только verbose_output .
verbose_output принимает все входящие логи (фильтр по DEBUG ) и выводит их в консоль, используя форматтер simple .
Я определил новый набор обработчиков для tryceratops , но все другие логгеры (в том числе из сторонних библиотек) будут использовать те, которые находятся в корне.
Кроме того, обратите внимание, что я могу переписать правила по умолчанию. Через настройки или позже динамически. Например, каждый раз, когда triceratops получает подобный флаг от CLI, он обновляет конфигурацию logging чтобы включить дебаг.
Логирование – это важно, но наличие хорошо структурированных исключений и блоков try/except также важно, поэтому вы можете также прочитать, как профессионально обрабатывать и структурировать исключения в Python.
Until now error messages haven’t been more than mentioned, but if you have tried out the examples you have probably seen some. There are (at least) two distinguishable kinds of errors: syntax errors and exceptions.
Когда нужно логировать?
Чтобы логи оставались реактивными, вам нужно логировать «события». Сделайте их такими же понятными и удобными для чтения, как эта статья. Возможно, вы не прочитали каждую строку, которую я написал выше, но вы все равно можете продолжить дальше, пропустить ненужные разделы и сосредоточиться на том, что привлекло ваше внимание. Логи должны быть такими же.
Есть эмпирическое правило построение логов:
В начале соответствующих операций или потоков (например, при подключении к сторонним сервисам и т.д.);
При любом достигнутом прогрессе (например, успешная аутентификация, получен валидный код ответа и т.д.);
При завершении операции (успешном или неудачном).
Логи должны рассказывать вам историю, у каждой истории есть начало, середина и конец.
Будьте осторожны с релевантностью, добавлять логи проще, чем удалять их, ведь все, что нерелевантно – шум.
Читайте также: