2023-01-29 17:36:21 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace EesyPHP;
|
|
|
|
|
|
|
|
use Throwable;
|
|
|
|
|
|
|
|
|
|
|
|
class Log {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Log file path
|
|
|
|
* @var string|null
|
|
|
|
*/
|
|
|
|
protected static $filepath = null;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Log file descriptor
|
|
|
|
* @var resource|null
|
|
|
|
*/
|
|
|
|
protected static $file_fd = null;
|
|
|
|
|
2023-02-27 23:43:47 +01:00
|
|
|
/**
|
|
|
|
* PHP error_log() fallback if no filepath configured or fail to open log file
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
protected static $error_log_fallback = true;
|
|
|
|
|
2023-01-29 17:36:21 +01:00
|
|
|
/*
|
|
|
|
* Log Levels
|
|
|
|
* @var array(string,int)
|
|
|
|
*/
|
|
|
|
protected static $levels = array(
|
|
|
|
'TRACE' => 0,
|
|
|
|
'DEBUG' => 1,
|
|
|
|
'INFO' => 2,
|
|
|
|
'WARNING' => 3,
|
|
|
|
'ERROR' => 4,
|
|
|
|
'FATAL' => 5,
|
|
|
|
);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Default log level
|
|
|
|
* @var string
|
|
|
|
*/
|
2023-02-16 01:53:08 +01:00
|
|
|
protected static $default_level = 'WARNING';
|
2023-01-29 17:36:21 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Current log level
|
|
|
|
* @var string|null
|
|
|
|
*/
|
|
|
|
protected static $level = null;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Log PHP errors levels (as specified to set_error_handler())
|
|
|
|
* Default (set in self::init() method):
|
|
|
|
* - In TRACE or DEBUG: E_ALL & ~E_STRICT
|
|
|
|
* - Otherwise: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
|
|
|
|
*/
|
|
|
|
protected static $php_errors_levels = null;
|
|
|
|
|
|
|
|
// Custom fatal error handler
|
|
|
|
protected static $fatal_error_handler = null;
|
|
|
|
|
2023-02-08 02:27:15 +01:00
|
|
|
/**
|
2023-01-29 17:36:21 +01:00
|
|
|
* Initialization
|
2023-02-08 02:27:15 +01:00
|
|
|
* @param string $filepath The log file path
|
|
|
|
* (optional, default: from log.file_path or log.cli_file_path is set)
|
|
|
|
* @param string|null $level The log level
|
|
|
|
* (optional, default: from log.level config key if set, otherwise,
|
|
|
|
* see self :: $default_level)
|
|
|
|
* @param int|null $php_errors_levels PHP errors level as expected by set_error_handler()
|
|
|
|
* (optional, default: from log.php_errors_levels if set, E_ALL & ~E_STRICT
|
|
|
|
* if level is TRACE or DEBUG, and E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
|
|
|
|
* otherwise)
|
2023-01-29 17:36:21 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2023-02-08 02:27:15 +01:00
|
|
|
public static function init($filepath=null, $level=null, $php_errors_levels=null) {
|
|
|
|
if ($filepath)
|
|
|
|
self :: $filepath = $filepath;
|
|
|
|
elseif (php_sapi_name() == 'cli')
|
2023-02-12 00:30:36 +01:00
|
|
|
self :: $filepath = App::get(
|
|
|
|
'log.cli_logfile_path', App::get('log.cli_file_path'));
|
2023-02-08 02:27:15 +01:00
|
|
|
else
|
2023-02-12 00:30:36 +01:00
|
|
|
self :: $filepath = App::get('log.file_path');
|
2023-01-29 17:36:21 +01:00
|
|
|
|
2023-02-27 23:43:47 +01:00
|
|
|
// PHP error_log() fallback
|
|
|
|
self :: $error_log_fallback = App::get('log.error_log_fallback', true, 'bool');
|
|
|
|
|
2023-01-30 00:11:26 +01:00
|
|
|
// Set log level
|
2023-02-12 00:30:36 +01:00
|
|
|
self :: set_level($level?$level:App::get('log.level'));
|
2023-01-29 17:36:21 +01:00
|
|
|
|
|
|
|
// Log PHP errors
|
2023-02-08 02:27:15 +01:00
|
|
|
if (!is_null($php_errors_levels)) {
|
2023-01-29 17:36:21 +01:00
|
|
|
self :: $php_errors_levels = $php_errors_levels;
|
2023-02-08 02:27:15 +01:00
|
|
|
}
|
2023-02-12 00:30:36 +01:00
|
|
|
elseif ($levels = App::get('log.php_errors_levels', array(), 'array')) {
|
2023-02-08 02:27:15 +01:00
|
|
|
$code = 'self :: $php_errors_levels = ';
|
|
|
|
while($level = array_shift($levels)) {
|
|
|
|
if (!is_string($level)) continue;
|
|
|
|
if (!defined($level) || !is_int(constant($level))) continue;
|
|
|
|
$code .= $level;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
foreach($levels as $level) {
|
|
|
|
if (!is_string($level)) continue;
|
|
|
|
$combine_operator = '&';
|
|
|
|
if (in_array($level[0], array('|', '^', '&'))) {
|
|
|
|
$combine_operator = $level[0];
|
|
|
|
$level = substr($level, 1);
|
|
|
|
}
|
|
|
|
$not = false;
|
|
|
|
if (in_array($level[0], array('!', '~'))) {
|
|
|
|
$not = $level[0];
|
|
|
|
$level = substr($level, 1);
|
|
|
|
}
|
|
|
|
if (!defined($level) || !is_int(constant($level))) continue;
|
|
|
|
$code .= " $combine_operator ";
|
|
|
|
if ($not) $code .= "$not";
|
|
|
|
$code .= $level;
|
|
|
|
}
|
|
|
|
$code .= ";";
|
|
|
|
eval($code);
|
|
|
|
}
|
|
|
|
elseif (in_array(self :: $level, array('DEBUG', 'TRACE'))) {
|
2023-01-29 17:36:21 +01:00
|
|
|
self :: $php_errors_levels = E_ALL & ~E_STRICT;
|
2023-02-08 02:27:15 +01:00
|
|
|
}
|
|
|
|
else {
|
2023-01-29 17:36:21 +01:00
|
|
|
self :: $php_errors_levels = E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED;
|
2023-02-08 02:27:15 +01:00
|
|
|
}
|
2023-01-29 17:36:21 +01:00
|
|
|
set_error_handler(array('EesyPHP\\Log', 'on_php_error'), self :: $php_errors_levels);
|
|
|
|
|
|
|
|
// Log uncatched exceptions
|
|
|
|
set_exception_handler(array('EesyPHP\\Log', 'exception'));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Log a message
|
|
|
|
* @param string $level The message level (key of self :: $levels)
|
|
|
|
* @param string $message The message to log
|
|
|
|
* @param array $extra_args Extra arguments to use to compute message using sprintf
|
|
|
|
* @return true
|
|
|
|
*/
|
|
|
|
public static function log($level, $message, ...$extra_args) {
|
2023-02-25 05:02:27 +01:00
|
|
|
global $argv;
|
2023-01-29 17:36:21 +01:00
|
|
|
|
|
|
|
if (!array_key_exists($level, self :: $levels)) $level = self :: $default_level;
|
|
|
|
if (self :: $levels[$level] < self :: $levels[self :: $level]) return true;
|
2023-02-08 02:27:15 +01:00
|
|
|
if(self :: $filepath && is_null(self :: $file_fd)) {
|
2023-01-29 17:36:21 +01:00
|
|
|
self :: $file_fd = fopen(self :: $filepath, 'a');
|
2023-02-27 23:43:47 +01:00
|
|
|
if (self :: $file_fd === false)
|
|
|
|
self :: error('Fail to open log file (%s)', self :: $filepath);
|
2023-01-29 17:36:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Extra arguments passed, format message using sprintf
|
|
|
|
if ($extra_args) {
|
|
|
|
$message = call_user_func_array(
|
|
|
|
'sprintf',
|
|
|
|
array_merge(array($message), $extra_args)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (php_sapi_name() == "cli") {
|
|
|
|
$msg = implode(' - ', array(
|
|
|
|
date('Y/m/d H:i:s'),
|
|
|
|
basename($argv[0]),
|
|
|
|
$level,
|
|
|
|
$message
|
|
|
|
))."\n";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$msg = array(
|
|
|
|
date('Y/m/d H:i:s'),
|
|
|
|
$_SERVER['REQUEST_URI'],
|
|
|
|
$_SERVER['REMOTE_ADDR'],
|
|
|
|
);
|
2023-02-25 05:02:27 +01:00
|
|
|
if (Auth::enabled())
|
2023-02-25 14:49:06 +01:00
|
|
|
$msg[] = (Auth::user()?Auth::user():'anonymous');
|
2023-01-29 17:36:21 +01:00
|
|
|
$msg[] = $level;
|
|
|
|
$msg[] = $message;
|
|
|
|
$msg = implode(' - ', $msg)."\n";
|
|
|
|
}
|
2023-02-08 02:27:15 +01:00
|
|
|
if (self :: $file_fd)
|
|
|
|
fwrite(self :: $file_fd, $msg);
|
2023-02-27 23:43:47 +01:00
|
|
|
elseif (self :: $error_log_fallback)
|
|
|
|
error_log($msg);
|
2023-01-29 17:36:21 +01:00
|
|
|
|
|
|
|
if ($level == 'FATAL')
|
|
|
|
if (!is_null(self :: $fatal_error_handler))
|
|
|
|
call_user_func(self :: $fatal_error_handler, $message);
|
|
|
|
elseif (function_exists('fatal_error'))
|
|
|
|
fatal_error($message);
|
|
|
|
else
|
|
|
|
die("\n$message\n\n");
|
|
|
|
elseif (php_sapi_name() == "cli")
|
|
|
|
echo $msg;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Log a trace message
|
|
|
|
* @param string $message The message to log
|
|
|
|
* @param array $extra_args Extra arguments to use to compute message using sprintf
|
|
|
|
* @return true
|
|
|
|
*/
|
|
|
|
public static function trace($message, ...$extra_args) {
|
|
|
|
return call_user_func_array(
|
|
|
|
array('EesyPHP\\Log', 'log'),
|
|
|
|
array_merge(array('TRACE', $message), $extra_args)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Log a debug message
|
|
|
|
* @param string $message The message to log
|
|
|
|
* @param array $extra_args Extra arguments to use to compute message using sprintf
|
|
|
|
* @return true
|
|
|
|
*/
|
|
|
|
public static function debug($message, ...$extra_args) {
|
|
|
|
return call_user_func_array(
|
|
|
|
array('EesyPHP\\Log', 'log'),
|
|
|
|
array_merge(array('DEBUG', $message), $extra_args)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Log an info message
|
|
|
|
* @param string $message The message to log
|
|
|
|
* @param array $extra_args Extra arguments to use to compute message using sprintf
|
|
|
|
* @return true
|
|
|
|
*/
|
|
|
|
public static function info($message, ...$extra_args) {
|
|
|
|
return call_user_func_array(
|
|
|
|
array('EesyPHP\\Log', 'log'),
|
|
|
|
array_merge(array('INFO', $message), $extra_args)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Log an warning message
|
|
|
|
* @param string $message The message to log
|
|
|
|
* @param array $extra_args Extra arguments to use to compute message using sprintf
|
|
|
|
* @return true
|
|
|
|
*/
|
|
|
|
public static function warning($message, ...$extra_args) {
|
|
|
|
return call_user_func_array(
|
|
|
|
array('EesyPHP\\Log', 'log'),
|
|
|
|
array_merge(array('WARNING', $message), $extra_args)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Log an error message
|
|
|
|
* @param string $message The message to log
|
|
|
|
* @param array $extra_args Extra arguments to use to compute message using sprintf
|
|
|
|
* @return true
|
|
|
|
*/
|
|
|
|
public static function error($message, ...$extra_args) {
|
|
|
|
return call_user_func_array(
|
|
|
|
array('EesyPHP\\Log', 'log'),
|
|
|
|
array_merge(array('ERROR', $message), $extra_args)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Log an fatal message
|
|
|
|
* @param string $message The message to log
|
|
|
|
* @param array $extra_args Extra arguments to use to compute message using sprintf
|
|
|
|
* @return true
|
|
|
|
*/
|
|
|
|
public static function fatal($message, ...$extra_args) {
|
|
|
|
return call_user_func_array(
|
|
|
|
array('EesyPHP\\Log', 'log'),
|
|
|
|
array_merge(array('FATAL', $message), $extra_args)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register a contextual fatal error handler
|
|
|
|
* @param null|callable $handler The fatal error handler (set as null to reset)
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public static function register_fatal_error_handler($handler) {
|
|
|
|
// @phpstan-ignore-next-line
|
|
|
|
if ($handler && !is_callable($handler))
|
|
|
|
self :: fatal('Invalid fatal error handler provided: it is not callable !');
|
|
|
|
self :: $fatal_error_handler = ($handler?$handler:null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Change of current log file
|
|
|
|
* @param string $file The new log file path
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function change_filepath($file) {
|
|
|
|
if ($file == self :: $filepath) return True;
|
|
|
|
if (self :: $file_fd) {
|
|
|
|
fclose(self :: $file_fd);
|
|
|
|
self :: $file_fd = null;
|
|
|
|
}
|
|
|
|
self :: $filepath = $file;
|
|
|
|
return True;
|
|
|
|
}
|
|
|
|
|
2023-02-27 18:09:15 +01:00
|
|
|
/**
|
|
|
|
* Get of current log file
|
|
|
|
* @return string|null Current log file path
|
|
|
|
*/
|
|
|
|
public static function filepath() {
|
|
|
|
return self :: $filepath;
|
|
|
|
}
|
|
|
|
|
2023-01-30 00:11:26 +01:00
|
|
|
/**
|
|
|
|
* Set current log level
|
|
|
|
* @param string $level The new log level to set
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public static function set_level($level=null) {
|
|
|
|
// Set default log level (if not defined or invalid)
|
|
|
|
if (is_null($level)) {
|
|
|
|
self :: $level = self :: $default_level;
|
|
|
|
}
|
|
|
|
elseif (!array_key_exists($level, self :: $levels)) {
|
|
|
|
self :: $level = self :: $default_level;
|
|
|
|
self :: warning(
|
|
|
|
"Invalid log level value found in configuration (%s). ".
|
2023-02-08 02:27:15 +01:00
|
|
|
"Set as default (%s).", vardump($level), self :: $default_level);
|
2023-01-30 00:11:26 +01:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
self :: $level = $level;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-29 17:36:21 +01:00
|
|
|
/*
|
|
|
|
*******************************************************************************
|
|
|
|
* Handle exception logging
|
|
|
|
*******************************************************************************
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the current backtrace
|
|
|
|
* @param int $ignore_last The number of last levels to ignore
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public static function get_debug_backtrace_context($ignore_last=0) {
|
|
|
|
$traces = debug_backtrace();
|
|
|
|
|
|
|
|
// Also ignore this function it self
|
|
|
|
$ignore_last++;
|
|
|
|
|
|
|
|
if (!is_array($traces) || count($traces) <= $ignore_last)
|
|
|
|
return "";
|
|
|
|
|
|
|
|
$msg = array();
|
|
|
|
for ($i=$ignore_last; $i < count($traces); $i++) {
|
|
|
|
$trace = array("#$i");
|
|
|
|
if (isset($traces[$i]['file']))
|
|
|
|
$trace[] = $traces[$i]['file'].(isset($traces[$i]['line'])?":".$traces[$i]['line']:"");
|
|
|
|
// @phpstan-ignore-next-line
|
|
|
|
if (isset($traces[$i]['class']) && isset($traces[$i]['function']))
|
|
|
|
$trace[] = implode(" ", array(
|
|
|
|
$traces[$i]['class'],
|
|
|
|
$traces[$i]['type'],
|
|
|
|
$traces[$i]['function']. "()"));
|
|
|
|
elseif (isset($traces[$i]['function']))
|
|
|
|
$trace[] = $traces[$i]['function']. "()";
|
|
|
|
$msg[] = implode(" - ", $trace);
|
|
|
|
}
|
|
|
|
|
|
|
|
return implode("\n", $msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Log an exception
|
|
|
|
* @param Throwable $exception
|
|
|
|
* @param string|null $prefix The prefix of the log message
|
|
|
|
* (optional, default: "An exception occured")
|
2023-01-29 19:18:52 +01:00
|
|
|
* @param array $extra_args Extra arguments to use to compute prefix using sprintf
|
2023-01-29 17:36:21 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2023-01-29 19:18:52 +01:00
|
|
|
public static function exception($exception, $prefix=null, ...$extra_args) {
|
2023-01-29 18:17:50 +01:00
|
|
|
SentryIntegration :: log($exception);
|
2023-01-29 19:18:52 +01:00
|
|
|
// If extra arguments passed, format prefix message using sprintf
|
|
|
|
if ($prefix && $extra_args) {
|
2023-01-29 17:36:21 +01:00
|
|
|
$prefix = call_user_func_array(
|
|
|
|
'sprintf',
|
2023-01-29 19:18:52 +01:00
|
|
|
array_merge(array($prefix), $extra_args)
|
2023-01-29 17:36:21 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
self :: error(
|
|
|
|
"%s:\n%s\n## %s:%d : %s",
|
|
|
|
($prefix?$prefix:"An exception occured"),
|
2023-02-23 11:58:19 +01:00
|
|
|
$exception->getTraceAsString(),
|
2023-01-29 17:36:21 +01:00
|
|
|
$exception->getFile(), $exception->getLine(),
|
|
|
|
$exception->getMessage());
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
*******************************************************************************
|
|
|
|
* Handle PHP error logging
|
|
|
|
*******************************************************************************
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert PHP error number to the corresponding label
|
|
|
|
* @param int $errno
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public static function errno2type($errno) {
|
|
|
|
$constants = get_defined_constants();
|
|
|
|
if (is_array($constants))
|
|
|
|
foreach($constants as $label => $value)
|
|
|
|
if ($value == $errno && preg_match('/^E_(.*)$/', $label, $m))
|
|
|
|
return $m[1];
|
|
|
|
return 'UNKNOWN ERROR #'.$errno;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Log a PHP error
|
|
|
|
* Note: method design to be used as callable by set_error_handler()
|
|
|
|
* @param int $errno The error number
|
|
|
|
* @param string $errstr The error message
|
|
|
|
* @param string $errfile The filename that the error was raised in
|
|
|
|
* @param int $errline The line number where the error was raised
|
|
|
|
* @return false Return false to let the normal error handler continues.
|
|
|
|
*/
|
|
|
|
public static function on_php_error($errno, $errstr, $errfile, $errline) {
|
2023-01-29 18:17:50 +01:00
|
|
|
self :: error(
|
2023-01-29 17:36:21 +01:00
|
|
|
"A PHP error occured : [%s] %s\nFile : %s (line : %d)",
|
|
|
|
self :: errno2type($errno), $errstr, $errfile, $errline
|
|
|
|
);
|
|
|
|
return False;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab
|