mrnaeem4/ci3-request-analysis

CodeIgniter 3 hook that intercepts incoming HTTP requests and writes structured JSON request logs (JSONL) to the CI3 logs directory with daily rotation and gzip compression.

Maintainers

Package info

github.com/mrnaeem4/ci3-request-analysis

pkg:composer/mrnaeem4/ci3-request-analysis

Transparency log

Statistics

Installs: 5

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.0 2026-09-02 09:14 UTC

This package is auto-updated.

Last update: 2026-09-02 09:15:09 UTC


README

CodeIgniter 3 hook that intercepts incoming HTTP requests, extracts structured metadata (headers, body, files, tenant, source IP, etc.), and writes one JSON Line per request directly to the CI3 application/logs directory — with sensitive-field redaction, body truncation, IP whitelisting, and daily log rotation with gzip compression.

Local logging only. No external server, no queue, no Guzzle. This is the CI3 counterpart of ci4-request-analysis.

Features

  • Attachable per-request via a single CI3 hook (post_controller_constructor), not a global middleware.
  • Writes directly to application/logs/analysis.log as JSON Lines (JSONL).
  • Daily rotation: a file from a previous day is renamed and gzip-compressed automatically on the next write.
  • Retention pruning: compressed logs older than REQUEST_LOG_RETENTION_DAYS (default 30) are deleted.
  • Configurable sensitive-field redaction (default: password, nik, Api-Key, no_telp).
  • Raw body truncation at 3 MB (configurable) with ... [truncated] suffix.
  • File upload metadata captured without binary content (name, size, MIME, extension, SHA-256 hash, double-extension detection).
  • IP/CIDR whitelist to skip private/internal traffic.
  • PHP 7.4+.

Requirements

  • PHP 7.4+
  • CodeIgniter 3.x
  • Composer (for autoloading), with a writable application/logs directory

Installation

composer require mrnaeem4/ci3-request-analysis

Custom vendor-dir: If your composer.json sets "vendor-dir" (e.g. application/third_party/vendor), the package installs there. That is fine — just point $config['composer_autoload'] at that exact vendor/autoload.php path. CodeIgniter's TRUE shortcut only looks at application/vendor/ and the project root.

If you do not use Composer, add a PSR-4 autoloader mapping MrNaeem\Ci3RequestAnalysis\ to the src/ directory, or require the three classes manually:

require_once __DIR__ . '/vendor/mrnaeem4/ci3-request-analysis/src/Config/RequestLog.php';
require_once __DIR__ . '/vendor/mrnaeem4/ci3-request-analysis/src/Services/RequestLogService.php';
require_once __DIR__ . '/vendor/mrnaeem4/ci3-request-analysis/src/Hooks/RequestLogHook.php';

Configuration

1. Enable hooks + Composer autoload

In application/config/config.php:

$config['enable_hooks']      = TRUE;
$config['composer_autoload'] = TRUE; // or absolute path to vendor/autoload.php

2. Register the hook

Important: CI3's array hook format (class/function/filename/ filepath) cannot resolve Composer namespaced classes — CI_Hooks::_run_hook() checks class_exists($class, false) (no autoload) and then require_onces the filepath/filename, which fails for vendor files. Use a closure instead (CI3 supports callables natively):

In application/config/hooks.php:

$hook['post_controller_constructor'] = function () {
    (new \MrNaeem\Ci3RequestAnalysis\Hooks\RequestLogHook())->before();
};

3. Load the .env file

CI3 does not parse a .env file by itself, and the config values are read with getenv(). Without loading the file, REQUEST_LOG_ENABLED is empty and the hook silently does nothing. Pick one:

Option A — vlucas/phpdotenv (recommended):

composer require vlucas/phpdotenv

Load it in index.php (the front controller) before requiring CodeIgniter.php:

require_once FCPATH . 'vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createUnsafeImmutable(FCPATH);
$dotenv->safeLoad();

Option B — Apache SetEnv:

# .htaccess
SetEnv REQUEST_LOG_ENABLED true
SetEnv REQUEST_LOG_REDACT_FIELDS "password,nik,Cookie,Api-Key,no_telp"

Option C — Nginx fastcgi_param:

location ~ \.php$ {
    fastcgi_param REQUEST_LOG_ENABLED true;
    # ...
}

4. Set environment variables

Values of the .env file (or server env):

REQUEST_LOG_ENABLED          = true
REQUEST_LOG_DIR              = ""          # empty → application/logs
REQUEST_LOG_FILE             = "analysis.log"
REQUEST_LOG_REDACT_FIELDS    = "password,nik,Api-Key,no_telp"
REQUEST_LOG_MAX_BODY_SIZE    = 3145728
REQUEST_LOG_WHITELIST_IPS    = "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1"
REQUEST_LOG_TRUNCATE_SUFFIX  = "... [truncated]"
REQUEST_LOG_RETENTION_DAYS   = 30
REQUEST_LOG_METHODS          = ""          # empty = log all; e.g. "POST,PUT,DELETE"
REQUEST_LOG_INCLUDE_PATHS    = ""          # empty = log all; regex, e.g. "^/api/"
REQUEST_LOG_EXCLUDE_PATHS    = ""          # regex, e.g. "^/assets/,^/health$"

Config reference

