前记

最近系统上出现部分日志丢失的情况,为了解决这个问题,开始了解Linux的日志结构和机制。以下是我的学习总结, 主要涉及到了如何发送日志, 怎么处理日志以及怎么管理日志。
注:由于这些应用都是需要用配置去改良或者指导他们如何去做,所以除了简述Linux的结构和功能外,更多的是如何去配置他们。但其中最重要的是了解整个日志记录的机制,以及通过配置去了解这套机制会如何去限制日志的记录以及优化传输,这样我们才能去灵活使用他们。

1.Linux中日志的发送流程

一般来说, 我们所写的Web应用的日志都是直接打开一个文件, 然后把日志信息写到这个文件中。 这样非常方便, 但是缺少了一个统一的定义和管理, 所以Linux中就使用了一套稳定的日志管理系统– rsyslog 。 它可以接收应用程序通过socket发送过来的日志数据, 然后按照规则非常方便的过滤和分发日志, 同时它也支持稳定的把所有日志从某台机器发送到另外一台机器, 不漏一条消息。

那么这整套机制是怎么运行的呢, 先看看一个本机应用程序发送日志, 本机rsyslog接收日志的流程图:

注:操作系统为debain7, 该流程图以本地发送, 本地接受为例。

在这个流程图中, 第一步是应用程序把日志根据 syslog协议 进行封装, 比如用户指定的日志等级, 系统log渠道等等, 然后把封装好的数据发送到指定socket – /dev/log
/dev/log 是一个UNIX域套接字,它负责接受在本地机器上运行的进程所产生的消息。

除此之外还有一个与它十分相似的 /dev/klog , /dev/klog 是一个从UNIX内核接受消息的设备的socket, 如果看到syslog记录了kern的日志,却没记录普通日志,那就是 /dev/log 这个socket缓冲区满了, 而 /dev/klog 缓存区还有空间, 还可以正常运行, 这时候可以尝试通过kern.ipc.maxsockbuf改变缓冲区大小,但并不能完全解决问题。

第二步是由系统的 journald 开始服务, 在我的系统中,由于使用了 Systemd , 它的附属套件 systemd/journald 提供了一个 socket /run/systemd/journal/syslog ,它通过软连接,让发送到 /dev/log/ 的数据转而发送到 /run/systemd/journal/syslog , 然后通过读取标准socket来读取日志。不过 systemd-journald 所记录的数据其实是在内存中,只是系统利用文件的型态将它记录到 /run/log/ 下面, 在重新开机后,这些数据就会被清除掉。
当然, 也可以通过在 /etc/systemd/journald.conf 中更改设置, 设置它的存储位置和存储限制, 但一般都不会去更改它的这些存储相关的配置, 而是设置 ForwardToSyslog=yes , 让journald把日志发送到rsyslog, 进入下一步。(journald自己也提供了很多 功能 ,比如有:过滤输出,大小限制等等)

PS: 在还没有 systemd 的时代, 必须要开机完成并且执行了 rsyslogd 这个 daemon 之后,登录文件才会开始记录日志。所以,需要自己产生一个 klogd 的服务, 才能将系统在开机过程、启动服务的过程中的信息记录下来,然后等 rsyslogd 启动后才传送给它来处理。现在有了systemd主动调用 systemd-journald 来协助记载登录文件, 在开机过程中的所有信息, 包括启动服务与服务若启动失败的情况等等,都可以直接被记录到 systemd-journald .

第三步是 rsyslog 服务了, 在rsyslog服务启动后(以监控本地日志为例子,也就是加载module(load=”imuxsock”)) 从 /run/systemd/journal/syslog 这个socket消费syslog类型日志, 这些日志在经过预处理后会进入到主队列, 然后根据对应规则被分发, 由于 rsyslog 采用C/S结构,它可以将日志的信息追加到对应的日志文件中,一般在 /var/log 目录下(可以通过配置进行更改)。还可以把日志数据通过网络协议发送到另一台Linux服务器上,或者将日志存储在MySQL或Oracle等数据库中。
除此之外, rsyslog 能够快速的过滤,转发,发送日志(官方说的每秒支持百万级日志),所以rsyslog的内容非常丰富。 rsyslog 为了能让我们方便的使用这些功能, 提供了多个版本的配置语言, 我们只要通过对配置文件进行简单的修改,就能实现传输,过滤等功能。

