← back to Rentv 826 Tracker
lib/parse.js
52 lines
'use strict';
// Parser for nginx "combined" log format:
// $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"
// e.g. 1.2.3.4 - boomer [10/Aug/2026:06:53:12 +0000] "GET /826/ HTTP/1.1" 200 1024 "https://ref" "Mozilla/5.0"
const LINE_RE = /^(\S+) \S+ (\S+) \[([^\]]+)\] "([^"]*)" (\d{3}) (\S+) "([^"]*)" "([^"]*)"/;
const MONTHS = { Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5, Jul: 6, Aug: 7, Sep: 8, Oct: 9, Nov: 10, Dec: 11 };
// "10/Aug/2026:06:53:12 +0000" -> epoch ms (UTC-correct via the offset)
function parseNginxTime(s) {
const m = /^(\d{2})\/(\w{3})\/(\d{4}):(\d{2}):(\d{2}):(\d{2}) ([+-]\d{4})$/.exec(s);
if (!m) return null;
const [, dd, mon, yyyy, hh, mi, ss, tz] = m;
const month = MONTHS[mon];
if (month === undefined) return null;
const offMin = (tz[0] === '-' ? -1 : 1) * (parseInt(tz.slice(1, 3), 10) * 60 + parseInt(tz.slice(3, 5), 10));
const asUTC = Date.UTC(+yyyy, month, +dd, +hh, +mi, +ss);
return asUTC - offMin * 60 * 1000;
}
// Returns a normalized hit object, or null if the line is unparseable.
function parseLine(line) {
const m = LINE_RE.exec(line);
if (!m) return null;
const [, remote_addr, remote_user_raw, time_local, request, status, bytes_raw, referer, ua] = m;
const ts = parseNginxTime(time_local);
if (ts == null) return null;
const reqParts = request.split(' ');
const method = reqParts[0] || null;
const path = reqParts[1] || null;
const remote_user = remote_user_raw === '-' ? null : remote_user_raw;
const bytes = bytes_raw === '-' ? 0 : parseInt(bytes_raw, 10) || 0;
return {
ip: remote_addr,
remote_user,
ts,
method,
path,
status: parseInt(status, 10),
bytes,
referer: referer === '-' ? null : referer,
ua: ua === '-' ? null : ua,
};
}
module.exports = { parseLine, parseNginxTime };