raven.module in Raven: Sentry Integration 7.4
Same filename and directory in other branches
Allows to track errors to Sentry server.
File
raven.moduleView source
<?php
/**
* @file
* Allows to track errors to Sentry server.
*/
use Sentry\Breadcrumb;
use Sentry\ClientInterface;
use Sentry\Dsn;
use Sentry\Event;
use Sentry\EventHint;
use Sentry\Integration\EnvironmentIntegration;
use Sentry\Integration\FatalErrorListenerIntegration;
use Sentry\Integration\FrameContextifierIntegration;
use Sentry\Integration\RequestIntegration;
use Sentry\Integration\TransactionIntegration;
use Sentry\SentrySdk;
use Sentry\Serializer\RepresentationSerializer;
use Sentry\Severity;
use Sentry\StacktraceBuilder;
use Sentry\State\Scope;
use Sentry\UserDataBag;
/**
* Implements hook_form_system_logging_settings_alter().
*/
function raven_form_system_logging_settings_alter(array &$form, array &$form_state) {
module_load_include('admin.inc', 'raven');
raven_settings_form($form, $form_state);
}
/**
* Implements hook_permission().
*/
function raven_permission() {
return [
'send javascript errors to sentry' => [
'title' => t('Send JavaScript errors to Sentry'),
'description' => t('For users with this permission, JavaScript errors will be captured and sent to Sentry.'),
],
];
}
/**
* Implements hook_init().
*/
function raven_init() {
if (!variable_get('raven_enabled', FALSE) || !raven_get_client()) {
return;
}
// Bind the logged-in user.
\Sentry\configureScope(function (Scope $scope) : void {
global $user;
// Bind the logged in user.
$context['id'] = $user->uid;
$context['ip_address'] = ip_address();
$context['roles'] = implode(', ', $user->roles);
if (user_is_logged_in() && variable_get('raven_send_user_data', FALSE)) {
$context['username'] = $user->name;
$context['email'] = $user->mail;
}
drupal_alter('raven_user', $context);
$scope
->setUser($context);
});
// Tag the request with something interesting.
\Sentry\configureScope(function (Scope $scope) : void {
$context = [];
drupal_alter('raven_tags', $context);
$scope
->setTags($context);
});
// Provide a bit of additional context.
\Sentry\configureScope(function (Scope $scope) : void {
$context = [];
drupal_alter('raven_extra', $context);
$scope
->setExtras($context);
});
}
/**
* Implements hook_page_build().
*/
function raven_page_build(&$page) {
if (variable_get('raven_js_enabled', FALSE) && user_access('send javascript errors to sentry')) {
drupal_add_library('raven', 'raven', TRUE);
}
}
/**
* Implements hook_library().
*/
function raven_library() {
global $user;
$libraries['raven']['version'] = '6.12.0';
$path = drupal_get_path('module', 'raven');
$libraries['raven']['js']["{$path}/js/bundle.tracing.min.js"] = [];
$options = new stdClass();
$options->dsn = empty($_SERVER['SENTRY_DSN']) ? variable_get('raven_public_dsn', '') : $_SERVER['SENTRY_DSN'];
if ($environment = empty($_SERVER['SENTRY_ENVIRONMENT']) ? variable_get('raven_environment') : $_SERVER['SENTRY_ENVIRONMENT']) {
$options->environment = $environment;
}
if ($release = empty($_SERVER['SENTRY_RELEASE']) ? variable_get('raven_release') : $_SERVER['SENTRY_RELEASE']) {
$options->release = $release;
}
$options->autoSessionTracking = variable_get('raven_auto_session_tracking', FALSE);
$options->sendClientReports = variable_get('raven_send_client_reports', FALSE);
if ($tracesSampleRate = variable_get('raven_js_traces_sample_rate', NULL)) {
$options->tracesSampleRate = (double) $tracesSampleRate;
}
$options->integrations = [];
$libraries['raven']['js'][] = [
'data' => [
'raven' => [
// Other modules can alter the @sentry/browser options.
'options' => $options,
'user' => [
'id' => $user->uid,
],
],
],
'type' => 'setting',
];
$libraries['raven']['js']["{$path}/js/raven.js"] = [
// Load in the footer to ensure settings are available.
'scope' => 'footer',
];
return $libraries;
}
/**
* Implements hook_watchdog().
*/
function raven_watchdog($log_entry) {
static $counter = 0;
if (!variable_get('raven_enabled', FALSE)) {
return;
}
$client = raven_get_client();
if (!$client) {
return;
}
$watchdog_levels = variable_get('raven_watchdog_levels', []);
$levels_map = [
WATCHDOG_EMERGENCY => Severity::FATAL,
WATCHDOG_ALERT => Severity::FATAL,
WATCHDOG_CRITICAL => Severity::FATAL,
WATCHDOG_ERROR => Severity::ERROR,
WATCHDOG_WARNING => Severity::WARNING,
WATCHDOG_NOTICE => Severity::INFO,
WATCHDOG_INFO => Severity::INFO,
WATCHDOG_DEBUG => Severity::DEBUG,
];
$variables = $log_entry['variables'];
if (!$variables) {
$variables = [];
}
$event = Event::createEvent();
$event
->setLevel(new Severity($levels_map[$log_entry['severity']]));
$message = html_entity_decode(strip_tags(strtr($log_entry['message'], $variables)), ENT_QUOTES, 'UTF-8');
$event
->setMessage($log_entry['message'], $variables, $message);
$extra = [
'request_uri' => $log_entry['request_uri'],
];
if ($log_entry['referer']) {
$extra['referer'] = $log_entry['referer'];
}
if ($log_entry['link']) {
$extra['link'] = $log_entry['link'];
}
$event
->setExtra($extra);
$event
->setLogger($log_entry['type']);
$user = UserDataBag::createFromUserIdentifier($log_entry['uid']);
$user
->setIpAddress($log_entry['ip']);
if ($log_entry['user'] && $log_entry['uid']) {
if (variable_get('raven_send_user_data', FALSE)) {
$user
->setEmail($log_entry['user']->mail);
$user
->setUsername($log_entry['user']->name);
}
$user
->setMetadata('roles', implode(', ', $log_entry['user']->roles));
}
$event
->setUser($user);
$filter = [
'process' => !empty($watchdog_levels[$log_entry['severity'] + 1]),
'log_entry' => $log_entry,
'event' => $event,
];
$ignored_types = array_map('trim', preg_split('/\\R/', variable_get('raven_ignored_types', ''), -1, PREG_SPLIT_NO_EMPTY));
if (in_array($log_entry['type'], $ignored_types)) {
$filter['process'] = FALSE;
}
drupal_alter('raven_watchdog_filter', $filter);
if ($filter['process']) {
// Save memory by not copying the object for each frame.
$stack = debug_backtrace(0);
// Ignore error handling and logging frames.
if (empty($stack[0]['class']) && isset($stack[0]['function']) && $stack[0]['function'] == 'raven_watchdog') {
array_shift($stack);
}
if (empty($stack[0]['class']) && isset($stack[0]['function']) && $stack[0]['function'] == 'call_user_func_array') {
array_shift($stack);
}
if (empty($stack[0]['class']) && isset($stack[0]['function']) && ($stack[0]['function'] == 'module_invoke_all' || $stack[0]['function'] == 'module_invoke')) {
array_shift($stack);
}
if (empty($stack[0]['class']) && isset($stack[0]['function']) && $stack[0]['function'] == 'watchdog' && empty($stack[1]['class']) && isset($stack[1]['function']) && $stack[1]['function'] == 'watchdog_exception') {
array_shift($stack);
}
elseif (empty($stack[0]['class']) && isset($stack[0]['function']) && $stack[0]['function'] == 'watchdog' && empty($stack[1]['class']) && isset($stack[1]['function']) && $stack[1]['function'] == '_drupal_log_error') {
array_shift($stack);
array_shift($stack);
}
if (empty($stack[0]['class']) && isset($stack[0]['function']) && $stack[0]['function'] == '_drupal_error_handler_real') {
array_shift($stack);
}
if (empty($stack[0]['class']) && isset($stack[0]['function']) && $stack[0]['function'] == '_drupal_error_handler' && empty($stack[0]['line'])) {
array_shift($stack);
}
if (empty($stack[0]['class']) && isset($stack[0]['function']) && ($stack[0]['function'] == 'watchdog_exception' || $stack[0]['function'] == '_drupal_exception_handler')) {
$arg = [
'watchdog_exception' => 1,
'_drupal_exception_handler' => 0,
];
// Use the exception backtrace for (usually) easier debugging.
$exception = $stack[0]['args'][$arg[$stack[0]['function']]];
$stack = $exception
->getTrace();
// Copy logic from _drupal_decode_exception().
array_unshift($stack, [
'line' => $exception
->getLine(),
'file' => $exception
->getFile(),
]);
if ($exception instanceof PDOException) {
$db_functions = [
'db_query',
'db_query_range',
];
while (!empty($stack[1]) && ($caller = $stack[1]) && (isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE) || in_array($caller['function'], $db_functions))) {
array_shift($stack);
}
}
}
if (!variable_get('raven_trace', FALSE)) {
foreach ($stack as &$frame) {
unset($frame['args']);
}
}
$stacktraceBuilder = new StacktraceBuilder($client
->getOptions(), new RepresentationSerializer($client
->getOptions()));
$stacktrace = $stacktraceBuilder
->buildFromBacktrace($stack, '', 0);
$stacktrace
->removeFrame(count($stacktrace
->getFrames()) - 1);
$rateLimit = variable_get('raven_rate_limit', 0);
if (!$rateLimit || $counter < $rateLimit) {
\Sentry\captureEvent($event, EventHint::fromArray([
'stacktrace' => $stacktrace,
]));
}
elseif ($counter == $rateLimit) {
\Sentry\captureException(new RavenRateLimitException('Log event discarded due to rate limit exceeded; future log events will not be captured by Sentry.'));
}
$counter++;
}
// Record a breadcrumb.
$breadcrumb = [
'log_entry' => $log_entry,
'process' => TRUE,
'breadcrumb' => [
'category' => $log_entry['type'],
'message' => $message,
'level' => $levels_map[$log_entry['severity']],
],
];
foreach ([
'%line',
'%file',
'%type',
'%function',
] as $key) {
if (isset($log_entry['variables'][$key])) {
$breadcrumb['breadcrumb']['data'][substr($key, 1)] = $log_entry['variables'][$key];
}
}
drupal_alter('raven_breadcrumb', $breadcrumb);
if (!empty($breadcrumb['process'])) {
\Sentry\addBreadcrumb(Breadcrumb::fromArray($breadcrumb['breadcrumb']));
}
}
/**
* Returns the Sentry PHP client instance, or NULL if it could not be created.
*
* @return \Sentry\ClientInterface|null
* Sentry PHP client instance.
*/
function raven_get_client() : ?ClientInterface {
if (!class_exists(SentrySdk::class)) {
return NULL;
}
if ($client = SentrySdk::getCurrentHub()
->getClient()) {
return $client;
}
$options = [
'default_integrations' => FALSE,
'dsn' => empty($_SERVER['SENTRY_DSN']) ? variable_get('raven_dsn', NULL) : $_SERVER['SENTRY_DSN'],
];
if (variable_get('raven_stack', TRUE)) {
$options['attach_stacktrace'] = TRUE;
}
if (variable_get('raven_fatal_error_handler', TRUE)) {
$options['integrations'][] = new FatalErrorListenerIntegration();
}
$options['integrations'][] = new RequestIntegration();
$options['integrations'][] = new TransactionIntegration();
$options['integrations'][] = new FrameContextifierIntegration();
$options['integrations'][] = new EnvironmentIntegration();
$options['integrations'][] = new RavenSanitizeIntegration();
if ($environment = empty($_SERVER['SENTRY_ENVIRONMENT']) ? variable_get('raven_environment') : $_SERVER['SENTRY_ENVIRONMENT']) {
$options['environment'] = $environment;
}
if ($release = empty($_SERVER['SENTRY_RELEASE']) ? variable_get('raven_release') : $_SERVER['SENTRY_RELEASE']) {
$options['release'] = $release;
}
if (!variable_get('raven_send_request_body', FALSE)) {
$options['max_request_body_size'] = 'none';
}
// Allow other modules to alter $options before passing into Raven client.
drupal_alter('raven_options', $options);
try {
\Sentry\init($options);
} catch (InvalidArgumentException $e) {
// Raven is incorrectly configured.
return NULL;
}
\Sentry\configureScope(function (Scope $scope) : void {
global $user;
$context['id'] = $user ? $user->uid : 0;
$context['ip_address'] = ip_address();
$scope
->setUser($context);
});
return SentrySdk::getCurrentHub()
->getClient();
}
/**
* Appends additional context.
*
* @param array $data
* Associative array of extra data.
*/
function raven_extra_context(array $data = []) {
if (raven_get_client()) {
\Sentry\configureScope(function (Scope $scope) use ($data) : void {
$scope
->setExtras($data);
});
}
}
/**
* Appends tags context.
*
* @param array $data
* Associative array of tags.
*/
function raven_tags_context(array $data = []) {
if (raven_get_client()) {
\Sentry\configureScope(function (Scope $scope) use ($data) : void {
$scope
->setTags($data);
});
}
}
/**
* Sends all unsent events.
*
* Call this function periodically if you have a long-running script or
* are processing a large set of data which may generate errors.
*/
function raven_flush() {
if ($client = raven_get_client()) {
$client
->flush();
}
}
/**
* Implements hook_seckit_options_alter().
*/
function raven_seckit_options_alter(&$options) {
if (!class_exists(Dsn::class)) {
return;
}
try {
$dsn = Dsn::createFromString(empty($_SERVER['SENTRY_DSN']) ? variable_get('raven_public_dsn') : $_SERVER['SENTRY_DSN']);
} catch (InvalidArgumentException $e) {
// Raven is incorrectly configured.
return;
}
$query = [
'sentry_key' => $dsn
->getPublicKey(),
];
if ($environment = empty($_SERVER['SENTRY_ENVIRONMENT']) ? variable_get('raven_environment') : $_SERVER['SENTRY_ENVIRONMENT']) {
$query['sentry_environment'] = $environment;
}
if ($release = empty($_SERVER['SENTRY_RELEASE']) ? variable_get('raven_release') : $_SERVER['SENTRY_RELEASE']) {
$query['sentry_release'] = $release;
}
$url = url(str_replace('/store/', '/security/', $dsn
->getStoreApiEndpointUrl()), [
'query' => $query,
]);
if (variable_get('raven_set_report_uri')) {
$options['seckit_ct']['report-uri'] = $url;
$options['seckit_xss']['csp']['report-uri'] = $url;
}
if (variable_get('raven_js_enabled', FALSE)) {
$options['seckit_xss']['csp']['connect-src'] .= $options['seckit_xss']['csp']['connect-src'] ? " {$dsn->getStoreApiEndpointUrl()} {$dsn->getEnvelopeApiEndpointUrl()}" : "{$options['seckit_xss']['csp']['default-src']} {$dsn->getStoreApiEndpointUrl()} {$dsn->getEnvelopeApiEndpointUrl()}";
}
}
Functions
Name | Description |
---|---|
raven_extra_context | Appends additional context. |
raven_flush | Sends all unsent events. |
raven_form_system_logging_settings_alter | Implements hook_form_system_logging_settings_alter(). |
raven_get_client | Returns the Sentry PHP client instance, or NULL if it could not be created. |
raven_init | Implements hook_init(). |
raven_library | Implements hook_library(). |
raven_page_build | Implements hook_page_build(). |
raven_permission | Implements hook_permission(). |
raven_seckit_options_alter | Implements hook_seckit_options_alter(). |
raven_tags_context | Appends tags context. |
raven_watchdog | Implements hook_watchdog(). |