如何在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日志记录已经可以做到这一点。如果您使用的是不同的logger每个应用程序中的对象,你可以通过你的实例使每一个使用不同的名称logger就像这样:logger = logging.getLogger(myAppName)。请注意,这__name__是python模块的名称,因此,如果每个应用程序都是其自己的python模块,则该模块也将正常工作。
Florian Castellane

Answers:


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)
哈亚特(Hayat)'18

是否可以添加一个等于“ levelname”的字段“ level”?请参阅:如何在Python日志消息中将“ levelname”重命名为“ level”?
马丁·托马

2
我可以只传递一串额外信息吗?这样的事情:“雇员ID 1029382发生错误”,而没有创建任何词典。
shreesh katti

50

您需要将dict作为参数传递给extra,以这种方式进行。

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 

另外,请注意,如果您尝试在不通过dict的情况下记录消息,则它将失败。

>>> 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()吗?我上次尝试时失败了。:/
Prakhar Mohan Srivastava 2015年

2
我喜欢@ mr2ert答案。您可以通过扩展logging.Formatter类为附加字段提供默认值:class CustomFormatter(logging.Formatter):def format(self,record):如果没有hasattr(record,'foo'):record.foo ='default_foo'return超级(CustomFormatter,self.format(record)h = loggin.StreamHandler()h.setFormatter(CustomFormatter('%(foo)s%(message)s')logger = logging.getLogger('bar')logger.addHandler( h)logger.error('hey!',extra = {''foo':'FOO'})logger.error('hey!')
Loutre

这种方法速度更快,但是您需要在每条日志消息上添加额外的行,这些行容易忘记并且容易出错。替换super()调用比unutbu的回答要混乱。
pevogam '16

@Prakhar Mohan Srivastava是的,它也可以很好地用于logging.info()。您收到什么错误消息?
shreesh katti

我可以只传递一串额外信息吗?这样的事情:“雇员ID 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(..))的第3方库一起使用,现在具有相同的日志格式。(对于“过滤器/适配器”,情况并非如此,您需要使用同一记录器对象)
  • 您可以堆叠/链接多个工厂

python 2.7可以替代吗?
karolch '19

2.7的好处不尽相同,而您必须使用适配器或过滤器。
艾哈迈德

5
这是nowaday的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

我自己实施此问题后就发现了这个问题。希望它能帮助某人。在下面的代码中,我引入了一个名为claim_idlogger格式。每当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)

类测试:logger = Logger.getLogger('Test')

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

这种实现将非常低效。
blakev
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.