Logstash 分布式日志管理

背景

日常部署服务时,为了达到 High Available 或是 Load Balance 的目的,经常会在多个服务器部署多个服务实例。不同实例的日志一般都是直接写入本地的日志文件中,一旦出现问题或是日常检查,运维人员需要登录不同的服务器获取日志详情,十分低效。

因此,希望能够通过Logstash读取本地日志文件,并进行简单的处理,将非格式文本解析为格式化数据,最终输出到 ES 或是 MySQL 等数据库管理工具进行同一管理查看,提升运维人员的工作效率。

查看官网,Logstash 提供了很多插件,对于日志解析而言,我觉得最主要的是 file 插件以及 log4j 插件。file 插件的优势在于能够解析各种非格式化文本,而 log4j 插件已经做好了足够的适配工作,但是只针对于 log4j 日志。由于此次解析的数据是 python log 数据,因此选择 file 插件来解析 log 文本数据。此外,想要将非格式化 log 文本转化为结构化数据,还需要配合 grok 正则表达式插件以及 date 时间转化插件等。

File 插件

介绍

File

Stream events from files, normally by tailing them in a manner similar to tail -0F but optionally reading them from the beginning.

Normally, logging will add a newline to the end of each line written. By default, each event is assumed to be one line and a line is taken to be the text before a newline character. If you would like to join multiple log lines into one event, you’ll want to use the multiline codec. The plugin loops between discovering new files and processing each discovered file. Discovered files have a lifecycle, they start off in the “watched” or “ignored” state. Other states in the lifecycle are: “active”, “closed” and “unwatched”

File 插件可以从指定的目录或者文件读取内容,输入到管道处理,也算是logstash的核心插件了,大多数的使用场景都会用到这个插件。简单的使用方法如下:

file {
    path => ["/root/syslog.txt"]
    start_position => "beginning"
    # 如果禁用 sincedb(将其设置为 /dev/null),则 logstash 将从头开始读取与路径匹配的所有文件
    # /dev/null 是 linux 系统中的存在的黑洞文件,因此 sincedb 将不会生成
    sincedb => /dev/null
}

基本配置项

常用的配置项如下:

  • path:需要读取的文件,可以设置通配符以及目录;
  • start_position:设置 Logstash 将从哪里开始解析文件;
  • delimiter:该配置设置文档的分隔符,即如何切割文档,默认是 \n。但是配置的时候不可以直接使用转义字符配置,Logstash 无法识别。如果你要用两个换行符分割,那么就要敲两次回车;
  • sincedb:该文件将记录 Logstash 处理 file 文件的进度,以便下次处理的时候从上次的偏移量继续处理。sincedb 默认是按照文件的 inode 等信息自动生成,其中记录了inode、主设备号、次设备号以及读取的位置。因此,如果一个文件仅仅是重命名,那么它的 inode 以及其他信息就不会改变,因此也不会重新读取文件的任何信息;
  • sincedb_write_interval:Logstash 每隔多久写一次 sincedb 文件,默认是 15 秒。
  • close_older:一个已经监听中的文件,如果超过这个值的时间内没有更新内容,就关闭监听它的文件句柄。默认是 3600 秒,即一小时。
  • ignore_older:在每次检查文件列表的时候,如果一个文件的最后修改时间超过这个值,就忽略这个文件。默认是 86400 秒,即一天。
  • stat_interval:Logstash 每隔多久检查一次被监听文件状态(是否有更新),默认是 1 秒。

Grok 插件

介绍

Grok is a great way to parse unstructured log data into something structured and queryable.

This tool is perfect for syslog logs, apache and other webserver logs, mysql logs, and in general, any log format that is generally written for humans and not computer consumption.

Logstash ships with about 120 patterns by default. You can find them here: https://github.com/logstash-plugins/logstash-patterns-core/tree/master/patterns. You can add your own trivially. (See the patterns_dir setting)

If you need help building patterns to match your logs, you will find the http://grokdebug.herokuapp.com and http://grokconstructor.appspot.com/ applications quite useful!

