欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于SpringBoot實(shí)現(xiàn)簡(jiǎn)單的ELK日志搜索系統(tǒng)

 更新時(shí)間:2025年08月13日 10:11:06   作者:dlwlrma-IU  
要使用 Spring Boot 實(shí)現(xiàn)簡(jiǎn)單的 ELK(Elasticsearch、Logstash、Kibana)系統(tǒng),需要滿足一系列前提條件,涵蓋環(huán)境準(zhǔn)備、技術(shù)基礎(chǔ)、組件認(rèn)知等多個(gè)方面,以下是詳細(xì)的前提說(shuō)明,需要的朋友可以參考下

一、基礎(chǔ)環(huán)境準(zhǔn)備

實(shí)現(xiàn) ELK 系統(tǒng)的首要前提是搭建好運(yùn)行所需的基礎(chǔ)環(huán)境,確保各組件能正常啟動(dòng)和通信。

Java 環(huán)境

  • Elasticsearch、Logstash、Spring Boot 均基于 Java 開(kāi)發(fā),需安裝JDK 8 及以上版本(推薦 JDK 11,兼容性更好)。
  • 配置JAVA_HOME環(huán)境變量,確保命令行可識(shí)別javajavac命令。

操作系統(tǒng)

  • 支持 Windows、Linux、macOS 等主流系統(tǒng),但生產(chǎn)環(huán)境推薦 Linux(如 CentOS、Ubuntu),穩(wěn)定性和性能更優(yōu)。
  • 注意:Elasticsearch 在 Linux 下需配置用戶權(quán)限(避免 root 用戶直接啟動(dòng)),并調(diào)整虛擬內(nèi)存參數(shù)(如vm.max_map_count=262144)。

網(wǎng)絡(luò)環(huán)境

  • 確保 ELK 各組件(Elasticsearch、Logstash、Kibana)及 Spring Boot 應(yīng)用在同一網(wǎng)絡(luò)環(huán)境中,端口可正常通信:
    • Elasticsearch 默認(rèn)端口:9200(HTTP)、9300(節(jié)點(diǎn)間通信)
    • Logstash 默認(rèn)端口:5044(接收 Beats 數(shù)據(jù))、9600(監(jiān)控)
    • Kibana 默認(rèn)端口:5601
  • 關(guān)閉防火墻或開(kāi)放上述端口(開(kāi)發(fā)環(huán)境可簡(jiǎn)化,生產(chǎn)環(huán)境需嚴(yán)格配置)。

二、ELK 組件安裝與配置

需單獨(dú)安裝 Elasticsearch、Logstash、Kibana,并完成基礎(chǔ)配置(以單機(jī)版為例,集群版需額外配置)。

Elasticsearch

  • 作用:存儲(chǔ)和索引日志數(shù)據(jù)。
  • 安裝:從官網(wǎng)下載對(duì)應(yīng)版本,解壓后即可運(yùn)行(bin/elasticsearch)。

基礎(chǔ)配置(config/elasticsearch.yml):

yaml

cluster.name: my-elk-cluster  # 集群名稱(單機(jī)可自定義)
node.name: node-1             # 節(jié)點(diǎn)名稱
network.host: 0.0.0.0         # 允許所有IP訪問(wèn)(開(kāi)發(fā)環(huán)境)
http.port: 9200               # HTTP端口

驗(yàn)證:訪問(wèn)http://localhost:9200,返回節(jié)點(diǎn)信息即啟動(dòng)成功。如下:

Logstash

  • 作用:收集、過(guò)濾、轉(zhuǎn)換日志數(shù)據(jù),發(fā)送到 Elasticsearch。

安裝:從官網(wǎng)下載,解壓后配置管道(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 }  # 同時(shí)輸出到控制臺(tái)(調(diào)試用)
}

啟動(dòng):bin/logstash -f config/logstash-simple.conf。如下:

Kibana

  • 作用:可視化展示 Elasticsearch 中的日志數(shù)據(jù)。

