当前位置: 代码网 > it编程>编程语言>Java > 基于SpringBoot实现简单的ELK日志搜索系统

基于SpringBoot实现简单的ELK日志搜索系统

2025年08月13日 Java 我要评论
一、基础环境准备实现 elk 系统的首要前提是搭建好运行所需的基础环境,确保各组件能正常启动和通信。java 环境elasticsearch、logstash、spring boot 均基于 java

一、基础环境准备

实现 elk 系统的首要前提是搭建好运行所需的基础环境,确保各组件能正常启动和通信。

java 环境

  • elasticsearch、logstash、spring boot 均基于 java 开发,需安装jdk 8 及以上版本(推荐 jdk 11,兼容性更好)。
  • 配置java_home环境变量,确保命令行可识别javajavac命令。

操作系统

  • 支持 windows、linux、macos 等主流系统,但生产环境推荐 linux(如 centos、ubuntu),稳定性和性能更优。
  • 注意:elasticsearch 在 linux 下需配置用户权限(避免 root 用户直接启动),并调整虚拟内存参数(如vm.max_map_count=262144)。

网络环境

  • 确保 elk 各组件(elasticsearch、logstash、kibana)及 spring boot 应用在同一网络环境中,端口可正常通信:
    • elasticsearch 默认端口:9200(http)、9300(节点间通信)
    • logstash 默认端口:5044(接收 beats 数据)、9600(监控)
    • kibana 默认端口:5601
  • 关闭防火墙或开放上述端口(开发环境可简化,生产环境需严格配置)。

二、elk 组件安装与配置

需单独安装 elasticsearch、logstash、kibana,并完成基础配置(以单机版为例,集群版需额外配置)。

elasticsearch

  • 作用:存储和索引日志数据。
  • 安装:从官网下载对应版本,解压后即可运行(bin/elasticsearch)。

基础配置(config/elasticsearch.yml):

yaml

cluster.name: my-elk-cluster  # 集群名称(单机可自定义)
node.name: node-1             # 节点名称
network.host: 0.0.0.0         # 允许所有ip访问(开发环境)
http.port: 9200               # http端口

验证:访问http://localhost:9200,返回节点信息即启动成功。如下:

logstash

  • 作用:收集、过滤、转换日志数据,发送到 elasticsearch。

安装:从官网下载,解压后配置管道(config/logstash-simple.conf):

conf

input {
  tcp {
    port => 5000  # 接收spring boot日志的端口
    codec => json_lines  # 解析json格式日志
  }
}
output {
  elasticsearch {
    hosts => ["localhost:9200"]  # elasticsearch地址
    index => "springboot-logs-%{+yyyy.mm.dd}"  # 日志索引名(按天分割)
  }
  stdout { codec => rubydebug }  # 同时输出到控制台(调试用)
}

启动:bin/logstash -f config/logstash-simple.conf。如下:

kibana

  • 作用:可视化展示 elasticsearch 中的日志数据。

安装:从官网下载,解压后配置(config/kibana.yml):

yaml

server.host: "0.0.0.0"  # 允许所有ip访问
elasticsearch.hosts: ["http://localhost:9200"]  # 连接elasticsearch

启动:bin/kibana,访问http://localhost:5601进入控制台。如下:

三、spring boot 应用准备

需开发或改造 spring boot 应用,使其能生成结构化日志并发送到 logstash。

项目基础

  • 需创建一个 spring boot 项目(推荐 2.x 或 3.x 版本),具备基础的日志输出功能(如使用logbacklog4j2)。
  • 依赖:无需额外引入 elk 相关依赖,但需确保日志框架支持 json 格式输出(如logstash-logback-encoder)。

日志配置

  • 目标:将 spring boot 日志以json 格式通过 tcp 发送到 logstash 的 5000 端口(与 logstash 输入配置对应)。

logback为例,在src/main/resources下创建logback-spring.xml

