ソースを参照

add-D4Metric-3.0

Qyanger 4 ヶ月 前
コミット
43b608cfb9

+ 23 - 2
README.md

@@ -47,6 +47,27 @@ ELASTICSEARCH_SOCKET_TIMEOUT=10s
 - `spring.elasticsearch.username=${ELASTICSEARCH_USERNAME:}`
 - `spring.elasticsearch.password=${ELASTICSEARCH_PASSWORD:}`
 
-## 文档
+### 最小可运行镜像(建议)
 
-完整接口文档见 `README-API.md`。
+```bash
+docker build -t seec-analysis-backend:latest .
+docker run --rm -p 8080:8080 \
+	-e ELASTICSEARCH_URIS=http://<cloud-es-service>:9200 \
+	-e ELASTICSEARCH_USERNAME=<optional> \
+	-e ELASTICSEARCH_PASSWORD=<optional> \
+	-e JAVA_OPTS="-Xms128m -Xmx256m" \
+	seec-analysis-backend:latest
+```
+
+### PaaS 必填配置清单
+
+- `ELASTICSEARCH_URIS`:云端 Elasticsearch 内网地址(必填)
+- `ELASTICSEARCH_USERNAME`:开启鉴权时必填
+- `ELASTICSEARCH_PASSWORD`:开启鉴权时必填
+
+### PaaS 可选配置
+
+- `JAVA_OPTS`:JVM 参数,默认 `-Xms128m -Xmx256m`
+- `ELASTICSEARCH_CONNECTION_TIMEOUT`:默认 `5s`
+- `ELASTICSEARCH_SOCKET_TIMEOUT`:默认 `10s`
+- `SERVER_PORT`:如果平台要求非 8080,可在平台里映射或额外覆盖

+ 9 - 6
src/main/java/com/demo/seecanalysisbackend/model/LogEntry.java

