elk+rabbitmq+springboot日志系统搭建说明
一、情况摆设
1、使用工具(xshell等)远程毗连到110服务器,首先摆设好 elk (ElasticSearch,logstash,kibana) 和 rabbitmq
elk和rabbitmq我先前已经在110服务器用docker摆设好了,详细摆设步调自行查阅资料。(110服务器只是我当时使用的服务器地点,不消在意。)
2、进入logstash举行设置
如果创建logstash容器时使用 -v 绑定了某个卷,则不需要进入logstash容器内部。
- 使用下令进入容器 (“logstash”是创建该容器时自界说的容器名字)
- docker exec -it logstash bash
复制代码
- 乐成进入后,进入config内查看 pipelines.yml 文件(7.0以下的旧版设置不在这里,发起使用新版)
- pipelines.yml文件中告诉了我们它启动是根据哪个路径的设置文件运行,我们找到对应的这个路径。
- 进入后我们看到此中有个默认的启动设置文件 logstash.conf(我已经给它加了个.backup后缀)
- 我们添加自己需要的设置,我这里已经添加好了 (logstash-mq.conf)
- #日志信息会在项目中发送到mq中(下面会有在项目中的设置),我们要从mq中拿到日志信息input { #平凡利用日志在下面设置的队列 rabbitmq { type =>"operation" durable => true exchange => "log.exchange.direct" exchange_type => "direct" key => "log_operation" host => "172.17.0.1" port => 5672 user => "superadmin" password => "admin2020" queue => "QUEUE_LOG_OPERATION" auto_delete => false } #上送指令的日志存在了下面设置的队列 rabbitmq { type =>"upmsg" durable => true exchange => "log.exchange.direct" exchange_type => "direct" key => "log_up_msg" host => "172.17.0.1" port => 5672 user => "superadmin" password => "admin2020" queue => "QUEUE_LOG_UP_MSG" auto_delete => false } #下发指令的日志存在了下面设置的队列 rabbitmq { type =>"downmsg" durable => true exchange => "log.exchange.direct" exchange_type => "direct" key => "log_down_msg" host => "172.17.0.1" port => 5672 user => "superadmin" password => "admin2020" queue => "QUEUE_LOG_DOWN_MSG" auto_delete => false }}#我们要输出到es,把日志信息存在es中,差别日志发到差别的索引中output { #下发指令 if "downMsg" in [message]{ elasticsearch { hosts => ["172.17.0.1:9200"] index => "downmsg-log-%{+YYYY.MM.dd}" } } #上送指令 else if "upMsg" in [message] { elasticsearch { hosts => ["172.17.0.1:9200"] index => "upmsg-log-%{+YYYY.MM.dd}" } } #平凡利用指令 else { elasticsearch { hosts => ["172.17.0.1:9200"] index => "operation-log-%{+YYYY.MM.dd}" } }}
复制代码 【说明】
- input就是logstash是设置好的队列的消费者,从队列拿到数据后 output 输出到es中,它会把数据放到动态生成的索引中,你可以把索引当成数据库。logstash可以用来过滤数据, input -> filter -> output,固然我这里没有设置,以后有空我会写个更加详细的说明,更多花里胡哨的利用可以阅读logstash官方文档。
- 这里的 ip地点 172.17.0.1 你可以当作 110服务器中的docker容器之间沟通的地点,你也可以配成可以访问的ip加端口。
- 【message】字段存放的就是推送到mq中详细的日志信息,这个 “upMsg” in [message]就是判断这个字段中有没有“upMsg”字符串,反面我会说这个字符串那里来的,通过这个我们将差别范例的日志放到差别索引中;”%{+YYYY.MM.dd}"这个很显着就是获取当天的日期了。天天的日志存在一个索引中。
(ps:你大概想说,这个判断能不能缩小范围,这样写似乎重复了挺多代码,答案是不能,原因我还未找到。你还想说,这个“upmsg”索引名不是驼峰定名,那就注意了:es中的索引名只能小写。)
- 更多详细设置后续需要可以自行更改,例如 output 那里可以设置数据到了多少条批量输出到es。
- 之后我们使用exit下令退出容器并重启容器(restart),进入mq的管理界面,队列、互换机等都会自动生成给我们,固然你自己事先添加也可以。当数据量够了,就会在es中找到大概生成我们设置的索引并存放数据。
二、集成到SpringBoot项目
【添加设置】
1、logback依赖,在maven项目中引入下面这个依赖就会帮我们引入logback相关依赖,详细可以点进入去搜索一下。
- org.springframework.boot spring-boot-starter-parent 2.3.2.RELEASE
复制代码 2、现在我们已经搭建好了底子情况,我们现在要把项目中的日志信息发送到mq中,我们需要添加一个设置
文件 logback-spring.xml,该文件在项目中的 common-web 模块的resource下(这只是我当时编写时的地方,不消在意),下面是日志写入mq的详细设置,这里的 appender 设置项要和刚才 input 中的设置信息相同。
- ${serverName} ${CONSOLE_LOG_PATTERN} ${LOG_CHARSET} ${LOG_PATTERN} ${LOG_CHARSET} 192.168.0.110:5672 36 true log_up_msg superadmin admin2020 true direct log.exchange.direct log_up_msg true UTF-8 true NON_PERSISTENT true false ${LOG_PATTERN} ${LOG_CHARSET} 192.168.0.110:5672 36 true log_down_msg superadmin admin2020 true direct log.exchange.direct log_down_msg true UTF-8 true NON_PERSISTENT true false ${LOG_PATTERN} ${LOG_CHARSET} 192.168.0.110:5672 36 true log_operation superadmin admin2020 true direct log.exchange.direct log_operation true UTF-8 true NON_PERSISTENT true false
复制代码 【说明】
1、主要看 appender 标签,这个标签体现你的日志要去那里, logger 标签体现你的日志在项目的什么地方拿的,比如你在某个类写个日志 log.info(“xxx”),你可以在 logger的name属性指定该类的包,大概直接指定这个类,然后用 appender-ref 子标签体现你想让这个类的日志输出到哪个appender,我们这里的 appender 设置了mq,这样日志就会发送到你指定的队列了。至于 root 标签,只要你在 logger 标签中指明 additivity=“true” ,那这个logger就成了 root标签的儿子,会把拿到的日志继承输出到 root标签指定的 appender中。
2、这个 appender的name属性值会添加到每条推送到这个appender的日志消息中,也就是上述message字段会存在这个name属性。
3、你可以在某个控制层大概业务层中的方法中用切面添加详细的日志(log.info(“x请求了x服务”),然后用logger指向这个切面类,你可以把差别范例的日志放到差别切面类中,然后用logger的name属性来指定。固然也可以不消切面,直接用logger的name指定某包下大概某类下的日志收集为一种日志范例。
【测试】
1、设置好后,我们做一些测试。我们在springboot项目中添加一个测试类,可以看到我在项目启动时会输出102条日志信息。(这个类在 org.zzz包下,对应上面 logger标签界说的name属性,这样会发送到三个队列中,)
- @Component@Slf4jpublic class Init implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { for (int i = 0; i < 102; i++) { log.info("我只是一条无聊的测试日志,我要去mq,然后再去logstash,然后再去es"); } }}
复制代码 2、启动乐成后,我们打开192.168.0.110:9100(管理工具es-head,摆设在110的docker),毗连到9200端口的es,可以看到索引已经创建 ,此中的文档数据,就是logstash从队列中拿过来然后放在这的。
因为我们把这个日志发到了三个差别队列,一开始我们在logstash设置的input是从这三个队列中拿的日志数据,output就是差别队列的日志创建或找到差别的索引;可以看到本日的(2020.12.31)在一个新的索引,昨天的(2020.12.30)是另一个索引。
3、我们点击 根本查询 ,选择想要查找的索引,must match_all是查询全部
4、点击搜索,此中的 message字段就是 日志信息。
5、我们进入 192.168.0.110:5601 kibana的管理界面,进入 菜单中的dev tools
6、我们可以通过 rest 请求 来利用和查看es中的详细数据,中括号中的字段就是一个查询条件的写法,可以自行学习es官方文档。log_mq就是一个索引名,你可以把它当成我要查哪个数据库。
【说明】kibana是个强大的工具,我这里就不详细说明白,可以自行学习。
【添加es的工具类,在项目中查看或利用】
1、添加依赖(版本号随引入的springboot的依赖版本)
- org.springframework.boot spring-boot-starter-data-elasticsearch
复制代码 2、我们在application.yml设置文件中添加es的uri(有许多设置的方式,只要你能连上es就行)
- spring: elasticsearch: rest: uris: 192.168.0.110:9200
复制代码 3、接下我们使用 ElasticsearchRestTemplate 来举行利用,实际底层也是request response的请求响应而已。
- @Componentpublic class ElasticSearchUtil { @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate; /** * @Description: 创建索引(可批量) * @Author: Chenhuanxing * @Date: 2020/12/29 0029 14:53 * @param indexName * @return {@link boolean} */ public boolean createIndex(String... indexName) { return elasticsearchRestTemplate.indexOps(IndexCoordinates.of(indexName)).create(); } /** * @Description: 建索引相当于给实体类建库,实体类要加上注解 @Document(indexName = "test") * @Author: Chenhuanxing * @Date: 2020/12/29 0029 11:18 * @param clazz * @return {@link boolean} */ public boolean createIndex(Class clazz) { return elasticsearchRestTemplate.indexOps(clazz).create(); } /** * @Description: 删除索引 * @Author: Chenhuanxing * @Date: 2020/12/29 0029 14:53 * @param indexName * @return {@link boolean} */ public boolean deleteIndex(String... indexName) { return elasticsearchRestTemplate.indexOps(IndexCoordinates.of(indexName)).delete(); } /** * @Description: 删除实体类上指明的索引 * @Author: Chenhuanxing * @Date: 2020/12/29 0029 14:53 * @param clazz * @return {@link boolean} */ public boolean deleteIndex(Class clazz) { return elasticsearchRestTemplate.indexOps(clazz).delete(); } /** * @Description: 索引是否存在 * @Author: Chenhuanxing * @Date: 2020/12/29 0029 14:53 * @param indexName * @return {@link boolean} */ public boolean isIndexExists(String indexName) { return elasticsearchRestTemplate.indexOps(IndexCoordinates.of(indexName)).exists(); } /** * @Description: 该实体类指明的索引是否存在 * @Author: Chenhuanxing * @Date: 2020/12/29 0029 14:52 * @param clazz * @return {@link boolean} */ public boolean isIndexExists(Class clazz) { return elasticsearchRestTemplate.indexOps(clazz).exists(); } /** * @Description: 获取指定索引和id的一条纪录,可以用map来接,即可以传入Map.class * @Author: Chenhuanxing * @Date: 2020/12/29 0029 14:38 * @param clazz * @param id * @param indexName * @return {@link T} */ public T getById(Class clazz, String id, String indexName) { return elasticsearchRestTemplate.get(id, clazz, IndexCoordinates.of(indexName)); } /** * @Description: 获取实体类上的指定的索引根据id查询的一条纪录 * @Author: Chenhuanxing * @Date: 2020/12/29 0029 14:38 * @param clazz (该实体类指明白哪个索引,用@Document(indexName = "索引名") 体现 ) * @param id 要查询的id * @return {@link T} */ public T getById(Class clazz, String id) { return getById(clazz, id, elasticsearchRestTemplate.getIndexCoordinatesFor(clazz).getIndexName()); } /** * @Description: 类似于 where fieldName = fieldValue 的条件查询, * 返回与文档纪录(相当于关系型数据库的一行行数据)对应实体类聚集; * 实体类上有指明哪个索引 (类上加上注解 @Document(indexName = "test")) * @Author: Chenhuanxing * @Date: 2020/12/29 0029 14:23 * @param clazz * @param fieldName * @param fieldValue * @return {@link List} */ public List getByFieldName(Class clazz,String fieldName,String fieldValue,int page, int size) { return getByFieldName(clazz, fieldName, fieldValue, elasticsearchRestTemplate.getIndexCoordinatesFor(clazz).getIndexName(),page,size); } /** * @Description: 类似于 where fieldName = fieldValue 的条件查询,把返回的数据映射在对类上 * @Author: Chenhuanxing * @Date: 2020/12/29 0029 14:43 * @param clazz 条件查询后返回数据对应的类 * @param fieldName 字段 * @param fieldValue 字段值 * @param indexName 索引名,要查询哪个索引下的数据 * @return {@link List} */ public List getByFieldName(Class clazz,String fieldName,String fieldValue,String indexName,int page, int size) { //匹配查询 MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder(fieldName,fieldValue); return doSearch(clazz, matchQueryBuilder, indexName, page, size); } /** * @Description: 获取指定索引的所有纪录,通报分页参数来分页 * @Author: Chenhuanxing * @Date: 2020/12/29 0029 15:37 * @param clazz * @param indexName * @param page * @param size * @return {@link List} */ public List getAllDocuments(Class clazz, String indexName, int page, int size) { //条件查询 BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder(); //必须匹配全部,即获取全部的意思而已 boolQueryBuilder.must(new MatchAllQueryBuilder()); return doSearch(clazz, boolQueryBuilder, indexName, page, size); } /** * @Description: 通用查询(有分页) * @Author: Chenhuanxing * @Date: 2020/12/29 0029 15:48 * @param clazz * @param queryBuilder * @param indexName * @param page * @param size * @return {@link List} */ private List doSearch(Class clazz,QueryBuilder queryBuilder,String indexName,int page,int size) { NativeSearchQuery query = new NativeSearchQuery(queryBuilder); query.setPageable(PageRequest.of(page, size)); SearchHits search = elasticsearchRestTemplate.search(query, clazz,IndexCoordinates.of(indexName)); //只获取对应的实体类聚集 return search.stream().parallel().map(SearchHit::getContent).collect(Collectors.toList()); }}
复制代码 【说明】
1、可以看到获取指定索引的全部文档的方法里
- //条件查询 BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder(); //必须匹配全部,即获取全部的意思而已 boolQueryBuilder.must(new MatchAllQueryBuilder()); return doSearch(clazz, boolQueryBuilder, indexName, page, size);
复制代码 可以看到这些什么什么queryBuilder,实际上就是和rest请求如下那些查询字段一样的,可以先去相识es的rest请求利用。
- "query": {"bool": {"must": [{"match_all": {}}]}}
复制代码 2、后续可以根据自己的需要,参考我的写法,在工具类中添加更多方法,仔细看下来并不难。固然这个也只是我根据elasticsearchRestTemplate的方法参数编写的工具类,根本上是我的个人明白,仅供参考。
【我们测试一下】
1、编写一个测试类
- @SpringBootTest(classes = {QuartzApplication.class})@RunWith(SpringJUnit4ClassRunner.class)@Slf4jpublic class TestES { @Autowired private ElasticSearchUtil searchUtil; @Test public void test1() { searchUtil.getAllDocuments(testAA.class, "downmsg-log-2020.12.31", 0, 10). forEach(System.out::println); } @Data class testAA{ private String message; }}
复制代码 2、输出效果
可以看到这testAA实体类的message字段对应该索引的message字段,也就是详细的日志信息,这个 粉紫色的 "downMsg"就是我们设置的 appender的name属性值,在output中我就是根据这个来区分差别日志创建或找到差别索引的,如果后续有更好的方法,可以自己完善一下,我这样写确实扩展性不高。
3、固然你也可以hashmap来接响应的数据,字段和值就是key和value。
4、你可以在在实体类上 用@Document 指明是哪个索引,可以少传一个参数,你在工具类添加个重载方法就行。
- @Data@Document(indexName = "downmsg-log-2020.12.31")class testAA{ private String message;}
复制代码 【大部分的个人观点和明白仅供参考】
来源:https://blog.csdn.net/m0_46859537/article/details/112008290
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |