多进程使用 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 的方案来写单个文件了。

3837 次点击
所在节点    Python
35 条回复
g5tf87
2023-05-31 11:00:18 +08:00
lolizeppelin xyjincan 说的是对的,但是要注意原子性也是有大小限制的,比如某些系统 4k 的 append 是原子。
可以参考 nginx 的实现方式,append+信号通知重新打开文件
billzhuang
2023-05-31 11:08:03 +08:00
写到 stdout 或 stderr ,然后用 flunetd 来收集。
xuanbg
2023-05-31 11:09:23 +08:00
什么年代了,还在代码中写日志到文件。。。搞一套 ELK 之类的日志平台又不费什么事。
NoOneNoBody
2023-05-31 11:20:24 +08:00
?
怎么我就没读明白需求呢
不同进程不同 log file 这个是明白了,然后是不希望每个进程都实例 化一个 logger ?只使用一个全局 logger 对象?

每进程一个 log 实例是最清晰的,因为消息也是分开的,没必要全局一起处理,除非需求是按级别,如 error 为全局消息

如果必须需要全局单实例,可以把 output 方法以参数分开就可以了,没必要初始化就设定 output 路径
“优雅一点”,就用别名方法 alias ,用 funtools.partical 或闭包把参数带上
nicebird
2023-05-31 11:26:34 +08:00
日志文件名字 增加进程名 & 进程 id
Nem0
2023-05-31 11:36:24 +08:00
我用的 loguru 中的 filter ,不同文件的 logger 有不同的日志头,以此写入不同文件
f1ynnv2
2023-05-31 11:39:07 +08:00
@NoOneNoBody #24 每个进程初始化一个 logger 没问题,但是某个进程的调用的所有模块如何使用这个进程独立的 logger 就是个问题了,要么通过传参的方式把 logger 传给子函数,要么在每个子函数里重新获取一下 logger ,这不就没有单进程多线程那种模块头部 import ,全模块随便用的方便了嘛。
f1ynnv2
2023-05-31 11:41:17 +08:00
@sujin190 #9 特意来感谢一下,最后还是修改了 doRollOver 这种方案,最终能实现多进程多线程与单进程多线程相同的使用方式:每个模块 import 一下封装好的 logger 模块里初始化好的全局 logger ,不管在哪个进程的线程里都可以直接使用 loggger.
ruanimal
2023-05-31 11:45:35 +08:00
又是 x-y 问题,你直接打到一个文件不就行了吗, 看 #4 的 concurrent-log-handler 库就行了
f1ynnv2
2023-05-31 11:47:53 +08:00
@todd7zhang #14 这个方案是给每个模块分配一个日志文件,确实可行
NoOneNoBody
2023-05-31 12:14:25 +08:00
@f1ynnv2 #27
所以我说没读明白

from mylog import Logger

def worker(logpath):
worker_log = Logger(savepath=logpath)
...

进程入口就是 worker ,传参一次就可以了,进程内其他什么都是调用 worker_log ,不需要再传参数了,这事应该不涉及进程安全,只有跨进程的才需要考虑进程安全

你简单说说某个子模块是如何调用 logger 的,根据#28 ,我觉得单个进程内只是 namespace 的问题
f1ynnv2
2023-05-31 13:36:51 +08:00
@NoOneNoBody #31 某个模块是可以被多个进程调用的,实际上在某个模块由于和进程入口不在同一个文件里,所以必然不是同一个 namespace ,要么通过传参的方式把 logger 传起来,要么在子模块的函数里重新初始一次 logger ,还是麻烦哦。
todd7zhang
2023-05-31 14:45:58 +08:00
@f1ynnv2 看完你的回复之后,我感觉你对 python 的 logger 理解有误。
每个 py 文件都应该有自己 logger, 每个 logger 不需要自己来输出到文件,你不需要也不应该从其他模块导入一个的 logger, 它最后都会把 log record propagate 到 root logger
你只要对 root logger 配置 handler 和 formatter 就行。
然后,我的代码是给每个子进程配置了他们 root logger ,file1.pyfile2.py 并没有额外指定日志输出文件。
f1ynnv2
2023-05-31 15:11:31 +08:00
@todd7zhang #33 按照你的代码来说的话, 确实。在整个 app 入口处初始一个 root logger ,然后每个模块里获取一下自己的 logger ,这个 logger 会使用 root logger 的配置。虽然不是我想要的那种每个模块 import 一下就直接用,但是你这种方法也挺简洁。如果没 4 楼和 9 楼的方案,确实可以用你的这种方案。
ClericPy
2023-05-31 22:11:29 +08:00
去年用系统自带的 logging 里的 Socket handler 做客户端, asyncio 自带的 Socket server + reuse_port 做服务端做了个多核日志服务, 分布式打过来的日志能扛的并发让我惊讶... asyncio 没加 uvloop 就超顶了, 切到 Python3.11 和 uvloop, 性能高的离谱...

服务端多核通信没走跨进程队列, 走的 asyncio 自带的 Unix domain Socket, 以前一直以为会扛不住, 结果才三个 CPU 就扛住了几万并发, CPU 使用率才 30% 多... 过分低估 Python 在 IO 密集时候的实力了, 连 pypy 都没上呢

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

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

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

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

© 2021 V2EX