Як додати власне поле до рядка формату журналу Python?


91

Мій поточний рядок формату:

formatter = logging.Formatter('%(asctime)s : %(message)s')

і я хочу додати нове поле з назвою, app_nameяке матиме різне значення в кожному скрипті, що містить цей форматтер.

import logging
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.addHandler(syslog)

Але я не впевнений, як передати це app_nameзначення реєстратору для інтерполяції у рядок формату. Я, очевидно, можу змусити його з'являтися в журналі, щоразу передаючи його, але це брудно.

Я пробував:

logging.info('Log message', app_name='myapp')
logging.info('Log message', {'app_name', 'myapp'})
logging.info('Log message', 'myapp')

але жодна робота не працює.


Ви дійсно хочете передавати це на кожен logдзвінок? Якщо так, подивіться на документи, де написано: "Ця функціональність може бути використана для введення ваших власних значень у LogRecord ..." Але це, здається, головний випадок для використання logger = logging.getLogger('myapp')та включення його у logger.infoвиклик.
abarnert

протоколювання python вже може це зробити afaik. якщо ви використовуєте інший loggerоб'єкт в кожному додатку, ви можете зробити кожен з них використовувати інше ім'я по інстанцірованія ваших loggerS наступним чином: logger = logging.getLogger(myAppName). зверніть увагу, що __name__це ім’я модуля python, тому, якщо кожна програма є своїм власним модулем python, це також буде працювати.
Флоріан Кастеллан

Відповіді:


131

Ви можете використовувати LoggerAdapter, щоб вам не потрібно було передавати додаткову інформацію під час кожного дзвінка в журнал:

import logging
extra = {'app_name':'Super App'}

logger = logging.getLogger(__name__)
syslog = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.setLevel(logging.INFO)
logger.addHandler(syslog)

logger = logging.LoggerAdapter(logger, extra)
logger.info('The sky is so blue')

журнали (щось на зразок)

2013-07-09 17:39:33,596 Super App : The sky is so blue

Фільтри також можна використовувати для додавання контекстної інформації.

import logging

class AppFilter(logging.Filter):
    def filter(self, record):
        record.app_name = 'Super App'
        return True

logger = logging.getLogger(__name__)
logger.addFilter(AppFilter())
syslog = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.setLevel(logging.INFO)
logger.addHandler(syslog)

logger.info('The sky is so blue')

видає подібний запис журналу.


3
Як ми можемо вказати це у config.iniфайлі? Я хочу додати поточне ім'я хосту socket.gethostname().
Лоран ЛАПОРТ

У мене цей зразок не працює для мене. import uuid uniqueId = str(uuid.uuid4()) extra = {"u_id" : uniqueId} RotatingHandler = RotatingFileHandler(LOG_FILENAME,encoding='utf-8',maxBytes=maxSize, backupCount=batchSize) logger.basicConfig(handlers=[RotatingHandler],level=logLevel.upper(),format='%(levelname)s %(u_id)s %(funcName)s %(asctime)s %(message)s ',datefmt='%m/%d/%Y %I:%M:%S %p') logger = logger.LoggerAdapter(logger=logger, extra=extra)
Хаят,

Чи можна додати поле "рівень", яке дорівнює "імені рівня"? Дивіться: Як я можу перейменувати “levelname” на “level” у повідомленнях журналу Python?
Мартін Тома,

2
Чи можу я просто передати рядок додаткової інформації. Приблизно так: "Сталася помилка для ідентифікатора працівника 1029382" Без створення словника.
Шріш Катті

50

Вам потрібно передати dict як параметр додаткові, щоб зробити це таким чином.

logging.info('Log message', extra={'app_name': 'myapp'})

Доказ:

>>> import logging
>>> logging.basicConfig(format="%(foo)s - %(message)s")
>>> logging.warning('test', extra={'foo': 'bar'})
bar - test 

Крім того, як примітка, якщо ви спробуєте зареєструвати повідомлення, не передаючи дикту, воно не вдасться.

>>> logging.warning('test')
Traceback (most recent call last):
  File "/usr/lib/python2.7/logging/__init__.py", line 846, in emit
    msg = self.format(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 723, in format
    return fmt.format(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 467, in format
    s = self._fmt % record.__dict__
KeyError: 'foo'
Logged from file <stdin>, line 1

Це також спрацює logging.info()? Це не вдалося, коли я намагався востаннє. : /
Прахар Мохан Шрівастава

2
Мені подобається відповідь @ mr2ert. Ви можете надати значення додатковому полю за замовчуванням, розширивши logging.Formatterклас: class CustomFormatter (logging.Formatter): def format (self, record): якщо немає hasattr (record, 'foo'): record.foo = 'default_foo' return super (CustomFormatter, self.format (record) h = loggin.StreamHandler () h.setFormatter (CustomFormatter ('% (foo) s% (message) s') logger = logging.getLogger ('bar') logger.addHandler ( h) logger.error ('гей!', додатково = {'foo': 'FOO'}) logger.error ('гей!')
loutre

Цей метод є швидшим, але вам потрібно додати зайві рядки до кожного повідомлення журналу, яке легко забути і схильне до помилок. Заміна викликів super () є більш заплутаною, ніж відповідь з unutbu.
pevogam

@Prakhar Mohan Srivastava Так. Це також буде добре працювати для logging.info (). Яке повідомлення про помилку ви отримуєте?
shreesh katti

Чи можу я просто передати рядок додаткової інформації. Приблизно так: "Сталася помилка для ідентифікатора працівника 1029382" Не створивши жодного словника та не передавши ключі
shreesh katti

23

Python3

Починаючи з Python3.2, ви тепер можете використовувати LogRecordFactory

>>> import logging
>>> logging.basicConfig(format="%(custom_attribute)s - %(message)s")
>>> old_factory = logging.getLogRecordFactory()
>>> def record_factory(*args, **kwargs):
        record = old_factory(*args, **kwargs)
        record.custom_attribute = "my-attr"
        return record

>>> logging.setLogRecordFactory(record_factory)
>>> logging.info("hello")
my-attr - hello

Звичайно, record_factoryможна налаштувати будь-який виклик, а значення custom_attributeможна оновити, якщо зберегти посилання на заводський виклик.

Чому це краще, ніж використання адаптерів / фільтрів?

  • Вам не потрібно передавати реєстратор навколо програми
  • Насправді це працює зі сторонніми бібліотеками, які використовують власний журнал реєстрації (шляхом простого дзвінка logger = logging.getLogger(..)), тепер матимуть той самий формат журналу. (це не стосується фільтрів / адаптерів, де вам потрібно використовувати той самий об'єкт реєстратора)
  • Ви можете скласти / зв’язати кілька заводів

Чи існує якась альтернатива для python 2.7?
karolch

Не з тими ж перевагами, з 2.7 вам доведеться користуватися адаптерами або фільтрами.
Ахмад

5
На сьогодні це найкраща відповідь python3
Стефан,

Відповідно до docs.python.org/3/howto/logging-cookbook.html : Цей шаблон дозволяє різним бібліотекам зв’язувати фабрики разом, і якщо вони не перезаписують атрибути один одного або ненавмисно перезаписують стандартні атрибути, не повинно бути сюрпризів. Однак слід мати на увазі, що кожна ланка в ланцюжку додає накладні витрати на всі операції реєстрації, і техніку слід використовувати лише тоді, коли використання фільтра не дає бажаного результату.
steve0hh

1
@ steve0hh одним з ключових бажаних результатів є можливість реєструвати контекстну інформацію в різних бібліотеках / модулях, чого можна досягти лише за допомогою цього способу. У більшості випадків бібліотеки не повинні торкатися конфігурації журналу, це відповідальність батьківської програми.
Ахмад

9

Інший спосіб - створити власний LoggerAdapter. Це особливо корисно, коли ви не можете змінити формат АБО, якщо ваш формат надається коду, який не надсилає унікальний ключ (у вашому випадку app_name ):

class LoggerAdapter(logging.LoggerAdapter):
    def __init__(self, logger, prefix):
        super(LoggerAdapter, self).__init__(logger, {})
        self.prefix = prefix

    def process(self, msg, kwargs):
        return '[%s] %s' % (self.prefix, msg), kwargs

І у своєму коді ви створюєте та ініціалізуєте свій реєстратор, як зазвичай:

    logger = logging.getLogger(__name__)
    # Add any custom handlers, formatters for this logger
    myHandler = logging.StreamHandler()
    myFormatter = logging.Formatter('%(asctime)s %(message)s')
    myHandler.setFormatter(myFormatter)
    logger.addHandler(myHandler)
    logger.setLevel(logging.INFO)

Нарешті, ви створили б адаптер обгортки, щоб за необхідності додати префікс:

    logger = LoggerAdapter(logger, 'myapp')
    logger.info('The world bores you when you are cool.')

Результат буде виглядати приблизно так:

2013-07-09 17:39:33,596 [myapp] The world bores you when you are cool.

1

Я знайшов це SO-питання після того, як сам його реалізував. Сподіваюся, це комусь допомагає. У наведеному нижче коді я навожу додатковий ключ, який викликається claim_idу форматі реєстратора. Він буде реєструвати ідентифікатор претензії щоразу, коли claim_idв середовищі присутній ключ. У моєму випадку використання мені потрібно було записати цю інформацію для функції AWS Lambda.

import logging
import os

LOG_FORMAT = '%(asctime)s %(name)s %(levelname)s %(funcName)s %(lineno)s ClaimID: %(claim_id)s: %(message)s'


class AppLogger(logging.Logger):

    # Override all levels similarly - only info overriden here

    def info(self, msg, *args, **kwargs):
        return super(AppLogger, self).info(msg, extra={"claim_id": os.getenv("claim_id", "")})


def get_logger(name):
    """ This function sets log level and log format and then returns the instance of logger"""
    logging.setLoggerClass(AppLogger)
    logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
    logger = logging.getLogger(name)
    logger.setLevel(logging.INFO)
    return logger


LOGGER = get_logger(__name__)

LOGGER.info("Hey")
os.environ["claim_id"] = "12334"
LOGGER.info("Hey")

Суть: https://gist.github.com/ramanujam/306f2e4e1506f302504fb67abef50652


0

Використовуючи відповідь mr2ert, я придумав це зручне рішення (хоча, мабуть, це не рекомендується) - перевизначити вбудовані методи реєстрації, щоб прийняти спеціальний аргумент і створити extra словник усередині методів:

import logging

class CustomLogger(logging.Logger):

   def debug(self, msg, foo, *args, **kwargs):
       extra = {'foo': foo}

       if self.isEnabledFor(logging.DEBUG):
            self._log(logging.DEBUG, msg, args, extra=extra, **kwargs)

   *repeat for info, warning, etc*

logger = CustomLogger('CustomLogger', logging.DEBUG)
formatter = logging.Formatter('%(asctime)s [%(foo)s] %(message)s') 
handler = logging.StreamHandler()
handler.setFormatter(formatter) 
logger.addHandler(handler)

logger.debug('test', 'bar')

Вихід:

2019-03-02 20:06:51,998 [bar] test

Це вбудована функція для довідки:

def debug(self, msg, *args, **kwargs):
    """
    Log 'msg % args' with severity 'DEBUG'.

    To pass exception information, use the keyword argument exc_info with
    a true value, e.g.

    logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)
    """
    if self.isEnabledFor(DEBUG):
        self._log(DEBUG, msg, args, **kwargs)

0

імпорт лісозаготівлі;

клас LogFilter (logging.Filter):

def __init__(self, code):
    self.code = code

def filter(self, record):
    record.app_code = self.code
    return True

logging.basicConfig (format = '[% (asctime) s:% (levelname) s] :: [% (module) s ->% (name) s] - APP_CODE:% (app_code) s - MSG:% (message ) s ');

Клас реєстратора:

def __init__(self, className):
    self.logger = logging.getLogger(className)
    self.logger.setLevel(logging.ERROR)

@staticmethod
def getLogger(className):
    return Logger(className)

def logMessage(self, level, code, msg):
    self.logger.addFilter(LogFilter(code))

    if level == 'WARN':        
        self.logger.warning(msg)
    elif level == 'ERROR':
        self.logger.error(msg)
    else:
        self.logger.info(msg)

class Test: logger = Logger.getLogger ('Test')

if __name__=='__main__':
    logger.logMessage('ERROR','123','This is an error')

Це впровадження буде дуже неефективним.
blakev
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.