gabriel-farache commented on code in PR #4094: URL: https://github.com/apache/incubator-kie-kogito-runtimes/pull/4094#discussion_r2581583731
########## kogito-test-utils/src/main/java/org/kie/kogito/test/utils/JsonProcessInstanceLogAnalyzer.java: ########## @@ -0,0 +1,559 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.test.utils; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +/** + * Utility class for analyzing process instance aware logging in JSON format. + * Supports parsing JSON log format with MDC fields including processInstanceId. + * This class replaces pipe-delimited format parsing for machine-consumable JSON logs. + */ +public class JsonProcessInstanceLogAnalyzer { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + // Common timestamp patterns in JSON logs + private static final DateTimeFormatter[] TIMESTAMP_FORMATTERS = { + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX"), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss,SSS"), + DateTimeFormatter.ISO_LOCAL_DATE_TIME + }; + + /** + * Represents a single JSON log entry with all its components. + */ + public static class JsonLogEntry { + public final LocalDateTime timestamp; + public final String level; + public final String loggerName; + public final String message; + public final Map<String, String> mdc; + public final String threadName; + public final String sequenceNumber; + public final JsonNode rawJson; + + public JsonLogEntry(LocalDateTime timestamp, String level, String loggerName, + String message, Map<String, String> mdc, String threadName, + String sequenceNumber, JsonNode rawJson) { + this.timestamp = timestamp; + this.level = level != null ? level : "INFO"; + this.loggerName = loggerName != null ? loggerName : "unknown.logger"; + this.message = message != null ? message : ""; + this.mdc = mdc != null ? new HashMap<>(mdc) : new HashMap<>(); + this.threadName = threadName; + this.sequenceNumber = sequenceNumber; + this.rawJson = rawJson; + } + + /** + * Get the process instance ID from MDC. + */ + public String getProcessInstanceId() { + return mdc.get("processInstanceId"); + } + + /** + * Check if this log entry has a process instance ID. + */ + public boolean hasProcessInstance() { + String processInstanceId = getProcessInstanceId(); + return processInstanceId != null && !processInstanceId.trim().isEmpty(); + } + + /** + * Check if this log entry is general context (no process instance ID). + */ + public boolean isGeneralContext() { + return !hasProcessInstance(); + } + + /** + * Get trace ID from MDC if available. + */ + public String getTraceId() { + return mdc.get("traceId"); + } + + /** + * Get span ID from MDC if available. + */ + public String getSpanId() { + return mdc.get("spanId"); + } + + @Override + public String toString() { + return String.format("JsonLogEntry{timestamp=%s, level=%s, processInstanceId=%s, logger=%s, message=%s}", + timestamp, level, getProcessInstanceId(), loggerName, message); + } + } + + /** + * Statistics about JSON log entries for analysis. + */ + public static class JsonLogStatistics { + public final long totalLogs; + public final long processSpecificLogs; + public final long generalContextLogs; + public final Map<String, Long> logsByProcessInstance; + public final Map<String, Long> logsByLevel; + public final Map<String, Long> logsByLogger; + public final long logsWithTracing; + + public JsonLogStatistics(List<JsonLogEntry> entries) { + this.totalLogs = entries.size(); + this.processSpecificLogs = entries.stream().filter(JsonLogEntry::hasProcessInstance).count(); + this.generalContextLogs = entries.stream().filter(JsonLogEntry::isGeneralContext).count(); + this.logsByProcessInstance = entries.stream() + .collect(Collectors.groupingBy( + entry -> entry.hasProcessInstance() ? entry.getProcessInstanceId() : "", + Collectors.counting())); + this.logsByLevel = entries.stream() + .collect(Collectors.groupingBy(entry -> entry.level, Collectors.counting())); + this.logsByLogger = entries.stream() + .collect(Collectors.groupingBy(entry -> entry.loggerName, Collectors.counting())); + this.logsWithTracing = entries.stream() + .filter(entry -> entry.getTraceId() != null) + .count(); + } + + @Override + public String toString() { + return String.format( + "JsonLogStatistics{total=%d, processSpecific=%d, general=%d, byProcess=%s, byLevel=%s, withTracing=%d}", + totalLogs, processSpecificLogs, generalContextLogs, logsByProcessInstance, logsByLevel, logsWithTracing); + } + } + + /** + * Parse JSON log file with multiline support and resilient error handling. + */ + public static List<JsonLogEntry> parseJsonLogFile(Path logFile) throws IOException { + List<String> lines = Files.readAllLines(logFile); Review Comment: I did not care much for perfs as it's a test util but sure, I can read line by line to be safe -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
