Есть ли способ использовать переменные данные в фактической части формата регистратора?
Я хочу, чтобы мои журналы содержали имена функций в стеке.Вот, например, следующий код:
# logging_utils.py
def init_logger(logger_name: str) -> logging.Logger:
log = logging.getLogger(logger_name)
log.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.INFO)
formatter = logging.Formatter(f'[ %(asctime)s ] [ %(levelname)s ] [ %(callStack?)s ] %(message)s')
handler.setFormatter(formatter)
log.addHandler(handler)
return log
def format_stack() -> str:
return ':'.join(frame.function for frame in inspect.stack()[::-1][:-1]).replace('<module>:', '')
def f():
g()
def g():
h()
def h():
logger = init_logger('x')
logger.info('My actual message')
if __name__ == '__main__':
f()
Я бы хотел, чтобы сообщения регистрации выглядели так:
[ 2019-02-18 14:14:23,558 ] [ INFO ] [ logging_utils:f:g:h ] My actual message
Ближайшее, к чему я пришел, - это использование собственного класса, подобного этому:
import sys
import inspect
import logging
class Logger:
_logger: logging.Logger
def __init__(self, name: str):
logger = logging.getLogger(name)
logger.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.INFO)
formatter = logging.Formatter(f'[ %(asctime)s ] [ %(levelname)s ] %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
self._logger = logger
@staticmethod
def _format_stack_for_logger() -> str:
stack = inspect.stack()[::-1]
stack_names = (inspect.getmodulename(stack[0].filename),
*(frame.function
for frame
in stack[1:-3]))
return '::'.join(stack_names)
def _log(self, level: int, msg: str, *args, **kwargs):
self._logger.log(level, '[ %s ] %s', self._format_stack_for_logger(), msg, *args, **kwargs)
def debug(self, msg: str, *args, **kwargs):
self._log(logging.DEBUG, msg, *args, **kwargs)
def info(self, msg: str, *args, **kwargs):
self._log(logging.INFO, msg, *args, **kwargs)
def warning(self, msg: str, *args, **kwargs):
self.info(logging.WARNING, msg, *args, **kwargs)
def error(self, msg: str, *args, **kwargs):
self._log(logging.ERROR, msg, *args, **kwargs)
def critical(self, msg: str, *args, **kwargs):
self._log(logging.CRITICAL, msg, *args, **kwargs)
def f():
g()
def g():
h()
def h():
logger = Logger('x')
logger.info('My actual message :(')
if __name__ == '__main__':
f()
Но информация трассировки стека ... полужестко закодирована (?) В строку регистрации.Я ищу элегантный способ получить этот результат.
Заранее спасибо!
РЕДАКТИРОВАТЬ:
Благодаря @VinaySajip для подталкивания вправильное направление.
К сожалению, метод Formatter
по умолчанию *1022* просто добавляет вывод formatStack
в конце строки для печати.Независимо от того, что вы делаете, если вы используете реализацию по умолчанию format
, это то, что произойдет.
Что я сделал, так это расширил класс Formatter
, переопределив метод format
.Содержимое метода форматирования копируется на 70% и вставляется из модуля logging
, но часть, которая добавляет информацию стека, удаляется.
Вместо этого красиво отформатированный результат inspect.stack()
(возвращаемый новым formatStack
) помещается в stack_info
член LogRecord
, который будет отформатирован.
Это сделано потому, что для получения окончательного сообщения, которое будет напечатано, словарь LogRecord
используется в качестве второго операнда fmt_string % args
.Поэтому, когда строка формата интерполируется со словарем LogRecord
, LogRecord.stack_info
будет содержать красиво отформатированный стек вызовов, поэтому все, что действительно нужно сделать, это использовать %(stack_info)s
в их строке формата.
По существу, ядобавлена поддержка %(stack_info)s
в строке форматирования форматера.
Код:
import sys
import inspect
import logging
from logging import Logger
class CallStackFormatter(logging.Formatter):
def formatStack(self, _ = None) -> str:
stack = inspect.stack()[::-1]
stack_names = (inspect.getmodulename(stack[0].filename),
*(frame.function
for frame
in stack[1:-9]))
return '::'.join(stack_names)
def format(self, record):
record.message = record.getMessage()
record.stack_info = self.formatStack()
if self.usesTime():
record.asctime = self.formatTime(record, self.datefmt)
s = self.formatMessage(record)
if record.exc_info:
# Cache the traceback text to avoid converting it multiple times
# (it's constant anyway)
if not record.exc_text:
record.exc_text = self.formatException(record.exc_info)
if record.exc_text:
if s[-1:] != "\n":
s = s + "\n"
s = s + record.exc_text
return s
def get_logger(name: str, fmt: str = '[ %(asctime)s ] [ %(levelname)-5s ] [ %(stack_info)s ] %(message)s') -> Logger:
logger = logging.getLogger(name)
logger.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.INFO)
formatter = CallStackFormatter(fmt)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger