Skip to main content

agent-census

What's hitting your site, classified by how it behaves -- not just what it claims to be.

Most of the traffic to a typical site isn't people; it's software, and a fair bit of it lies about what it is. agent-census reads your access log and sorts the clients by what they actually do -- whether they pull a page's sub-resources like a browser, walk the site like a crawler, poll a feed on a schedule, or go looking for known-vulnerable paths. Anything claiming to be a known crawler is checked against DNS and published address ranges, so a Googlebot arriving from some random datacentre gets called what it is. What you end up with is your traffic broken down by what each client is for. The User-Agent still counts -- it's just treated as a claim to weigh against behaviour and origin, not a fact to take on trust.

Here's a sample report generated from a real access log.

Install

pipx is recommended:

pipx install agent-census

Use: Analysis

The simplest case is analyzing one or more Apache logs in the default combined format:

agent-census analyze access.log* > census.html

Several rotated logs are pooled into one analysis. You can pass them in any order (a shell glob is fine): each file is peeked and the set is sorted into chronological order before analysis, so a client spanning rotations is treated as one and timing metrics see requests in time order. Plain and .gz files mix freely.

The presets common, combined, and vhost_combined are available via --log-format-preset.

For a custom log format, pass the LogFormat/CustomLog directive string verbatim from your Apache config. Tab separators (\t), quoted fields with spaces, %{...}x SSL variables, and %{...}e environment variables are all handled:

agent-census analyze access.log \
    --log-format '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i" %D'

See "What to log" below for the most important information to gather.

Cloudflare Logpush logs (newline-delimited JSON) are also supported, as another preset:

agent-census analyze cloudflare-logs.json --log-format-preset cloudflare

Options

Use agent-census analyze -h for the full list of analysis options.

robots.txt compliance: Use --robots-file to supply a local file, hostname, or URL:

agent-census analyze access.log --robots-file ./robots.txt

Output format: Output is a self-contained HTML page by default; redirect it with -o, or pass --md for Markdown:

agent-census analyze access.log -o census.html
agent-census analyze access.log --md

Browsable per-client detail: when an HTML report is written to a file (-o), each client is clickable — a click opens its full trace and classification rationale in-page, no separate inspect run needed. The per-client files are written to a directory named after the report (census.htmlcensus.inspect/), which the report links to. This is on by default; --no-inspect-data skips it (and its per-client trace capture):

agent-census analyze access.log -o census.html
# writes census.html and its sibling census.inspect/ directory
agent-census analyze access.log -o census.html --no-inspect-data
# just census.html, no per-client data

The report and its <name>.inspect/ directory travel together — serve or move them as a pair. Because the directory is named after the report:

  • Re-running the same report (e.g. a cron job to census.html) overwrites census.inspect/, pruning files for clients that no longer appear — so disk use tracks the latest run, not the sum of all runs.
  • Keeping history needs nothing special: write to a dated name (census-2024-06-01.html) and each run gets its own …inspect/ directory, leaving earlier ones untouched.
  • Several reports in one folder don't collide — each has its own directory.

Serve the report over HTTP (the in-page view fetches the data files); a report opened as a local file, or one built with --no-inspect-data, still copies a client's id on click, as before.

Host header filtering: --vhost SUBSTRING analyses only the lines served for a matching host:

agent-census analyze access.log --log-format-preset vhost_combined \
    --vhost mnot.net --vhost www.mnot.net

Time window: --since limits the analysis to recent traffic (e.g. 1w, 36h, 90m; units s/m/h/d/w). A log file that falls entirely outside the window is skipped without being read — and the count of skipped files is reported so it's never a silent omission. The window is anchored at the current time by default; for archived logs whose newest entry is itself in the past, add --from-latest to anchor it at the newest timestamp in the logs instead.

agent-census analyze access.log* --since 1w
agent-census analyze archive/access.log.*.gz --since 1w --from-latest

