Elasticsearch查詢Java實現映射文檔

項目概述

本項目基於Elasticsearch 7.17.15版本實現了一套完整的日誌查詢和分析系統,採用了Spring Boot 2.7.18框架,提供了豐富的查詢功能包括複合查詢、高亮顯示、異常分析等。

技術棧版本

  • Elasticsearch: 7.17.15
  • Spring Boot: 2.7.18
  • Spring Cloud: 3.1.8
  • Java: JDK 1.8

核心功能詳細實現

1. 複合查詢構建(BoolQueryBuilder)

項目實現了複雜的複合查詢邏輯,支持must、should、must_not子句組合:

Java實現
/**
 * 構建複合查詢條件
 * @param request 搜索請求參數
 * @return BoolQueryBuilder 複合查詢構建器
 */
private BoolQueryBuilder buildQuery(LogSearchRequest request) {
    BoolQueryBuilder query = QueryBuilders.boolQuery();
  
    // 添加時間範圍查詢
    if (request.getStartTime() != null && request.getEndTime() != null) {
        RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("@timestamp")
            .gte(request.getStartTime().atZone(ZoneOffset.ofHours(0))
                .withZoneSameInstant(ZoneOffset.UTC).format(DATE_FORMAT))
            .lte(request.getEndTime().atZone(ZoneOffset.ofHours(0))
                .withZoneSameInstant(ZoneOffset.UTC).format(DATE_FORMAT));
        query.must(rangeQuery);
    }
  
    // 添加關鍵詞搜索(MultiMatchQuery)
    if (request.getKeyword() != null && !request.getKeyword().trim().isEmpty()) {
        MultiMatchQueryBuilder multiMatchQuery = QueryBuilders.multiMatchQuery(request.getKeyword().trim())
            .field("message", 5.0f)      // message字段權重最高
            .field("stack_trace", 4.0f)  // 堆棧跟蹤權重較高
            .field("class_name", 3.0f)   // 類名權重中等
            .field("method_name", 3.0f)  // 方法名權重中等
            .field("service_name", 2.0f) // 服務名權重較低
            .type(MultiMatchQueryBuilder.Type.BEST_FIELDS)
            .fuzziness("AUTO")           // 自動模糊匹配
            .prefixLength(2)             // 前綴長度
            .maxExpansions(50);          // 最大擴展數
        query.must(multiMatchQuery);
    }
  
    // 添加精確匹配條件
    if (request.getLevel() != null) {
        query.must(QueryBuilders.termQuery("level", request.getLevel()));
    }
  
    if (request.getService() != null) {
        query.must(QueryBuilders.termQuery("service_name", request.getService()));
    }
  
    // 添加服務列表過濾
    if (request.getServices() != null && !request.getServices().isEmpty()) {
        query.must(QueryBuilders.termsQuery("service_name", request.getServices()));
    }
  
    return query;
}
對應DSL語法
{
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "@timestamp": {
              "gte": "2023-01-01T00:00:00Z",
              "lte": "2023-01-31T23:59:59Z"
            }
          }
        },
        {
          "multi_match": {
            "query": "error keyword",
            "fields": ["message^5", "stack_trace^4", "class_name^3", "method_name^3", "service_name^2"],
            "type": "best_fields",
            "fuzziness": "AUTO",
            "prefix_length": 2,
            "max_expansions": 50
          }
        },
        {
          "term": {
            "level": "ERROR"
          }
        },
        {
          "term": {
            "service_name": "user-service"
          }
        }
      ],
      "should": [],
      "must_not": []
    }
  }
}
DSL語法説明

基本結構:

  • bool 查詢容器包含 mustshouldmust_notfilter 子句
  • must 子句表示必須滿足的條件(AND邏輯)
  • should 子句表示可選滿足的條件(OR邏輯)
  • must_not 子句表示必須排除的條件(NOT邏輯)
  • filter 子句類似 must,但不參與評分計算

查詢類型映射:

  • QueryBuilders.boolQuery()"bool": { }
  • QueryBuilders.rangeQuery("@timestamp")"range": { "@timestamp": { } }
  • QueryBuilders.multiMatchQuery()"multi_match": { }
  • QueryBuilders.termQuery()"term": { }
  • QueryBuilders.termsQuery()"terms": { }

2. 高亮顯示功能實現