安裝:從官網(wǎng)下載,解壓后配置(config/kibana.yml):

yaml

server.host: "0.0.0.0"  # 允許所有IP訪問(wèn)
elasticsearch.hosts: ["http://localhost:9200"]  # 連接Elasticsearch

啟動(dòng):bin/kibana,訪問(wèn)http://localhost:5601進(jìn)入控制臺(tái)。如下:

三、Spring Boot 應(yīng)用準(zhǔn)備

需開(kāi)發(fā)或改造 Spring Boot 應(yīng)用,使其能生成結(jié)構(gòu)化日志并發(fā)送到 Logstash。

項(xiàng)目基礎(chǔ)

  • 需創(chuàng)建一個(gè) Spring Boot 項(xiàng)目(推薦 2.x 或 3.x 版本),具備基礎(chǔ)的日志輸出功能(如使用logbacklog4j2)。
  • 依賴:無(wú)需額外引入 ELK 相關(guān)依賴,但需確保日志框架支持 JSON 格式輸出(如logstash-logback-encoder)。

日志配置

  • 目標(biāo):將 Spring Boot 日志以JSON 格式通過(guò) TCP 發(fā)送到 Logstash 的 5000 端口(與 Logstash 輸入配置對(duì)應(yīng))。

logback為例,在src/main/resources下創(chuàng)建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" />  <!-- 同時(shí)輸出到控制臺(tái) -->
  </root>
</configuration>

依賴:在pom.xml中添加 Logstash 編碼器(若使用 logback):

xml

<dependency>
  <groupId>net.logstash.logback</groupId>
  <artifactId>logstash-logback-encoder</artifactId>
  <version>7.4.0</version>
</dependency>

四、技術(shù)知識(shí)儲(chǔ)備

ELK 組件基礎(chǔ)

  • 了解 Elasticsearch 的索引、文檔、映射(Mapping)概念,知道如何通過(guò) API 查看索引數(shù)據(jù)。
  • 理解 Logstash 的管道(Pipeline)結(jié)構(gòu):Input(輸入)、Filter(過(guò)濾)、Output(輸出),能簡(jiǎn)單配置過(guò)濾規(guī)則(如過(guò)濾無(wú)用日志字段)。
  • 熟悉 Kibana 的基本操作:創(chuàng)建索引模式(Index Pattern)、使用 Discover 查看日志、創(chuàng)建可視化圖表(Visualize)和儀表盤(Dashboard)。

Spring Boot 日志框架

  • 了解 Spring Boot 默認(rèn)日志框架(logback)的配置方式,能自定義日志格式、級(jí)別、輸出目的地。
  • 理解 JSON 日志的優(yōu)勢(shì)(結(jié)構(gòu)化數(shù)據(jù)便于 Elasticsearch 索引和查詢)。

網(wǎng)絡(luò)與調(diào)試能力

  • 能使用telnetnc測(cè)試端口連通性(如檢查 Spring Boot 到 Logstash 的 5000 端口是否可通)。
  • 會(huì)查看組件日志排查問(wèn)題:
    • Elasticsearch 日志:logs/elasticsearch.log
    • Logstash 日志:logs/logstash-plain.log
    • Kibana 日志:logs/kibana.log

五、具體代碼實(shí)現(xiàn)

在springboot的配置文件中編寫訪問(wèn)地址:

spring.application.name=elkdemo
logName= #日志的名稱catalina-2025.07.30
elasticsearchHost= #es的地址
elasticsearchPort= #es的端口號(hào)9200
elasticsearchDefaultHost= #默認(rèn)的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() {
        // 使用相同的連接配置創(chuàng)建ElasticsearchClient
        ClientConfiguration clientConfiguration = ClientConfiguration.builder()
                .connectedTo(elasticsearchDefaultHost)
                .build();

        return ElasticsearchClients.createImperative(clientConfiguration);
    }

    @Bean
    public ElasticsearchTemplate elasticsearchTemplate() {
        return new ElasticsearchTemplate(elasticsearchClient());
    }
}

