*/ 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