項目實現了完整的Elasticsearch高亮功能,支持多字段高亮顯示:

Java實現
/**
 * 配置高亮顯示功能
 * @param searchSourceBuilder 搜索源構建器
 */
private void configureHighlighting(SearchSourceBuilder searchSourceBuilder) {
    HighlightBuilder highlightBuilder = new HighlightBuilder();
  
    // 為message字段添加高亮配置
    HighlightBuilder.Field highlightMessage = new HighlightBuilder.Field("message");
    highlightMessage.preTags("<em class=\"highlight\">");
    highlightMessage.postTags("</em>");
    highlightMessage.fragmentSize(600);    // 片段大小
    highlightMessage.numOfFragments(3);    // 片段數量
    highlightBuilder.field(highlightMessage);

    // 為stack_trace字段添加高亮配置
    HighlightBuilder.Field highlightStackTrace = new HighlightBuilder.Field("stack_trace");
    highlightStackTrace.preTags("<em class=\"highlight\">");
    highlightStackTrace.postTags("</em>");
    highlightStackTrace.fragmentSize(600); // 片段大小
    highlightStackTrace.numOfFragments(3); // 片段數量
    highlightBuilder.field(highlightStackTrace);
  
    searchSourceBuilder.highlighter(highlightBuilder);
}
對應DSL語法
{
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "error",
            "fields": ["message^5", "stack_trace^4", "class_name^3", "method_name^3", "service_name^2"]
          }
        }
      ]
    }
  },
  "highlight": {
    "fields": {
      "message": {
        "pre_tags": ["<em class=\"highlight\">"],
        "post_tags": ["</em>"],
        "fragment_size": 600,
        "number_of_fragments": 3
      },
      "stack_trace": {
        "pre_tags": ["<em class=\"highlight\">"],
        "post_tags": ["</em>"],
        "fragment_size": 600,
        "number_of_fragments": 3
      }
    }
  }
}
高亮DSL語法説明

高亮配置項:

  • fields - 需要高亮顯示的字段列表
  • pre_tags - 高亮內容的前綴標籤
  • post_tags - 高亮內容的後綴標籤
  • fragment_size - 高亮片段的最大字符數
  • number_of_fragments - 返回的高亮片段數量

Java API映射:

  • HighlightBuilder"highlight": { }
  • HighlightBuilder.Field("message")"message": { }
  • .preTags()"pre_tags": [ ]
  • .postTags()"post_tags": [ ]
  • .fragmentSize()"fragment_size": 600
  • .numOfFragments()"number_of_fragments": 3

3. 聚合查詢實現

系統實現了多種聚合查詢功能,用於數據統計和分析:

Java實現
/**
 * 構建聚合查詢
 * @return SearchSourceBuilder 包含聚合的搜索源
 */
private SearchSourceBuilder buildAggregations(SearchSourceBuilder searchSourceBuilder) {
    // 按服務名稱聚合
    searchSourceBuilder.aggregation(AggregationBuilders
        .terms("services")
        .field("service_name")
        .size(100));
  
    // 按日誌級別聚合
    searchSourceBuilder.aggregation(AggregationBuilders
        .terms("levels")
        .field("level")
        .size(10));
  
    // 按時間範圍聚合(日聚合)
    searchSourceBuilder.aggregation(AggregationBuilders
        .date_histogram("time_buckets")
        .field("@timestamp")
        .calendarInterval(DateHistogramInterval.DAY)
        .format("yyyy-MM-dd"));
  
    return searchSourceBuilder;
}
對應DSL語法
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "services": {
      "terms": {
        "field": "service_name",
        "size": 100
      }
    },
    "levels": {
      "terms": {
        "field": "level",
        "size": 10
      }
    },
    "time_buckets": {
      "date_histogram": {
        "field": "@timestamp",
        "calendar_interval": "day",
        "format": "yyyy-MM-dd"
      }
    }
  }
}
聚合DSL語法説明

聚合類型:

  • terms 聚合 → 分組統計,類似於SQL的GROUP BY
  • date_histogram 聚合 → 按時間間隔分組
  • count 統計 → 自動計算每個桶的文檔數量

Java API映射:

  • AggregationBuilders.terms("services")"services": { "terms": { } }
  • AggregationBuilders.date_histogram("time_buckets")"time_buckets": { "date_histogram": { } }
  • .field("service_name")"field": "service_name"
  • .size(100)"size": 100
  • .calendarInterval(DateHistogramInterval.DAY)"calendar_interval": "day"
  • .format("yyyy-MM-dd")"format": "yyyy-MM-dd"

