Logging Modes
Here's a complete operational answer based on your current config.Your effective configuration
With
'strictness' => 'monitor-only', the following overrides kick in (everything else is default):| Detector | State | Source |
|---|---|---|
dns_verification_enabled |
OFF | monitor-only override |
dynamic_ip_ranges_enabled |
OFF | monitor-only override |
rate_limit_enabled |
OFF | monitor-only override |
dnsbl_enabled |
OFF | monitor-only override |
enable_fingerprinting |
OFF | monitor-only override |
enable_behavioral_analysis |
OFF | monitor-only override |
enable_client_hints_validation |
OFF | monitor-only override |
enable_agentic_detection |
OFF | monitor-only override |
enable_head_request_detection |
OFF | monitor-only override |
enable_asset_scraping_detection |
OFF | monitor-only override |
block_unverified_ai |
OFF | monitor-only override |
strict_search_engines |
OFF | monitor-only override |
enable_ai_crawler_control |
ON | default |
logging |
ON | your config |
verbose |
OFF | default |
BotDetector + BlacklistDetector are always on (they're the core detection path). Everything else is gated behind config flags that monitor-only disables.What WILL appear in the log right now
The logging rule (
BadBehaviour::log_and_return()):$should_log = $result->is_enforced_block()
|| $result->is_monitored()
|| ($result->code === ResultCode::ALLOWED && $this->config->verbose);
So your log will contain exactly three categories:
1. ENFORCED blocks (real 403s served)
Only two triggers in monitor-only mode — these are the “obvious attack” exceptions that the library always blocks regardless of strictness:
| Trigger | Result code | Why always enforced |
|---|---|---|
| Empty / missing User-Agent (< 5 chars) | blocked.malicious_ua |
No UA = never a legitimate browser, mobile app, or HTTP client |
Raw unencoded <script> / javascript: in URI |
blocked.attack_pattern |
Browsers always percent-encode per RFC 3986; only scanners/manual cURL produce raw payloads |
2. MONITORED detections (would-have-blocked, but suppressed)
Anything that
BotDetector or BlacklistDetector would block under normal/strict mode is logged here but NOT enforced. This is the entire point of monitor-only.What you'll see as MONITORED in the
status_code column: status_code |
What triggered it |
|---|---|
monitored.bot |
Recognized bot (Applebot, Baidu, Sogou, GPTBot, AhrefsBot, …) that would block in normal mode |
monitored.attack_pattern |
URL-encoded attack payloads (Tier 1/2 patterns from BlacklistDetector) |
monitored.malicious_ua |
UA matches MALICIOUS_* prefix/substring/regex but UA isn't empty (i.e., bot-like UA but with content) |
The
original_code column will show what WOULD have applied (blocked.bot, blocked.malicious_ua, etc.).3. ALLOWED requests — NOT logged ❌
verbose defaults to false, so a request that doesn't match any detector produces no log row. This is correct for monitor-only (you don't want a 99% noise table), but means you have no visibility into legitimate traffic patterns.What WON'T appear (and why)
| You'd want to know about… | But won't see because… |
|---|---|
| Normal browser traffic | verbose=false skips ALLOWED |
| Rotating UAs / botnet behavior | enable_behavioral_analysis=false in monitor-only |
| Bad JA3/HTTP2 fingerprints | enable_fingerprinting=false |
| IP rate exceeded (e.g., scraper at 100 req/min) | rate_limit_enabled=false |
| DNSBL hits (Spamhaus, etc.) | dnsbl_enabled=false |
| Headless Chrome / spoofed User-Agent via Client Hints | enable_client_hints_validation=false |
| HEAD flood (enumeration probes) | enable_head_request_detection=false |
| AI agents (think-then-fetch pattern) | enable_agentic_detection=false |
| Image/PDF scraping without Referer | enable_asset_scraping_detection=false |
| DNS-verified spoofers | dns_verification_enabled=false (also makes dynamic IP ranges inert) |
| Cloudflare/CDN probe mismatches | dynamic_ip_ranges_enabled=false |
These detectors literally don't run, so they can't log anything.
Three concrete ways to log more
Option A — Log EVERY request (recommended for first 1–2 weeks)
Add
'verbose' => true. This is the lowest-risk way to see everything happening on your site:return [
'preset' => 'minimal',
'strictness' => 'monitor-only',
'logging' => true,
'verbose' => true, // ← ADD THIS
];
Effect: Every request (allowed or blocked) gets logged. The table fills up — typically 50–500 MB/day for a small site. That's the tradeoff.
What you get: Complete traffic visibility. You can answer:
- “How many requests per IP per hour?”
- “Which UAs are hitting me?”
- “What's the country distribution?”
- “Are there requests with weird header patterns?”
What you DON'T get: The detectors above (behavioral, fingerprinting, rate limits) still don't run.
Option B — Add specific experimental detectors
Keep monitor-only mode but enable the specific detectors you care about:
return [
'preset' => 'minimal',
'strictness' => 'monitor-only',
'logging' => true,
'verbose' => true,
// Pick what you want to see in logs:
'rate_limit_enabled' => true, // See 429s
'enable_behavioral_analysis' => true, // See botnet patterns
'enable_fingerprinting' => true, // See JA3/HTTP2 hits
'enable_head_request_detection' => true, // See HEAD enumeration
'enable_asset_scraping_detection' => true, // See image/PDF scraping
'enable_client_hints_validation' => true, // See spoofed Chrome UAs
'enable_agentic_detection' => true, // See AI agent patterns
];
Effect: Those detectors run, log their findings as MONITORED, but don't block anything. You see exactly what would happen at
normal or strict strictness without breaking real users. Option C — Switch to normal strictness
return [
'preset' => 'minimal',
'strictness' => 'normal', // ← CHANGE THIS
'logging' => true,
'verbose' => true,
];
Effect: Now you get the production baseline:
- DNS verification ON (catches bot spoofers, logs the verified/unverified decision)
- Rate limiting ON (logs 429s as ENFORCED)
- Dynamic IP ranges ON (catches CDN edge probes)
- Experimental detectors still OFF
Plus monitor-only's
maybe_demote_to_monitored step no longer triggers (because is_monitor_only_effective() returns false), so anything that matches a detector will actually be blocked. This is the right move when you're done evaluating and ready to enforce.SQL queries to verify what's being logged
After running for a day with your current config:
-- 1. Sanity check: is anything being logged at all?
SELECT COUNT(*) AS total_rows
FROM bad_behaviour
WHERE date >= CURDATE();
-- Expected: dozens to thousands depending on traffic
-- 2. What's the enforcement split?
SELECT enforcement_action, COUNT(*) AS n
FROM bad_behaviour
WHERE date >= CURDATE()
GROUP BY enforcement_action;
-- Expected (monitor-only + verbose=false):
-- enforced ~0-20 (only empty UA + raw XSS)
-- monitored ~50-5000 (would-have-blocked bots + attacks)
-- allowed 0 (skipped without verbose)
-- 3. Which bots are being seen?
SELECT status_code, COUNT(*) AS n
FROM bad_behaviour
WHERE enforcement_action = 'monitored'
AND date >= CURDATE()
GROUP BY status_code
ORDER BY n DESC;
-- 4. Top offenders (IPs with most would-have-blocked hits)
SELECT ip, COUNT(*) AS monitored_hits, MIN(date) AS first_seen
FROM bad_behaviour
WHERE enforcement_action = 'monitored'
AND date >= NOW() - INTERVAL 7 DAY
GROUP BY ip
ORDER BY monitored_hits DESC
LIMIT 20;
-- 5. Did anything actually get a 403? (Should be empty UA + raw XSS only)
SELECT ip, status_code, status_message, date
FROM bad_behaviour
WHERE enforcement_action = 'enforced'
ORDER BY date DESC
LIMIT 50;
-- 6. Bot category breakdown (good for "are AI crawlers hitting me?")
SELECT
JSON_UNQUOTE(JSON_EXTRACT(metadata, '$.bot_category')) AS bot_category,
status_code,
COUNT(*) AS n
FROM bad_behaviour
WHERE date >= CURDATE()
AND JSON_EXTRACT(metadata, '$.bot_category') IS NOT NULL
GROUP BY bot_category, status_code
ORDER BY n DESC;
My recommendation: progressive rollout
| Phase | Config | Duration | Goal |
|---|---|---|---|
| 1. Discovery | monitor-only + verbose=true |
1–2 weeks | See what's actually hitting your site — traffic patterns, UAs, IP distribution |
| 2. Detection evaluation | monitor-only + verbose=true + enable_*_detection flags |
1–2 weeks | See what each detector catches without blocking. Identify false positives in your traffic. |
| 3. Soft enforcement | strictness=normal + verbose=false |
1–2 weeks | Real DNS verification + rate limiting goes live. Experiment detectors stay off unless you found evidence you need them. |
| 4. Full enforcement | strictness=strict + chosen detectors |
ongoing | Switch to strict only after you've tuned the FP rate at normal strictness. |
Phase 1 is what you're missing right now. Add
'verbose' => true and re-run bin/diagnose.php to confirm the change took effect. After a week you'll have real data to decide whether Phase 2, 3, or stay-at-1 makes sense for your traffic profile.TL;DR
| Question | Answer |
|---|---|
| Will I see allowed traffic? | No — verbose=false (default). |
| Will I see “would-have-blocked” detections? | Yes — that's the whole point of monitor-only. |
| Will anything actually get a 403? | Only empty UA and raw unencoded XSS — these are the “obvious attack” exceptions. |
| How do I see all traffic? | Add 'verbose' => true. |
| How do I see behavioral/fingerprint/rate-limit hits without blocking? | Set 'strictness' => 'monitor-only' + the specific enable_* flags to true. |
| How do I start blocking for real? | Switch 'strictness' to 'normal' (soft) or 'strict' (aggressive). |
Your current config is correct for the “don't break anything, just observe” phase — but you're observing a deliberately narrow slice of traffic.
verbose=true is the smallest change with the biggest visibility gain.