Last active
June 30, 2019 12:55
-
-
Save spajak/4e50a139d9e553b7ba8974b629bf9768 to your computer and use it in GitHub Desktop.
PHP 7 runtime errors/exceptions handling done right
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Simple, custom, bulletproof PHP 7 runtime errors/exceptions handling done right (I hope so :). | |
* By handling I mean logging to systemd journal or stderr, and die if any exception, | |
* error, warning, and even notice or strict occurs. This is not all-in-one | |
* or an ultimate solusion, but a good base to start with. | |
* Logs go to stderr or systemd journal (recommended, but optional). Systemd extension | |
* for PHP can be found at https://github.com/systemd/php-systemd | |
*/ | |
# Available log levels | |
#--------------------- | |
# LOG_EMERG (0) | |
# LOG_ALERT (1) | |
# LOG_CRIT (2) | |
# LOG_ERR (3) | |
# LOG_WARNING (4) | |
# LOG_NOTICE (5) | |
# LOG_INFO (6) | |
# LOG_DEBUG (7) | |
# Where to log; journal (systemd) or php://stderr (or a file) | |
if (!defined('MY_ERROR_LOG')) { | |
define('MY_ERROR_LOG', 'php://stderr'); | |
} | |
# Log level. Debug level logs stack traces | |
if (!defined('MY_LOG_LEVEL')) { | |
define('MY_LOG_LEVEL', LOG_DEBUG); | |
} | |
# Log app name (facility, identifier) | |
if (!defined('MY_LOG_FACILITY')) { | |
define('MY_LOG_FACILITY', 'spblue'); | |
} | |
# Log date format (not used with systemd) | |
if (!defined('MY_LOG_DATE_FORMAT')) { | |
define('MY_LOG_DATE_FORMAT', 'Y-m-d H:i:s'); | |
} | |
# Report everything. This is important | |
error_reporting(E_ALL); | |
/** | |
* Pretty exceptions format | |
*/ | |
function formatException(Throwable $e) | |
{ | |
$type = get_class($e); | |
if ($e instanceof ErrorException) { | |
# Only ErrorException has PHP severity | |
switch ($e->getSeverity()) { | |
case E_NOTICE: $type = 'PHP Notice'; break; | |
case E_WARNING: $type = 'PHP Warning'; break; | |
case E_STRICT: $type = 'PHP Strict'; break; | |
default: $type = 'PHP Error'; | |
} | |
} | |
$message = 'We have a problem: [%s]: %s, in file %s at line %s.'; | |
$args = [ | |
$type, | |
$e->getMessage() ?: '<no message>', | |
$e->getFile() ?: '<unknown file>', | |
$e->getLine() ?: '<unknown line>' | |
]; | |
if (MY_LOG_LEVEL >= LOG_DEBUG) { | |
$message .= "\nStack trace:\n%s"; | |
$args[] = $e->getTraceAsString(); | |
} | |
return sprintf($message, ...$args); | |
} | |
/** | |
* Useful alias | |
*/ | |
function logException(Throwable $e) | |
{ | |
logError(formatException($e)); | |
} | |
/** | |
* Be strict. None shall pass. | |
*/ | |
set_error_handler(function($severity, $message, $file, $line) { | |
if (error_reporting() !== 0) { // <== Error was NOT suppressed with @. Log this, we will die | |
// This is a very neat way to have consistent message with stack trace | |
logException(new ErrorException($message, 0, $severity, $file, $line)); | |
} | |
if (error_reporting() === 0) { | |
return true; // Error suppressed with @. Continue script execution and don't fire default PHP handler | |
} | |
die(); | |
}, E_ALL); | |
/** | |
* Log, log and log (and die) | |
*/ | |
set_exception_handler(function(Throwable $e) { | |
// As simple as that | |
logException($e); | |
die(); | |
}); | |
function logError($message, ...$args) | |
{ | |
errorLog(LOG_ERR, $message, ...$args); | |
} | |
function logWarning($message, ...$args) | |
{ | |
errorLog(LOG_WARNING, $message, ...$args); | |
} | |
function logNotice($message, ...$args) | |
{ | |
errorLog(LOG_NOTICE, $message, ...$args); | |
} | |
function logInfo($message, ...$args) | |
{ | |
errorLog(LOG_INFO, $message, ...$args); | |
} | |
function logDebug($message, ...$args) | |
{ | |
errorLog(LOG_DEBUG, $message, ...$args); | |
} | |
function logLevelToString($level) | |
{ | |
$type = null; | |
switch ($level) { | |
case LOG_DEBUG: $type = 'DEBUG'; break; | |
case LOG_INFO: $type = 'INFO'; break; | |
case LOG_NOTICE: $type = 'NOTICE'; break; | |
case LOG_WARNING: $type = 'WARNING'; break; | |
default: $type = 'ERROR'; break; | |
} | |
return $type; | |
} | |
/** | |
* Log error to php://stderr or systemd journal. Die with a reason | |
* if something goes wrong. | |
*/ | |
function errorLog($level, $message, ...$args) | |
{ | |
$level = max(min(LOG_DEBUG, (int) $level), LOG_EMERG); | |
if ($level > MY_LOG_LEVEL) { | |
return; | |
} | |
if (!empty($args) and false != $m = @sprintf($message, ...$args)) { | |
$message = $m; | |
} | |
if (MY_ERROR_LOG === 'journal' or MY_ERROR_LOG === 'systemd') { | |
$logTo = extension_loaded('systemd') ? 'journal' : 'php://stderr'; | |
} else { | |
$logTo = MY_ERROR_LOG; | |
} | |
if ($logTo === 'journal') { | |
$args = [ | |
sprintf('MESSAGE=%s', $message), | |
sprintf('PRIORITY=%s', $level), | |
sprintf('SYSLOG_IDENTIFIER=%s', MY_LOG_FACILITY) | |
]; | |
if (!sd_journal_send(...$args)) { | |
die('Unable to log to systemd journal'); | |
} | |
} else { | |
$message = sprintf( | |
"[%s] [%s] %s: %s\n", | |
date(MY_LOG_DATE_FORMAT), | |
MY_LOG_FACILITY, | |
logLevelToString($level), | |
$message | |
); | |
if (false !== $file = @fopen($logTo, 'a')) { | |
if (!fwrite($file, $message)) { | |
fclose($file); | |
die('Unable to write to log file'); | |
} | |
fclose($file); | |
} else { | |
die('Unable to open log file'); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment