7

更新、2013 年 9 月 12 日:

私はもう少し深く掘り下げました.systemdそしてjournal、私はこれに出くわしまし.それは次のように述べています:

systemd-journald受信したすべてのログ メッセージをAF_UNIX SOCK_DGRAMソケット/run/systemd/journal/syslog(存在する場合) に転送します。これは、データをさらに処理するために Unix syslog デーモンによって使用される可能性があります。

マンページに従って、下にsyslogも配置するように環境をセットアップしました。それに応じてコードを調整しました。

define('NL', "\n\r");

$log = function ()
{
    if (func_num_args() >= 1)
    {
        $message = call_user_func_array('sprintf', func_get_args());

        echo '[' . date('r') . '] ' . $message . NL; 
    }
};

$syslog = '/var/run/systemd/journal/syslog';

$sock = socket_create(AF_UNIX, SOCK_DGRAM, 0);
$connection = socket_connect($sock, $syslog);

if (!$connection)
{
    $log('Couldn\'t connect to ' . $syslog);
}
else
{
    $log('Connected to ' . $syslog);

    $readables = array($sock);

    socket_set_nonblock($sock);

    while (true)
    {
        $read = $readables;
        $write = $readables;
        $except = $readables;

        $select = socket_select($read, $write, $except, 0);

        $log('Changes: %d.', $select);
        $log('-------');
        $log('Read: %d.', count($read));
        $log('Write: %d.', count($write));
        $log('Except: %d.', count($except));

        if ($select > 0)
        {
            if ($read)
            {
                foreach ($read as $readable)
                {
                    $data = socket_read($readable, 4096, PHP_BINARY_READ);

                    if ($data === false)
                    {
                        $log(socket_last_error() . ': ' . socket_strerror(socket_last_error()));
                    }
                    else if (!empty($data))
                    {
                        $log($data);
                    }
                    else
                    {
                        $log('Read empty.');
                    }
                }
            }

            if ($write)
            {
                foreach ($write as $writable)
                {
                    $data = socket_read($writable, 4096, PHP_BINARY_READ);

                    if ($data === false)
                    {
                        $log(socket_last_error() . ': ' . socket_strerror(socket_last_error()));
                    }
                    else if (!empty($data))
                    {
                        $log($data);
                    }
                    else
                    {
                        $log('Write empty.');
                    }
                }
            }
        }
    }
}

writeこれは明らかに、ソケットの変更のみを表示 (選択) します。まあ、ここで何かが間違っているかもしれないので、私はそれらから読み込もうとしましたが、運がありません(また、あるべきではありません):

[Thu, 12 Sep 2013 14:45:15 +0300] Changes: 1.
[Thu, 12 Sep 2013 14:45:15 +0300] -------
[Thu, 12 Sep 2013 14:45:15 +0300] Read: 0.
[Thu, 12 Sep 2013 14:45:15 +0300] Write: 1.
[Thu, 12 Sep 2013 14:45:15 +0300] Except: 0.
[Thu, 12 Sep 2013 14:45:15 +0300] 11: Resource temporarily unavailable

さて、これは私を少し狂わせます。syslogドキュメントには、これが可能であると書かれています。コードの何が問題になっていますか?

オリジナル:

単純に、次の方法でプロトタイプを作成しました。

while(true)
{
    exec('journalctl -r -n 1 | more', $result, $exit);

    // do stuff
}

しかし、これは間違っていると感じ、システム リソースを大量に消費します。その後、journald にソケットがあることがわかりました。

私は接続して読み込もうとしました:

AF_UNIX, SOCK_DGRAM : /var/run/systemd/journal/socket
AF_UNIX, SOCK_STREAM : /var/run/systemd/journal/stdout

指定されたソケット。

では/var/run/systemd/journal/socketsocket_select0 の変更が表示されます。I always (すべてのループ)では/var/run/systemd/journal/stdout、0 バイトのデータで 1 つの変更を取得します。

これは私の「読者」です:

<?php

define('NL', "\n\r");

$journal = '/var/run/systemd/journal/socket';
$jSTDOUT = '/var/run/systemd/journal/stdout';

$journal = $jSTDOUT;

$sock = socket_create(AF_UNIX, SOCK_STREAM, 0);
$connection = @socket_connect($sock, $journal);

$log = function ($message)
{
    echo '[' . date('r') . '] ' . $message . NL; 
};

if (!$connection)
{
    $log('Couldn\'t connect to ' . $journal);
}
else
{
    $log('Connected to ' . $journal);

    $readables = array($sock);

    while (true)
    {
        $read = $readables;

        if (socket_select($read, $write = NULL, $except = NULL, 0) < 1)
        {
            continue;
        }

        foreach ($read as $read_socket)
        {
            $data = @socket_read($read_socket, 1024, PHP_BINARY_READ);

            if ($data === false)
            {
                $log('Couldn\'t read.');

                socket_shutdown($read_socket, 2);
                socket_close($read_socket);

                $log('Server terminated.');
                break 2;
            }

            $data = trim($data);

            if (!empty($data))
            {
                $log($data);
            }
        }
    }

    $log('Exiting.');
}