4. 異常分析功能

項目實現了智能的異常分析功能,結合精確匹配和模糊搜索:

Java實現
/**
 * 異常原因分析方法 - 綜合查詢實現
 * @param definiteKeyword 精確查詢關鍵字
 * @param possibleKeyword 可能的錯誤關鍵字(模糊匹配)
 * @return 分析結果
 */
public String analyzeError(String definiteKeyword, String possibleKeyword) {
    try {
        logger.info("Starting error analysis with definiteKeyword: {}, possibleKeyword: {}", 
            definiteKeyword, possibleKeyword);
    
        // 設置查詢時間範圍為最近7天
        LocalDateTime endTime = LocalDateTime.now();
        LocalDateTime startTime = endTime.minusDays(7);
    
        StringBuilder result = new StringBuilder();
        result.append("異常分析結果:\n\n");
    
        // 構建複合查詢條件
        BoolQueryBuilder combinedQuery = QueryBuilders.boolQuery();
    
        // 添加時間範圍查詢
        RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("@timestamp")
            .gte(startTime.atZone(ZoneOffset.ofHours(0)).withZoneSameInstant(ZoneOffset.UTC).format(DATE_FORMAT))
            .lte(endTime.atZone(ZoneOffset.ofHours(0)).withZoneSameInstant(ZoneOffset.UTC).format(DATE_FORMAT));
        combinedQuery.must(rangeQuery);
    
        // 添加精確匹配條件(使用match_phrase確保短語完全匹配)
        if (definiteKeyword != null && !definiteKeyword.trim().isEmpty()) {
            result.append("1. 精確關鍵字 '").append(definiteKeyword).append("' 的查詢條件:\n");
            BoolQueryBuilder exactQuery = QueryBuilders.boolQuery();
            exactQuery.should(QueryBuilders.matchPhraseQuery("message", definiteKeyword.trim()));
            exactQuery.should(QueryBuilders.matchPhraseQuery("stack_trace", definiteKeyword.trim()));
            exactQuery.should(QueryBuilders.matchPhraseQuery("class_name", definiteKeyword.trim()));
            exactQuery.should(QueryBuilders.matchPhraseQuery("method_name", definiteKeyword.trim()));
            exactQuery.should(QueryBuilders.matchPhraseQuery("service_name", definiteKeyword.trim()));
            exactQuery.minimumShouldMatch(1);
            combinedQuery.should(exactQuery);
        }
    
        // 添加模糊匹配條件(使用multi_match)
        if (possibleKeyword != null && !possibleKeyword.trim().isEmpty()) {
            result.append("2. 可能關鍵字 '").append(possibleKeyword).append("' 的查詢條件:\n");
            MultiMatchQueryBuilder fuzzyQuery = QueryBuilders.multiMatchQuery(possibleKeyword.trim())
                .field("message", 5.0f)
                .field("stack_trace", 4.0f)
                .field("class_name", 3.0f)
                .field("method_name", 3.0f)
                .field("service_name", 2.0f)
                .type(MultiMatchQueryBuilder.Type.BEST_FIELDS)
                .fuzziness("AUTO")
                .prefixLength(2)
                .maxExpansions(50);
            combinedQuery.should(fuzzyQuery);
        }
    
        // 設置至少一個should子句需要匹配
        combinedQuery.minimumShouldMatch(1);
    
        // 執行復合查詢
        Map<String, Object> combinedResult = searchLogs(combinedRequest, combinedQuery);
        // ... 處理結果邏輯
    
        return result.toString();
    } catch (Exception e) {
        logger.error("Error analysis failed", e);
        throw new RuntimeException("異常分析失敗: " + e.getMessage(), e);
    }
}
對應DSL語法
{
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "@timestamp": {
              "gte": "2023-01-01T00:00:00Z",
              "lte": "2023-01-07T23:59:59Z"
            }
          }
        }
      ],
      "should": [
        {
          "bool": {
            "should": [
              {
                "match_phrase": {
                  "message": "exact error keyword"
                }
              },
              {
                "match_phrase": {
                  "stack_trace": "exact error keyword"
                }
              },
              {
                "match_phrase": {
                  "class_name": "exact error keyword"
                }
              },
              {
                "match_phrase": {
                  "method_name": "exact error keyword"
                }
              },
              {
                "match_phrase": {
                  "service_name": "exact error keyword"
                }
              }
            ],
            "minimum_should_match": 1
          }
        },
        {
          "multi_match": {
            "query": "fuzzy error keyword",
            "fields": ["message^5", "stack_trace^4", "class_name^3", "method_name^3", "service_name^2"],
            "type": "best_fields",
            "fuzziness": "AUTO",
            "prefix_length": 2,
            "max_expansions": 50
          }
        }
      ],
      "minimum_should_match": 1
    }
  }
}
異常分析DSL語法説明

