systemd journal file: PHP parsing library

systemd, a popular user-space system/service management suite on Linux, offers logging functionality, storing incoming logs in a binary journal format.

On live Linux system running systemd, these journals are typically located at:

  • /run/log/journal/machine-id/*.journal (volatile, lost after reboot)
  • /var/log/journal/machine-id/*.journal (persistent, but disabled by default on Debian / Ubuntu)

File extension

journal

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of systemd journal file using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

PHP source code to parse systemd journal file

SystemdJournal.php

<?php
// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

/**
 * systemd, a popular user-space system/service management suite on Linux,
 * offers logging functionality, storing incoming logs in a binary journal
 * format.
 * 
 * On live Linux system running systemd, these journals are typically located at:
 * 
 * * /run/log/journal/machine-id/*.journal (volatile, lost after reboot)
 * * /var/log/journal/machine-id/*.journal (persistent, but disabled by default on Debian / Ubuntu)
 */

namespace {
    class SystemdJournal extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Kaitai\Struct\Struct $_parent = null, \SystemdJournal $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m__raw_header = $this->_io->readBytes($this->lenHeader());
            $_io__raw_header = new \Kaitai\Struct\Stream($this->_m__raw_header);
            $this->_m_header = new \SystemdJournal\Header($_io__raw_header, $this, $this->_root);
            $this->_m_objects = [];
            $n = $this->header()->numObjects();
            for ($i = 0; $i < $n; $i++) {
                $this->_m_objects[] = new \SystemdJournal\JournalObject($this->_io, $this, $this->_root);
            }
        }
        protected $_m_lenHeader;

        /**
         * Header length is used to set substream size, as it thus required
         * prior to declaration of header.
         */
        public function lenHeader() {
            if ($this->_m_lenHeader !== null)
                return $this->_m_lenHeader;
            $_pos = $this->_io->pos();
            $this->_io->seek(88);
            $this->_m_lenHeader = $this->_io->readU8le();
            $this->_io->seek($_pos);
            return $this->_m_lenHeader;
        }
        protected $_m_dataHashTable;
        public function dataHashTable() {
            if ($this->_m_dataHashTable !== null)
                return $this->_m_dataHashTable;
            $_pos = $this->_io->pos();
            $this->_io->seek($this->header()->ofsDataHashTable());
            $this->_m_dataHashTable = $this->_io->readBytes($this->header()->lenDataHashTable());
            $this->_io->seek($_pos);
            return $this->_m_dataHashTable;
        }
        protected $_m_fieldHashTable;
        public function fieldHashTable() {
            if ($this->_m_fieldHashTable !== null)
                return $this->_m_fieldHashTable;
            $_pos = $this->_io->pos();
            $this->_io->seek($this->header()->ofsFieldHashTable());
            $this->_m_fieldHashTable = $this->_io->readBytes($this->header()->lenFieldHashTable());
            $this->_io->seek($_pos);
            return $this->_m_fieldHashTable;
        }
        protected $_m_header;
        protected $_m_objects;
        protected $_m__raw_header;
        public function header() { return $this->_m_header; }
        public function objects() { return $this->_m_objects; }
        public function _raw_header() { return $this->_m__raw_header; }
    }
}

