公司产品是spring cloud框架,有很多个不同的服务,每个服务有多个实例。实例日志输出格式基本是一样的,且都在同一个文件夹,日志中有一个字段表示的就是服务名称。我想实现每个服务日志都生成自己的服务名称的索引,每台终端使用filebeat作为日志采集器,输出到logstash中,然后再推送进ES。
一、方案初步选择
每台机器可能放了好几个不同类型的服务,如果我要按日志分别输出到不通的索引中的话,大致有一下方式:
- 在每台机器下给每个日志类型文件增加一个
log_type
字段,然后在es output中按照log_type
创建索引。这样的话不够灵活,一旦服务器有新的服务,就需要手动添加一个日志源,配置也多。 - 在filebeat日志源头上直接指定
*.log
(服务日志在同一个文件夹下),然后在es grok中进行日志清洗,找出servername, 然后output中使用%{servername}
变量名称创建索引。这样客户端就可以随意加不同的实例,但是logstash因为grok
负载就略有升高。
二、logstash 中条件判断
有时您只想在特定条件下过滤或输出事件。为此,您可以使用条件(conditional)。比如在elk系统中想要添加一个type类型的关键字来根据不同的条件赋值,最后好做统计。官网链接
条件语支持if,else if和else语句并且可以嵌套。
条件语法如下:
if EXPRESSION {
...
} else if EXPRESSION {
...
} else {
...
}
比较操作:
相等: ==, !=, <, >, <=, >=
正则: =~(匹配正则), !~(不匹配正则)
包含: in(包含), not in(不包含)
布尔操作:
and(与), or(或), nand(非与), xor(非或)
一元运算符:
表达式可能很长且很复杂。表达式可以包含其他表达式,您可以使用!来取反表达式,并且可以使用括号(...)对它们进行分组。
!(取反)
()(复合表达式), !()(对复合表达式结果取反)
三、 Logstash 中Grok失败处理
按照上面我们的第二个方案的话,如果我们以grok出来的变量名称作为索引名称的话,如果grok失败,那么肯定就会以变量名称创建索引,比如创建成这样 bituan-%(servername)-2019-05-24
这不是我们想要得到的结果,所以我们需要处理grok失败的单独放到一个索引中,如果grok
失败,会在tags
字段打上_grokparsefailures
。参考官网配置
input { # ... }
filter {
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} \[%{IPV4:ip};%{WORD:environment}\] %{LOGLEVEL:log_level} %{GREEDYDATA:message}" }
}
}
output {
if "_grokparsefailure" in [tags] {
# write events that didn't match to a file
file { "path" => "/tmp/grok_failures.txt" }
} else {
elasticsearch { }
}
}
四、线上Logstash 配置示例
- filebeat中日志源头指定
*.log
,且指定了log_type
(就是类似与一个tag,fields.log_type都以bituan-开头) - output中
[fields][log_type] =~ "bituan-*"
使用了正则,所以其同一类型的日志就不要再单独写一个output输出了,比如在前面又写了一个if [fields][log_type] == "bituan-sms-gateway" { elasticsearch { } }
,这样的话sms-gateway的日志就会重复记录,分别输出到不同的索引(在logstash 6.2版本中测试是这样),此时就应该用if else if else 这样写就不会重复写入。
log日志格式:
2019-05-24 10:36:22.406 INFO [query,,,] 19129 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
input {
file {
path => "/data/logs/nginx/access.log"
type => "nginx-access"
start_position => "beginning"
# sincedb_path => "/usr/local/logstash/sincedb"
codec => "json"
}
beats {
host => "172.31.37.118"
port => 5400
}
}
filter {
if [fields][log_type] =~ "bituan-*" {
grok {
match => ["message", "%{TIMESTAMP_ISO8601:logdate}[T ]*%{LOGLEVEL:loglevel}[T ]*\[(?<servername>([a-zA-Z0-9._-]+))%{GREEDYDATA:loginfo}"]
}
date {
match => ["logdate", "yyyy-MM-dd HH:mm:ss.SSS"]
target => "@timestamp"
}
mutate {
update => {"message" => "%{logdate} [%{servername}%{loginfo}"}
remove_field => ["logdate","loginfo"]
}
}
if [type] == "nginx-access" {
geoip {
source => "http_x_forwarded_for"
target => "geoip"
database => "/home/GeoLite2-City_20190226/GeoLite2-City.mmdb"
add_field => [ "[geoip][coordinates]", "%{[geoip][longitude]}" ]
add_field => [ "[geoip][coordinates]", "%{[geoip][latitude]}" ]
}
mutate {
convert => [ "[geoip][coordinates]", "float"]
}
}
}
output {
if [type] == "nginx-access" {
elasticsearch {
hosts => ["172.31.15.220:9200"]
manage_template => true
index => "logstash-nginx-web-%{+YYYY-MM-dd}"
}
}
else if [fields][log_type] == "sms-gateway" {
elasticsearch {
hosts => ["172.31.15.220:9200"]
manage_template => true
index => "bituan-sms-gateway-%{+YYYY-MM-dd}"
}
}
else if [fields][log_type] == "mycat" {
elasticsearch {
hosts => ["172.31.15.220:9200"]
manage_template => true
index => "bituan-mycat-%{+YYYY-MM-dd}"
}
}
else if "_grokparsefailure" not in [tags] {
if [fields][log_type] =~ "bituan-*" {
elasticsearch {
hosts => ["172.31.15.220:9200"]
manage_template => true
index => "bituan-%{servername}-%{+YYYY-MM-dd}"
}
}
}
else {
elasticsearch {
hosts => ["172.31.15.220:9200"]
index => "bituan-failure-%{+YYYY-MM-dd}"
}
}
# stdout { codec => "rubydebug"}
}