以编程方式解析 journalctl 以启动操作

Programmaticaly parse journalctl to initiate an action

当我的 3d 打印机耗尽耗材时,它会暂停而不发送任何警告消息。我能找到的唯一条目是在 journalctl 上的一条消息,如下所示: Jan 22 04:36:39 ultimakersystem-flipflop_wsgi.py[2884]: INF - root:80 - PrintJobStatus updated! 6e408dcf-9887, paused

我绝对需要在打印机暂停时得到提醒,否则打印会被破坏。有没有办法以编程方式 poll/parse journalctl,例如通过 bash 或 python 脚本,并根据需要发送电子邮件?

可能还有其他比轮询更优雅的方法,但是 FWIW:

  1. journalctl可以time-filter其输出;例如查看最后 30 分钟 activity:
journalctl --since "30 min ago" --until "now"
  1. cron 可以 运行 不同时间间隔的作业;例如每 30 分钟 运行 以上 journalctl 作业,将此行放入您的 crontab:
0,30 * * * * /bin/journalctl --since "30 min ago" --until "now"
  1. 必须检查上面的 journalctl 输出是否与感兴趣的 日志条目 匹配。从您发布的错误消息中,我不知道 关键字 究竟是什么提示了一个空的灯丝箱,但我们现在假设它们是 ultimakersystempaused. awk 提供了一种可扩展的方式来查找匹配的单词和短语:当两个模式出现在 journalctl 输出的同一行时,awk '/pattern1/ && /pattern2/' 匹配,因此从 [=] 添加一个 pipe 14=]输出到awk;上面的 cron 作业修改如下:
0,30 * * * * /bin/journalctl --since "30 min ago" --until "now" | awk '/ultimakersystem/ && /paused/'
  1. 最后一步是测试 awk 是否找到匹配项,如果找到,则向指定用户发送消息。如果有一个或多个匹配,只需要发送一封电子邮件,因此匹配测试将在 END 部分进行。 cron 作业再次更新如下:
0,30 * * * * /bin/journalctl --since "30 min ago" --until "now" | awk '/ultimakersystem/ && /paused/ {m=1}; END { if (m == 1){system("cat warn.txt | mail -s FILAMENT_NEEDED $USER")} }'

awk 使用 pattern-action 范式; /ultimakersystem/ && /paused/ 模式 {m=1} 动作 awk 处理完所有 journalctl 输出后,执行 END 部分。一个简单的 if (m == 1) 测试确定是否出现一个或多个匹配项,如果出现,则调用 mail 命令发送 warn.txt 中定义的消息。 GNU awksystem 函数 REF 提供对系统命令库的访问。

在更改此食谱的许多其他可能性中,可以将 date-time 邮票 插入电子邮件正文,方法是将 cat warn.txt 替换为 date,并向 $USER 以外的人发送邮件。请参阅 man mail,或您的电子邮件客户端的文档。

我已经用 python 脚本解决了这个问题。我认为它比 cronjob 更有效,因为它会在找到关键字(在本例中为“暂停”一词)时做出反应,而无需轮询。

import systemd.journal
import socket
from time import sleep
import smtplib
from smtplib import SMTP

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'])
         alertmail(entry['MESSAGE'])

def alertmail(logEntry):
    # returns first occurrence of Substring
    trigger_text = 'pause'
    result = logEntry.find(trigger_text)
    if (logEntry.find(trigger_text) != -1):
        print ("Contains '" + trigger_text + "' at index:", result)

        fromaddr = "ultimaker@mymailstrasse.com"
        toaddrs  = "aag@bluewin.ch"
        msg = "ultimaker has paused!!! " + logEntry
        smtp = smtplib.SMTP('mail.mymailstrasse.com', port='587')
        smtp.ehlo()  # send the extended hello to our server
        smtp.starttls()  # tell server we want to communicate with TLS encryption
        smtp.login('ultimaker@mail.mymailstrasse.com', 'pw')  # login to our email server

        # send our email message 'msg' to our boss
        smtp.sendmail('ultimaker@mail.mymailstrasse.com',
                    'aag@bluewin.ch',
                    msg)
                    
        smtp.quit()  # finally, don't forget to close the connection
    else:
        print ("Doesn't contain given substring")

if __name__ == '__main__':
    main()