xml

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appender name="logstash" class="net.logstash.logback.appender.logstashtcpsocketappender">
    <destination>localhost:5000</destination>  <!-- logstash地址和端口 -->
    <encoder class="net.logstash.logback.encoder.logstashencoder">
      <!-- 自定义字段(可选) -->
      <includemdckeyname>requestid</includemdckeyname>
      <customfields>{"application":"my-springboot-app"}</customfields>
    </encoder>
  </appender>
  
  <root level="info">
    <appender-ref ref="logstash" />
    <appender-ref ref="console" />  <!-- 同时输出到控制台 -->
  </root>
</configuration>

依赖:在pom.xml中添加 logstash 编码器(若使用 logback):

xml

<dependency>
  <groupid>net.logstash.logback</groupid>
  <artifactid>logstash-logback-encoder</artifactid>
  <version>7.4.0</version>
</dependency>

四、技术知识储备

elk 组件基础

  • 了解 elasticsearch 的索引、文档、映射(mapping)概念,知道如何通过 api 查看索引数据。
  • 理解 logstash 的管道(pipeline)结构:input(输入)、filter(过滤)、output(输出),能简单配置过滤规则(如过滤无用日志字段)。
  • 熟悉 kibana 的基本操作:创建索引模式(index pattern)、使用 discover 查看日志、创建可视化图表(visualize)和仪表盘(dashboard)。

spring boot 日志框架

  • 了解 spring boot 默认日志框架(logback)的配置方式,能自定义日志格式、级别、输出目的地。
  • 理解 json 日志的优势(结构化数据便于 elasticsearch 索引和查询)。

网络与调试能力

  • 能使用telnetnc测试端口连通性(如检查 spring boot 到 logstash 的 5000 端口是否可通)。
  • 会查看组件日志排查问题:
    • elasticsearch 日志:logs/elasticsearch.log
    • logstash 日志:logs/logstash-plain.log
    • kibana 日志:logs/kibana.log

五、具体代码实现

在springboot的配置文件中编写访问地址:

spring.application.name=elkdemo
logname= #日志的名称catalina-2025.07.30
elasticsearchhost= #es的地址
elasticsearchport= #es的端口号9200
elasticsearchdefaulthost= #默认的es地址localhost:9200

编写es的config配置类

package com.example.demo.config;

import org.apache.http.httphost;
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.client.elc.elasticsearchtemplate;
import co.elastic.clients.elasticsearch.elasticsearchclient;
import org.springframework.data.elasticsearch.client.clientconfiguration;
import org.springframework.data.elasticsearch.client.elc.elasticsearchclients;

@configuration
public class elasticsearchconfig {

    @value("${elasticsearchhost}")
    private string elasticsearchhost;

    @value("${elasticsearchport}")
    private integer elasticsearchport;

    @value("${elasticsearchdefaulthost}")
    private string elasticsearchdefaulthost;
    @bean
    public resthighlevelclient resthighlevelclient() {
        // 配置elasticsearch地址
        return new resthighlevelclient(
                restclient.builder(
                        new httphost(elasticsearchhost, elasticsearchport, "http")
                )
        );
    }

    @bean
    public elasticsearchclient elasticsearchclient() {
        // 使用相同的连接配置创建elasticsearchclient
        clientconfiguration clientconfiguration = clientconfiguration.builder()
                .connectedto(elasticsearchdefaulthost)
                .build();

        return elasticsearchclients.createimperative(clientconfiguration);
    }

    @bean
    public elasticsearchtemplate elasticsearchtemplate() {
        return new elasticsearchtemplate(elasticsearchclient());
    }
}

编写两个基础的controller接口

package com.example.demo.controller;

import com.example.demo.model.document;
import com.example.demo.service.searchservice;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.web.bind.annotation.*;

import java.io.ioexception;
import java.util.list;
import java.util.map;

@restcontroller
@requestmapping("/api")
public class searchcontroller {

    @autowired
    private searchservice searchservice;

    // 搜索接口
    @getmapping("/search")
    public list<document> search(
            //query就是要搜索的关键字
            @requestparam string query,
            @requestparam(defaultvalue = "1") int page,
            @requestparam(defaultvalue = "10") int size
    ) throws ioexception {
        return searchservice.searchdocuments(query, page, size);
    }

