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 · 2503 次点击
    这是一个创建于 1113 天前的主题,其中的信息可能已经有所发展或是发生改变。

    需求: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   ·   实用小工具   ·   5371 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 06:44 · PVG 14:44 · LAX 22:44 · JFK 01:44
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.