Logstash作为ELK技术栈中重要的一员,本文介绍Logstash的一些使用。
官网 Logstash:收集、解析和转换日志 | Elastic
拉到下方有 Logstash文档 和 Logstash论坛 以及 下载 的链接。
Logstash 是一个开源的数据收集引擎。它具有实时的数据传输能力,可以按照我们定制的规范来做数据的收集、解析和存储。也就是说Logstash有3个核心组成部分,分别是数据收集、数据解析和数据转存。这个三个部分组成了一个类似于管道的数据流,由输入端进行数据的采集,管道本身做数据的过滤和解析,输出端把过滤和解析后的数据输出到目标数据库中。
大致翻一下官方手册可以看到其功能还是非常强大的。Logstash Reference [8.4] | Elastic
能用上的就有不少,如 elasticsearch/redis/file/http/kafka/tcp/udp/stdout/websocket/mongodb等。
#Input有如下插件
Input plugins
azure_event_hubs
beats
cloudwatch
couchdb_changes
dead_letter_queue
elastic_agent
elasticsearch
exec
file
ganglia
gelf
generator
github
google_cloud_storage
google_pubsub
graphite
heartbeat
http
http_poller
imap
irc
java_generator
java_stdin
jdbc
jms
jmx
kafka
kinesis
log4j
lumberjack
meetup
pipe
puppet_facter
rabbitmq
redis
relp
rss
s3
s3-sns-sqs
salesforce
snmp
snmptrap
sqlite
sqs
stdin
stomp
syslog
tcp
twitter
udp
unix
varnishlog
websocket
wmi
xmpp
#output有如下插件
Output plugins
boundary
circonus
cloudwatch
csv
datadog
datadog_metrics
dynatrace
elastic_app_search
elastic_workplace_search
elasticsearch
email
exec
file
ganglia
gelf
google_bigquery
google_cloud_storage
google_pubsub
graphite
graphtastic
http
influxdb
irc
java_stdout
juggernaut
kafka
librato
loggly
lumberjack
metriccatcher
mongodb
nagios
nagios_nsca
opentsdb
pagerduty
pipe
rabbitmq
redis
redmine
riak
riemann
s3
sink
sns
solr_http
sqs
statsd
stdout
stomp
syslog
tcp
timber
udp
webhdfs
websocket
xmpp
zabbix
https://www.elastic.co/guide/en/logstash/current/index.html
这里就不多说了,如下确保java环境是ok的即可。
curl -L -O https://artifacts.elastic.co/downloads/logstash/logstash-7.3.0.tar.gz
tar -xzvf logstash-7.3.0.tar.gz
cd logstash-7.3.0
#启动logstash,-e选项指定输入输出;这里输入采用标准输入,标准输出作为输出。
./bin/logstash -e 'input { stdin { } } output { stdout {} }'
启动完毕后输入字符串,可以得到响应的输出,如下说明安装成功。
更多安装方式见 如何安装 Elastic 栈中的 Logstash_Elastic 中国社区官方博客的博客-CSDN博客
Logstash管道有两个必须元素,输入(inputs)和输出(outputs),以及一个可选元素filters。
将其中的 config.reload.automatic 选项改为 true。其好处是每次改完配置文件后不需要重启Logstash,会自动加载变化后的配置文件。
在config目录下(其实啥目录都行),创建test.conf文件,内容如下。
input 监听9900端口的的tcp数据
output 打印到标准输出
input {
tcp {
port => 9900
}
}
output {
stdout {
codec => rubydebug #以rubydebug格式在控制台输出
#codec => json #以json格式在控制台输出
}
}
执行logstash
./bin/logstash -f ./config/test.conf
另外起一个终端,通过nc命令向9900端口发送数据。
echo 'hello logstash!!!!!!!' | nc localhost 9900
#注:别的机器发也行,ip位置指定为logstash所在的ip即可
效果如下:
如下图:上方为修改配置文件后自动加载的日志记录、下方位置为改成json格式之后的输出。
input {
tcp {
port => 9900
}
}
filter {
grok {
match => { "message" => "%{COMBINEDAPACHELOG}" }
}
}
output {
stdout {
codec => rubydebug #以rubydebug格式在控制台输出
}
}
创建一个文本文件test.log,内容如下:
14.49.42.25 - - [12/May/2019:01:24:44 +0000] "GET /articles/ppp-over-ssh/ HTTP/1.1" 200 18586 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b1) Gecko/20091014 Firefox/3.6b1 GTB5"
14.49.42.25 - - [12/May/2019:01:24:15 +0000] "GET /articles/openldap-with-saslauthd/ HTTP/1.1" 200 12700 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b1) Gecko/20091014 Firefox/3.6b1 GTB5"
14.49.42.25 - - [12/May/2019:01:24:06 +0000] "GET /articles/dynamic-dns-with-dhcp/ HTTP/1.1" 200 18848 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b1) Gecko/20091014 Firefox/3.6b1 GTB5"
14.49.42.25 - - [12/May/2019:01:24:54 +0000] "GET /articles/ssh-security/ HTTP/1.1" 200 16543 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b1) Gecko/20091014 Firefox/3.6b1 GTB5"
14.49.42.25 - - [12/May/2019:01:25:25 +0000] "GET /articles/week-of-unix-tools/ HTTP/1.1" 200 9313 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b1) Gecko/20091014 Firefox/3.6b1 GTB5"
14.49.42.25 - - [12/May/2019:01:25:33 +0000] "GET /blog/geekery/headless-wrapper-for-ephemeral-xservers.html HTTP/1.1" 200 11902 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b1) Gecko/20091014 Firefox/3.6b1 GTB5"
66.249.73.135 - - [12/May/2019:01:25:58 +0000] "GET /misc/nmh/replcomps HTTP/1.1" 200 891 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
114.80.81.51 - - [12/May/2019:01:26:10 +0000] "GET /blog/geekery/xvfb-firefox.html HTTP/1.1" 200 10975 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b1) Gecko/20091014 Firefox/3.6b1 GTB5"
46.105.14.53 - - [12/May/2019:01:26:18 +0000] "GET /blog/tags/puppet?flav=rss20 HTTP/1.1" 200 14872 "-" "UniversalFeedParser/4.2-pre-314-svn +http://feedparser.org/"
61.55.141.10 - - [12/May/2019:01:26:17 +0000] "GET /blog/tags/boredom-induced-research HTTP/1.0" 200 17808 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b1) Gecko/20091014 Firefox/3.6b1 GTB5"
执行命令
head -n 1 test.log | nc localhost 9900
效果如下:
也就是说通过Grok过滤器,他会通过正则表达式进行匹配把我们输入的非结构化数据变为一个结构化的数据。从上面可以看到其提取出来了request、port、host、clientip等字段。
更多关于Grok过滤器参见 Logstash:Grok filter 入门_Elastic 中国社区官方博客的博客-CSDN博客
前面知道了clientip,但是不知道这个IP是从哪儿来的,即具体的国家、经纬度地理信息。为此可以使用Geoip过滤器。
input {
tcp {
port => 9900
}
}
filter {
grok {
match => { "message" => "%{COMBINEDAPACHELOG}" }
}
geoip {
source => "clientip"
}
}
output {
stdout { }
}
执行
head -n 1 test.log | nc localhost 9900
效果如下
我们注意到agent字段比较长。但是并没有明确区分浏览器、语言等字段。为此可以使用useragent过滤器来进一步丰富。
注意到bytes是一个字符串类型,但实际可能希望他是一个数值。为此可以使用mutate:convert过滤器。
input {
tcp {
port => 9900
}
}
filter {
grok {
match => { "message" => "%{COMBINEDAPACHELOG}" }
}
mutate {
convert => {
"bytes" => "integer"
}
}
geoip {
source => "clientip"
}
useragent {
source => "agent"
target => "useragent"
}
}
output {
stdout { }
}
不过agent看起来并没有提取相应字段
前面所有的输出都是stdout,也就是输出到Logstash运行的console(控制台)。下面演示将数据输出到Elasticsearch。
注:本机已经部署好了elasticsearch和kibana,且已经成功启动可用。
input {
tcp {
port => 9900
}
}
filter {
grok {
match => { "message" => "%{COMBINEDAPACHELOG}" }
}
mutate {
convert => {
"bytes" => "integer"
}
}
geoip {
source => "clientip"
}
useragent {
source => "agent"
target => "useragent"
}
date {
match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
}
}
output {
stdout { }
elasticsearch {
hosts => ["localhost:9200"]
#user => "elastic"
#password => "changeme"
}
}
关于输入我们保留了stdout和elasticsearch,前者主要是为了方便调试。
执行命令
head -n 1 test.log | nc localhost 9900
在kibana中执行如下指令即可查看到写入es的数据。
#统计数据条数
GET logstash/_count
#访问数据
GET logstash/_search
#也可以看到对应的logstash命名的索引
GET _cat/aliases
注意:有的ES实例的ip可能是禁止ping的,我们可以通过curl指令判断logstash所在机器是否能成功访问到ES实例。参见 ES(elasticsearch)常用的curl命令_焱齿的博客-CSDN博客_curl查询es某个索引数据
关于具体怎么配置,在官方说明文档中找到input、output等对应的elasticsearch插件看看样例就都知道了。
Elasticsearch input plugin | Logstash Reference [8.4] | Elastic
(1)input设置elasticsearch
根据官方使用手册,列出一些常用的参数。
1、hosts:指定一个或多个你要查询的ES的主机。每个主机可以是 IP,HOST,IP:port,或者 HOSY:port。默认的端口是9200.
2、index:指定作用的索引。所有索引 "*" 即可
3、query:指定查询语句
4、proxy:设置为正向的http代理。空的话默认为没有设置代理。
5、request_timeout_seconds:秒单位的单次请求ES的最大时间,当单次请求的数量十分巨大的时候,超时极易发生。数值默认为60s。
6、schedule:顾名思义 定期的运行cron格式的时间表,例如 "* * * * *" 表示每分钟执行一次查询。默认情况认为无时间表,此时仅执行一次。
7、scroll: 参数控制scroll请求两次间隔间的保活时间(单位是秒),并且启动scroll过程。超时适用于每次往返即上一次滚动请求到下一个滚动请求之间. 默认值1m
8、size:设置每次scroll返回的最大消息条数。默认 1000
9、docinfo:如果设置的话在事件中就会包括诸如index,type,docid等文档信息。bool值默认为false
10、docinfo_fields: 如已经通过设置docinfo为true执行需要元数据存储,此字段列出事件中需要保存的元数据字段有哪些。默认值是 ["_index", "_type", "_id"]。
11、docinfo_target:如已经通过设置docinfo为true执行需要元数据存储,则此选项将在其下存储元数据字段的字段命名为子字段。
创建es_sync.conf文件:
#好用的
input {
elasticsearch {
hosts => ["http://11.168.176.227:9200"]
index => "es_qidian_flow_oa_20220906"
query => '{"query":{"bool":{ "must":[{"term":{"session_id": "webim_2852199659_240062447027410_1662447030899"}}]}}}'
}
}
output {
stdout { }
}
#最好有缩进,看起来更舒服。这个也是可以的
input {
elasticsearch {
hosts => ["http://11.168.176.227:9200"]
index => "es_qidian_flow_oa_20220906"
query => '{
"query":{
"bool":{
"must":[
{
"term":{"session_id": "webim_2852199659_240062447027410_1662447030899"}
}
]
}
}
}'
}
}
output {
stdout { }
}
#其中的type感觉不用指定也是ok的。
注:此时如果继续往源ES中写入满足条件的数据,是不会被增量同步过来的(只执行一次嘛)。
设置定时任务。如下为 每分钟查询一次并将结果输出至console标准输出 的配置。
input {
elasticsearch {
hosts => ["http://11.168.xxx.227:9200"]
index => "es_qidian_flow_oa_20220906"
query => '{
"query":{
"bool":{
"must":[
{
"term":{"session_id": "webim_2852199659_240062447027410_1662447030899"}
}
]
}
}
}'
scroll => "1m"
docinfo => true
size => 2000
schedule => "* * * * *" #定时任务,每分钟1次
}
}
filter {
mutate {
remove_field => ["flow_type", "source", "@version", "@timestamp"]
}
}
output {
stdout { }
}
问:如何实现增量同步?
大:借助schedule做定时任务,在output处配置docid去重。仅仅这样可能还是不够,query语句处指定过滤最近t分钟内的数据进行同步。t的取值要和scroll间隔相匹配。如下配置就为每1min同步最近3分钟的内的数据,这样应该就不会丢数据了;另外通过docid也可以实现去重。
注:看了下logstash从elasticsearch导出数据,增量同步貌似都是这么实现的。
input {
elasticsearch {
hosts => "1.1.1.1:9200"
index => "es-runlog-2019.11.20"
query => '{"query":{"range":{"@timestamp":{"gte":"now-3m","lte":"now/m"}}}}'
size => 5000
scroll => "1m"
docinfo => true
schedule => "* * * * *" #定时任务,每分钟执行一次
}
}
filter {
mutate {
remove_field => ["source", "@version"]
}
}
output {
stdout {}
}
(2)filter插件
功能也非常多,这里简单列几个。Mutate filter plugin | Logstash Reference [8.4] | Elastic
date插件、grok插件、geoip插件前面都有简单演示过,这里专门介绍下 mutate(变异)插件。
适用于所有filter插件的通用选项:
1、add_field:在事件中添加任意字段。字段名称可以是动态的,并使用%{Field}包含事件的各个部分。
filter {
mutate {
add_field => { "foo_%{somefield}" => "Hello world, from %{host}" }
}
}
2、remove_field:从此事件中删除任意字段。字段名称可以是动态的,并使用%{field}包含事件的各个部分示例。
filter {
mutate {
remove_field => [ "foo_%{somefield}" ]
}
}
3、add_tag:
4、remove_tag:
mutate插件的一些选项
1、convert:将字段的值转换为其他类型,如将字符串转换为整数。如果字段值是数组,则将转换所有成员。如果字段是散列,则不会采取任何操作。
filter {
mutate {
convert => {
"fieldname" => "integer"
"booleanfield" => "boolean"
}
}
}
2、copy:将现有字段复制到其他字段。现有目标字段将被覆盖。
filter {
mutate {
copy => { "source_field" => "dest_field" }
}
}
3、merge:
4、rename:
5、replace:
6、update:
上述conf中加入mutate→remove_field之后可以看到输出就不包括remove列出的那些字段了。
(3)output插件 设置 elasticsearch
参见官网 Output plugins | Logstash Reference [8.4] | Elastic
将一个ES的数据导入另一个ES,索引、type、docid保持不变;同时也配置了schedule定时任务。如下。
input {
elasticsearch {
hosts => ["http://11.168.xxx.227:9200"]
index => "es_qidian_flow_oa_20220906"
query => '{
"query":{
"bool":{
"must":[
{
"term":{"session_id": "webim_2852199659_240062447027410_1662447030899"}
}
]
}
}
}'
scroll => "1m"
docinfo => true
size => 2000
schedule => "* * * * *"
}
}
filter {
mutate {
remove_field => ["flow_type", "source", "@version", "@timestamp"]
}
}
output {
elasticsearch {
hosts => ["http://10.101.xxx.15:9200"]
index => "%{[@metadata][_index]}"
document_type => "%{[@metadata][_type]}"
document_id => "%{[@metadata][_id]}"
}
stdout { }
}
经过测试效果完全符合预期。
如果事件中没有包含目标索引前缀的字段,该怎么办?
这时候可以使用mutate过滤器和条件添加[@metadata]字段来设置每个事件的目标索引。[@metadata]字段将不会发送到Elasticsearch。
input {
elasticsearch {
hosts => ["http://11.168.176.227:9200"]
index => "es_qidian_flow_oa_20220906"
query => '{
"query":{
"bool":{
"must":[
{
"term":{"session_id": "webim_2852199659_240062447027410_1662447030899"}
}
]
}
}
}'
scroll => "1m"
docinfo => true
size => 2000
schedule => "* * * * *"
}
}
filter {
mutate {
remove_field => ["flow_type", "source", "@version", "@timestamp"]
add_field => { "[@metadata][new_index]" => "es_qidian_flow_oa_zs_%{+YYYY_MM}"}
}
}
output {
elasticsearch {
hosts => ["http://10.101.203.15:9200"]
index => "%{[@metadata][new_index]}"
document_type => "%{[@metadata][_type]}"
document_id => "%{[@metadata][_id]}"
}
stdout { }
}
这个时候出来的索引名称如下。
再给一个实例。总之各种拼接都是ok的。
filter {
if [log_type] in [ "test", "staging" ] {
mutate { add_field => { "[@metadata][target_index]" => "test-%{+YYYY.MM}" } }
} else if [log_type] == "production" {
mutate { add_field => { "[@metadata][target_index]" => "prod-%{+YYYY.MM.dd}" } }
} else {
mutate { add_field => { "[@metadata][target_index]" => "unknown-%{+YYYY}" } }
}
}
output {
elasticsearch {
index => "%{[@metadata][target_index]}"
}
}
关于增量迁移可以参照是个实践:
记一次在线跨集群迁移ES数据 - 腾讯云开发者社区-腾讯云