日志文件大部分都是非结构化的文本数据,需要人工去匹配文本规则,剥离出关心的数据,并结构化存储,方便搜索及排错。Grok 是 Logstash 最重要的插件,利用正则表达式来解析非结构化数据。你可以在Grok 里预定义好命名正则表达式,在稍后 Grok 参数或者其他正则表达式里引用它。

预定义 pattern

Grok 过滤器器附带了各种正则表达式和模式,用于你可以在日志中遇到的常见数据类型和表达式,例如 IP,用户名,电子邮件,主机名等。Logstash 读取日志时,可以使用这些模式来找到我们想要变成结构化字段的日志消息的语义元素。在 Logstash-8.4.3 中,附带的预定义 pattern 保存在 logstash-8.4.3/vendor/bundle/jruby/2.6.0/gems/logstash-patterns-core-4.3.4/patterns/legacy/grok-patterns,用户可以将想要配置的正则表达式模板也追加在其中。下面列出常用的 Grok 预定义 pattern:

USERNAME [a-zA-Z0-9._-]+
USER %{USERNAME}
INT (?:[+-]?(?:[0-9]+))
BASE10NUM (?<![0-9.+-])(?>[+-]?(?:(?:[0-9]+(?:\.[0-9]+)?)|(?:\.[0-9]+)))
NUMBER (?:%{BASE10NUM})
BASE16NUM (?<![0-9A-Fa-f])(?:[+-]?(?:0x)?(?:[0-9A-Fa-f]+))
BASE16FLOAT \b(?<![0-9A-Fa-f.])(?:[+-]?(?:0x)?(?:(?:[0-9A-Fa-f]+(?:\.[0-9A-Fa-f]*)?)|(?:\.[0-9A-Fa-f]+)))\b