Client identity: Use --identity to change how requests are associated with clients. The default, ip_ua, groups by (IP, User-Agent). Behind a CDN, use forwarded (the left-most X-Forwarded-For); for IP-rotating bots in one range, ip_ua_subnet.

agent-census analyze access.log --identity forwarded

AS lookups: If your logs don't record the AS number, point --mm-asn-db at a MaxMind ASN database to recover it from each client's IP. The database is consulted first (it can be fresher than the log) and is remembered between runs:

agent-census analyze access.log --mm-asn-db ./GeoLite2-ASN.mmdb

Country flags: point --mm-country-db at a MaxMind country (or city) database to show a small flag next to the highest-traffic non-human clients we haven't tied to a specific operator — an unknown scraper or an impersonator, where the origin country adds signal (a verified, IP/rDNS-identified crawler gets none). For a client spanning many IPs the flag is its traffic-majority country; a client with no country above 70% of its traffic shows a neutral 🌐 instead. In HTML the flag carries the country name as a tooltip and each address gets its own flag on expansion. Also remembered between runs:

agent-census analyze access.log --mm-country-db ./GeoLite2-Country.mmdb

A directory of databases: if you keep your .mmdb files in one place (e.g. a geoipupdate target), point --mm-db-dir at it and both databases are picked up automatically — by each file's metadata, so it doesn't matter what they're named or which vendor they're from. An explicit --mm-asn-db / --mm-country-db still wins for that one role.

agent-census analyze access.log --mm-db-dir /usr/share/GeoIP

Remembered settings

Some options are sticky, so you needn't retype them. --log-format / --log-format-preset, --identity, and --robots-file / --robots-url are saved to ~/.config/agent-census/config.json and reused when a later run omits them; the MaxMind database paths (--mm-asn-db / --mm-country-db / --mm-db-dir) are remembered the same way, as noted with their own options above. Passing one updates the saved value, and a run prints a note: naming what it saved and its scope. Use --config PATH to read and write a different settings file (handy for a checked-in, per-project config).

--no-persist keeps a run from writing anything: saved settings (and a --site) are still read and applied, but nothing is stored — for one-off runs, scripts and CI, or trying a setting without disturbing the saved config.

Per-site settings. If you analyse more than one site, --site NAME keeps their settings apart. Each site remembers its own log files, --vhost filter, log format, identity, robots source, and output path (-o, kept separately per command), so after seeding a site you can analyse it by name alone:

# seed the site: its settings, log files, and vhost filter are saved under "blog"
agent-census analyze /var/log/access.log* --site blog \
    --vhost blog.example --robots-file /srv/blog/robots.txt

# later runs need only the name
agent-census analyze --site blog

A site's settings override the global defaults; anything passed on the command line overrides the site (and is remembered under it). Resolution is command line → site → defaults.

What goes per-site vs. global follows what a setting describes:

  • Per-site — settings that describe the site's data: which log files are it, which --vhost lines are it, how they're formatted, and its robots policy. A saved log file that has since rotated away is skipped with a note rather than failing the run, so a site's list can name rotated logs that come and go.
  • Global — settings that describe this machine or account: the Cloudflare API token and the MaxMind database paths. These are the same whatever site you look at, so they always live in the defaults.

The preference-style keys (log format, identity, robots source) can also be set without --site to give a global baseline that any site inherits until it sets its own. The "which data" keys (log files, --vhost) only ever attach to a site — a global default there would silently filter unrelated runs. The output path (-o) is the same: it's remembered only under a named site (and per command, so analyze and inspect don't share a destination), so a run without --site still writes to stdout and never silently overwrites a file.

Use: Inspecting a client

To see why a client was classified the way it was, use inspect. It shows every signal that fired (including the runners-up), the measured features, the robots.txt finding, and the request trace:

agent-census inspect access.log --kind vuln_scanner
agent-census inspect access.log --client 203.0.113.66
agent-census inspect access.log --kind scraper --network aws

