A comprehensive WordPress logging service with Wonolog integration, secure file logging, multi-server protection, and environment-based configuration.
- PSR-3 Compatibility: Full implementation of PSR-3 LoggerInterface for standardized logging
- Environment-Based Configuration: Powered by WP Env for flexible configuration management
- Wonolog Integration: Automatic detection and seamless integration with Wonolog
- Secure File Logging: WordPress.org compliant fallback logging with multi-server protection
- Multi-Server Protection: Built-in protection files for Apache, Nginx, IIS, LiteSpeed, and more
- Configurable Log Levels: Minimum log level filtering with environment variable support
- Intelligent Log Retention: Environment-specific retention policies with automatic cleanup
- Hook System: Extensible architecture with WordPress hooks for customization
- Debug Mode Support: Intelligent handling of development vs production environments
- Zero Dependencies: Works with or without external logging libraries, with automatic fallback to native file-based logging
- Plugin Isolation: Each plugin/theme gets independent log directories and settings
WP Logger intentionally uses mixed type hints throughout its API, diverging from strict PSR Log ^2.0+ signatures. This design choice aligns with our primary integration target and WordPress ecosystem philosophy:
Wonolog Compatibility: Our primary target, Wonolog, accepts truly mixed types natively (Throwable, Arrays, Objects, WP_Error):
// Wonolog native usage
do_action('wonolog.log', $exception); // ✅ Throwable
do_action('wonolog.log', ['data' => 'log']); // ✅ Array
do_action('wonolog.log', $wpError); // ✅ WP_ErrorPSR Log Version Differences:
- PSR Log ^1.0: Uses
mixedtype hint but primarily expects strings - PSR Log ^2.0+: Strictly enforces
string|\Stringableonly
WordPress Ecosystem: WordPress prioritizes pragmatism and developer experience over strict typing. Our design reflects this philosophy.
// ✅ Natural and intuitive - all supported
$logger->error($exception); // Throwable objects
$logger->warning($wpError); // WP_Error objects
$logger->info(['user' => 123]); // Structured data
$logger->debug($customObject); // Any object
// vs. ❌ Verbose PSR ^2.0+ requirement
$logger->error($exception->getMessage(), ['exception' => $exception]);- With Wonolog: Mixed types passed through natively without conversion
- Fallback Mode: Mixed types converted to strings via
formatMessage() - Best of Both: WordPress flexibility + PSR compatibility + Wonolog power
This approach ensures zero breaking changes while maximizing compatibility across the WordPress ecosystem.
Install via Composer:
composer require wp-spaghetti/wp-logger<?php
use WpSpaghetti\WpLogger\Logger;
// Initialize with minimal configuration
$logger = new Logger([
'component_name' => 'my-awesome-plugin'
]);
// Standard PSR-3 logging methods
$logger->info('Plugin initialized successfully');
$logger->error('Something went wrong', ['error_code' => 500]);
$logger->debug('Debug information', ['user_id' => 123]);<?php
use WpSpaghetti\WpLogger\Logger;
// Configuration can come from environment variables via WP Env
$logger = new Logger([
'component_name' => 'my-plugin',
'retention_days' => 60, // Can be overridden by LOGGER_RETENTION_DAYS env var
'min_level' => 'info', // Can be overridden by LOGGER_MIN_LEVEL env var
]);
// Logging behavior:
// - If Wonolog is available: uses Wonolog for advanced logging
// - If Wonolog not available: uses fallback (error_log in debug/dev, file logging in production)
$logger->debug('Debug information');
$logger->info('Informational message');
$logger->warning('Warning message');
$logger->error('Error occurred');<?php
// In your plugin main file
use WpSpaghetti\WpLogger\Logger;
class MyPlugin
{
private Logger $logger;
public function __construct()
{
$this->logger = new Logger([
'component_name' => 'my-plugin',
'retention_days' => 30
]);
add_action('init', [$this, 'init']);
}
public function init(): void
{
$this->logger->info('Plugin initialized');
// Your plugin logic here...
try {
$this->doSomething();
} catch (\Exception $exception) {
$this->logger->error('Operation failed', [
'exception' => $exception
]);
}
}
}WP Logger v2.0 supports multiple configuration sources with intelligent priority:
- Plugin-specific Environment Variables (
MY_PLUGIN_*) - Global Logger Environment Variables (
LOGGER_*) - WordPress Constants (wp-config.php)
- Configuration Array (passed to constructor)
- Default Values
$config = [
// Required: Unique identifier for your plugin/theme
'component_name' => 'my-plugin',
// Optional: Days to keep log files (default: 30)
'retention_days' => 60,
// Optional: Minimum log level to record (default: 'debug')
'min_level' => 'info',
// Optional: Wonolog namespace (default: 'Inpsyde\Wonolog')
'wonolog_namespace' => 'MyApp\Wonolog',
// Optional: WordPress constant to disable logging
// (default: auto-generated from component_name)
'disabled_constant' => 'MY_PLUGIN_LOGGER_DISABLED',
// Optional: WordPress constant for retention days
// (default: auto-generated from component_name)
'retention_days_constant' => 'MY_PLUGIN_LOGGER_RETENTION_DAYS'
];# Global logger settings
LOGGER_COMPONENT_NAME=my-plugin # Plugin/Theme identifier
LOGGER_RETENTION_DAYS=30 # Log retention period
LOGGER_MIN_LEVEL=info # Minimum log level
LOGGER_DISABLED=false # Disable all logging
LOGGER_WONOLOG_NAMESPACE=Inpsyde\Wonolog
# Plugin-specific settings (higher priority)
MY_PLUGIN_LOGGER_RETENTION_DAYS=60
MY_PLUGIN_LOGGER_DISABLED=false
MY_PLUGIN_LOGGER_MIN_LEVEL=warning// Control logging behavior per plugin
define('MY_PLUGIN_LOGGER_DISABLED', true); // Disable all logging
define('MY_PLUGIN_LOGGER_RETENTION_DAYS', 90); // Keep logs for 90 days
// Global WordPress debug (affects fallback behavior)
define('WP_DEBUG', true); // Forces error_log() usage in fallback modeWP Logger uses a simple, reliable logging strategy:
- With Wonolog Available: Uses Wonolog for advanced logging features, routing, and formatting
- Without Wonolog (Fallback):
- Development/Debug Mode: Logs go to PHP error_log for immediate developer feedback
- Production Mode: Logs go to secure files in WordPress uploads directory
- Disabled Mode: Only hooks are triggered (no actual logging)
When Wonolog is not available, WP Logger adapts its fallback behavior:
- Detection:
WP_DEBUG=trueorWP_ENVIRONMENT_TYPE=development - Behavior: Uses PHP error_log for immediate developer feedback
- Format: Includes environment tags for context
- Performance: Optimized for debugging with detailed context
- Detection:
WP_DEBUG=falseandWP_ENVIRONMENT_TYPE=production - Behavior: Uses secure file logging with WordPress-compliant protection
- Format: Minimal overhead, performance-optimized
- Security: Protected directories with multi-server support
WP Logger creates the following structure:
wp-content/uploads/
└── my-plugin/
├── index.php # Directory protection
└── logs/
├── .htaccess # Apache protection
├── web.config # IIS protection
├── index.php # Universal protection
├── README # Server configuration guide
├── 2024-01-15_a1b2c3d4.dat
└── 2024-01-16_a1b2c3d4.dat
Initialize logger with configuration options.
Log message with arbitrary level (PSR-3 interface).
emergency($message, array $context = []): voidalert($message, array $context = []): voidcritical($message, array $context = []): voiderror($message, array $context = []): voidwarning($message, array $context = []): voidnotice($message, array $context = []): voidinfo($message, array $context = []): voiddebug($message, array $context = []): void
Get Wonolog logger instance if available.
Force refresh Wonolog availability cache.
Get comprehensive debug information for troubleshooting.
Get current logger configuration.
<?php
/*
Plugin Name: My Awesome Plugin
Version: 1.0.0
*/
use WpSpaghetti\WpLogger\Logger;
class MyAwesomePlugin
{
private Logger $logger;
public function __construct()
{
$this->logger = new Logger([
'component_name' => 'my-awesome-plugin',
'retention_days' => 30
]);
register_activation_hook(__FILE__, [$this, 'activate']);
register_deactivation_hook(__FILE__, [$this, 'deactivate']);
add_action('plugins_loaded', [$this, 'init']);
}
public function activate(): void
{
$this->logger->info('Plugin activated', ['version' => '1.0.0']);
}
public function deactivate(): void
{
$this->logger->info('Plugin deactivated');
}
public function init(): void
{
$this->logger->debug('Plugin initialization started');
// Your plugin logic...
$this->logger->info('Plugin fully initialized');
}
}
new MyAwesomePlugin();<?php
// In functions.php
use WpSpaghetti\WpLogger\Logger;
$theme_logger = new Logger([
'component_name' => get_template(),
'retention_days' => 14
]);
// Log theme setup
add_action('after_setup_theme', function() use ($theme_logger) {
$theme_logger->info('Theme setup completed', [
'theme' => get_template(),
'version' => wp_get_theme()->get('Version')
]);
});
// Log template loading for debugging
add_action('template_redirect', function() use ($theme_logger) {
global $template;
$theme_logger->debug('Template loaded', [
'template' => basename($template),
'query_vars' => get_query_var('all')
]);
});<?php
use WpSpaghetti\WpLogger\Logger;
class ErrorHandler
{
private Logger $logger;
public function __construct()
{
$this->logger = new Logger(['component_name' => 'error-handler']);
// Hook into WordPress error handling
add_action('wp_die_handler', [$this, 'handle_wp_die']);
set_error_handler([$this, 'handle_php_error']);
set_exception_handler([$this, 'handle_exception']);
}
public function handle_wp_die($message): void
{
$this->logger->critical('WordPress died', ['message' => $message]);
}
public function handle_php_error($severity, $message, $file, $line): bool
{
$this->logger->error('PHP Error', [
'severity' => $severity,
'message' => $message,
'file' => basename($file),
'line' => $line
]);
return false; // Let PHP handle it too
}
public function handle_exception(\Throwable $exception): void
{
$this->logger->critical('Uncaught Exception', [
'exception' => $exception
]);
}
}<?php
/*
Plugin Name: Environment-Aware Plugin
Version: 2.0.0
*/
use WpSpaghetti\WpLogger\Logger;
class EnvironmentAwarePlugin
{
private Logger $logger;
public function __construct()
{
// Logger automatically detects environment via WP Env
$this->logger = new Logger();
register_activation_hook(__FILE__, [$this, 'activate']);
add_action('plugins_loaded', [$this, 'init']);
}
public function activate(): void
{
$debugInfo = $this->logger->getDebugInfo();
$this->logger->info('Plugin activated', [
'environment' => $debugInfo['environment_type'],
'is_container' => $debugInfo['is_container'],
'server' => $debugInfo['server_software']
]);
}
public function init(): void
{
$debugInfo = $this->logger->getDebugInfo();
if ($debugInfo['is_development']) {
// Development-specific initialization
$this->enableDebugMode();
$this->logger->debug('Plugin initialized in development mode');
} elseif ($debugInfo['is_staging']) {
// Staging-specific initialization
$this->enableStagingFeatures();
$this->logger->info('Plugin initialized in staging mode');
} else {
// Production initialization
$this->enableProductionOptimizations();
$this->logger->notice('Plugin initialized in production mode');
}
}
private function enableDebugMode(): void
{
// Enable verbose logging, profiling, etc.
$this->logger->debug('Debug mode enabled');
}
private function enableStagingFeatures(): void
{
// Enable testing features, disable emails, etc.
$this->logger->info('Staging features enabled');
}
private function enableProductionOptimizations(): void
{
// Enable caching, disable debug features, etc.
$this->logger->notice('Production optimizations enabled');
}
}
new EnvironmentAwarePlugin();WP Logger provides several hooks for customization:
// Completely override logging (return non-null to prevent default logging)
add_filter('wp_logger_override_log', function($result, $level, $message, $context, $config) {
// Custom logging implementation
my_custom_logger($level, $message, $context);
return true; // Prevent default logging
}, 10, 5);// Change Wonolog namespace
add_filter('wp_logger_wonolog_namespace', function($namespace) {
return 'MyApp\Logger';
});
// Modify Wonolog prefix
add_filter('wp_logger_wonolog_prefix', function($prefix, $level, $message, $context, $config) {
return 'myapp.log';
}, 10, 5);
// Customize Wonolog action name
add_filter('wp_logger_wonolog_action', function($action, $level, $message, $context, $config) {
return 'custom.log.' . $level;
}, 10, 5);// React to all log entries
add_action('wp_logger_logged', function($level, $message, $context, $component_name) {
// Send critical errors to external monitoring
if ($level === 'critical') {
send_to_monitoring_service($message, $context);
}
}, 10, 4);
// Handle fallback logging
add_action('wp_logger_fallback', function($level, $message, $context, $component_name) {
// Custom fallback when Wonolog is not available
}, 10, 4);
// Hook into specific log levels during fallback
add_action('wp_logger_fallback_error', function($message, $context, $component_name) {
// Handle error-level logs specifically
}, 10, 3);WP Logger automatically creates protection files for various web servers:
- Apache:
.htaccessfiles - Nginx: Configuration examples in README
- IIS:
web.configfiles - LiteSpeed:
.htaccesscompatible - Universal:
index.phpprotection files
For Nginx, add to your server configuration:
# Block access to WP Logger files
location ~* /wp-content/uploads/.*/logs/ {
deny all;
return 403;
}// Get comprehensive debug info
$debugInfo = $logger->getDebugInfo();
var_dump($debugInfo);Logs not appearing:
- Check if
WP_DEBUGis enabled for immediate error_log output - Verify the disable logging constant is not set
- Ensure WordPress upload directory is writable
Wonolog not detected:
- Verify Wonolog is properly installed and activated
- Check the Wonolog namespace configuration
- Use
refreshWonologCache()if Wonolog state changes
File permissions:
- WordPress upload directory must be writable
- Log files are created with restrictive permissions automatically
- PHP 8.0 or higher
- WordPress 5.0 or higher
- PSR Log 1.0|2.0|3.0 for interface compatibility
- WP Env 2.0+ for environment management
- Optional: Wonolog for advanced logging
Please see CHANGELOG for a detailed list of changes for each release.
We follow Semantic Versioning and use Conventional Commits to automatically generate our changelog.
- Major versions (1.0.0 → 2.0.0): Breaking changes
- Minor versions (1.0.0 → 1.1.0): New features, backward compatible
- Patch versions (1.0.0 → 1.0.1): Bug fixes, backward compatible
All releases are automatically created when changes are pushed to the main branch, based on commit message conventions.
For your contributions please use:
See CONTRIBUTING for detailed guidelines.
