Spring Data Elasticsearch

一、 ELK

Elasticsearch官网中提供了很多技术,其中一些技术是可以配合使用的,里面比较出名的就是ELK技术栈。
ELK是Elasticsearch、Logstash、Kibana三个软件首字母。
Elasticsearch: 全文检索工具。
Kibana:页面管理工具。可以通过Kibana的管理界面操作Elasticsearch
Logstash:日志收集的工具,通过此工具可以实现日志内容收集及格式转换。

二、 Elasticsearch简介

Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式的全文搜索引擎,其对外服务是基于RESTful web接口发布的。Elasticsearch是用Java开发的应用,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到近实时搜索,稳定,可靠,快速,安装使用方便。

1 功能分类

Elasticsearch具备两个主要功能:
搜索。功能和Solr类似。代替目前(海量数据)的MySQL模糊查询。
分析。结合LogStash使用。

2 相关概念

2.1 cluster
集群。Elasticsearch集群由一或多个节点组成,其中有一个主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。Elasticsearch的一个概念就是去中心化,字面上理解就是无中心节点,这是对于集群外部来说的,因为从外部看Elasticsearch集群,在逻辑上是个整体,你与集群中的任何一个节点通信和与整个Elasticsearch集群通信是等价的。也就是说,主节点的存在不会产生单点安全隐患、并发访问瓶颈等问题。
2.2 Index
索引。相当于关系型数据库中的表。其中存储若干相似结构的Document数据。如:客户索引,订单索引,商品索引等。Elasticsearch中的索引不像数据库表格一样有强制的数据结构约束,在理论上,可以存储任意结构的数据。但了为更好的为业务提供搜索数据支撑,还是要设计合适的索引体系来存储不同的数据。
2.3 shards
primary shard:代表索引的主分片,Elasticsearch可以把一个完整的索引分成多个primary shard,这样的好处是可以把一个大的索引拆分成多个分片,分布存储在不同的Elasticsearch节点上,从而形成分布式存储,并为搜索访问提供分布式服务,提高并发处理能。primary shard的数量只能在索引创建时指定,并且索引创建后不能再更改primary shard数量。
2.4 replicas
replica shard:代表索引主分片的副本,Elasticsearch可以设置多个replica shard。replica shard的作用:一是提高系统的容错性,当某个节点某个primary shard损坏或丢失时可以从副本中恢复。二是提高Elasticsearch的查询效率,Elasticsearch会自动对搜索请求进行负载均衡,将并发的搜索请求发送给合适的节点,增强并发处理能力。
2.5 Type
类型。每个索引中都必须有唯一的一个Type,Type是Index中的一个逻辑分类。Elasticsearch中的数据Document是存储在索引下的Type中的。
注意:Elasticsearch5.x及更低版本中,一个Index中可以有多个Type。Elasticsearch6.x版本之后,type概念被弱化,一个index中只能有唯一的一个type。且在7.x版本之后,删除type定义。
2.6 Document
文档。Elasticsearch中的最小数据单元。一个Document就是一条数据,一般使用JSON数据结构表示。每个Index下的Type中都可以存储多个Document。一个Document中可定义多个field,field就是数据字段。如:学生数据({“name”:“张三”, “age”:20, “gender”:“男”})。
2.7 元数据
在Elasticsearch中所有以“_”开头的属性都成为元数据,都有着自己特定的含义。
例如:_index:表示索引
2.8 倒排索引(Elasticsearch搜索原理,面试题)反向索引
对数据进行分析,抽取出数据中的词条,以词条作为key,对应数据的存储位置作为value,实现索引的存储。这种索引称为倒排索引。倒排索引是Document写入Elasticsearch时分析维护的。

3 Elasticsearch常见使用场景

维基百科:全文检索,高亮显示,搜索推荐The Guardian(国外的一个新闻网站),此平台可以对用户的行为(点击、浏览、收藏、评论)、社区网络数据(对新闻的评论等)进行数据分析,为新闻的发布者提供相关的公众反馈。
Stack Overflow(国外的程序异常讨论论坛)
Github(开源代码管理),在千亿级别的代码行中搜索信息电子商务平台等。

三、 Linux安装Elasticsearch

使用的Elasticsearch的版本是7.6.2。Elasticsearch7.x要求Linux内核必须是4+版本以上。
在linux操作系统中,查看内核版本的命令是: uname -a
课堂使用的Linux是CentOS8。内核使用的是4.18。

1 基于Docker安装

1.1.1 拉取Elasticsearch镜像
如果docker还没有开启。需要先开始docker软件
systemctl start docker
拉取Elasticsearch镜像
docker pull elasticsearch:7.6.2
1.1.2 创建并启动容器
docker run --name=elasticsearch -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.6.2
discovery.type=single-node 集群模式:单机版
1.1.3 查看日志
docker logs -f elasticsearch
如果日志中出现started状态,说明启动成功
1.1.4 测试结果
curl http://localhost:9200

1.2 基于Docker的Kibana

Kibana的版本必须和ES的版本对应。
1.2.1 拉取镜像
docker pull kibana:7.6.2
1.2.2 新建并启动容器
docker run -d --name kibana --link elasticsearch:elasticsearch -p 5601:5601 kibana:7.6.2
1.2.3 修改Kibana参数
# docker exec -it kibana /bin/bash
# cd config
# vi kibana.yml
选中部分修改成Docker的IP
在这里插入图片描述
重启docker容器
# docker restart kibana
1.2.4 访问
在浏览器输入 http://192.168.8.137:5601

四、SpringData Elasticsearch

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0">

	<modelVersion>4.0.0</modelVersion>
	<groupId>com.bjsxt</groupId>
	<artifactId>ol_data_elasticsearch</artifactId>
	<version>1.0-SNAPSHOT</version>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.5.RELEASE</version>
	</parent>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
		</dependency>
	
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
		
		<dependency>
			<groupId>net.logstash.logback</groupId>
			<artifactId>logstash-logback-encoder</artifactId>
			<version>6.6</version>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	
	</dependencies>
</project>
application.yml
spring:
  elasticsearch:
    rest:
      uris: http://192.168.8.132:9200
创建实体
package com.bjsxt.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.io.Serializable;

/**
 * 商品类型
 *
 * Document - 描述当前类型对应一个Elasticsearch中的索引。
 *  必要属性:
 *    indexName - 对应的索引命名
 *  可选属性:
 *    shards - 主分片数量, 默认1.
 *    replicas - 副本分片数量,默认1.
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "java_item", shards = 1, replicas = 0)
public class Item implements Serializable {
    /**
     * Id - 当前的属性,对应Elasticsearch中索引的_id元数据。代表主键。
     *      且,当前属性会在Elasticsearch保存的数据结构中。{"id":"", "title":"", "remark":""}
     */
    @Id
    @Field(name = "id", type = FieldType.Keyword)
    private String id; // 主键
    /**
     * Field - 描述Java类型中的属性的映射
     *  name - 对应的ES索引中的字段名。默认和属性同名。
     *  type - 对应的字段类型,默认是FieldType.Auto。建议定义正确的映射类型。避免可能发生的映射错误。
     *  index - 是否创建索引,text类型创建倒排索引,其他类型创建正排索引。默认true
     */
    @Field(name = "title", type = FieldType.Text, analyzer = "ik_max_word")
    private String title; // 标题
    @Field(name = "remark", type = FieldType.Text, analyzer = "ik_smart")
    private String remark; // 详情
    @Field(name = "price", type = FieldType.Long)
    private Long price; // 单价, 单位是分
    @Field(name = "number", type = FieldType.Integer, index = false)
    private int num; // 库存数量
}

创建索引 设置映射
@SpringBootTest(classes = {MyDataElasticsearchApplication.class})
@RunWith(SpringRunner.class)
public class TestDataElasticsearch {
    // 基于HTTP协议,9200端口的RestAPI访问客户端。
    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Test
    public void createAndPutMapping(){
        // 创建索引
        elasticsearchRestTemplate.indexOps(Item.class).create();
        // 设置映射
        elasticsearchRestTemplate.indexOps(Item.class).putMapping(
                elasticsearchRestTemplate.indexOps(Item.class).createMapping()
        );
    }
简单增删改查
@SpringBootTest(classes = {MyDataElasticsearchApplication.class})
@RunWith(SpringRunner.class)
public class TestDataElasticsearch {
    // 基于HTTP协议,9200端口的RestAPI访问客户端。
    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Test
    public void testGet(){
        // 主键查询数据
        Item item = elasticsearchRestTemplate.get("001", Item.class);
        System.out.println(item);
    }