    // 详情接口
    @getmapping("/document/{id}")
    public map<string, object> getdocumentdetail(
            @pathvariable string id,
            @requestparam string indexname){
        map<string, object> documentbyid = searchservice.getdocumentbyid(id, indexname);
        return documentbyid;
    }

}

编写对应的实现类

package com.example.demo.service;

import co.elastic.clients.elasticsearch.elasticsearchclient;
import co.elastic.clients.elasticsearch.core.getresponse;
import com.example.demo.model.document;
import co.elastic.clients.elasticsearch.core.getrequest;
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.index.query.multimatchquerybuilder;
import org.elasticsearch.index.query.querybuilders;
import org.elasticsearch.search.searchhit;
import org.elasticsearch.search.builder.searchsourcebuilder;
import org.elasticsearch.search.sort.sortbuilders;
import org.elasticsearch.search.sort.sortorder;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.beans.factory.annotation.value;
import org.springframework.stereotype.service;

import java.io.ioexception;
import java.util.arraylist;
import java.util.list;
import java.util.map;


@service
public class searchservice {

    @autowired
    private resthighlevelclient client;

    @value("${logname}")
    private string logname;

    @autowired
    private elasticsearchclient elasticsearchclient;
    public list<document> searchdocuments(string query, int page, int size) throws ioexception {
        // 使用存在的索引名(在配置文件编写)
        searchrequest searchrequest = new searchrequest(logname);
        searchsourcebuilder sourcebuilder = new searchsourcebuilder();

        // 只搜索映射中存在的字段
        multimatchquerybuilder multimatchquery = querybuilders.multimatchquery(
                query,
                "@version",
                "event.original",  // 嵌套字段
                "host.name",
                "log.file.path",
                "message",
                "tags"
        );

        sourcebuilder.query(multimatchquery);
        //分页开始位置
        sourcebuilder.from((page - 1) * size);
        //每一页的大小
        sourcebuilder.size(size);
        //按照时间降序排序
        sourcebuilder.sort(sortbuilders.fieldsort("@timestamp").order(sortorder.desc));
        //执行搜索
        searchrequest.source(sourcebuilder);
        searchresponse searchresponse = client.search(searchrequest, requestoptions.default);

        list<document> documents = new arraylist<>();
        //遍历es中命中的文档
        for (searchhit hit : searchresponse.gethits()) {
            //获取到的源数据进行类型转换为map对象
            map<string, object> source = hit.getsourceasmap();
            document document = new document();
            document.setid(hit.getid());

            //使用 @timestamp 作为标题(时间戳)
            document.settitle((string) source.get("@timestamp"));

            //处理嵌套字段 event
            map<string, object> event = (map<string, object>) source.get("event");
            if (event != null) {
                document.setcontent((string) event.get("original"));
            }
            document.settimestamp((string) source.get("@timestamp"));
            documents.add(document);
        }
        return documents;
    }

    public map<string,object> getdocumentbyid(string id, string indexname) {
        try {
            getrequest request = new getrequest.builder()
                    .index(indexname)
                    .id(id)
                    .build();
            //转换
            getresponse<map> response = elasticsearchclient.get(request, map.class);

            if (response.found()) {
                return response.source(); // 返回完整文档内容
            } else {
                throw new runtimeexception("文档不存在: " + id + " in index " + indexname);
            }
        } catch (ioexception e) {
            throw new runtimeexception("查询失败", e);
        }
    }
}

编写modle实体类

package com.example.demo.model;

import org.springframework.data.annotation.id;
import org.springframework.data.elasticsearch.annotations.field;
import org.springframework.data.elasticsearch.annotations.fieldtype;

public class document {

    @id
    private string id;
    
    @field(type = fieldtype.text)
    private string title;
    
    @field(type = fieldtype.text)
    private string content;
    
    @field(type = fieldtype.date)
    private string timestamp;

    public string getid() { return id; }
    public void setid(string id) { this.id = id; }
    public string gettitle() { return title; }
    public void settitle(string title) { this.title = title; }
    public string getcontent() { return content; }
    public void setcontent(string content) { this.content = content; }
    public string gettimestamp() { return timestamp; }
    public void settimestamp(string timestamp) { this.timestamp = timestamp; }
}