編寫兩個(gè)基礎(chǔ)的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就是要搜索的關(guān)鍵字
            @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;
    }

}

編寫對(duì)應(yīng)的實(shí)現(xiàn)類

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);
        //分頁(yè)開(kāi)始位置
        sourceBuilder.from((page - 1) * size);
        //每一頁(yè)的大小
        sourceBuilder.size(size);
        //按照時(shí)間降序排序
        sourceBuilder.sort(SortBuilders.fieldSort("@timestamp").order(SortOrder.DESC));
        //執(zhí)行搜索
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        List<Document> documents = new ArrayList<>();
        //遍歷es中命中的文檔
        for (SearchHit hit : searchResponse.getHits()) {
            //獲取到的源數(shù)據(jù)進(jìn)行類型轉(zhuǎn)換為map對(duì)象
            Map<String, Object> source = hit.getSourceAsMap();
            Document document = new Document();
            document.setId(hit.getId());

            //使用 @timestamp 作為標(biāo)題(時(shí)間戳)
            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();
            //轉(zhuǎn)換
            GetResponse<Map> response = elasticsearchClient.get(request, Map.class);

            if (response.found()) {
                return response.source(); // 返回完整文檔內(nèi)容
            } else {
                throw new RuntimeException("文檔不存在: " + id + " in index " + indexName);
            }
        } catch (IOException e) {
            throw new RuntimeException("查詢失敗", e);
        }
    }
}

編寫Modle實(shí)體類

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目錄下編寫簡(jiǎn)單的前端代碼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 日志搜索系統(tǒng)</title>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <link  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 日志搜索系統(tǒng)</h2>
        <div>
            <input type="text" id="query" class="search-input" placeholder="請(qǐng)輸入搜索關(guān)鍵詞...">
            <button class="search-button" onclick="search()">
                <i class="fa fa-search"></i> 搜索
            </button>
        </div>
        <div style="margin-top: 10px; font-size: 14px; color: #666;">
            支持關(guān)鍵詞搜索,例如: <code>ERROR</code>、<code>command line</code>、<code>2025-07-30</code>
        </div>
    </div>

    <div class="result-list" id="results">
        <div class="no-results">請(qǐng)輸入關(guān)鍵詞進(jìn)行搜索</div>
    </div>

    <div class="pagination" id="pagination">
        <!-- 分頁(yè)按鈕將動(dòng)態(tài)生成 -->
    </div>
</div>

<script>
    // 當(dāng)前頁(yè)碼和每頁(yè)大小
    let currentPage = 1;
    const pageSize = 10;
    let totalPages = 1;
    let currentQuery = '';

    // 搜索函數(shù)
    async function search(page = 1) {
        const queryInput = document.getElementById('query');
        currentQuery = queryInput.value.trim();
        currentPage = page;

        if (!currentQuery) {
            alert('請(qǐng)輸入搜索關(guān)鍵詞');
            return;
        }

        try {
            // 顯示加載狀態(tài)
            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> 搜索失敗,請(qǐng)重試</div>';
        }
    }

    // 渲染搜索結(jié)果
    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> 沒(méi)有找到匹配的結(jié)果</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 || '無(wú)標(biāo)題'}</div>
                <div class="result-meta">
                    <span><i class="fa fa-clock-o"></i> ${doc.timestamp || '未知時(shí)間'}</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) + '...' : '無(wú)內(nèi)容'}</div>
            </div>
        `).join('');

        resultsDiv.innerHTML = resultItems;
    }

    // 渲染分頁(yè)控件
    function renderPagination() {
        const paginationDiv = document.getElementById('pagination');

        // 假設(shè)后端返回總頁(yè)數(shù)
        // 實(shí)際應(yīng)用中應(yīng)從后端獲取總記錄數(shù),計(jì)算總頁(yè)數(shù)
        totalPages = Math.ceil(50 / pageSize); // 示例:假設(shè)總共有50條記錄

        let paginationHtml = '';

        // 上一頁(yè)按鈕
        if (currentPage > 1) {
            paginationHtml += `<button class="page-button" onclick="search(${currentPage - 1})">上一頁(yè)</button>`;
        }

        // 頁(yè)碼按鈕
        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>`;
        }

        // 下一頁(yè)按鈕
        if (currentPage < totalPages) {
            paginationHtml += `<button class="page-button" onclick="search(${currentPage + 1})">下一頁(yè)</button>`;
        }

        paginationDiv.innerHTML = paginationHtml;
    }

    // 打開(kāi)詳情頁(yè)
    function openDetail(id, indexName) {
        window.location.href = `detail.html?id=${id}&index=${indexName}`;
    }
