使用ELK记录微服务日志
1. 简介
上一节我们讲解了 Logback 的配置,完成了日志打印到文件的第一步
一般来说,后台服务规模比较小的情况下,这样是没问题的,但是一旦分布式部署众多服务器,日志的查询和管理就成了很大的问题
这个时候我们可以使用比较成熟的分布式日志解决方案:ELK
ELK 是 Elasticsearch、Logstash、Kibana 的缩写
简单来说就是通过 Logstash 收集处理日志,然后存储到 Elasticsearch,在通过 Kinbana 进行可视化的搜索、查询、汇聚分析。
我们使用使用 SpringBoot 来构建微服务,可以配置 logstash-logback-encoder 通过tcp或udp把产生的日志传送到 Logstash服务
简单流程如图:
2. 搭建
为了方便起见,我们使用开源项目 docker-elk 的配置来进行安装
环境要求:
- Docker 版本 17.05 以上
docker version
- Docker Compose 版本 1.20.0 以上
docker-compose version
- 2G 内存以上
这里我使用我电脑上装的一台虚拟机,ip 是 192.168.0.2
在服务器上 clone 项目:
| git clone https://github.com/deviantony/docker-elk.git
chcon -R system_u:object_r:admin_home_t:s0 docker-elk/
|
起目录结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| docker-elk/ ├── docker-compose.yml ├── docker-stack.yml ├── elasticsearch │ ├── config │ └── Dockerfile ├── .env ├── extensions │ ├── apm-server │ ├── curator │ ├── enterprise-search │ ├── logspout │ ├── metricbeat │ └── README.md ├── kibana │ ├── config │ └── Dockerfile ├── LICENSE ├── logstash │ ├── config │ ├── Dockerfile │ └── pipeline └── README.md
|
.env
文件定义了使用的 Elasticsearch 版本,我这里用是 7.10.1
,有需要可以自行修改
然后我们修改一下 Logstash 的 build 配置,安装 json_lines 插件
| vi logstash/Dockerfile
RUN bin/logstash-plugin install logstash-codec-json_lines
|
再修改 Logstash 的配置文件 vi logstash/pipeline/logstash.conf
,配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| input { beats { port => 5044 }
tcp { port => 5000 hots => "0.0.0.0" codec => json_lines } }
output { elasticsearch { hosts => "elasticsearch:9200" user => "elastic" password => "changeme" ecs_compatibility => disabled codec => json } }
|
我们可以直接启动:
第一次安装会下载镜像,并在镜像基础上 build,需要耐心等待
当我们看到三个 done,并且容器也开始打印 log 的时候,就启动完成了,后面 还需要等待 1 分钟左右,Kibana 才可以访问
我们访问之前,先用命令访问 Kibana Api,创建一个 index pattern:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| curl -XPOST -D- 'http://localhost:5601/api/saved_objects/index-pattern' \ -H 'Content-Type: application/json' \ -H 'kbn-version: 7.10.2' \ -u elastic:changeme \ -d '{"attributes":{"title":"logstash-*","timeFieldName":"@timestamp"}}'
[root@localhost docker-elk] > -H 'Content-Type: application/json' \ > -H 'kbn-version: 7.10.2' \ > -u elastic:changeme \ > -d '{"attributes":{"title":"logstash-*","timeFieldName":"@timestamp"}}' HTTP/1.1 200 OK kbn-name: kibana kbn-license-sig: 9c18aa7a665c803facb3814b41effaf3fd82a9af5581d75bf4d30992a38908a7 content-type: application/json; charset=utf-8 cache-control: private, no-cache, no-store, must-revalidate content-length: 280 Date: Sun, 17 Jan 2021 13:26:40 GMT Connection: keep-alive
{"type":"index-pattern","id":"a1a4cb40-58c7-11eb-a33e-6bbb0a638f52","attributes":{"title":"logstash-*","timeFieldName":"@timestamp"},"references":[],"migrationVersion":{"index-pattern":"7.6.0"},"updated_at":"2021-01-17T13:26:39.859Z","version":"Wzc2LDNd","namespaces":["default"]}
|
3. Kibana配置
直接访问 192.168.0.2:5601
输入 docker-elk 配置的默认账户名:elastic
密码:changeme
即可访问
我这里测试使用就不改密码了 ,生产环境建议按如下步骤修改密码:
| # 1. 批量生产随机密码,记录下密码 docker-compose exec -T elasticsearch bin/elasticsearch-setup-passwords auto --batch # 2. 删除 docker-compose.yml 中的 ELASTIC_PASSWORD # 3. 修改kibana/config/kibana.yml 使用 kibana_system 用户和你自己的密码(小于7.8.0版本可以用 kibana y用户) # 修改logstash/config/logstash.yml 使用 logstash_system 用户和你自己的密码 # 修改logstash/pipeline/logstash.conf 修改 elastic 用户的密码
|
我们点击左侧菜单栏中的 Disocver:
可以看到,我们之前创建的 index pattern 已经有了:
不过这个时候还没有数据,我们接着往下看
4. 微服务配置
这里还是再我们上回的 web-log 的基础上修改
引入 logstash-logback-encoder:
| <dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>6.6</version> </dependency>
|
为了不影响之前的配置,我们拷贝一份 logback-spring-logstash.xml
,并增加一个 dev profile:
dev 的 yml 配置如下,这里我们指定配置文件,和 Logstash的 host:
| server: port: 8080 spring: application: name: web-simple logstash: host: 192.168.0.2 logging: level: root: debug config: classpath:logback-spring-logstash.xml
|
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
| <?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">
<springProperty name="LOG_STASH_HOST" scope="context" source="logstash.host" defaultValue="localhost"/>
<property name="FILE_ERROR_PATTERN" value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} %file:%line: %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<property name="LOG_HOME" value="logs"/> <property name="LOG_FILE_SIZE" value="100MB"/>
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/> <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/> <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<property name="CONSOLE_LOG_PATTERN_COLOR" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{0}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<property name="SELF_DEFINE_LOG_PATTERN" value="[%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint}] [%clr(%5.5p){magenta}] [%clr(%5.10t){faint}] [%clr(%30.30c{0}.%20.20M:%4.4L){cyan}] -> %replace(%.-300m){'\r\n','__'}%ex{full}%n"/>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFO</level> </filter> <encoder> <pattern>${CONSOLE_LOG_PATTERN_COLOR}</pattern> <charset>UTF-8</charset> </encoder> </appender>
<appender name="CONSOLE2" class="ch.qos.logback.core.ConsoleAppender"> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFO</level> </filter>
<encoder> <pattern>${SELF_DEFINE_LOG_PATTERN}</pattern> <charset>UTF-8</charset> </encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>Info</level> </filter>
<File>${LOG_HOME}/web-log.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern>${LOG_HOME}/Log._%d{yyyy-MM-dd}.part_%i.log</FileNamePattern> <maxHistory>180</maxHistory> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>${LOG_FILE_SIZE}</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy>
<encoder> <pattern>${FILE_LOG_PATTERN}</pattern> <charset>UTF-8</charset> </encoder> </appender>
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender"> <destination>${LOG_STASH_HOST}:5000</destination> <encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder"> <providers> <timestamp> <timeZone>Asia/Shanghai</timeZone> </timestamp> <pattern> <pattern> { "project": "web-mall", "level": "%level", "service": "${APP_NAME:-}", "pid": "${PID:-}", "thread": "%thread", "class": "%logger", "message": "%message", "stack_trace": "%exception{20}" } </pattern> </pattern> </providers> </encoder> <connectionStrategy> <roundRobin> <connectionTTL>5 minutes</connectionTTL> </roundRobin> </connectionStrategy> </appender>
<root level="info"> <appender-ref ref="CONSOLE2"/> </root>
<springProfile name="pro"> <root level="info"> <appender-ref ref="FILE"/> </root> </springProfile>
<springProfile name="dev"> <root level="info"> <appender-ref ref="FILE"/> <appender-ref ref="LOGSTASH"/> </root> </springProfile> </configuration>
|
然后在 IDEA 的启动选项里指定 dev ,启动应用:
再访问一下测试请求:
5. 日志查询
我们刷新 Kibana:
由于我们日志等级是 debug,所以进来了大量日志
这个时候左侧可以看到 左侧很多字段都是 unknown field,我们的日志内容在 message
里,我们需要刷新一下字段:
再回来刷新就发现字段已经正常了:
我前面的日志打印了时间戳:1610934226338
我们搜索一下看看,搜索栏输入:message : *1610934226338*
可以看到,能正常搜索到日志
我们展开日志,可以更清晰地看到信息:
我们还可以按我们想要的格式展现日志:比如 project + ip + message,在左侧上点 + 号添加:
至此,我们完成了日志的统一存储、查询
其实我们还可以分场景收集日志,比如分访问日志、应用日志、错误日志,可以用于访问量统计,日志查询,错误监控等
在 LogStash 的 input 配置多个端口,配置不同的 type,然后根据 type 创建不同的 ES index 即可
可以根据业务需要去配置,这里就不细展开了
好了,本篇到此结束,希望对你有帮助!
源码及脚本都在Github上
Enjoy it!