1. logging日志模块介绍
在我们进行代码编写过程中,会遇到一些需要输出信息的情况。为了便于调试、信息记录以及运行时出现异常情况时快速定位问题,Python提供了一个logging日志模块。该模块可以将信息打印在控制台以及记录在文件中。
1.1 日志等级
logging日志模块提供了6种日志级别,用来区分日志输出的重要性和优先级。从高到低依次是:CRITICAL、ERROR、WARNING、INFO、DEBUG、NOTSET。
import logging
logging.critical('这是critical级别的日志')
logging.error('这是error级别的日志')
logging.warning('这是warning级别的日志')
logging.info('这是info级别的日志')
logging.debug('这是debug级别的日志')
logging.notset('这是notset级别的日志')
上面的代码分别输出了不同级别的日志,但因为默认输出级别是WARNING,所以只有WARNING、ERROR、CRITICAL级别的日志被输出,其他的没有。
1.2 日志输出格式
logging模块允许我们自定义日志输出格式,包含日志级别、时间戳、信息内容等。下面是一个自定义输出格式的例子:
import logging
# 创建一个logger
logger = logging.getLogger('mylogger')
logger.setLevel(logging.DEBUG)
# 创建一个输出到控制台的处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
# 创建一个输出到文件的处理器
file_handler = logging.FileHandler('my.log')
file_handler.setLevel(logging.WARNING)
# 自定义日志输出格式
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
# 将处理器添加到logger中
logger.addHandler(console_handler)
logger.addHandler(file_handler)
# 输出日志
logger.debug('debug message')
logger.info('info message')
logger.warning('warning message')
logger.error('error message')
logger.critical('critical message')
在上面的例子中,我们自定义了日志的输出格式,包括时间戳、日志级别和信息内容。然后我们创建了一个输出到控制台的处理器和一个输出到文件的处理器,分别设置了输出级别和格式。最后,将处理器添加到logger中。
1.3 日志轮换
日志轮换是指当日志文件到达一定大小或者时间时,就会自动创建一个新的日志文件,将原先的日志文件进行备份保存。
logging模块提供了两种常见的日志轮换方式:根据文件大小轮换和根据时间轮换。
import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
# 根据文件大小轮换
rotate_handler = RotatingFileHandler('my.log', maxBytes=1024, backupCount=5)
# 根据时间轮换
time_rotate_handler = TimedRotatingFileHandler('my.log', when='H', interval=1, backupCount=5)
上面的代码分别创建了一个根据文件大小轮换的处理器和一个根据时间轮换的处理器。其中,maxBytes表示每个日志文件的最大大小,backupCount表示备份数量。
2. 多进程日志处理
如果我们在多进程运行的程序中使用logging模块,可能会遇到日志文件被多个进程写入的问题。这样会造成日志记录出错或者丢失,因为多个进程同时写同一个文件,会导致多个进程之间的日志内容重叠到一起。
所以,我们需要使用线程安全的日志处理器来处理多进程日志。
2.1 使用QueueHandler处理多进程日志
QueueHandler是logging模块提供的一种线程安全的处理器,它可以将日志消息放入队列中,然后由主进程负责将消息从队列中取出并写入到文件中。
import logging
import multiprocessing
from logging.handlers import QueueHandler, QueueListener
def worker_handler(queue):
handler = QueueHandler(queue)
logger = logging.getLogger('mylogger')
logger.addHandler(handler)
def main():
queue = multiprocessing.Queue(-1)
listener = QueueListener(queue, logging.FileHandler('my.log'))
listener.start()
p = multiprocessing.Process(target=worker_handler, args=(queue,))
p.start()
p.join()
listener.stop()
if __name__ == '__main__':
main()
在上面的代码中,我们在主进程中创建了一个QueueListener处理器,它会不断从队列中取出日志消息并写入到日志文件中。然后,我们创建了一个子进程,将日志消息通过QueueHandler处理后放入队列中。在程序运行过程中,子进程会不断往队列中放入日志消息,主进程则负责将队列中的日志消息写入到文件。
2.2 使用SocketServer处理多进程日志(推荐使用这种方式)
SocketServer是Python的一个标准库,它可以创建一个TCP服务器,并监听指定的端口,可以让多个进程通过网络连接共享日志。
我们可以在主进程中创建一个TCP服务器,然后将日志消息通过SocketHandler发送给主进程,主进程则负责将日志消息写入到文件。这种方式不需要使用共享内存或者队列,因此代码更加简洁。
import logging
import multiprocessing
import logging.handlers
import socketserver
class LogRecordStreamHandler(socketserver.StreamRequestHandler):
def handle(self):
while True:
try:
data = self.request.recv(1024)
if not data:
break
# 将日志消息转换为LogRecord对象,并写入日志文件
record = logging.makeLogRecord(data)
logger = logging.getLogger('mylogger')
logger.handle(record)
except Exception as e:
print(e)
def worker():
logger = logging.getLogger('mylogger')
logger.setLevel(logging.INFO)
handler = logging.handlers.SocketHandler('localhost', 9000)
logger.addHandler(handler)
for i in range(10):
logger.info(f'hello from {multiprocessing.current_process().name}')
def main():
tcp_server = socketserver.TCPServer(('localhost', 9000), LogRecordStreamHandler)
tcp_server.serve_forever()
if __name__ == '__main__':
p = multiprocessing.Process(target=main)
p.start()
pool = multiprocessing.Pool()
for i in range(10):
pool.apply_async(worker)
pool.close()
pool.join()
p.terminate()
在上面的代码中,我们创建了一个TCPServer服务器,并通过LogRecordStreamHandler处理器监听端口。当客户端连接上来后,从socket中读取日志消息,并转换为LogRecord对象后写入日志文件中。子进程中的worker函数会不断往日志处理器中写入日志消息,这些消息会通过socket发送给主进程,然后再写入到日志文件中。
3. 总结
logging模块是Python内置的一个标准库,可以方便地输出日志信息和记录错误信息。在多进程运行的程序中,我们需要使用线程安全的日志处理器来避免日志内容重叠的问题。
QueueHandler可以将日志消息放入队列中,然后由主进程负责将消息从队列中取出并写入到文件中。而SocketServer可以创建一个TCP服务器,并监听指定的端口,可以让多个进程通过网络连接共享日志。