This page is for a developer who has just cloned the repository. It explains what every folder is, how a log line and a beacon hit travel through the code, how the panel is put together, and which files to open for the most common changes. It stays high level on purpose; the linked pages and SPEC.md go deeper.
Loghound is open source and contributions are welcome. To become a contributor to this or any other Opensolr open source project, write to support@opensolr.com and tell us what you would like to work on.
Loghound is plain PHP 8.1+ with no Composer packages, no npm and no build step. It is really four programs sharing one library in src/:
The ingest daemon
bin/loghound-tail runs all the time, reads your access logs as they grow, and writes one document per request into the hits index.
The scorer
bin/loghound-score runs every minute, closes finished visits, merges beacon data, scores each visit and writes it into the sessions index.
The beacon
public/b.js runs in visitors' browsers and posts to public/collect.php, which only stages the data locally.
The panel
public/index.php serves the web interface; every card on a page loads its own data from the two indexes.
Everything Loghound learns lives in two Opensolr indexes it provisions for you; small working state (file positions, open visits, staged beacon rows) lives in a local SQLite file.
bin/The command-line programs: the daemon, the scorer, retention, the schema check and the setup wizard.
src/All PHP classes, namespace Loghound\, loaded by src/autoload.php.
public/The web root: the panel, the beacon collector, the beacon script, CSS, JavaScript and vendored assets.
solr/The configuration of the two indexes, hits/conf and sessions/conf.
config/loghound.example.php, the documented template. The live loghound.php is written by setup and never committed.
install/install.sh, uninstall.sh, the systemd units and timers, web server and PHP-FPM examples.
tests/A dependency-free test runner, the tests, anonymised log fixtures and the secret scanner.
tools/panel-preview.php, for looking at the panel with demo data while developing.
var/Runtime state, never committed: the SQLite database, status files, setup jobs.
docs/Operator and developer documentation in Markdown: install, beacon, detection, panel, schema, privacy, security, attacks.
SPEC.mdThe binding technical specification. Field names, paths and signatures in it are contracts.
README.md, CONTRIBUTING.md, CHANGELOG.md, LICENSEThe front page, the rules for contributions, the release history (Keep a Changelog, semantic versions), and the MIT licence.
bin/loghound-tailTail, parse, enrich, group into visits, and write batches into the hits index.
loghound-scoreClose idle visits, merge beacon data, apply the scoring rules, write visits into the sessions index, rebuild daily rollups.
loghound-retentionDelete what is older than the retention period, keep the indexes within the plan, purge old local state.
loghound-schemaCompare the live schemas with this release; --apply uploads them.
loghound-setupThe interactive setup wizard; it shares its logic with the browser installer.
Every command and option is on the command reference; the services and timers on running it.
src/Top-level classes
Load, validate and save the configuration (every default is in Config::defaults()); the local SQLite store for file positions, open visits, caches, staged beacon rows and rate limits.
Follow files across rotation; compile an Apache or nginx log format; find the logs and their format; turn a line into a normalised request.
Group requests into visits; skip traffic that is never recorded; the operator's own attack patterns; where a visit really came from.
The only way code talks to Solr, with its query safety checks; the Opensolr account API; the Opensolr request log; query shapes; plan quota and size-based retention.
Validates beacon data and merges it into visits.
Escaping, safe paths, CSRF and authentication; the panel's read cache; asset versions; safe CSV export; the failure report.
Subfolders
src/Enrich/— adds context to a request:Geo(country and timezone),Asn(network, netname, reverse DNS),Ua(browser and declared bots).src/Score/— detection:Signalsextracts the evidence,Rulesholds the weighted rules and the verdict,Attacksspots hostile requests.src/Panel/— the web interface: one controller class per view (Overview,Bots,Fingerprints,Sessions,Seo...), plusLayout(routes, navigation, page chrome),Gateway(the panel's only route to Solr),Query,Facets,Sorting,Pagingand the long-runningJobs.src/Setup/— installing: the browserInstaller, detection and requirement checks, index provisioning and schema sync, resumable jobs, reset and teardown.src/Auth/— panel sign-in extras: two-factor codes, the QR code for the authenticator, “stay signed in”.src/Live/— the live view, reading logs as they are written without touching ingest.src/Geo/Countries.phpandsrc/Beacon/Doc.php— the one country table, and the one description of the beacon shown wherever it is documented.
A log line, into the hits index
Tailreads the new line and remembers its position inState.LogFormatandParserturn it into a request with named fields; lines that do not parse are sampled to a file.Enrich\Geo,Enrich\AsnandScore\Attacksadd context;ExclusionsandAttackPatternsapply the operator's rules.Sessionizerattaches the request to a visit (same network and browser, 30 minutes of inactivity closes it).Solr::addDocs()writes the batch into the hits index, afterQuotaconfirms there is room.
A visit, into the sessions index
loghound-scorefinds visits that went idle.Beacon::mergeIntoSession()adds what the browser reported.Score\Signalsgathers the evidence andScore\Rulescomputes the score, the verdict and its reasons.- The visit document, and the daily rollups, are written into the sessions index.
A beacon hit
b.jsin the visitor's browser posts tocollect.php.- The collector checks the signed token, size, origin and allowed hosts, rate-limits, stages the row in SQLite, and always answers 204. It never touches Solr.
- The next scorer run merges the staged row into its visit.
A panel page
public/index.phpsets security headers, loads the configuration, sends first-time visitors to the installer, and checks sign-in and CSRF.- The view named in
?v=is looked up inLayout::routes(); its controller draws the page frame without querying Solr. - Each card then asks the server for its own data (
?api=); the controller answers throughGateway,CacheandSolr, so one slow card never blocks the others. - On the browser side,
public/assets/js/app.jsloads the matching module frompublic/assets/js/views/, which fills its cards withloadCard()fromcore.js.
- The configuration is a PHP array in
config/loghound.php, mode 0640, written byConfig::save(). Its sections are documented inconfig/loghound.example.phpand on settings. - Secrets — the Opensolr API key, the beacon secret, the IP salt, the panel password hash and the two-factor secret — live in that file; sign-in state lives in files under
var/with mode 0600. None of them is ever committed. - Working state is
var/state.db(SQLite) and a few status files, also undervar/.
solr/hits/conf holds one document per request; solr/sessions/conf one per visit plus daily rollups. Every field is explained on the Solr schema. The schema rules are strict: fields are not stored unless needed, string fields carry no norms, there is no catch-all field, and an undeclared field is silently ignored.
- Change
solr/hits/conf/schema.xmlorsolr/sessions/conf/schema.xml. - Run
bin/loghound-schema --checkto see what differs from the live index. - Run
bin/loghound-schema --applyto upload it. Operators do the same after upgrading.
src/Score/Signals.php (the evidence) and src/Score/Rules.php (weights, reasons); raise Rules::RULE_VERSION when weights change; tests in tests/test_scoring.php for the rule firing and not firing; docs/DETECTION.md.
A controller in src/Panel/, registered in Layout::routes() and Layout::nav(); a module in public/assets/js/views/ imported by app.js; docs/PANEL.md.
The schema in solr/*/conf/schema.xml; fill it in Parser.php (hits) or bin/loghound-score (sessions); allow it in src/Panel/Query.php; SPEC.md and docs/SCHEMA.md. Renaming or removing a field is discussed first.
public/b.js, public/collect.php, src/Beacon.php, src/Beacon/Doc.php; docs/BEACON.md. A test fails if the documented options fall behind the script.
src/LogDetect.php, plus a fixture in tests/fixtures/.
Config::defaults() (and validate()), config/loghound.example.php, the settings page in src/Panel/Settings.php, and its documentation.
A script in bin/ following the existing ones, the bin list in composer.json, and for a schedule a unit and timer in install/ plus install.sh.
public/assets/css/panel.css, the only file where colours are defined.
- A test file is
tests/test_<name>.phpreturning named closures; the runner finds it by itself. - Versions are the headings in
CHANGELOG.mdand the matching git tags.
They are written in CONTRIBUTING.md; in short:
- No dependencies: no Composer packages, no npm, no build step, no CDN.
- Security first: escape at output, user text to Solr only as bound parameters, allowlisted field names, safe paths, CSRF on every change, no secrets in code.
- Honesty: an unknown number is absent, never zero; every verdict carries its reasons; limitations are documented.
- Style: PSR-12, four spaces, single quotes, strict types, and the comment rules in
CONTRIBUTING.md. - One change per pull request, with its documentation and a changelog entry.
Become a contributor
Read SPEC.md and CONTRIBUTING.md in the repository, then write to support@opensolr.com to ask for contributor access to Loghound or any other Opensolr open source project. Issues and pull requests on GitHub are open to everyone.