Variable Default Description
REQUEST_LOG_ENABLED false Master switch for the hook.
REQUEST_LOG_DIR '' (→ application/logs) Directory for the log file.
REQUEST_LOG_FILE analysis.log Log file name (single file, rotated daily).
REQUEST_LOG_REDACT_FIELDS password,nik,Api-Key,no_telp Comma-separated sensitive fields (case-insensitive).
REQUEST_LOG_MAX_BODY_SIZE 3145728 (3 MB) raw_body truncation length (bytes).
REQUEST_LOG_WHITELIST_IPS RFC1918 + localhost CIDR ranges to skip.
REQUEST_LOG_TRUNCATE_SUFFIX ... [truncated] Appended when the body is truncated.
REQUEST_LOG_RETENTION_DAYS 30 Days of compressed logs kept before pruning.
REQUEST_LOG_METHODS '' (all) Comma-separated HTTP methods to log (e.g. POST,PUT).
REQUEST_LOG_INCLUDE_PATHS '' (all) Regex allow-list for request paths (comma-separated).
REQUEST_LOG_EXCLUDE_PATHS '' (none) Regex deny-list for request paths (comma-separated).

Path & method filtering

By default every request (including GET /) is logged. Restrict it:

# Only mutating requests, only under /api/, never static assets or health checks
REQUEST_LOG_METHODS       = "POST,PUT,PATCH,DELETE"
REQUEST_LOG_INCLUDE_PATHS = "^/api/"
REQUEST_LOG_EXCLUDE_PATHS = "^/api/assets/,^/api/health$"

Patterns are regex matched against the request path (e.g. /api/upload). includePaths is evaluated first; a request must match at least one include pattern when that list is non-empty. excludePaths wins afterwards.

Log payload

Each line in analysis.log is a JSON object (envelope + log_data):

{
  "log_data": {
    "timestamp": "2026-08-31T02:15:04+00:00",
    "domain": "app.example.com",
    "path": "/api/profile/update",
    "method": "POST",
    "srcip": "203.0.113.10",
    "user_agent": "Mozilla/5.0 ...",
    "query_string": "page=1",
    "headers": { "Content-Type": "application/json", ... },
    "raw_body": "{\"name\":\"User\",\"email\":\"user@example.com\",\"password\":\"***REDACTED***\"}",
    "file_count": 1,
    "file_names": ["shell.php.jpg"],
    "file_metadata": [
      {
        "original_name": "shell.php.jpg",
        "size": 20480,
        "mime_type": "image/jpeg",
        "extension": "jpg",
        "hash": "3c98...",
        "has_double_extension": true
      }
    ]
  },
  "retry_count": 0,
  "last_attempt": null,
  "created_at": "2026-08-31T02:15:04+00:00"
}

How it works

Request → CI3 Hook (post_controller_constructor)
  ├─ enabled? ──no──► done
  ├─ IP whitelisted? ──yes──► done
  ├─ method/path filter passes? ──no──► done
  ├─ collect: headers, body, files, tenant, srcip...
  │    ├─ redact sensitive fields (headers + body)
  │    ├─ truncate body at max size
  │    └─ extract file metadata (no binary)
  ├─ rotate if the active log is from a previous day (rename + gzip + prune)
  └─ append one JSONL line to application/logs/analysis.log

The write is a single append with LOCK_EX, so it does not block the request and is safe for concurrent PHP-FPM workers.

Security

CI3 places index.php in the same directory as application/, so by default everything under application/ — including application/logs/analysis.log, which contains request headers and bodies — is reachable directly from the browser (http://host/application/logs/analysis.log). Apply at least one of the layers below; using all three is recommended.

1. Keep application/ out of the web root (recommended)

Move the front controller so the public web root only contains index.php, .htaccess and assets:

project/
├── application/          ← not web-accessible
├── system/
└── public/               ← document root
    ├── index.php
    └── .htaccess

Then fix the paths in public/index.php (a copy of the original, with $system_path/$application_folder updated to ../system and ../application) and set $config['base_url'] accordingly in application/config/config.php.

2. Redirect the log directory outside the web root

Even with the default layout, point logs somewhere the browser cannot reach:

REQUEST_LOG_DIR = "/var/log/myapp/request-analysis"

The hook falls back to application/logs only when REQUEST_LOG_DIR is empty.

3. Deny access with the web server

Apache — ship an .htaccess inside the log directory (and the application/ directory) with:

<IfModule mod_authz_core.c>
    Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
    Order deny,allow
    Deny from all
</IfModule>

The sample app already includes sample/application/logs/.htaccess and sample/application/uploads/.htaccess. If AllowOverride is disabled, deny the paths in the vhost instead:

<Directory "/path/to/app/application/logs">
    Require all denied
</Directory>

Nginx:

location ~ ^/(application|system)/ {
    deny all;
}
location ^~ /application/logs/ {
    deny all;
}

Laravel-style check — as a last line of defense, application/logs/.htaccess also rejects *.log / *.gz matches even if directory-level rules are ignored.

Notes

  • By default every request is logged (including GET /). Use REQUEST_LOG_METHODS / REQUEST_LOG_INCLUDE_PATHS / REQUEST_LOG_EXCLUDE_PATHS to narrow it down — see Path & method filtering above.
  • Binary file content is never stored; only metadata is captured.
  • Redaction applies to both request headers (e.g. Cookie) and the body.
  • application/logs should not be publicly accessible.

Sample app

A minimal CI3 application fragment lives under sample/:

  • sample/application/config/config.php — hooks + Composer autoload enabled
  • sample/application/config/hooks.php — hook registration
  • sample/application/controllers/Home.phppost + upload demo endpoints
  • sample/application/views/home/index.php — test forms

Copy the relevant files into an existing CI3 application and start your server.

Changelog

See CHANGELOG.md.

License

MIT