流日志抛出“调用 Python 对象时超出最大递归深度”错误。怎么处理?

问题描述 投票:0回答:1

我有一个 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 存储桶的方法并不理想。有哪些选项可以解决此问题?

python
1个回答
0
投票

您的代码的问题在于它如何处理每个日志消息。以下是发生递归错误的原因和替代解决方案:

理解递归错误:

  • 您的
    S3Handler.emit
    函数会格式化日志记录并将其编码为 UTF-8。
  • 这将创建一个新的字符串对象。
  • try
    块内,如果上传日志时出错,
    print
    语句会尝试格式化错误消息(可能包含原始日志记录)。
  • 这会再次触发对原始日志记录的格式化,导致循环并超出递归限制。

解决方案:

以下是解决此问题的几种方法:

  1. 单独记录错误:

不要在

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)}")
  1. 缓冲日志并定期上传:

不要单独上传每条日志消息,而是将它们累积在缓冲区中并定期上传到 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()
  1. 使用第三方库:

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)

选择正确的解决方案:

  • 选项 1 很简单,但对于大容量日志可能效率不高。
  • 选项 2 提供了性能和复杂性之间的平衡。
  • 选项 3 是最容易实现的,但添加了外部依赖项。

选择最佳方法时,请考虑应用程序的日志量和所需的复杂性。

© www.soinside.com 2019 - 2024. All rights reserved.