2025-04-25 07:02:49 +00:00
# Logger plugin
## Overview
2026-01-20 08:06:39 +00:00
The Logger plugin (located in `plugins/logs/` ) provides a modular, pluggable logging
system for the application. It records both user and system events in the `log`
table and exposes retrieval utilities plus a built-in UI at `?page=logs` .
The plugin uses the callable dispatcher pattern with `PluginRouteRegistry` for routing
and follows the App API pattern for service access.
## Features
1. **Log entry management**
- PSR-3-style `log()` method with level + context payloads
- Core helper `app_log()` for simplified access with NullLogger fallback
2. **Filtering & pagination**
- Query by scope, user, time range, message text, or specific user IDs
- Pagination-ready result sets with newest-first sorting
3. **User awareness**
- Stores username via joins for auditing
- Captures current user IP via plugin bootstrap
4. **Auto-migration**
- `logs_ensure_tables()` function creates the `log` table on demand
- Called automatically via `logger.system_init` hook
5. **UI integration**
- Adds a "Logs" entry to the top menu
- Provides list/detail views with tabs for user vs system scopes
- Uses callable dispatcher for route handling
2025-04-25 07:02:49 +00:00
## Installation
2026-01-20 08:06:39 +00:00
1. Copy the `logs` folder into the project's `plugins/` directory.
2. Enable the plugin via the admin plugin management interface (stored in `settings` table).
3. The plugin bootstrap automatically:
- Registers the `logs` route prefix with a callable dispatcher
- Sets up the `logs_ensure_tables()` migration function
- Initializes the logger via the `logger.system_init` hook
2025-04-25 07:02:49 +00:00
## Database Schema
The plugin defines the following table (auto-created):
```sql
CREATE TABLE IF NOT EXISTS `log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`scope` SET('user','system') NOT NULL,
`message` VARCHAR(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
CONSTRAINT `log_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
```
2026-01-20 08:06:39 +00:00
## Routing & Dispatcher
The plugin registers its route using `PluginRouteRegistry` :
```php
register_plugin_route_prefix('logs', [
'dispatcher' => function($action, array $context = []) {
require_once PLUGIN_LOGS_PATH . 'controllers/logs.php';
if (function_exists('logs_plugin_handle')) {
return logs_plugin_handle($action, $context);
}
return false;
},
'access' => 'private',
'defaults' => ['action' => 'list'],
'plugin' => 'logs',
]);
```
## Hook + Loader API
Core must fire the initialization hook after the database connection is ready:
2025-04-25 07:02:49 +00:00
```php
do_hook('logger.system_init', ['db' => $db]);
```
2026-01-20 08:06:39 +00:00
The plugin listener:
- calls `logs_ensure_tables()` to create the `log` table if needed
- resolves the current user IP
- exposes `$GLOBALS['logObject']` (`Log` instance) and `$GLOBALS['user_IP']`
When `$logObject` is not available, use `app_log($level, $message, $context)` which falls back to `NullLogger` .
## PHP API
`Log` lives in `plugins/logs/models/Log.php` and receives the database connector.
### Methods
2025-04-25 07:02:49 +00:00
```php
2026-01-20 08:06:39 +00:00
Log::log(string $level, string $message, array $context = []): void
Log::readLog(int $userId, string $scope, int $offset = 0, int $itemsPerPage = 0, array $filters = []): array
2025-04-25 07:02:49 +00:00
```
2026-01-20 08:06:39 +00:00
### Supported log levels
`emergency` , `alert` , `critical` , `error` , `warning` , `notice` , `info` , `debug`
### Supported filters
- `from_time` : `YYYY-MM-DD` lower bound (inclusive)
- `until_time` : `YYYY-MM-DD` upper bound (inclusive)
- `message` : substring match across message text
- `id` : explicit user ID (system scope only)
### Typical usage
2025-04-25 07:02:49 +00:00
```php
2026-01-20 08:06:39 +00:00
app_log('info', 'User updated profile', [
'user_id' => $userId,
'scope' => 'user',
]);
$entries = $logObject->readLog(
$userId,
$scope,
$offset,
$itemsPerPage,
['message' => 'profile']
);
2025-04-25 07:02:49 +00:00
```
2026-01-20 08:06:39 +00:00
## Usage guidelines
1. **When to log**
- User actions, authentication events, configuration changes
- System events, background job outcomes, and security anomalies
2. **Message hygiene**
- Keep messages concise, include essential metadata, avoid sensitive data
3. **Data integrity**
- Validate user input before logging to avoid malformed queries
- Wrap bulk insertions in transactions when necessary
4. **Performance**
- Prefer pagination for large result sets
- Index columns used by custom filters if extending the schema
5. **Retention**
- Schedule archival/log rotation via cron if the table grows quickly
2025-04-25 07:02:49 +00:00
## File Structure
```
2026-01-20 08:06:39 +00:00
plugins/logs/
├─ bootstrap.php # registers route, migration function, hooks & menu
├─ plugin.json # plugin metadata
├─ README.md # this documentation
├─ controllers/
│ └─ logs.php # procedural handler functions for callable dispatcher
2025-04-25 07:02:49 +00:00
├─ models/
2026-01-20 08:06:39 +00:00
│ ├─ Log.php # main Log class
│ └─ LoggerFactory.php # migration + factory
2025-04-25 07:02:49 +00:00
├─ helpers/
2026-01-20 08:06:39 +00:00
│ ├─ logs_view_helper.php
├─ helpers.php # plugin helper wrapper
2025-04-25 07:02:49 +00:00
└─ migrations/
└─ create_log_table.sql
```
2026-01-20 08:06:39 +00:00
## Controller Architecture
The controller uses procedural functions instead of classes:
- `logs_plugin_handle($action, $context)` - main dispatcher function
- `logs_plugin_render_list($logObject, $db, $userId, $validSession, $app_root)` - renders log list with filters and pagination
The callable dispatcher pattern provides:
- Clean separation of concerns
- Access to request context (user_id, db, app_root, valid_session)
- Consistent error handling and layout rendering
## Admin Plugin Check
The plugin provides `logs_ensure_tables()` for the admin plugin management interface:
- **Owned tables:** `log` (will be removed on purge)
- **Referenced tables:** `user` (dependency, not removed)
2025-04-25 07:02:49 +00:00
## Uninstall / Disable
2026-01-20 08:06:39 +00:00
To disable the plugin:
- Use the admin plugin management interface to disable it (updates the `settings` table), or
- Delete the `plugins/logs/` folder entirely
When disabled, the `app_log()` helper automatically falls back to `NullLogger` , so logging calls remain safe and won't cause errors. To remove plugin data, use the admin plugin management interface to purge the `log` table.