我有一个在谷歌云中的 Kubernetes 中运行的应用程序。该应用程序是使用 fastapi 用 python 编写的。该应用程序的日志可以通过谷歌云日志记录看到,但是它们的“serverity”似乎翻译错误: 虽然 fastapi 的访问日志正确写入为“INFO”严重性,但从自定义记录器写入的任何消息都会显示为错误,即使它们是由
logger.info
调用写入的。
我通过
--log-config
命令行选项将以下日志记录配置传递给 uvicorn:
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"simple": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
},
"default": {
"()": "uvicorn.logging.DefaultFormatter",
"datefmt": "%Y-%m-%dT%H:%M:%S",
"format": "[%(asctime)s.%(msecs)04dZ] %(name)s %(levelprefix)s %(message).400s"
},
"access": {
"()": "uvicorn.logging.AccessFormatter",
"datefmt": "%Y-%m-%dT%H:%M:%S",
"format": "[%(asctime)s.%(msecs)04dZ] %(name)s %(levelprefix)s %(message)s"
}
},
"handlers": {
"default": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr"
},
"access": {
"formatter": "access",
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout"
}
},
"loggers": {
"uvicorn.error": {
"level": "INFO",
"handlers": ["default"],
"propagate": false
},
"uvicorn.access": {
"level": "INFO",
"handlers": ["access"],
"propagate": false
},
"uvicorn": {
"level": "INFO",
"handlers": ["default"],
"propagate": false
},
"jsonrpc": {
"level": "INFO",
"handlers": ["default"],
"propagate": false
},
"api": {
"level": "INFO",
"handlers": ["default"],
"propagate": false
}
},
"root": {
"level": "INFO",
"handlers": ["default"]
}
}
所有
uvicorn*
记录器均已正确处理,但 jsonrpc
和 api
记录器始终在 google cloud 中显示为“错误”。
按照 google-cloud-logging 的文档,我使用以下内容来设置云日志记录:
import google.cloud.logging
client = google.cloud.logging.Client()
client.setup_logging()
我错过了什么?我的配置文件有问题吗?这是预期的还是已知的行为?
编辑: 看来这里的部分问题是控制台处理程序转到的流。将默认处理程序切换到 stdout 并添加打印到 stderr 的错误处理程序似乎可以解决问题。
在此发帖是因为我遇到了类似的问题,对于部署在 Vertex AI 端点后面的 Docker 容器,所有日志均显示错误严重性,并且此处没有可用的解决方案。以下代码为我解决了这个问题:
import logging
import sys
info_stream_handler = logging.StreamHandler(stream=sys.stdout)
info_stream_handler.setLevel(logging.INFO)
cloud_logger = logging.getLogger('cloudLogger')
cloud_logger.setLevel(logging.INFO)
cloud_logger.addHandler(info_stream_handler)
cloud_logger.propagate = False
cloud_logger.info("Info Log Message")
Vertex AI 将记录模型容器中的 stdout 和 stderr 流 ( https://cloud.google.com/vertex-ai/docs/predictions/online-prediction-logging )。
首先,我一直在使用logging.basicConfig(level=logging.INFO)。
根据 python 日志记录模块文档 - https://docs.python.org/3/library/logging.html#logging.basicConfig,这会向根记录器添加默认的 StreamHandler。 StreamHandler 的默认流是 stderr,这意味着所有日志都被发送到 stderr,并由 Cloud Logging 提取为错误日志。
为了解决此问题,我创建了一个 StreamHandler 并指定将 INFO 日志发送到 stdout 而不是 stderr,并使用 addHandler() 方法将此处理程序添加到根记录器。
在此之后,我遇到的最后一个问题是 GCP 上的重复日志,其中一个具有正确的 INFO 级别,以及具有错误严重性的同一日志的重复项。
原因是日志被传递到 INFO StreamHandler,后者正确地将这些日志写入 stdout,但由于 python 日志记录模块的性质,所有这些日志也被传递到记录器“祖先”根记录器,将相同的日志写入 stderr,并导致重复。日志记录模块的 python 文档解释了这一点:
解决此问题的方法是将 cloud_logger.propagate 设置为 False,并且不将日志传递给祖先记录器。
文档说:
严重性: 默认情况下,写入标准输出的日志位于
级别和写入标准错误的日志位于INFO
等级。结构化日志可以包含ERROR
字段,该字段定义 日志的严重性。severity
所以@melcon的解决方案实际上不起作用。它只是将所有日志级别设置为
INFO
(因为 StreamHandler
采用标准输出)。相反,您可以使用 StructuredLogHandler
:
import logging
import sys
import json
from google.cloud import logging as cloud_logging
from google.cloud.logging_v2.handlers import CloudLoggingHandler
class StructuredLogHandler(logging.StreamHandler):
def emit(self, record):
log_entry = {
'message': record.getMessage(),
'severity': record.levelname
}
self.stream.write(json.dumps(log_entry) + '\n')
# Initialize the Cloud Logging client
client = cloud_logging.Client()
cloud_handler = CloudLoggingHandler(client)
# Create a logger
logger = logging.getLogger('cloudLogger')
logger.setLevel(logging.DEBUG) # Capture all levels of logging
# Create a structured log handler for stdout
structured_handler = StructuredLogHandler(stream = sys.stdout)
structured_handler.setLevel(logging.DEBUG) # Capture all levels of logging
# Add the handlers to the logger
logger.addHandler(structured_handler)
logger.addHandler(cloud_handler)
logger.propagate = False
# Sample log messages with different severities
logger.debug("This is a DEBUG message")
logger.info("This is an INFO message")
logger.warning("This is a WARNING message")
logger.error("This is an ERROR message")
logger.critical("This is a CRITICAL message")
因此日志将在 Google Cloud 中显示为: