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()):


PHP
$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:


PHP
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:


PHP
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


PHP
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:


SQL
-- 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.