多进程使用 logging 把日志存储到不同文件的实践

2023-05-31 08:21:03 +08:00
 f1ynnv2

之前用 python 写单进程多线程的应用时,使用 logging 记录日志写到同一个文件很正常,最近写了个多进程多线程的应用使用 logging 记录日志到同一个文件时就开始出现问题了,主要就是基于时间的文件归档时会出 bug 。

简单搜了一下也找到了原因:logging 是线程安全但是进程不安全,官方推荐使用 queue 或者 socket 来实现多进程日志记录到同一个文件,或者自己修改 doRollOver 相关的操作。

我想问的问题是,我其实并没有上面“多进程存储日志到一个日志文件”的需求,相反我是希望把每个进程的日志单独存储为一个文件的,但是实践上感觉不太方便,以下是详情:

之前写单进程多线程时,只要在 logging 的封装模块里初始化一个全局的 logger ,其它所有模块不管是在哪个线程里,只要 import 一下这个 logger 就可以直接拿来用。 但是现在写多进程多线程的时候问题就出现了:

因为想问问大家,这个需求“多进程通过 logging 写日志到各自文件”是否有更优雅的实践?如果没的话,最后也只能回归官方通过 socket/queue 的方案来写单个文件了。

3896 次点击
所在节点    Python
35 条回复
Three2
2023-05-31 08:28:35 +08:00
每个进程的日志名字不同不就完事了
f1ynnv2
2023-05-31 08:45:06 +08:00
@Three2 #1 可能我没表达清楚,我最后的问题其实就是“如何优雅的实现每个进程使用不同日志名字”。目前只想到这一个办法,但是不够优雅:

-------

每个进程的启动函数里初始化一个专属的 logger ,指定不同的存储文件,然后通过参数的方式传递给调用的所有模块 /函数,让这些模块 /函数直接使用这个 logger 。技术上可行,但是实践上非常不优雅,调用的每个子模块都要传递这个 logger 参数。
HFcbyqP0iVO5KM05
2023-05-31 08:48:06 +08:00
全局的 logger 定义为 None ,第一次用的时候再初始化它。
feihuaxx010
2023-05-31 08:57:24 +08:00
试试 concurrent-log-handler
lucifer69
2023-05-31 09:12:00 +08:00
我也遇到过这个问题,是按你想到的另一个方案做的。import 封装的 logger 模块,里面有个方法是 get_logger ,要使用的地方传递 logger 名字,模块里面有同名就复用,没同名就再初始化一个。用起来确实麻烦
encro
2023-05-31 09:15:02 +08:00
chatgpt:

在多进程程序中,为每个进程创建不同的 logger ,并将其 log 输出到不同的文件中。例如:

```
from multiprocessing import Process
import logging

def process1():
logger = logging.getLogger('process1')
logger.setLevel(logging.DEBUG)
# 创建文件处理器
file_handler = logging.FileHandler('process1.log')
file_handler.setLevel(logging.DEBUG)
# 设置格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
# 将文件处理器添加到 logger
logger.addHandler(file_handler)

logger.debug('Process 1 started')

def process2():
logger = logging.getLogger('process2')
logger.setLevel(logging.DEBUG)
# 创建文件处理器
file_handler = logging.FileHandler('process2.log')
file_handler.setLevel(logging.DEBUG)
# 设置格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
# 将文件处理器添加到 logger
logger.addHandler(file_handler)

logger.debug('Process 2 started')

if __name__ == '__main__':
p1 = Process(target=process1)
p2 = Process(target=process2)
p1.start()
p2.start()
p1.join()
p2.join()
```
encro
2023-05-31 09:17:50 +08:00
简单来说可以定一个 getlogger 方法,传递线程 id 进去,让后在方法里面调用 logging.FileHandler(线程 id.log)写到不同文件
sujin190
2023-05-31 09:25:33 +08:00
如果你日志是有时许的,每个进程写单独文件,看日志的时候还不累死了,不就是时间的文件归档有问题么,修改下 doRollOver 加个锁就是了呗,也不是啥麻烦的事情
sujin190
2023-05-31 09:30:41 +08:00
https://gist.github.com/snower/adcf300f3daff99549dbe1949982a5dc

我们项目就是重写了 doRollOver ,使用文件锁来处理,这样就算多个进程是独立创建的也没有问题,只是似乎不能在 windows 上用,而且这个函数只有在需要重新创建日志文件的时候才会调用,正常写日志的时候不会有影响,所以也没啥性能问题
f1ynnv2
2023-05-31 09:32:25 +08:00
@sujin190 #8 其实多进程下,不仅仅是按时间归档的问题,如果是高并发的时候,写日志到一个文件也有小概率出现日志混乱的问题,尤其是日志内容比较大的情况。
todd7zhang
2023-05-31 09:42:02 +08:00
子进程启动的地方,先 init_logger(), 这里面只需要设置 root logger 的 file handler 。然后其他地方使用各自 py 文件下的 logger 啊
BingoXuan
2023-05-31 09:43:20 +08:00
多进程模式下,我是单独开一个 daemon 进程保存 log 的,用 zmq 的 pub 和 sub 模式来通信
sujin190
2023-05-31 09:44:22 +08:00
@f1ynnv2 #10 确定不是写的问题,我们十多个进程每天写入数十 G 的时候没发现有错行的问题,也这样运行好多年了,没发现啥异常
todd7zhang
2023-05-31 09:46:20 +08:00
```python
# example.py
import logging
import multiprocessing
import os

_logger = logging.getLogger(__name__)

def init_logging():
# create logger
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)

# create file handler
log_file = 'my_log_{}.log'.format(os.getpid())
file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.DEBUG)

# create formatter
formatter = logging.Formatter('%(asctime)s - %(process)d - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)

# add file handler to logger
root_logger.addHandler(file_handler)



def start():
init_logging()
from . import file1, file2
file1.say_hello()
file2.say_hello()


def main():
processes = []
for i in range(4):
p = multiprocessing.Process(target=start)
processes.append(p)
p.start()

for p in processes:
p.join()

if __name__ == '__main__':
main()


# file1.py
import logging
_logger = logging.getLogger(__name__)

def say_hello():
_logger.info("this is hello message from file1.py")


# file2.py
import logging
_logger = logging.getLogger(__name__)

def say_hello():
_logger.info("this is hello message from file2.py")

```
f1ynnv2
2023-05-31 09:46:48 +08:00
@sujin190 #9 感谢,我来研究一下。
Trim21
2023-05-31 09:48:04 +08:00
loguru
lolizeppelin
2023-05-31 09:51:07 +08:00
linux 下 append 写入,是原子性,多进程安全的
你的问题应该不是多进程的安全问题

基于时间的文件归档有问题的话,多半是你们滚日志的时候只有主进程做了重新打开日志文件的操作

正确流程是发信号到进程组,所有进程组内所有进程都重新打开日志文件
todd7zhang
2023-05-31 10:01:29 +08:00
todd7zhang
2023-05-31 10:10:32 +08:00
我认为,日志滚动不应该让 python 来做,python 就直接 logging 到 stdout 。
外面用 supervisor 来守护,同时 supervisor 来收集日志到文件,最后由 logrotate 做每天的日志滚动。
xyjincan
2023-05-31 10:14:42 +08:00
写日志用追加模式,linux 中,底层追加模式写文件用的是系统调用,具有原子性

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/944380

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX