【问题标题】:Reading systemd journal from Python script从 Python 脚本中读取 systemd 日志
【发布时间】:2014-12-07 11:51:39
【问题描述】:

我正在尝试使用 systemd 库在 Python 中模拟这个 shell 命令 http://www.freedesktop.org/software/systemd/python-systemd/journal.html

我实际上是在尝试在 Python 中模拟此命令。

journalctl --since=-5m --no-pager

我看到其他人在 Python 中通过调用日志可执行文件来执行此操作,但这是一种非常糟糕的方式。

我根据上面链接的文档编写了这个简单的脚本

import select
from systemd import journal

j = journal.Reader()
j.log_level(journal.LOG_INFO)
# j.add_match(_SYSTEMD_UNIT="systemd-udevd.service")

j.seek_tail()
j.get_next()

while j.get_next():
    for entry in j:
        if entry['MESSAGE'] != "":
            print(str(entry['__REALTIME_TIMESTAMP'] )+ ' ' + entry['MESSAGE'])

这里有几个问题

  1. 日志似乎从大约 5 天前开始,这意味着 seek_tail 似乎没有工作。
  2. 我在这里收到了很多垃圾,我应该使用一个特定的过滤器来匹配我从问题开头给出的 journalctl 命令中获得的数据吗?

理想情况下,从长远来看,我只想根据一组过滤器/匹配项来关注该期刊,以模拟命令“journalctl -f”,但我只需要先解决这个问题。我想以这样的方式结束,但它也不起作用。

import select
from systemd import journal

j = journal.Reader()
j.log_level(journal.LOG_INFO)

# j.add_match(_SYSTEMD_UNIT="systemd-udevd.service")
j.seek_tail()

p = select.poll()
p.register(j, j.get_events())
while p.poll():

    while j.get_next():
        for entry in j:
            if entry['MESSAGE'] != "":
                print(str(entry['__REALTIME_TIMESTAMP'] )+ ' ' + entry['MESSAGE'])

【问题讨论】:

    标签: python systemd journal


    【解决方案1】:

    我也在研究类似的 python 模块。

    根据以下链接,我们必须调用sd_journal_previous(在python systemd模块中,即journal.Reader().get_previous())。

    http://www.freedesktop.org/software/systemd/man/sd_journal_seek_tail.html

    https://bugs.freedesktop.org/show_bug.cgi?id=64614

    此外,您的示例代码将消耗 80 - 100% 的 CPU 负载,因为即使在获取条目后阅读器的状态仍保持“可读”,这会导致 poll() 过多。

    根据以下链接,似乎我们必须在每个poll()之后调用sd_journal_process(在python systemd模块中,即journal.Reader().process()),以重置可读状态文件描述符。

    http://www.freedesktop.org/software/systemd/man/sd_journal_get_events.html

    总之,您的示例代码将是:

    import select
    from systemd import journal
    
    j = journal.Reader()
    j.log_level(journal.LOG_INFO)
    
    # j.add_match(_SYSTEMD_UNIT="systemd-udevd.service")
    j.seek_tail()
    j.get_previous()
    # j.get_next() # it seems this is not necessary.
    
    p = select.poll()
    p.register(j, j.get_events())
    
    while p.poll():
        if j.process() != journal.APPEND:
            continue
    
        # Your example code has too many get_next() (i.e, "while j.get_next()" and "for event in j") which cause skipping entry.
        # Since each iteration of a journal.Reader() object is equal to "get_next()", just do simple iteration.
        for entry in j:
            if entry['MESSAGE'] != "":
                print(str(entry['__REALTIME_TIMESTAMP'] )+ ' ' + entry['MESSAGE'])
    

    【讨论】:

    • 非常感谢它似乎可以工作。我确实怀疑也许 seek_tail 后跟 get_events 可能会导致它循环回到开头。我愚蠢地从未想过尝试 get_previous 然后 get_next。现在要弄乱日志级别,因为命令 journalctl -f 中的信息比使用 loglevel.INFO 时要多
    • 即使 loglevel.DEBUG 显示少于命令 'journalctl -f' 的东西,例如启动和停止服务,python 脚本似乎也没有捕捉到。
    • 没关系,我没有以 root 身份运行脚本。呃!!!感谢您的帮助,它现在可以正常工作了。
    • 我发现“j.get_next()”(在 j.get_previous() 之后)似乎没有必要。
    • 有一个方法reliable_fd()。是否需要在轮询之前调用?好吧,我不知道在什么情况下它可能是假的。当我调用它时,它返回 True。也许它不适用于非持久性期刊?
    【解决方案2】:

    前面的答案有效,关于在 seek_tail() 之后调用 get_next() 的提示确实很重要。

    一种更简单的方法是(但显然使用轮询的先前版本在更大的应用程序中更灵活)

    import systemd.journal
    
    def main():
      j = systemd.journal.Reader()
      j.seek_tail()
      j.get_previous()
      while True:
        event = j.wait(-1)
        if event == systemd.journal.APPEND:
          for entry in j:
             print entry['MESSAGE']
    
    if __name__ == '__main__':
      main()
    

    如果想详细了解正在发生的事情,支持调试输出的以下版本可能会有所帮助(使用一些参数调用它会打开它)

    import sys
    import systemd.journal
    
    def main(debug):
      j = systemd.journal.Reader()
      j.seek_tail()
      j.get_previous()
      while True:
        event = j.wait(-1)
        if event == systemd.journal.APPEND:
          for entry in j:
             print entry['MESSAGE']
        elif debug and event == systemd.journal.NOP:
          print "DEBUG: NOP"
        elif debug and event == systemd.journal.INVALIDATE:
          print "DEBUG: INVALIDATE"
        elif debug:
          raise ValueError, event   
    
    if __name__ == '__main__':
      main(len(sys.argv) > 1)
    

    对我来说,它总是在开始时导致一个 INVALIDATE。不确定这到底意味着什么。让用作迭代器的东西失效对我来说,我可能会以某种方式重新创建/重新打开/刷新它。但至少在我的基本测试期间,上面的代码可以正常工作。不确定是否有任何竞争条件。实际上,我很难解释该代码如何没有种族。

    【讨论】:

    • 不要丢弃带有 INVALIDATE 的事件。它只是一个标志,告诉客户端清除/刷新其显示,而不是附加新消息。
    • @leoluk 你能解释一下你的意思 a.) 客户端 b.) 清除刷新它的显示吗?所有之前显示的事件都无效?在什么情况下会发生这种情况?有一段时间没有使用它了,但是在我上面的回答中,INVALIDATE 总是出现并且仅作为第一个事件出现。所以在那种情况下,就不会真正清除/刷新。除非程序仍然显示来自不同机器或先前启动的某些内容,否则混合来自不同来源的输入只是愚蠢的实现。新的消息来源不需要告诉我,忘记以前的。
    • 是的,据我所知,清除所有事件:“如果 SD_JOURNAL_INVALIDATE,日志文件被添加或删除(可能是由于旋转)。在后一种事件中,实时取景 UI 可能应该刷新它们的整个显示”,见freedesktop.org/software/systemd/man/…
    • 我偶然发现了一件事。在event = j.wait(-1) 中,event 是一个integer corresponding to a state change。我首先认为它包含一个事件,也就是一个日志记录行。将event 视为state_change 可能不会那么令人困惑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-09-13
    • 1970-01-01
    • 1970-01-01
    • 2019-07-26
    • 2013-10-08
    • 1970-01-01
    • 2021-10-24
    相关资源
    最近更新 更多