    @Test
    public void testDeleteDocument(){
        // 主键删除
        String result = elasticsearchRestTemplate.delete("-P8E8XcBBxUhJQR7_GTO", Item.class);
        System.out.println(result);
    }

    /**
     * 使用覆盖方式实现更新。就是使用save方法。参数对象的主键有数据,且对应ES中的主键,则是覆盖。
     */
    @Test
    public void testSave(){
        // 保存单条数据。 返回值就是保存的对象。 如果参数对象中Id主键属性值是null,则返回的对象中Id值是ES生成的主键。
        Item item = elasticsearchRestTemplate.save(
                new Item("001", "华为P40", "2020年生产,高端手机", 429900L, 999)
        );
        System.out.println(item);
        item = elasticsearchRestTemplate.save(
                new Item(null, "华为P40 Pro", "华为P40增强版手机", 589900L, 999)
        );
        System.out.println(item);

        // 批量新增,返回值特性和保存单条数据一致。
        List<Item> items = new ArrayList<>();
        items.add(new Item("003", "华为P40 Pro Plus", "华为P40Pro增强版手机", 629900L, 999));
        items.add(new Item("004", "小米10", "很贵的暖手宝", 229900L, 999));
        items.add(new Item("005", "三星", "可以自卫,容易误伤", 329900L, 999));
        Iterable<Item> results = elasticsearchRestTemplate.save(items);
        for(Item i : results){
            System.out.println(i);
        }
    }

    @Test
    public void deleteIndex(){
        // 所有针对索引的操作都封装在IndexOperations类型中。
        IndexOperations indexOps = elasticsearchRestTemplate.indexOps(Item.class);
        indexOps.delete();
    }
搜索
package com.bjsxt.test;

import com.bjsxt.MyDataElasticsearchApplication;
import com.bjsxt.pojo.Item;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.List;

@SpringBootTest(classes = {MyDataElasticsearchApplication.class})
@RunWith(SpringRunner.class)
public class TestDataElasticsearch {
    // 基于HTTP协议,9200端口的RestAPI访问客户端。
    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    // 高亮搜索
    @Test
    public void testHighlight(){
        // 定义高亮字段
        HighlightBuilder.Field titleField = new HighlightBuilder.Field("title");
        titleField.preTags("<span style='color:red'>");
        titleField.postTags("</span>");
        titleField.fragmentSize(2);
        titleField.numOfFragments(1);

        // withHighlightFields(Field... 高亮字段数组)
        Query query =
                new NativeSearchQueryBuilder()
                        .withQuery(QueryBuilders.matchQuery("title", "华为"))
                        .withHighlightFields(titleField)
                        .build();

        SearchHits<Item> hits =
                elasticsearchRestTemplate.search(query, Item.class);

        // 注意, Item对象中,不包含高亮数据,需要手工处理。
        for(SearchHit<Item> hit : hits){
            Item item = hit.getContent();
            // 处理高亮的方式
            // 根据高亮字段名称,获取高亮数据集合。返回结果是List<String>
            List<String> hlList = hit.getHighlightField("title");
            if(hlList != null && hlList.size() > 0){
                // 有高亮数据
                item.setTitle(hlList.get(0));
            }

            System.out.println(item);
        }
    }