読み取りソケットにデータがないため、何か間違ったことをしていると思います。

質問、アイデア:

私の目標は、メッセージを読み取り、それらのいくつかに対してcallbackを実行することです。

ジャーナルメッセージをプログラムで読む方法の正しい方向を教えてもらえますか?

4

1 に答える 1

11

下のソケット/run/systemd/journal/はこれには機能しません -実際…/socketには書き込み専用 (つまり、ジャーナルに…/stdoutデータを供給するために使用)…/syslogですが、ソケットは実際の syslogd 以外で使用されることは想定されておらず、journald は送信しません。その上のメタデータ。(実際、…/syslogソケットはデフォルトでは存在しません。syslogd は実際にそれをリッスンする必要があり、journald はそれに接続します。)

正式な方法は、ジャーナル ファイルから直接読み取り、 inotifyを使用して変更を監視することです (これは同じことであり、ポーリングの代わりに使用することjournalctl --followさえあります)。tail -f /var/log/syslogC プログラムでは、libsystemd-journalの関数を使用できます。これは、必要な解析とフィルタリングさえも実行します。

他の言語では、次の 3 つの選択肢があります。C ライブラリを呼び出します。自分でジャーナル ファイルを解析します (形式は文書化されています)。または、 JSON 形式のエントリ (またはより詳細なジャーナル エクスポート形式)journalctl --followを出力するように指示できるフォーク。3 番目のオプションは、ストリーム全体に対して 1 つのプロセスのみをフォークするため、実際には非常にうまく機能します。そのための PHP ラッパーを作成しました (以下を参照)。

最近の systemd バージョン ( v193 ) には も付属してsystemd-journal-gatewaydいます。これは基本的に の HTTP ベースのバージョンですjournalctl。つまり、JSON またはジャーナル エクスポート ストリームを で取得できますhttp://localhost:19531/entries。( gatewaydjournalctlは両方とも、HTML 5 Web ページからストリームにアクセスするためのサーバー送信イベントもサポートしています。) ただし、明らかなセキュリティ上の問題により、gatewaydはデフォルトで無効になっています。


添付:PHPラッパーjournalctl --follow

<?php
/* © 2013 Mantas Mikulėnas <grawity@gmail.com>
 * Released under the MIT Expat License <https://opensource.org/licenses/MIT>
 */

/* Iterator extends Traversable {
    void    rewind()
    boolean valid()
    void    next()
    mixed   current()
    scalar  key()
}
calls:  rewind, valid==true, current, key
    next, valid==true, current, key
    next, valid==false
*/

class Journal implements Iterator {
    private $filter;
    private $startpos;
    private $proc;
    private $stdout;
    private $entry;

    static function _join_argv($argv) {
        return implode(" ",
            array_map(function($a) {
                return strlen($a) ? escapeshellarg($a) : "''";
            }, $argv));
    }

    function __construct($filter=[], $cursor=null) {
        $this->filter = $filter;
        $this->startpos = $cursor;
    }

    function _close_journal() {
        if ($this->stdout) {
            fclose($this->stdout);
            $this->stdout = null;
        }
        if ($this->proc) {
            proc_close($this->proc);
            $this->proc = null;
        }
        $this->entry = null;
    }

    function _open_journal($filter=[], $cursor=null) {
        if ($this->proc)
            $this->_close_journal();

        $this->filter = $filter;
        $this->startpos = $cursor;

        $cmd = ["journalctl", "-f", "-o", "json"];
        if ($cursor) {
            $cmd[] = "-c";
            $cmd[] = $cursor;
        }
        $cmd = array_merge($cmd, $filter);
        $cmd = self::_join_argv($cmd);

        $fdspec = [
            0 => ["file", "/dev/null", "r"],
            1 => ["pipe", "w"],
            2 => ["file", "/dev/null", "w"],
        ];

        $this->proc = proc_open($cmd, $fdspec, $fds);
        if (!$this->proc)
            return false;
        $this->stdout = $fds[1];
    }

    function seek($cursor) {
        $this->_open_journal($this->filter, $cursor);
    }

    function rewind() {
        $this->seek($this->startpos);
    }

    function next() {
        $line = fgets($this->stdout);
        if ($line === false)
            $this->entry = false;
        else
            $this->entry = json_decode($line);
    }

    function valid() {
        return ($this->entry !== false);
        /* null is valid, it just means next() hasn't been called yet */
    }

    function current() {
        if (!$this->entry)
            $this->next();
        return $this->entry;
    }

    function key() {
        if (!$this->entry)
            $this->next();
        return $this->entry->__CURSOR;
    }
}

$a = new Journal();

foreach ($a as $cursor => $item) {
    echo "================\n";
    var_dump($cursor);
    //print_r($item);
    if ($item)
        var_dump($item->MESSAGE);
}
于 2013-09-16T18:37:31.623 に答える