</script>
</body>
</html>

在resource目錄下編寫簡(jiǎn)單的前端代碼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 搜索系統(tǒng)</title>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <link  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> 返回搜索結(jié)果
        </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> 正在加載數(shù)據(jù)...
    </div>

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

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

<script>
    // 原生JSON高亮格式化函數(shù)
    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>';
        });
    }

    // 頁(yè)面加載完成后執(zhí)行
    document.addEventListener('DOMContentLoaded', function() {
        // 獲取URL參數(shù)
        const urlParams = new URLSearchParams(window.location.search);
        const docId = urlParams.get('id');
        const indexName = urlParams.get('index');

        // 驗(yàn)證參數(shù)
        if (!docId || !indexName) {
            document.getElementById('loading').style.display = 'none';
            document.getElementById('error').textContent = '錯(cuò)誤:缺少文檔ID或索引名參數(shù)';
            document.getElementById('error').style.display = 'block';
            return;
        }

        // 更新元信息
        document.getElementById('document-id').textContent = `文檔ID: ${docId}`;
        document.getElementById('index-name').textContent = `索引: ${indexName}`;

        // 記錄開(kāi)始時(shí)間
        const startTime = Date.now();

        // 請(qǐng)求數(shù)據(jù)
        axios.get(`/api/document/${docId}`, {
            params: {
                indexName: indexName
            },
            timeout: 15000
        })
        .then(response => {
            // 計(jì)算加載時(shí)間
            const loadTime = Date.now() - startTime;
            document.getElementById('load-time').textContent = `加載時(shí)間: ${loadTime}ms`;

            // 隱藏加載狀態(tài),顯示內(nèi)容
            document.getElementById('loading').style.display = 'none';
            document.getElementById('json-container').style.display = 'block';

            // 格式化并顯示JSON
            document.getElementById('json-container').innerHTML = syntaxHighlight(response.data);
        })
        .catch(error => {
            // 處理錯(cuò)誤
            document.getElementById('loading').style.display = 'none';
            let errorMsg = '加載失敗: ';

            if (error.response) {
                errorMsg += `服務(wù)器返回 ${error.response.status} 錯(cuò)誤`;
            } else if (error.request) {
                errorMsg += '未收到服務(wù)器響應(yīng),請(qǐng)檢查網(wǎng)絡(luò)';
            } else {
                errorMsg += error.message;
            }

            document.getElementById('error').textContent = errorMsg;
            document.getElementById('error').style.display = 'block';
        });
    });

    // 返回上一頁(yè)
    function goBack() {
        window.history.back();
    }
</script>
</body>
</html>

六、效果展示

訪問(wèn)localhost:8080即可展示界面,如下:

當(dāng)我們搜索某個(gè)關(guān)鍵字時(shí),是支持全文索引的:

當(dāng)點(diǎn)擊某個(gè)具體的文檔時(shí),可以查看詳情:

七、其他注意事項(xiàng)

版本兼容性

  • ELK 組件版本需保持一致(如均使用 7.17.x 或 8.x),避免版本不兼容導(dǎo)致通信失敗。
  • Spring Boot 版本與日志組件版本兼容(如 logstash-logback-encoder 需與 logback 版本匹配)。