    // 排序和分页
    // withPageable(Pageable) 设置分页+排序
    // Pageable接口有实现类,类型是PageRequest,静态方法of.
    // of(int page, int size[, Sort sort]) ; page 第几页,从0开始计数;  size 每页查询多少条数据
    // Sort 排序类型, 静态方法  Sort.by(Direction 升降序, String... 按照哪一个字段排序)
    // Sort.by(Sort.Order... 多字段排序方案)  Sort.Order.desc("降序字段名")   Sort.Order.asc("升序字段名")
    @Test
    public void testSortAndPageable(){
        Query query =
                new NativeSearchQueryBuilder()
                        .withQuery(QueryBuilders.matchAllQuery())
                        .withPageable(
                                PageRequest.of(1, 2,
                                        Sort.by(
                                                Sort.Order.desc("price"),
                                                Sort.Order.asc("title")
                                        ))
                        )
                        .build();
        SearchHits<Item> hits =
                elasticsearchRestTemplate.search(query, Item.class);
        for(SearchHit<Item> hit : hits){
            System.out.println(hit.getContent());
        }
    }

    // 搜索数据 search

    // DSL bool
    @Test
    public void testBool(){
        // 创建一个bool搜索条件。  bool:{ must:[{match:{field:value}}], must_not:[], should:[] }
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        // 获取bool搜索条件中的Must条件集合。增加到集合中的条件,就是必要条件。
        List<QueryBuilder> must = queryBuilder.must();
        must.add(QueryBuilders.rangeQuery("price").gt(100000L));
        // 其他的搜索条件集合获取方式相似。
        List<QueryBuilder> mustNot = queryBuilder.mustNot();
        List<QueryBuilder> should = queryBuilder.should();

        // 简化设置搜索条件的方式是,下述代码相当于: queryBuilder.should().add(QueryBuilders.matchAllQuery());
        queryBuilder.should(QueryBuilders.matchAllQuery());

        Query query = new NativeSearchQueryBuilder().withQuery(queryBuilder).build();
        SearchHits<Item> hits =
                elasticsearchRestTemplate.search(query, Item.class);
        for(SearchHit<Item> hit : hits){
            System.out.println(hit.getContent());
        }
    }

    // DSL match all
    @Test
    public void testMatchAll(){
        SearchHits<Item> hits =
                elasticsearchRestTemplate.search(
                        new NativeSearchQueryBuilder()
                                .withQuery(QueryBuilders.matchAllQuery())
                                .build(),
                        Item.class
                );
        for(SearchHit<Item> hit : hits){
            System.out.println(hit.getContent());
        }
    }

    // DSL match phrase
    @Test
    public void testMatchPhrase(){
        SearchHits<Item> hits =
                elasticsearchRestTemplate.search(
                        new NativeSearchQueryBuilder()
                                .withQuery(QueryBuilders.matchPhraseQuery("title", "华为P40 Pro"))
                                .build(),
                        Item.class
                );
        for(SearchHit<Item> hit : hits){
            System.out.println(hit.getContent());
        }
    }

    // DSL range
    @Test
    public void testRangeQuery(){
        SearchHits<Item> hits =
                elasticsearchRestTemplate.search(
                        new NativeSearchQueryBuilder()
                                .withQuery(QueryBuilders.rangeQuery("price").gt(200000L).lt(500000L))
                                .build(),
                        Item.class
                );
        for(SearchHit<Item> hit : hits){
            System.out.println(hit.getContent());
        }
    }

    // DSL match
    @Test
    public void testMatchQuery(){
        SearchHits<Item> hits =
                elasticsearchRestTemplate.search(
                        new NativeSearchQueryBuilder()
                                .withQuery(QueryBuilders.matchQuery("title", "小米"))
                                .build(),
                        Item.class
                );
        System.out.println("数据数量: " + hits.getTotalHits());
        for(SearchHit<Item> hit : hits){
            System.out.println(hit.getContent());
        }
    }

