如何让Python logging在Syslog宕机时不中断执行、持久化重试并缓冲日志
嘿,这个问题我太熟悉了!默认的SysLogHandler在TCP模式下确实会因为Syslog服务器宕机抛出异常,直接搞崩你的脚本——毕竟它没内置重试和缓冲机制。不过不用慌,我们有几种靠谱的解决方案,不用给每一行logger都套try/except!
方案1:用logging内置组件实现异步缓冲+重试(推荐)
Python的logging模块本身就提供了QueueHandler和QueueListener,可以把日志发送逻辑放到后台线程里,彻底隔离主线程和日志发送的异常。配合一个带重试逻辑的SysLogHandler,就能实现你要的“不中断脚本+自动重试+日志缓冲”效果。
具体步骤和代码示例:
import logging import queue from logging.handlers import SysLogHandler, QueueHandler, QueueListener import socket import time def create_retrying_syslog_handler(syslog_address): # 实现自动重试连接的逻辑 def _retry_until_connected(): while True: try: # 尝试创建SysLogHandler并连接 handler = SysLogHandler(address=syslog_address, socktype=socket.SOCK_STREAM) print(f"成功连接到Syslog服务器 {syslog_address}") return handler except (socket.error, ConnectionRefusedError): print(f"无法连接到Syslog服务器 {syslog_address},10秒后重试...") time.sleep(10) # 初始化带重试的handler handler = _retry_until_connected() # 重写emit方法,处理发送时的异常 original_emit = handler.emit def wrapped_emit(record): try: original_emit(record) except (socket.error, ConnectionRefusedError): print(f"发送日志到Syslog失败,重新连接服务器...") nonlocal handler # 重新连接后重试发送当前日志 handler = _retry_until_connected() handler.emit(record) handler.emit = wrapped_emit return handler # 配置日志系统 logger = logging.getLogger() logger.setLevel(logging.INFO) # 创建日志队列,主线程把日志丢到队列里,不阻塞 log_queue = queue.Queue(-1) queue_handler = QueueHandler(log_queue) logger.addHandler(queue_handler) # 替换成你的Syslog服务器地址 syslog_address = ("your-syslog-server-ip", 514) # 创建带重试的Syslog handler syslog_handler = create_retrying_syslog_handler(syslog_address) # 启动QueueListener,后台线程处理队列里的日志 listener = QueueListener(log_queue, syslog_handler) listener.start() # 测试代码——即使Syslog宕机,主线程也不会崩溃 try: count = 0 while True: count += 1 logger.info(f"这是第 {count} 条测试日志,即使Syslog挂了也能继续运行!") time.sleep(1) except KeyboardInterrupt: # 程序退出时停止listener listener.stop()
这个方案的优势:
- 主线程完全不阻塞,日志发送逻辑在后台线程执行
- 自动重试连接和日志发送,无需手动干预
- 用的都是logging内置组件,不需要额外安装第三方库
方案2:自定义SysLogHandler实现同步缓冲与重试
如果你不想用线程,也可以直接继承SysLogHandler,重写emit方法,自己实现连接重试和日志缓冲逻辑:
import logging from logging.handlers import SysLogHandler import socket import time from collections import deque class RetryingSysLogHandler(SysLogHandler): def __init__(self, address, socktype=socket.SOCK_STREAM, buffer_max_size=1000, retry_interval=10): super().__init__(address=address, socktype=socktype) # 日志缓冲队列,满了会自动丢弃最早的日志 self.log_buffer = deque(maxlen=buffer_max_size) self.retry_interval = retry_interval self.is_connected = False # 初始化连接 self._connect() def _connect(self): """不断重试直到连接成功,同时发送缓冲的日志""" while not self.is_connected: try: # 关闭旧连接(如果有的话) self.close() self.socket = self.makeSocket() self.is_connected = True print(f"成功连接到Syslog服务器 {self.address}") # 发送缓冲中积压的日志 while self.log_buffer: record = self.log_buffer.popleft() super().emit(record) except (socket.error, ConnectionRefusedError): print(f"连接Syslog失败,{self.retry_interval}秒后重试...") time.sleep(self.retry_interval) def emit(self, record): """重写emit方法,处理发送异常和缓冲""" if not self.is_connected: self._connect() try: super().emit(record) except (socket.error, ConnectionRefusedError): self.is_connected = False # 将当前日志加入缓冲 if len(self.log_buffer) < self.log_buffer.maxlen: self.log_buffer.append(record) print(f"日志发送失败,已加入缓冲(当前缓冲数量:{len(self.log_buffer)})") else: print(f"日志缓冲已满,丢弃日志:{record.getMessage()}") # 重新尝试连接 self._connect() # 使用示例 logger = logging.getLogger() logger.setLevel(logging.INFO) # 替换成你的Syslog服务器地址 handler = RetryingSysLogHandler(address=("your-syslog-server-ip", 514), socktype=socket.SOCK_STREAM) logger.addHandler(handler) # 测试 try: count = 0 while True: count += 1 logger.info(f"自定义Handler测试日志:第 {count} 条") time.sleep(1) except KeyboardInterrupt: handler.close()
这个方案的特点是逻辑更集中,所有重试和缓冲都在Handler内部处理,适合不需要异步的场景。
总结
- 优先推荐方案1,异步处理能避免日志发送阻塞主线程,尤其适合高并发或者对响应速度有要求的脚本
- 如果你的场景不需要异步,方案2的自定义Handler也能完美解决问题
- 两种方案都不需要修改业务代码里的
logger语句,只要配置好Handler就行
内容的提问来源于stack exchange,提问作者Luis Díaz
相关产品推荐
相关产品推荐