匹配類型:

  • match_phrase → 短語精確匹配,要求所有詞按照指定順序連續出現
  • multi_match → 多字段模糊匹配
  • fuzziness: "AUTO" → 自動調整模糊匹配容錯度
  • minimum_should_match → 控制should子句的最小匹配數

Java API映射:

  • QueryBuilders.matchPhraseQuery()"match_phrase": { }
  • QueryBuilders.multiMatchQuery()"multi_match": { }
  • .minimumShouldMatch(1)"minimum_should_match": 1
  • .fuzziness("AUTO")"fuzziness": "AUTO"
  • .prefixLength(2)"prefix_length": 2
  • .maxExpansions(50)"max_expansions": 50

5. 搜索排序和分頁

Java實現
/**
 * 配置搜索排序和分頁
 */
private void configureSearchSource(LogSearchRequest request, SearchSourceBuilder searchSourceBuilder) {
    // 設置分頁
    searchSourceBuilder.from(request.getFrom());
    searchSourceBuilder.size(request.getSize());
  
    // 配置排序
    searchSourceBuilder.sort(SortBuilders.scoreSort().order(SortOrder.DESC));  // 按相關性得分排序
    searchSourceBuilder.sort("@timestamp", SortOrder.DESC);                    // 按時間倒序排序
  
    // 配置搜索源
    searchSourceBuilder.trackScores(true);  // 跟蹤相關性得分
    searchSourceBuilder.timeout(TimeValue.timeValueSeconds(30));  // 設置超時時間
}
對應DSL語法
{
  "from": 0,
  "size": 20,
  "sort": [
    {
      "_score": {
        "order": "desc"
      }
    },
    {
      "@timestamp": {
        "order": "desc"
      }
    }
  ],
  "timeout": "30s",
  "track_scores": true
}

6. 常用查詢類型對比

精確匹配 vs 模糊匹配

查詢類型

Java實現

DSL語法

適用場景

精確匹配

QueryBuilders.termQuery("level", "ERROR")

"term": { "level": "ERROR" }

精確字段匹配

短語匹配

QueryBuilders.matchPhraseQuery("message", "error occurred")

"match_phrase": { "message": "error occurred" }

短語精確匹配

模糊匹配

QueryBuilders.matchQuery("message", "erorr")

"match": { "message": "erorr" }

文本相似度匹配

多字段匹配

QueryBuilders.multiMatchQuery("error")

"multi_match": { "query": "error" }

跨字段搜索

範圍查詢類型

Java實現

DSL語法

示例

QueryBuilders.rangeQuery("@timestamp").gte("2023-01-01").lte("2023-01-31")

"range": { "@timestamp": { "gte": "2023-01-01", "lte": "2023-01-31" } }

時間範圍

QueryBuilders.gteQuery("@timestamp").lt("now-7d")

"range": { "@timestamp": { "gte": "now-7d" } }

相對時間

QueryBuilders.numericRangeQuery("count").gte(100).lte(1000)

"range": { "count": { "gte": 100, "lte": 1000 } }

數值範圍

性能優化和錯誤處理

1. 連接池配置優化

Java配置
/**
 * Elasticsearch客户端配置優化
 */
@Configuration
@EnableConfigurationProperties(LogProperties.class)
public class AppConfig {
  
