Logstash的多行输入以及自定义Pattern

前言

通常我们仅仅用logstash处理access log。这个时候logstash的配置如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
input {
stdin {}
}

filter {
grok {
match => { "message" => "%{HTTPD_COMMONLOG}" }
}
}

output {
stdout { codec => rubydebug }
}

我们知道,logstash的config内容分为input,filter和output三个部分。
这三个部分都集成了很多插件。我们可以从不同的地方进行input,通过filter对data进行整理,然后output。

在实际问题中,我遇到几个问题。

  • log的单位并不是一行,而是多行。

  • 我可以自定义match的pattern吗?

  • grok的match里面的message能省略掉吗?

接受以多行为单位输入的Log

我们知道,apache access log 和 nginx access log 以及大多数log都是以单行为单位输出的。

比如

1
[Fri Sep 09 10:42:29.902022 2011] [core:error] [pid 35708:tid 4328636416] [client 72.15.99.187] File does not exist: /usr/local/apache2/htdocs/favicon.ico

input的大多数插件默认是接受单行单位log输入。我们可以用multiline这个codec plugin 来接受多行输入。

1
2
3
4
5
6
7
8
input {
stdin {
codec => multiline {
pattern => "^\s"
what => "previous"
}
}
}

上面的配置的意思是,当以空格开头的行,我们把它和上面一行归并到一个log中。具体的其他用法可以查看multiline plugin的文档。

自定义grok的pattern

grok里面的match的message,其实是定义在这里的各种pattern。
我们可以自定义pattern。

形式如下

1
2
3
4
5
USERNAME [a-zA-Z0-9._-]+
USER %{USERNAME}
EMAILLOCALPART [a-zA-Z][a-zA-Z0-9_.+-=:]+
EMAILADDRESS %{EMAILLOCALPART}@%{HOSTNAME}
INT (?:[+-]?(?:[0-9]+))

将这些pattern保存在文件中,然后在logstash的config中制定读取pattern的目录即可。

1
2
3
4
5
6
filter {
grok {
patterns_dir => ["/usr/local/logstash/patterns"]
match => { "message" => "%{USER: user} %{INT: age} %{EMAILADDRESS: email}"}
}
}

这样,我们输出的时候可以获得到message,user,age,email这几个field。

省略grok解析出的一些field

可以用mutate这个plugin来移除一些field。

1
2
3
mutate {
remove_field => [ "message" ]
}

这样output的结果中就不会有message这个field了。

感想

logstash提供了丰富的插件。在遇到问题的时候除了善于搜索还要有耐心阅读插件文档,必要时候需要写demo进行测试。

使用ELK来可视化微信群聊记录

自从有了itchat这个神器之后,一直在考虑能做些什么东西。
先后做了发送撤回消息的bot和推荐spotify音乐的bot,到头来,所有的聊天记录都静静地躺在我的redis里面,需要利用起来也十分麻烦。
于是最近,我突然想到,既然ELK能用来可视化log,那何尝不能用来可视化一下微信的聊天记录呢。

需要的东西

  1. ELK全家桶。

  2. Redis,用来通过在itchat中pub,在logstash中sub来获取微信聊天内容。

  3. Itchat。基于web微信的第三方SDK。

Step by Step

首先自然需要写个Itchat的脚本来获取群消息。例子看文档已经足够。

接下来假设我们获取到一个类似

1
message = {"msg_from": "gyorou", "msg_content": "fuck the world"}

的消息内容。我们需要把 message传递给logstash,再由logstash写入elasticsearch。

我想到的方法是使用Redis的pubsub方法。

先把message dump成 string的形式,交给redis publish出去。

1
2
3
4
5
6
7
8
import json
import redis
import itchat

# 省略
redis = redis.client(...)
message = json.dumps(message)
redis.publish('wechat_message', message)

为了接收publish的message,logstash应该有如下的输入配置。

1
2
3
4
5
6
7
input {
redis {
data_type => "channel"
codec => "json"
key => "wechat_message"
}
}

注意点是需要codec => "json"这一项,因为,我们publish的是一个string,我们要需要将其按照json的格式解析出来里面的内容。

接下来,配置logstash的输出。

1
2
3
4
5
6
output {
elasticsearch {
index => "from_my_wechat"
doucument_type => "chatlog"
}
}

以上两项分别对应elasticsearch的index和type。这样我们可以通过localhost:9200/from_my_wechat/chatlog的形式访问和操作被索引的聊天内容。

最后打开kibana,把我们的索引项目称from_my_wechat填进去就大功告成了。
随便点开图标一览,画两个图呗,比如统计一下谁特么发言最多这种。


To do

  • 对中文的field需要进行分词。
  • 对占用较大的无用field需要整理移除。

以上。

ELK docker-compose 全家桶实现Nginx access log 可视化

ELK docker-compose 全家桶实现Nginx access log可视化

Elasticsearch Logstash 和 Kibana 组成的log可视化工具链基本已经成为低成本服务的标配。
但是这三个东西繁杂的配置有时候还是会让人奔溃。

