The Logger class provides file-based application logging with level filtering, log rotation, and PSR-3-inspired {placeholder} message interpolation. A single shared instance is used by both the framework's error handler and your application code — so PHP errors, uncaught exceptions, and your own log calls all land in the same file.
- Class:
JiFramework\Core\Logger\Logger - Access:
$app->logger
Features at a glance:
- 8 log levels — DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY
- Level filtering — entries below the configured threshold are silently discarded
- Automatic log rotation — keeps a configurable number of archived log files
{placeholder}interpolation — substitutes context values into message strings- Graceful failure — unwritable paths trigger
E_USER_WARNINGand disable the logger rather than crashing the application - Shared with
ErrorHandler— PHP errors and uncaught exceptions are logged automatically
$app = new App();
$app->logger->info('User {name} logged in', ['name' => 'Alice']);
$app->logger->error('Payment failed for order {id}', ['id' => 1042]);
$app->logger->debug('Cache miss for key {key}', ['key' => 'user:42']);
Logger settings are defined in your jiconfig.php file.
// jiconfig.php
return [
'log_enabled' => true,
'log_level' => 'DEBUG', // minimum level to write
'log_file_name' => 'app.log',
'log_max_file_size' => 5242880, // 5 MB in bytes
'log_max_files' => 20, // number of archived files to keep
// 'log_file_path' => '/custom/path/logs/', // defaults to storage/Logs/
];
log_enabled— (bool) Enable or disable all logging. Whenfalsethe Logger constructs successfully but all write calls are no-ops. Default:true.log_level— (string) Minimum level to write. Entries below this threshold are silently discarded. Default:DEBUG.log_file_name— (string) Name of the active log file. Default:app.log.log_max_file_size— (int) Maximum file size in bytes before rotation is triggered. Default:5242880(5 MB).log_max_files— (int) Number of archived (rotated) files to keep alongside the active log. Default:20.log_file_path— (string, optional) Absolute path to the log directory including trailing separator. Defaults tostorage/Logs/inside the project root.
Eight levels are available in ascending order of severity. Only entries at or above the configured log_level threshold are written to disk — everything below is discarded without file I/O.
DEBUG— Detailed diagnostic information. Use during development to trace execution flow.INFO— Normal application events: user logins, successful operations, background job completions.NOTICE— Normal but noteworthy events that may warrant attention.WARNING— Unexpected situations that do not stop execution but should be reviewed.ERROR— Runtime errors that require attention. The operation failed but the application continues.CRITICAL— Critical conditions: a component is unavailable, unexpected exceptions in core paths.ALERT— Action must be taken immediately (e.g. database down, payment gateway unreachable).EMERGENCY— System is unusable.
// jiconfig.php: log_level = 'WARNING'
// Only WARNING and above will be written:
$app->logger->debug('Cache miss'); // discarded
$app->logger->info('User logged in'); // discarded
$app->logger->notice('Slow query'); // discarded
$app->logger->warning('Disk low'); // written
$app->logger->error('DB timeout'); // written
$app->logger->critical('Cache down');// written
In production it is recommended to set log_level to WARNING or ERROR to avoid filling disk with debug noise. Use DEBUG during development.
log(string $level, string $message, array $context = []): void
The core logging method. All convenience methods delegate to this. Use it directly when the level is determined at runtime.
$level— (string) Log level name (case-insensitive). Unknown values fall back toDEBUG.$message— (string) Log message. May contain{placeholder}tokens.$context— (array, optional) Key-value pairs substituted into the message placeholders.
$logger = $app->logger;
$logger->log('INFO', 'Application started');
$logger->log('WARNING', 'Retrying request {n} of {max}', ['n' => 2, 'max' => 3]);
$logger->log('ERROR', 'Query failed: {error}', ['error' => $e->getMessage()]);
// Level determined at runtime
$level = $someCondition ? 'WARNING' : 'INFO';
$logger->log($level, 'Status: {status}', ['status' => $status]);
Each written entry follows the format:
[2026-03-03 12:00:00] [WARNING] Retrying request 2 of 3
Each of the eight log levels has a dedicated shorthand method. All accept the same signature: a message string and an optional context array.
debug(string $message, array $context = []): void
info(string $message, array $context = []): void
notice(string $message, array $context = []): void
warning(string $message, array $context = []): void
error(string $message, array $context = []): void
critical(string $message, array $context = []): void
alert(string $message, array $context = []): void
emergency(string $message, array $context = []): void
$log = $app->logger;
// Development — verbose tracing
$log->debug('Rendering template {tpl}', ['tpl' => 'home.php']);
// Normal business events
$log->info('User {id} registered', ['id' => $user['id']]);
$log->notice('Config key {key} is deprecated', ['key' => 'old_key']);
// Problems
$log->warning('Rate limit approaching for IP {ip}', ['ip' => $ip]);
$log->error('Email delivery failed for {to}', ['to' => $email]);
// Severe
$log->critical('Primary DB unreachable, switching to replica');
$log->alert('Payment gateway down — all transactions failing');
$log->emergency('Disk full, application cannot write files');
Place {key} tokens anywhere in the message string. The second argument is an associative array of replacements. This keeps log messages readable without string concatenation.
$app->logger->info(
'User {name} (id={id}) logged in from {ip}',
['name' => 'Alice', 'id' => 42, 'ip' => '192.168.1.1']
);
// [2026-03-03 12:00:00] [INFO] User Alice (id=42) logged in from 192.168.1.1
Type handling:
- string / int / float — substituted as-is.
- bool —
truebecomes"true",falsebecomes"false". - null — becomes
"null". - array / object — JSON-encoded.
$app->logger->debug('Flags: active={active}, deleted={deleted}, meta={meta}', [
'active' => true, // "true"
'deleted' => false, // "false"
'meta' => ['role' => 'admin'], // {"role":"admin"}
]);
// Flags: active=true, deleted=false, meta={"role":"admin"}
Unmatched tokens are left in the string unchanged. Context keys with no corresponding {token} in the message are silently ignored.
When the active log file reaches log_max_file_size bytes, it is automatically rotated before the next write. No external cron or tool is required.
How rotation works:
- The current
app.logis renamed toapp.log.0. - Existing archives are shifted up:
.0becomes.1,.1becomes.2, and so on. - Archives beyond the
log_max_fileslimit are deleted. - A fresh empty
app.logis opened for subsequent writes.
// With log_max_files = 3, after several rotations:
storage/Logs/
app.log ← active (current request writes here)
app.log.0 ← most recent archive
app.log.1
app.log.2 ← oldest archive (deleted on next rotation)
Configuration:
log_max_file_size— (int) Size threshold in bytes. Default:5242880(5 MB).log_max_files— (int) Number of archived files to retain. Default:20. Set to1to keep only the most recent archive.
setLogFile(string $logFilePath): void
Closes the current log file handle and opens a new one at the given path. Useful for switching log files mid-request, such as writing request-specific or module-specific logs.
$logFilePath— (string) Absolute path to the new log file. The directory is created automatically if it does not exist.
$log = $app->logger;
$log->info('Written to app.log');
// Switch to a module-specific file
$log->setLogFile(STORAGE_PATH . 'Logs/payments.log');
$log->info('Payment processed for order {id}', ['id' => $orderId]);
// Switch back
$log->setLogFile(STORAGE_PATH . 'Logs/app.log');
$log->info('Back to main log');
If logging is disabled (log_enabled = false), setLogFile() is a no-op. Log rotation and the log_max_file_size / log_max_files settings apply to the new file just as they do to the default one.
The Logger instance is created eagerly inside new App() and shared with ErrorHandler. This means all PHP errors, uncaught exceptions, and fatal shutdown errors are automatically written to the same log file as your application-level calls — with no extra setup.
$app = new App();
// From this point:
// - All PHP E_WARNING, E_NOTICE, etc. → logged as ERROR
// - All uncaught exceptions → logged as ERROR with full stack trace
// - Fatal E_ERROR / E_PARSE → logged via shutdown handler
// - Your own calls → $app->logger->info(...) etc.
// All in the same storage/Logs/app.log
A typical log file in development will look like:
[2026-03-03 12:00:01] [INFO] User 42 logged in
[2026-03-03 12:00:05] [WARNING] Rate limit approaching for IP 203.0.113.5
[2026-03-03 12:00:09] [ERROR] Unhandled Exception: Division by zero
Exception: DivisionByZeroError
Message: Division by zero
File: /app/pages/calc.php (Line 18)
Stack trace:
#0 ...
Graceful failure — if the log directory cannot be created or the log file cannot be opened, the Logger emits an E_USER_WARNING and disables itself. The application continues running; log calls become silent no-ops. This prevents a missing log directory from taking down the entire application.