125

我当前的格式字符串是:

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

但没有一个工作。

4

9 回答 9

170

您可以使用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')

产生类似的日志记录。

于 2013-07-09T21:40:46.953 回答
59

您需要将 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 

另外,请注意,如果您尝试在不传递字典的情况下记录消息,那么它将失败。

>>> 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
于 2013-07-09T21:40:05.653 回答
49

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(..))的 3rd 方库一起使用,现在将具有相同的日志格式。(过滤器/适配器不是这种情况,您需要使用相同的记录器对象)
  • 您可以堆叠/链接多个工厂
于 2019-09-06T10:39:37.600 回答
13

另一种方法是创建自定义 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.
于 2018-05-30T21:53:22.110 回答
2

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

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

于 2020-12-24T06:44:41.243 回答
1

如果你需要一个默认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
于 2021-08-05T19:07:43.727 回答
1

我在自己实施后发现了这个 SO 问题。希望它可以帮助某人。在下面的代码中,我引入了一个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

于 2020-04-13T22:36:19.803 回答
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)
于 2019-03-02T18:11:38.880 回答
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')
于 2020-03-01T10:07:22.757 回答