    @Bean
    public RestHighLevelClient elasticsearchClient() {
        return new RestHighLevelClient(
            RestClient.builder(
                new HttpHost("localhost", 9200, "http"),
                new HttpHost("localhost", 9201, "http")
            )
            .setRequestConfigCallback(requestConfigBuilder -> 
                requestConfigBuilder
                    .setConnectTimeout(5000)      // 連接超時5秒
                    .setSocketTimeout(30000)      // 讀取超時30秒
                    .setConnectionRequestTimeout(1000) // 連接請求超時1秒
            )
            .setHttpClientConfigCallback(httpClientBuilder -> 
                httpClientBuilder
                    .setMaxConnTotal(100)         // 最大連接數
                    .setMaxConnPerRoute(20)       // 每個路由最大連接數
            )
        );
    }
}
對應配置項説明
  • setConnectTimeout(5000) → 連接建立超時時間
  • setSocketTimeout(30000) → 數據讀取超時時間
  • setMaxConnTotal(100) → 客户端最大連接池大小
  • setMaxConnPerRoute(20) → 每個主機最大連接數

2. 完整查詢DSL示例

複雜查詢DSL
{
  "index": "logstash-2023.01.01,logstash-2023.01.02",
  "from": 0,
  "size": 100,
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "@timestamp": {
              "gte": "2023-01-01T00:00:00Z",
              "lte": "2023-01-31T23:59:59Z"
            }
          }
        },
        {
          "multi_match": {
            "query": "database connection error",
            "fields": ["message^5", "stack_trace^4", "class_name^3"],
            "type": "best_fields",
            "fuzziness": "AUTO",
            "prefix_length": 2,
            "max_expansions": 50
          }
        }
      ],
      "must_not": [
        {
          "term": {
            "level": "DEBUG"
          }
        }
      ],
      "filter": [
        {
          "terms": {
            "service_name": ["user-service", "order-service"]
          }
        }
      ]
    }
  },
  "sort": [
    {
      "@timestamp": {
        "order": "desc"
      }
    }
  ],
  "highlight": {
    "fields": {
      "message": {
        "pre_tags": ["<em class=\"highlight\">"],
        "post_tags": ["</em>"],
        "fragment_size": 600,
        "number_of_fragments": 3
      },
      "stack_trace": {
        "pre_tags": ["<em class=\"highlight\">"],
        "post_tags": ["</em>"],
        "fragment_size": 600,
        "number_of_fragments": 3
      }
    }
  },
  "aggs": {
    "services": {
      "terms": {
        "field": "service_name",
        "size": 10
      }
    },
    "time_buckets": {
      "date_histogram": {
        "field": "@timestamp",
        "calendar_interval": "day",
        "format": "yyyy-MM-dd"
      }
    }
  },
  "timeout": "30s",
  "track_scores": true
}
對應的完整Java實現
/**
 * 完整的日誌搜索實現
 */
public Map<String, Object> searchLogs(LogSearchRequest request) {
    try {
        // 構建索引模式
        String indexPattern = buildIndexPattern(request.getStartTime(), request.getEndTime());
        logger.info("Searching logs in index pattern: {}", indexPattern);

        // 構建複合查詢
        BoolQueryBuilder query = buildQuery(request);

        // 配置搜索源
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(query);
        searchSourceBuilder.from(request.getFrom());
        searchSourceBuilder.size(request.getSize());
        searchSourceBuilder.sort(SortBuilders.scoreSort().order(SortOrder.DESC));
        searchSourceBuilder.sort("@timestamp", SortOrder.DESC);
        searchSourceBuilder.trackScores(true);
        searchSourceBuilder.timeout(TimeValue.timeValueSeconds(30));

        // 配置高亮
        if (request.getKeyword() != null && !request.getKeyword().trim().isEmpty()) {
            configureHighlighting(searchSourceBuilder);
        }

        // 配置聚合
        searchSourceBuilder.aggregation(AggregationBuilders
            .terms("services")
            .field("service_name")
            .size(10));
        searchSourceBuilder.aggregation(AggregationBuilders
            .date_histogram("time_buckets")
            .field("@timestamp")
            .calendarInterval(DateHistogramInterval.DAY)
            .format("yyyy-MM-dd"));

        // 構建搜索請求
        SearchRequest searchRequest = new SearchRequest();
        searchRequest.indices(indexPattern.split(","));
        searchRequest.source(searchSourceBuilder);

        // 執行搜索
        SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
        return processSearchResponse(response, request);

    } catch (IOException e) {
        logger.error("Failed to search logs", e);
        throw new RuntimeException("ES search failed: " + e.getMessage(), e);
    }
}

3. 錯誤處理機制

異常處理DSL注意事項
/**
 * 錯誤處理機制
 */
