Python rsyslog日志集中化

下位机十多台树莓派,就开始考虑将日志集中在一起方便调试查看,其实一开始我对这个不太熟悉,对集中化这个词不太敏感,只是想要把日志发送到同一个主机上,后来网上查了查,这叫日志集中化,名气最大的是ELK,日志一把梭
上面说的 ELK 都比较复杂,其实用一台服务器作为日志收集服务,使用 Linux 自带的 rsyslog即可,Python 日志库也自带了 SysLogHandler. 简单配置一下即可使用。

1. 什么是rsyslog

1.1 基本介绍

开始之前想说说什么是 syslog,在 Linux中,有很多后台程序都是以后台进程的形式存在运行,例如 crontab/sshd/nginx 等,有些是系统的,有些是我们自己添加的,但是,他们都有一些相同的特点:

  • 我们不能直接从标准输入给他们输入,也不能直接从标准输出获得他们的输出
  • 他们通常在固定的位置有日志可以查看,这个位置通常在/var/log/

对于其中的第 2 个特点,大部分 Linux 后台进程都通过 syslog来实现。因为服务器中很多进程的调试和维护都需要一个稳定专业的日志系统,因此,Linux 提供了一个守护进程专门用来处理系统日志,而这个守护进程就是 syslogd,不过,现在的系统大都使用 rsyslod(syslog 升级版) 代替,提供扩展的过滤,消息的加密保护,各种配置选项,输入和输出模块,支持通过TCP或者UDP协议进行传输

1.2 日志位置

rsyslogd 在接收到日志之后,需要将日志输出到特定的日志文件中,默认情况下:
其中大部分日志消息会在/var/log/rsyslog中,接下来有各类的消息,

  • 认证信息会保存到/var/log/auth文件中
  • 调试信息会保存到/var/log/debug文件中
  • 普通信息会保存到/var/log/messages文件中
  • 内核消息会保存到/var/log/kern.log文件中
  • 邮件消息会保存到/var/log/mail.log文件中

但是这些都是可以改变的,配置文件的位置是 /etc/rsyslog.conf,可以查看配置文件里RULES项的日志对一对上面的东西

1.3 传输方式

rsyslog提供三个远程日志传输方式:

  • UDP: 数据包传输可信度不高
  • TCP: 数据包传输可信度比较高
  • RELP: 数据包传输可信度最高,避免数据丢失,比较新的协议,目前应用较少

关于TCP和UDP的传输方式,rsyslog官方推荐使用TCP传输方式

In general, we suggest to use TCP syslog. It is way more reliable than UDP syslog and still pretty fast. The main reason is, that UDP might suffer of message loss. This happens when the syslog server must receive large bursts of messages. If the system buffer for UDP is full, all other messages will be dropped. With TCP, this will not happen. But sometimes it might be good to have a UDP server configured as well. That is, because some devices (like routers) are not able to send TCP syslog by design. In that case, you would need both syslog server types to have everything covered. If you need both syslog server types configured, please make sure they run on proper ports. By default UDP syslog is received on port 514. TCP syslog needs a different port because often the RPC service is using this port as well.

2. rsyslogc基础配置

rsyslog主要配置文件在/etc/rsyslog.conf。这里你可以配置 global directives, modulesrules that consist of filter and action parts,也可以写一些注释。
这里我们主要来讲讲rules板块的配置,rules简单点说就是让系统日志进来的规则,就好比数据库先筛选一部分记录然后给这部分记录做一些动作,等于table.filter().update()
以下内容整理来自redhat rsyslog配置

2.1 Filters(过滤器)

**Facility/Priority-based filters **

FACILITY指定产生特定系统日志消息的子系统。例如,邮件子系统处理所有与邮件相关的系统日志消息。 FACILITY可以用以下关键字之一(或数字代码)表示:kern (0), user (1), mail (2), daemon (3), auth (4), syslog (5), lpr (6), news (7), cron (8), authpriv (9), ftp (10), and local0 through local7 (16 - 23)

PRIORITY指定系统日志消息的优先级。可以通过以下关键字之一(或数字)来表示优先级:debug (7), info (6), notice (5), warning (4), err (3), crit (2), alert (1), and emerg (0)

To select all kernel syslog messages with any priority, add the following text into the configuration file:

选择任何优先级的所有内核syslog消息:

1
kern.*`

要选择优先级crit更高的所有邮件系统日志消息,请使用以下形式:

1
mail.crit

要选择除具有info或debug优先级的消息外的所有cron syslog消息,请以以下形式设置配置:

1
cron.!info,!debug

此外,还支持比较符过滤,比如:

1
2
3
:msg, contains, "error"
:hostname, isequal, "host1"
:msg, !regex, "fatal .* error"

以及表达式过滤器:

1
if EXPRESSION then ACTION else ACTION

2.2 Actions(动作)

2.2.1 将syslog消息存入文件

大多数操作指定将系统日志消息保存到哪个日志文件。这是通过在已定义的选择器之后指定文件( FILTER PATH)路径来完成的:

1
cron.* /var/log/cron.log

默认情况下,每次生成系统日志消息时都会同步日志文件。使用破折号(-)作为指定的文件路径的前缀以省略同步:

1
FILTER -PATH

指定的文件路径可以是静态或动态的。如上例所示,静态文件由固定文件路径表示。动态文件路径可能会根据收到的消息而有所不同。动态文件路径由模板和问号(?)前缀表示,FILTER ?DynamicFile

1
2
template(name="DynFile" type="string" string="/var/log/line/%fromhost%.log")
local7.* ?DynFile

动态模板是很大的一块内容,具体的不展开我用到的不多,就上面这个例子。详情可以看rsyslog template以及需要用到的属性

2.2.2 通过网络发送syslog消息

rsyslog允许您通过网络发送和接收syslog消息。此功能使您可以在一台计算机上管理多个主机的系统日志消息。要将系统日志消息转发到远程计算机,请使用以下语法,@[(zNUMBER)]HOST:[PORT]@代表udp,@@代表TCP

1
2
3
4
*.* @192.168.0.1
*.* @@example.com:6514
*.* @(z9)[2001:db8::1]
Storing syslog messages in a database

2.2.3 其他传输方式

  • Output channels
  • Sending syslog messages to specific users
  • Executing a program
  • Storing syslog messages in a database
    :PLUGIN:DB_HOST,DB_NAME,DB_USER,DB_PASSWORD;[TEMPLATE]
  • Discarding syslog messages (丢弃日志local5.* stop

2.2.4 指定多个动作

可以为每个选择器指定多个操作。要为一个选择器指定多个动作,请将每个动作写在单独的行上,并在其前面加上一个&字符

1
2
3
4
5
6
7
FILTER ACTION
& ACTION
& ACTION

kern.=crit user1
& ^test-program;temp
& @192.168.0.1

2.3 Log Rotation (日志轮替)

我们可以在/etc/logrotate.conf配置文件中进行日志轮替的配置

1
2
3
4
5
6
# rotate log files weekly
weekly
# keep 4 weeks worth of backlogs
rotate 4
# uncomment this if you want your log files compressed
compress

3. 使用Python发送日志到rsyslog

我们查阅一下 Python 文档中的 logging 模块的文档,可以发现又一个 handler 叫做:SysLogHandler,看一下参数,并不比 syslog 的原始函数简单,但是,我们可以忽略所有这些参数,而简单得控制日志输出:

1
class logging.handlers.SysLogHandler(address=('localhost', SYSLOG_UDP_PORT), facility=LOG_USER, socktype=socket.SOCK_DGRAM)

address: 前面说了 rsyslog 是一个套接字,这里可以制定套接字的地址,注意:这个可以是不在同一台机器,默认为(‘localhost’, 514)
facility:这个参数的作用是告诉 rsyslog 日志的类型,从而可以让他根据不同的类型执行不同的操作,默认为LOG_USER,下面的例子我们使用自定义的日志设备local7
socktype:默认UDP,想换TCP用socket.SOCK_STREAM
使用的话就和其他的 Handler 一致,简单得记一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import logging
from logging.handlers import SysLogHandler

_LOG_SERVER = ('192.168.123.222', 514)

def remote_logger(name):
logger = logging.getLogger(name)
logger.setLevel(logging.INFO)
handler1 = SysLogHandler(
address=_LOG_SERVER, facility=SysLogHandler.LOG_LOCAL7)
# python logging 默认所有的日志都是输出到 stderr 中的,可以在supervisor的stderr_logfile指定文件中查看相关日志
# 或者 stdout_handler = logging.StreamHandler(sys.stdout)
handler2 = StreamHandler()
formatter1 = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
formatter2 = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler1.setFormatter(formatter1)
handler2.setFormatter(formatter2)
logger.addHandler(handler1)
logger.addHandler(handler2)
return logger

logger = remote_logger('test')
logger.info('xxx')

4. 记录Python使用TCP的一些坑

鉴于上面所说的,官方推荐使用TCP,而在上面的例子中我们只要改下socktype的参数就行,但随后发现日志总是没法显示,要么就是好多行在一起,找了好多资料才发现其中原因,具体看Python’s SyslogHandler and TCPSysLogHandler messages grouped on one line on remote server,概括来说就是TCP格式标准不支持,要自己动手额外打些补丁,比较简便的方法是使用syslog_rfc5424_formatter库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import logging
import logging.handlers
import socket
from syslog_rfc5424_formatter import RFC5424Formatter

my_logger = logging.getLogger('MyLogger')
my_logger.setLevel(logging.DEBUG)

handler = logging.handlers.SysLogHandler(address = ('0.0.0.0',8514),socktype=socket.SOCK_STREAM)
# some old syslog daemons expect a NUL terminator, but graylog will not work if a NUL terminator is appended.
handler.append_nul = False
handler.setFormatter(RFC5424Formatter())
my_logger.addHandler(handler)

my_logger.debug('wolfgang was here\n')

当然我们可以用一些方法曲线救国,我们用UDP写入到本地然后在用rsylog自带的TCP传输写入到远程,这个方法还可以,尽量保证数据准确性

5. 在Docker中使用rsyslog

当需要一个干干静静的日志服务器时,我们需要用一个docker来解决此事,仅14M的镜像大小,没有类似于mailauth等其他杂的信息,docker logs -f container_id直接跟踪,多协议支持,安逸!
我参考的是dockerhub上的rsyslog/syslog_appliance_alpine,正常的话使用docker run --name testsyslog -d -p 666:514/udp rsyslog/syslog_appliance_alpine rsyslog 打开容器监听666端口上的system日志,然而我们自定义的话使用LOCAL7类型日志,简单改动下,自己做了个shijiahuan /rsyslog,详细说明都有,不展开。

6. 总结

在学习日志集中化过程中,也发现了ELK,相信以后如果接触大型项目也会要学习到的,而几个小机器,用rsyslog绰绰有余~