如何将自定义字段添加到 Python 日志格式字符串?

我当前的格式字符串是:

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')

但都没用。

111441 次浏览

您需要将 dict 作为一个参数传递给 add 才能这样做。

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

您可以使用 记录器适配器,这样您就不必在每次日志调用时传递额外的信息:

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')

产生类似的日志记录。

另一种方法是创建自定义 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.

使用 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)

蟒蛇3

从 Python 3.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(..))的第三方库,现在日志格式是相同的。(Filters/Adapters 不是这种情况,需要使用相同的日志记录器对象)
  • 您可以堆叠/链接多个工厂

进口日志;

类 LogFilter (logging.Filter) :

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


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

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')

这个问题是我自己实现后发现的。希望能帮到别人。在下面的代码中,我将以日志记录器格式引入一个称为 claim_id的额外键。每当环境中出现 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

接受的答案没有在日志文件中记录格式,而格式反映在系统输出中。 或者,我使用一种更简单的方法,作为;

logging.basicConfig(filename="mylogfile.test",
filemode="w+",
format='%(asctime)s: ' +app_name+': %(message)s ',
level=logging.DEBUG)

如果您需要一个默认的 extra映射,还有,您希望为特别的日志消息定制它,那么在 Python 2.7 + 中可以通过创建一个 LoggerAdapter来合并默认的 extra字典和来自给定消息的任何 extra来实现。

import logging
import os
import sys


logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s %(levelname)-8s Py%(python)-4s pid:%(pid)-5s %(message)s',
)
_logger = logging.getLogger("my-logger")
_logger.setLevel(logging.DEBUG)




class DefaultExtrasAdapter(logging.LoggerAdapter):
def __init__(self, logger, extra):
super(DefaultExtrasAdapter, self).__init__(logger, extra)


def process(self, msg, kwargs):
# Speed gain if no extras are present
if "extra" in kwargs:
copy = dict(self.extra).copy()
copy.update(kwargs["extra"])
kwargs["extra"] = copy
else:
kwargs["extra"] = self.extra
return msg, kwargs




LOG = DefaultExtrasAdapter(_logger, {"python": sys.version_info[0], "pid": os.getpid()})


if __name__ == "__main__":
LOG.info("<-- With defaults")
LOG.info("<-- With my version", extra={"python": 3.10})
LOG.info("<-- With my pid", extra={"pid": 0})
LOG.info("<-- With both", extra={"python": 2.7, "pid": -1})

结果:

2021-08-05 18:58:27,308 INFO     Py2    pid:8435  <-- With defaults
2021-08-05 18:58:27,309 INFO     Py3.1  pid:8435  <-- With my version
2021-08-05 18:58:27,309 INFO     Py2    pid:0     <-- With my pid
2021-08-05 18:58:27,309 INFO     Py2.7  pid:-1    <-- With both