尝试寻找可用docker container来解决繁杂的配置问题是一种常用的偷懒思路。
今天从docker-elk这个全家桶开始尝试简单配置即用的log可视化构成。

clone下来之后看说明,其实已经差不多可用了。
修改一下logstash的config。使其能够对应nginx的log格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
input {
tcp {
port => 5000
}
}

## Add your filters / logstash plugins configuration here
filter {
grok {
match => [ "message" , "%{COMBINEDAPACHELOG}+%{GREEDYDATA:extra_fields}"]
overwrite => [ "message" ]
}

mutate {
convert => ["response", "integer"]
convert => ["bytes", "integer"]
convert => ["responsetime", "float"]
}

geoip {
source => "clientip"
target => "geoip"
add_tag => [ "nginx-geoip" ]
}

date {
match => [ "timestamp" , "dd/MMM/YYYY:HH:mm:ss Z" ]
remove_field => [ "timestamp" ]
}

useragent {
source => "agent"
}
}

output {
elasticsearch {
hosts => "elasticsearch:9200"
}
}

好了已经差不多能跑了试一下。

1
2
3
$ sudo sysctl -w vm.max_map_count=262144
$ docker-compose up -d
nc localhost 5000 < /path/to/logfile.log

果然打开localhost:5601就可以用kibana看到各种统计数据和图表了。
但是默认的设置有个问题,就是无法实时对acess log 进行监控,我们每次都需要跑一下 nc localhost 5000 < /path/to/logfile.log 把log 喂给logstash。这样是很蛋疼的。我们希望logstash能够实时watch log文件然后随时更新数据。

思路很简单,我们可以把/var/log/nginx/access.log symlink到当前目录,然后把目录挂在到docker container中。

1
2
3
$ cd docker-elk
$ mkdir access_log
$ sudo ln -s /var/log/nginx/access.log ./access_log/access.log

修改logstash的config, 将input block 改成 file block。

1
2
3
4
5
6
7
input {
file {
path => "/access_log/access.log"
start_position => "beginning"
}
}
#以下省略

修改docker-compose.yml 将 ./access_log 挂到 /access_log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#以上省略
logstash:
build: logstash/
command: -f /etc/logstash/conf.d/
volumes:
- ./logstash/config:/etc/logstash/conf.d
- ./access_log:/access_log
ports:
- "5000:5000"
networks:
- docker_elk
depends_on:
- elasticsearch

#以下省略

重新build一下开跑。

1
$docker-compose stop && docker-compose up -d

再此打开localhost:5601,发现特么什么狗屁都没有了。这个是为什么?
翻看docker-compose启动时候的console log,发现logstash说自己没有访问/access_log/access.log的权限。

我们执行docker exec -it dockerelk_logstash_1 /bin/bash 进入到container内部一探究竟。

1
2
3
4
5
6
7
8
root@7c7edc22ff14:/# ps aux |grep logstash
logstash 1 10.2 24.7 4197892 507684 ? Ssl 13:36 2:50 /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+DisableExplicitGC -Djava.awt.headless=true -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -Xmx1g -Xms256m -Xss2048k -Djffi.boot.library.path=/usr/share/logstash/vendor/jruby/lib/jni -Xbootclasspath/a:/usr/share/logstash/vendor/jruby/lib/jruby.jar -classpath : -Djruby.home=/usr/share/logstash/vendor/jruby -Djruby.lib=/usr/share/logstash/vendor/jruby/lib -Djruby.script=jruby -Djruby.shell=/bin/sh org.jruby.Main /usr/share/logstash/lib/bootstrap/environment.rb logstash/runner.rb -f /etc/logstash/conf.d/
root@7c7edc22ff14:/# cd /access_log
root@7c7edc22ff14:/access_log# ls -la
total 24
drwxrwxr-x 2 1000 1000 4096 Feb 13 13:15 .
drwxr-xr-x 66 root root 4096 Feb 13 13:36 ..
-rw-rw---- 2 www-data adm 15198 Feb 13 13:37 access.log

跑logstash的是用户logstash,而logstash用户没有access.log的访问权限。
在docker container中挂在的目录,其用户权限是和host相同的。所以我们必须在host中增加logstash用户的权限。
退出docker container,在host中我们尝试个logstash用户添加access.log的访问权限。

1
2
$ sudo setfacl -m u:logstash:r access.log
setfacl: Option -m: Invalid argument near character 6

host系统中并没有logstash用户,所以map不到相关的uid。那就去container中找一下logstash的uid就是了。

1
2
3
4
5
root@7c7edc22ff14:/# cat /etc/passwd

...
...
logstash:x:999:999:LogStash Service User:/usr/share/logstash:/usr/sbin/nologin

container中logstash uid是999,我们在host中直接用uid设置权限。

1
$sudo setfacl -m u:999:r access.log

重新build一下,然后docker-compose up, 再打开kibana,这次终于看到了期待的结果。