private Map<String, Object> handleException(Exception e, SearchRequest searchRequest) {
    logger.error("Elasticsearch search failed", e);
  
    // 檢查不同類型的異常
    if (e instanceof ConnectException) {
        throw new RuntimeException("ES服務連接失敗,請檢查網絡和ES集羣狀態", e);
    } else if (e instanceof SocketTimeoutException) {
        throw new RuntimeException("ES查詢超時,請優化查詢條件或調整超時時間", e);
    } else if (e instanceof IndexNotFoundException) {
        throw new RuntimeException("索引不存在,請檢查索引名稱和權限", e);
    } else if (e instanceof SearchPhaseExecutionException) {
        throw new RuntimeException("查詢語法錯誤,請檢查查詢條件", e);
    } else {
        throw new RuntimeException("未知錯誤: " + e.getMessage(), e);
    }
}
DSL查詢驗證
/**
 * 驗證查詢條件的正確性
 */
private void validateSearchRequest(LogSearchRequest request) {
    // 驗證時間範圍
    if (request.getStartTime() != null && request.getEndTime() != null) {
        if (request.getStartTime().isAfter(request.getEndTime())) {
            throw new IllegalArgumentException("開始時間不能晚於結束時間");
        }
    
        // 限制時間範圍不超過30天
        if (request.getStartTime().isBefore(request.getEndTime().minusDays(30))) {
            throw new IllegalArgumentException("查詢時間範圍不能超過30天");
        }
    }
  
    // 驗證分頁參數
    if (request.getSize() > 1000) {
        throw new IllegalArgumentException("單頁查詢數量不能超過1000條");
    }
  
    // 驗證關鍵詞長度
    if (request.getKeyword() != null && request.getKeyword().length() > 100) {
        throw new IllegalArgumentException("關鍵詞長度不能超過100個字符");
    }
}

配置説明

1. application.yml配置

# Elasticsearch配置
elasticsearch:
  hosts: localhost:9200,localhost:9201
  username: elastic
  password: password
  index-pattern: "logstash-*"
  connection-timeout: 5000
  socket-timeout: 30000
  max-conn-total: 100
  max-conn-per-route: 20

# 日誌配置
logging:
  level:
    com.inspur.app.log.service.LogQueryService: DEBUG
    org.elasticsearch: WARN

2. 索引模式配置

Java實現
/**
 * 動態構建索引模式(基於時間範圍)
 * @param start 開始時間
 * @param end 結束時間
 * @return 索引模式字符串
 */
private String buildIndexPattern(LocalDateTime start, LocalDateTime end) {
    Set<String> indices = new HashSet<>();
    LocalDateTime startDay = start.withHour(0).withMinute(0).withSecond(0).withNano(0);
    LocalDateTime endDay = end.withHour(0).withMinute(0).withSecond(0).withNano(0);
  
    // 如果結束時間大於當前時間,將結束時間設置為當前時間
    if (endDay.isAfter(LocalDateTime.now())) {
        endDay = LocalDateTime.now();
    }
  
    // 生成索引名稱模式
    LocalDateTime current = startDay;
    while (!current.isAfter(endDay)) {
        String indexName = "logstash-" + current.format(INDEX_FORMAT);
        indices.add(indexName);
        current = current.plusDays(1);
    }
  
    return String.join(",", indices);
}

API接口文檔

1. 日誌搜索接口

/**
 * 日誌搜索接口
 */
@RestController
@RequestMapping("/api/logs")
public class LogQueryController {
  
