mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 08:09:18 +02:00
Integrate Monolog: replace four logging systems with single PSR-3 factory
- Add monolog/monolog dependency (^3.10)
- Create app/Logger.php central factory with channels: app, admin, error, audit
- Each channel gets RotatingFileHandler (30-day retention) with pass-through LineFormatter
preserving existing JSON format contracts
- Rewrite AppLogger as thin facade delegating to Logger::get('app')
- Rewrite ErrorHandler::log() to delegate to Logger::get('error')
- Rewrite AdminLogger file output to delegate to Logger::get('admin'), keep DB writes
- Add Monolog file shadow to Audit via Logger::get('audit') (Option A per monolog-plan)
- Log level controlled by LOG_LEVEL env var (defaults: DEBUG in cli-server, WARNING otherwise)
- Graceful NullHandler fallback when log directory is not writable
- Update SystemController LOG_FILES: remove php_error, add app/admin/error/audit
- JSON app logs parsed to readable one-liners in the log viewer
- Remove nginx config tab (parametres + fragment + template + css)
- Friendly empty-state message when app log files don't exist yet (notYet)
- PHP tail fallback when exec() unavailable
- All 228 PHPUnit tests pass, no call sites changed
This commit is contained in:
141
docs/monolog-plan.md
Normal file
141
docs/monolog-plan.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# XAMXAM — Monolog Integration Plan
|
||||
|
||||
## Goal
|
||||
|
||||
Replace the three separate logging systems (`AppLogger`, `AdminLogger`, `ErrorHandler`, `Audit`) with a single
|
||||
Monolog-based logger, PSR-3 compliant, without changing any call sites in the first pass.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
composer require monolog/monolog
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Understand the current landscape
|
||||
|
||||
Four logging systems exist. Map them before touching anything:
|
||||
|
||||
| Class | What it logs | Output | Call sites |
|
||||
|---|---|---|---|
|
||||
| `AppLogger` | App-level errors, warnings | File (JSON lines) | Scattered across controllers |
|
||||
| `AdminLogger` | Admin actions, audit trail | File + DB | Admin controllers |
|
||||
| `ErrorHandler` | PHP errors, exceptions | File (JSON lines) | Registered globally in boot |
|
||||
| `Audit` | Data mutations (create/edit/delete) | DB table | DB layer, controllers |
|
||||
|
||||
Before writing any code, grep the codebase for every call site of each class and note the method signatures.
|
||||
The goal is to know exactly what the new unified interface must support before designing it.
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Create a central `Logger` factory
|
||||
|
||||
Create `app/Logger.php` — a single factory/registry that holds named Monolog channel instances.
|
||||
Do not replace any existing class yet. Just build the foundation.
|
||||
|
||||
```php
|
||||
// Channels to create:
|
||||
// - 'app' → replaces AppLogger
|
||||
// - 'admin' → replaces AdminLogger
|
||||
// - 'error' → replaces ErrorHandler logging
|
||||
// - 'audit' → replaces Audit (DB writes stay, but structured through Monolog)
|
||||
```
|
||||
|
||||
Each channel gets:
|
||||
- A `RotatingFileHandler` writing to `storage/logs/{channel}.log`, keeping 30 days
|
||||
- A `JsonFormatter` so log lines stay JSON (preserving the existing format contract)
|
||||
- Log level set from an environment variable (`LOG_LEVEL`, defaulting to `WARNING` in production, `DEBUG` in dev)
|
||||
|
||||
The factory must be a simple static registry (`Logger::get('app')`) so existing call sites can be migrated
|
||||
one file at a time without passing instances around.
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Replace `AppLogger`
|
||||
|
||||
- Rewrite `AppLogger` as a thin wrapper that delegates to `Logger::get('app')`
|
||||
- Keep the existing public method signatures identical — no call sites change in this step
|
||||
- Run the app, verify log output appears in `storage/logs/app.log`
|
||||
- Delete the old file-writing implementation inside `AppLogger`, keep the class as a facade for now
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Replace `ErrorHandler` logging
|
||||
|
||||
- In `ErrorHandler`, replace the internal `log()` method to delegate to `Logger::get('error')`
|
||||
- Monolog's `ErrorHandler` integration can optionally replace the manual `set_error_handler` /
|
||||
`set_exception_handler` registration — evaluate whether to adopt that or keep the custom handler
|
||||
and just swap the write path
|
||||
- Verify that fatal errors and uncaught exceptions still produce log entries
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Replace `AdminLogger`
|
||||
|
||||
This is the most complex because `AdminLogger` writes to both a file and the DB.
|
||||
|
||||
- File path → delegate to `Logger::get('admin')` with a `RotatingFileHandler`
|
||||
- DB writes → keep as-is for now inside `AdminLogger`, or add a custom Monolog `Handler` that
|
||||
writes to the DB table. A custom handler is cleaner but optional in this pass.
|
||||
- Keep public method signatures identical
|
||||
|
||||
---
|
||||
|
||||
## Step 6 — Replace `Audit`
|
||||
|
||||
`Audit` is DB-only (no file output). Two options:
|
||||
|
||||
- **Option A (simple):** Keep `Audit` as-is, add a Monolog `Logger::get('audit')` that shadows
|
||||
writes to a file for debuggability, call both from `Audit` methods
|
||||
- **Option B (clean):** Write a custom Monolog `AuditHandler` that writes to the DB table,
|
||||
replace `Audit` entirely
|
||||
|
||||
Option A is lower risk for this pass. Option B is the right long-term shape.
|
||||
Recommend Option A now, Option B as a follow-up.
|
||||
|
||||
---
|
||||
|
||||
## Step 7 — Collapse the facades
|
||||
|
||||
Once all four classes delegate to Monolog internally, the facades (`AppLogger`, `AdminLogger`, etc.)
|
||||
are just indirection. This step is optional in this pass but sets up the cleanup:
|
||||
|
||||
- Identify call sites that use `AppLogger::warning(...)` style static calls
|
||||
- Decide whether to keep the facades permanently (low churn, acceptable) or migrate call sites
|
||||
to `Logger::get('app')->warning(...)` directly (cleaner, more churn)
|
||||
- A middle path: have the facades implement `Psr\Log\LoggerInterface` explicitly, which makes
|
||||
them swappable in tests
|
||||
|
||||
---
|
||||
|
||||
## Step 8 — Add context standardisation
|
||||
|
||||
One of the main wins of Monolog over the current setup is structured context. Once the plumbing works,
|
||||
add processors to inject consistent fields into every log entry:
|
||||
|
||||
- `WebProcessor` — adds URL, IP, HTTP method to every request log automatically
|
||||
- A custom processor for `request_id` — generate a UUID per request in `App::boot()` and attach
|
||||
it to all channels so log entries from one request can be correlated across channels
|
||||
|
||||
---
|
||||
|
||||
## What NOT to do in this pass
|
||||
|
||||
- Do not change any call site outside the four logger classes
|
||||
- Do not change log file paths or formats yet (other tooling may depend on them)
|
||||
- Do not add Slack/email handlers yet — get the foundation right first
|
||||
- Do not touch `Audit`'s DB schema
|
||||
|
||||
---
|
||||
|
||||
## Definition of done
|
||||
|
||||
- `composer require monolog/monolog` is the only `composer.json` change
|
||||
- All four logging systems write through Monolog internally
|
||||
- Existing log file locations and JSON format are preserved
|
||||
- No call site outside the four logger classes has changed
|
||||
- `AppLogger`, `AdminLogger`, `ErrorHandler`, `Audit` still exist and work as before from the outside
|
||||
- A single `LOG_LEVEL` environment variable controls verbosity across all channels
|
||||
Reference in New Issue
Block a user