    // query string search。  在所有的字段中搜索。
    @Test
    public void testQueryString(){
        // 所有的搜索逻辑,都是通过search实现的。
        /*
         * SearchHits<T> search(Query query, Class<T> clazz)
         * 返回值,就是在Kibana控制台上,搜索的结果JSON转换的Java对象。
         * 泛型就是搜索结果数据,转换的Java对象类型。
         * 参数query,就是搜索条件。
         * 搜索条件,建议使用构建器来创建。
         *
         * QueryBuilder - 用来描述搜索条件的。在DSL和querystring两种搜索中,有什么搜索方式,就有对应的实现类型。
         * 如:querystring - QueryStringQueryBuilder
         * 如:DSL中的match - MatchQueryBuilder
         * 如:DSL中的range - RangeQueryBuilder
         */

        QueryBuilder queryBuilder = QueryBuilders.queryStringQuery("华为");
        Query query =
                new NativeSearchQueryBuilder()
                        .withQuery(queryBuilder)
                        .build();
        SearchHits<Item> hits = elasticsearchRestTemplate.search(query, Item.class);
        System.out.println("总计数据行数: " + hits.getTotalHits());

        System.out.println(elasticsearchRestTemplate.indexOps(Item.class));

        // SearchHits类型,本身实现了集合接口|可迭代接口。
        for(SearchHit<Item> hit : hits){
            Item item = hit.getContent();
            System.out.println("SearchHits.toString() - " + hits.toString());
            System.out.println("SearchHit.toString() - " + hit.toString());
            System.out.println("_id - " + hit.getId());
            System.out.println("查询到的商品对象 - " + item);
        }
        System.out.println("=====================================================================");
        for(SearchHit<Item> hit : hits.getSearchHits()){
            System.out.println("查询到的商品 - " + hit.getContent());
        }

    }

五、 LogStash

1 什么是LogStash

官方文字说明:Logstash 是开源的服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到您最喜欢的“存储库”中。
通俗说明:Logstash是一款强大的数据处理工具,常用作日志处理。
到目前为止,Logstash已经有超过200个可用的插件,以及创建和贡献自己的灵活性。社区生态非常完善,对于我们可以放心的使用。
在这里插入图片描述
在这里插入图片描述

2 为什么使用Logstash

通常当系统发生故障时,工程师需要登录到各个服务器上,使用 grep / sed / awk 等 Linux 脚本工具去日志里查找故障原因。在没有日志系统的情况下,首先需要定位处理请求的服务器,如果这台服务器部署了多个实例,则需要去每个应用实例的日志目录下去找日志文件。每个应用实例还会设置日志滚动策略(如:每天生成一个文件),还有日志压缩归档策略等。
这样一系列流程下来,对于我们排查故障以及及时找到故障原因,造成了比较大的麻烦。因此,如果我们能把这些日志集中管理,并提供集中检索功能,不仅可以提高诊断的效率,同时对系统情况有个全面的理解,避免事后救火的被动。
所以日志集中管理功能就可以使用ELK技术栈进行实现。Elasticsearch只有数据存储和分析的能力,Kibana就是可视化管理平台。还缺少数据收集和整理的角色,这个功能就是Logstash负责的。

3 Logstash工作原理

3.1 Data Source
Logstash 支持的数据源有很多。例如对于日志功能来说只能能有日志记录和日志传递功能的日志都支持,Spring Boot中默认推荐logback支持日志输出功能(输出到数据库、数据出到文件)。
我们就使用logback进行日志输出给Logstash。
3.2 Logstash Pipeline
整个整体就是Logstash的功能。
在Logstash中包含非常重要的三个功能:
a) Input
输入源,一般配置为自己监听的主机及端口。DataSource向指定的ip及端口输出日志,Input 输入源监听到数据信息就可以进行收集。
b) Filter
过滤功能,对收集到的信息进行过滤(额外处理),也可以省略这个配置(不做处理)
c) Output
把收集到的信息发送给谁。在ELK技术栈中都是输出给Elasticsearch,后面数据检索和数据分析的过程就给Elasticsearch了。

最终效果:通过整体步骤就可以把原来一行日志信息转换为Elasticsearch支持的Document形式(键值对形式)的数据进行存储。
在这里插入图片描述

4 安装Logstash
4.1 安装Logstash
docker pull logstash:7.6.2

4.2 启动容器
docker run -it -p 4560:4560 --name logstash -d logstash:7.6.2

4.3 修改配置
进入容器
docker exec -it logstash /bin/bash
修改配置文件
vi /usr/share/logstash/config/logstash.yml
把ip修改成elasticsearch访问地址IP
在这里插入图片描述

4.4 修改输入输出配置
继续在容器命令行输入
vi /usr/share/logstash/pipeline/logstash.conf

配置解释说明:

input:接收日志输入配置
tcp: 协议
mode: logstash服务
port:端口,自己指定。默认4560
output:日志处理输出
elasticsearch: 交给es处理
action:es中index命令。也就是新增命令。
hosts:es的主机
index:存储日志的索引。如果不存在可以自动创建。默认的type名称为doc
一定要先启动编辑状态(点击键盘i键)在粘贴,如果没启用第一行是nput{少个i。

input {
        tcp {
                mode => "server"
                port => 4560
        }
}
filter {
}
output {
        elasticsearch {
                action => "index"
                hosts  => "192.168.8.128:9200"
                index  => "test_log"
        }
}

4.5 重启容器
退出容器命令行,进入到Linux终端,重启logstash容器。
docker restart logstash
在这里插入图片描述

4.6 查看日志
docker logs -f logstash
在这里插入图片描述

六、 使用Logback向Logstash中输出日志

pom.xml

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.3.RELEASE</version>
</parent>
<dependencies>
    <dependency>
        <groupId>net.logstash.logback</groupId>
        <artifactId>logstash-logback-encoder</artifactId>
        <version>6.3</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--该日志将日志级别不同的log信息保存到不同的文件中 -->
<configuration>
   <include resource="org/springframework/boot/logging/logback/defaults.xml" />

   <springProperty scope="context" name="springAppName"
      source="spring.application.name" />

   <!-- 日志在工程中的输出位置 -->
   <property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}" />

   <!-- 控制台的日志输出样式 -->
   <property name="CONSOLE_LOG_PATTERN"
      value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />

   <!-- 控制台输出 -->
   <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
      <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
         <level>INFO</level>
      </filter>
      <!-- 日志输出编码 -->
      <encoder>
         <pattern>${CONSOLE_LOG_PATTERN}</pattern>
         <charset>utf8</charset>
      </encoder>
   </appender>
    <!-- logstash远程日志配置-->
    <appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <destination>192.168.8.132:4560</destination>
        <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder" />
    </appender>
   <!-- 日志输出级别 -->
   <root level="DEBUG">
      <appender-ref ref="console" />
      <appender-ref ref="logstash" />
   </root>
</configuration>

启动类

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class,args);
    }
}

七、 在Kibana中查看日志信息

1 使用命令方式查看
可以直接在Dev Tools中输入命令查看日志信息。
输入: GET test_log/_search 查看全部。
在这里插入图片描述

八、 搭建日志系统

pom.xml

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.3.RELEASE</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
    </dependency>
</dependencies>

application.yml

spring:
  elasticsearch:
    rest:
      uris: http://192.168.8.132:9200

新建实体

/**
 * 对应Elasticsearch中的logstash_log_index索引。
 */
@Data
@Document(indexName = "logstash_log_index")
public class MyLogMessage implements Serializable {
    @Id
    private String id;
    @Field(name = "host", type = FieldType.Text)
    private String host;
    @Field(name = "@version", type = FieldType.Text)
    private String version;
    @Field(name = "port", type = FieldType.Long)
    private Long port;
    @Field(name = "@timestamp", type = FieldType.Date, format = DateFormat.date_time)
    private Date timestamp;
    @Field(name = "message", type = FieldType.Text)
    private String message;
}

MyLogMessageDao

public interface MyLogMessageDao {
    /**
     * 在Message字段中做搜索。 分页处理。
     * @param page 从0开始的页码
     * @param rows 每页多少行
     * @param query 关键字
     * @return
     *  key                     value
     *  total                   总计数据行数
     *  List<MyLogMessage>      当前页面显示的数据集合,对象中的message高亮处理。
     */
    Map<String, Object> searchByPage(int page, int rows, String query);
}

MyLogMessageDaoImpl

@Repository
public class MyLogMessageDaoImpl implements MyLogMessageDao {
    @Autowired
    private ElasticsearchRestTemplate restTemplate;

