SpringBoot集成ELK实现数据存储和日志管理

摘要

本文介绍在SpringBoot 2.7.18中集成Elasticsearch、Logstash、Kibana的步骤,简单展示了ES增删改查的API用法,测试Logstash日志收集,并实现Kibana数据看板可视化分析日志。

示例步骤

1) 启动docker中的elk服务

我这里logstash通信地址是192.168.233.129:4560

2) 引入依赖

xml 复制代码
<dependencies>
    <!--我这里sprinboot用的2.7.18 es9.0.3服务调save接口时会有报错 但不影响逻辑生效-->
    <!-- Spring Data Elasticsearch -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
    <!-- Elasticsearch Rest High Level Client -->
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
    </dependency>
    <!--集成logstash 版本有讲究 不然会报错-->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.11</version>
    </dependency>
    <dependency>
        <groupId>net.logstash.logback</groupId>
        <artifactId>logstash-logback-encoder</artifactId>
        <version>6.6</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
</dependencies>

3)配置连接ES(据说过时了,写法仅供参考)

kotlin 复制代码
package org.coffeebeans.config;

import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;

@Configuration
public class ElasticsearchConfig {

    @Value("${spring.data.elasticsearch.rest.uris}")
    private String elasticsearchUrl;

    @Value("${spring.data.elasticsearch.rest.username}")
    private String username;

    @Value("${spring.data.elasticsearch.rest.password}")
    private String password;

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));

        return new RestHighLevelClient(
                RestClient.builder(new HttpHost("192.168.233.129", 9200, "http"))
                        .setHttpClientConfigCallback(httpClientBuilder ->
                                httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)));
    }

    @Bean
    public ElasticsearchOperations elasticsearchTemplate(RestHighLevelClient client) {
        return new ElasticsearchRestTemplate(client);
    }
}

application.yml

yaml 复制代码
spring:
  data:
    elasticsearch:
      rest:
        uris: http://192.168.233.129:9200 # Elasticsearch 地址
        username: elastic
        password: your_password
        connection-timeout: 1000 #连接超时
        read-timeout: 3000 #请求超时
      data:
        elasticsearch:
          repositories:
            enabled: true

4)建立实体对象映射ES索引

vbnet 复制代码
package org.coffeebeans.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.elasticsearch.common.geo.GeoPoint;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.*;
import java.math.BigDecimal;
import java.util.Date;

/**
 * <li>ClassName: Book </li>
 * <li>Author: OakWang </li>
 * 把实体映射成ES的索引
 */
@Data
@Document(indexName = "book")//indexName指定索引名 默认存储库引导时创建索引
public class Book {

    //标记主键字段 对应ES的_id
    @Id
    private Integer id;

    //type指定字段类型;analyzer指定自定义分析器和规范化器,这里使用IK分词器(需要es安装插件)
    //FieldType常用 Text, Keyword, Integer, Long, Double, Date, Boolean, Object, Nested, Ip
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String title;

    @Field(type = FieldType.Keyword)
    private String author;

    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String content;

    @Field(type = FieldType.Double)
    private BigDecimal price;

    //标记地理坐标字段(经纬度)
    @GeoPointField
    private GeoPoint location;

    @CreatedDate
    @Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second)// format指定内置日期格式
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//这里应该确保时间格式和索引里的匹配
    private Date createTime;

    /*
        对应ES的restful写法 使用IK分词器的写法(ES需要提前安装好IK分词器)

        PUT /book
        {
          "settings": {
            "number_of_shards": 2,
            "number_of_replicas": 1,
            "analysis": {
              "analyzer": {
                "ik_max_word": {
                  "type": "custom",
                  "tokenizer": "ik_max_word"
                }
              }
            }
          },
          "mappings": {
            "properties": {
              "title": {
                "type": "text",
                "analyzer": "ik_max_word"
              },
              "author": {
                "type": "keyword"
              },
              "content": {
                "type": "text",
                "analyzer": "ik_max_word"
              },
              "price": {
                "type": "double"
              },
              "location": {
                "type": "geo_point"
              },
              "createTime": {
                "type": "date",
                "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss||strict_date_optional_time"
              }
            }
          }
        }
     */


    /*
        使用内置分词器的写法

        PUT /book
        {
          "settings": {
            "number_of_shards": 2,
            "number_of_replicas": 1
          },
          "mappings": {
            "properties": {
              "title":   { "type": "text", "analyzer": "standard" },
              "author":  { "type": "keyword" },
              "content": { "type": "text", "analyzer": "standard" },
              "price":   { "type": "double" },
              "location":{ "type": "geo_point" },
              "createTime": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss||strict_date_optional_time" }
            }
          }
        }

     */

}

