Server Log Analyzer

Analyze Nginx and Apache access logs in your browser. Top IPs, paths, status codes, a traffic chart, an error rate, live filters and CSV export.

🌐 Español

Drop your Nginx/Apache access log here (common or combined format — a multi-hundred-MB file is read in streamed chunks, never all at once)

🔒 Private by design: everything runs locally in your browser and never uploaded to any server.

An access log answers questions you cannot answer any other way. Which address is hammering you. Which path is throwing 500s. When the error rate started climbing, and whether it started before or after the deploy. The obstacle is never the questions, it is that the answers live in a few hundred megabytes of text where every line looks identical to the last.

The usual escape routes are a shell one-liner you rebuild from memory every time, or a site that wants you to upload the file. This page is the third option: the whole thing runs in the tab you already have open.

One anchored regex, two NCSA layouts

Access log lines look like they could be split on spaces, right up until they cannot. The request field is a quoted string containing spaces of its own, and a user agent string can carry a dozen. So each line is matched against a single anchored pattern that treats every quoted group as one field and allows an escaped quote inside it, because real user agents occasionally quote themselves.

The trailing referer and user agent pair is optional in that pattern, which is how format detection falls out for free: if the pair matched, the line was Combined; if it did not, the line was Common. The strip above the summary reports both counts, so a file that mixes them tells you so. Lines that match neither shape are counted and reported as skipped rather than silently dropped, and a handful of those is normal in any rotated log.

Timestamps are read from the bracketed field, offset and all. A line stamped 13:55:36 with a -0700 offset is stored as 20:55:36 UTC, which is why everything on the page is labeled UTC rather than converted into the timezone of whoever happens to be reading. A timestamp that cannot be parsed does not throw the line away: its address, path and status still count, and only the time-based features skip it.

Chunked reading and the quarter-million ceiling

The file is pulled through the browser’s own stream reader in bounded chunks and decoded incrementally, so the whole thing is never held as one enormous string. Lines that straddle a chunk boundary are stitched back together rather than being split in two, and the progress percentage counts bytes consumed rather than lines, since nobody knows the line count in advance.

Reading therefore scales further than the interactive part does. Filtering, ranking and charting all need every parsed entry alive as a real object, and that is where a genuinely huge log runs out of room. Parsing stops at 250,000 entries, cancels the rest of the stream instead of draining it, and puts a warning above the summary saying the analysis covers only that many. It is disclosed rather than silent, and the honest fix for a bigger file is to narrow it with the command line first and bring the smaller result here.

From a dropped file to an exported CSV

  1. Drop the file onto the box, or press Choose a file. The picker is filtered to .log and .txt, though a dropped file is judged on its contents rather than its name.
  2. Watch the Reading & parsing percentage. Nothing appears until the read finishes, then the meta strip, the summary panels and the table arrive together.
  3. Read the three tiles first: Entries (filtered), the 4xx/5xx error rate to one decimal place, and the Date range in UTC.
  4. Narrow with the four controls. Status takes a minimum and a maximum, then there is an IP address box, a Path contains box, and From and To datetime pickers. Everything above and below updates as you type.
  5. Press Export filtered CSV for exactly the rows currently surviving, or Clear filters to go back to the whole file. Load another file starts over.

Four filters that all narrow the same view

The filters combine with AND, and each behaves slightly differently on purpose.

Status is an inclusive range, so 500 to 599 gives you server errors and 400 to 499 gives you client ones. The address box is an exact match rather than a substring, because a partial address match is almost always an accident: typing 203.0.113 finds nothing, and it has to be the whole address. The path box is the opposite, a case-insensitive substring, so “api” catches /api/login and /API/v2/users alike.

The date pickers deserve a warning. They are ordinary datetime-local inputs, which means the value they hand over is interpreted in your own device’s timezone, while every timestamp shown on the page has already been normalized to UTC. If your machine is not on UTC, the From and To values are offset from the labels in the table by exactly your local offset, so shift them accordingly when you are pinning down an incident window. Entries whose timestamp never parsed are excluded whenever a date bound is set, since there is no way to know whether they fall inside it.

