View source for 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()##):

%%(hl 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:

%%(hl 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:

%%(hl 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 ====

%%(hl 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:

%%(hl 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.