Logging


Links:


Dependencies:


CI

Neuron-PHP Logging

A flexible and powerful logging component for PHP 8.4+ applications, part of the Neuron-PHP framework.

Features

Requirements

Installation

Install via Composer:

composer require neuron-php/logging

Quick Start

The simplest way to start logging is using the singleton facade:

use Neuron\Log\Log;

// Set the minimum log level
Log::setRunLevel( 'debug' );

// Write log messages
Log::debug( 'Debug message' );
Log::info( 'Information message' );
Log::warning( 'Warning message' );
Log::error( 'Error message' );
Log::fatal( 'Fatal error' );

// With array context and interpolation (PSR-3 style)
Log::error( 'User {userId} failed login from {ip}', [
	'userId' => 12345,
	'ip' => '192.168.1.1',
	'attempts' => 3
] );

Destinations

The logging component supports writing to various destinations:

Available Destinations

Formats

Each destination can use different formatting:

Usage Examples

Basic Logging

use Neuron\Log\Log;

// Configure run level
Log::setRunLevel( 'debug' );

// Simple logging
Log::debug( 'Debug information' );
Log::info( 'Application started' );
Log::warning( 'Memory usage high' );

// With array context
Log::error( 'Database connection failed', [
	'host' => 'db.example.com',
	'port' => 3306,
	'error' => 'Connection timeout'
] );

// With message interpolation
Log::info( 'User {user} performed {action}', [
	'user' => '[email protected]',
	'action' => 'delete_records',
	'count' => 42
] );

File Logging

use Neuron\Log\Logger;
use Neuron\Log\Destination\File;
use Neuron\Log\Format\PlainText;

// Create a file logger
$fileDestination = new File( new PlainText() );
$fileDestination->open( [ 'path' => '/var/log/app.log' ] );

$logger = new Logger( $fileDestination );
$logger->setRunLevel( 'info' );
$logger->info( 'Application event logged to file' );

JSON Logging

use Neuron\Log\Logger;
use Neuron\Log\Destination\File;
use Neuron\Log\Format\JSON;

// Log in JSON format for structured logging
$jsonDestination = new File( new JSON() );
$jsonDestination->open( [ 'path' => '/var/log/app.json' ] );

$jsonLogger = new Logger( $jsonDestination );

// Array context is automatically included in JSON output
$jsonLogger->error( 'Database error', [
	'query' => 'SELECT * FROM users',
	'error_code' => 1054,
	'duration' => 234.5
] );

Slack Integration

use Neuron\Log\Log;
use Neuron\Log\Logger;
use Neuron\Log\Destination\Slack;
use Neuron\Log\Format\SlackFormat;

$slack = new Slack( new SlackFormat() );
$slack->open( [
	'endpoint' => $_ENV['LOG_SLACK_WEBHOOK_URL'],
	'params' => [
		'channel'  => '#alerts',
		'username' => 'AppLogger',
		'icon_emoji' => ':warning:'
	]
] );

$slackLogger = new Logger( $slack );
$slackLogger->setRunLevel( 'error' );

// Add to the multiplexer
Log::getInstance()->Logger->addLog( $slackLogger );

// Now errors and above will also go to Slack with context
Log::error( 'Payment processing failed', [
	'transaction_id' => 'txn_12345',
	'amount' => 99.99,
	'currency' => 'USD'
] );

Laravel Nightwatch Integration

use Neuron\Log\Logger;
use Neuron\Log\Destination\Nightwatch;
use Neuron\Log\Format\Nightwatch as NightwatchFormat;

// Create Nightwatch destination with default channel
$nightwatch = new Nightwatch( new NightwatchFormat() );
$nightwatch->open( [
	'token' => $_ENV['NIGHTWATCH_TOKEN'],  // Your Nightwatch API token
	'endpoint' => 'https://nightwatch.laravel.com/api/logs', // Optional, uses default
	'batch_size' => 10,  // Optional: batch logs for better performance
	'timeout' => 5       // Optional: API request timeout in seconds
] );

$nightwatchLogger = new Logger( $nightwatch );
$nightwatchLogger->setRunLevel( 'info' );

// Log messages will be sent to Nightwatch
$nightwatchLogger->info( 'Application started' );
$nightwatchLogger->error( 'Database connection failed', [
	'host' => 'db.example.com',
	'port' => 3306,
	'error' => 'Connection timeout'
] );