5)定义Repository

kotlin 复制代码
package org.coffeebeans.mapper;

import org.coffeebeans.entity.Book;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

/**
 * <li>ClassName: BookRepository </li>
 * <li>Author: OakWang </li>
 */
@Repository
public interface BookRepository extends ElasticsearchRepository<Book, String> {
    // 自定义查询方法(根据方法名自动生成查询) 这一步没试过
}

6)逻辑类Service

typescript 复制代码
package org.coffeebeans.service;

import lombok.extern.slf4j.Slf4j;
import org.coffeebeans.entity.Book;
import org.coffeebeans.mapper.BookRepository;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * <li>ClassName: BookService </li>
 * <li>Author: OakWang </li>
 */
@Slf4j
@Service
public class BookService {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Autowired
    private BookRepository bookRepository;

    // 保存
    public Book save(Book book) {
        return bookRepository.save(book);
    }

    // 查询
    public Book findById(String id) {
        return bookRepository.findById(id).orElse(null);
    }

    // 删除
    public void deleteById(String id) {
        bookRepository.deleteById(id);
    }

    // 高亮查询
    public List<Map<String, Object>> searchWithHighlight(String keyword) {
        // 1. 构建查询条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(QueryBuilders.matchQuery("title", keyword));

        // 2. 配置高亮字段和样式
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder
                .field("title")  // 指定需要高亮的字段
                .preTags("<span style= 'color:red'>")  // 匹配词前缀标签
                .postTags("</span>");  // 匹配词后缀标签
        sourceBuilder.highlighter(highlightBuilder);

        // 3. 构建搜索请求
        SearchRequest searchRequest = new SearchRequest("book");
        searchRequest.source(sourceBuilder);

        List<Map<String, Object>> results = new ArrayList<>();
        try {
            // 4. 执行查询
            SearchResponse searchResponse= restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

            // 5. 处理高亮结果
            for (SearchHit hit : searchResponse.getHits()) {
                Map<String, Object> sourceAsMap = hit.getSourceAsMap(); // 获取结果集
                Map<String, HighlightField> highlightFields = hit.getHighlightFields(); // 获取高亮字段

                // 将高亮字段值替换到原始结果中
                if (highlightFields.containsKey("title")) {
                    HighlightField highlightField = highlightFields.get("title");
                    if (highlightField.getFragments() != null && highlightField.getFragments().length > 0) {
                        sourceAsMap.put("title", highlightField.getFragments()[0].string());
                    }
                }
                results.add(sourceAsMap);
            }
        } catch (IOException e) {
            e.printStackTrace();
            log.error("查询失败:" + e.getMessage());
        }
        return results;
    }
}

7)绑定logstash

定义输出到logstash的日志逻辑logback.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="log.path" value="../logs/elasticsearch-use"/>
    <property name="console.log.pattern"
              value="%red(%d{yyyy-MM-dd HH:mm:ss.SSS}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
    <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>

    <!-- logstash推送 -->
    <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <destination>192.168.233.129:4560</destination>
        <encoder class="net.logstash.logback.encoder.LogstashEncoder" charset="UTF-8"/>
    </appender>

    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${console.log.pattern}</pattern>
            <charset>utf-8</charset>
        </encoder>
    </appender>

    <!-- 系统操作日志 -->
    <root level="info">
        <appender-ref ref="LOGSTASH"/>
        <appender-ref ref="console"/>
    </root>
</configuration>

8)运行测试类,写入日志

vbscript 复制代码
package org.coffeebeans;

import lombok.extern.slf4j.Slf4j;
import org.coffeebeans.entity.Book;
import org.coffeebeans.service.BookService;
import org.elasticsearch.common.geo.GeoPoint;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.math.BigDecimal;
import java.util.Date;

/**
 * <li>ClassName: org.coffeebeans.ESTest </li>
 * <li>Author: OakWang </li>
 */
@Slf4j
@SpringBootTest
public class ESTest {


    @Autowired
    private BookService bookService;

    @Test
    void test1() {
       Book book = new Book();
       book.setId(1);
       book.setAuthor("作者");
       book.setPrice(new BigDecimal("30.00"));
       book.setLocation(new GeoPoint(40.12, -71.34));
       book.setTitle("你是一个牛马");
       book.setContent("赚钱生活学习");
       book.setCreateTime(new Date());
       bookService.save(book); //这里es和服务端版本不兼容会报错Unable to parse response body for Response,但不影响结果
       /*
            类比request:
          PUT /book/_doc/1
          {
            "id": 1,
            "author": "作者",
            "price": 30.00,
            "location": {
             "lat": 40.12,
             "lon": -71.34
            },
            "title": "你是一个牛马",
            "content": "赚钱生活学习",
            "createTime": "2025-07-20 12:30:39"
          }
         */
    }

