1 安装ELK
ELK其实是Elasticsearch,Logstash 和 Kibana三个产品的首字母缩写,这三款都是开源产品。
ElasticSearch(简称ES),是一个实时的分布式搜索和分析引擎,它可以用于全文搜索,结构化搜索以及分析。
Logstash,是一个数据收集引擎,主要用于进行数据收集、解析,并将数据发送给ES。支持的数据源包括本地文件、ElasticSearch、MySQL、Kafka等等。
Kibana,为 Elasticsearch 提供了分析和 Web 可视化界面,并生成各种维度表格、图形。
1.1 安装ElasticSearch
下载地址:https://www.elastic.co/cn/downloads/elasticsearch
8.2.2 Linux版本:https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.2.2-linux-x86_64.tar.gz
下载后上传到服务器/app/elk目录下,执行:
tar -zxvf elasticsearch-8.2.2-linux-x86_64.tar.gz
cd elasticsearch-8.2.2
# 修改ES配置
vim ./config/elasticsearch.yml
ES配置修改内容:
17行: cluster.name: test
23行: node.name: node-1
33行: path.data: /app/elk/elasticsearch-8.2.2/data
37行: path.logs: /app/elk/elasticsearch-8.2.2/logs
56行: network.host: 0.0.0.0
61行: http.port: 9200
74行: cluster.initial_master_nodes: ["node-1"]
88行: action.destructive_requires_name: false
98行: xpack.security.enabled: false
100行:xpack.security.enrollment.enabled: false
104行:enabled: false
109行:enabled: false
保存后启动ES,
注意:一定要用非root用户启动:
# 创建用户
useradd yehongzhi
# 设置密码
passwd yehongzhi
# 赋予用户权限
chown -R yehongzhi:yehongzhi /app/elk/elasticsearch-8.2.2/
# 切换用户
su yehongzhi
# 启动 -d表示后台启动
./bin/elasticsearch -d
如果报错:
max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
编辑文件/etc/sysctl.conf
sudo vi /etc/sysctl.conf
在末尾添加:
vm.max_map_count=655360
保存后再执行:
sudo sysctl -p
再启动ES就可以了。
ES启动后访问:http://x.x.x.x:9200
返回下面的内容,代表ES启动成功了
{
"name": "node-1",
"cluster_name": "test",
"cluster_uuid": "Lo18yh07T-mfMxwQ6QJM8A",
"version": {
"number": "8.2.2",
"build_flavor": "default",
"build_type": "tar",
"build_hash": "9876968ef3c745186b94fdabd4483e01499224ef",
"build_date": "2022-05-25T15:47:06.259735307Z",
"build_snapshot": false,
"lucene_version": "9.1.0",
"minimum_wire_compatibility_version": "7.17.0",
"minimum_index_compatibility_version": "7.0.0"
},
"tagline": "You Know, for Search"
}
提供删除ES索引的脚本:
#/bin/bash
# 清理几天前的索引
dayn=2
# ES IP & port
esip=127.0.0.1
esport=9200
# 匹配日期正则表达式,日期格式为:YYYY.mm.dd
regular="[0-9]{4}(.[0-9]{2}){2}"
#指定日期
date1=`date -d "$dayn day ago" +%Y-%m-%d`
#当前日期
time=`date "+%Y-%m-%d %H:%M:%S"`
echo "${time}:开始清理 $date1(${dayn}天)之前ES索引"
res=`curl -XGET "http://${esip}:${esport}/_cat/indices/?v"|grep -E $regular|awk '{print $3}'`
t1=`date -d "$date1" +%s`
for var in $res;
do
# 如果索引中的日期格式已经是YYYY-mm-dd格式,就不用使用sed替换了,本例索引中日期格式为:YYYY.mm.dd
date2=`echo $var| tr -cd $regular|sed 's/\./-/g'`
t2=`date -d "$date2" +%s`
if [ $t1 -gt $t2 ]; then
#echo "$date1 > $date2"
echo "${time}: 清理 $var 索引"
#curl --user account:pwd -XDELETE "http://${esip}:${esport}/$var"
curl -XDELETE "http://${esip}:${esport}/$var"
fi
done
1.2 安装Kibana
下载地址:https://www.elastic.co/cn/downloads/kibana
8.2.2 Linux版本:https://artifacts.elastic.co/downloads/kibana/kibana-8.2.2-linux-x86_64.tar.gz
下载后上传服务器/app/elk目录下解压:
tar -zxvf kibana-8.2.2-linux-x86_64.tar.gz
修改配置文件 kibana-8.2.2/config/kibana.yml
6行: server.port: 5601
11行: server.host: "x.x.x.x"
26行: server.publicBaseUrl: "http://x.x.x.x:5601"
32行: server.name: "kibana"
# ES ip
43行: elasticsearch.hosts: ["http://x.x.x.x:9200"]
63行: elasticsearch.requestTimeout: 30000
# 设置中文版,默认英文版
167行: i18n.locale: "zh-CN"
同样使用非root用户启动:
nohup ./bin/kibana &
这里提供一段重启脚本:
#!/bin/bash
kibanapid=`ps -ef|grep -v grep|grep kibana-8.2.2|awk '{print $2}'`
echo old kibana-pid=$kibanapid
echo kill $kibanapid
kill -9 $kibanapid
sleep 5s
echo start kibana
nohup /app/elk/kibana-8.2.2/bin/kibana >/dev/null 2>&1 &
sleep 5s
kibanapid=`ps -ef|grep -v grep|grep kibana-8.2.2|awk '{print $2}'`
echo new kibana-pid=$kibanapid
其他服务ES、logstash启动脚本类似。
启动kibana后访问:http://x.x.x.x:5601
1.3 安装Logstash
下载地址:https://www.elastic.co/cn/downloads/logstash
8.2.2 Linux版本:https://artifacts.elastic.co/downloads/logstash/logstash-8.2.2-linux-x86_64.tar.gz
下载后上传服务器/app/elk目录下解压:
tar -zxvf logstash-8.2.2-linux-x86_64.tar.gz
编辑logstash-8.2.2/config/logstash.yml
19行: node.name: logstash
133行: api.http.host: x.x.x.x
295行: xpack.monitoring.enabled: true
# ES IP
299行: xpack.monitoring.elasticsearch.hosts: ["http://x.x.x.x:9200"]
311行: xpack.monitoring.elasticsearch.sniffing: true
312行: xpack.monitoring.collection.interval: 10s
313行: xpack.monitoring.collection.pipeline.details.enabled: true
# ES IP
322行: xpack.management.elasticsearch.hosts: ["http://x.x.x.x:9200"]
使用非root用户启动logstash,使用默认配置logstash-sample.conf,后面会根据实际业务需求进行配置修改。
cd logstash-8.2.2
nohup ./bin/logstash -f /usr/logstash-8.2.2/config/logstash-sample.conf &
2 日志上传配置
2.1 log4j2
项目中使用了log4j2,则直接修改log4j2.xml配置文件,实现日志上传。
官方给了说明:https://www.elastic.co/guide/en/logstash/current/plugins-inputs-tcp.html
2.1.1 log4j2.xml修改
log4j2.xml添加配置:
2.1.2 logstash配置修改
在logstash-8.2.2/config/目录下创建配置文件tcp.conf,该配置文件包括3部分:input、filter、output
2.1.2.1 input
通过命令:
./bin/logstash-plugin input
./bin/logstash-plugin codec
可以查看当前logstash版本支持的input插件和codec插件,如果缺少插件,还可以手工安装插件:
# 插件名不要带版本号
./bin/logstash-plugin install --no-verify 插件名
需要的插件可以在这里搜索:https://rubygems.org/
input的知识可参考:https://doc.yonyoucloud.com/doc/logstash-best-practice-cn/input/index.html
log4j2使用TCP传送日志:
input {
tcp {
host => "0.0.0.0"
# 和log4j2.xml配置的端口保持一致
port => "1234"
mode => "server"
type => "microservice"
codec => plain
add_field => {"appName" => "microservice"}
}
}
2.1.2.2 filter
filter参考:https://doc.yonyoucloud.com/doc/logstash-best-practice-cn/filter/index.html
有关grok,参考:https://blog.51cto.com/u_12827626/3318179
filter {
#默认是UTC日期,把变量index_date改成UTC+8日期,用于输出到ES时用做索引
ruby {
code => "event.set('index_date', event.get('@timestamp').time.localtime + 8*60*60)"
}
mutate {
convert => ["index_date", "string"]
gsub => ["index_date", "T([\S\s]*?)Z", ""]
gsub => ["index_date", "-", "."]
}
# 不需要的字段也可以删掉
mutate {
remove_field => "@version"
}
grok {
match => {
"message" => "%{TIMESTAMP_ISO8601:timestamp}\|%{LOGLEVEL:loglevel}\|%{DATA:thread}\|%{DATA:classpath}:%{INT:line}\|TID: %{DATA:traceId}\|%{GREEDYDATA:logdata}"
}
#remove_field => ["index_date"]
}
date {
match => ["[log][timeMillis]", "UNIX_MS"]
target => "@timestamp"
}
}
2.1.2.3 output
参考:https://doc.yonyoucloud.com/doc/logstash-best-practice-cn/output/index.html
output {
if [type] == "microservice" {
elasticsearch {
# ES IP
hosts => ["http://x.x.x.x:9200"]
# 这样写,凌晨时写入的日志,还是写入到前一天的索引中
#index => "log-microservice-%{+YYYY.MM.dd}"
# UTC+8日期
index => "log-microservice-%{index_date}"
#user => "elastic"
#password => "changeme"
}
}
}
2.2 logback
logback比log4j2麻烦多了,推荐使用log4j2。
实现logback传输日志到logstash服务器,主要有两种方法
1、socket通信,使用SocketAppender
先看一段话:
SocketAppender被设计为通过序列化ILoggingEvent实例把记录输出到远程实体。被序列化的事件的真实类型是LoggingEventVO, 它实现了ILoggingEvent接口。尽管如此, 就记录事件而言, 远程记录仍然是无损的。在接收和反序列化后, 事件像是从本地产生的一样被记录。运行在不同机器上的多个SocketAppender实例可以把各自的记录输出到一个格式固定的中央记录服务器。
SocketAppender不关联layout, 因为它把序列化的事件发送到远程服务器。SocketAppender运作在TCP层上, TCP层提供可靠、有序、流量控制的端到端的二进制流。因此, 如果远程服务器可访问, 则记录事件最终会到达那里。否则, 如果远程服务器关机或不可访问, 那么记录事件会被抛弃。当远程服务器恢复可用时, 会透明地继续传输事件。这种透明的重新连接是由一个连接器(connector)线程执行的, 它定时尝试连接服务器。
记录事件被本地TCP实现自动地缓冲。这意味着如果服务器连接很慢, 但快于客户端生成事件的速度, 那么客户端不会受网络连接慢的影响。但是如果网络连接慢于生成事件的速度, 那么客户端只能按网络速度执行。特别是在服务器宕机这种极端情况下, 客户端会被阻塞。
如果连接器线程仍然存在, 即使SocketAppender不再关联到任何logger, 也不会被垃圾回收。连接器线程只在当与服务器之间没有连接时存在。为避免这个垃圾回收问题, 你应当显式地关闭 SocketAppender。会创建/销毁很多SocketAppender实例的长期运行的程序应当注意这个垃圾回收问题。多数其他程序可以安全地忽略这个问题。如果宿主JVM在SocketAppender关闭之前退出了, 不管 SocketAppender被显式关闭还是交给垃圾回收, 都有可能在管道(pipe)里剩有一些未被传输的数据, 这些数据会丢失。为避免数据丢失, 一般在退出程序之前显式地调用SocketAppender的close()方法或调用LoggerContext的stop()方法就可以了。
SocketAppender发送的数据是序列化之后的数据,logstash的input缺少反序列化的插件。
以前有个logstash-input-log4j插件可以接收序列化数据,但该插件已经被放弃使用。
即使input使用codec => plain { charset => "ISO-8859-1" },接收到的数据也是存在乱码,而且会少接收很多日志,有用的日志没几条,乱七八糟的日志很多。
好在官方提供了SimpleSocketServer样例代码:https://github.com/qos-ch/logback/blob/master/logback-classic/src/main/java/ch/qos/logback/classic/net/SimpleSocketServer.java
创建个java项目打包个jar包出来,运行后就可以接收socket发过来的日志。
使用命令:
java -jar SimpleSocketServer.jar port configurefile
官方给的configurefile例子:
https://github.com/qos-ch/logback/blob/master/logback-examples/src/main/resources/chapters/appenders/socket/server1.xml
https://github.com/qos-ch/logback/blob/master/logback-examples/src/main/resources/chapters/appenders/socket/server2.xml
如果不想自己动手去打包,我这里有已经打包好的包可以下载:https://download.csdn.net/download/kitter2008/85709895
2、使用LogstashTcpSocketAppender
这种方法需要在工程pom.xml文件里添加logstash依赖包:
net.logstash.logback
logstash-logback-encoder
7.1.1
logback.xml配置中添加:
127.0.0.1:1235
这种方式是简单很多,但需要修改项目的pom.xml文件。作为测试的我,不便于去修改pom.xml文件,万一出现冲突啥的,也麻烦,即使让开发去修改,也会存在很多服务器节点都需要修改。而修改logback.xml配置文件就简单多了,直接去托管服务器上修改配置文件即可。
所以,最终,我选择:使用SocketAppender通过TCP发送日志到SimpleSocketServer服务,SimpleSocketServer服务收到日志再转发给logstash,logstash存入到ES,kibana负责搜索日志。
2.2.1 logback.xml修改
x.x.x.x
9999
10000
false
2.2.2 SimpleSocketServer部署
java -jar logbacksocketserver-0.0.1-SNAPSHOT.jar 9999 server2.xml
2.2.3 logstash配置修改
2.2.2.1 input
input {
tcp {
host => "0.0.0.0"
port => "4568"
mode => "server"
type => "logbackserver"
codec => json_lines
add_field => {"appName" => "pg-grs"}
}
}
2.2.2.2 filter
filter {
ruby {
code => "event.set('index_date', event.get('@timestamp').time.localtime + 8*60*60)"
}
mutate {
convert => ["index_date", "string"]
gsub => ["index_date", "T([\S\s]*?)Z", ""]
gsub => ["index_date", "-", "."]
}
grok {
match => {
# 同时匹配log4j2和logback,添加2条匹配规则
"message" => ["%{TIMESTAMP_ISO8601:timestamp}\|%{LOGLEVEL:loglevel}\|%{DATA:thread}\|%{DATA:classpath}:%{INT:line}\|TID: %{DATA:traceId}\|%{GREEDYDATA:logdata}",
"%{GREEDYDATA:logdata}"]
}
}
date {
match => ["[log][timeMillis]", "UNIX_MS"]
target => "@timestamp"
}
}
2.2.2.3 output
output {
if [type] == "logbackserver" {
elasticsearch {
hosts => ["http://x.x.x.x:9200"]
index => "log-backserver-%{index_date}"
codec => plain { charset => "UTF-8" }
}
}
}
2.3 Django的logging
实践证明,python-logstash和python3-logstash都不好使,最后选择了python-logstash-async。
2.3.1 安装python-logstash-async
pip install python-logstash-async
2.3.1 配置Django的settings.py
参考:https://python-logstash-async.readthedocs.io/en/latest/usage.html#usage-with-django
LOGGING = {
...
'formatters': {
...
'logstash': {
'()': 'logstash_async.formatter.DjangoLogstashFormatter',
'message_type': 'python-logstash',
'fqdn': False, # Fully qualified domain name. Default value: false.
'extra_prefix': 'dev', #
'extra': {
'application': PROJECT_APP,
'project_path': PROJECT_APP_PATH,
'environment': 'production'
}
},
},
'handlers': {
...
'logstash': {
'level': 'DEBUG',
'class': 'logstash_async.handler.AsynchronousLogstashHandler',
'formatter': 'logstash',
'transport': 'logstash_async.transport.TcpTransport',
'host': 'logstash.host.tld',
'port': 5959,
'ssl_enable': True,
'ssl_verify': True,
'ca_certs': 'etc/ssl/certs/logstash_ca.crt',
'certfile': '/etc/ssl/certs/logstash.crt',
'keyfile': '/etc/ssl/private/logstash.key',
'database_path': '{}/logstash.db'.format(PROJECT_ROOT),
},
},
'loggers': {
'django.request': {
'handlers': ['logstash'],
'level': 'DEBUG',
'propagate': True,
},
},
...
}
2.4 启动logstash服务
nohup ./bin/logstash -f /usr/logstash-8.2.2/config/tcp.conf &
提供重启脚本:
#!/bin/bash
logstashpid=`jps|grep -v grep|grep Logstash|awk '{print $1}'`
echo old logstash-pid=$logstashpid
echo kill $logstashpid
kill -9 $logstashpid
sleep 5s
echo start logstash
nohup /app/elk/logstash-8.2.2/bin/logstash -f /app/elk/logstash-8.2.2/config/tcp_pg.conf >/dev/null 2>&1 &
sleep 5s
logstashpid=`jps|grep -v grep|grep Logstash|awk '{print $1}'`
echo new logstash-pid=$logstashpid