Access Log Statistics
Hard · Parsing
Web servers like nginx and Apache write one line per request in the Combined Log Format, and many setups add the response time at the end:
127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /index.html HTTP/1.1" 200 2326 "http://example.com/" "Mozilla/5.0" 12.5
The parts are: client address, ident, user (three tokens without spaces, often -), the time in [...], the request line in quotes, the 3-digit status, the response size (digits or -), the referer in quotes, the user agent in quotes, and the response time in milliseconds (digits with an optional decimal part). Parts are separated by single spaces. Inside a quoted part, \" is an escaped quote (the way Apache writes it); a bare " would end the part, so it cannot appear inside.
Write analyzeLogs(lines) that returns { total, byStatus, topPaths, p95, errors }:
- A line is malformed if it does not match the format, the status is not between 100 and 599, or the request line is not METHOD PATH PROTOCOL (method in capital letters, protocol like HTTP/1.1 or HTTP/2, single spaces). errors lists the 1-based line numbers of malformed lines. Blank lines (only whitespace) are skipped: they are neither counted nor errors.
- total: the number of valid lines.
- byStatus: counts per status class such as "2xx" or "4xx". Only include classes that appear.
- topPaths: the 3 most requested paths as { path, count } (fewer if there are fewer paths), most requests first. Ties are sorted by path in plain string order (<). The query string (from ?) is not part of the path.
- p95: the 95th percentile response time using the nearest-rank method: sort the times ascending and take the value at 1-based position ceil(0.95 × n). With 20 values that is the 19th smallest, with 10 values the largest. It is null when there are no valid lines. (Tip: compute the rank as Math.ceil(95 * n / 100) to avoid floating point surprises.)