namespace SystemdJournal {
    class Header extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \SystemdJournal $_parent = null, \SystemdJournal $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_signature = $this->_io->readBytes(8);
            if (!($this->signature() == "\x4C\x50\x4B\x53\x48\x48\x52\x48")) {
                throw new \Kaitai\Struct\Error\ValidationNotEqualError("\x4C\x50\x4B\x53\x48\x48\x52\x48", $this->signature(), $this->_io(), "/types/header/seq/0");
            }
            $this->_m_compatibleFlags = $this->_io->readU4le();
            $this->_m_incompatibleFlags = $this->_io->readU4le();
            $this->_m_state = $this->_io->readU1();
            $this->_m_reserved = $this->_io->readBytes(7);
            $this->_m_fileId = $this->_io->readBytes(16);
            $this->_m_machineId = $this->_io->readBytes(16);
            $this->_m_bootId = $this->_io->readBytes(16);
            $this->_m_seqnumId = $this->_io->readBytes(16);
            $this->_m_lenHeader = $this->_io->readU8le();
            $this->_m_lenArena = $this->_io->readU8le();
            $this->_m_ofsDataHashTable = $this->_io->readU8le();
            $this->_m_lenDataHashTable = $this->_io->readU8le();
            $this->_m_ofsFieldHashTable = $this->_io->readU8le();
            $this->_m_lenFieldHashTable = $this->_io->readU8le();
            $this->_m_ofsTailObject = $this->_io->readU8le();
            $this->_m_numObjects = $this->_io->readU8le();
            $this->_m_numEntries = $this->_io->readU8le();
            $this->_m_tailEntrySeqnum = $this->_io->readU8le();
            $this->_m_headEntrySeqnum = $this->_io->readU8le();
            $this->_m_ofsEntryArray = $this->_io->readU8le();
            $this->_m_headEntryRealtime = $this->_io->readU8le();
            $this->_m_tailEntryRealtime = $this->_io->readU8le();
            $this->_m_tailEntryMonotonic = $this->_io->readU8le();
            if (!($this->_io()->isEof())) {
                $this->_m_numData = $this->_io->readU8le();
            }
            if (!($this->_io()->isEof())) {
                $this->_m_numFields = $this->_io->readU8le();
            }
            if (!($this->_io()->isEof())) {
                $this->_m_numTags = $this->_io->readU8le();
            }
            if (!($this->_io()->isEof())) {
                $this->_m_numEntryArrays = $this->_io->readU8le();
            }
        }
        protected $_m_signature;
        protected $_m_compatibleFlags;
        protected $_m_incompatibleFlags;
        protected $_m_state;
        protected $_m_reserved;
        protected $_m_fileId;
        protected $_m_machineId;
        protected $_m_bootId;
        protected $_m_seqnumId;
        protected $_m_lenHeader;
        protected $_m_lenArena;
        protected $_m_ofsDataHashTable;
        protected $_m_lenDataHashTable;
        protected $_m_ofsFieldHashTable;
        protected $_m_lenFieldHashTable;
        protected $_m_ofsTailObject;
        protected $_m_numObjects;
        protected $_m_numEntries;
        protected $_m_tailEntrySeqnum;
        protected $_m_headEntrySeqnum;
        protected $_m_ofsEntryArray;
        protected $_m_headEntryRealtime;
        protected $_m_tailEntryRealtime;
        protected $_m_tailEntryMonotonic;
        protected $_m_numData;
        protected $_m_numFields;
        protected $_m_numTags;
        protected $_m_numEntryArrays;
        public function signature() { return $this->_m_signature; }
        public function compatibleFlags() { return $this->_m_compatibleFlags; }
        public function incompatibleFlags() { return $this->_m_incompatibleFlags; }
        public function state() { return $this->_m_state; }
        public function reserved() { return $this->_m_reserved; }
        public function fileId() { return $this->_m_fileId; }
        public function machineId() { return $this->_m_machineId; }
        public function bootId() { return $this->_m_bootId; }
        public function seqnumId() { return $this->_m_seqnumId; }
        public function lenHeader() { return $this->_m_lenHeader; }
        public function lenArena() { return $this->_m_lenArena; }
        public function ofsDataHashTable() { return $this->_m_ofsDataHashTable; }
        public function lenDataHashTable() { return $this->_m_lenDataHashTable; }
        public function ofsFieldHashTable() { return $this->_m_ofsFieldHashTable; }
        public function lenFieldHashTable() { return $this->_m_lenFieldHashTable; }
        public function ofsTailObject() { return $this->_m_ofsTailObject; }
        public function numObjects() { return $this->_m_numObjects; }
        public function numEntries() { return $this->_m_numEntries; }
        public function tailEntrySeqnum() { return $this->_m_tailEntrySeqnum; }
        public function headEntrySeqnum() { return $this->_m_headEntrySeqnum; }
        public function ofsEntryArray() { return $this->_m_ofsEntryArray; }
        public function headEntryRealtime() { return $this->_m_headEntryRealtime; }
        public function tailEntryRealtime() { return $this->_m_tailEntryRealtime; }
        public function tailEntryMonotonic() { return $this->_m_tailEntryMonotonic; }
        public function numData() { return $this->_m_numData; }
        public function numFields() { return $this->_m_numFields; }
        public function numTags() { return $this->_m_numTags; }
        public function numEntryArrays() { return $this->_m_numEntryArrays; }
    }
}

namespace SystemdJournal {
    class JournalObject extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Kaitai\Struct\Struct $_parent = null, \SystemdJournal $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_padding = $this->_io->readBytes(\Kaitai\Struct\Stream::mod((8 - $this->_io()->pos()), 8));
            $this->_m_objectType = $this->_io->readU1();
            $this->_m_flags = $this->_io->readU1();
            $this->_m_reserved = $this->_io->readBytes(6);
            $this->_m_lenObject = $this->_io->readU8le();
            switch ($this->objectType()) {
                case \SystemdJournal\JournalObject\ObjectTypes::DATA:
                    $this->_m__raw_payload = $this->_io->readBytes(($this->lenObject() - 16));
                    $_io__raw_payload = new \Kaitai\Struct\Stream($this->_m__raw_payload);
                    $this->_m_payload = new \SystemdJournal\DataObject($_io__raw_payload, $this, $this->_root);
                    break;
                default:
                    $this->_m_payload = $this->_io->readBytes(($this->lenObject() - 16));
                    break;
            }
        }
        protected $_m_padding;
        protected $_m_objectType;
        protected $_m_flags;
        protected $_m_reserved;
        protected $_m_lenObject;
        protected $_m_payload;
        protected $_m__raw_payload;
        public function padding() { return $this->_m_padding; }
        public function objectType() { return $this->_m_objectType; }
        public function flags() { return $this->_m_flags; }
        public function reserved() { return $this->_m_reserved; }
        public function lenObject() { return $this->_m_lenObject; }
        public function payload() { return $this->_m_payload; }
        public function _raw_payload() { return $this->_m__raw_payload; }
    }
}

