The Logger
The Logger
Hazaar has a built-in frontend/backend logging system that provides a consistent interface for multiple logging backends. The logger implements the PSR-3 LoggerInterface standard, making it familiar and interoperable with other PHP logging systems.
Overview
The Hazaar logging system consists of:
- Frontend: A unified interface (
Hazaar\Logger\Frontend) that implements PSR-3 LoggerInterface - Log Facade: A static accessor (
Hazaar\Log) for convenient access to the logger throughout your application - Backends: Multiple storage/output adapters (file, database, syslog, memory, chain)
When logging is enabled, Hazaar itself uses the logger to output information about requests, responses, and internal operations. Your application can use the same logger to track application-specific events, errors, and debugging information.
Configuration
To use the Hazaar\Log facade, you must first enable logging in your application configuration file (e.g., app/configs/app.php).
Minimal Configuration
The simplest configuration just enables logging. This will use the default File backend and write logs to hazaar.log in the application's runtime directory:
return [
'production' => [
'log' => [
'enable' => true,
],
],
];{
"production": {
"log": {
"enable": true
}
}
}[production.log]
enable = trueFull Configuration
For more control over logging behavior, you can specify the backend, log level, message prefix, and backend-specific options:
return [
'production' => [
'log' => [
'enable' => true,
'backend' => 'file',
'level' => 'notice',
'prefix' => '{ip} [{localtime}] {level}',
'options' => [
// Backend-specific options
],
],
],
];{
"production": {
"log": {
"enable": true,
"backend": "file",
"level": "notice",
"prefix": "{ip} [{localtime}] {level}",
"options": {
// Backend-specific options
}
}
}
}[production.log]
enable = true
backend = "file"
level = "notice"
prefix = "{ip} [{localtime}] {level}"
# Backend-specific options can be added as neededConfiguration Options
- enable (bool): Enable or disable logging. Default:
false - backend (string): The backend to use. Options:
file,database,syslog,memory,chain. Default:file - level (string): Minimum log level to record. Default:
error - prefix (string): Prefix template for log messages. Default:
{ip} [{localtime}] {level} - options (array): Backend-specific configuration options
Log Levels
Log levels determine which messages are recorded. Messages are logged if their level is equal to or higher priority than the configured level:
| Level | Priority | Description |
|---|---|---|
emergency | 0 | System is unusable |
alert | 1 | Action must be taken immediately |
critical | 2 | Critical conditions |
error | 3 | Runtime errors |
warning | 4 | Exceptional occurrences that are not errors |
notice | 5 | Normal but significant events |
info | 6 | Interesting events |
debug | 7 | Detailed debug information |
For example, if you set level to warning, only messages with levels emergency, alert, critical, error, and warning will be logged. Messages with notice, info, and debug will be ignored.
Using the Log Facade
While you can instantiate a Hazaar\Logger\Frontend directly, the recommended approach is to use the Hazaar\Log facade, which provides static access to the application-level logger when logging is enabled.
The Log facade supports all PSR-3 log levels:
use Hazaar\Log;
// Emergency: system is unusable
Log::emergency('Database connection pool exhausted');
// Alert: action must be taken immediately
Log::alert('Disk space critically low');
// Critical: critical conditions
Log::critical('Application component unavailable');
// Error: runtime errors that don't require immediate action
Log::error('Failed to process payment for order {orderId}', ['orderId' => 12345]);
// Warning: exceptional occurrences that are not errors
Log::warning('API rate limit at 90%');
// Notice: normal but significant events
Log::notice("User '{username}' logged in", ['username' => 'john_doe']);
// Info: interesting events
Log::info('Email sent to {email}', ['email' => '[email protected]']);
// Debug: detailed debug information
Log::debug('Processing request', ['method' => 'POST', 'uri' => '/api/users']);Using Logs Anywhere
The Log facade can be used anywhere in your application - controllers, models, services, helpers, etc. Here's an example from a controller:
<?php
namespace App\Controller;
use Hazaar\Controller\Action;
use Hazaar\Log;
class Main extends Action
{
public function login(): ?Response
{
if ($this->request->isPost()) {
$username = $this->request->param('username');
$result = $this->session->authenticate($username, $password);
if ($result->success) {
Log::notice("User '{username}' logged in", ['username' => $username]);
return $this->response->redirect('/dashboard');
}
Log::warning("Failed login attempt for user '{username}'", ['username' => $username]);
$this->view->set('error', 'Invalid username or password');
}
$this->view('login');
return $this->response->ok();
}
}Message Interpolation
The logger supports message interpolation, allowing you to embed context values directly in your log messages using curly braces:
Log::info('User {username} updated profile {field}', [
'username' => 'john_doe',
'field' => 'email'
]);
// Output: User john_doe updated profile emailArrays and objects in the context are automatically JSON-encoded:
Log::debug('Request data: {data}', [
'data' => ['action' => 'update', 'id' => 123]
]);
// Output: Request data: {"action":"update","id":123}Automatic Request Context
Hazaar automatically sets the logging context at the beginning of each request with information about the incoming HTTP request. These values are available for interpolation in all log messages throughout the request lifecycle without needing to explicitly pass them:
- {ip}: Client IP address from
$request->getRemoteAddr() - {method}: HTTP method (GET, POST, PUT, DELETE, etc.)
- {path}: Request path/URI
- {protocol}: HTTP protocol version (HTTP/1.1, HTTP/2, etc.)
- {referer}: HTTP referer header (or '-' if not present)
- {useragent}: User agent string (or '-' if not present)
This means you can use these variables in your log messages without explicitly adding them to the context:
Log::notice('User logged in');
// With default prefix outputs: 192.168.1.100 [2026-02-19 14:30:45] NOTICE User logged in
Log::info('Profile updated from {referer}');
// Outputs: 192.168.1.100 [2026-02-19 14:30:45] INFO Profile updated from https://example.com/dashboard
Log::debug('Request received');
// Full context includes: ip, method, path, protocol, referer, useragentAdding Custom Context
You can add additional global context that will be available for all subsequent log messages during the request using addContext():
use Hazaar\Log;
// Add custom context that merges with existing request context
Log::addContext([
'user_id' => $user->id,
'session_id' => $session->id,
]);
// Now these are available in all log messages along with the automatic request context
Log::info('Action performed by user {user_id} in session {session_id}');
// Outputs: 192.168.1.100 [2026-02-19 14:30:45] INFO Action performed by user 123 in session abc...Important: Use addContext() to add to existing context. The setContext() method will overwrite all existing context, including the automatic request context (ip, method, path, etc.), which is rarely what you want:
// ❌ Bad: This will remove all automatic request context
Log::setContext(['user_id' => $user->id]);
// ✅ Good: This adds to the existing context
Log::addContext(['user_id' => $user->id]);Log Message Prefix
The logger supports a customizable prefix that's prepended to all log messages. The default prefix is:
{ip} [{localtime}] {level}This produces output like:
192.168.1.100 [2026-02-19 14:30:45] NOTICE User 'john_doe' logged inYou can customize the prefix in your configuration:
'log' => [
'enable' => true,
'prefix' => '[{localtime}] {level}',
]Built-in prefix variables:
- {ip}: Client IP address (automatically added to context)
- {localtime}: Current timestamp in Y-m-d H:i:s format
- {level}: Log level in uppercase (ERROR, WARNING, etc.)
You can also include any custom context variables in your prefix. For application-level context that should be set once during bootstrap (before request context is added), you can use either method:
use Hazaar\Log;
// In bootstrap.php or early in application initialization
Log::addContext([
'app' => 'MyApp',
'version' => '1.0.0'
]);Then use a prefix like:
'prefix' => '[{app} v{version}] {level}'Logging Backends
File Backend
The File backend is the primary and most commonly used logging backend. It writes log messages to files on disk, with support for separate error logs.
Configuration
'log' => [
'enable' => true,
'backend' => 'file',
'level' => 'notice',
'options' => [
'logfile' => '/var/log/myapp/hazaar.log',
'errfile' => '/var/log/myapp/error.log',
],
]{
"log": {
"enable": true,
"backend": "file",
"level": "notice",
"options": {
"logfile": "/var/log/myapp/hazaar.log",
"errfile": "/var/log/myapp/error.log"
}
}
}[log]
enable = true
backend = "file"
level = "notice"
[log.options]
logfile = "/var/log/myapp/hazaar.log"
errfile = "/var/log/myapp/error.log"Options
- logfile (string): Path to the main log file. Default: Runtime path
hazaar.log - errfile (string): Path to the error log file. Warnings and higher priority messages are written here. Default: Runtime path
error.log
Important Notes
- The directory must be writable by the web server user
- If not specified, logs default to the application's runtime directory
- Error logs (
errfile) receive messages with levelwarningor higher, in addition to the main log file - Useful for separating critical issues from general application logging
- Log files are opened in append mode
Example Configuration
'production' => [
'log' => [
'enable' => true,
'backend' => 'file',
'level' => 'warning', // Only log warnings and errors in production
'options' => [
'logfile' => '/var/log/myapp/app.log',
'errfile' => '/var/log/myapp/error.log',
],
],
],
'development' => [
'include' => 'production',
'log' => [
'level' => 'debug', // Log everything in development
],
],{
"production": {
"log": {
"enable": true,
"backend": "file",
"level": "warning",
"options": {
"logfile": "/var/log/myapp/app.log",
"errfile": "/var/log/myapp/error.log"
}
}
},
"development": {
"include": "production",
"log": {
"level": "debug"
}
}
}[production.log]
enable = true
backend = "file"
level = "warning"
[production.log.options]
logfile = "/var/log/myapp/app.log"
errfile = "/var/log/myapp/error.log"
[development]
include = "production"
[development.log]
level = "debug"Database Backend
The Database backend logs messages to a database table using Hazaar's DBI adapter, supporting multiple database types (PostgreSQL, MySQL, SQLite, etc.).
Configuration
'log' => [
'enable' => true,
'backend' => 'database',
'level' => 'notice',
'options' => [
'type' => 'pgsql',
'host' => 'localhost',
'dbname' => 'myapp_production',
'user' => 'logger',
'password' => 'secret',
'table' => 'application_logs',
'write_ip' => true,
'write_timestamp' => true,
'write_uri' => true,
'columnMap' => [
'message' => 'message',
'level' => 'level',
'remote' => 'client_ip',
'timestamp' => 'logged_at',
'uri' => 'request_uri',
],
],
]Options
- type (string): Database type (sqlite, pgsql, mysql, etc.). Default:
sqlite - host (string): Database server host. Default:
localhost - dbname (string): Database name. Default:
hazaar_{APPLICATION_ENV} - user (string): Database username. Default:
null - password (string): Database password. Default:
null - table (string): Log table name. Default:
log - write_ip (bool): Log client IP address. Default:
true - write_timestamp (bool): Log timestamp. Default:
true - write_uri (bool): Log request URI. Default:
true - columnMap (array): Map log fields to custom column names
Database Schema
PostgreSQL example:
CREATE TABLE application_logs (
id SERIAL PRIMARY KEY,
message TEXT NOT NULL,
level VARCHAR(20) NOT NULL,
client_ip INET,
logged_at TIMESTAMP WITH TIME ZONE NOT NULL,
request_uri TEXT
);
CREATE INDEX idx_logs_level ON application_logs(level);
CREATE INDEX idx_logs_timestamp ON application_logs(logged_at);MySQL example:
CREATE TABLE application_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
message TEXT NOT NULL,
level VARCHAR(20) NOT NULL,
client_ip VARCHAR(45),
logged_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
request_uri TEXT,
INDEX idx_level (level),
INDEX idx_timestamp (logged_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;Important Notes
- Requires a database connection for every log write
- Consider performance impact for high-volume logging
- Use appropriate indexes for efficient log queries
- Consider log rotation/archival strategies for large log tables
- Failed writes will throw an exception and prevent further logging attempts
Syslog Backend
The Syslog backend writes log messages to the system's syslog facility, integrating with system-wide logging infrastructure.
Configuration
'log' => [
'enable' => true,
'backend' => 'syslog',
'level' => 'notice',
'options' => [
'ident' => 'myapp',
'facility' => LOG_USER,
],
]Options
- ident (string): Identifier string prepended to every message. Default:
hazaar - facility (int): Syslog facility constant. Default:
LOG_USER
Available facilities:
LOG_USER- Generic user-level messagesLOG_LOCAL0throughLOG_LOCAL7- Reserved for local useLOG_DAEMON- System daemonsLOG_AUTH- Security/authorization messages- See
openlog()PHP function for all options
Important Notes
- Messages are sent to the system's syslog daemon
- Log messages can be viewed with
journalctl(systemd) or/var/log/syslog(traditional) - Useful for centralized logging across multiple applications
- Integrates with existing system monitoring tools
- No need to manage log files or rotation
- Log levels are automatically mapped to syslog priorities
Example Usage
// With facility LOG_LOCAL0 and ident 'myapp'
Log::error('Database connection failed');View in system logs:
sudo journalctl -t myapp
# or
tail -f /var/log/syslog | grep myappMemory Backend
The Memory backend stores log messages in an array in memory. Primarily useful for testing and development.
Configuration
'log' => [
'enable' => true,
'backend' => 'memory',
'level' => 'debug',
]Options
No configuration options required.
Important Notes
- Logs are lost when the request ends
- Cannot be used to persist logs across requests
- Useful for unit testing and debugging
- Access logs programmatically during runtime
- No performance overhead from I/O operations
Accessing Logs
The Memory backend is typically accessed directly for testing:
use Hazaar\Logger\Backend\Memory;
$backend = new Memory([]);
$logger = new \Hazaar\Logger\Frontend([
'enable' => true,
'backend' => 'memory',
]);
Log::info('Test message');
// Access the backend to read logs
$logs = $backend->read();Chain Backend
The Chain backend allows logging to multiple backends simultaneously, useful for development (console + file) or redundancy (database + file).
Configuration
'log' => [
'enable' => true,
'backend' => 'chain',
'level' => 'notice',
'options' => [
'backends' => ['file', 'syslog'],
],
]Options
- backends (array): Array of backend names to write to. Default:
['memory', 'file']
Important Notes
- Each backend uses default configuration (cannot customize per-backend in chain)
- Log messages are written to all backends in sequence
- If one backend fails, others still receive the message
- Performance impact scales with number of backends
- Consider using for development but single backend for production
Example Multi-Backend Setup
Development configuration logging to both console (syslog) and file:
'development' => [
'log' => [
'enable' => true,
'backend' => 'chain',
'level' => 'debug',
'options' => [
'backends' => ['file', 'syslog'],
],
],
],Best Practices
Log Level Guidelines
- emergency/alert: Reserve for catastrophic failures requiring immediate attention
- critical: System component failures that don't bring down the entire system
- error: Errors that are logged but handled (failed API calls, validation errors)
- warning: Unexpected situations that don't prevent operation (deprecated API usage)
- notice: Significant business events (user registration, login, important actions)
- info: General informational messages (cron job completion, cache cleared)
- debug: Detailed technical information for troubleshooting (query details, variable states)
Performance Considerations
- Set appropriate log levels in production (typically
warningornotice) - Avoid logging in tight loops without level checks
- Use File backend for highest performance
- Consider log volume when using Database backend
- Implement log rotation for File backend
Security Considerations
- Never log sensitive information (passwords, tokens, credit card numbers)
- Be careful with interpolation - sanitize user input before logging
- Restrict log file permissions appropriately
- Consider separate logs for security-related events
- Implement log retention policies for compliance
Development vs Production
Development configuration:
'development' => [
'log' => [
'enable' => true,
'backend' => 'file',
'level' => 'debug', // Log everything
],
],Production configuration:
'production' => [
'log' => [
'enable' => true,
'backend' => 'file',
'level' => 'warning', // Only warnings and errors
'options' => [
'logfile' => '/var/log/myapp/app.log',
'errfile' => '/var/log/myapp/error.log',
],
],
],Conclusion
The Hazaar logging system provides a flexible, PSR-3 compliant logging solution that integrates seamlessly with your application. By using the Hazaar\Log facade, you can add comprehensive logging throughout your application with minimal code, while the pluggable backend system allows you to adapt the logging infrastructure to your specific needs.