Всем привет. Хочу поговорить про модуль логирования в Python, а именно стандартный модуль "logging". Описывать по шагам как его применять - я не буду, тут о другом. Что мы ждем от лога? - правильно, информативности. и особенно в том месте лога где содержится информация об ошибке.
Рассмотрим простой пример, создадим логгер и поймаем ошибку выхода за пределы списка:
import logging
logfile = 'log_1.log'
log = logging.getLogger("my_log")
log.setLevel(logging.INFO)
FH = logging.FileHandler(logfile)
basic_formater = logging.Formatter('%(asctime)s : [%(levelname)s] : %(message)s')
FH.setFormatter(basic_formater)
log.addHandler(FH)
A = [i for i in range(5)]
log.info("start program")
try:
for i in range(6):
print (A[i]**2)
log.info("program calculate square " + str(A[i]))
except:
log.error("произошла ошибка")
log.info("end program")
лог будет выглядеть так-
2021-02-16 13:06:40,391 : [INFO] : start program
2021-02-16 13:06:40,391 : [INFO] : program calculate square 0
2021-02-16 13:06:40,407 : [INFO] : program calculate square 1
2021-02-16 13:06:40,407 : [INFO] : program calculate square 2
2021-02-16 13:06:40,407 : [INFO] : program calculate square 3
2021-02-16 13:06:40,422 : [INFO] : program calculate square 4
2021-02-16 13:06:40,422 : [ERROR] : произошла ошибка
2021-02-16 13:06:40,422 : [INFO] : end program
Строка с ошибкой не несет в себе ничего, кроме факта наличия ошибки. Хотелось бы больше. Можно перехватить сообщение об ошибке из класса "Exception", меняем
except Exception as Ex:
log.error(Ex)
Получаем:
2021-02-16 13:13:22,371 : [INFO] : program calculate square 4
2021-02-16 13:13:22,371 : [ERROR] : list index out of range
2021-02-16 13:13:22,371 : [INFO] : end program
Уже лучше, но не понятно где именно произошла ошибка, в каком модуле, каком файле, на какой строке...
Конечно, модуль "logging" позволяет задавать формат лога и включать в него "полезную информацию". почему тут кавычки?- потому что модуль делает это своебразно, например если ошибка возникла в функции "foo" а для логирования мы используем отдельную функцию "error_log" то в логе будет запись, что ошибка произошла в модуле "error_log", а не там, где это было на самом деле. Т.е. в логе мы видим откуда (файл, модуль, строка) был вызван метод log.error(текст), но нам это по сути не интересно.
Подробнее разберем наверное самый важный вопрос для разработчика - "на какой строчке ошибка"?
Если мы изменим наш базовый формат лога
basic_formater = logging.Formatter('%(asctime)s : [%(levelname)s] [%(lineno)d] : %(message)s')
Получим
2021-02-16 13:53:44,635 : [INFO] [17] : program calculate square 4
2021-02-16 13:53:44,635 : [ERROR] [19] : list index out of range
2021-02-16 13:53:44,635 : [INFO] [20] : end program
Видно, что номер строки [19] - это не та строка, где произошла ошибка (16). Как и в примере с именем модуля, мы обломались. Как быть?
Можно вытащить полный текст ошибки из модуля "traceback"
import traceback
...
...
except :
log.error(traceback.format_exc())
И получим
2021-02-16 14:59:14,142 : [ERROR] [20] : Traceback (most recent call last):
File "C:\Users\***\Desktop\logg.py", line 17, in print (A[i]**2) IndexError: list index out of range
Отлично, в логе видим "line 17", там и ошибка. Круто.
Но что, если все таки нам нужно поменять формат? чтобы номер строки ошибки был не где-то там в тексте, а на самом видно месте, как это можно сделать?
Номер строки ошибки можно получить конструкцией:
frame = traceback.extract_tb(sys.exc_info()[2])
line_no = str(frame[0]).split()[4]
И пойдем еще дальше - будем менять формат только для ошибок. Т.к. с какой строчки прошло INFO - нам не интересно. Для этого определим отдельную функцию, итоговый код будет такой:
import logging
import traceback
import sys
logfile = 'log_1.log'
log = logging.getLogger("my_log")
log.setLevel(logging.INFO)
FH = logging.FileHandler(logfile, encoding='utf-8')
## задаем базовый формат сообщения в логе
basic_formater = logging.Formatter('%(asctime)s : [%(levelname)s] : %(message)s')
FH.setFormatter(basic_formater)
log.addHandler(FH)
## функция для записи в лог сообщений об ошибке
def error_log(line_no):
## задаем формат ошибочных сообщений, добавляем номер строки
err_formater = logging.Formatter('%(asctime)s : [%(levelname)s][LINE ' + line_no + '] : %(message)s')
## устанавливаем формат ошибок в логгер
FH.setFormatter(err_formater)
log.addHandler(FH)
## пишем сообщение error
log.error(traceback.format_exc())
## возвращаем базовый формат сообщений
FH.setFormatter(basic_formater)
log.addHandler(FH)
A = [i for i in range(5)]
log.info("start program")
try:
for i in range(6):
print (A[i]**2)
log.info("program calculate square " + str(A[i]))
except :
frame = traceback.extract_tb(sys.exc_info()[2])
line_no = str(frame[0]).split()[4]
## вызываем функцию записи ошибки и передаем в нее номер строки с ошибкой
error_log(line_no)
log.info("end program")
и сам лог
2021-02-16 15:39:06,972 : [INFO] : start program
2021-02-16 15:39:06,990 : [INFO] : program calculate square 0
2021-02-16 15:39:06,992 : [INFO] : program calculate square 1
2021-02-16 15:39:06,992 : [INFO] : program calculate square 2
2021-02-16 15:39:06,992 : [INFO] : program calculate square 3
2021-02-16 15:39:07,007 : [INFO] : program calculate square 4
2021-02-16 15:39:07,007 : [ERROR][LINE 32] : Traceback (most recent call last):
File "C:\Users\***\Desktop\logg.py", line 32, in print (A[i]**2) IndexError: list index out of range2021-02-16 15:39:07,007 : [INFO] : end program
Так лог получается информативнее, чем это позволяют стандартные методы модуля "logging"