Skip to content

Instantly share code, notes, and snippets.

@spajak
Last active June 30, 2019 12:55
Show Gist options
  • Save spajak/4e50a139d9e553b7ba8974b629bf9768 to your computer and use it in GitHub Desktop.
Save spajak/4e50a139d9e553b7ba8974b629bf9768 to your computer and use it in GitHub Desktop.
PHP 7 runtime errors/exceptions handling done right
<?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