Compare commits

..

No commits in common. "master" and "2.0.1" have entirely different histories.

10 changed files with 83 additions and 361 deletions

View file

@ -5,51 +5,6 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.7] - 2025-01-13
This update introduces a minor fix
### Fixed
- Fixed FileLogging issue by setting the write permission to 0666 for the log file if it doesn't exist.
## [2.0.6] - 2025-01-10
This update introduces a minor change
### Changed
- File logging is disabled for web environments due to instability in file locking, until a better solution is found.
## [2.0.5] - 2025-01-09
This update introduces a minor bug fix
### Fixed
- Refactor file locking to return status and handle failure.
## [2.0.4] - 2024-12-04
This update introduces a minor bug fix
## [2.0.3] - 2024-11-05
This update introduces a minor bug fix
## [2.0.2] - 2024-10-30
This update introduces minor improvements
### Changed
- Refactored exception handling in FileLogging where it will always attempt to print the exception no matter
the log level for as long as the log level isn't silent
- Implement enhanced error and exception handling
## [2.0.1] - 2024-10-29 ## [2.0.1] - 2024-10-29
This update introduces a critical bug fix where Console logging was enabled in web environments This update introduces a critical bug fix where Console logging was enabled in web environments

View file

@ -20,7 +20,7 @@
"package": "net.nosial.loglib", "package": "net.nosial.loglib",
"company": "Nosial", "company": "Nosial",
"copyright": "Copyright (c) 2022-2023 Nosial", "copyright": "Copyright (c) 2022-2023 Nosial",
"version": "2.0.7", "version": "2.0.1",
"uuid": "de1deca6-7b65-11ed-a8b0-a172264634d8" "uuid": "de1deca6-7b65-11ed-a8b0-a172264634d8"
}, },
"build": { "build": {

View file

@ -1,87 +0,0 @@
<?php
namespace LogLib\Classes;
class BacktraceParser
{
/**
* Determines if the given backtrace originates from the exception handler.
*
* @param array $backtrace The backtrace array to inspect.
* @return bool Returns true if the backtrace originates from the exception handler within LogLib\Runtime class, false otherwise.
*/
public static function fromExceptionHandler(array $backtrace): bool
{
/** @var array $trace */
foreach($backtrace as $trace)
{
if(!isset($trace['function']) || $trace['function'] != 'exceptionHandler')
{
continue;
}
if(!isset($trace['class']) || $trace['class'] != 'LogLib\Runtime')
{
continue;
}
return true;
}
return false;
}
/**
* Determines if the given backtrace originates from the error handler.
*
* @param array $backtrace The backtrace array to inspect.
* @return bool Returns true if the backtrace originates from the error handler within LogLib\Runtime class, false otherwise.
*/
public static function fromErrorHandler(array $backtrace): bool
{
/** @var array $trace */
foreach($backtrace as $trace)
{
if(!isset($trace['function']) || $trace['function'] != 'errorHandler')
{
continue;
}
if(!isset($trace['class']) || $trace['class'] != 'LogLib\Runtime')
{
continue;
}
return true;
}
return false;
}
/**
* Determines if a given backtrace contains a call to the shutdownHandler method in the LogLib\Runtime class.
*
* @param array $backtrace The backtrace to be analyzed.
* @return bool True if the shutdownHandler method in the LogLib\Runtime class is found in the backtrace; otherwise, false.
*/
public static function fromShutdownHandler(array $backtrace): bool
{
/** @var array $trace */
foreach($backtrace as $trace)
{
if(!isset($trace['function']) || $trace['function'] != 'shutdownHandler')
{
continue;
}
if(!isset($trace['class']) || $trace['class'] != 'LogLib\Runtime')
{
continue;
}
return true;
}
return false;
}
}

View file

@ -32,11 +32,8 @@
// Create the file if it doesn't exist // Create the file if it doesn't exist
if (!file_exists($filePath)) if (!file_exists($filePath))
{ {
// Create the file $this->fileHandle = fopen($filePath, 'w');
touch($filePath); fclose($this->fileHandle);
// Set the file permissions to 0666
chmod($filePath, 0666);
} }
} }
@ -45,12 +42,12 @@
* *
* @throws RuntimeException if unable to open or lock the file. * @throws RuntimeException if unable to open or lock the file.
*/ */
private function lock(): bool private function lock(): void
{ {
$this->fileHandle = @fopen($this->filePath, 'a'); $this->fileHandle = fopen($this->filePath, 'a');
if ($this->fileHandle === false) if ($this->fileHandle === false)
{ {
return false; throw new RuntimeException("Unable to open the file: " . $this->filePath);
} }
// Keep trying to acquire the lock until it succeeds // Keep trying to acquire the lock until it succeeds
@ -67,8 +64,6 @@
flock($this->fileHandle, LOCK_UN); flock($this->fileHandle, LOCK_UN);
$this->lock(); $this->lock();
} }
return true;
} }
/** /**
@ -92,11 +87,7 @@
*/ */
public function append(string $data): void public function append(string $data): void
{ {
if(!$this->lock()) $this->lock();
{
// Do not proceed if the file cannot be locked
return;
}
if ($this->fileHandle !== false) if ($this->fileHandle !== false)
{ {

View file

@ -157,34 +157,36 @@
* If $ansi is true, the output will be colored using ANSI escape codes. * If $ansi is true, the output will be colored using ANSI escape codes.
* If the event has no backtrace, the constant CallType::LAMBDA_CALL will be returned. * If the event has no backtrace, the constant CallType::LAMBDA_CALL will be returned.
*/ */
public static function getTraceString(Event $event, bool $ansi = true): ?string public static function getTraceString(Event $event, bool $ansi=true): ?string
{ {
if ($event->getBacktrace() === null || count($event->getBacktrace()) === 0) if($event->getBacktrace() === null || count($event->getBacktrace()) === 0)
{ {
return CallType::LAMBDA_CALL->value; return CallType::LAMBDA_CALL->value;
} }
$backtrace = $event->getBacktrace()[count($event->getBacktrace()) - 1]; $backtrace = $event->getBacktrace()[count($event->getBacktrace()) - 1];
// Ignore \LogLib namespace // Ignore \LogLib namespace
if (isset($backtrace['class']) && str_starts_with($backtrace['class'], 'LogLib')) if(isset($backtrace['class']) && str_starts_with($backtrace['class'], 'LogLib'))
{ {
if (isset($backtrace['file'])) if(isset($backtrace['file']))
{ {
if ($ansi) if($ansi)
{ {
return "\033[1;37m" . basename($backtrace['file']) . "\033[0m"; return "\033[1;37m" . basename($backtrace['file']) . "\033[0m";
} }
return basename($backtrace['file']); return basename($backtrace['file']);
} }
return self::determineCallType($event->getBacktrace())->value; // Return a placeholder value return basename($backtrace['file']);
} }
if ($backtrace['function'] === '{closure}') if($backtrace['function'] === '{closure}')
{ {
if (isset($backtrace['file'])) if(isset($backtrace['file']))
{ {
if ($ansi) if($ansi)
{ {
return "\033[1;37m" . basename($backtrace['file']) . "\033[0m" . CallType::STATIC_CALL->value . CallType::LAMBDA_CALL->value; return "\033[1;37m" . basename($backtrace['file']) . "\033[0m" . CallType::STATIC_CALL->value . CallType::LAMBDA_CALL->value;
} }
@ -192,24 +194,25 @@
return basename($backtrace['file']) . CallType::STATIC_CALL->value . CallType::LAMBDA_CALL->value; return basename($backtrace['file']) . CallType::STATIC_CALL->value . CallType::LAMBDA_CALL->value;
} }
return self::determineCallType($event->getBacktrace())->value . CallType::STATIC_CALL->value . CallType::LAMBDA_CALL->value; // Adjusted to handle missing 'file' return basename($backtrace['file']) . CallType::STATIC_CALL->value . CallType::LAMBDA_CALL->value;
} }
if ($backtrace['function'] === 'eval') if($backtrace['function'] === 'eval')
{ {
if (isset($backtrace['file'])) if(isset($backtrace['file']))
{ {
if ($ansi) if($ansi)
{ {
return "\033[1;37m" . basename($backtrace['file']) . "\033[0m" . CallType::STATIC_CALL->value . CallType::EVAL_CALL->value; return "\033[1;37m" . basename($backtrace['file']) . "\033[0m" . CallType::STATIC_CALL->value . CallType::EVAL_CALL->value;
} }
return basename($backtrace['file']) . CallType::STATIC_CALL->value . CallType::EVAL_CALL->value; return basename($backtrace['file']) . CallType::STATIC_CALL->value . CallType::EVAL_CALL->value;
} }
return self::determineCallType($event->getBacktrace())->value . CallType::STATIC_CALL->value . CallType::EVAL_CALL->value; // Adjusted to handle missing 'file'
return basename($backtrace['file']) . CallType::STATIC_CALL->value . CallType::EVAL_CALL->value;
} }
if ($ansi) if($ansi)
{ {
$function = sprintf("\033[1;37m%s\033[0m", $backtrace['function']); $function = sprintf("\033[1;37m%s\033[0m", $backtrace['function']);
} }
@ -220,9 +223,9 @@
$class = null; $class = null;
if (isset($backtrace["class"])) if(isset($backtrace["class"]))
{ {
if ($ansi) if($ansi)
{ {
$class = sprintf("\033[1;37m%s\033[0m", $backtrace['class']); $class = sprintf("\033[1;37m%s\033[0m", $backtrace['class']);
} }
@ -232,7 +235,7 @@
} }
} }
if ($class === null) if($class === null)
{ {
return $function . CallType::FUNCTION_CALL->value; return $function . CallType::FUNCTION_CALL->value;
} }
@ -241,32 +244,6 @@
return "{$class}{$type->value}{$function}" . CallType::FUNCTION_CALL->value; return "{$class}{$type->value}{$function}" . CallType::FUNCTION_CALL->value;
} }
/**
* Determines the type of call based on the provided backtrace.
*
* @param array $backtrace The backtrace information of the calling code.
* @return CallType The type of call detected.
*/
private static function determineCallType(array $backtrace): CallType
{
if(BacktraceParser::fromErrorHandler($backtrace))
{
return CallType::ERROR_HANDLER;
}
if(BacktraceParser::fromExceptionHandler($backtrace))
{
return CallType::EXCEPTION_HANDLER;
}
if(BacktraceParser::fromShutdownHandler($backtrace))
{
return CallType::SHUTDOWN_HANDLER;
}
return CallType::UNKNOWN_FILE;
}
/** /**
* Converts an exception object to an array representation. * Converts an exception object to an array representation.
* *

View file

@ -38,32 +38,4 @@
* @var string EVAL_CALL * @var string EVAL_CALL
*/ */
case EVAL_CALL = 'eval()'; case EVAL_CALL = 'eval()';
/**
* Represents an unknown file.
*
* @var string UNKNOWN_FILE
*/
case UNKNOWN_FILE = '?';
/**
* Represents a runtime error handler.
*
* @var string ERROR_HANDLER
*/
case ERROR_HANDLER = 'RUNTIME_ERROR';
/**
* Represents a shutdown handler event.
*
* @var string SHUTDOWN_HANDLER
*/
case SHUTDOWN_HANDLER = 'SHUTDOWN_ERROR';
/**
* Represents an exception handler for runtime exceptions.
*
* @var string EXCEPTION_HANDLER
*/
case EXCEPTION_HANDLER = 'RUNTIME_EXCEPTION';
} }