資源配置

  • Elasticsearch 對(duì)內(nèi)存要求較高,建議開(kāi)發(fā)環(huán)境分配至少 2GB 內(nèi)存(修改config/jvm.options中的-Xms2g -Xmx2g)。
  • Logstash 和 Kibana 可根據(jù)需求調(diào)整內(nèi)存配置。

安全配置(可選)

  • 生產(chǎn)環(huán)境需開(kāi)啟 ELK 的安全功能(如 Elasticsearch 的用戶名密碼認(rèn)證、SSL 加密),Spring Boot 和 Logstash 需配置對(duì)應(yīng)認(rèn)證信息。

以上就是基于SpringBoot實(shí)現(xiàn)簡(jiǎn)單的ELK日志搜索系統(tǒng)的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot ELK日志搜索的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • JAVA中的Configuration類詳解

    JAVA中的Configuration類詳解

    這篇文章主要介紹了JAVA中的Configuration類詳解,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • SpringBoot開(kāi)發(fā)項(xiàng)目,引入JPA找不到findOne方法的解決

    SpringBoot開(kāi)發(fā)項(xiàng)目,引入JPA找不到findOne方法的解決

    這篇文章主要介紹了SpringBoot開(kāi)發(fā)項(xiàng)目,引入JPA找不到findOne方法的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • 如何將Tomcat容器替換為Jetty容器

    如何將Tomcat容器替換為Jetty容器

    這篇文章主要介紹了如何將Tomcat容器替換為Jetty容器問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-03-03
  • Spring學(xué)習(xí)筆記之RestTemplate使用小結(jié)

    Spring學(xué)習(xí)筆記之RestTemplate使用小結(jié)

    這篇文章主要給大家介紹了關(guān)于Spring學(xué)習(xí)筆記之RestTemplate使用的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-08-08
  • Java中字節(jié)流和字符流的理解(超精簡(jiǎn)!)

    Java中字節(jié)流和字符流的理解(超精簡(jiǎn)!)

    Java通過(guò)稱為流的抽象來(lái)執(zhí)行I/O操作,下面這篇文章主要給大家介紹了關(guān)于Java中字節(jié)流和字符流理解,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07
  • 重寫Java中的equals方法介紹

    重寫Java中的equals方法介紹

    這篇文章主要介紹了重寫Java中的equals方法介紹,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-11-11
  • idea tomcat亂碼問(wèn)題的解決及相關(guān)設(shè)置的步驟

    idea tomcat亂碼問(wèn)題的解決及相關(guān)設(shè)置的步驟

    這篇文章主要介紹了idea tomcat亂碼問(wèn)題的解決及相關(guān)設(shè)置的步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • java實(shí)現(xiàn)選擇排序算法

    java實(shí)現(xiàn)選擇排序算法

    本篇文章介紹直接選擇排序算法的JAVA實(shí)現(xiàn)。直接選擇排序算法的基本思想是:n個(gè)記錄的文件的直接選擇排序可經(jīng)過(guò)n-1趟直接選擇排序得到有序結(jié)果
    2015-04-04
  • java設(shè)計(jì)模式之橋接模式(Bridge)

    java設(shè)計(jì)模式之橋接模式(Bridge)

    這篇文章主要為大家詳細(xì)介紹了java設(shè)計(jì)模式之橋接模式Bridge,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-01-01
  • 基于SpringBoot和Vue實(shí)現(xiàn)分片上傳系統(tǒng)

    基于SpringBoot和Vue實(shí)現(xiàn)分片上傳系統(tǒng)

    最近想做一個(gè)關(guān)于文件上傳的個(gè)人小網(wǎng)盤,一開(kāi)始嘗試使用了OSS的方案,但是該方案對(duì)于大文件來(lái)說(shuō)并不友好,所以開(kāi)始嘗試分片上傳方案的探索,接下來(lái)小編給大家詳細(xì)的介紹一下如何基于SpringBoot和Vue實(shí)現(xiàn)分片上傳系統(tǒng),需要的朋友可以參考下
    2023-12-12

最新評(píng)論