通過 Systemd Journal 收集日誌

sparkdev發表於2019-03-11

隨著 systemd 成了主流的 init 系統,systemd 的功能也在不斷的增加,比如對系統日誌的管理。Systemd 設計的日誌系統好處多多,這裡筆者就不再贅述了,本文筆者主要介紹 systemd journal 收集日誌的三種方式:

  • 程式使用 libc 庫中的 syslog() 函式輸出的日誌
  • 使用 printf() 函式列印的日誌
  • 任何服務程式輸出到 STDOUT/STDERR 的所有內容

說明:本文的演示環境為 ubuntu 16.04。

syslog()

該函式的宣告如下:

#include <syslog.h>
void syslog(int priority, const char *message, ... /* argument */);

建立下面的 C 語言程式碼,並儲存到檔案 clog.c 檔案中:

#include <syslog.h>

int main(int argc, char *argv[]) {
        syslog(LOG_NOTICE, "C Hello World");
        return 0;
}

用下面的命令編譯程式:

$ gcc -Wall clog.c -o clog

然後執行編譯好的 clog 程式,就可以從 journal -f 的輸出中看到對應的日誌:

這裡筆者執行了三次 clog 程式,所以日誌輸出了三遍。
程式碼中的 LOG_NOTICE 代表日誌的嚴重等級,我們可以使用下面定義好的等級:

#define    LOG_EMERG    0    /* system is unusable */
#define    LOG_ALERT    1    /* action must be taken immediately */
#define    LOG_CRIT    2    /* critical conditions */
#define    LOG_ERR        3    /* error conditions */
#define    LOG_WARNING    4    /* warning conditions */
#define    LOG_NOTICE    5    /* normal but significant condition */
#define    LOG_INFO    6    /* informational */
#define    LOG_DEBUG    7    /* debug-level messages */

下面嘗試在 python 程式碼中做同樣的事情,把下面的程式碼儲存到檔案 plog.py 中:

#!/usr/bin/evn python

import syslog
syslog.syslog('P Hello World')

然後執行下面的命令:

$ python plog.py

筆者同樣執行了三遍,這次輸出的是 python 程式碼中的日誌。

我們還可以通過 journalctl -o json-pretty -f 命令檢視 json 格式的日誌:

printf()

journal 可以捕獲服務程式往 STDOUT/STDERR 輸出的所有內容,比如 C 語言中 print 函式列印的內容,Python 中 print 列印的內容,以及 Shell 指令碼中 echo 列印的內容等等都可以被 journal 捕獲到並加入到日誌中。注意,只有以 service 的方式執行程式時,journal 才會捕獲 STDOUT/STDERR 輸出的內容。
建立下面的 C 語言程式碼,並儲存到檔案 printlog.c 檔案中:

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("C Print Hello World.\n");
    return 0;
}

用下面的命令編譯程式:

$ gcc -Wall printlog.c -o printlog

配置一個簡單的 service,先建立 一個配置檔案  /lib/systemd/system/testlog.service,其內容如下:

[Unit]
Description=test log

[Service]
ExecStart=/home/nick/projects/journaldemo/printlog

[Install]
WantedBy=multi-user.target
$ sudo systemctl daemon-reload
$ sudo systemctl start testlog.service

Journal 會捕獲 STDOUT/STDERR 輸出的內容:

預設情況下,這樣輸出的日誌等級為 LOG_INFO(6),我們可以通過 json 格式的日誌看到日誌等級資訊:

我們還可以在列印日誌時指定日誌的等級,比如在每行列印的內容前加上"<N>",N為需要指定的日誌等級。看下面的 C 語言示例:

#include<stdio.h>

#define PREFIX_NOTICE "<5>"

int main(void){
  printf(PREFIX_NOTICE "Hello World\n");
  fprintf(stderr, "<3>Hello  Error\n");

  return 0;
}

把上面的程式碼編譯為 printlog 程式,再檢視下日誌,顯示的就是我們自己設定的日誌等級:

在 Python 中的用法如下:

#!/usr/bin/env python
print '<5>Hello World'

在 bash 中的用法如下:

#!/bin/bash
echo "<5>Hello World"

Systemd 日誌庫

Systemd 提供了原生的 C 語言庫(systemd/sd-journal.h) 用於向 journal 輸出日誌(ubuntu 16.04 需要通過 sudo apt install libsystemd-dev 命令安裝 libsystemd-dev 包),相關函式的宣告為:

#include <systemd/sd-journal.h>
int sd_journal_print(int priority, const char *format, ...);
int sd_journal_send(const char *format, ...);

把下面的示例程式碼會把日誌傳送給 journal:

#include <systemd/sd-journal.h>

int main(int argc, char *argv[]) {
        sd_journal_print(LOG_NOTICE, "Hello World");
        return 0;
}

相比上文使用 print() 或者 syslog() 提交的日誌,使用 sd_journal_print 可以直觀的指定 LOG 等級、日誌內容等等,另外輸出的日誌會包含執行程式碼的位置資訊,例如執行到的函式,程式碼檔案位置,程式碼具體行數,方便開發人員除錯。

除了可以包含程式碼資訊,也可以向提交的日誌中加入自定義段包含更多自定義資訊,配合 journalctl 工具,可以更方便的過濾日誌,如下面的程式碼:

#include <systemd/sd-journal.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
        sd_journal_send("MESSAGE=Hello World!",
                        "MESSAGE_ID=52fb62f99e2c49d89cfbf9d6de5e3555",
                        "PRIORITY=5",
                        "HOME=%s", getenv("HOME"),
                        "TERM=%s", getenv("TERM"),
                        "PAGE_SIZE=%li", sysconf(_SC_PAGESIZE),
                        "N_CPUS=%li", sysconf(_SC_NPROCESSORS_ONLN),
                        NULL);
        return 0;
}

我們在日誌中新增了自定義的欄位,並且可以通過 journalctl 過濾這些欄位。

除了 systemd 提供的一套C語言庫,目前其他個別語言也衍生了 systemd 的相關擴充套件,其中就包含了和 journald 相關的擴充套件。下面是段 python 的演示程式碼:

from systemd import journal
journal.send('Hello world')
journal.send('Hello, again, world', FIELD2='Greetings!', FIELD3='Guten tag')

在執行上面的程式碼前你需要先通過 pip 安裝 python 的 systemd 模組(pip install systemd ),然後執行這段程式碼,你就可以從日誌中看到它輸出的資訊了:

展開成 json 格式看下:

我們自定義的欄位 FIELD2 和 FIELD3 也都輸出到日誌中了。

總結

本文介紹了常見的一些往 systemd journal 中寫入日誌的方式,瞭解這些日誌的寫入方式可以幫助我們更好的設計應用的日誌輸出,並有助於我們通過日誌解決問題。

參考:
Systemd 日誌管理相關
systemd for Developers III

相關文章