PHP Magic Constants for Maintainable Logging Systems
PHP magic constants provide automatic context about code execution location, enabling logging systems that track method calls, file locations, and class hierarchies without manual instrumentation. Combined with Monolog and PSR-3 logging standards, these constants create maintainable logging architectures that scale with application complexity.
Understanding PHP Magic Constants
PHP provides eight magic constants that automatically resolve to contextual values at compile time. Unlike regular constants, these values change based on their location in the code, making them invaluable for debugging and logging systems.
Complete Magic Constants Reference
Each magic constant serves specific debugging and logging purposes:
<?php
// Basic demonstration of all PHP magic constants
class ExampleService
{
public function demonstrateMagicConstants(): array
{
return [
'__FILE__' => __FILE__,
'__DIR__' => __DIR__,
'__LINE__' => __LINE__,
'__FUNCTION__' => __FUNCTION__,
'__CLASS__' => __CLASS__,
'__METHOD__' => __METHOD__,
'__NAMESPACE__' => __NAMESPACE__,
'__TRAIT__' => null, // Only available inside traits
];
}
}
trait ExampleTrait
{
public function getTraitInfo(): string
{
return __TRAIT__; // Returns the trait name
}
}
// Usage examples
$service = new ExampleService();
$constants = $service->demonstrateMagicConstants();
foreach ($constants as $constant => $value) {
if ($value !== null) {
echo "{$constant}: {$value}" . PHP_EOL;
}
}
__NAMESPACE__ only makes sense once a file actually declares a namespace, so it's
worth showing separately:
<?php
namespace App\Services;
class NamespaceExample
{
public function getNamespace(): string
{
return __NAMESPACE__; // Returns 'App\Services'
}
}
| Constant | Returns | Primary Use Case |
|---|---|---|
__FILE__ |
Full file path | File-based error tracking |
__DIR__ |
Directory path | Configuration and asset loading |
__LINE__ |
Current line number | Precise error location |
__FUNCTION__ |
Function name | Function-level logging |
__CLASS__ |
Class name | Class-based log categorisation |
__METHOD__ |
Class::method | Method execution tracking |
__NAMESPACE__ |
Current namespace | Module-based logging |
__TRAIT__ |
Trait name | Trait-specific debugging |
Monolog and PSR-3 Foundation
Monolog 3.x provides the de facto logging implementation for PHP applications, fully implementing the PSR-3 Logger Interface. The latest version requires PHP 8.1+ and offers enhanced performance and type safety.
Modern Installation and Setup
Install Monolog 3.x with proper version constraints using Composer:
{
"name": "lts/php-magic-constants-maintainable-logging",
"description": "PHP Magic Constants for Maintainable Logging Systems",
"type": "project",
"require": {
"php": "^8.1",
"monolog/monolog": "^3.0",
"psr/log": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"phpstan/phpstan": "^1.10",
"friendsofphp/php-cs-fixer": "^3.0"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true
},
"minimum-stability": "stable",
"prefer-stable": true
}
The enhanced logger demonstrates magic constants integration with Monolog's processor system:
<?php
declare(strict_types=1);
namespace App\Logging;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Processor\IntrospectionProcessor;
use Monolog\Processor\PsrLogMessageProcessor;
use Monolog\Formatter\JsonFormatter;
use Psr\Log\LoggerInterface;
/**
* Enhanced logger that uses PHP magic constants for contextual logging
*/
class EnhancedLogger
{
private LoggerInterface $logger;
public function __construct(string $channel = 'app')
{
$this->logger = new Logger($channel);
// Add handlers for different log levels
$this->setupHandlers();
// Add processors for enhanced context
$this->setupProcessors();
}
private function setupHandlers(): void
{
// Daily rotating logs with JSON formatting
$rotatingHandler = new RotatingFileHandler(
__DIR__ . '/../../var/logs/app.log',
7, // Keep 7 days
Logger::DEBUG
);
$rotatingHandler->setFormatter(new JsonFormatter());
// Error-specific handler
$errorHandler = new StreamHandler(
__DIR__ . '/../../var/logs/errors.log',
Logger::ERROR
);
$errorHandler->setFormatter(new JsonFormatter());
$this->logger->pushHandler($rotatingHandler);
$this->logger->pushHandler($errorHandler);
}
private function setupProcessors(): void
{
// PSR-3 message processing for placeholder interpolation
$this->logger->pushProcessor(new PsrLogMessageProcessor());
// Introspection processor adds file, line, class, function info
$this->logger->pushProcessor(new IntrospectionProcessor());
}
/**
* Create context-aware log entry using magic constants
*/
public function logWithContext(
string $level,
string $message,
array $context = [],
?string $callerFile = null,
?int $callerLine = null,
?string $callerFunction = null,
?string $callerClass = null,
?string $callerMethod = null
): void {
// Enhance context with caller information
$enhancedContext = array_merge($context, [
'caller' => [
'file' => $callerFile ?? 'unknown',
'line' => $callerLine ?? 0,
'function' => $callerFunction ?? 'unknown',
'class' => $callerClass ?? 'unknown',
'method' => $callerMethod ?? 'unknown',
],
'timestamp' => date('c'),
]);
$this->logger->log($level, $message, $enhancedContext);
}
/**
* Helper method to create a context-aware info log
*/
public function info(string $message, array $context = []): void
{
$this->logWithContext(
'info',
$message,
$context,
debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'] ?? null,
debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['line'] ?? null,
debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['function'] ?? null,
debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['class'] ?? null
);
}
/**
* Get the underlying logger instance
*/
public function getLogger(): LoggerInterface
{
return $this->logger;
}
}
Automatic Context with Logging Traits
Traits provide reusable logging functionality that automatically injects magic constants into log context. This approach eliminates manual context building whilst maintaining consistency across application components.
<?php
declare(strict_types=1);
namespace App\Logging;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
/**
* Logging trait that automatically includes magic constants in log context
*/
trait LoggingTrait
{
private LoggerInterface $logger;
/**
* Set the logger instance
*/
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
/**
* Log with automatic context enrichment using magic constants
*/
protected function log(
string $level,
string $message,
array $context = []
): void {
if (!isset($this->logger)) {
return; // Graceful degradation if no logger set
}
// Enrich context with magic constants and caller information
$enrichedContext = array_merge($context, [
'source' => [
'file' => __FILE__,
'line' => __LINE__,
'class' => __CLASS__,
'trait' => __TRAIT__,
'method' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'] ?? 'unknown',
],
'runtime' => [
'memory_usage' => memory_get_usage(true),
'peak_memory' => memory_get_peak_usage(true),
'execution_time' => microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'],
]
]);
$this->logger->log($level, $message, $enrichedContext);
}
/**
* Log debug information
*/
protected function logDebug(string $message, array $context = []): void
{
$this->log(LogLevel::DEBUG, $message, $context);
}
/**
* Log informational messages
*/
protected function logInfo(string $message, array $context = []): void
{
$this->log(LogLevel::INFO, $message, $context);
}
/**
* Log warning messages
*/
protected function logWarning(string $message, array $context = []): void
{
$this->log(LogLevel::WARNING, $message, $context);
}
/**
* Log error messages
*/
protected function logError(string $message, array $context = []): void
{
$this->log(LogLevel::ERROR, $message, $context);
}
/**
* Log critical errors
*/
protected function logCritical(string $message, array $context = []): void
{
$this->log(LogLevel::CRITICAL, $message, $context);
}
/**
* Log method entry with parameters
*/
protected function logMethodEntry(array $parameters = []): void
{
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$caller = $backtrace[1] ?? [];
$this->logDebug('Method entry', [
'method' => ($caller['class'] ?? '') . '::' . ($caller['function'] ?? 'unknown'),
'parameters' => $parameters,
'source_line' => $backtrace[0]['line'] ?? 0,
]);
}
/**
* Log method exit with return value
*/
protected function logMethodExit($returnValue = null): void
{
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$caller = $backtrace[1] ?? [];
$this->logDebug('Method exit', [
'method' => ($caller['class'] ?? '') . '::' . ($caller['function'] ?? 'unknown'),
'return_value' => $returnValue,
'source_line' => $backtrace[0]['line'] ?? 0,
]);
}
}
Service Integration Pattern
Services using the logging trait automatically gain contextual logging without modifying business logic:
<?php
declare(strict_types=1);
namespace App\Services;
use App\Logging\LoggingTrait;
use Psr\Log\LoggerInterface;
/**
* Example service demonstrating magic constants in logging
*/
class UserService
{
use LoggingTrait;
private array $users = [];
public function __construct(LoggerInterface $logger)
{
$this->setLogger($logger);
$this->logInfo('UserService initialised', [
'initialization_file' => __FILE__,
'initialization_line' => __LINE__,
'class' => __CLASS__,
'method' => __METHOD__,
]);
}
/**
* Create a new user
*/
public function createUser(array $userData): array
{
$this->logMethodEntry(['userData' => $userData]);
try {
$this->validateUserData($userData);
$user = [
'id' => uniqid(),
'email' => $userData['email'],
'name' => $userData['name'],
'created_at' => date('c'),
'created_from' => [
'file' => __FILE__,
'line' => __LINE__,
'method' => __METHOD__,
]
];
$this->users[$user['id']] = $user;
$this->logInfo('User created successfully', [
'user_id' => $user['id'],
'email' => $user['email'],
'source_method' => __METHOD__,
'source_line' => __LINE__,
]);
$this->logMethodExit($user);
return $user;
} catch (\Exception $e) {
$this->logError('Failed to create user', [
'error_message' => $e->getMessage(),
'error_file' => $e->getFile(),
'error_line' => $e->getLine(),
'input_data' => $userData,
'method' => __METHOD__,
'line' => __LINE__,
]);
throw $e;
}
}
/**
* Retrieve user by ID
*/
public function getUser(string $userId): ?array
{
$this->logDebug('Attempting to retrieve user', [
'user_id' => $userId,
'method' => __METHOD__,
'line' => __LINE__,
]);
$user = $this->users[$userId] ?? null;
if ($user === null) {
$this->logWarning('User not found', [
'user_id' => $userId,
'available_users' => array_keys($this->users),
'method' => __METHOD__,
'line' => __LINE__,
]);
} else {
$this->logInfo('User retrieved successfully', [
'user_id' => $userId,
'method' => __METHOD__,
'line' => __LINE__,
]);
}
return $user;
}
/**
* Private method to validate user data
*/
private function validateUserData(array $userData): void
{
$this->logDebug('Validating user data', [
'validation_keys' => array_keys($userData),
'method' => __METHOD__,
'line' => __LINE__,
]);
$requiredFields = ['email', 'name'];
$missingFields = array_diff($requiredFields, array_keys($userData));
if (!empty($missingFields)) {
$this->logError('User data validation failed', [
'missing_fields' => $missingFields,
'provided_fields' => array_keys($userData),
'method' => __METHOD__,
'line' => __LINE__,
]);
throw new \InvalidArgumentException(
'Missing required fields: ' . implode(', ', $missingFields)
);
}
if (!filter_var($userData['email'], FILTER_VALIDATE_EMAIL)) {
$this->logError('Invalid email format', [
'email' => $userData['email'],
'method' => __METHOD__,
'line' => __LINE__,
]);
throw new \InvalidArgumentException('Invalid email format');
}
$this->logDebug('User data validation passed', [
'method' => __METHOD__,
'line' => __LINE__,
]);
}
/**
* Get all users with logging
*/
public function getAllUsers(): array
{
$this->logInfo('Retrieving all users', [
'total_users' => count($this->users),
'method' => __METHOD__,
'line' => __LINE__,
]);
return array_values($this->users);
}
}
Advanced Context Processing
Custom Monolog processors enhance log records with magic constants and runtime information.
The DebugContextProcessor demonstrates sophisticated context enrichment:
<?php
declare(strict_types=1);
namespace App\Logging\Processors;
use Monolog\LogRecord;
use Monolog\Processor\ProcessorInterface;
/**
* Custom Monolog processor that enriches log records with debugging context
* using PHP magic constants and runtime information
*/
class DebugContextProcessor implements ProcessorInterface
{
private bool $includeTrace;
private int $skipStackFrames;
public function __construct(bool $includeTrace = false, int $skipStackFrames = 0)
{
$this->includeTrace = $includeTrace;
$this->skipStackFrames = $skipStackFrames;
}
/**
* Process log record and add debugging context
*/
public function __invoke(LogRecord $record): LogRecord
{
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10 + $this->skipStackFrames);
// Find the first frame that's not from the logging system
$relevantFrame = $this->findRelevantFrame($trace);
if ($relevantFrame) {
$extra = $record->extra;
// Add magic constants context
$extra['debug_context'] = [
'file' => $relevantFrame['file'] ?? 'unknown',
'line' => $relevantFrame['line'] ?? 0,
'function' => $relevantFrame['function'] ?? 'unknown',
'class' => $relevantFrame['class'] ?? null,
'type' => $relevantFrame['type'] ?? null,
];
// Add runtime context
$extra['runtime_context'] = [
'memory_usage' => $this->formatBytes(memory_get_usage(true)),
'peak_memory' => $this->formatBytes(memory_get_peak_usage(true)),
'included_files_count' => count(get_included_files()),
'declared_classes_count' => count(get_declared_classes()),
];
// Add environment context
$extra['environment_context'] = [
'php_version' => PHP_VERSION,
'sapi_name' => PHP_SAPI,
'os' => PHP_OS_FAMILY,
'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'CLI',
];
// Optionally include stack trace
if ($this->includeTrace) {
$extra['stack_trace'] = $this->formatStackTrace($trace);
}
return $record->with(extra: $extra);
}
return $record;
}
/**
* Find the most relevant frame in the stack trace
*/
private function findRelevantFrame(array $trace): ?array
{
$ignoredClasses = [
'Monolog\\',
'Psr\\Log\\',
'App\\Logging\\',
];
foreach ($trace as $frame) {
if (!isset($frame['class'])) {
return $frame; // Function call, likely relevant
}
$isIgnored = false;
foreach ($ignoredClasses as $ignoredClass) {
if (strpos($frame['class'], $ignoredClass) === 0) {
$isIgnored = true;
break;
}
}
if (!$isIgnored) {
return $frame;
}
}
return $trace[0] ?? null; // Fallback to first frame
}
/**
* Format stack trace for logging
*/
private function formatStackTrace(array $trace): array
{
$formatted = [];
foreach ($trace as $index => $frame) {
$formatted[] = sprintf(
'#%d %s%s%s() called at [%s:%d]',
$index,
$frame['class'] ?? '',
$frame['type'] ?? '',
$frame['function'] ?? 'unknown',
$frame['file'] ?? 'unknown',
$frame['line'] ?? 0
);
}
return $formatted;
}
/**
* Format bytes into human-readable format
*/
private function formatBytes(int $bytes): string
{
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$power = $bytes > 0 ? floor(log($bytes, 1024)) : 0;
return round($bytes / (1024 ** $power), 2) . ' ' . $units[$power];
}
}
Processor Benefits
- Automatic Context: Magic constants added without manual intervention
- Stack Trace Analysis: Intelligent frame selection ignoring logging infrastructure
- Runtime and Environment Data: Memory usage, performance figures, PHP version, SAPI, and system information in one place
Performance-Aware Logging
Performance logging uses magic constants for method timing and resource monitoring. The performance logger provides microtime-based timing with automatic context:
<?php
declare(strict_types=1);
namespace App\Logging;
use Psr\Log\LoggerInterface;
/**
* Performance-focused logger that uses magic constants for method timing
*/
class PerformanceLogger
{
private LoggerInterface $logger;
private array $timers = [];
private array $counters = [];
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* Start timing a method or operation
*/
public function startTimer(
?string $identifier = null,
?string $callerMethod = null,
?string $callerClass = null,
?int $callerLine = null
): string {
// Auto-generate identifier if not provided
if ($identifier === null) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
$identifier = sprintf(
'%s::%s@%d',
$callerClass ?? $backtrace['class'] ?? 'global',
$callerMethod ?? $backtrace['function'] ?? 'unknown',
$callerLine ?? $backtrace['line'] ?? 0
);
}
$this->timers[$identifier] = [
'start_time' => microtime(true),
'memory_start' => memory_get_usage(true),
'caller_method' => $callerMethod,
'caller_class' => $callerClass,
'caller_line' => $callerLine,
];
$this->logger->debug('Timer started', [
'timer_id' => $identifier,
'method' => $callerMethod,
'class' => $callerClass,
'line' => $callerLine,
'memory_at_start' => $this->formatBytes(memory_get_usage(true)),
]);
return $identifier;
}
/**
* Stop timing and log performance metrics
*/
public function stopTimer(
string $identifier,
array $additionalContext = []
): array {
if (!isset($this->timers[$identifier])) {
$this->logger->warning('Timer not found', [
'timer_id' => $identifier,
'available_timers' => array_keys($this->timers),
]);
return [];
}
$timer = $this->timers[$identifier];
$endTime = microtime(true);
$endMemory = memory_get_usage(true);
$metrics = [
'timer_id' => $identifier,
'execution_time_ms' => round(($endTime - $timer['start_time']) * 1000, 3),
'memory_usage_delta' => $endMemory - $timer['memory_start'],
'memory_usage_delta_formatted' => $this->formatBytes($endMemory - $timer['memory_start']),
'peak_memory' => $this->formatBytes(memory_get_peak_usage(true)),
'caller_method' => $timer['caller_method'],
'caller_class' => $timer['caller_class'],
'caller_line' => $timer['caller_line'],
];
$finalContext = array_merge($metrics, $additionalContext);
// Log at different levels based on execution time
$executionTime = $metrics['execution_time_ms'];
if ($executionTime > 1000) {
$this->logger->warning('Slow operation detected', $finalContext);
} elseif ($executionTime > 500) {
$this->logger->info('Operation completed (moderate duration)', $finalContext);
} else {
$this->logger->debug('Operation completed', $finalContext);
}
unset($this->timers[$identifier]);
return $metrics;
}
/**
* Increment a performance counter
*/
public function incrementCounter(
string $counterName,
int $increment = 1,
?string $callerMethod = null,
?string $callerClass = null
): int {
if (!isset($this->counters[$counterName])) {
$this->counters[$counterName] = 0;
}
$this->counters[$counterName] += $increment;
$this->logger->debug('Counter incremented', [
'counter_name' => $counterName,
'increment' => $increment,
'new_value' => $this->counters[$counterName],
'caller_method' => $callerMethod,
'caller_class' => $callerClass,
]);
return $this->counters[$counterName];
}
/**
* Log current performance snapshot
*/
public function logSnapshot(
string $label,
?string $callerMethod = null,
?string $callerClass = null,
?int $callerLine = null
): void {
$snapshot = [
'label' => $label,
'timestamp' => date('c'),
'memory_usage' => $this->formatBytes(memory_get_usage(true)),
'peak_memory' => $this->formatBytes(memory_get_peak_usage(true)),
'active_timers' => count($this->timers),
'counters' => $this->counters,
'included_files' => count(get_included_files()),
'caller_method' => $callerMethod,
'caller_class' => $callerClass,
'caller_line' => $callerLine,
];
$this->logger->info('Performance snapshot', $snapshot);
}
/**
* Get all active timers
*/
public function getActiveTimers(): array
{
return array_keys($this->timers);
}
/**
* Get counter values
*/
public function getCounters(): array
{
return $this->counters;
}
/**
* Format bytes into human-readable format
*/
private function formatBytes(int $bytes): string
{
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$power = $bytes > 0 ? floor(log($bytes, 1024)) : 0;
return round($bytes / (1024 ** $power), 2) . ' ' . $units[$power];
}
}
Performance Logging Patterns
Key patterns for production performance monitoring:
- Method-Level Timing: Automatic timer identification using magic constants
- Memory and Counter Tracking: Memory usage deltas for leak detection, plus operation counters with contextual information
- Threshold-Based Alerting: Log level adjustment based on execution time
Centralised Logger Factory
A logger factory centralises configuration whilst providing specialised loggers for different application components. The factory pattern ensures consistent logging setup across services:
<?php
declare(strict_types=1);
namespace App\Logging;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\BrowserConsoleHandler;
use Monolog\Processor\PsrLogMessageProcessor;
use Monolog\Processor\IntrospectionProcessor;
use Monolog\Formatter\JsonFormatter;
use Monolog\Formatter\LineFormatter;
use App\Logging\Processors\DebugContextProcessor;
use Psr\Log\LoggerInterface;
/**
* Factory for creating configured loggers with magic constants support
*/
class LoggerFactory
{
private string $logDirectory;
private bool $debugMode;
private array $defaultChannels = [
'app' => Logger::INFO,
'database' => Logger::DEBUG,
'security' => Logger::WARNING,
'performance' => Logger::INFO,
'api' => Logger::INFO,
];
public function __construct(
string $logDirectory = __DIR__ . '/../../var/logs',
bool $debugMode = false
) {
$this->logDirectory = $logDirectory;
$this->debugMode = $debugMode;
// Ensure log directory exists
if (!is_dir($this->logDirectory)) {
mkdir($this->logDirectory, 0755, true);
}
}
/**
* Create a logger for a specific channel
*/
public function createLogger(
string $channel,
?int $level = null
): LoggerInterface {
$logger = new Logger($channel);
$logLevel = $level ?? $this->defaultChannels[$channel] ?? Logger::INFO;
// Add handlers based on environment
$this->addHandlers($logger, $channel, $logLevel);
// Add processors for enhanced context
$this->addProcessors($logger);
return $logger;
}
/**
* Create enhanced logger with magic constants support
*/
public function createEnhancedLogger(string $channel): EnhancedLogger
{
return new EnhancedLogger($channel);
}
/**
* Create performance logger
*/
public function createPerformanceLogger(string $channel = 'performance'): PerformanceLogger
{
$logger = $this->createLogger($channel, Logger::DEBUG);
return new PerformanceLogger($logger);
}
/**
* Add appropriate handlers based on channel and environment
*/
private function addHandlers(Logger $logger, string $channel, int $level): void
{
// Main log file with rotation
$mainHandler = new RotatingFileHandler(
$this->logDirectory . "/{$channel}.log",
7, // Keep 7 days
$level
);
$mainHandler->setFormatter($this->createJsonFormatter());
$logger->pushHandler($mainHandler);
// Error-specific handler for serious issues
if ($level <= Logger::ERROR) {
$errorHandler = new StreamHandler(
$this->logDirectory . '/errors.log',
Logger::ERROR
);
$errorHandler->setFormatter($this->createJsonFormatter());
$logger->pushHandler($errorHandler);
}
// Console handler for development
if ($this->debugMode) {
$consoleHandler = new StreamHandler('php://stdout', Logger::DEBUG);
$consoleHandler->setFormatter($this->createLineFormatter());
$logger->pushHandler($consoleHandler);
// Browser console for web requests
if (isset($_SERVER['HTTP_HOST'])) {
$browserHandler = new BrowserConsoleHandler();
$logger->pushHandler($browserHandler);
}
}
// Channel-specific handlers
$this->addChannelSpecificHandlers($logger, $channel);
}
/**
* Add channel-specific handlers
*/
private function addChannelSpecificHandlers(Logger $logger, string $channel): void
{
switch ($channel) {
case 'security':
// Security events should be logged separately
$securityHandler = new StreamHandler(
$this->logDirectory . '/security.log',
Logger::INFO
);
$securityHandler->setFormatter($this->createJsonFormatter());
$logger->pushHandler($securityHandler);
break;
case 'database':
// Database queries and performance
$dbHandler = new RotatingFileHandler(
$this->logDirectory . '/database.log',
30, // Keep longer for analysis
Logger::DEBUG
);
$dbHandler->setFormatter($this->createJsonFormatter());
$logger->pushHandler($dbHandler);
break;
case 'api':
// API requests and responses
$apiHandler = new RotatingFileHandler(
$this->logDirectory . '/api.log',
14,
Logger::INFO
);
$apiHandler->setFormatter($this->createJsonFormatter());
$logger->pushHandler($apiHandler);
break;
}
}
/**
* Add processors for enhanced logging context
*/
private function addProcessors(Logger $logger): void
{
// PSR-3 message processing for placeholder interpolation
$logger->pushProcessor(new PsrLogMessageProcessor());
// Introspection processor for basic caller info
$logger->pushProcessor(new IntrospectionProcessor(
Logger::DEBUG, // Only add introspection for debug and above
['Monolog\\', 'App\\Logging\\'] // Skip these namespaces
));
// Custom debug context processor
$logger->pushProcessor(new DebugContextProcessor(
$this->debugMode, // Include stack trace in debug mode
1 // Skip one frame to get actual caller
));
// Add request context for web requests
if (isset($_SERVER['REQUEST_METHOD'])) {
$logger->pushProcessor(function ($record) {
// LogRecord is readonly in Monolog 3.x, so build a new instance via with()
return $record->with(extra: array_merge($record->extra, [
'request_context' => [
'method' => $_SERVER['REQUEST_METHOD'] ?? 'unknown',
'uri' => $_SERVER['REQUEST_URI'] ?? 'unknown',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'request_id' => $_SERVER['HTTP_X_REQUEST_ID'] ?? uniqid(),
],
]));
});
}
}
/**
* Create JSON formatter for structured logging
*/
private function createJsonFormatter(): JsonFormatter
{
return new JsonFormatter(
JsonFormatter::BATCH_MODE_JSON,
true, // Append a newline after each record
true, // Ignore empty context and extra arrays
true // Include stack traces for logged exceptions
);
}
/**
* Create line formatter for console output
*/
private function createLineFormatter(): LineFormatter
{
$format = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n";
$formatter = new LineFormatter($format, 'Y-m-d H:i:s');
$formatter->allowInlineLineBreaks(true);
$formatter->ignoreEmptyContextAndExtra(true);
return $formatter;
}
/**
* Get available log channels
*/
public function getAvailableChannels(): array
{
return array_keys($this->defaultChannels);
}
/**
* Set debug mode
*/
public function setDebugMode(bool $debugMode): void
{
$this->debugMode = $debugMode;
}
}
Factory Architecture Benefits
- Channel Separation: Different log files for different concerns
- Environment Adaptation: Debug vs production handler configuration
- Processor Consistency: Uniform context enrichment across loggers
- Handler Specialisation: Channel-specific output formatting and storage
- Testability: Services depend on the
LoggerInterfacethe factory returns, not on the factory itself
Real-World Implementation Examples
Practical examples demonstrate magic constants in production scenarios:
<?php
declare(strict_types=1);
require_once 'vendor/autoload.php';
use App\Logging\LoggerFactory;
use App\Services\UserService;
use App\Logging\PerformanceLogger;
// Initialise the logging system
$loggerFactory = new LoggerFactory(__DIR__ . '/logs', true);
// Create different types of loggers
$appLogger = $loggerFactory->createLogger('app');
$performanceLogger = $loggerFactory->createPerformanceLogger();
// Example 1: Basic logging with magic constants
function demonstrateBasicLogging()
{
global $appLogger;
$appLogger->info('Function started', [
'function' => __FUNCTION__,
'file' => __FILE__,
'line' => __LINE__,
]);
// Simulate some work
sleep(1);
$appLogger->info('Function completed', [
'function' => __FUNCTION__,
'execution_time' => '1 second',
'line' => __LINE__,
]);
}
// Example 2: Service with integrated logging
$userService = new UserService($appLogger);
try {
// Create a user - this will demonstrate method-level logging
$user = $userService->createUser([
'email' => '[email protected]',
'name' => 'John Doe'
]);
echo "Created user: {$user['id']}\n";
// Retrieve the user
$retrievedUser = $userService->getUser($user['id']);
echo "Retrieved user: {$retrievedUser['name']}\n";
// Try to get a non-existent user
$nonExistentUser = $userService->getUser('non-existent-id');
} catch (Exception $e) {
echo "Error: {$e->getMessage()}\n";
}
// Example 3: Performance logging with timing
function demonstratePerformanceLogging()
{
global $performanceLogger;
// Start timing with automatic identification
$timerId = $performanceLogger->startTimer(
null, // Auto-generate ID
__FUNCTION__,
__CLASS__ !== '' ? __CLASS__ : 'global',
__LINE__
);
// Simulate database operations
$performanceLogger->incrementCounter('database_queries', 1, __FUNCTION__);
usleep(250000); // 250ms
$performanceLogger->incrementCounter('database_queries', 2, __FUNCTION__);
usleep(150000); // 150ms
// Log a performance snapshot
$performanceLogger->logSnapshot(
'After database operations',
__FUNCTION__,
__CLASS__ !== '' ? __CLASS__ : 'global',
__LINE__
);
// Stop timing
$metrics = $performanceLogger->stopTimer($timerId, [
'operations_performed' => 'Database queries and cache checks',
'queries_executed' => 3,
]);
echo "Performance metrics: " . json_encode($metrics, JSON_PRETTY_PRINT) . "\n";
}
// Example 4: Error handling with context
function demonstrateErrorLogging()
{
global $appLogger;
try {
// Simulate an error condition
$data = json_decode('invalid json', true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('JSON parsing failed: ' . json_last_error_msg());
}
} catch (Exception $e) {
$appLogger->error('JSON parsing error occurred', [
'error_message' => $e->getMessage(),
'error_code' => $e->getCode(),
'error_file' => $e->getFile(),
'error_line' => $e->getLine(),
'function' => __FUNCTION__,
'file' => __FILE__,
'line' => __LINE__,
'input_data' => 'invalid json',
'json_error_code' => json_last_error(),
]);
// Re-throw or handle as needed
throw $e;
}
}
// Example 5: Namespace-aware logging
// See namespace-aware-logging.php for the App\Examples\NamespaceExample class
// Run examples
echo "Running logging examples...\n\n";
echo "1. Basic logging:\n";
demonstrateBasicLogging();
echo "\n2. Performance logging:\n";
demonstratePerformanceLogging();
echo "\n3. Namespace-aware logging:\n";
$namespaceExample = new App\Examples\NamespaceExample($appLogger);
$namespaceExample->doSomething();
echo "\n4. Error logging:\n";
try {
demonstrateErrorLogging();
} catch (Exception $e) {
echo "Caught and logged error: {$e->getMessage()}\n";
}
echo "\nAll examples completed. Check the log files in the logs directory.\n";
Example 5 above refers to a NamespaceExample class living in its own namespaced
file, since a bracketed namespace block cannot share a file with un-namespaced
global code:
<?php
declare(strict_types=1);
namespace App\Examples;
class NamespaceExample
{
private $logger;
public function __construct($logger)
{
$this->logger = $logger;
$this->logger->info('Class instantiated', [
'class' => __CLASS__,
'namespace' => __NAMESPACE__,
'method' => __METHOD__,
'file' => __FILE__,
'line' => __LINE__,
]);
}
public function doSomething(): void
{
$this->logger->debug('Method execution started', [
'method' => __METHOD__,
'class' => __CLASS__,
'namespace' => __NAMESPACE__,
'line' => __LINE__,
]);
// Simulate work
usleep(100000);
$this->logger->debug('Method execution completed', [
'method' => __METHOD__,
'line' => __LINE__,
]);
}
}
Production Logging Strategies
Effective production logging balances information depth with performance impact:
- Structured JSON: Machine-readable logs for analysis tools, with environment-appropriate verbosity levels
- Context Minimisation: Essential information without overwhelming detail
- Performance Monitoring: Resource usage tracking without overhead
Security and Sensitive Data Handling
Magic constants enhance security logging by providing precise context for security events. However, careful consideration prevents sensitive data exposure:
Security Logging Best Practices
- Context Filtering: Remove passwords, tokens, and personal data from context
- File Path Sanitisation: Avoid exposing internal directory structures in logs
- Stack Trace Limits: Restrict stack trace depth to prevent information disclosure
- Access Control: Secure log file permissions and access patterns
Sensitive Data Redaction
Implement context processors that sanitise sensitive data whilst preserving debugging value:
<?php
declare(strict_types=1);
// Example context sanitisation
$sanitizedContext = array_map(function ($value, $key) {
if (in_array($key, ['password', 'token', 'secret'], true)) {
return '[REDACTED]';
}
return $value;
}, $context, array_keys($context));
Testing and Debugging Strategies
Magic constants significantly improve debugging by providing automatic context without manual instrumentation. Testing logging systems requires mock loggers and context validation:
Testing Approaches
- Mock Logger Testing: Verify log messages and context without file operations, and assert magic constants provide expected values, using PHPUnit and its assertions
- Performance Testing: Measure logging overhead in high-throughput scenarios using PHPBench
- Integration Testing: Validate end-to-end logging pipeline functionality
Development Environment Configuration
Development logging should maximise debugging information whilst maintaining performance:
<?php
declare(strict_types=1);
// Development logger configuration
$logger = $loggerFactory->createLogger('app', Logger::DEBUG);
$logger->pushProcessor(new DebugContextProcessor(true, 0)); // Include full stack traces
Performance Considerations
Magic constants are resolved at compile time, making them performant for logging. However, context building and log processing can impact performance in high-throughput applications:
Optimisation Strategies
- Log Level Filtering: Disable debug logging in production
- Lazy Context Building: Build expensive context only when needed
- Asynchronous Logging: Queue log entries for background processing using message queues
- Selective Processing: Apply expensive processors only to specific channels
- Batching: Group multiple log writes into a single handler flush rather than one write per record
Memory Management
Large context arrays and stack traces can consume significant memory. Implement context limits and cleanup strategies:
<?php
declare(strict_types=1);
// Memory-conscious logging
$context = array_slice($fullContext, 0, 50); // Limit context size
$logger->info($message, $context);
unset($context); // Explicit cleanup
Integration with Modern PHP Ecosystems
Magic constants logging integrates cleanly with popular PHP frameworks and tools:
Framework Integration
- Symfony: Monolog integration with kernel events and service container
- Laravel: Built-in Monolog support with channel-based configuration
- PSR-11 Containers: Dependency injection for logger factory and services
- PSR-15 Middleware: Request/response logging with automatic context
Monitoring and Observability
Structured logs with magic constants integrate with modern observability platforms:
- ELK Stack: Elasticsearch indexing of structured JSON logs
- Grafana: Visualisation of performance metrics from logs
- Sentry: Error tracking with rich context from magic constants
- DataDog: Application performance monitoring with log correlation
Future-Proofing and Evolution
As PHP evolves, magic constants remain stable whilst logging ecosystems advance. Consider these trends for long-term maintainability:
Emerging Patterns
- OpenTelemetry: Distributed tracing with magic constants context
- Structured, Streamed Logging: Consistent JSON schemas across services, moving towards event-driven, real-time architectures
- AI-Powered Log Analysis: Machine learning on rich context data
Migration Strategies
Plan for logging system evolution whilst maintaining backward compatibility:
- Version Compatibility: Maintain support for older Monolog versions
- Context Schema Evolution: Additive changes to log context structure
- Handler Migration: Gradual transition to new log storage systems, tracking the performance impact as you go
Conclusion
PHP magic constants transform logging from manual instrumentation to automatic context enrichment. Combined with Monolog's processing capabilities and PSR-3 standards, they create maintainable logging architectures that scale with application complexity.
The key to successful implementation lies in balancing information richness with performance impact, using structured logging for observability, and maintaining security awareness in context handling. Magic constants provide the foundation for logging systems that grow with your application whilst maintaining debugging effectiveness.
As PHP applications become more distributed and complex, automatic context generation through magic constants gets more useful for debugging and monitoring, not less. The patterns and implementations shown here should give you a reasonable foundation for production logging systems that support both development productivity and operational visibility.
Got a PHP system that needs this kind of attention?
Get in touch