Log File Pattern Analyzer (java, written by Codex)
envgap__codex__java-t1-5
Written by a coding agent; not on GitHubWritten 2026-03-02
01 / FAILURE SIGNATURE
As the study recorded it
None
Not a benchmark task.
- The project already builds and runs before the fix, so there is nothing to repair.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
pom.xml- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/java-t1 #5 · read the task the agent was given
Codex wrote this java project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: Log File Pattern Analyzer Write a program that analyzes structured and semi-structured log files to detect patterns, extract statistics, and identify anomalies such as error spikes and unusual activity. FUNCTIONAL REQUIREMENTS: - Accept a log file path as a command-line argument - Auto-detect common log formats: Apache/Nginx access logs, syslog, and JSON-structured logs - Parse timestamps, log levels (DEBUG, INFO, WARN, ERROR, FATAL), source identifiers, and message content - Compute statistics: total entries, entries per log level, entries per hour/day, top 10 most frequent messages (grouped by template after removing variable parts like IPs, timestamps, and IDs) - Detect error spikes: flag any time window where the error rate exceeds 3x the overall average error rate - Support filtering by date range via --from and --to flags (ISO 8601 format) - Support filtering by log level via --level flag (show that level and above) - Print a summary report to console with counts, top patterns, and detected anomalies - Save the full analysis as a JSON report file with --output flag (default: log_analysis.json) - Support processing multiple log files by accepting a glob pattern or directory path - If no input file is given, generate a sample log file with mixed levels, an error spike period, and varied message templates, then analyze it - Handle malformed log lines gracefully by counting them separately and continuing analysis Create a complete Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include: - Source code - pom.xml with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>tmlr.codex_generated.p05</groupId>
<artifactId>log-file-pattern-analyzer</artifactId>
<version>1.0.0</version>
<name>Log File Pattern Analyzer</name>
<description>Analyzes structured and semi-structured logs for statistics and anomalies.</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<mainClass>LogFilePatternAnalyzer</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
README.md
# Log File Pattern Analyzer (Java) Analyzes Apache/Nginx, syslog, JSON, and generic logs to extract statistics, message templates, and anomalies. ## Features - Accepts: - file path - directory path - glob pattern - Auto-detects common log formats and parses timestamp/level/source/message. - Computes: - total parsed entries - malformed line count - entries per level/hour/day - top 10 message templates - Detects error spikes where window error rate > 3x average. - Filters: - `--from`, `--to` (ISO 8601) - `--level` (and above) - Prints summary and writes JSON report (`--output`, default `log_analysis.json`). - No input: generates sample log and analyzes it. ## Requirements - Ubuntu 22.04 - JDK 17+ ## Dependencies (Pinned) No external runtime dependencies. - Direct runtime dependencies: none - Transitive runtime dependencies: none ## Run ```bash cd TMLR/code_generation/codex_generated/p_05/java mkdir -p out javac -d out src/main/java/LogFilePatternAnalyzer.java java -cp out LogFilePatternAnalyzer ./app.log java -cp out LogFilePatternAnalyzer ./logs --from 2026-01-10T00:00:00Z --to 2026-01-11T00:00:00Z --level WARN --output report.json java -cp out LogFilePatternAnalyzer ```
src/main/java/LogFilePatternAnalyzer.java
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LogFilePatternAnalyzer {
private static final List<String> LEVELS = List.of("DEBUG", "INFO", "WARN", "ERROR", "FATAL");
private static final Pattern APACHE_RE = Pattern.compile(
"^(\\d{1,3}(?:\\.\\d{1,3}){3})\\s+\\S+\\s+\\S+\\s+\\[([^\\]]+)\\]\\s+\"([^\"]*)\"\\s+(\\d{3})\\s+(\\S+).*$"
);
private static final Pattern SYSLOG_RE = Pattern.compile(
"^([A-Z][a-z]{2}\\s+\\d+\\s+\\d\\d:\\d\\d:\\d\\d)\\s+(\\S+)\\s+([^:]+):\\s*(.*)$"
);
private static final Pattern GENERIC_RE = Pattern.compile(
"^(\\d{4}-\\d{2}-\\d{2}[T ][\\d:.+\\-Z]+)?\\s*\\[?(DEBUG|INFO|WARN|WARNING|ERROR|FATAL|CRITICAL)\\]?\\s*([A-Za-z0-9_.-]+)?\\s*[-:]?\\s*(.*)$",
Pattern.CASE_INSENSITIVE
);
public static void main(String[] args) {
try {
int code = run(args);
System.exit(code);
} catch (Exception ex) {
System.err.println("Unexpected failure: " + ex.getMessage());
System.exit(1);
}
}
private static int run(String[] args) throws IOException {
Instant from = null;
Instant to = null;
String level = "DEBUG";
String output = "log_analysis.json";
String inputArg = null;
for (int i = 0; i < args.length; i++) {
String arg = args[i];
if ("--from".equals(arg)) {
i++;
if (i >= args.length) return usage();
from = parseTimestamp(args[i]);
if (from == null) {
System.err.println("Invalid --from timestamp. Use ISO 8601.");
return 1;
}
} else if ("--to".equals(arg)) {
i++;
if (i >= args.length) return usage();
to = parseTimestamp(args[i]);
if (to == null) {
System.err.println("Invalid --to timestamp. Use ISO 8601.");
return 1;
}
} else if ("--level".equals(arg)) {
i++;
if (i >= args.length) return usage();
String normalized = normalizeLevel(args[i]);
if (normalized == null) {
System.err.println("Invalid --level. Use DEBUG|INFO|WARN|ERROR|FATAL");
return 1;
}
level = normalized;
} else if ("--output".equals(arg)) {
i++;
if (i >= args.length) return usage();
output = args[i];
} else {
inputArg = arg;
}
}
List<Path> files;
if (inputArg == null) {
Path sample = Paths.get("sample.log").toAbsolutePath().normalize();
Files.writeString(sample, createSampleLogs(), StandardCharsets.UTF_8);
files = List.of(sample);
System.out.println("No input provided. Generated sample log: " + sample);
} else {
files = resolveInputs(inputArg);
if (files.isEmpty()) {
System.err.println("No input log files matched.");
return 1;
}
}
List<LogEntry> entries = new ArrayList<>();
int malformed = 0;
for (Path file : files) {
List<String> lines;
try {
lines = Files.readAllLines(file, StandardCharsets.UTF_8);
} catch (IOException ex) {
System.err.println("Failed to read " + file + ": " + ex.getMessage());
return 1;
}
for (String line : lines) {
if (line.trim().isEmpty()) continue;
LogEntry parsed = parseLine(line);
if (parsed == null) {
malformed++;
continue;
}
if (!levelAtLeast(parsed.level, level)) continue;
if (from != null && parsed.timestamp != null && parsed.timestamp.isBefore(from)) continue;
if (to != null && parsed.timestamp != null && parsed.timestamp.isAfter(to)) continue;
entries.add(parsed);
}
}
Map<String, Object> report = computeReport(entries, malformed, files, from, to, level);
printSummary(report);
Path outPath = Paths.get(output).toAbsolutePath().normalize();
Files.writeString(outPath, JsonWriter.pretty(report) + System.lineSeparator(), StandardCharsets.UTF_8);
System.out.println("\nSaved JSON report: " + outPath);
return 0;
}
private static int usage() {
System.err.println("Usage: java -cp out LogFilePatternAnalyzer <path|dir|glob> [--from iso] [--to iso] [--level L] [--output file]");
return 1;
}
private static List<Path> resolveInputs(String inputArg) throws IOException {
List<Path> files = new ArrayList<>();
boolean wildcard = inputArg.contains("*") || inputArg.contains("?");
if (wildcard) {
Path dir = Paths.get(inputArg).getParent();
if (dir == null) dir = Paths.get(".");
String pattern = Paths.get(inputArg).getFileName().toString();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, pattern)) {
for (Path p : stream) if (Files.isRegularFile(p)) files.add(p.toAbsolutePath().normalize());
}
Collections.sort(files);
return files;
}
Path abs = Paths.get(inputArg).toAbsolutePath().normalize();
if (Files.isDirectory(abs)) {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(abs)) {
for (Path p : stream) {
if (!Files.isRegularFile(p)) continue;
String name = p.getFileName().toString().toLowerCase(Locale.ROOT);
if (name.endsWith(".log") || name.endsWith(".txt") || name.endsWith(".json") || name.endsWith(".jsonl")) {
files.add(p.toAbsolutePath().normalize());
}
}
}
Collections.sort(files);
return files;
}
if (Files.isRegularFile(abs)) return List.of(abs);
return files;
}
private static String normalizeLevel(String raw) {
if (raw == null) return "INFO";
String u = raw.toUpperCase(Locale.ROOT);
if ("WARNING".equals(u)) u = "WARN";
if ("ERR".equals(u)) u = "ERROR";
if ("CRITICAL".equals(u)) u = "FATAL";
return LEVELS.contains(u) ? u : null;
}
private static boolean levelAtLeast(String level, String threshold) {
return LEVELS.indexOf(level) >= LEVELS.indexOf(threshold);
}
private static Instant parseTimestamp(String text) {
if (text == null || text.isBlank()) return null;
String s = text.trim();
try {
return Instant.parse(s);
} catch (DateTimeParseException ignored) {
}
try {
return OffsetDateTime.parse(s).toInstant();
} catch (DateTimeParseException ignored) {
}
try {
return LocalDateTime.parse(s, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).toInstant(ZoneOffset.UTC);
} catch (DateTimeParseException ignored) {
}
try {
int year = LocalDateTime.now(ZoneOffset.UTC).getYear();
LocalDateTime ldt = LocalDateTime.parse(s + " " + year, DateTimeFormatter.ofPattern("MMM d HH:mm:ss yyyy", Locale.ENGLISH));
return ldt.toInstant(ZoneOffset.UTC);
} catch (DateTimeParseException ignored) {
}
return null;
}
private static LogEntry parseLine(String line) {
LogEntry json = parseJsonLine(line);
if (json != null) return json;
LogEntry apache = parseApache(line);
if (apache != null) return apache;
LogEntry syslog = parseSyslog(line);
if (syslog != null) return syslog;
return parseGeneric(line);
}
private static LogEntry parseJsonLine(String line) {
if (!line.trim().startsWith("{")) return null;
String levelRaw = extractJsonValue(line, "level");
if (levelRaw == null) levelRaw = extractJsonValue(line, "severity");
if (levelRaw == null) levelRaw = extractJsonValue(line, "log_level");
String level = normalizeLevel(levelRaw == null ? "INFO" : levelRaw);
if (level == null) return null;
String message = extractJsonValue(line, "message");
if (message == null) message = extractJsonValue(line, "msg");
if (message == null) message = extractJsonValue(line, "event");
if (message == null) message = "";
String timestampRaw = extractJsonValue(line, "timestamp");
if (timestampRaw == null) timestampRaw = extractJsonValue(line, "time");
if (timestampRaw == null) timestampRaw = extractJsonValue(line, "datetime");
if (timestampRaw == null) timestampRaw = extractJsonValue(line, "date");
String source = extractJsonValue(line, "source");
if (source == null) source = extractJsonValue(line, "logger");
if (source == null) source = extractJsonValue(line, "service");
return new LogEntry("json", parseTimestamp(timestampRaw), level, source, message);
}
private static String extractJsonValue(String line, String key) {
Pattern p = Pattern.compile("\"" + Pattern.quote(key) + "\"\\s*:\\s*(\"((?:\\\\.|[^\"])*)\"|[0-9.+\\-]+|true|false|null)");
Matcher m = p.matcher(line);
if (!m.find()) return null;
String raw = m.group(1);
if (raw == null) return null;
if (raw.startsWith("\"") && raw.endsWith("\"")) {
return raw.substring(1, raw.length() - 1).replace("\\\"", "\"").replace("\\\\", "\\");
}
return raw;
}
private static LogEntry parseApache(String line) {
Matcher m = APACHE_RE.matcher(line);
if (!m.matches()) return null;
String stamp = m.group(2).replaceFirst(":", " ").replace("/", " ");
Instant ts = parseTimestamp(stamp);
int status = Integer.parseInt(m.group(4));
String level = status >= 500 ? "ERROR" : status >= 400 ? "WARN" : "INFO";
return new LogEntry("apache", ts, level, m.group(1), m.group(3) + " status=" + m.group(4) + " bytes=" + m.group(5));
}
private static LogEntry parseSyslog(String line) {
Matcher m = SYSLOG_RE.matcher(line);
if (!m.matches()) return null;
String msg = m.group(4);
String level = "INFO";
Matcher lm = Pattern.compile("\\b(DEBUG|INFO|WARN|WARNING|ERROR|FATAL|CRITICAL)\\b", Pattern.CASE_INSENSITIVE).matcher(msg);
if (lm.find()) level = normalizeLevel(lm.group(1));
if (level == null) level = "INFO";
return new LogEntry("syslog", parseTimestamp(m.group(1)), level, m.group(3), msg);
}
private static LogEntry parseGeneric(String line) {
Matcher m = GENERIC_RE.matcher(line);
if (!m.matches()) return null;
String level = normalizeLevel(m.group(2));
if (level == null) return null;
return new LogEntry("generic", parseTimestamp(m.group(1)), level, m.group(3), m.group(4) == null ? line : m.group(4));
}
private static Map<String, Object> computeReport(
List<LogEntry> entries, int malformed, List<Path> files, Instant from, Instant to, String threshold
) {
Map<String, Integer> levelCounts = new LinkedHashMap<>();
for (String level : LEVELS) levelCounts.put(level, 0);
Map<String, Integer> hourly = new LinkedHashMap<>();
Map<String, Integer> daily = new LinkedHashMap<>();
Map<String, Integer> templates = new LinkedHashMap<>();
Map<String, WindowStat> hourStats = new LinkedHashMap<>();
int errorCount = 0;
int timestamped = 0;
DateTimeFormatter hourFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH':00:00'X").withZone(ZoneOffset.UTC);
DateTimeFormatter dayFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneOffset.UTC);
for (LogEntry entry : entries) {
levelCounts.put(entry.level, levelCounts.getOrDefault(entry.level, 0) + 1);
String template = templateize(entry.message);
templates.put(template, templates.getOrDefault(template, 0) + 1);
if ("ERROR".equals(entry.level) || "FATAL".equals(entry.level)) errorCount++;
if (entry.timestamp != null) {
timestamped++;
String h = hourFmt.format(entry.timestamp);
String d = dayFmt.format(entry.timestamp);
hourly.put(h, hourly.getOrDefault(h, 0) + 1);
daily.put(d, daily.getOrDefault(d, 0) + 1);
WindowStat ws = hourStats.getOrDefault(h, new WindowStat());
ws.total++;
if ("ERROR".equals(entry.level) || "FATAL".equals(entry.level)) ws.errors++;
hourStats.put(h, ws);
}
}
double avgErrorRate = entries.isEmpty() ? 0.0 : (double) errorCount / entries.size();
List<Map<String, Object>> spikes = new ArrayList<>();
List<String> windows = new ArrayList<>(hourStats.keySet());
Collections.sort(windows);
for (String w : windows) {
WindowStat ws = hourStats.get(w);
double rate = ws.total == 0 ? 0.0 : (double) ws.errors / ws.total;
if (avgErrorRate > 0 && rate > avgErrorRate * 3.0) {
Map<String, Object> s = new LinkedHashMap<>();
s.put("windowStart", w);
s.put("totalEntries", ws.total);
s.put("errorEntries", ws.errors);
s.put("errorRate", rate);
s.put("averageErrorRate", avgErrorRate);
spikes.add(s);
}
}
List<Map.Entry<String, Integer>> tplEntries = new ArrayList<>(templates.entrySet());
tplEntries.sort((a, b) -> {
int cmp = Integer.compare(b.getValue(), a.getValue());
if (cmp != 0) return cmp;
return a.getKey().compareTo(b.getKey());
});
List<Map<String, Object>> topPatterns = new ArrayList<>();
for (int i = 0; i < Math.min(10, tplEntries.size()); i++) {
Map<String, Object> p = new LinkedHashMap<>();
p.put("template", tplEntries.get(i).getKey());
p.put("count", tplEntries.get(i).getValue());
topPatterns.add(p);
}
Map<String, Object> metadata = new LinkedHashMap<>();
metadata.put("analyzedAt", Instant.now().toString());
List<String> fileList = new ArrayList<>();
for (Path f : files) fileList.add(f.toString());
metadata.put("files", fileList);
metadata.put("from", from == null ? null : from.toString());
metadata.put("to", to == null ? null : to.toString());
metadata.put("levelThreshold", threshold);
Map<String, Object> totals = new LinkedHashMap<>();
totals.put("parsedEntries", entries.size());
totals.put("malformedLines", malformed);
totals.put("timestampedEntries", timestamped);
Map<String, Object> anomalies = new LinkedHashMap<>();
anomalies.put("errorSpikes", spikes);
Map<String, Object> report = new LinkedHashMap<>();
report.put("metadata", metadata);
report.put("totals", totals);
report.put("entriesPerLevel", levelCounts);
report.put("entriesPerHour", sortMap(hourly));
report.put("entriesPerDay", sortMap(daily));
report.put("topMessagePatterns", topPatterns);
report.put("anomalies", anomalies);
return report;
}
private static <T> Map<String, T> sortMap(Map<String, T> input) {
List<String> keys = new ArrayList<>(input.keySet());
Collections.sort(keys);
Map<String, T> out = new LinkedHashMap<>();
for (String k : keys) out.put(k, input.get(k));
return out;
}
private static void printSummary(Map<String, Object> report) {
@SuppressWarnings("unchecked")
Map<String, Object> metadata = (Map<String, Object>) report.get("metadata");
@SuppressWarnings("unchecked")
Map<String, Object> totals = (Map<String, Object>) report.get("totals");
@SuppressWarnings("unchecked")
Map<String, Object> perLevel = (Map<String, Object>) report.get("entriesPerLevel");
@SuppressWarnings("unchecked")
List<Map<String, Object>> top = (List<Map<String, Object>>) report.get("topMessagePatterns");
@SuppressWarnings("unchecked")
Map<String, Object> anomalies = (Map<String, Object>) report.get("anomalies");
@SuppressWarnings("unchecked")
List<Map<String, Object>> spikes = (List<Map<String, Object>>) anomalies.get("errorSpikes");
@SuppressWarnings("unchecked")
List<String> files = (List<String>) metadata.get("files");
System.out.println("Log File Pattern Analyzer");
System.out.println("=========================");
System.out.println("Files analyzed : " + files.size());
System.out.println("Parsed entries : " + totals.get("parsedEntries"));
System.out.println("Malformed lines: " + totals.get("malformedLines"));
System.out.println("Level filter : " + metadata.get("levelThreshold"));
if (metadata.get("from") != null || metadata.get("to") != null) {
System.out.println("Date range : " + (metadata.get("from") == null ? "-" : metadata.get("from"))
+ " to " + (metadata.get("to") == null ? "-" : metadata.get("to")));
}
System.out.println("\nEntries per level:");
for (String level : LEVELS) {
System.out.println("- " + level + ": " + perLevel.get(level));
}
System.out.println("\nTop patterns:");
if (top.isEmpty()) {
System.out.println("- (none)");
} else {
for (int i = 0; i < top.size(); i++) {
System.out.println((i + 1) + ". " + top.get(i).get("count") + "x " + top.get(i).get("template"));
}
}
System.out.println("\nDetected anomalies:");
if (spikes.isEmpty()) {
System.out.println("- No error spikes detected.");
} else {
for (Map<String, Object> s : spikes) {
double r = ((Number) s.get("errorRate")).doubleValue();
double a = ((Number) s.get("averageErrorRate")).doubleValue();
System.out.println("- " + s.get("windowStart") + ": error_rate="
+ String.format(Locale.ROOT, "%.3f", r) + " avg="
+ String.format(Locale.ROOT, "%.3f", a)
+ " (" + s.get("errorEntries") + "/" + s.get("totalEntries") + ")");
}
}
}
private static String templateize(String message) {
String out = message;
out = out.replaceAll("\\b\\d{4}-\\d{2}-\\d{2}[T ][\\d:.+\\-Z]+\\b", "<TIMESTAMP>");
out = out.replaceAll("\\b\\d{1,3}(?:\\.\\d{1,3}){3}\\b", "<IP>");
out = out.replaceAll("\\b[0-9a-f]{8}-[0-9a-f-]{27,}\\b", "<UUID>");
out = out.replaceAll("(?i)\\b0x[0-9a-f]+\\b", "<HEX>");
out = out.replaceAll("(?i)\\b(id|user|session|req|trace)[=:]?[A-Za-z0-9_-]+\\b", "<ID>");
out = out.replaceAll("\\b\\d+\\b", "<NUM>");
out = out.replaceAll("\\s+", " ").trim();
return out;
}
private static String createSampleLogs() {
StringBuilder sb = new StringBuilder();
Instant start = Instant.parse("2026-01-10T08:00:00Z");
for (int i = 0; i < 240; i++) {
Instant t = start.plusSeconds(i * 60L);
String level = i % 40 == 0 ? "WARN" : "INFO";
sb.append(t).append(" [").append(level).append("] api-gateway - Request completed id=req-")
.append(1000 + i).append(" user=u").append(i % 20).append('\n');
}
Instant spike = Instant.parse("2026-01-10T12:00:00Z");
for (int i = 0; i < 60; i++) {
Instant t = spike.plusSeconds(i * 30L);
sb.append("{\"timestamp\":\"").append(t).append("\",\"level\":\"ERROR\",\"source\":\"payment-service\",\"message\":\"Payment failure for user_id=")
.append(5000 + i).append(" ip=10.0.0.").append(i % 10).append("\"}\n");
}
sb.append("127.0.0.1 - - [10/Jan/2026:13:10:01 +0000] \"GET /health HTTP/1.1\" 200 64\n");
sb.append("Jan 10 14:00:20 host1 scheduler: WARN job id=abc123 delayed by 45s\n");
sb.append("BROKEN LINE WITHOUT FORMAT\n");
return sb.toString();
}
private static final class LogEntry {
final String format;
final Instant timestamp;
final String level;
final String source;
final String message;
LogEntry(String format, Instant timestamp, String level, String source, String message) {
this.format = format;
this.timestamp = timestamp;
this.level = level;
this.source = source;
this.message = message;
}
}
private static final class WindowStat {
int total;
int errors;
}
private static final class JsonWriter {
static String pretty(Object value) {
StringBuilder sb = new StringBuilder();
write(value, sb, true, 0);
return sb.toString();
}
@SuppressWarnings("unchecked")
private static void write(Object value, StringBuilder sb, boolean pretty, int indent) {
if (value == null) {
sb.append("null");
return;
}
if (value instanceof String) {
sb.append('"').append(escape((String) value)).append('"');
return;
}
if (value instanceof Number || value instanceof Boolean) {
sb.append(value);
return;
}
if (value instanceof List<?>) {
List<Object> list = (List<Object>) value;
sb.append('[');
if (!list.isEmpty()) {
if (pretty) sb.append('\n');
for (int i = 0; i < list.size(); i++) {
if (pretty) indent(sb, indent + 1);
write(list.get(i), sb, pretty, indent + 1);
if (i < list.size() - 1) sb.append(',');
if (pretty) sb.append('\n');
}
if (pretty) indent(sb, indent);
}
sb.append(']');
return;
}
if (value instanceof Map<?, ?>) {
Map<String, Object> map = (Map<String, Object>) value;
sb.append('{');
if (!map.isEmpty()) {
if (pretty) sb.append('\n');
int i = 0;
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (pretty) indent(sb, indent + 1);
sb.append('"').append(escape(entry.getKey())).append('"').append(pretty ? ": " : ":");
write(entry.getValue(), sb, pretty, indent + 1);
if (i < map.size() - 1) sb.append(',');
if (pretty) sb.append('\n');
i++;
}
if (pretty) indent(sb, indent);
}
sb.append('}');
return;
}
sb.append('"').append(escape(String.valueOf(value))).append('"');
}
private static String escape(String text) {
return text.replace("\\", "\\\\").replace("\"", "\\\"")
.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t");
}
private static void indent(StringBuilder sb, int level) {
for (int i = 0; i < level; i++) sb.append(" ");
}
}
}