--network matches a substring of the origin-network name and composes with --kind, so the two together select a single cell of the cross-tab.

inspect emits Markdown. For the same detail browsable from the report itself, write the HTML report to a file (-o) and click a client (see --inspect-data above).

Most analyze options apply; see agent-census inspect -h for a full list of options.

What to log

The Apache combined format already carries everything the core analysis needs. The common preset drops the User-Agent and the Referer, so prefer combined, or a custom format that includes them.

Required (all present in combined):

  • Client address (%h) -- the identity everything else groups on, and the basis for the network, datacentre, and crawler-verification checks.
  • Timestamp (%t) -- timing regularity, peak request rate, the reported time range, ordering and --since windowing across multiple files, and (with --quiescent-hours) freeing memory mid-run.
  • Request line ("%r") -- the method and path; the most load-bearing field, behind vulnerability probing, feed detection, path coverage, and crawl shape.
  • Status code (%>s) -- the status mix, 404 storms, 304 Not Modified (the has-cache tag), and robots.txt compliance.
  • User-Agent ("%{User-Agent}i") -- browser, bot, and declared-crawler recognition.

Strongly Recommended. The first two are already in combined; the rest aren't in any preset, so add them to a custom LogFormat (quoted) -- they're worth it:

  • Referer ("%{Referer}i", in combined) -- referer-following, which separates crawlers from scrapers and flags fabricated referers.
  • Bytes sent (%b or %B, in combined) -- the bandwidth figures in the report.
  • AS organisation and number ("%{MM_ASORG}e" and "%{MM_ASN}e", MaxMind mod_maxminddb) -- name datacentre clients by their hosting organisation, and recognise datacentres and ASN-listed crawlers by AS number. Much of Networks and hosting leans on these; log both (the number drives recognition, the org names it). Can't log them? --mm-asn-db recovers the AS from a MaxMind database instead (see Options).
  • Content-Type ("%{Content-Type}o") -- the response media type, which sharpens feed-reader detection (an RSS/Atom type, not just a feed-shaped URL).
  • X-Forwarded-For ("%{X-Forwarded-For}i") -- if you're behind a CDN or proxy, for --identity forwarded.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

agent_census-0.3.3.tar.gz (358.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

agent_census-0.3.3-py3-none-any.whl (300.2 kB view details)

Uploaded Python 3

File details

Details for the file agent_census-0.3.3.tar.gz.

File metadata

  • Download URL: agent_census-0.3.3.tar.gz
  • Upload date:
  • Size: 358.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for agent_census-0.3.3.tar.gz
Algorithm Hash digest
SHA256 6659a1064e6a85dd79f47b7a6cd2dd95347e0489743a5b79fe98fdfc99779595
MD5 320334047064b7299cd2ed08934a29a5
BLAKE2b-256 ee200b4f4e187937a47a9763b8c5473d3e0ab3f839eafb0a18eca72bb22fe2c5

See more details on using hashes here.

Provenance

The following attestation bundles were made for agent_census-0.3.3.tar.gz:

Publisher: publish.yml on mnot/agent-census

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file agent_census-0.3.3-py3-none-any.whl.

File metadata

  • Download URL: agent_census-0.3.3-py3-none-any.whl
  • Upload date:
  • Size: 300.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for agent_census-0.3.3-py3-none-any.whl
Algorithm Hash digest
SHA256 2899a6d51e08caafd51c51e5d6eeebb1c8e4f1cf2794907963b304043179c0a5
MD5 4e51702f001bafff294d2f620488ffad
BLAKE2b-256 6a8e7db20b4637433dd179f5815cb68e232aa2d81ef9176ae85e84aaad02d03d

See more details on using hashes here.

Provenance

The following attestation bundles were made for agent_census-0.3.3-py3-none-any.whl:

Publisher: publish.yml on mnot/agent-census

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page