如上面的结构图,rsyslog中只有一个主消息队列,任何消息都要先进入这个队列,然后直到进入到动作队列之后消息才会从这个队列中删除。通常,我们都不会去动主队列的配置,因为默认的设置已经工作得很好;消息经过主消息队列之后,就被rule processor解析和处理,然后根据预先配置的规则压入各自的动作队列,动作队列的消息最终被消费掉, 而我们经常要配置的就是这一部分。

可以发现, 上面这三步就是一个简单的消息队列, 应用程序通过syslog把日志输出到一个指定的log, 这是一个生产的步骤, systemd-journald 则是以 FIFO 的形式暂存日志数据, 最后被 rsyslog 以消费者的形式进行消费。而 rsyslog 的内部则更像一个高级版本的消费队列, 一个简易版本的 RabbitMQ

2.journald的配置

上面说到, 整个日志发送流程中, 对于 journald 只使用到它的暂存日志并转发到 rsyslog 的功能,所以这里分析的与暂存内存相关的配置, 如果要了解更多可以直接从man手册查看, 如果觉得内容太干, 也可以跳过, 都是从官网那边整理过来的。

  • Storage: 指定收到数据时, 如何存数据,默认值为 none , 此外它还有几个值:

  • volatile: 表示仅保存在内存中, 也就是仅保存在 /run/log/journal 目录中(将会被自动按需创建)。
  • persistent: 表示优先保存在磁盘上, 也就优先保存在 /var/log/journal 目录中(将会被自动按需创建), 但若失败(例如在系统启动早期”/var”尚未挂载), 则降级转而保存在 /run/log/journal 目录中(将会被自动按需创建)。
  • auto: 与 persistent 类似, 但不自动创建 /var/log/journal 目录, 因此可以根据该目录的存在与否决定日志的保存位置。
  • none: 表示不保存任何日志(直接丢弃所有收集到的日志), 但日志转发不受影响。
  • Compress: 默认值”yes”, 它表示压缩存储大于特定阈值(默认为512字节)的对象。 也可以直接设置一个字节值(可以带有 K, M, G 后缀)来表示要压缩存储大于指定阈值的对象。

  • Seal: 默认值”yes”, 它表示如果存在一个”sealing key”(由 journalctl(1) 的 –setup-keys 命令创建), 那么就为所有持久保存的日志文件启用FSS(Seekable Sequential Key Generators)保护, 以避免日志文件 被恶意或无意的修改。

  • SplitMode: 设置是否按照每个用户分割日志文件,以实现对日志的访问控制(日志守护进程会确保每个用户都能读取自己的日志文件)。 默认值为uid, “uid” 表示每个用户都有自己专属的日志文件(无论该用户是否拥有登录会话),但系统用户的日志依然记录到系统日志中。此外还有一个值”none”, 它表示不对日志文件按不同用户进行分割,而是将所有日志都记录到系统日志中。这意味着非特权用户根本无法读取属于自己的日志信息。 需要注意的是, 仅分割持久保存的日志(/var/log/journal), 永不分割内存中的日志(/run/log/journal)。

  • RateLimitIntervalSec: 用于设置一个时间段长度,可以使用下面的时间单位: “ms”, “s”, “min”, “h”, “d”来限制日志的生成速率.表示在 RateLimitIntervalSec 时间段内, 每个服务最多允许产生 RateLimitBurst 数量(条数)的日志。 在同一个时间段内,超出数量限制的日志将被丢弃,直到下一个时间段才能再次开始记录。 对于所有被丢弃的日志消息,仅用一条类似”xxx条消息被丢弃”的消息来代替。 这个限制是针对每个服务的限制,一个服务超限并不会影响到另一个服务的日志记录。
    如果一个服务已经通过 systemd LogRateLimitIntervalSec= LogRateLimitBurst= 选项限制了自身的日志生成速率,那么将会覆盖此处的设置。

  • RateLimitBurst 用于设置一个正整数,表示消息条数,默认值是10000条, 说明见RateLimitIntervalSec。

  • SystemMaxUse: 限制磁盘使用量, 也就是 /var/log/journal 的使用量。限制全部日志文件加在一起最多可以占用多少空间。默认值是空间的10%与4G空间两者中的较小者;

  • SystemKeepFree: 限制磁盘使用量, 也就是 /var/log/journal 的使用量, 但与 SystemMaxUse 有所不同, 它的值的意思是除日志文件之外,至少保留多少空间给其他用途。 systemd-journald 会同时使用 SystemKeepFree SystemMaxUse 两个值, 并且尽量限制日志文件的总大小,以同时满足这两个限制。默认值是空间的15%与4G空间两者中的较大者;

  • SystemMaxFileSize: 限制磁盘使用量, 也就是 /var/log/journal 的使用量。它限制的是单个日志文件的最大体积, 到达此限制后日志文件将会自动滚动。 默认值是对应的 SystemMaxUse值的1/8 , 这也意味着日志滚动 默认保留7个历史文件。日志大小 可以使用以1024为基数的 K, M, G, T, P, E 后缀, 分别对应于 1024, 1024², … 字节。

  • SystemMaxFiles,
    限制磁盘使用量, 也就是 /var/log/journal 的使用量。 限制最多允许同时存在多少个日志文件, 超出此限制后, 最老的日志文件将被删除, 而当前的活动日志文件 则不受影响。 默认值为100个。

  • RuntimeMaxUse
    限制内存使用量, 也就是 /run/log/journal 的使用量。限制全部日志文件加在一起最多可以占用多少空间。默认值是空间的10%与4G空间两者中的较小者;

  • RuntimeKeepFree
    限制内存使用量, 也就是 /run/log/journal 的使用量。除日志文件之外,至少保留多少空间给其他用途。systemd-journald 会同时RuntimeKeepFree与RuntimeMaxUse , 并且尽量限制日志文件的总大小,以同时满足这两个限制。默认值是空间的15%与4G空间两者中的较大者;

  • RuntimeMaxFileSize
    限制内存使用量, 也就是 /run/log/journal 的使用量。限制单个日志文件的最大体积, 到达此限制后日志文件将会自动滚动。 默认值是对应的 RuntimeMaxUse值的1/8 , 这也意味着日志滚动 默认保留7个历史文件。日志大小 可以使用以1024为基数的 K, M, G, T, P, E 后缀, 分别对应于 1024, 1024², … 字节。

  • RuntimeMaxFiles
    限制内存使用量, 也就是 /run/log/journal 的使用量。 限制最多允许同时存在多少个日志文件, 超出此限制后, 最老的日志文件将被删除, 而当前的活动日志文件 则不受影响。 默认值为100个。

  • MaxFileSec 日志滚动的时间间隔。 默认值是一个月, 设为零表示禁用基于时间的日志滚动策略。 可以使用 “year”, “month”, “week”, “day”, “h”, “m” 时间后缀, 若不使用后缀则表示以秒为单位。
    通常 并不需要使用基于时间的日志滚动策略, 因为由 SystemMaxFileSize 与 RuntimeMaxFileSize 控制的基于文件大小的日志滚动策略 已经可以确保日志文件的大小不会超标。

  • MaxRetentionSec 日志文件的最大保留期限。 默认值零表示不使用基于时间的日志删除策略。可以使用 “year”, “month”, “week”, “day”, “h”, “m” 时间后缀, 若不使用后缀则表示以秒为单位。
    当日志文件的最后修改时间(mtime)与当前时间之差, 大于此处设置的值时,日志文件将会被删除。 通常并不需要使用基于时间的日志删除策略,因为由 SystemMaxUse= 与 RuntimeMaxUse= 控制的基于文件大小的日志滚动策略 已经可以确保日志文件的大小不会超标。

  • SyncIntervalSec 向磁盘刷写日志文件的时间间隔, 默认值是五分钟。
    刷写之后,日志文件将会处于离线(OFFLINE)状态。 注意,当接收到 CRIT, ALERT, EMERG 级别的日志消息后, 将会无条件的立即刷写日志文件。 因此该设置仅对 ERR, WARNING, NOTICE, INFO, DEBUG 级别的日志消息有意义。

  • ForwardToSyslog 表示是否将接收到的日志消息转发给传统的 syslog 守护进程,默认值为”no”。 如果设为”yes”,但是没有任何进程监听对应的套接字,那么这种转发是无意义的。 此选项可以被内核引导选项 “systemd.journald.forward_to_syslog” 覆盖。

  • ForwardToKMsg 表示是否将接收到的日志消息转发给内核日志缓冲区(kmsg),默认值为”no”。 此选项可以被内核引导选项 “systemd.journald.forward_to_kmsg” 覆盖。

  • ForwardToConsole 表示是否将接收到的日志消息转发给系统控制台,默认值为”no”。 如果设为”yes”,那么可以通过下面的 TTYPath= 指定转发目标。 此选项可以被内核引导选项 “systemd.journald.forward_to_console” 覆盖。

  • ForwardToWall 表示是否将接收到的日志消息作为警告信息发送给所有已登录用户,默认值为”yes”。 此选项可以被内核引导选项 “systemd.journald.forward_to_wall” 覆盖。

  • MaxLevelStore 设置记录到日志文件中的最高日志等级,默认值为”debug”;可以被内核引导选项”systemd.journald.max_level_store”覆盖
    可以设为日志等级的名称, 也可以设为日志等级对应的数字: “emerg”(0), “alert”(1), “crit”(2), “err”(3), “warning”(4), “notice”(5), “info”(6), “debug”(7) 。 所有高于设定等级的日志消息都将被直接丢弃, 仅保存/转发小于等于设定等级的日志消息。

  • MaxLevelSyslog 设置转发给传统的 syslog 守护进程的最高日志等级, 默认值为”debug”;可以被内核引导选项”systemd.journald.max_level_syslog”覆盖
    选项说明同MaxLevelStore

  • MaxLevelKMsg 设置转发给内核日志缓冲区(kmsg)的最高日志等级,默认值为”notice”; 可以被内核引导选项”systemd.journald.max_level_kmsg”覆盖
    选项说明同MaxLevelStore

  • MaxLevelConsole 设置转发给系统控制台的最高日志等级,默认值为”info”;可以被内核引导选项”systemd.journald.max_level_console”覆盖
    选项说明同MaxLevelStore

  • MaxLevelWall 设置作为警告信息发送给所有已登录用户的最高日志等级,默认值为”emerg”;可以被内核引导选项”systemd.journald.max_level_wall”覆盖
    选项说明同MaxLevelStore

  • ReadKMsg 是否收集内核日志。 默认值 yes 表示从 /dev/kmsg 中读取内核产生的日志消息。

  • TTYPath 指定 ForwardToConsole=yes 时所使用的控制台TTY, 默认值是 /dev/console

  • LineMax
    在将日志流转化为日志记录时,每条日志记录最大允许的长度(字节)。 如果将单元的标准输出(STDOUT)/标准错误(STDERR)通过流套接字连接到日志中, 那么将会以换行符(“\n”, ASCII 10)与NUL字符(“\0”, ASCII 0)作为分割符, 把日志流切分成一条条独立的日志记录。 如果超过此处设置的长度之后仍然没有遇到分割符, 那么将会自动插入一个分割符,以强制将单行超长日志截断为多行。 此选项的值越大,每个日志流客户端日志守护进程占用的内存也越大(最大值等于此选项的值)。 另外,此选项的值太大也会造成与传统日志传输协议的不兼容(太长的日志无法封装在单个 AF_UNIX 或 AF_INET 报文内)。 此选项的值以字节为单位,同时也可以在数字的末尾加上 K, M, G, T 后缀(以1024为基准)。 默认值 48K 是一个足够大并且也能保持与传统日志传输协议兼容的值。 注意, 不能设为小于 79 的值(将被自动提升到79)。

    3.rsyslog配置

    上面说到, rsyslog 的配置主要是针对主队列之后的所有模块, 同时它目前支持两种语法配置, 个人觉得两种都有它的方便性,所以以下的配置一般都是混用。 rsyslog 的配置文件一般位于: /etc/rsyslog.conf , 他可以引用模块, 设置变量, 还有设置消息规则, 而规则就是指明一条消息要怎么输出, 也就是上面所说的过滤器+动作队列+输出模块的组合。

    3.1过滤器

    消息经过主队列后进来的第一步就是过滤器, 过滤器有时候被称为选择器(selector), 它用于过滤消息, 并把消息指向对应的操作队列, rsyslog 可以配置三种形式的过滤器, 分别为 Facility/Priority-based 过滤器, Property-based 过滤器以及 Expression-based 过滤器。

    3.1.1.Facility/Priority-based 过滤器

    在syslog中指定消息发送时必须带有 facility priority 字段, 对于 facility 我更想把它定义是 channel , 也就是渠道, Linux系统默认设置了24个 facility , 前16个都有专门的用途, 后8个交给用户自定义, 一般来说一个线上的机器只运行几个服务, 8个是够的, 以下是Linux支持渠道的列表:
    | 代码 | 名称 | 描述 |
    | —- | ——– | ———- |
    | 0 | kern | 内核 |
    | 1 | user | 用户级 |
    | 2 | mail | 邮件 |
    | 3 | daemon | 系统 |
    | 4 | auth | 安全与授权 |
    | 5 | syslog | 守护进程 |
    | 6 | lpr | 打印相关 |
    | 7 | news | 网络消息 |
    | 8 | uucp | uucp子系统 |
    | 9 | | 时钟 |
    | 10 | authpriv | 安全与授权 |
    | 11 | ftp | FTP |
    | 12 | - | NTP |
    | 13 | - | 日志审计 |
    | 14 | - | 日志报警 |
    | 15 | cron | 定时器 |
    | 16 | local0 | 用户自定义 |
    | 17 | local1 | 用户自定义 |
    | 18 | local2 | 用户自定义 |
    | 19 | local3 | 用户自定义 |
    | 20 | local4 | 用户自定义 |
    | 21 | local5 | 用户自定义 |
    | 22 | local6 | 用户自定义 |
    | 23 | local7 | 用户自定义 |

    而至于 priority , 大家都会十分熟悉, 只不过这里换了个说法, 设计上他代表的就是日志等级, 支持的日志等级列表有:
    |代码|名称|简写|描述|
    | —- | ————- | —— | —- |
    | 0 | Emergency | emerg | 紧急 |
    | 1 | Alert | alert | 报警 |
    | 2 | Critical | crit | 关键 |
    | 3 | Error | err | 错误 |
    | 4 | Warning | warn | 警告 |
    | 5 | Notice | notice | 通知 |
    | 6 | Informational | info | 消息 |
    | 7 | Debug | debug | 调试 |

    了解完了 Facility Priority 后就可以编写过滤器了, 过滤器编写十分简单, 他就是一个通配符, 通过 . 来区分 Facility Priority . 前面是 Facility , . 后面是 Priority
    例如通配符 \* 的意思是任何类型, 而 \*.\* 的意思就是匹配所有的 Facility Priority

    举个例子, 一般默认的配置里都有一段规则:

    1
    cron.alert /var/log/cron

    其中 corn.alert 就是规则中的 Facility Priority 过滤器,其中 Facility 为cron的, Priority 为alert, 一般来说就是crontab定期任务所产生的日志且任何等级等于或高于alert的日志(也就是代码值大于alert的日志等级)都会被指定的动作处理。
    如果希望只处理定义等级的日志,而不处理高于这个等级的日志,可以使用等号(=),例如:

    1
    cron.=alert /var/log/cron

    此外, 有一些子系统产生的日志可能没有Priority,那么可以使用关键字none,例如

    1
    news.none /var/log/messages

    除了星号和等号,过滤器中还可以使用逗号(,), 感叹号(!)和分号(;),逗号用于分隔多个Priority,而感叹号的作用是取反,例如:

    1
    cron.!info,!debug /var/log
    
    
    
    
        
    /cron

    意思是除了info和debug等级的日志外,都写入到/var/log/cron中。
    对于分号(;), 它表示或的意思, 如系统中的默认配置:

    1
    2
    3
    *.=debug;\
    auth,authpriv.none;\
    news.none;mail.none -/var/log/debug

    它表示的是 *.=debug , auth,authpriv.none , news.none mail.none 四组过滤器都不要马上写入到 /var/log/debug 中。

    3.1.2.Property-based 过滤器

    Property-based 过滤器是基于属性的过滤器,使我们可以根据不同的属性值进行日志处理。配置文件中常用的属性有:msg、hostname、fromhost、programname、timegenerated等。它的基本语法是:

    1
    PROPERTY,[!]COMPARE_OPERATION,"VALUE"

    其中 PROPERTRY 为属性, COMPARE_OPERATION 为操作符, VALUE 为值

    属性过滤器可用的操作符如下:

  • contains 检查属性值是否包含指定的字符串(大小写敏感)
  • contains_i 和上面一样,但忽略大小写
  • isequal 属性值是否等于目标字符串
  • startswith 属性值是否以某字符串开头(大小写敏感)
  • startswith_i 如上,但忽略大小写
  • regex 正则表达式匹配
  • ereregex 使用扩展正则表达式匹配
  • isempty 属性值是否为空
  • 一个具体的例子是:

    1
    2
    3
    4
    #日志信息中是否包含“error”字符串
    :msg, contains, "error"
    #主机名称是否相等
    :hostname, isequal, "host1"

    3.1.3.Expression-based 过滤器

    Expression-based 过滤器是一个基于表达式的过滤器,这个表达式是一个条件表达式,即当满足特定条件的时候,执行指定的操作。
    一般来说,表达式过滤器需要上面两种过滤器来结合使用。
    基本语法如下(这里使用了新式语法):

    1
    if expression_true then action else action

    else后续部分并不是必须的,它可以指定不满足条件的时候所执行的操作。
    表达式中可用的操作符有:

  • and、or、not
  • ==、!=、<>、<、>、<=、>= (!=和<>的作用基本相等)
  • contains
  • startswith、startswith_i(case-insensitive)
  • 一些例子如下::

    1
    2
    3
    4
    5
    #日志中包含error,保存到/var/log/errlog中
    if $msg contains 'error' then /var/log/errlog
    #如果要同时满足多个条件,使用and连接这些条件,整个表达式需要写在一行中
    if $syslogfacility-text == 'local0' and $msg startswith 'DEVNAME' and ($msg contains 'error1' or $msg contains 'error0') then /var/log/somelog
    if $syslogfacility-text == 'local0' and $msg startswith 'DEVNAME' and not ($msg contains 'error1' or $msg contains 'error0') then /var/log/somelog

    3.1.4.动作

    在上面介绍过滤器时, 其中有一段关于分号(;)配置示例:

    1
    2
    3
    *.=debug;\
    auth,authpriv.none;\
    news.none;mail.none -/var/log/debug

    其中 /var/log/debug 前面的 - 就是一个动作, 表示日志不会马上写入到文件中,而是缓存到内存中。
    常见的动作有:

  • 转发到另一台服务器
  • 写入数据库
  • 发送给用户
  • 如从 rsyslog 的默认配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    auth,authpriv.*	/var/log/auth.log
    *.*;auth,authpriv.none -/var/log/syslog
    cron.* /var/log/cron.log
    daemon.* -/var/log/daemon.log
    kern.* -/var/log/kern.log
    lpr.* -/var/log/lpr.log
    mail.* -/var/log/mail.log
    user.* -/var/log/user.log

    *.emerg :omusrmsg:*

    可以看出 rsyslog 会根据规则写入到各个指定的文件夹,动作前面有一个(-)横杠,表示日志不会马上写入到文件中,而是缓存到内存中,这样可以提高日志系统的性能,但有可能会造成日志的丢失;使用波浪符号(~)表示处理需要丢弃的日志。
    而omusrmsg则表示会把消息转发给指定的用户,用户登录时就可以收到该消息.默认为全部用户,如果需要指定用户则如下编写:

    1
    *.emerg	:omusrmsg:root,so1n

    它代表会把消息发送给用户root以及我(so1n)

    3.1.5.输出

    rsyslog 的输出有多种, 比如上面介绍过滤器时, 它的动作是把日志写入到哪个文件以及通过配置写入到数据库, 此外它还支持把日志通过远程传输的方式传到别的服务器。 rsyslog 提供三个远程日志传输方式:

  • UDP: 基于传统UDP协议进行远程日志传输,也是传统syslog使用的传输协议;
    可靠性比较低,但性能损耗最少, 在网络情况比较差, 或者接收服务器压力比较高情况下,可能存在丢日志情况。 仅在对日志完整性要求不是很高,以及在可靠的局域网环境下可以使用。

  • TCP: 基于传统TCP协议明文传输,需要回传进行确认,可靠性比较高;
    但在接收服务器宕机或者两者之间网络出问题的情况下,会出现丢日志情况。
    这种协议相比于UDP在可靠性方面已经好很多,并且rsyslog原生支持,配置简单,
    同时针对可能丢日志情况,可以进行额外配置提高可靠性,因此使用比较广。

  • RELP: RELP(Reliable Event Logging Protocol)是基于TCP封装的可靠日志消息传输协议;
    是为了解决 TCP UDP 协议的缺点而在应用层实现的传输协议,也是三者之中最可靠的。 不过需要多安装一个包 rsyslog-relp 以支持该协议。

    远程传输需要更改客户端和服务端的配置, 首先是客户端配置, 客户端配置需要先配置对应的传输模块:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 使用udp传输模块
    module(load="imudp")
    # 使用udp传输模块, 指定端口为514
    input(type="imudp" port="514")

    # 使用tcp传输模块
    module(load="imtcp")
    # 使用tcp传输模块, 指定端口为514
    input(type="imtcp" port="514")

    然后在动作那里配上如下格式

    1
    @[(zNUMBER)]HOST:[PORT]

    其中@代表udp(UDP 在主机名前加”@”;TCP 在主机名前加”@@”;RELP 在主机名前加”:omrelp:”),可选值zNUMBER设置了是否允许使用zlib对日志压缩(压缩级别1-9)。比如下面是一个使用relp发送的示例:

    1
    # local0.=warn,local1.* :omrelp:35.227.112.245:30514

    而服务端的配置也不复杂, 首先是UDP, 先在服务端创建配置文件:/etc/rsyslog.d/udp.conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # server configure
    $ModLoad imudp # 加载模块
    $UDPServerRun 10514 # 指定监听端口
    $AllowedSender UDP, 10.0.0.0/16 # 设置白名单, 也就是客户端的IP

    # 创建模板location和uformat
    # location模板是根据客户端IP存放到不同目录下,以日期命名文件
    $template location,/data/%fromhost-ip%/%$YEAR%-%$MONTH%-%$DAY%.log
    # uformat模板是自定义日志格式
    $template uformat,"%fromhost-ip% %msg%\n"

    # 使用属性过滤器匹配非本地传输的日志, 然后执行动作, 这个动作使用了上面的模板, 也就是按照指定的文件路径及格式保存
    :fromhost-ip, !isequal, "127.0.0.1" -?location;uformat
    # 最后一句指明了该日志已经被匹配过了, 不需要其他的动作队列处理, 其中& 表示已经匹配处理的内容,~ 表示不再进行其他处理, 是stop元素的一个符号代替
    & ~

    接着就是TCP, 在服务端创建配置文件:/etc/rsyslog.d/tcp.conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # server configure
    $ModLoad imtcp # 加载模块
    $InputTCPServerRun 20514 # 指定监听端口
    $AllowedSender TCP, 10.0.0.0/16 # 设置白名单
    # 根据客户端IP存放到不同目录下,以日期命名文件
    $template location,"/data/log/syslog/%fromhost-ip%.log"
    # 自定义日志格式
    $template uformat,"%timestamp% %fromhost-ip% %rawmsg%\n"
    # 把非本地传输的日志按照指定的文件路径及格式保存
    :fromhost-ip, !isequal, "127.0.0.1" -?location;uformat
    # & 表示已经匹配处理的内容,stop 表示不再进行其他处理
    & stop

    最后是RELP, 首先需要在客户端和服务端安装RELP

    1
    2
    apt-get update
    apt-get -y install rsyslog-relp

    客户端需要额外加载模块

    1
    module(load="omrelp")

    然后在服务端编辑配置(/etc/rsyslog.d/relp.conf)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # server configure
    $ModLoad imrelp # 加载模块
    $InputRELPServerRun 30514 # 指定监听端口
    # 根据客户端IP存放到不同目录下,以日期命名文件
    $template location,"/data/log/syslog/%fromhost-ip%.log"
    # 自定义日志格式
    $template uformat,"%timestamp% %fromhost-ip% %rawmsg%\n"
    # 把非本地传输的日志按照指定的文件路径及格式保存
    :fromhost-ip, !isequal, "127.0.0.1" -?location;uformat
    # & 表示已经匹配处理的内容,stop 表示不再进行其他处理
    & stop

    3.1.6模板

    模板 $template 常用于接收端, 可用来定义消息格式、文件名。 语法如下:

    1
    2
    $template <模板名>,<内容>,<可选项>
    $template MyTemplateName,"\7Text %property% some more text\n",<options>

    其中内容可以使用模板变量, 比如一条消息格式如下:

    1
    2
    <接收内容的时间> <发送者的hostname> <$InputFileTag> <原始消息%msg%>
    Dec 18 20:39:27 jumper-172-31-56-18 karltestdemoTag blala... dummy msg

    如果只需要显示原始消息,可设置

    1
    $template CleanMsgFormat,"%msg%\n"

    除了msg变量, 模板还支持很多变量,常见变量如下( 更多变量见官网文档 ):

    1
    2
    3
    4
    5
    6
    7
    8
    %msg%
    %syslogfacility%
    %HOSTNAME%
    %syslogpriority%
    %timereported:::date-mysql%
    %timegenerated:::date-mysql%
    %iut%
    '%syslogtag%'

    如果要生成动态文件名,并把日志写入该文件,那可以这样配置:

    1
    2
    $template DynamicFile,"/var/log/test_logs/%timegenerated%-test.log"    # timegenerated属性从日志信息中提取出消息的时间戳,这样可以为每个日志生成唯一文件名称。
    *.* ?DynamicFile

    3.1.7常用模块

    上面我们看到服务端会通过引入模块来拓展输出模块的功能, 比如输出到本地文件或者输出到 Redis , Kafka 等等…

    imfile模块

    imfile模块主要解决的问题是将非syslog日志转为syslog日志
    假设有个没有按照syslog协议生成的日志文件 /var/log/helloworld.log , 下面将用 imfile 模块把它加载到 rsyslog 中并处理:

    1
    2
    3
    4
    5
    6
    7
    # 加载imfile这个模块, 使用inotify模式来实时加载数据
    module(load="imfile" mode="inotify" PollingInterval="1")
    # 指定输入端使用了imfile, 配置是日志文件路径以及tag, severity, facility这些参数
    input(type="imfile" File="/var/log/helloworld.log" Tag="helloworld" Severity="error" Facility="local0")
    # 由于local有限,一般可以专门预留一个local用来接受带有Tag的日志,解决local不足的问题
    # 将helloworld的应用日志发送到远程服务器
    :programname, contains, "helloworld" @192.168.1.2

    其他imfile配置如下,如果使用input做输入端,input的参数基本是以下配置的名称去掉input前缀,
    更多配置见
    官网

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $InputFileName /path/to/file # 待监控的文件路径
    $InputFileTag tag # 文件唯一标识tag,最好保持唯一,用于接收端区分原始log文件,可以包含特殊字符,如":"、","等
    $InputFileStateFile /path/to/state/file
    # 需要保证发送端唯一,记录读取到哪儿,状态文件保存在$WorkDirectory,默认为 /var/lib/rsyslog
    # 如果某个要监控的文件名变化了,一定要重新设置该值
    $InputFileFacility facility # log类型,默认local0, local开头的表示自定义类型
    $InputFileSeverity severity # log级别:info,warning,默认notice
    $InputRunFileMonitor # 启动监控当前的文件,如果忘记这行,则啥事也不会发生
    $InputFilePollInterval seconds # 全局设置,默认轮询是10s
    $InputFilePersistStateInterval lines # 每多少行更新state文件状态
    $InputFileMaxLinesAtOnce number # 默认10240,如果在发送端,需要同时监控多个文件,会处理完当前文件特定行后,切换到下一个文件,避免一个文件一直占用处理,导致收集别的文件不及时。
    $InputFileBindRuleset ruleset # 绑定ruleset,可以把这个listener绑定到特点的规则(http://www.rsyslog.com/doc/v5-stable/concepts/multi_ruleset.html)

    在input的语法中stateFile参数已经不建议使用。原因在于,为了防止出现重复的state files,rsyslog会基于下面的规则自动生成这些文件:

  • 在具体的被监控文件前添加”imfile-state:”字符串
  • 文件名前的反斜杠会被替换为短横杠。
    尽量在文件刚生成时初始化日志,或者使用freshStartTail参数,然rsyslog并不建议使用该参数…
  • omprog模块

    omprog模块可以让日志通过管道的形式发送给程序(以每行日志分开发送),然后再由程序处理日志(类似于Map-Reduce)。

    以下是官网的一个例子, 例子中该模块会根据配置执行Python程序,并通过stdin的形式发送到Python程序,Python程序会一直运行,等待数据的到来,如果收到数据则处理数据(这个例子是写入数据库), 如果程序终止,则重新启动,如果rsyslo终止,则程序的stdin会捕获到EOF,此时程序会终止.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # 配置
    module(load="omprog")

    # 省略了过滤器, 只说一下语法
    action(type="omprog"
    name="db_forward"
    binary="/usr/share/logging/db_forward.py"
    confirmMessages="on" # 它告诉rsyslog等待程序确认其初始化以及收到的每条消息。该设置的目的是防止由于数据库连接失败而导致日志丢失。如果程序无法将日志写入数据库,它将通过stdout向rsyslog返回否定确认
    confirmTimeout="30000" # 指定超时时间内未收到程序的响应,则rsyslog将终止并重新启动它
    queue.type="LinkedList"
    queue.saveOnShutdown="on"
    queue.workerThreads="5" # 使用具有(最多)5个工作线程的专用磁盘辅助队列,以避免在高负载时影响其他日志目标
    action.resumeInterval="5" # Rsyslog将失败的日志保留在队列中,并在5秒后再次将其发送给程序。
    killUnresponsive="on"
    output="/var/log/db_forward.log" # 程序将错误详细信息写入stderr,rsyslog捕获并写入/var/log/db_forward.log
    )

    除了使用模块接收日志,还可以用程序读取日志(建议用上inotify),或者开个端口接收日志

    3.2.各个日志文件简介