eesyphp/src/SentryIntegration.php

153 lines
4.5 KiB
PHP

<?php
namespace EesyPHP;
use Throwable;
use Exception;
class SentryIntegration {
/**
* Sentry DSN
* @var string|null
*/
protected static $dsn = null;
/**
* Types of PHP error to log in Sentry
* @see https://www.php.net/manual/fr/errorfunc.constants.php
* @var array<int>
*/
protected static $php_error_types;
/**
* Log level
* @var string|null
*/
protected static $log_level = null;
/**
* Initialization
* @return void
*/
public static function init() {
// In phpstan context, do not initialize
if (defined('__PHPSTAN_RUNNING__') && constant('__PHPSTAN_RUNNING__')) // @phpstan-ignore-line
return;
// Set config default values
App :: set_default(
'sentry',
[
'dsn' => null,
'traces_sample_rate' => 0.2,
'php_error_types' => [
'E_ERROR', 'E_PARSE', 'E_CORE_ERROR', 'E_COMPILE_ERROR', 'E_USER_ERROR',
'E_RECOVERABLE_ERROR', 'E_DEPRECATED',
],
'log_level' => 'FATAL',
]
);
self :: $dsn = App::get('sentry.dsn');
if (!self :: $dsn) {
Log :: trace("SentryIntegration::init(): no DSN configured");
return;
}
self :: $log_level = App::get('sentry.log_level', null, 'string');
if (!Log::check_level(self :: $log_level)) {
$bad_level = self :: $log_level;
self :: $log_level = App::get_default("sentry.log_level");
Log::warning(
"Invalid log level configured for Sentry (%s), use default (%s)",
$bad_level,
self :: $log_level
);
}
\Sentry\init([
'dsn' => self :: $dsn,
'traces_sample_rate' => App::get('sentry.traces_sample_rate', null, 'float'),
]);
\Sentry\configureScope(function (\Sentry\State\Scope $scope): void {
$scope->setUser([
'username' => Auth::user()?Auth::user()->username:null,
'ip_address' => php_sapi_name()=='cli'?null:$_SERVER['REMOTE_ADDR'],
]);
});
self :: $php_error_types = [];
foreach(App::get('sentry.php_error_types', null, 'array') as $php_error_type) {
if (is_string($php_error_type) && defined($php_error_type))
$php_error_type = constant($php_error_type);
if (!is_int($php_error_type)) continue;
if (in_array($php_error_type, self :: $php_error_types)) continue;
self :: $php_error_types[] = $php_error_type;
}
set_error_handler(
[static :: class, 'on_php_error'],
E_ALL
);
}
/**
* Log an exception or a message in Sentry
* @param string|Throwable $message
* @param array $extra_args Extra arguments to use to compute message using sprintf
* @return void
*/
public static function log($message, ...$extra_args) {
if (!self :: $dsn) {
Log :: trace('Sentry DSN not configured, do not log this error');
return;
}
if (is_string($message)) {
// Extra arguments passed, format message using sprintf
if ($extra_args) {
$message = call_user_func_array(
'sprintf',
array_merge([$message], $extra_args)
);
}
Log :: debug('Error logged in Sentry');
\Sentry\captureMessage($message);
}
elseif ($message instanceof Exception) {
Log :: debug('Exception logged in Sentry');
\Sentry\captureException($message);
}
}
/**
* Log a PHP error in Sentry
* 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) {
if (in_array($errno, self :: $php_error_types))
self :: log(
"A PHP error occurred : [%s] %s\nFile : %s (line : %d)",
Log :: errno2type($errno), $errstr, $errfile, $errline
);
return false;
}
/**
* Method to run on logged message (directly called by Log::log())
* Important: This method can't log anything to avoid log loop.
* @param string $level
* @param string $message
* @return void
*/
public static function on_logged_message($level, $message) {
if (self :: $dsn && Log::check_message_level($level, self :: $log_level))
\Sentry\captureMessage($message);
}
}
# vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab