Python 异步有什么办法实现类似 Linux 的 tail -f 的功能吗? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
LeeReamond
V2EX    Python

Python 异步有什么办法实现类似 Linux 的 tail -f 的功能吗?

  •  
  •   LeeReamond 2021-12-07 17:44:49 +08:00 3032 次点击
    这是一个创建于 1480 天前的主题,其中的信息可能已经有所发展或是发生改变。

    需求:web 服务里需要监测日志,服务是异步的,理论上最好用 ws 的方式传输更新数据,而不要让前端轮询。

    linux 的 shell 下用 tail -f 可以完美满足需求,但是 shell 登录太麻烦了。

    想要实现的效果是,给文件一个钩子,把它像流一样处理,文件不变的时候就 await 阻塞住,文件更新的话则得到 await 返回,返回的内容就是新增的内容(姑且目前认为只会新增)

    类似于下面这样的伪代码这种感觉

    app = Framework() @app.ws('/ws/log-tail') async def client_async_log(ws, file_name): async with ws.connect() as conn: file = Tail(file_name) while True: string = await file.update_content() await conn.send(string) 

    不知道有没有方式实现。按照我的想法似乎以前倒是用过类似 watchdog 的服务,文件更新后可以得到一个异步回调,但是只知道更新了,不知道更新了哪些内容啊

    Lotussha
        1
    Lotussha  
       2021-12-07 17:50:40 +08:00
    刚好我也需要这个功能
    fgwmlhdkkkw
        2
    fgwmlhdkkkw  
       2021-12-07 17:55:17 +08:00
    socket af_unix
    可以吗?我没试过
    hsfzxjy
        3
    hsfzxjy  
       2021-12-07 17:55:57 +08:00 via Android
    defunct9
        4
    defunct9  
       2021-12-07 17:55:58 +08:00
    没有
    2i2Re2PLMaDnghL
        5
    2i2Re2PLMaDnghL  
       2021-12-07 18:45:34 +08:00   1
    基于文件系统的 notify 也是只知道更新了内容,不知道更新哪些内容。
    tail.c 里似乎也是自行判断的,甚至 regular file 还可能会变小(提示 file truncated ),判断这种情况的代码处打上了这样的注释(有两处,略微不同):
    ```
    /* XXX: This is only a heuristic, as the file may have also
    been truncated and written to if st_size >= size
    (in which case we ignore new data <= size).
    Though in the inotify case it's more likely we'll get
    separate events for truncate() and write(). */
    ```

    所以实现方法就还是 inotify 之类的,然后再把它包装成你需要的样子。
    qieqie
        6
    qieqie  
       2021-12-07 19:06:14 +08:00
    只考虑追加写的话,先 lseek 再一直 read 就行了(结合 notify ),os 会负责记住这个 fd 的 offset 的
    akira
        7
    akira  
       2021-12-07 19:21:00 +08:00
    看看 filebeat 之类的是不是你要的
    jaredyam
        8
    jaredyam  
       2021-12-07 20:03:31 +08:00
    Tailing a File A Python version of 'tail -f'
    import time
    import os
    def follow(thefile):
    thefile.seek(0, os.SEEK_END) # End-of-file
    while True:
    line = thefile.readline()
    if not line:
    time.sleep(0.1) # Sleep briefly
    continue
    yield line
    Idea : Seek to the end of the file and repeatedly try to read new lines. If new data is written to the file, we'll pick it up.

    Copyright (C) 2018, http://www.dabeaz.com Example File: follow.py
    iyaozhen
        9
    iyaozhen  
       2021-12-07 20:17:41 +08:00
    之前写过一个,给 tail -f 套了个 epoll 的壳
    https://github.com/iyaozhen/filebeat.py
    37Y37
        10
    37Y37  
       2021-12-07 20:55:53 +08:00
    LeeReamond
        11
    LeeReamond  
    OP
       2021-12-07 21:29:54 +08:00
    @iyaozhen
    @Lotussha
    @2i2Re2PLMaDnghL
    @qieqie
    @jaredyam 感谢楼上老哥回复,看了看代码,似乎核心代码是 popen 然后依赖于 popen.stdout 搞些事情。我自己试了试这个 fd 是可以 while True: p.stdout.readline()的,最简单的套个线程转协程就行了。。楼上老哥说也可以 epoll
    LeeReamond
        12
    LeeReamond  
    OP
       2021-12-07 21:37:22 +08:00
    @iyaozhen 老哥我看你那个代码里,tail -f filename comment ,为啥把 comment 加在这个位置就可以显示在进程信息里,我在本地的 shell 里试了试不好使。还有你的 popen 设的 buffsize=-1 是干啥用的
    iyaozhen
        13
    iyaozhen  
       2021-12-07 23:16:39 +08:00
    @LeeReamond 额,那个其实语法就是 tail -f 两个文件,第二个肯定不存在,但进程里面可以看见命令信息,防止运维给 kill 了

    默认就是-1
    negative bufsize (the default) means the system default of io.DEFAULT_BUFFER_SIZE will be used.
    so1n
        14
    so1n  
       2021-12-07 23:32:08 +08:00
    aionotify 可以很好的解决
    ClericPy
        15
    ClericPy  
       2021-12-08 01:08:11 +08:00
    以前用过 Python 的第三方库

    不过既然你都提到 tail -f 了, 为啥不用 tail -F 呢, 分分钟写个脚本给你管道符转发出去就行了

    tail -F xxx.log | python3 serv.py

    看完是不是感觉很无聊, 因为 aws 挂了我特么在加班找问题...
    Richard14
        16
    Richard14  
       2021-12-08 01:15:25 +08:00
    https://gist.github.com/RedmondLee/e92341616a020fbe1fed85903a264efc

    试着写了个最小实现,用另一个线程封装 selector ,每次流更新后提醒主线程的事件循环去获取内容(因为 EVENT_READ 已经准备好,read 可以认为是非阻塞的)。

    但是最后结果还是不太对,主线程的 call_soon 函数虽然正常执行,但是其过程中的 future.set_result(None)并不能触发主事件循环中 await 这个 future 的函数返回。或者说偶尔能触发,偶尔不能触发,搞不太清楚为什么。
    qW7bo2FbzbC0
        17
    qW7bo2FbzbC0  
       2021-12-10 14:40:06 +08:00
    1.没有读取位置信息时,从开始,或者倒数 X KB 处读取,有上次读取位置信息时接着读
    2.读到文件结尾时自动结束,并记录位置信息,等待间隔后,再次读取位置信息接着读
    3.甚至读取前调用 stat 记录文件 inode 信息,inode 变化后,重置位置信息,从零(开头)开始读

    考虑到大日志文件的性能问题,我使用了 go 进行实现
    考虑到大量数据传输,与解耦问题,我使用了 kafka 进行中转
    考虑到超长行问题,我使用了 kafka 的压缩算法 ==> cpu 和内存占用略高

    然后就是,需要定义行切割和碎片行合并问题
    fighterhit
        18
    fighterhit  
       2021-12-12 02:29:46 +08:00
    无意间看到有个 golang 版的 tail -f 实现:github.com/hpcloud/tail ,可以参考下
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2582 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 14:53 PVG 22:53 LAX 06:53 JFK 09:53
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86