View file

@ -24,6 +24,7 @@ class ConsoleLogging implements LogHandlerInterface
*/ */
public static function handle(Application $application, Event $event): void public static function handle(Application $application, Event $event): void
{ {
// Check if the application is running in a CLI environment, if not, return
if(!Utilities::runningInCli()) if(!Utilities::runningInCli())
{ {
return; return;
@ -207,10 +208,7 @@ class ConsoleLogging implements LogHandlerInterface
print('Stack Trace:' . PHP_EOL); print('Stack Trace:' . PHP_EOL);
foreach($trace as $item) foreach($trace as $item)
{ {
if(isset($item['file']) && isset($item['line'])) print( ' - ' . self::color($item['file'], ConsoleColors::RED) . ':' . $item['line'] . PHP_EOL);
{
print( ' - ' . self::color($item['file'], ConsoleColors::RED) . ':' . $item['line'] . PHP_EOL);
}
} }
} }

View file

@ -29,25 +29,32 @@ class FileLogging implements LogHandlerInterface
if(Validate::checkLevelType(LogLevel::DEBUG, $application->getConsoleLoggingLevel())) if(Validate::checkLevelType(LogLevel::DEBUG, $application->getConsoleLoggingLevel()))
{ {
$backtrace_output = Utilities::getTraceString($event, false); $backtrace_output = Utilities::getTraceString($event, false);
$output = sprintf("[%s] [%s] [%s] %s %s" . PHP_EOL, $output = sprintf("[%s] [%s] [%s] %s %s" . PHP_EOL,
self::getTimestamp(), $application->getApplicationName(), $event->getLevel()->name, $backtrace_output, $event->getMessage() self::getTimestamp(), $application->getApplicationName(), $event->getLevel()->name, $backtrace_output, $event->getMessage()
); );
if($event->getException() !== null)
{
$output .= self::outException($event->getException());
}
} }
else if(Validate::checkLevelType(LogLevel::VERBOSE, $application->getConsoleLoggingLevel())) else if(Validate::checkLevelType(LogLevel::VERBOSE, $application->getConsoleLoggingLevel()))
{ {
$backtrace_output = Utilities::getTraceString($event, false); $backtrace_output = Utilities::getTraceString($event, false);
$output = sprintf("[%s] [%s] [%s] %s %s" . PHP_EOL, self::getTimestamp(), $application->getApplicationName(), $event->getLevel()->name, $backtrace_output, $event->getMessage()); $output = sprintf("[%s] [%s] [%s] %s %s" . PHP_EOL, self::getTimestamp(), $application->getApplicationName(), $event->getLevel()->name, $backtrace_output, $event->getMessage());
if($event->getException() !== null)
{
$output .= self::outException($event->getException());
}
} }
else else
{ {
$output = sprintf("[%s] [%s] [%s] %s" . PHP_EOL, self::getTimestamp(), $application->getApplicationName(), $event->getLevel()->name, $event->getMessage()); $output = sprintf("[%s] [%s] [%s] %s" . PHP_EOL, self::getTimestamp(), $application->getApplicationName(), $event->getLevel()->name, $event->getMessage());
} }
if($event->getException() !== null)
{
$output .= self::outException($event->getException());
}
self::getLogger($application)->append($output); self::getLogger($application)->append($output);
} }
@ -88,9 +95,9 @@ class FileLogging implements LogHandlerInterface
$logging_file = $logging_directory . DIRECTORY_SEPARATOR . Utilities::sanitizeFileName($application->getApplicationName()) . '-' . date('Y-m-d') . '.log'; $logging_file = $logging_directory . DIRECTORY_SEPARATOR . Utilities::sanitizeFileName($application->getApplicationName()) . '-' . date('Y-m-d') . '.log';
if(!file_exists($logging_file) && !@touch($logging_file)) if(!file_exists($logging_file))
{ {
throw new RuntimeException(sprintf("Cannot write to %s due to insufficient permissions", $logging_file)); touch($logging_file);
} }
return $logging_file; return $logging_file;
@ -133,10 +140,7 @@ class FileLogging implements LogHandlerInterface
$output .= 'Stack Trace:' . PHP_EOL; $output .= 'Stack Trace:' . PHP_EOL;
foreach($trace as $item) foreach($trace as $item)
{ {
if(isset($item['file']) && isset($item['line'])) $output .= ' - ' . $item['file'] . ':' . $item['line'] . PHP_EOL;
{
$output .= ' - ' . $item['file'] . ':' . $item['line'] . PHP_EOL;
}
} }
} }

