Чтение журнала systemd из скрипта Python

Я пытаюсь эмулировать эту команду оболочки в Python, используя библиотеки systemd 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'])

person phoenixdigital    schedule 12.10.2014    source источник


Ответы (2)


Я также работаю над аналогичным модулем 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 % нагрузки ЦП, потому что состояние читателя остается доступным для чтения даже после получения записи, что приводит к слишком большому количеству poll().

Согласно следующей ссылке, кажется, что мы должны вызывать sd_journal_process (в модуле python systemd, то есть journal.Reader().process()) после каждого poll(), чтобы сбросить читаемое состояние файлового дескриптора.

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'])
person takaomag    schedule 14.10.2014
comment
Это отлично, спасибо за то, что это работает. Я подозревал, что, возможно, seek_tail, за которым следует get_events, может привести к возврату к началу. Я по глупости никогда не думал попробовать get_previous, а затем get_next. Теперь, чтобы возиться с уровнем журнала, как кажется, в команде journalctl -f больше информации, чем при использовании loglevel.INFO - person phoenixdigital; 16.10.2014
comment
даже loglevel.DEBUG показывает меньше, чем команда «journalctl -f», такие вещи, как запуск и остановка служб, похоже, не перехватываются сценарием python. - person phoenixdigital; 16.10.2014
comment
Неважно, я не запускал скрипт как root. ДУХ!!! Спасибо за помощь в этом, теперь он работает отлично. - person phoenixdigital; 16.10.2014
comment
Я обнаружил, что j.get_next() (после j.get_previous()) не нужен. - person takaomag; 16.10.2014
comment
Есть способ reliable_fd(). Что нужно вызывать перед опросом? Ну, я не знаю, при каких обстоятельствах это может быть False. Когда я назвал это, он вернул True. Может быть, это не работает на непостоянных журналах? - person Uwe Geuder; 17.02.2015
comment
Все работает нормально, за исключением первого раза при вызове j.process() после появления первого события. Здесь идентификатор события journal.INVALIDATE вместо journal.APPEND. Таким образом, первое событие не будет напечатано, потому что вызывается continue. Просто в следующий раз печатаются два первых события после которых все работает нормально и как положено. - person Jan; 30.08.2019

Предыдущий ответ работает, и подсказка о вызове get_next() после seek_tail() действительно важна.

Проще было бы (но очевидно, что предыдущая версия, использующая опрос, более гибкая в больших приложениях)

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 в начале. Не уверен, что это на самом деле означает. То, что что-то, что используется в качестве итератора, недействительно, звучит для меня так, что я мог бы каким-то образом воссоздать/повторно открыть/обновить его. Но, по крайней мере, во время моего базового тестирования приведенный выше код просто работает. Не уверен, что могут быть какие-то условия гонки. На самом деле мне было бы трудно объяснить, как этот код свободен от рас.

person Uwe Geuder    schedule 17.02.2015
comment
Не отбрасывайте события с INVALIDATE. Это просто флаг, который говорит клиенту очистить/обновить его отображение, а не добавлять новые сообщения. - person leoluk; 02.01.2016
comment
@leoluk Можете ли вы объяснить, что вы подразумеваете под а.) клиентом б.) четким обновлением его дисплея? Все ранее показанные события недействительны? В каком контексте это могло произойти? Не использовал его какое-то время, но в моем ответе выше INVALIDATE всегда и только как первое событие. Так что в таком случае не было бы толком очищать/обновлять. Разве что программа еще показывала что-то с другой машины или предыдущую загрузку, но это было бы просто глупой реализацией смешивать ввод из разных источников. Новый источник мне рассказывать не надо, про предыдущие забудьте. - person Uwe Geuder; 08.01.2016
comment
Да, насколько я понял, очистить все события: Если SD_JOURNAL_INVALIDATE, файлы журнала были добавлены или удалены (возможно, из-за ротации). В последнем случае пользовательский интерфейс для просмотра в реальном времени, вероятно, должен обновить весь свой дисплей, см. freedesktop.org/software/systemd/man/ - person leoluk; 09.01.2016