raven.module in Raven: Sentry Integration 7.3
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.
*/
/**
* 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 array(
'send javascript errors to sentry' => array(
'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_libraries_info().
*/
function raven_libraries_info() {
$libraries = array();
if (module_exists('xautoload')) {
$libraries['sentry-php'] = array(
'name' => 'Sentry PHP',
'vendor url' => 'https://github.com/getsentry/sentry-php',
'download url' => 'https://github.com/getsentry/sentry-php/releases',
'version arguments' => array(
'file' => 'lib/Raven/Client.php',
'pattern' => '#const\\s+VERSION\\s*=\\s*\'([0-9a-z._-]+)\';#',
'lines' => 25,
),
'xautoload' => function ($adapter) {
$adapter
->composerJson('composer.json');
},
);
}
return $libraries;
}
/**
* Implements hook_init().
*/
function raven_init() {
global $user;
if (!variable_get('raven_enabled', FALSE)) {
return;
}
$client = raven_get_client();
if (!$client) {
return;
}
// Bind the logged in user.
$context['id'] = $user->uid;
$context['ip_address'] = ip_address();
$context['roles'] = implode(', ', $user->roles);
if (user_is_logged_in()) {
$context['name'] = $user->name;
$context['email'] = $user->mail;
}
drupal_alter('raven_user', $context);
$client
->user_context($context);
// Tag the request with something interesting.
$context = array();
drupal_alter('raven_tags', $context);
$client
->tags_context($context);
// Provide a bit of additional context.
$context = array();
drupal_alter('raven_extra', $context);
$client
->extra_context($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"] = array();
$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 ($traces_sample_rate = variable_get('raven_js_traces_sample_rate', NULL)) {
$options->tracesSampleRate = (double) $traces_sample_rate;
}
$options->integrations = array();
$libraries['raven']['js'][] = array(
'data' => array(
'raven' => array(
// Other modules can alter the @sentry/browser options.
'options' => $options,
'user' => array(
'id' => $user->uid,
),
),
),
'type' => 'setting',
);
$libraries['raven']['js']["{$path}/js/raven.js"] = array(
// Load in the footer to ensure settings are available.
'scope' => 'footer',
);
return $libraries;
}
/**
* Implements hook_watchdog().
*/
function raven_watchdog($log_entry) {
if (!variable_get('raven_enabled', FALSE)) {
return;
}
$client = raven_get_client();
if (!$client) {
return;
}
$watchdog_levels = variable_get('raven_watchdog_levels', array());
$levels_map = array(
WATCHDOG_EMERGENCY => Raven_Client::FATAL,
WATCHDOG_ALERT => Raven_Client::FATAL,
WATCHDOG_CRITICAL => Raven_Client::FATAL,
WATCHDOG_ERROR => Raven_Client::ERROR,
WATCHDOG_WARNING => Raven_Client::WARNING,
WATCHDOG_NOTICE => Raven_Client::INFO,
WATCHDOG_INFO => Raven_Client::INFO,
WATCHDOG_DEBUG => Raven_Client::DEBUG,
);
$variables = $log_entry['variables'];
if (!$variables) {
$variables = array();
}
if (!function_exists('truncate_utf8')) {
require_once DRUPAL_ROOT . '/includes/unicode.inc';
unicode_check();
}
$message = truncate_utf8(html_entity_decode(strip_tags(strtr($log_entry['message'], $variables)), ENT_QUOTES, 'UTF-8'), variable_get('raven_message_limit', 2048), FALSE, TRUE);
$data = array(
'level' => $levels_map[$log_entry['severity']],
'sentry.interfaces.Message' => array(
'message' => $log_entry['message'],
'params' => $log_entry['variables'],
'formatted' => $message,
),
'extra' => array(
'link' => $log_entry['link'],
'request_uri' => $log_entry['request_uri'],
'referer' => $log_entry['referer'],
),
'logger' => $log_entry['type'],
'user' => array(
'id' => $log_entry['uid'],
'ip_address' => $log_entry['ip'],
),
);
if ($log_entry['user']) {
$data['user']['roles'] = implode(', ', $log_entry['user']->roles);
if ($log_entry['uid']) {
$data['user']['name'] = $log_entry['user']->name;
$data['user']['email'] = $log_entry['user']->mail;
}
}
$filter = array(
'process' => !empty($watchdog_levels[$log_entry['severity'] + 1]),
'log_entry' => $log_entry,
'data' => &$data,
);
$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 = array(
'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, array(
'line' => $exception
->getLine(),
'file' => $exception
->getFile(),
));
if ($exception instanceof PDOException) {
$db_functions = array(
'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);
}
}
}
// By default, disable reflection tracing for user watchdog entries.
if ($data['logger'] === 'user' && $client->trace && !variable_get('raven_trace_user', FALSE)) {
$client->trace = FALSE;
$client
->capture($data, $client->auto_log_stacks ? $stack : FALSE);
$client->trace = TRUE;
}
else {
$client
->capture($data, $client->auto_log_stacks ? $stack : FALSE);
}
}
// Record a breadcrumb.
$breadcrumb = array(
'log_entry' => $log_entry,
'process' => TRUE,
'breadcrumb' => array(
'category' => $log_entry['type'],
'message' => $message,
'level' => $data['level'],
),
);
foreach (array(
'%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'])) {
$client->breadcrumbs
->record($breadcrumb['breadcrumb']);
}
}
/**
* Returns the Sentry PHP client instance, or NULL if it could not be created.
*
* @return Raven_Client|null
* Raven PHP client library instance.
*/
function raven_get_client() {
global $user;
static $client;
if (!isset($client)) {
if (!raven_libraries_load()) {
return NULL;
}
// Prepare config.
$dsn = empty($_SERVER['SENTRY_DSN']) ? variable_get('raven_dsn', NULL) : $_SERVER['SENTRY_DSN'];
$timeout = variable_get('raven_timeout', 2);
$message_limit = variable_get('raven_message_limit', 2048);
$stack = variable_get('raven_stack', TRUE);
$trace = variable_get('raven_trace', FALSE);
// Build the field sanitization regular expression.
$fields = array(
'SESS',
'key',
'token',
'pass',
'authorization',
'password',
'passwd',
'secret',
'password_confirmation',
'card_number',
'auth_pw',
);
drupal_alter('raven_sanitize_fields', $fields);
$fields_re = '/(' . implode('|', $fields) . ')/i';
$options = array(
'timeout' => $timeout,
'message_limit' => $message_limit,
'auto_log_stacks' => $stack,
'trace' => $trace,
'processorOptions' => array(
'RavenSanitizeCookieProcessor' => array(
'fields_re' => $fields_re,
),
'Raven_Processor_SanitizeDataProcessor' => array(
'fields_re' => $fields_re,
),
),
'curl_method' => 'async',
'verify_ssl' => TRUE,
);
// Ease the upgrade path by checking if class exists.
if (class_exists('Raven_Processor_SanitizeDataProcessor') && class_exists('RavenSanitizeCookieProcessor')) {
$options['processors'] = array(
'RavenSanitizeCookieProcessor',
'Raven_Processor_SanitizeDataProcessor',
);
}
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;
}
$raven_ssl = variable_get('raven_ssl', 'verify_ssl');
// Verify against a CA certificate.
if ($raven_ssl == 'ca_cert') {
$options['ca_cert'] = drupal_realpath(variable_get('raven_ca_cert', ''));
}
elseif ($raven_ssl == 'no_verify_ssl') {
$options['verify_ssl'] = FALSE;
}
// Breadcrumbs confuse Drupal as to which line of code is throwing an error.
$options['install_default_breadcrumb_handlers'] = FALSE;
// Allow other modules to alter $options before passing into Raven client.
drupal_alter('raven_options', $options);
try {
// Instantiate a new client with a compatible DSN.
$client = new Raven_Client($dsn, $options);
} catch (InvalidArgumentException $e) {
// Raven is incorrectly configured.
return NULL;
}
// Bind user context to prevent session ID from being sent as user ID.
$client
->user_context(array(
'id' => $user ? $user->uid : 0,
'ip_address' => ip_address(),
));
// Register fatal error handler.
if (variable_get('raven_fatal_error_handler', TRUE)) {
$handler = new Raven_ErrorHandler($client);
$reserved_memory = variable_get('raven_fatal_error_handler_memory', 2.5 * 1024);
$handler
->registerShutdownFunction($reserved_memory);
// Register shutdown function again for fatal error via async.
register_shutdown_function(array(
$client,
'onShutdown',
));
}
}
return $client;
}
/**
* Loads Sentry PHP library.
*
* @return bool
* Returns TRUE if libraries loaded or FALSE otherwise.
*/
function raven_libraries_load() {
$library['loaded'] = FALSE;
if (class_exists('Raven_Client')) {
$library['loaded'] = TRUE;
}
elseif (function_exists('composer_autoloader') && composer_autoloader() && class_exists('Raven_Client')) {
$library['loaded'] = TRUE;
}
elseif (module_exists('libraries')) {
$library = libraries_load('sentry-php');
}
if (!$library['loaded']) {
// This function can be called multiple times, so prevent multiple messages.
drupal_set_message(t('Sentry PHP library cannot be loaded. Check status report for more details.'), 'warning', FALSE);
}
return (bool) $library['loaded'];
}
/**
* Appends additional context.
*
* @param array $data
* Associative array of extra data.
*/
function raven_extra_context(array $data = array()) {
$client = raven_get_client();
if (!$client) {
return;
}
$client
->extra_context($data);
}
/**
* Appends tags context.
*
* @param array $data
* Associative array of tags.
*/
function raven_tags_context(array $data = array()) {
$client = raven_get_client();
if (!$client) {
return;
}
$client
->tags_context($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() {
$client = raven_get_client();
if (!$client) {
return;
}
$client
->onShutdown();
}
/**
* Implements hook_seckit_options_alter().
*/
function raven_seckit_options_alter(&$options) {
if (!class_exists('Raven_Client')) {
return;
}
try {
$dsn = Raven_Client::parseDSN(empty($_SERVER['SENTRY_DSN']) ? variable_get('raven_public_dsn') : $_SERVER['SENTRY_DSN']);
} catch (InvalidArgumentException $e) {
// Raven is incorrectly configured.
return;
}
$query = array(
'sentry_key' => $dsn['public_key'],
);
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['server']), array(
'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['server']} " : "{$options['seckit_xss']['csp']['default-src']} {$dsn['server']} ";
$options['seckit_xss']['csp']['connect-src'] .= str_replace('/store/', '/envelope/', $dsn['server']);
}
}
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_libraries_info | Implements hook_libraries_info(). |
raven_libraries_load | Loads Sentry PHP library. |
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(). |