A Java 17 command-line application that parses a .txt log file line-by-line,
classifies each line as APM, application, or request, aggregates
statistics, and writes three JSON output files.
- Java 17+
- Maven 3.8+
mvn clean packageThis produces a self-contained, runnable JAR at target/logparser.jar.
java -jar target/logparser.jar --file input.txtThe app writes three files in the current directory:
| File | Aggregation |
|---|---|
apm.json |
Per metric: minimum, median, average, max |
application.json |
Count per severity level (e.g. ERROR, INFO, DEBUG, WARNING) |
request.json |
Per URL: response times (min, 95_percentile, max) and 2XX/4XX/5XX counts |
All three files are always created. If no log of a given type was seen the
file contains {}.
Each line is a series of key=value (or key="quoted value") pairs. The
factory dispatches each line to the first parser that recognizes it:
| Type | Required keys | Extracted fields |
|---|---|---|
| APM | metric= and value= |
metric (string), value (numeric) |
| Request | request_url= and response_time_ms= |
request_url, response_time_ms (int), response_status (int) |
| Application | level= |
level (e.g. ERROR, INFO, DEBUG, WARNING) |
Malformed or unrecognized lines are silently ignored.
- Median with even count: average of the two middle values.
- P95: sort response times ascending; take the value at index
ceil(0.95 * n) - 1. - Status code categorization:
200–299 → 2XX,400–499 → 4XX,500–599 → 5XX. Other codes are not categorized; the three buckets are always emitted (even when 0).
A worked example lives in samples/:
- Input:
samples/sample-input.txt - Expected output:
samples/expected/apm.json,samples/expected/application.json,samples/expected/request.json
To reproduce:
java -jar target/logparser.jar --file samples/sample-input.txtConcrete output produced from samples/sample-input.txt:
apm.json — grouped by metric name with min/median/average/max
{
"cpu_usage_percent" : {
"minimum" : 65.0,
"median" : 68.5,
"average" : 68.5,
"max" : 72.0
},
"memory_usage_percent" : {
"minimum" : 80.0,
"median" : 80.0,
"average" : 80.0,
"max" : 80.0
}
}application.json — counts per severity level
{
"DEBUG" : 1,
"ERROR" : 2,
"INFO" : 3,
"WARNING" : 1
}request.json — per URL response time stats and 2XX/4XX/5XX counts (always all three keys)
{
"/api/status" : {
"response_times" : {
"min" : 100,
"95_percentile" : 300,
"max" : 300
},
"status_codes" : {
"2XX" : 3,
"4XX" : 0,
"5XX" : 1
}
},
"/api/users" : {
"response_times" : {
"min" : 80,
"95_percentile" : 80,
"max" : 80
},
"status_codes" : {
"2XX" : 0,
"4XX" : 1,
"5XX" : 0
}
}
}- Strategy pattern — each
LogLineParserimplementation handles one log type and returnsnullfor any line it can't fully parse. - Factory pattern —
LogLineParserFactory.getParser(line)returns the matching parser, ornullif no parser recognizes the line. Order matters (APM, Request, Application) so a request line that also containslevel=is routed correctly. - Aggregators keep state per log type (
ApmAggregator,AppLogAggregator,RequestAggregator) and produce the final maps thatLogFileWriter(Jackson,INDENT_OUTPUT) serializes to JSON.
src/main/java/com/logparser/
├── Main.java (CLI: --file <path>)
├── LogParserApp.java (orchestrator)
├── parser/
│ ├── LogLineParser.java (Strategy interface + shared key=value regex)
│ ├── ApmLogParser.java
│ ├── ApplicationLogParser.java
│ ├── RequestLogParser.java
│ └── LogLineParserFactory.java (Factory)
├── model/
│ ├── LogEntry.java (marker interface)
│ ├── ApmEntry.java
│ ├── AppLogEntry.java
│ └── RequestEntry.java
├── aggregator/
│ ├── ApmAggregator.java
│ ├── AppLogAggregator.java
│ └── RequestAggregator.java
└── writer/
└── LogFileWriter.java
mvn testJUnit 5 covers each parser (valid + malformed inputs) and each aggregator (sample inputs against expected outputs). 18 tests total.