    @Override
    public Map<String, Object> searchByPage(int page, int rows, String query) {
        HighlightBuilder.Field f = new HighlightBuilder.Field("message");
        f.preTags("<span style='color:red'>");
        f.postTags("</span>");
        f.fragmentSize(Integer.MAX_VALUE);
        f.numOfFragments(1);

        SearchHits<MyLogMessage> hits =
                restTemplate.search(
                        new NativeSearchQueryBuilder()
                                .withQuery(QueryBuilders.matchQuery("message", query)) // 搜索条件
                                .withPageable(PageRequest.of(page, rows)) // 分页
                                .withHighlightFields(f) // 高亮
                                .build(),
                        MyLogMessage.class
                );

        List<MyLogMessage> contents = new ArrayList<>();

        for(SearchHit<MyLogMessage> hit : hits){
            MyLogMessage logMessage = hit.getContent();
            // 处理高亮
            List<String> hl = hit.getHighlightField("message");
            if(hl != null && hl.size() > 0){
                // 有高亮
                logMessage.setMessage(hl.get(0));
            }

            contents.add(logMessage);
        }

        Map<String, Object> result = new HashMap<>();
        result.put("total", hits.getTotalHits());
        result.put("datas", contents);

        return result;
    }
}

新建service及实现类

package com.bjsxt.service;

import java.util.Map;

public interface MyLogService {
    Map<String, Object> searchByPage(int page, int rows, String query);
}

@Service
public class MyLogServiceImpl implements MyLogService {
    @Autowired
    private MyLogMessageDao logMessageDao;
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public Map<String, Object> searchByPage(int page, int rows, String query) {
        Map<String, Object> result = logMessageDao.searchByPage(page, rows, query);
        
        return result;
    }
}

新建控制器

@RestController
public class MyLogController {
    @Autowired
    private MyLogService logService;

    @RequestMapping("/find")
    public Map<String, Object> findByPage(
            @RequestParam(name = "page", defaultValue = "0") int page,
            @RequestParam(name = "rows", defaultValue = "10") int rows,
            @RequestParam(name = "query", defaultValue = "level") String query){
        return logService.searchByPage(page, rows, query);
    }
}

启动类

@SpringBootApplication
public class MyLogManagerApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyLogManagerApplication.class, args);
    }
}

测试结果
在浏览器输入: http://localhost:8080/ ?page=1,rows=2

九、 在Java代码中获取日志信息

新建实体类

MessageBody

/**
 * 是MyLogMessage类型对象中message字符串属性转换后的Java对象类型。
 */
@Data
public class MessageBody implements Serializable {
    // 使用Jackson把Json字符串转成Java对象,默认是同名转换。如果JSON中的属性名和Java对象的属性名不同
    // 用JsonProperty注解定义JSON中的属性名称。
    @JsonProperty("@timestamp")
    private Date timestamp;
    @JsonProperty("@version")
    private String version;
    private String message;
    @JsonProperty("logger_name")
    private String loggerName;
    @JsonProperty("thread_name")
    private String threadName;
    private String level;
    @JsonProperty("level_value")
    private String levelValue;
}

修改实体类 添加MessageBody

/**
 * 对应Elasticsearch中的logstash_log_index索引。
 */
@Data
@Document(indexName = "logstash_log_index")
public class MyLogMessage implements Serializable {
    @Id
    private String id;
    @Field(name = "host", type = FieldType.Text)
    private String host;
    @Field(name = "@version", type = FieldType.Text)
    private String version;
    @Field(name = "port", type = FieldType.Long)
    private Long port;
    @Field(name = "@timestamp", type = FieldType.Date, format = DateFormat.date_time)
    private Date timestamp;
    @Field(name = "message", type = FieldType.Text)
    private String message;

    private MessageBody body;
}

修改service实现类

@Service
public class MyLogServiceImpl implements MyLogService {
    @Autowired
    private MyLogMessageDao logMessageDao;
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public Map<String, Object> searchByPage(int page, int rows, String query) {
        Map<String, Object> result = logMessageDao.searchByPage(page, rows, query);

        for(MyLogMessage message : (List<MyLogMessage>)result.get("datas")){
            // 获取JSON格式字符串
            String jsonMessage = message.getMessage();
            MessageBody messageBody = null;
            try {
                // 转换字符串为Java对象
                messageBody = objectMapper.readValue(jsonMessage, MessageBody.class);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            // 设置对象
            message.setBody(messageBody);
        }

        return result;
    }
}

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AloneDrifters

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值