@@ -17,31 +17,34 @@ import java.util.Map;
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
-@Document(indexName = "logs", createIndex = false)
+@Document(indexName = "k8s-logs-*", createIndex = false)
 public class LogEntry {
     
     @Id
     private String id;
     
-    @Field(type = FieldType.Date, name = "timestamp", format = DateFormat.date_time)
+    @Field(type = FieldType.Date, name = "@timestamp", format = DateFormat.date_time)
     private LocalDateTime timestamp;
     
     @Field(type = FieldType.Text, name = "raw_message")
     private String rawMessage;
+
+    @Field(type = FieldType.Text, name = "business_info")
+    private String businessInfo;
     
     @Field(type = FieldType.Keyword, name = "service_name")
     private String serviceName;
     
-    @Field(type = FieldType.Keyword, name = "thread")
+    @Field(type = FieldType.Keyword, name = "process_name")
     private String thread;
     
-    @Field(type = FieldType.Keyword, name = "level")
+    @Field(type = FieldType.Keyword, name = "log_level")
     private String level;
 
     @Field(type = FieldType.Keyword, name = "application")
     private String application;
     
-    @Field(type = FieldType.Keyword, name = "class_name")
+    @Field(type = FieldType.Keyword, name = "method_location")
     private String className;
     
     @Field(type = FieldType.Keyword, name = "method_name")
@@ -53,7 +56,7 @@ public class LogEntry {
     @Field(type = FieldType.Keyword, name = "span_id")
     private String spanId;
     
-    @Field(type = FieldType.Keyword, name = "biz_name")
+    @Field(type = FieldType.Keyword, name = "bizName")
     private String bizName;
     
     @Field(type = FieldType.Object, name = "context")

+ 6 - 6
src/main/java/com/demo/seecanalysisbackend/repository/LogEntryRepository.java

@@ -29,24 +29,24 @@ public interface LogEntryRepository extends ElasticsearchRepository<LogEntry, St
     Page<LogEntry> findByTimestampBetweenAndBizNameIn(LocalDateTime startTime, LocalDateTime endTime, 
                                                      List<String> bizNames, Pageable pageable);
     
-    @Query("{\"bool\": {\"must\": [{\"range\": {\"timestamp\": {\"gte\": \"?0\", \"lte\": \"?1\"}}}, {\"match\": {\"message\": \"?2\"}}]}}")
+    @Query("{\"bool\": {\"must\": [{\"range\": {\"@timestamp\": {\"gte\": \"?0\", \"lte\": \"?1\"}}}, {\"multi_match\": {\"query\": \"?2\", \"fields\": [\"raw_message\", \"business_info\", \"method_location\", \"service_name\", \"trace_id\", \"bizName\"]}}]}}")
     Page<LogEntry> findByTimestampBetweenAndMessage(LocalDateTime startTime, LocalDateTime endTime, 
                                                    String message, Pageable pageable);
     
-    @Query("{\"bool\": {\"must\": [{\"range\": {\"timestamp\": {\"gte\": \"?0\", \"lte\": \"?1\"}}}, {\"term\": {\"level\": \"?2\"}}]}}")
+    @Query("{\"bool\": {\"must\": [{\"range\": {\"@timestamp\": {\"gte\": \"?0\", \"lte\": \"?1\"}}}, {\"term\": {\"log_level\": \"?2\"}}]}}")
     long countByTimestampBetweenAndLevel(LocalDateTime startTime, LocalDateTime endTime, String level);
     
-    @Query("{\"bool\": {\"must\": [{\"range\": {\"timestamp\": {\"gte\": \"?0\", \"lte\": \"?1\"}}}]}}")
+    @Query("{\"bool\": {\"must\": [{\"range\": {\"@timestamp\": {\"gte\": \"?0\", \"lte\": \"?1\"}}}]}}")
     long countByTimestampBetween(LocalDateTime startTime, LocalDateTime endTime);
     
-    @Query("{\"bool\": {\"must\": [{\"range\": {\"timestamp\": {\"gte\": \"?0\", \"lte\": \"?1\"}}}, {\"term\": {\"biz_name\": \"?2\"}}]}}")
+    @Query("{\"bool\": {\"must\": [{\"range\": {\"@timestamp\": {\"gte\": \"?0\", \"lte\": \"?1\"}}}, {\"term\": {\"bizName\": \"?2\"}}]}}")
     long countByTimestampBetweenAndBizName(LocalDateTime startTime, LocalDateTime endTime, String bizName);
     
-    @Query("{\"bool\": {\"must\": [{\"range\": {\"timestamp\": {\"gte\": \"?0\", \"lte\": \"?1\"}}}, {\"term\": {\"service_name\": \"?2\"}}]}}")
+    @Query("{\"bool\": {\"must\": [{\"range\": {\"@timestamp\": {\"gte\": \"?0\", \"lte\": \"?1\"}}}, {\"term\": {\"service_name\": \"?2\"}}]}}")
     Page<LogEntry> findByTimestampBetweenAndServiceName(LocalDateTime startTime, LocalDateTime endTime, 
                                                        String serviceName, Pageable pageable);
     
-    @Query("{\"bool\": {\"must\": [{\"range\": {\"timestamp\": {\"gte\": \"?0\", \"lte\": \"?1\"}}}, {\"term\": {\"trace_id\": \"?2\"}}]}}")
+    @Query("{\"bool\": {\"must\": [{\"range\": {\"@timestamp\": {\"gte\": \"?0\", \"lte\": \"?1\"}}}, {\"term\": {\"trace_id\": \"?2\"}}]}}")
     Page<LogEntry> findByTimestampBetweenAndTraceId(LocalDateTime startTime, LocalDateTime endTime, 
                                                    String traceId, Pageable pageable);
 }

+ 66 - 20
src/main/java/com/demo/seecanalysisbackend/service/D4MetricsService.java

@@ -14,6 +14,7 @@ import org.springframework.stereotype.Service;
 import java.time.Duration;
 import java.time.LocalDateTime;
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
 @Service
@@ -30,6 +31,7 @@ public class D4MetricsService {
     );
     private static final int D4_QUERY_PAGE_SIZE = 1000;
     private static final long DEFAULT_D4_SLA_THRESHOLD_MS = 24L * 60L * 60L * 1000L;
+    private static final String BUSINESS_INFO_SEPARATOR = "\\|";
     
     public D4Metrics calculateD4Metrics(LocalDateTime startTime, LocalDateTime endTime) {
         log.info("Calculating D4 metrics from {} to {}", startTime, endTime);
@@ -156,16 +158,18 @@ public class D4MetricsService {
     
     private Map<String, Double> calculateCompletionRateByTaskType(List<LogEntry> assignedLogs, List<LogEntry> completedLogs) {
         Map<String, Long> assignedByType = assignedLogs.stream()
-            .filter(log -> log.getContext() != null && log.getContext().containsKey("taskType"))
+            .map(log -> getContextString(log, "taskType"))
+            .filter(Objects::nonNull)
             .collect(Collectors.groupingBy(
-                log -> (String) log.getContext().get("taskType"),
+                taskType -> taskType,
                 Collectors.counting()
             ));
         
         Map<String, Long> completedByType = completedLogs.stream()
-            .filter(log -> log.getContext() != null && log.getContext().containsKey("taskType"))
+            .map(log -> getContextString(log, "taskType"))
+            .filter(Objects::nonNull)
             .collect(Collectors.groupingBy(
-                log -> (String) log.getContext().get("taskType"),
+                taskType -> taskType,
                 Collectors.counting()
             ));
         
@@ -183,16 +187,18 @@ public class D4MetricsService {
     
     private Map<String, Double> calculateCompletionRateByProject(List<LogEntry> assignedLogs, List<LogEntry> completedLogs) {
         Map<String, Long> assignedByProject = assignedLogs.stream()
-            .filter(log -> log.getContext() != null && log.getContext().containsKey("projectId"))
+            .map(log -> getContextString(log, "projectId"))
+            .filter(Objects::nonNull)
             .collect(Collectors.groupingBy(
-                log -> String.valueOf(log.getContext().get("projectId")),
+                projectId -> projectId,
                 Collectors.counting()
             ));
         
         Map<String, Long> completedByProject = completedLogs.stream()
-            .filter(log -> log.getContext() != null && log.getContext().containsKey("projectId"))
+            .map(log -> getContextString(log, "projectId"))
+            .filter(Objects::nonNull)
             .collect(Collectors.groupingBy(
-                log -> String.valueOf(log.getContext().get("projectId")),
+                projectId -> projectId,
                 Collectors.counting()
             ));
         
@@ -258,18 +264,20 @@ public class D4MetricsService {
     
     private Map<String, Long> calculateCollaborationsBySource(List<LogEntry> collabLogs) {
         return collabLogs.stream()
-            .filter(log -> log.getContext() != null && log.getContext().containsKey("source"))
+            .map(log -> getContextString(log, "source"))
+            .filter(Objects::nonNull)
             .collect(Collectors.groupingBy(
-                log -> (String) log.getContext().get("source"),
+                source -> source,
                 Collectors.counting()
             ));
     }
     
     private Map<String, Long> calculateConflictsByType(List<LogEntry> conflictLogs) {
         return conflictLogs.stream()
-            .filter(log -> log.getContext() != null && log.getContext().containsKey("conflictType"))
+            .map(log -> getContextString(log, "conflictType"))
+            .filter(Objects::nonNull)
             .collect(Collectors.groupingBy(
-                log -> String.valueOf(log.getContext().get("conflictType")),
+                conflictType -> conflictType,
                 Collectors.counting()
             ));
     }
@@ -371,18 +379,12 @@ public class D4MetricsService {
     }
 
     private String getContextString(LogEntry logEntry, String key) {
-        if (logEntry == null || logEntry.getContext() == null) {
-            return null;
-        }
-        Object value = logEntry.getContext().get(key);
+        Object value = getContextValue(logEntry, key);
         return value != null ? String.valueOf(value) : null;
     }
 
     private Long getContextLong(LogEntry logEntry, String key) {
-        if (logEntry == null || logEntry.getContext() == null) {
-            return null;
-        }
-        Object value = logEntry.getContext().get(key);
+        Object value = getContextValue(logEntry, key);
         if (value == null) {
             return null;
         }
@@ -404,4 +406,48 @@ public class D4MetricsService {
             }
         }
     }
+
+    private Object getContextValue(LogEntry logEntry, String key) {
+        if (logEntry == null || key == null) {
+            return null;
+        }
+        Map<String, Object> context = logEntry.getContext();
+        if (context != null && context.containsKey(key)) {
+            return context.get(key);
+        }
+        Map<String, Object> parsed = parseBusinessInfo(logEntry);
+        return parsed.get(key);
+    }
+
+    private Map<String, Object> parseBusinessInfo(LogEntry logEntry) {
+        if (logEntry == null || logEntry.getBusinessInfo() == null || logEntry.getBusinessInfo().isBlank()) {
+            return Collections.emptyMap();
+        }
+
+        if (logEntry.getContext() == null) {
+            logEntry.setContext(new ConcurrentHashMap<>());
+        }
+
+        if (!logEntry.getContext().isEmpty()) {
+            return logEntry.getContext();
+        }
+
+        String[] pairs = logEntry.getBusinessInfo().split(BUSINESS_INFO_SEPARATOR);
+        for (String pair : pairs) {
+            if (pair == null || pair.isBlank()) {
+                continue;
+            }
+            int splitIndex = pair.indexOf('=');
+            if (splitIndex <= 0 || splitIndex == pair.length() - 1) {
+                continue;
+            }
+            String key = pair.substring(0, splitIndex).trim();
+            String value = pair.substring(splitIndex + 1).trim();
+            if (!key.isEmpty() && !value.isEmpty() && !"null".equalsIgnoreCase(value)) {
+                logEntry.getContext().put(key, value);
+            }
+        }
+
+        return logEntry.getContext();
+    }
 }