// For production use with the singleton
use Neuron\Log\Log;

// Add Nightwatch to the global logger
Log::getInstance()->Logger->addLog( $nightwatchLogger );

// Now all logs at info level and above go to Nightwatch
Log::info( 'User logged in', [ 'user_id' => 123 ] );
Log::warning( 'API rate limit approaching', [ 'requests' => 950, 'limit' => 1000 ] );
Log::error( 'Payment processing failed', [ 'transaction_id' => 'txn_abc123' ] );

Nightwatch with Channels

The channel name is automatically passed to Nightwatch when using named channels:

use Neuron\Log\Log;
use Neuron\Log\Logger;
use Neuron\Log\Destination\Nightwatch;
use Neuron\Log\Format\Nightwatch as NightwatchFormat;

// Create a single Nightwatch format instance
$nightwatchFormat = new NightwatchFormat( 'neuron', 'my-app' );

// Create Nightwatch destination
$nightwatch = new Nightwatch( $nightwatchFormat );
$nightwatch->open( [ 'token' => $_ENV['NIGHTWATCH_TOKEN'] ] );

// Create logger and add to multiple channels
$logger = new Logger( $nightwatch );
Log::addChannel( 'audit', $logger );
Log::addChannel( 'security', $logger );
Log::addChannel( 'payments', $logger );

// Logs automatically include the channel name
Log::channel( 'audit' )->info( 'User updated profile', [ 'user_id' => 123 ] );
// Nightwatch receives: {"channel": "audit", "message": "User updated profile", ...}

Log::channel( 'security' )->warning( 'Failed login attempt', [ 'ip' => '192.168.1.1' ] );
// Nightwatch receives: {"channel": "security", "message": "Failed login attempt", ...}

Log::channel( 'payments' )->error( 'Payment failed', [ 'amount' => 99.99 ] );
// Nightwatch receives: {"channel": "payments", "message": "Payment failed", ...}

The channel name appears in the Nightwatch dashboard for easy filtering and monitoring.

Array Context Support (PSR-3 Compatible)

use Neuron\Log\Log;

// Pass array context as second parameter
Log::error( 'Database query failed', [
	'query' => 'SELECT * FROM orders WHERE id = ?',
	'params' => [12345],
	'duration' => 1234.5,
	'error' => 'Connection timeout'
] );

// Complex nested arrays
Log::info( 'Order processed', [
	'order' => [
		'id' => 'ORD-12345',
		'items' => [
			[ 'sku' => 'WIDGET-1', 'qty' => 2 ],
			[ 'sku' => 'GADGET-5', 'qty' => 1 ]
		],
		'total' => 149.99
	],
	'customer_id' => 789
] );

// Exception tracking with automatic formatting
try
{
	// some operation
}
catch( Exception $e )
{
	Log::error( 'Operation failed', [
		'exception' => $e,  // Stack trace automatically captured
		'operation' => 'process_payment',
		'user_id' => $userId
	] );
}

Message Interpolation

// Use {placeholders} in messages - PSR-3 style
Log::info( 'User {userId} logged in from {ip} at {time}', [
	'userId' => 12345,
	'ip' => '192.168.1.100',
	'time' => date( 'H:i:s' )
] );
// Output: User 12345 logged in from 192.168.1.100 at 14:30:45

// Interpolation works with all log levels
Log::error( 'Failed to send email to {email}: {error}', [
	'email' => '[email protected]',
	'error' => 'SMTP connection refused',
	'retry_count' => 3
] );

Global and Local Context

use Neuron\Log\Log;

// Set global context that applies to all logs
Log::setContext( 'app_version', '2.0.0' );
Log::setContext( 'environment', 'production' );
Log::setContext( 'server', gethostname() );

// Can also set array values in global context
Log::setContext( 'tags', [ 'monitoring', 'production' ] );

// Local context in log call is merged with global
Log::info( 'User action', [
	'action' => 'profile_update',
	'user_id' => 456
] );
// Both global and local context are included

### Multiple Channels

