如何将自定义字段添加到 Python 日志格式字符串?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17558552/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I add custom field to Python log format string?
提问by nickponline
My current format string is:
我当前的格式字符串是:
formatter = logging.Formatter('%(asctime)s : %(message)s')
and I want to add a new field called app_name and which will have a different value in each script that contains this formatter.
我想添加一个名为 app_name 的新字段,该字段在包含此格式化程序的每个脚本中将具有不同的值。
import logging
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.addHandler(syslog)
But I'm not sure how to pass that app_name
value to the logger to interpolate into the format string. I can obviously get it to appear in the log message but passing it each time but this is messy.
但我不确定如何将该app_name
值传递给记录器以插入格式字符串。我显然可以让它出现在日志消息中,但每次都传递它,但这很混乱。
I've tried:
我试过了:
logging.info('Log message', app_name='myapp')
logging.info('Log message', {'app_name', 'myapp'})
logging.info('Log message', 'myapp')
but none work.
但没有工作。
采纳答案by unutbu
You could use a LoggerAdapterso you don't have to pass the extra info with every logging call:
您可以使用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')
logs (something like)
日志(类似的东西)
2013-07-09 17:39:33,596 Super App : The sky is so blue
Filterscan also be used to add contextual information.
过滤器还可用于添加上下文信息。
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')
produces a similar log record.
产生类似的日志记录。
回答by mr2ert
You need to pass the dict as a parameter to extra to do it that way.
您需要将 dict 作为参数传递给 extra 才能这样做。
logging.info('Log message', extra={'app_name': 'myapp'})
Proof:
证明:
>>> import logging
>>> logging.basicConfig(format="%(foo)s - %(message)s")
>>> logging.warning('test', extra={'foo': 'bar'})
bar - test
Also, as a note, if you try to log a message without passing the dict, then it will fail.
另外,请注意,如果您尝试在不通过 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
回答by rouble
Another way is to create a custom LoggerAdapter. This is particularly useful when you can't change the format OR if your format is shared with code that does not send the unique key (in your case app_name):
另一种方法是创建自定义 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
And in your code, you would create and initialize your logger as usual:
在您的代码中,您将像往常一样创建和初始化记录器:
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)
Finally, you would create the wrapper adapter to add a prefix as needed:
最后,您将创建包装器适配器以根据需要添加前缀:
logger = LoggerAdapter(logger, 'myapp')
logger.info('The world bores you when you are cool.')
The output will look something like this:
输出将如下所示:
2013-07-09 17:39:33,596 [myapp] The world bores you when you are cool.
回答by Yaniv K.
Using mr2ert's answer, I came up with this comfortable solution (Though I guess it's not recommended) - Override the built-in logging methods to accept the custom argument and create the extra
dictionary inside the methods:
使用 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')
Output:
输出:
2019-03-02 20:06:51,998 [bar] test
This is the built in function for reference:
这是内置函数供参考:
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)
回答by Ahmad
Python3
蟒蛇3
As of Python3.2 you can now use LogRecordFactory
从 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
Of course record_factory
can be customized to be any callable and the value of custom_attribute
could be updated if you keep a reference to the factory callable.
当然,record_factory
可以自定义为任何可调用对象,custom_attribute
如果您保留对工厂可调用对象的引用,则可以更新的值。
Why is that better than using Adapters / Filters?
为什么这比使用适配器/过滤器更好?
- You don't need to pass your logger around the application
- It actually works with 3rd party libraries that uses their own logger (by just calling
logger = logging.getLogger(..)
) would now have the same log format. (this is not the case with Filters / Adapters where you need to be using the same logger object) - You can stack/chain multiple factories
- 您不需要在应用程序周围传递记录器
- 它实际上适用于使用自己的记录器(通过调用
logger = logging.getLogger(..)
)的3rd 方库现在具有相同的日志格式。(过滤器/适配器不是这种情况,您需要使用相同的记录器对象) - 您可以堆叠/链接多个工厂
回答by user3672617
import logging;
导入日志;
class LogFilter(logging.Filter):
类 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');
logging.basicConfig(format='[%(asctime)s:%(levelname)s]::[%(module)s -> %(name)s] - APP_CODE:%(app_code)s - MSG:%(message )s');
class Logger:
类记录器:
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')
类测试:logger = Logger.getLogger('Test')
if __name__=='__main__':
logger.logMessage('ERROR','123','This is an error')
回答by rhn89
I found this SO question after implementing it myself. Hope it helps someone. In the code below, I'm inducing an extra key called claim_id
in the logger format. It will log the claim_id whenever there is a claim_id
key present in the environment. In my use case, I needed to log this information for an AWS Lambda function.
我自己实施后发现了这个问题。希望它可以帮助某人。在下面的代码中,我引入了一个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")
Gist: https://gist.github.com/ramanujam/306f2e4e1506f302504fb67abef50652
要点:https: //gist.github.com/ramanujam/306f2e4e1506f302504fb67abef50652