    /**
     * 搜索日誌
     * @param request 搜索請求參數
     * @return 搜索結果
     */
    @PostMapping("/search")
    public ResponseEntity<Map<String, Object>> searchLogs(@RequestBody LogSearchRequest request) {
        try {
            validateSearchRequest(request);  // 驗證請求參數
            Map<String, Object> result = logQueryService.searchLogs(request);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            logger.error("Log search failed", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(Collections.singletonMap("error", "搜索失敗: " + e.getMessage()));
        }
    }
  
    /**
     * 異常分析接口
     * @param request 分析請求參數
     * @return 分析結果
     */
    @PostMapping("/analyze-error")
    public ResponseEntity<Map<String, Object>> analyzeError(@RequestBody ErrorAnalysisRequest request) {
        try {
            Map<String, Object> result = logQueryService.analyzeError(request);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            logger.error("Error analysis failed", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(Collections.singletonMap("error", "分析失敗: " + e.getMessage()));
        }
    }
  
    /**
     * 獲取過濾選項
     * @return 可用的過濾選項
     */
    @GetMapping("/filters")
    public ResponseEntity<Map<String, Object>> getFilters() {
        try {
            Map<String, Object> filters = new HashMap<>();
            filters.put("services", logQueryService.getDistinctServices());
            filters.put("levels", logQueryService.getDistinctLevels());
            return ResponseEntity.ok(filters);
        } catch (Exception e) {
            logger.error("Failed to get filters", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(Collections.singletonMap("error", "獲取過濾選項失敗: " + e.getMessage()));
        }
    }
}

最佳實踐和注意事項

1. 查詢性能優化

DSL優化建議:

  • 使用 filter 而不是 must 來執行精確匹配,避免計算相關性得分
  • 合理設置 size 參數,避免一次返回過多數據
  • 使用索引模式而不是通配符,提高查詢效率
  • 為常用字段建立合適的mapping,包括analyzer設置

Java API優化:

// 好的做法:使用filter執行精確匹配
BoolQueryBuilder query = QueryBuilders.boolQuery();
query.filter(QueryBuilders.termQuery("level", "ERROR"));  // 不參與評分

// 避免的做法:在must中使用精確匹配
query.must(QueryBuilders.termQuery("level", "ERROR"));   // 參與評分,影響性能

2. 高亮功能使用

DSL高亮優化:

{
  "highlight": {
    "fields": {
      "message": {
        "pre_tags": ["<em>"],
        "post_tags": ["</em>"],
        "fragment_size": 200,      // 適當的片段大小
        "number_of_fragments": 1,  // 減少片段數量
        "require_field_match": false
      }
    }
  }
}

Java API對應:

// 優化高亮配置
HighlightBuilder.Field highlightField = new HighlightBuilder.Field("message");
highlightField.preTags("<em>");
highlightField.postTags("</em>");
highlightField.fragmentSize(200);          // 設置合適的片段大小
highlightField.numOfFragments(1);          // 只返回第一個片段
highlightField.requireFieldMatch(false);   // 不要求字段匹配

3. 安全考慮

DSL注入防護:

/**
 * 驗證用户輸入,防止DSL注入
 */
private String sanitizeKeyword(String keyword) {
    if (keyword == null) return null;
  
    // 移除潛在的DSL注入字符
    keyword = keyword.replaceAll("[{}\\[\\]<>:\"']", "");
  
    // 限制關鍵詞長度
    if (keyword.length() > 100) {
        keyword = keyword.substring(0, 100);
    }
  
    return keyword;
}

DSL安全檢查:

{
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "user input",  // 經過安全處理的輸入
            "fields": ["message", "stack_trace"]
          }
        }
      ]
    }
  }
}

4. 監控和調試

查詢調試信息:

/**
 * 輸出調試信息
 */
private void logQueryDebug(SearchSourceBuilder searchSourceBuilder) {
    if (logger.isDebugEnabled()) {
        try {
            // 將SearchSourceBuilder轉換為DSL格式
            XContentBuilder builder = XContentFactory.jsonBuilder();
            searchSourceBuilder.toXContent(builder, ToXContent.EMPTY_PARAMS);
            logger.debug("Generated DSL: {}", builder.string());
        } catch (IOException e) {
            logger.warn("Failed to generate DSL debug info", e);
        }
    }
}

總結

本項目實現了完整的Elasticsearch日誌查詢和分析系統,通過Java API和原始DSL語法的對比映射,為開發者提供了清晰的技術實現參考。文檔中的代碼示例和DSL語法説明可以直接用於類似的日誌查詢系統開發。

關鍵優勢:

  1. 雙重實現方式 - Java API和DSL語法對照,便於理解和調試
  2. 完整的錯誤處理 - 全面的異常捕獲和用户友好的錯誤提示
  3. 性能優化實踐 - 連接池配置、查詢優化、索引管理
  4. 安全防護機制 - 輸入驗證、DSL注入防護、權限控制
  5. 可維護性設計 - 模塊化實現、清晰註釋、錯誤日誌記錄

通過這種方式,開發者可以深入理解Elasticsearch的底層機制,同時享受Java API的便利性和DSL的靈活性。