我正在尝试创建一个集中模块来设置我的日志格式化程序,以便在我的lambda函数中的多个python模块之间共享。此功能最终将在本地内部部署设备上的AWS Greengrass上运行。
出于某种原因,当我添加自己的处理程序来格式化消息时,日志将被输出两次 - 一次是在正确的日志级别,第二次是在错误的级别。
如果我使用标准的python记录器而没有设置任何处理程序,它就可以正常工作。
main.py
:
import logging
logging.debug("test1")
cloudwatch logs
:
12:28:42 [DEBUG]-main.py:38,test1
我的目标是在我的代码上有一个格式化程序,它将这些日志消息格式化为JSON。然后它们将被摄入集中式日志记录数据库。但是,当我这样做时,我会得到两次日志消息。
loghelper.py
:
def setup_logging(name):
formatter = logging.Formatter("%(name)s, %(asctime)s, %(message)s")
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(formatter)
logger = logging.getLogger(name)
if logger.handlers:
for handler in logger.handlers:
logger.removeHandler(handler)
logger.setLevel(logging.DEBUG)
logger.addHandler(handler)
return logger
main.py
:
import logging
logger = loghelper.setup_logging('main.test_function')
def test_function():
logger.debug("test function log statement")
test_function()
现在运行lambda函数时,我在云监视日志中收到两次调试消息,如下所示:
cloudwatch logs
:
12:22:53 [DEBUG]-main.py:5, test function log statement
12:22:53 [INFO]-__init__.py:880,main.test_function,2018-06-18 12:22:53,099, test function log statement
请注意:
我无法解释这种行为,并会对可能导致这种行为的任何想法表示感谢。我也不知道第880行存在哪个构造函数。这可能会对正在发生的事情有所了解。
参考文献:
设置全局格式化程序: How to define a logger in python once for the whole program?
清除默认的lambda日志处理程序: Using python Logging with AWS Lambda
创建全局记录器: Python: logging module - globally
答案 0 :(得分:8)
AWS Lambda还在根记录器上设置了一个处理程序,和捕获了写入stdout
的任何内容,并将其记录为级别INFO
。因此,您的日志消息被捕获两次:
这就是所有消息都以(asctime) [(levelname)]-(module):(lineno),
信息开头的原因;根记录器配置为输出具有该格式的消息,写入stdout的信息只是该输出中的另一个%(message)
部分。
当您处于AWS环境或时,不要设置处理程序,禁止将输出传播到根处理程序,并将所有消息记录为{{1 AWS的消息;在后一种情况下,您自己的格式化程序可以在输出中包含INFO
级别信息。
您可以使用levelname
禁用日志传播,此时您的消息将仅传递给您的处理程序,而不是传递给根处理程序。
另一种选择是仅依赖AWS根记录器配置。根据{{3}},根记录器配置为:
logger.propagate = False
这会将logging.Formatter.converter = time.gmtime
logger = logging.getLogger()
logger_handler = LambdaLoggerHandler()
logger_handler.setFormatter(logging.Formatter(
'[%(levelname)s]\t%(asctime)s.%(msecs)dZ\t%(aws_request_id)s\t%(message)s\n',
'%Y-%m-%dT%H:%M:%S'
))
logger_handler.addFilter(LambdaLoggerFilter())
logger.addHandler(logger_handler)
上的time.localtime
转换器替换为logging.Formatter
(因此时间戳使用UTC而不是locatime),设置自定义处理程序以确保消息转到Lambda基础结构,配置格式,并添加一个过滤器对象,只对记录添加time.gmtime
属性(所以上面的格式化程序可以包含它),但实际上并没有过滤任何东西。
您可以通过更新aws_request_id
对象上的属性来更改该处理程序上的格式化程序:
handler.formatter
然后完全删除自己的记录器处理程序。你确实要小心这个; AWS Lambda基础架构很可能依赖于所使用的特定格式。您在问题中显示的输出不包含日期组件(for handler in logging.getLogger().handlers:
formatter = handler.formatter
if formatter is not None and 'aws_request_id' in formatter._fmt:
# this is the AWS Lambda formatter
# formatter.datefmt => '%Y-%m-%dT%H:%M:%S'
# formatter._style._fmt =>
# '[%(levelname)s]\t%(asctime)s.%(msecs)dZ'
# '\t%(aws_request_id)s\t%(message)s\n'
字符串的%Y-%m-%dT
部分),这可能意味着该格式已被解析并正在呈现给您Web应用程序视图的数据。
答案 1 :(得分:1)
我不确定这是否是导致问题的原因,但默认情况下,Python的记录器会将其消息传播到日志记录层次结构。您可能知道,Python记录器在树中组织,顶部有root
记录器,下面有其他记录器。在记录器名称中,点(.
)引入了新的层次结构级别。所以,如果你这样做
logger = logging.getLogger('some_module.some_function`)
然后你实际上有3个记录器:
The root logger (`logging.getLogger()`)
A logger at module level (`logging.getLogger('some_module'))
A logger at function level (`logging.getLogger('some_module.some_function'))
如果您在记录器上发出日志消息并且未根据记录器最低级别丢弃该消息,则该消息将传递给记录器的处理程序和到其父记录器。有关详细信息,请参阅this flowchart。
如果该父记录器(或层次结构中较高的任何记录器)也有处理程序,那么它们也会被调用。
我怀疑在您的情况下,根记录器或main
记录器会以某种方式最终附加一些处理程序,这会导致重复的消息。为避免这种情况,您可以将记录器中的propagate
设置为False
,或仅将处理程序附加到根记录器。