    @Test
    void test2() {
       log.info("返回结果:" + bookService.findById("1"));
        /*
            返回结果:Book(id=1, title=你是一个牛马, author=作者, content=赚钱生活学习, price=30.0, location=40.12, -71.34, createTime=Mon Jul 20 12:30:39 CST 2025)

            类比request:GET /book/_doc/1
            返回:{"id":1,"title":"你是一个牛马","author":"作者","content":"赚钱生活学习","price":30.0,"location":{"lat":40.12,"lon":-71.34,"geohash":"drjk0xegcw06","fragment":true},"createTime":"2025-07-20 12:30:39"}
         */
    }

    @Test
    void test3() {
       log.info("返回结果:" + bookService.searchWithHighlight("你是一个牛马"));
        /*
            返回结果:[{createTime=2025-07-28T14:09:39, author=作者, price=30.0, location={lon=-71.34, lat=40.12}, _class=org.coffeebeans.entity.Book, id=1, title=<span style= 'color:red'>你</span><span style= 'color:red'>是</span><span style= 'color:red'>一个</span><span style= 'color:red'>牛马</span>, content=赚钱生活学习}]

            类比request:
          GET /book/_search
          {
            "query": {
             "match": {
               "title": {
                "query": "你是一个牛马",
                "analyzer": "ik_max_word"
               }
              }
            },
            "highlight": {
             "pre_tags": ["<span style='color:red'>"],
             "post_tags": ["</span>"],
             "fields": {
               "title": {}
             }
            }
          }
            返回:[{"createTime":"2025-07-20T12:30:39","author":"作者","price":30.0,"location":{"lon":-71.34,"lat":40.12},"_class":"org.coffeebeans.entity.Book","id":1,"title":"<span style= 'color:red'>你</span><span style= 'color:red'>是</span><span style= 'color:red'>一个</span><span style= 'color:red'>牛马</span>","content":"赚钱生活学习"}]
         */
    }

    @Test
    void test4() {
       bookService.deleteById("1"); //这里es和服务端版本不兼容会报错Unable to parse response body for Response,但不影响结果
        /*
            类比request:
            DELETE /book/_doc/1
         */
    }

    @Test
    void test5() {
       log.info("返回结果:来自sprinboot的测试日志");
    }

}

9)kibana新建数据视图管理日志

需确保日志写进来了,索引模式才能匹配到源,进行视图管理。

打开discover进行日志筛选

总结

以上我们了解了如何在Springboot2.7.18中集成ELK,以实现ES的API调用,并能用Logstash收集日志,ES存储日志,Kibana可视化分析日志。常见问题有版本间的兼容性、日志推送的集成写法、端口通信、ES配置等。

关注公众号:咖啡Beans

在这里,我们专注于软件技术的交流与成长,分享开发心得与笔记,涵盖编程、AI、资讯、面试等多个领域。无论是前沿科技的探索,还是实用技巧的总结,我们都致力于为大家呈现有价值的内容。期待与你共同进步,开启技术之旅。

相关推荐
要一起看日出3 小时前
Shiro概述
java·spring boot·java-ee
Q_Q5110082854 小时前
springboot+python+uniapp基于微信小程序的旅游服务系统景点信息展示 路线推荐 在线预约 评论互动系统
spring boot·python·微信小程序·django·flask·uni-app
王嘉俊9254 小时前
SpringBoot应用开发指南:从入门到高级配置与自动装配原理
java·spring boot·后端·spring·ssm
维持好习惯5 小时前
复杂Excel文件导入功能(使用AI快速实现)
java·spring boot·excel
单线程bug6 小时前
Spring Boot中Filter与Interceptor的区别
java·spring boot·后端
小蒜学长6 小时前
基于uni-app的蛋糕订购小程序的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端·小程序·uni-app
Ttang236 小时前
ES+MySQL实时搜索架构实战解析
mysql·elasticsearch·架构
麦麦大数据7 小时前
J002 Vue+SpringBoot电影推荐可视化系统|双协同过滤推荐算法评论情感分析spark数据分析|配套文档1.34万字
vue.js·spring boot·数据分析·spark·可视化·推荐算法
鼠鼠我捏,要死了捏13 小时前
Spring Boot Actuator自定义指标与监控实践指南
spring boot·监控·actuator