Bucket sizes, UTC labels and the traffic line

The chart spans the earliest to the latest timestamp in whatever is currently filtered, split into equal windows chosen from that span: minute windows for three hours or less, hour windows up to three days, day windows beyond that. That keeps the chart between roughly two dozen and a couple of hundred points instead of two bars or five thousand.

Empty windows are drawn as real zero points rather than being skipped, so a gap in traffic looks like a gap instead of a straight line between two busy periods. The first and last bucket labels are printed under the axis in UTC as a date and a time.

Below it sit four top-ten panels. Top IPs and Top paths are plain descending counts. Status codes groups everything into 2xx, 3xx, 4xx, 5xx and other. Top user agents can only be populated from Combined lines, so a pure Common-format log shows a note there saying exactly that rather than an empty box.

The nine columns you get back

The export writes address, timestamp, method, path, protocol, status, bytes, referer and user agent, with RFC 4180 quoting, and downloads as your file’s own name with a -filtered suffix. Timestamps come out as full ISO 8601 strings in UTC, which sort correctly as text and import cleanly, except on a line whose own timestamp never parsed, which exports the raw bracketed text instead. Referer and user agent are empty strings for Common-format rows, because those fields never existed on those lines.

Once the CSV is off this page you can keep working on it: open it in the CSV Viewer to sort and search, or take a request you first spotted in a browser capture and cross-check it against what the server recorded using the HAR File Viewer. When the pattern turns out to need a rate limit, a cache rule or a redirect, the Nginx and .htaccess Config Generator writes the block for you. The rest of the toolkit lives on the developer tools hub and in the developer tools guide.

See it in action

Screenshot of the Server Log Analyzer tool with a dropzone for an Nginx or Apache access log in common or combined format
Server Log Analyzer mid-process: a dropzone for an Nginx or Apache access log in common or combined format.
Screenshot of the Server Log Analyzer result screen showing the log read and summarised, with the parse result reported back for the lines it recognised
The finished result: the log read and summarised, with the parse result reported back for the lines it recognised. The download link is a local blob URL — the file never leaves your device.

Frequently asked questions

Which log layouts will it read, and which will it refuse?

The two NCSA layouts Nginx and Apache write out of the box. Common is host, identd, authuser, bracketed timestamp, quoted request, status and byte count. Combined is all of that plus a quoted referer and a quoted user agent. Both can sit in the same file, since the format of each line is decided by whether the trailing quoted pair is present. A JSON log format, IIS W3C Extended, and CloudFront or S3 access logs use different field layouts and are not parsed.

My file has thousands of lines but nothing loaded. What happened?

If not a single line matches either layout, the tool says so and names the number of lines it looked at, rather than showing you an empty dashboard. That message almost always means a custom log format directive on the server, an application log rather than an access log, or a file that is really a different format under a .log name. An empty file gets its own separate message.

Does the byte count column mean what I think it does?

It is the response body size the server recorded for that request, and it excludes headers. A literal dash, which servers write when there was no body to send, is stored as zero rather than skipped, so a 304 with no content sits at zero in the table and contributes zero to nothing else. The column is not summed anywhere in the summary panels.

How many rows does the table actually render at once?

Only the slice that fits in the scroll viewport, plus spacer rows above and below to keep the scrollbar the right length. That is what lets a quarter of a million parsed entries scroll smoothly instead of putting a quarter of a million table rows in the document. The row you are looking at is real, live data, not a sample.

Can I compare two log files side by side?

Not in one view. Loading another file replaces the current analysis entirely, since every summary panel and the chart are computed from one parsed entry list. The practical workaround is to export each file's filtered rows to CSV and compare those, which also gives you a permanent record of the slice you were looking at.

Do the summary panels reflect my filters or the whole file?

Your filters, always. The entry count, the error rate, the date range, the traffic chart and all four top-ten panels are recomputed from whatever is currently passing, so narrowing to one address genuinely shows you that address's own busiest paths and its own error rate. The counter next to the export button spells out how many of the total entries survive, which is the number to sanity check before you read anything else.

Related tools