namespace SystemdJournal\JournalObject {
    class ObjectTypes {
        const UNUSED = 0;
        const DATA = 1;
        const FIELD = 2;
        const ENTRY = 3;
        const DATA_HASH_TABLE = 4;
        const FIELD_HASH_TABLE = 5;
        const ENTRY_ARRAY = 6;
        const TAG = 7;
    }
}

/**
 * Data objects are designed to carry log payload, typically in
 * form of a "key=value" string in `payload` attribute.
 */

namespace SystemdJournal {
    class DataObject extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \SystemdJournal\JournalObject $_parent = null, \SystemdJournal $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_hash = $this->_io->readU8le();
            $this->_m_ofsNextHash = $this->_io->readU8le();
            $this->_m_ofsHeadField = $this->_io->readU8le();
            $this->_m_ofsEntry = $this->_io->readU8le();
            $this->_m_ofsEntryArray = $this->_io->readU8le();
            $this->_m_numEntries = $this->_io->readU8le();
            $this->_m_payload = $this->_io->readBytesFull();
        }
        protected $_m_nextHash;
        public function nextHash() {
            if ($this->_m_nextHash !== null)
                return $this->_m_nextHash;
            if ($this->ofsNextHash() != 0) {
                $io = $this->_root()->_io();
                $_pos = $io->pos();
                $io->seek($this->ofsNextHash());
                $this->_m_nextHash = new \SystemdJournal\JournalObject($io, $this, $this->_root);
                $io->seek($_pos);
            }
            return $this->_m_nextHash;
        }
        protected $_m_headField;
        public function headField() {
            if ($this->_m_headField !== null)
                return $this->_m_headField;
            if ($this->ofsHeadField() != 0) {
                $io = $this->_root()->_io();
                $_pos = $io->pos();
                $io->seek($this->ofsHeadField());
                $this->_m_headField = new \SystemdJournal\JournalObject($io, $this, $this->_root);
                $io->seek($_pos);
            }
            return $this->_m_headField;
        }
        protected $_m_entry;
        public function entry() {
            if ($this->_m_entry !== null)
                return $this->_m_entry;
            if ($this->ofsEntry() != 0) {
                $io = $this->_root()->_io();
                $_pos = $io->pos();
                $io->seek($this->ofsEntry());
                $this->_m_entry = new \SystemdJournal\JournalObject($io, $this, $this->_root);
                $io->seek($_pos);
            }
            return $this->_m_entry;
        }
        protected $_m_entryArray;
        public function entryArray() {
            if ($this->_m_entryArray !== null)
                return $this->_m_entryArray;
            if ($this->ofsEntryArray() != 0) {
                $io = $this->_root()->_io();
                $_pos = $io->pos();
                $io->seek($this->ofsEntryArray());
                $this->_m_entryArray = new \SystemdJournal\JournalObject($io, $this, $this->_root);
                $io->seek($_pos);
            }
            return $this->_m_entryArray;
        }
        protected $_m_hash;
        protected $_m_ofsNextHash;
        protected $_m_ofsHeadField;
        protected $_m_ofsEntry;
        protected $_m_ofsEntryArray;
        protected $_m_numEntries;
        protected $_m_payload;
        public function hash() { return $this->_m_hash; }
        public function ofsNextHash() { return $this->_m_ofsNextHash; }
        public function ofsHeadField() { return $this->_m_ofsHeadField; }
        public function ofsEntry() { return $this->_m_ofsEntry; }
        public function ofsEntryArray() { return $this->_m_ofsEntryArray; }
        public function numEntries() { return $this->_m_numEntries; }
        public function payload() { return $this->_m_payload; }
    }
}

namespace SystemdJournal {
    class State {

        /**
         * File is closed and thus not being written into right now
         */
        const OFFLINE = 0;

        /**
         * File is open and thus might be undergoing update at the moment
         */
        const ONLINE = 1;

        /**
         * File has been rotated, no further updates to this file are to be expected
         */
        const ARCHIVED = 2;
    }
}