View file

@ -213,7 +213,42 @@
*/ */
public static function registerExceptionHandler(): void public static function registerExceptionHandler(): void
{ {
Runtime::registerExceptionHandler(); set_exception_handler(static function(Throwable $throwable)
{
try
{
self::error('Runtime', $throwable->getMessage(), $throwable);
}
catch(Exception)
{
return;
}
});
// Register error handler
set_error_handler(static function($errno, $errstr, $errfile, $errline)
{
// Convert error to exception and throw it
try
{
self::warning('Runtime', sprintf("%s:%s (%s) %s", $errfile, $errline, $errno, $errstr));
}
catch(Exception)
{
return;
}
});
register_shutdown_function(static function()
{
$error = error_get_last();
if ($error !== null && ($error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR)))
{
// Convert fatal error to exception and handle it
$exception = new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']);
self::error('Fatal', $exception->getMessage(), $exception);
}
});
} }
/** /**
@ -223,6 +258,7 @@
*/ */
public static function unregisterExceptionHandler(): void public static function unregisterExceptionHandler(): void
{ {
Runtime::unregisterExceptionHandler(); set_exception_handler(null);
} }
} }

View file

@ -1,124 +0,0 @@
<?php
namespace LogLib;
use ErrorException;
use Exception;
use Throwable;
class Runtime
{
/**
* Registers an exception handler that logs any uncaught exceptions as errors.
*
* @return void
*/
public static function registerExceptionHandler(): void
{
set_exception_handler([__CLASS__, 'exceptionHandler']);
set_error_handler([__CLASS__, 'errorHandler']);
register_shutdown_function([__CLASS__, 'shutdownHandler']);
}
/**
* Handles uncaught exceptions by logging them with a fatal error level.
*
* @param Throwable $throwable The exception or error that was thrown.
* @return void
*/
public static function exceptionHandler(Throwable $throwable): void
{
try
{
Log::Fatal('Runtime', $throwable->getMessage(), $throwable);
}
catch(Exception)
{
return;
}
}
/**
* Handles PHP errors by converting them to exceptions and logging appropriately.
*
* @param int $errno The level of the error raised.
* @param string $errstr The error message.
* @param string $errfile The filename that the error was raised in.
* @param int $errline The line number the error was raised at.
* @return bool True to prevent PHP's internal error handler from being invoked.
*/
public static function errorHandler(int $errno, string $errstr, string $errfile = '', int $errline = 0): bool
{
try
{
// Convert error to exception for consistent handling
$exception = new ErrorException($errstr, 0, $errno, $errfile, $errline);
// Handle different error types
switch ($errno)
{
case E_ERROR:
case E_PARSE:
case E_CORE_ERROR:
case E_COMPILE_ERROR:
case E_USER_ERROR:
Log::error('Runtime', $errstr, $exception);
break;
case E_USER_DEPRECATED:
case E_DEPRECATED:
case E_USER_NOTICE:
case E_NOTICE:
case E_USER_WARNING:
case E_WARNING:
default:
Log::warning('Runtime', $errstr, $exception);
break;
}
}
catch(Exception)
{
return false;
}
// Return true to prevent PHP's internal error handler
return true;
}
/**
* Handles script shutdown by checking for any fatal errors and logging them.
*
* This method is designed to be registered with the `register_shutdown_function`,
* and it inspects the last error that occurred using `error_get_last`. If a fatal
* error is detected, it logs the error details.
*
* @return void
*/
public static function shutdownHandler(): void
{
$error = error_get_last();
if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR]))
{
try
{
$exception = new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']);
Log::error('Fatal Error', $error['message'], $exception);
}
catch(Exception)
{
return;
}
}
}
/**
* Unregisters the currently registered exception handler.
*
* @return void
*/
public static function unregisterExceptionHandler(): void
{
set_exception_handler(null);
}
}