版本说明 #
- 日志模块实现
日期 版本 修改内容 2022/03/01 V0.1 创建
实现目标 #
-
整个上层日志最后能导出到一个文件中,方便根据日志记录顺序,查找问题。避免多日志文件不好查时序的麻烦
-
整个程序运行后,一直往内存中写日志,只在用户想要导出日志时,才写U盘,尽量减少程序运行时耗时
-
能做到内存中始终能导出最近的日志到U盘中
-
程序崩溃后,发生崩溃点的日志保存处理(在DEBUG版本中,将日志输出缓冲设置为0)
参考实现 #
Qt5官方日志QLoggingCategory测试 #
QLoggingCategory represents a certain logging category - identified by a string - at runtime. A category can be configured to enable or disable logging of messages per message type.
Split up logging messages in hierarchical categories.Category is identified by it’s name category.subcategory.subsubcategory[…] Logging of messages can be enabled or disabled based on the category and message type, at runtime.
优点:
1、可以将各个模块日志通过各自的Debug Level进行控制
2、运行时,动态修改各个模块的Debug Level
Improving Logging Output #
https://community.kde.org/Guidelines_and_HOWTOs/Debugging/Using_Error_Messages
Qt provides a way of controlling the output of the logging methods via an environment variable. You can tell it to include the application name and PID, as well as the debugging category, and color-code the text. For example, running the following lines in your shell will produce something that looks quite like kDebug
’s colored output:
c=`echo -e "\033"`
export QT_MESSAGE_PATTERN="%{appname}(%{pid})/(%{category}) $c\[31m%{if-debug}$c\[34m%{endif}%{function}$c\[0m: %{message}"
unset c
See qSetMessagePattern documentation for the full list of placeholders.
内部实现代码 #
#define Q_DECLARE_LOGGING_CATEGORY(name) \
extern const QLoggingCategory &name();
#define Q_LOGGING_CATEGORY(name, ...) \
const QLoggingCategory &name() \
{ \
static const QLoggingCategory category(__VA_ARGS__); \
return category; \
}
//使用
#define qCDebug(category, ...) \
for (bool qt_category_enabled = category().isDebugEnabled(); qt_category_enabled; qt_category_enabled = false) \
QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC, category().categoryName()).debug(__VA_ARGS__)
moduo日志实现参考20210901 #
#define LOG_TRACE if (muduo::Logger::logLevel() <= muduo::Logger::TRACE) \
muduo::Logger(__FILE__, __LINE__, muduo::Logger::TRACE, __func__).stream()
#define LOG_DEBUG if (muduo::Logger::logLevel() <= muduo::Logger::DEBUG) \
muduo::Logger(__FILE__, __LINE__, muduo::Logger::DEBUG, __func__).stream()
#define LOG_INFO if (muduo::Logger::logLevel() <= muduo::Logger::INFO) \
muduo::Logger(__FILE__, __LINE__).stream()
#define LOG_WARN muduo::Logger(__FILE__, __LINE__, muduo::Logger::WARN).stream()
#define LOG_ERROR muduo::Logger(__FILE__, __LINE__, muduo::Logger::ERROR).stream()
#define LOG_FATAL muduo::Logger(__FILE__, __LINE__, muduo::Logger::FATAL).stream()
#define LOG_SYSERR muduo::Logger(__FILE__, __LINE__, false).stream()
#define LOG_SYSFATAL muduo::Logger(__FILE__, __LINE__, true).stream()
syslog日志实现 #
日志消息级别syslog Level #
- 0 EMERG(紧急):会导致主机系统不可用的情况
- 1 ALERT(警告):必须马上采取措施解决的问题
- 2 CRIT(严重):比较严重的情况
- 3 ERR(错误):运行出现错误
- 4 WARNING(提醒):可能会影响系统功能的事件
- 5 NOTICE(注意):不会影响系统但值得注意
- 6 INFO(信息):一般信息
- 7 DEBUG(调试):程序或系统调试信息等
syslog | journald priority level | QtMsgType | 说明 |
---|---|---|---|
LOG_EMERG | emerg | QtFatalMsg | System is unusable |
LOG_ALERT | alert | Should be corrected immediately | |
LOG_CRIT | crit | QtCriticalMsg | Critical conditions |
LOG_ERR | err | Error conditions | |
LOG_WARNING | warning | QtWarningMsg | May indicate that an error will occur if action is not taken. |
LOG_NOTICE | notice | Events that are unusual, but not error conditions. | |
LOG_INFO | info | QtInfoMsg | Normal operational messages that require no action. |
LOG_DEBUG | debug | QtDebugMsg | Information useful to developers for debugging the application. |
syslog与QDebug配合 #
Qt4封装
#include <QApplication>
#include <syslog.h>
void customMessageHandler(QtMsgType type, const char* msg)
{
switch(type)
{
case QtDebugMsg:
syslog(LOG_DEBUG, "%s", msg);
break;
case QtInfoMsg:
syslog(LOG_INFO, "%s", msg);
break;
case QtWarningMsg:
syslog(LOG_WARNING, "%s", msg);
break;
case QtCriticalMsg:
syslog(LOG_CRIT, "%s", msg);
break;
case QtFatalMsg:
syslog(LOG_ERR, "%s", msg);
abort();//这里退出进程了
break;
default:
syslog(LOG_DEBUG, "%s", msg);
break;
}
}
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
qInstallMsgHandler(customMessageHandler);
return a.exec();
}
Qt5封装
#include <QApplication>
#include <syslog.h>
static const char LOG_LEVEL_CHAR[8] = {'!', 'A', 'C', 'E', 'W', 'N', 'I', 'D'};
static void customMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
QByteArray localMsg = msg.toLocal8Bit();
const char *file = context.file ? context.file : "";
const char *function = context.function ? context.function : "";
const char *category = context.category ? context.category : "";
int level = LOG_INFO;
switch (type) {
case QtDebugMsg:
level = LOG_DEBUG;
break;
case QtInfoMsg:
level = LOG_INFO;
break;
case QtWarningMsg:
level = LOG_WARNING;
break;
case QtCriticalMsg:
level = LOG_CRIT;
break;
case QtFatalMsg:
level = LOG_ERR;
break;
default:
break;
}
syslog(level, "[%c](%s,%s:%u, %s): %s \n", LOG_LEVEL_CHAR[level],category,file, context.line, function, localMsg.constData());
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
qInstallMessageHandler(customMessageHandler);
return a.exec();
}
Qt4中的qInstallMsgHandler函数被Qt5的qInstallMessageHandler替代了,增加了QMessageLogContext类,可以输出函数名、行号、文件路径信息
测试结果如下,可以看出并没有显示函数名、行号等信息
May 24 09:43:23 am335x-evm test_log_category[954]: Debug: Log something:qCDebug 74 (:0, , awesomecategory)
May 24 09:43:23 am335x-evm test_log_category[954]: Info: Log something:qCInfo 74 (:0, , awesomecategory)
May 24 09:43:23 am335x-evm test_log_category[954]: Warning: Log something:qCWarning 74 (:0, , awesomecategory)
May 24 09:43:23 am335x-evm test_log_category[954]: Critical: Log something:qCCritical 74 (:0, , awesomecategory)
May 24 09:43:23 am335x-evm test_log_category[954]: Debug: Log something:qDebug 74 (:0, , default)
May 24 09:43:23 am335x-evm test_log_category[954]: Info: Log something:qInfo 74 (:0, , default)
May 24 09:43:23 am335x-evm test_log_category[954]: Warning: Log something:qWarning 74 (:0, , default)
May 24 09:43:23 am335x-evm test_log_category[954]: Critical: Log something:qCritical 74 (:0, , default)
通过以下两种方式修改pro文件后,可以开启函数名、行号等信息显示
#方式1
CONFIG += debug
CONFIG(debug, debug|release) {
DESTDIR = build/debug
}
CONFIG(release, debug|release) {
DESTDIR = build/release
}
#方式2
#Note: By default, this information is recorded only in debug builds.
#You can overwrite this explicitly by defining QT_MESSAGELOGCONTEXT or QT_NO_MESSAGELOGCONTEXT.
DEFINES +=QT_MESSAGELOGCONTEXT
May 24 10:14:49 am335x-evm test_log_category[916]: [D](category1,../widget.cpp:28, virtual void Widget::timerEvent(QTimerEvent*)): Log something:qCDebug1 1
May 24 10:14:49 am335x-evm test_log_category[916]: [I](category1,../widget.cpp:29, virtual void Widget::timerEvent(QTimerEvent*)): Log something:qCInfo1 1
May 24 10:14:49 am335x-evm test_log_category[916]: [W](category1,../widget.cpp:30, virtual void Widget::timerEvent(QTimerEvent*)): Log something:qCWarning1 1
May 24 10:14:49 am335x-evm test_log_category[916]: [C](category1,../widget.cpp:31, virtual void Widget::timerEvent(QTimerEvent*)): Log something:qCCritical1 1
May 24 10:14:49 am335x-evm test_log_category[916]: [D](category2,../widget.cpp:34, virtual void Widget::timerEvent(QTimerEvent*)): Log something:qCDebug2 1
May 24 10:14:49 am335x-evm test_log_category[916]: [I](category2,../widget.cpp:35, virtual void Widget::timerEvent(QTimerEvent*)): Log something:qCInfo2 1
May 24 10:14:49 am335x-evm test_log_category[916]: [W](category2,../widget.cpp:36, virtual void Widget::timerEvent(QTimerEvent*)): Log something:qCWarning2 1
May 24 10:14:49 am335x-evm test_log_category[916]: [C](category2,../widget.cpp:37, virtual void Widget::timerEvent(QTimerEvent*)): Log something:qCCritical2 1
May 24 10:14:49 am335x-evm test_log_category[916]: [D](default,../widget.cpp:39, virtual void Widget::timerEvent(QTimerEvent*)): Log something:qDebug3 1
May 24 10:14:49 am335x-evm test_log_category[916]: [I](default,../widget.cpp:40, virtual void Widget::timerEvent(QTimerEvent*)): Log something:qInfo3 1
May 24 10:14:49 am335x-evm test_log_category[916]: [W](default,../widget.cpp:41, virtual void Widget::timerEvent(QTimerEvent*)): Log something:qWarning3 1
May 24 10:14:49 am335x-evm test_log_category[916]: [C](default,../widget.cpp:42, virtual void Widget::timerEvent(QTimerEvent*)): Log something:qCritical3 1
为了区分同一个进程内的不同模块日志,可使用QLoggingCategory类
Logging kernel oops to MTD #
A kernel error, or oops, is normally logged via the klogd and syslogd daemons to a circular memory buffer or a file.
Following a reboot, the log will be lost in the case of a ring buffer, and even in the case of a file, it may not have been properly written to before the system crashed.
A more reliable method is to write oops and kernel panics to an MTD partition as a circular log buffer.
You can enable it with CONFIG_MTD_OOPS and add console=ttyMTDN to the kernel command line, with N being the MTD device number to write the messages to.
• /var/log Generally, logging to flash memory is not desirable because of the many small write cycles it generates. A simple solution is to mount /var/log using tmpfs, making all log messages volatile. In the case of syslogd, BusyBox has a version that can log to a circular ring buffer.
journald #
The journal collects:
- All data logged via libc
syslog()
- The data from the kernel logged with
printk()
- Everything written to STDOUT/STDERR of any system service
root@am335x-evm:~# systemctl status systemd-journald
* systemd-journald.service - Journal Service
Loaded: loaded (/lib/systemd/system/systemd-journald.service; static; vendor preset: disabled)
Active: active (running) since Mon 2021-05-24 08:05:14 UTC; 11min ago
TriggeredBy: * systemd-journald.socket
* systemd-journald-dev-log.socket
* systemd-journald-audit.socket
Docs: man:systemd-journald.service(8)
man:journald.conf(5)
Main PID: 95 (systemd-journal)
Status: "Processing requests..."
Tasks: 1 (limit: 454)
Memory: 8.9M
CGroup: /system.slice/systemd-journald.service
`-95 /lib/systemd/systemd-journald
May 24 08:05:14 am335x-evm systemd-journald[95]: Journal started
May 24 08:05:14 am335x-evm systemd-journald[95]: Runtime Journal (/run/log/journal/d962f275af8b4c3ca16323491f1b0a43) is 8.0M, max 64.0M, 56.0M free.
May 24 08:05:15 am335x-evm systemd-journald[95]: Runtime Journal (/run/log/journal/d962f275af8b4c3ca16323491f1b0a43) is 8.0M, max 64.0M, 56.0M free.
Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.
systemd-journald.service #
root@am335x-evm:~# cat /lib/systemd/system/systemd-journald.service
# SPDX-License-Identifier: LGPL-2.1+
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Journal Service
Documentation=man:systemd-journald.service(8) man:journald.conf(5)
DefaultDependencies=no
Requires=systemd-journald.socket
After=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket syslog.socket
Before=sysinit.target
[Service]
OOMScoreAdjust=-250
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_SYSLOG CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_CHOWN CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETUID CAP_SETGID CAP_MAC_OVERRIDE
DeviceAllow=char-* rw
ExecStart=/lib/systemd/systemd-journald
FileDescriptorStoreMax=4224
IPAddressDeny=any
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
Restart=always
RestartSec=0
RestrictAddressFamilies=AF_UNIX AF_NETLINK
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
Sockets=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket
StandardOutput=null
SystemCallArchitectures=native
SystemCallErrorNumber=EPERM
SystemCallFilter=@system-service
Type=notify
WatchdogSec=3min
# If there are many split up journal files we need a lot of fds to access them
# all in parallel.
LimitNOFILE=524288
默认配置 journald.conf #
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Entries in this file show the compile time defaults.
# You can change settings by editing this file.
# Defaults can be restored by simply deleting this file.
#
# See journald.conf(5) for details.
[Journal]
#Storage=auto
#Compress=yes
#Seal=yes
#SplitMode=uid
#SyncIntervalSec=5m
#RateLimitIntervalSec=30s
#RateLimitBurst=10000
#SystemMaxUse=
#SystemKeepFree=
#SystemMaxFileSize=
#SystemMaxFiles=100
#RuntimeMaxUse=
#RuntimeKeepFree=
#RuntimeMaxFileSize=
#RuntimeMaxFiles=100
#MaxRetentionSec=
#MaxFileSec=1month
#ForwardToSyslog=no
#ForwardToKMsg=no
#ForwardToConsole=no
#ForwardToWall=yes
#TTYPath=/dev/console
#MaxLevelStore=debug
#MaxLevelSyslog=debug
#MaxLevelKMsg=notice
#MaxLevelConsole=info
#MaxLevelWall=emerg
#LineMax=48K
#ReadKMsg=yes
Storage=
/run/log/journal目录存在则保存到内存中,the existence of the directory controls the storage mode
Note that journald will initially use volatile storage, until a call to journalctl –flush (or sending SIGUSR1
to journald) will cause it to switch to persistent logging (under the conditions mentioned above). This is done automatically on boot via “systemd-journal-flush.service
”.
MaxLevelStore=`, `MaxLevelSyslog=`, `MaxLevelKMsg=`, `MaxLevelConsole=`, `MaxLevelWall=
Controls the maximum log level of messages that are stored in the journal, forwarded to syslog, kmsg, the console or wall (if that is enabled, see above). As argument, takes one of “emerg
”, “alert
”, “crit
”, “err
”, “warning
”, “notice
”, “info
”, “debug
”, or integer values in the range of 0–7 (corresponding to the same levels). Messages equal or below the log level specified are stored/forwarded, messages above are dropped. Defaults to “debug
” for MaxLevelStore=
and MaxLevelSyslog=
, to ensure that the all messages are stored in the journal and forwarded to syslog. Defaults to “notice
” for MaxLevelKMsg=
, “info
” for MaxLevelConsole=
, and “emerg
” for MaxLevelWall=
. These settings may be overridden at boot time with the kernel command line options “systemd.journald.max_level_store=
”, “systemd.journald.max_level_syslog=
”, “systemd.journald.max_level_kmsg=
”, “systemd.journald.max_level_console=
”, “systemd.journald.max_level_wall=
”.
通过查看man journald.conf手册,为了满足需要,只需要/etc/systemd/journald.conf 做如下配置
RuntimeMaxUse=16M
#重启日志模块
$ systemctl restart systemd-journald
测试发现,设置为8M时,能导出的日志文件大概在176KB,里面包含1673行日志
测试发现,占用为72M时,能导出的日志文件大概在3726KB,里面包含29888行日志
如果只设置RuntimeMaxUse=16M,发现最后占用的空间会超过设定的空间16M,,于是测试同时设置RuntimeKeepFree=100M,仍然会超过预想的16M
如果同时设置RuntimeMaxUse=16M和RuntimeMaxFiles=2这两个配置项,才能达到预想的16M,最终的配置如下
RuntimeMaxUse=16M
RuntimeMaxFiles=2
测试日志轮转 #
while true; do echo testing 1234567890 | systemd-cat; done
备份日志脚本参考 #
[root@study ~]# vim /backups/backup.sh
#!/bin/bash
if [ "${1}" == "log" ]; then
logger -p syslog.info "backup.sh is starting"
fi
source="/etc /home /root /var/lib /var/spool/{cron,at,mail}"
target="/backups/backup-system-$(date +%Y-%m-%d).tar.gz"
[ ! -d /backups ] && mkdir /backups
tar -zcvf ${target} ${source} &> /backups/backup.log
if [ "${1}" == "log" ]; then
logger -p syslog.info "backup.sh is finished"
fi
[root@study ~]# /backups/backup.sh log
[root@study ~]# journalctl SYSLOG_FACILITY=5 -n 3
Aug 19 18:09:37 study.centos.vbird dmtsai[29850]: backup.sh is starting
Aug 19 18:09:54 study.centos.vbird dmtsai[29855]: backup.sh is finished
测试syslog关联 #
#include <stdio.h>
#include <syslog.h>
int main()
{
printf("hello world1\n");
syslog(LOG_INFO, "hello world2\n");
fprintf(stdout,"this is fprintf stdout test!\n");
fprintf(stderr,"this is fprintf stderr test!\n");
return 0;
}
测试1:直接运行测试程序,可以看出syslog方式输出的日志被记录到journald日志系统中
May 24 09:57:30 am335x-evm test_journald[1918]: hello world2
测试2:将测试程序添加到systemd中
root@am335x-evm:~# cat /etc/systemd/system/test_journald.service
[Unit]
Description=test_journald
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/home/root/test_journald
Restart=always
KillMode=process
可以看出,如果通过service方式启动,不管是printf,stdout,stderr,还是syslog方式,全部都被记录到journald日志系统中了
root@am335x-evm:~# systemctl restart test_journald
root@am335x-evm:~# journalctl /home/root/test_journald
May 24 14:01:12 am335x-evm test_journald[3799]: hello world2
May 24 14:01:12 am335x-evm test_journald[3799]: this is fprintf stderr test!
May 24 14:01:12 am335x-evm test_journald[3799]: hello world1
May 24 14:01:12 am335x-evm test_journald[3799]: this is fprintf stdout test!
May 24 14:01:12 am335x-evm test_journald[3801]: hello world2
May 24 14:01:12 am335x-evm test_journald[3801]: this is fprintf stderr test!
May 24 14:01:12 am335x-evm test_journald[3801]: hello world1
May 24 14:01:12 am335x-evm test_journald[3801]: this is fprintf stdout test!
May 24 14:01:12 am335x-evm test_journald[3802]: hello world2
May 24 14:01:12 am335x-evm test_journald[3802]: this is fprintf stderr test!
May 24 14:01:12 am335x-evm test_journald[3802]: hello world1
May 24 14:01:12 am335x-evm test_journald[3802]: this is fprintf stdout test!
May 24 14:01:12 am335x-evm test_journald[3803]: hello world2
May 24 14:01:12 am335x-evm test_journald[3803]: this is fprintf stderr test!
May 24 14:01:12 am335x-evm test_journald[3803]: hello world1
May 24 14:01:12 am335x-evm test_journald[3803]: this is fprintf stdout test!
May 24 14:01:12 am335x-evm test_journald[3804]: hello world2
May 24 14:01:12 am335x-evm test_journald[3804]: this is fprintf stderr test!
May 24 14:01:12 am335x-evm test_journald[3804]: hello world1
May 24 14:01:12 am335x-evm test_journald[3804]: this is fprintf stdout test!
常用命令 #
$ journalctl --since=2012-10-15 --until="2011-10-16 23:59:59"
$ journalctl -u httpd --since=00:00 --until=9:30
$ journalctl /usr/sbin/vpnc /usr/sbin/dhclient
#The + is an explicit OR you can use in addition to the implied OR
$ journalctl _HOSTNAME=theta _UID=70 + _HOSTNAME=epsilon _COMM=avahi-daemon
#显示特定程序的所有消息
$ journalctl /usr/lib/systemd/systemd
#查看特定位置的日志
$ journalctl -D /mnt/var/log/journal -xe
#Displaying Logs by Process ID
$ journalctl _PID=1221
#只展示内核日志
$ journalctl -k
#显示毫秒
$ journalctl -o short-precise
# 显示Priority level
$ journalctl -p err..alert
#手动清理日志,会保留最新的日志,从旧的日志开始删除
$ journalctl --vacuum-size=10M
$ journalctl --vacuum-time=2weeks
#查看当前使用空间
$ journalctl --disk-usage
Archived and active journals take up 8.0M in the file system.
#修改/etc/systemd/journald.conf配置文件后,重启systemd-journald日志服务
$ systemctl restart systemd-journald
应用程序日志重定向 #
#include <stdio.h>
#include <syslog.h>
#include <QDebug>
int main()
{
qDebug()<< __FILE__ << __FUNCTION__ << __LINE__ <<"this is QDebug test1";
printf("this is printf test ,hello world1\n");
syslog(LOG_INFO, "this is syslog test,hello world2\n");
fprintf(stdout,"this is fprintf stdout test!\n");
fprintf(stderr,"this is fprintf stderr test!\n");
qDebug()<< __FILE__ << __FUNCTION__ << __LINE__ <<"this is QDebug test2";
return 0;
}
通过管道和systemd-cat程序,将程序中的所有日志重定向到journald中
./test_journald 2>&1 |systemd-cat
May 24 13:02:31.141452 am335x-evm cat[1438]: ../test.cpp main 6 this is QDebug test1
May 24 13:02:31.145114 am335x-evm test_journald[1437]: this is syslog test,hello world2
May 24 13:02:31.147729 am335x-evm cat[1438]: this is fprintf stderr test!
May 24 13:02:31.152862 am335x-evm cat[1438]: ../test.cpp main 11 this is QDebug test2
May 24 13:02:31.152862 am335x-evm cat[1438]: this is printf test ,hello world1
May 24 13:02:31.152862 am335x-evm cat[1438]: this is fprintf stdout test!
从输出结果可以看出:
1、通过systemd-cat导入到journald后,日志中的进程名为cat,而使用syslog后,能正确显示进程名
2、QDebug和syslog的优先级比printf和fprintf stdout方式要高,并且QDebug和syslog会按照出现的顺序进行输出
日志集中管理 #
利用rsyslog,将日志发送到远程服务器,可以使前期调试阶段更方便
总结 #
1、为了将上层应用和内核的日志统一输出,便于定位问题,需要利用systemd中的强大的journald日志管理程序
2、为了保证日志统一输出到journald中,针对Qt程序和非Qt程序调用统一封装日志接口宏,在日志接口宏中统一调用syslog接口
3、对于Qt程序,可以利用QLoggingCategory类,可以控制日志按模块输出
参考链接 #
http://highscalability.com/log-everything-all-time
http://jinke.me/2018-05-10-muduo-logger/
鸟叔systemd-journald.service 简介 #
https://wizardforcel.gitbooks.io/vbird-linux-basic-4e/content/160.html