POSINT \b(?:[1-9][0-9]*)\b
NONNEGINT \b(?:[0-9]+)\b
WORD \b\w+\b
NOTSPACE \S+
SPACE \s*
DATA .*?
GREEDYDATA .*
QUOTEDSTRING (?>(?<!\\)(?>"(?>\\.|[^\\"]+)+"|""|(?>'(?>\\.|[^\\']+)+')|''|(?>`(?>\\.|[^\\`]+)+`)|``))
UUID [A-Fa-f0-9]{8}-(?:[A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12}

基本语法

因此,Grok 过滤器通过将文本模式组合成与您的日志匹配的内容来工作。你可以通过定义 Grok 模式来告诉 Grok 要搜索什么数据:%{SYNTAX:SEMANTIC}。其中,SYNTAX 是已经预定义好的正则表达式 pattern,SEMANTIC 是将匹配得到的结果放到 Logstash 中的 field。例如:

# 10.124.5.222     RUNNING
filter { 
  grok { 
    # 匹配得到 10.124.5.22 以及 RUNNING 信息
    # 分别存储到 client 以及 status 中
    match => { "message" => "%{IP:client}\s+%{WORD:status}" } 
  }
}

此外,在匹配规则中,我们仍然可以使用正则表达式的语法规则去匹配字符,如如果不确定空格数量,可以使用 \s+ 代表至少有一个空格。

多项选择

有时候会碰上一个日志有多种可能格式的情况。这时候要写成单一正则就比较困难,或者全用 | 隔开又比较丑陋。这时候,Logstash 的语法提供了一个有趣的解决方式。

文档中,都说明 logstash/filters/grok 插件的 match 参数应该接受的是一个 Hash 值。但是因为早期的 Logstash 语法中 Hash 值也是用 [] 这种方式书写的,所以其实现在传递 Array 值给 match 参数也完全没问题。所以,这里其实可以传递多个正则来匹配同一个字段:

match => [
    "message", "(?<request_time>\d+(?:\.\d+)?)",
    "message", "%{SYSLOGBASE} %{DATA:message}",
    "message", "(?m)%{WORD}"
]

测试

对于 match 模板,编写的时候需要调试,因此需要一些测试工具,推荐:grokdebug。

测试

日志读取

日志文件格式:

2022-10-14 15:32:33,206      topology.py[line:62 ]    DEBUG: 
--加载拓扑
--Request data:
--Response result:True
--Response data:

首先测试 file 的读取功能,配置如下:

input {
    file {
        path => ["/root/syslog.txt"]
        start_position => "beginning"
        sincedb => /dev/null
        # 在 UNIX 上,像 \r\n 这样的字符串不会被解释为转义
        # 要将分隔符设置为换行符,需要使用文字换行符,即手动换行
        delimiter => "

"
    }
}

得到结果:

{
       "message" => "2022-10-14 16:12:33,454      topology.py[line:62 ]    DEBUG: \n--加载拓扑\n--Request data:\n--Response result:True\n--Response data:",
      "@version" => "1",
          "host" => {
        "name" => "localhost.localdomain"
    },
         "event" => {
        "original" => "2022-10-14 16:12:33,454      topology.py[line:62 ]    DEBUG: \n--加载拓扑\n--Request data:\n--Response result:True\n--Response data:"
    },
           "log" => {
        "file" => {
            "path" => "/root/syslog.txt"
        }
    },
    "@timestamp" => 2022-10-20T10:14:28.793590201Z
}

日志解析

根据日志的格式,编写了 match 的 pattern,使用的都是 Grok 已定义的模板,也不多作解释:

filter {
  grok {
    match => {
      "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{DATA:file_name}\s+%{LOGLEVEL:level}:\s+\n--%{DATA:info}\n--Request data:\n*%{DATA:request_data}\n--Response result:%{DATA:response_result}\n--Response data:\n*%{GREEDYDATA:response_data}"
    }
  }
  mutate {
    remove_field => ["message"]
  }
}

output {
  elasticsearch {
    hosts => ["10.124.5.222:9200"]
    # 指定特定的 index
    index => "srte_syslog"
    timeout => 300
  }
}

下面是解析得到的结果,发现有一些 log 解析失败了,仔细分析之后发现 pattern 不太符合,需要增加解析 pattern,有一些额外的信息也不需要入 es,可以直接在 Logstash 直接删除:

filter {
  grok {
    match => {
      "message" => [
        "%{TIMESTAMP_ISO8601:timestamp}\s+%{DATA:file_name}\s+%{LOGLEVEL:level}:\s+\n--%{DATA:info}\n--Request data:\n*%{DATA:request_data}\n--Response result:%{DATA:response_result}\n--Response data:\n*%{GREEDYDATA:response_data}",
        "%{TIMESTAMP_ISO8601:timestamp}\s+%{DATA:file_name}\s+%{LOGLEVEL:level}:\s+\nApi request:\n\s*url=%{DATA:api_url}\n\s*method=%{WORD:method}\n\s*data=%{DATA:request_data}\n\s*json=%{GREEDYDATA}",
        "%{TIMESTAMP_ISO8601:timestamp}\s+%{DATA:file_name}\s+%{LOGLEVEL:level}:\s+\nApi request completed:\n\s*status_code=%{NUMBER:status_code}\n\s*result=%{GREEDYDATA:response_data}"
      ]
    }
  }
  mutate {
    remove_field => ["message", "@version", "host", "log", "event"]
  }
}

具体结果见下图:

分布式日志管理

目前,简单的 web server 结构如下:

用户访问 web 应用时,会被 nginx 负载均衡到实际的服务器上,用户的信息会被 server 和自身的信息一同被记录在本地,不同的 server 之间的日志是互不干预的。在处理分布式日志的时候, log 文件自带时间,Logstash 通过解析时间戳,就可以获得整个 web server 集群的时序化日志。

因此,Logstash 的部署方式是每台 server 上部署一个 Logstash 实例,直接解析本地的 log 日志文件。Output 设置为同一个 ES 集群 index,这样就可以实现分布式日志的管理了。因为每台 server 的 log 日志数据是隔离,也就无需关心数据可能出现的冗余冲突的问题,只需要在输出的数据中添加 server 以及 timestamp 信息,就能很好地管理不同 server 的分布式日志。

参考

  1. Logstash 官方文档

  2. Logstash 最佳实践文章来源地址https://uudwc.com/A/BvkPX

原文地址:https://blog.csdn.net/qq_43619899/article/details/127490339

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

h
上一篇 2023年09月25日 02:23
QT基础介绍
下一篇 2023年09月25日 02:30