我有一个 Python 应用程序,它将日志流式传输到第三方服务(在本例中,它是一个 AWS S3 存储桶)。
我的简化代码如下所示:
class S3Handler(logging.StreamHandler):
def __init__(self, bucket_name, key):
super().__init__()
self.bucket_name = bucket_name
self.key = key
self.s3_client = boto3.client('s3',
endpoint_url=...,
aws_access_key_id=...,
aws_secret_access_key=...)
def emit(self, record):
try:
log_entry = self.format(record).encode('utf-8')
self.s3_client.put_object(Bucket=self.bucket_name, Key=self.key, Body=log_entry, ACL="public-read")
except Exception as e:
print(f"Error while logging to S3: {str(e)}")
s3_handler = S3Handler(bucket_name='mybucketname', key='path/to/logfile.txt')
logging.getLogger().addHandler(s3_handler)
目标是当我运行脚本时,所有日志都将保存在 S3 存储桶中。该脚本可以运行 30 秒,也可以运行 10 小时。
当我运行此代码时,我立即收到此错误:
Error while logging to S3: maximum recursion depth exceeded while calling a Python object
经过一番谷歌搜索,我发现这个错误背后的原因是超过了Python提供的默认1000次迭代配额。有一种方法可以增加这个配额,但我认为对于这种特殊情况来说它不实用或不可行。
显然,我将脚本日志简化到 S3 存储桶的方法并不理想。有哪些选项可以解决此问题?
您的代码的问题在于它如何处理每个日志消息。以下是发生递归错误的原因和替代解决方案:
理解递归错误:
S3Handler.emit
函数会格式化日志记录并将其编码为 UTF-8。try
块内,如果上传日志时出错,print
语句会尝试格式化错误消息(可能包含原始日志记录)。解决方案:
以下是解决此问题的几种方法:
不要在
emit
函数中打印错误消息,而是将其记录到单独的记录器或标准错误流中。这可以防止递归循环:
def emit(self, record):
try:
# ... your existing code for uploading logs ...
Except Exception as e:
# Log the error to a separate logger or standard error
error_logger = logging.getLogger('error_logger')
error_logger.error(f"Error logging to S3: {str(e)}")
不要单独上传每条日志消息,而是将它们累积在缓冲区中并定期上传到 S3。这减少了 API 调用次数并避免了递归:
class BufferedS3Handler(logging.Handler):
def __init__(self, bucket_name, key, buffer_size=10):
super().__init__()
self.bucket_name = bucket_name
self.key = key
self.buffer_size = buffer_size
self.buffer = []
self.s3_client = boto3.client('s3', ...)
def emit(self, record):
log_entry = self.format(record).encode('utf-8')
self.buffer.append(log_entry)
if len(self.buffer) >= self.buffer_size:
self.flush_buffer()
def flush_buffer(self):
if self.buffer:
# Upload the entire buffer content to S3 using put_object
all_logs = b'\n'.join(self.buffer)
self.s3_client.put_object(Bucket=self.bucket_name, Key=self.key, Body=all_logs)
self.buffer.clear()
aws-logging-handlers
等多个库可以简化 S3 日志记录。这些库处理缓冲、重试和其他功能:
from aws_logging_handlers.s3 import S3Handler
s3_handler = S3Handler(bucket="mybucketname", key="path/to/logfile.txt")
logging.getLogger().addHandler(s3_handler)
选择正确的解决方案:
选择最佳方法时,请考虑应用程序的日志量和所需的复杂性。