```php
use Neuron\Log\Log;
use Neuron\Log\Logger;
use Neuron\Log\Destination\File;
use Neuron\Log\Destination\Slack;
use Neuron\Log\Format\PlainText;
use Neuron\Log\Format\SlackFormat;

// Create an audit logger
$auditFile = new File( new PlainText() );
$auditFile->open( [ 'path' => '/var/log/audit.log' ] );
$auditLogger = new Logger( $auditFile );

// Create a real-time alerts logger
$alertSlack = new Slack( new SlackFormat() );
$alertSlack->open( [
	'endpoint' => $_ENV['SLACK_WEBHOOK'],
	'params' => [ 'channel' => '#alerts' ]
] );
$alertLogger = new Logger( $alertSlack );

// Register channels
Log::addChannel( 'audit', $auditLogger );
Log::addChannel( 'alerts', $alertLogger );

// Use specific channels
Log::channel( 'audit' )->info( 'User login', [ 'user' => $username ] );
Log::channel( 'alerts' )->fatal( 'System down!' );

Multiplexer (Multiple Destinations)

use Neuron\Log\LogMux;
use Neuron\Log\Logger;
use Neuron\Log\Destination\File;
use Neuron\Log\Destination\StdErr;
use Neuron\Log\Format\PlainText;
use Neuron\Log\Format\JSON;

// Create multiple loggers
$fileLogger = new Logger( new File( new JSON() ) );
$fileLogger->getDestination()->open( [ 'path' => '/var/log/app.json' ] );
$fileLogger->setRunLevel( 'debug' );

$consoleLogger = new Logger( new StdErr( new PlainText() ) );
$consoleLogger->setRunLevel( 'warning' );

// Create multiplexer
$mux = new LogMux();
$mux->addLog( $fileLogger );
$mux->addLog( $consoleLogger );

// Logs go to both destinations based on their run levels
$mux->debug( 'Debug info' );     // Only to file
$mux->warning( 'Warning!' );     // To both file and console
$mux->error( 'Error occurred' ); // To both file and console

Custom Filters

use Neuron\Log\Logger;
use Neuron\Log\Filter\IFilter;
use Neuron\Log\RunLevel;

class ProductionFilter implements IFilter
{
	public function shouldLog( RunLevel $level, string $message, array $context ): bool
	{
		// Don't log debug messages in production
		if( $level === RunLevel::DEBUG && $_ENV['APP_ENV'] === 'production' )
		{
			return false;
		}

		// Don't log sensitive data
		if( str_contains( $message, 'password' ) || str_contains( $message, 'token' ) )
		{
			return false;
		}

		return true;
	}
}

$logger = new Logger( $destination );
$logger->addFilter( new ProductionFilter() );

Advanced Configuration

Environment-Based Configuration

use Neuron\Log\Log;
use Neuron\Log\Logger;
use Neuron\Log\Destination\File;
use Neuron\Log\Destination\StdOut;
use Neuron\Log\Format\JSON;
use Neuron\Log\Format\PlainText;

$environment = $_ENV['APP_ENV'] ?? 'development';

if( $environment === 'production' )
{
	// Production: JSON to file
	$destination = new File( new JSON() );
	$destination->open( [ 'path' => '/var/log/app.json' ] );
	$runLevel = 'warning';
}
else
{
	// Development: Plain text to console
	$destination = new StdOut( new PlainText( true ) );
	$runLevel = 'debug';
}

$logger = new Logger( $destination );
$logger->setRunLevel( $runLevel );

Log::getInstance()->Logger->addLog( $logger );

Testing with Memory Logger

use Neuron\Log\Logger;
use Neuron\Log\Destination\Memory;
use Neuron\Log\Format\Raw;

// For unit testing
$memoryDestination = new Memory( new Raw() );
$testLogger = new Logger( $memoryDestination );

$testLogger->error( 'Test error' );
$testLogger->info( 'Test info' );

// Retrieve logged messages
$logs = $memoryDestination->getLogs();
assert( count( $logs ) === 2 );
assert( $logs[0]['level'] === 'error' );

API Reference

Log Levels

The following log levels are supported (from lowest to highest severity):

Logger Methods

$logger->debug( string $message, array $context = [] );
$logger->info( string $message, array $context = [] );
$logger->warning( string $message, array $context = [] );
$logger->error( string $message, array $context = [] );
$logger->fatal( string $message, array $context = [] );
$logger->log( RunLevel $level, string $message, array $context = [] );

Testing

Run the test suite:

./vendor/bin/phpunit tests

Run tests with coverage:

./vendor/bin/phpunit tests --coverage-text

Contributing

Contributions are welcome! Please ensure all tests pass and maintain code coverage above 95%.

License

MIT License - see LICENSE file for details.

More Information