[ {"name": "the Common Log Format example from the Apache docs", "args": ["127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326"], "expect": {"remoteHost": "127.0.0.1", "ident": null, "user": "frank", "at": 971211336, "method": "GET", "path": "/apache_pb.gif", "protocol": "HTTP/1.0", "status": 200, "bytes": 2326, "referer": null, "userAgent": null}}, {"name": "the Combined Log Format example from the Apache docs", "args": ["127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326 \"http://www.example.com/start.html\" \"Mozilla/4.08 [en] (Win98; I ;Nav)\""], "expect": {"remoteHost": "127.0.0.1", "ident": null, "user": "frank", "at": 971211336, "method": "GET", "path": "/apache_pb.gif", "protocol": "HTTP/1.0", "status": 200, "bytes": 2326, "referer": "http://www.example.com/start.html", "userAgent": "Mozilla/4.08 [en] (Win98; I ;Nav)"}}, {"name": "a 304 with no body logs bytes as -, which is null", "args": ["192.168.1.5 - - [28/Sep/2026:09:15:00 +0000] \"GET /index.html HTTP/1.1\" 304 -"], "expect": {"remoteHost": "192.168.1.5", "ident": null, "user": null, "at": 1790586900, "method": "GET", "path": "/index.html", "protocol": "HTTP/1.1", "status": 304, "bytes": null, "referer": null, "userAgent": null}}, {"name": "a request line of - (client timed out) keeps the line with no method, path or protocol", "args": ["10.0.0.1 - - [28/Sep/2026:09:15:00 +0100] \"-\" 408 -"], "expect": {"remoteHost": "10.0.0.1", "ident": null, "user": null, "at": 1790583300, "method": null, "path": null, "protocol": null, "status": 408, "bytes": null, "referer": null, "userAgent": null}}, {"name": "a TLS handshake sent to a plain HTTP port is a malformed request, still counted", "args": ["10.0.0.1 - - [28/Sep/2026:09:15:00 +0000] \"\\x16\\x03\\x01\\x02\\x00\\x01\\x00\\x01\\xfc\\x03\\x03\" 400 226"], "expect": {"remoteHost": "10.0.0.1", "ident": null, "user": null, "at": 1790586900, "method": null, "path": null, "protocol": null, "status": 400, "bytes": 226, "referer": null, "userAgent": null}}, {"name": "an escaped quote inside a field does not end it, and is returned as logged", "args": ["203.0.113.9 - - [28/Sep/2026:23:59:59 +0000] \"POST /api/v1/login?next=%2F HTTP/2.0\" 201 17 \"-\" \"curl \\\"test\\\"/8.4\""], "expect": {"remoteHost": "203.0.113.9", "ident": null, "user": null, "at": 1790639999, "method": "POST", "path": "/api/v1/login?next=%2F", "protocol": "HTTP/2.0", "status": 201, "bytes": 17, "referer": null, "userAgent": "curl \\\"test\\\"/8.4"}}, {"name": "an IPv6 client, an offset that crosses into the previous year, and an extra field after the user agent", "args": ["2001:db8::1 - - [01/Jan/2026:00:30:00 +0100] \"GET / HTTP/1.1\" 200 512 \"https://example.com/\" \"Mozilla/5.0\" 0.004"], "expect": {"remoteHost": "2001:db8::1", "ident": null, "user": null, "at": 1767223800, "method": "GET", "path": "/", "protocol": "HTTP/1.1", "status": 200, "bytes": 512, "referer": "https://example.com/", "userAgent": "Mozilla/5.0"}}, {"name": "a half-hour negative offset", "args": ["10.2.3.4 ident42 alice [31/Dec/1999:23:59:59 -0530] \"DELETE /items/7 HTTP/1.1\" 204 0"], "expect": {"remoteHost": "10.2.3.4", "ident": "ident42", "user": "alice", "at": 946704599, "method": "DELETE", "path": "/items/7", "protocol": "HTTP/1.1", "status": 204, "bytes": 0, "referer": null, "userAgent": null}}, {"name": "a trailing CRLF from the file is ignored", "args": ["127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326\r\n"], "expect": {"remoteHost": "127.0.0.1", "ident": null, "user": "frank", "at": 971211336, "method": "GET", "path": "/apache_pb.gif", "protocol": "HTTP/1.0", "status": 200, "bytes": 2326, "referer": null, "userAgent": null}}, {"name": "an HTTP/0.9 request has no protocol", "args": ["127.0.0.1 - - [29/Feb/2024:12:00:00 +0000] \"GET /\" 200 10"], "expect": {"remoteHost": "127.0.0.1", "ident": null, "user": null, "at": 1709208000, "method": "GET", "path": "/", "protocol": null, "status": 200, "bytes": 10, "referer": null, "userAgent": null}}, {"name": "a lowercase method is not a request line", "args": ["127.0.0.1 - - [29/Feb/2024:12:00:00 +0000] \"get / HTTP/1.1\" 400 0"], "expect": {"remoteHost": "127.0.0.1", "ident": null, "user": null, "at": 1709208000, "method": null, "path": null, "protocol": null, "status": 400, "bytes": 0, "referer": null, "userAgent": null}}, {"name": "29 February in a non-leap year is not a date, so not a log line", "args": ["127.0.0.1 - - [29/Feb/2023:12:00:00 +0000] \"GET / HTTP/1.1\" 200 10"], "expect": null}, {"name": "a leap second is not a Unix time", "args": ["127.0.0.1 - - [31/Dec/2016:23:59:60 +0000] \"GET / HTTP/1.1\" 200 10"], "expect": null}, {"name": "an unknown month is junk", "args": ["127.0.0.1 - - [10/Foo/2000:13:55:36 -0700] \"GET / HTTP/1.1\" 200 10"], "expect": null}, {"name": "a status outside 100 to 599 is junk", "args": ["127.0.0.1 - - [10/Oct/2000:13:55:36 -0700] \"GET / HTTP/1.1\" 999 10"], "expect": null}, {"name": "text after the byte count that is not the Combined fields is junk", "args": ["127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] \"GET /apache_pb.gif HTTP/1.0\" 200 2326 extra"], "expect": null}, {"name": "an unterminated request is junk", "args": ["127.0.0.1 - - [10/Oct/2000:13:55:36 -0700] \"GET / HTTP/1.1 200 10"], "expect": null}, {"name": "a line with no status or bytes is junk", "args": ["127.0.0.1 - - [10/Oct/2000:13:55:36 -0700] \"GET / HTTP/1.1\""], "expect": null}, {"name": "non-ASCII digits in the status are not digits", "args": ["127.0.0.1 - - [10/Oct/2000:13:55:36 -0700] \"GET / HTTP/1.1\" ٢٠٠ 10"], "expect": null}, {"name": "an empty line is null", "args": [""], "expect": null}, {"name": "some other log format is null", "args": ["Sep 28 09:15:00 web1 sshd[123]: Accepted publickey for root"], "expect": null} ]