在resource目录下编写简单的前端代码index.html

<!doctype html>
<html lang="zh-cn">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>elk 日志搜索系统</title>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="external nofollow"  rel="external nofollow"  rel="stylesheet">
    <style>
        * {
            box-sizing: border-box;
        }
        body {
            font-family: 'segoe ui', tahoma, geneva, verdana, sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
        }
        .search-box {
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            margin-bottom: 20px;
        }
        .search-input {
            width: 80%;
            padding: 10px;
            font-size: 16px;
            border: 1px solid #ddd;
            border-radius: 4px;
            margin-right: 10px;
        }
        .search-button {
            padding: 10px 20px;
            background-color: #2196f3;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
        }
        .search-button:hover {
            background-color: #0b7dda;
        }
        .result-list {
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            padding: 20px;
        }
        .result-item {
            border-bottom: 1px solid #eee;
            padding: 15px 0;
            cursor: pointer;
        }
        .result-item:last-child {
            border-bottom: none;
        }
        .result-item:hover {
            background-color: #f9f9f9;
        }
        .result-title {
            font-size: 18px;
            color: #2196f3;
            margin-bottom: 5px;
        }
        .result-meta {
            font-size: 14px;
            color: #666;
            margin-bottom: 10px;
        }
        .result-content {
            font-size: 15px;
            color: #333;
            line-height: 1.5;
            max-height: 60px;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .pagination {
            margin-top: 20px;
            display: flex;
            justify-content: center;
        }
        .page-button {
            padding: 8px 16px;
            margin: 0 5px;
            border: 1px solid #ddd;
            border-radius: 4px;
            cursor: pointer;
        }
        .page-button.active {
            background-color: #2196f3;
            color: white;
            border-color: #2196f3;
        }
        .no-results {
            text-align: center;
            padding: 50px 0;
            color: #666;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="search-box">
        <h2>elk 日志搜索系统</h2>
        <div>
            <input type="text" id="query" class="search-input" placeholder="请输入搜索关键词...">
            <button class="search-button" onclick="search()">
                <i class="fa fa-search"></i> 搜索
            </button>
        </div>
        <div style="margin-top: 10px; font-size: 14px; color: #666;">
            支持关键词搜索,例如: <code>error</code>、<code>command line</code>、<code>2025-07-30</code>
        </div>
    </div>

    <div class="result-list" id="results">
        <div class="no-results">请输入关键词进行搜索</div>
    </div>

    <div class="pagination" id="pagination">
        <!-- 分页按钮将动态生成 -->
    </div>
</div>

<script>
    // 当前页码和每页大小
    let currentpage = 1;
    const pagesize = 10;
    let totalpages = 1;
    let currentquery = '';

    // 搜索函数
    async function search(page = 1) {
        const queryinput = document.getelementbyid('query');
        currentquery = queryinput.value.trim();
        currentpage = page;

        if (!currentquery) {
            alert('请输入搜索关键词');
            return;
        }

        try {
            // 显示加载状态
            document.getelementbyid('results').innerhtml = '<div class="no-results"><i class="fa fa-spinner fa-spin"></i> 正在搜索...</div>';

            const response = await axios.get('/api/search', {
                params: {
                    query: currentquery,
                    page: currentpage,
                    size: pagesize
                }
            });

            renderresults(response.data);
            renderpagination();
        } catch (error) {
            console.error('搜索失败:', error);
            document.getelementbyid('results').innerhtml = '<div class="no-results"><i class="fa fa-exclamation-triangle"></i> 搜索失败,请重试</div>';
        }
    }

    // 渲染搜索结果
    function renderresults(documents) {
        const resultsdiv = document.getelementbyid('results');

        if (!documents || documents.length === 0) {
            resultsdiv.innerhtml = '<div class="no-results"><i class="fa fa-search"></i> 没有找到匹配的结果</div>';
            return;
        }

        const resultitems = documents.map(doc => `
            <div class="result-item" onclick="opendetail('${doc.id}', 'catalina-2025.07.30')">
                <div class="result-title">${doc.title || '无标题'}</div>
                <div class="result-meta">
                    <span><i class="fa fa-clock-o"></i> ${doc.timestamp || '未知时间'}</span>
                    <span style="margin-left: 15px;"><i class="fa fa-file-text-o"></i> ${doc.id}</span>
                </div>
                <div class="result-content">${doc.content ? doc.content.substr(0, 200) + '...' : '无内容'}</div>
            </div>
        `).join('');

        resultsdiv.innerhtml = resultitems;
    }

    // 渲染分页控件
    function renderpagination() {
        const paginationdiv = document.getelementbyid('pagination');

        // 假设后端返回总页数
        // 实际应用中应从后端获取总记录数,计算总页数
        totalpages = math.ceil(50 / pagesize); // 示例:假设总共有50条记录

        let paginationhtml = '';

        // 上一页按钮
        if (currentpage > 1) {
            paginationhtml += `<button class="page-button" onclick="search(${currentpage - 1})">上一页</button>`;
        }

        // 页码按钮
        const maxvisiblepages = 5;
        let startpage = math.max(1, currentpage - math.floor(maxvisiblepages / 2));
        let endpage = math.min(startpage + maxvisiblepages - 1, totalpages);

        if (endpage - startpage + 1 < maxvisiblepages) {
            startpage = math.max(1, endpage - maxvisiblepages + 1);
        }

        for (let i = startpage; i <= endpage; i++) {
            paginationhtml += `<button class="page-button ${i === currentpage ? 'active' : ''}" onclick="search(${i})">${i}</button>`;
        }

        // 下一页按钮
        if (currentpage < totalpages) {
            paginationhtml += `<button class="page-button" onclick="search(${currentpage + 1})">下一页</button>`;
        }

        paginationdiv.innerhtml = paginationhtml;
    }

    // 打开详情页
    function opendetail(id, indexname) {
        window.location.href = `detail.html?id=${id}&index=${indexname}`;
    }
</script>
</body>
</html>

在resource目录下编写简单的前端代码detail.html

<!doctype html>
<html lang="zh-cn">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>日志详情 | elk 搜索系统</title>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="external nofollow"  rel="external nofollow"  rel="stylesheet">
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            font-family: 'consolas', 'monaco', monospace;
            background-color: #f5f5f5;
            padding: 20px;
            line-height: 1.5;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            padding: 20px;
        }

        .header {
            margin-bottom: 20px;
        }

        .back-button {
            padding: 8px 16px;
            background-color: #2196f3;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            display: inline-flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 15px;
        }

        .back-button:hover {
            background-color: #0b7dda;
        }

        .meta-info {
            margin-bottom: 20px;
            padding: 10px;
            background-color: #f9f9f9;
            border-radius: 4px;
            font-size: 14px;
        }

        .meta-item {
            margin-right: 20px;
            display: inline-block;
        }

        .json-container {
            background-color: #f9f9f9;
            border-radius: 4px;
            padding: 20px;
            overflow-x: auto;
            white-space: pre-wrap;
        }

        .json-key {
            color: #0033a0;
            font-weight: bold;
        }

        .json-string {
            color: #008000;
        }

        .json-number {
            color: #800000;
        }

        .json-boolean {
            color: #0000ff;
        }

        .json-null {
            color: #808080;
        }

        .error {
            color: #dc3545;
            padding: 20px;
            text-align: center;
            background-color: #f8d7da;
            border-radius: 4px;
        }

        .loading {
            text-align: center;
            padding: 50px 0;
            color: #666;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="header">
        <button class="back-button" onclick="goback()">
            <i class="fa fa-arrow-left"></i> 返回搜索结果
        </button>

        <div class="meta-info">
            <div class="meta-item">
                <i class="fa fa-database"></i> <span id="index-name">加载中...</span>
            </div>
            <div class="meta-item">
                <i class="fa fa-file-text-o"></i> <span id="document-id">加载中...</span>
            </div>
            <div class="meta-item">
                <i class="fa fa-clock-o"></i> <span id="load-time">加载中...</span>
            </div>
        </div>
    </div>

    <div id="loading" class="loading">
        <i class="fa fa-spinner fa-spin"></i> 正在加载数据...
    </div>

    <div id="error" class="error" style="display: none;"></div>

    <div id="json-container" class="json-container" style="display: none;"></div>
</div>

<script>
    // 原生json高亮格式化函数
    function syntaxhighlight(json) {
        if (typeof json !== 'string') {
            json = json.stringify(json, undefined, 2);
        }

        // 正则匹配不同json元素并添加样式类
        json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
        return json.replace(/("(\\u[a-za-z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[ee][+-]?\d+)?)/g, function (match) {
            let cls = 'json-number';
            if (/^"/.test(match)) {
                if (/:$/.test(match)) {
                    cls = 'json-key';
                } else {
                    cls = 'json-string';
                }
            } else if (/true|false/.test(match)) {
                cls = 'json-boolean';
            } else if (/null/.test(match)) {
                cls = 'json-null';
            }
            return '<span class="' + cls + '">' + match + '</span>';
        });
    }

    // 页面加载完成后执行
    document.addeventlistener('domcontentloaded', function() {
        // 获取url参数
        const urlparams = new urlsearchparams(window.location.search);
        const docid = urlparams.get('id');
        const indexname = urlparams.get('index');

        // 验证参数
        if (!docid || !indexname) {
            document.getelementbyid('loading').style.display = 'none';
            document.getelementbyid('error').textcontent = '错误:缺少文档id或索引名参数';
            document.getelementbyid('error').style.display = 'block';
            return;
        }

        // 更新元信息
        document.getelementbyid('document-id').textcontent = `文档id: ${docid}`;
        document.getelementbyid('index-name').textcontent = `索引: ${indexname}`;

        // 记录开始时间
        const starttime = date.now();

        // 请求数据
        axios.get(`/api/document/${docid}`, {
            params: {
                indexname: indexname
            },
            timeout: 15000
        })
        .then(response => {
            // 计算加载时间
            const loadtime = date.now() - starttime;
            document.getelementbyid('load-time').textcontent = `加载时间: ${loadtime}ms`;

            // 隐藏加载状态,显示内容
            document.getelementbyid('loading').style.display = 'none';
            document.getelementbyid('json-container').style.display = 'block';

            // 格式化并显示json
            document.getelementbyid('json-container').innerhtml = syntaxhighlight(response.data);
        })
        .catch(error => {
            // 处理错误
            document.getelementbyid('loading').style.display = 'none';
            let errormsg = '加载失败: ';

            if (error.response) {
                errormsg += `服务器返回 ${error.response.status} 错误`;
            } else if (error.request) {
                errormsg += '未收到服务器响应,请检查网络';
            } else {
                errormsg += error.message;
            }

            document.getelementbyid('error').textcontent = errormsg;
            document.getelementbyid('error').style.display = 'block';
        });
    });

    // 返回上一页
    function goback() {
        window.history.back();
    }
</script>
</body>
</html>

六、效果展示

访问localhost:8080即可展示界面,如下:

当我们搜索某个关键字时,是支持全文索引的:

当点击某个具体的文档时,可以查看详情:

七、其他注意事项

版本兼容性

  • elk 组件版本需保持一致(如均使用 7.17.x 或 8.x),避免版本不兼容导致通信失败。
  • spring boot 版本与日志组件版本兼容(如 logstash-logback-encoder 需与 logback 版本匹配)。

资源配置

  • elasticsearch 对内存要求较高,建议开发环境分配至少 2gb 内存(修改config/jvm.options中的-xms2g -xmx2g)。
  • logstash 和 kibana 可根据需求调整内存配置。

安全配置(可选)

  • 生产环境需开启 elk 的安全功能(如 elasticsearch 的用户名密码认证、ssl 加密),spring boot 和 logstash 需配置对应认证信息。

以上就是基于springboot实现简单的elk日志搜索系统的详细内容,更多关于springboot elk日志搜索的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com