Add rollbar module

This commit is contained in:
Oliver Davies 2021-04-14 14:39:04 +01:00
parent 168fab9955
commit 51eb917570
17 changed files with 1068 additions and 1 deletions

View file

@ -45,6 +45,7 @@
"drush/drush": "^10",
"nesbot/carbon": "^2.33",
"platformsh/config-reader": "^2.4",
"rollbar/rollbar": "^2.1",
"symfony/config": "^3.4",
"tightenco/collect": "^8.17"
},

678
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,4 @@
Rollbar
=======
This module provides Drupal 8 support for [rollbar](https://rollbar.com).

View file

@ -0,0 +1,11 @@
{
"name": "drupal/rollbar",
"description": "Rollbar integration for Drupal 8.",
"type": "drupal-module",
"license": "GPL 2.0+",
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"rollbar/rollbar": "^1.0"
}
}

View file

@ -0,0 +1,10 @@
enabled: false
access_token: null
capture_uncaught: true
capture_unhandled_rejections: false
environment: production
log_level: []
channels: ''
rollbar_js_url: https://cdnjs.cloudflare.com/ajax/libs/rollbar.js/1.9.3/rollbar.min.js
host_white_list: ''

View file

@ -0,0 +1,31 @@
rollbar.settings:
type: config_object
label: 'Rollbar settings'
mapping:
enabled:
type: boolean
label: 'Whether Rollbar is enabled'
access_token:
type: string
label: 'The rollbar API access token'
capture_uncaught:
type: boolean
label: 'The rollbar captureUncaught option'
capture_unhandled_rejections:
type: boolean
label: 'The rollbar captureUnhandledRejections option'
environment:
type: string
label: 'Environment that will be reported to rollbar'
log_level:
type: sequence
label: 'Log levels that will be reported to rollbar'
channels:
type: string
label: 'Channels to filter'
rollbar_js_url:
type: string
label: 'URL to the rollbar library'
host_white_list:
type: string
label: 'Comma-separated host white list'

View file

@ -0,0 +1,12 @@
var _rollbarConfig = {
accessToken: drupalSettings.rollbar.access_token,
captureUncaught: drupalSettings.rollbar.capture_uncaught,
captureUnhandledRejections: drupalSettings.rollbar.capture_unhandled_rejections,
rollbarJsUrl: drupalSettings.rollbar.rollbar_js_url,
payload: {
environment: drupalSettings.rollbar.environment
}
};
// Rollbar Snippet
!function(r){function e(n){if(o[n])return o[n].exports;var t=o[n]={exports:{},id:n,loaded:!1};return r[n].call(t.exports,t,t.exports,e),t.loaded=!0,t.exports}var o={};return e.m=r,e.c=o,e.p="",e(0)}([function(r,e,o){"use strict";var n=o(1).Rollbar,t=o(2);_rollbarConfig.rollbarJsUrl=_rollbarConfig.rollbarJsUrl||"https://cdnjs.cloudflare.com/ajax/libs/rollbar.js/1.9.3/rollbar.min.js";var a=n.init(window,_rollbarConfig),i=t(a,_rollbarConfig);a.loadFull(window,document,!_rollbarConfig.async,_rollbarConfig,i)},function(r,e){"use strict";function o(r){return function(){try{return r.apply(this,arguments)}catch(r){try{console.error("[Rollbar]: Internal error",r)}catch(r){}}}}function n(r,e,o){window._rollbarWrappedError&&(o[4]||(o[4]=window._rollbarWrappedError),o[5]||(o[5]=window._rollbarWrappedError._rollbarContext),window._rollbarWrappedError=null),r.uncaughtError.apply(r,o),e&&e.apply(window,o)}function t(r){var e=function(){var e=Array.prototype.slice.call(arguments,0);n(r,r._rollbarOldOnError,e)};return e.belongsToShim=!0,e}function a(r){this.shimId=++c,this.notifier=null,this.parentShim=r,this._rollbarOldOnError=null}function i(r){var e=a;return o(function(){if(this.notifier)return this.notifier[r].apply(this.notifier,arguments);var o=this,n="scope"===r;n&&(o=new e(this));var t=Array.prototype.slice.call(arguments,0),a={shim:o,method:r,args:t,ts:new Date};return window._rollbarShimQueue.push(a),n?o:void 0})}function l(r,e){if(e.hasOwnProperty&&e.hasOwnProperty("addEventListener")){var o=e.addEventListener;e.addEventListener=function(e,n,t){o.call(this,e,r.wrap(n),t)};var n=e.removeEventListener;e.removeEventListener=function(r,e,o){n.call(this,r,e&&e._wrapped?e._wrapped:e,o)}}}var c=0;a.init=function(r,e){var n=e.globalAlias||"Rollbar";if("object"==typeof r[n])return r[n];r._rollbarShimQueue=[],r._rollbarWrappedError=null,e=e||{};var i=new a;return o(function(){if(i.configure(e),e.captureUncaught){i._rollbarOldOnError=r.onerror,r.onerror=t(i);var o,a,c="EventTarget,Window,Node,ApplicationCache,AudioTrackList,ChannelMergerNode,CryptoOperation,EventSource,FileReader,HTMLUnknownElement,IDBDatabase,IDBRequest,IDBTransaction,KeyOperation,MediaController,MessagePort,ModalWindow,Notification,SVGElementInstance,Screen,TextTrack,TextTrackCue,TextTrackList,WebSocket,WebSocketWorker,Worker,XMLHttpRequest,XMLHttpRequestEventTarget,XMLHttpRequestUpload".split(",");for(o=0;o<c.length;++o)a=c[o],r[a]&&r[a].prototype&&l(i,r[a].prototype)}return e.captureUnhandledRejections&&(i._unhandledRejectionHandler=function(r){var e=r.reason,o=r.promise,n=r.detail;!e&&n&&(e=n.reason,o=n.promise),i.unhandledRejection(e,o)},r.addEventListener("unhandledrejection",i._unhandledRejectionHandler)),r[n]=i,i})()},a.prototype.loadFull=function(r,e,n,t,a){var i=function(){var e;if(void 0===r._rollbarPayloadQueue){var o,n,t,i;for(e=new Error("rollbar.js did not load");o=r._rollbarShimQueue.shift();)for(t=o.args,i=0;i<t.length;++i)if(n=t[i],"function"==typeof n){n(e);break}}"function"==typeof a&&a(e)},l=!1,c=e.createElement("script"),p=e.getElementsByTagName("script")[0],s=p.parentNode;c.crossOrigin="",c.src=t.rollbarJsUrl,c.async=!n,c.onload=c.onreadystatechange=o(function(){if(!(l||this.readyState&&"loaded"!==this.readyState&&"complete"!==this.readyState)){c.onload=c.onreadystatechange=null;try{s.removeChild(c)}catch(r){}l=!0,i()}}),s.insertBefore(c,p)},a.prototype.wrap=function(r,e){try{var o;if(o="function"==typeof e?e:function(){return e||{}},"function"!=typeof r)return r;if(r._isWrap)return r;if(!r._wrapped){r._wrapped=function(){try{return r.apply(this,arguments)}catch(e){throw"string"==typeof e&&(e=new String(e)),e._rollbarContext=o()||{},e._rollbarContext._wrappedSource=r.toString(),window._rollbarWrappedError=e,e}},r._wrapped._isWrap=!0;for(var n in r)r.hasOwnProperty(n)&&(r._wrapped[n]=r[n])}return r._wrapped}catch(e){return r}};for(var p="log,debug,info,warn,warning,error,critical,global,configure,scope,uncaughtError,unhandledRejection".split(","),s=0;s<p.length;++s)a.prototype[p[s]]=i(p[s]);r.exports={Rollbar:a,_rollbarWindowOnError:n}},function(r,e){"use strict";r.exports=function(r,e){return function(o){if(!o&&!window._rollbarInitialized){var n=window.RollbarNotifier,t=e||{},a=t.globalAlias||"Rollbar",i=window.Rollbar.init(t,r);i._processShimQueue(window._rollbarShimQueue||[]),window[a]=i,window._rollbarInitialized=!0,n.processPayloads()}}}}]);
// End Rollbar Snippet

View file

@ -0,0 +1,6 @@
name: Rollbar
description: Integration with Rollbar
type: module
configure: rollbar.settings
core: 8.x
package: Services

View file

@ -0,0 +1,16 @@
<?php
/**
* @file
* Updates for the rollbar module.
*/
/**
* Updates the rollbar configuration.
*/
function rollbar_update_8001(&$sandbox) {
$config_factory = \Drupal::configFactory();
$config = $config_factory->getEditable('rollbar.settings');
$config->set('host_white_list', '');
$config->save(TRUE);
}

View file

@ -0,0 +1,7 @@
global:
version: VERSION
header: true
js:
js/rollbar.js: {}
dependencies:
- core/drupalSettings

View file

@ -0,0 +1,5 @@
rollbar.settings:
title: 'Rollbar'
parent: system.admin_config_services
description: 'Configure the Rollbar integration.'
route_name: rollbar.settings

View file

@ -0,0 +1,43 @@
<?php
/**
* @file
* Module file for 'rollbar'.
*/
/**
* Implements hook_page_attachments_alter().
*
* Retrieves the configured settings for the rollbar library. Attaches the
* rollbar JS snippet to the page, along with the retrieved settings as
* part of the `drupalSettings` object.
*/
function rollbar_page_attachments_alter(array &$attachments) {
$config = \Drupal::config('rollbar.settings');
// We only add the library and settings to the page if enabled.
if (!empty($config) && $config->get('enabled')) {
$settings = [
'access_token' => $config->get('access_token'),
'capture_uncaught' => $config->get('capture_uncaught'),
'capture_unhandled_rejections' => $config->get('capture_unhandled_rejections'),
'environment' => $config->get('environment'),
'rollbar_js_url' => $config->get('rollbar_js_url'),
];
// Extract host white list, if set, and add it to configuration.
if ($hosts = $config->get('host_white_list')) {
$host_list = explode(',', trim($hosts));
if (!empty($host_list)) {
$settings['host_white_list'] = $host_list;
}
}
// Allow alteration of the rollbar settings by other modules.
\Drupal::moduleHandler()->alter('rollbar_settings', $settings);
$attachments['#attached']['library'][] = 'rollbar/global';
$attachments['#attached']['drupalSettings']['rollbar'] = $settings;
}
}

View file

@ -0,0 +1,2 @@
administer rollbar:
title: "Administer Rollbar"

View file

@ -0,0 +1,9 @@
rollbar.settings:
path: '/admin/config/services/rollbar'
defaults:
_form: '\Drupal\rollbar\Form\RollbarSettingsForm'
_title: 'Rollbar'
requirements:
_permission: 'administer rollbar'
options:
_admin_route: true

View file

@ -0,0 +1,6 @@
services:
logger.rollbar:
class: Drupal\rollbar\Logger\RollbarLogger
arguments: ['@config.factory', '@logger.log_message_parser']
tags:
- { name: logger }

View file

@ -0,0 +1,125 @@
<?php
namespace Drupal\rollbar\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Configuration form for rollbar settings.
*/
class RollbarSettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'rollbar_settings';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['rollbar.settings'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('rollbar.settings');
$form['enabled'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enabled'),
'#default_value' => $config->get('enabled'),
];
$form['access_token'] = [
'#type' => 'textfield',
'#title' => $this->t('Access Token'),
'#default_value' => $config->get('access_token'),
'#required' => TRUE,
];
$form['capture_uncaught'] = [
'#type' => 'checkbox',
'#title' => $this->t('Capture Uncaught'),
'#default_value' => $config->get('capture_uncaught'),
];
$form['capture_unhandled_rejections'] = [
'#type' => 'checkbox',
'#title' => $this->t('Capture uncaught rejections'),
'#default_value' => $config->get('capture_unhandled_rejections'),
];
$form['environment'] = [
'#type' => 'textfield',
'#title' => $this->t('Environment'),
'#default_value' => $config->get('environment'),
'#description' => $this->t('The environment string to use when reporting errors'),
];
$form['log_level'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Log level'),
'#default_value' => $config->get('log_level'),
'#description' => $this->t('Selected log types will be send to Rollbar'),
'#options' => [
'Emergency',
'Alert',
'Critical',
'Error',
'Warning',
'Notice',
'Info' ,
'Debug',
]
];
$form['channels'] = [
'#type' => 'textfield',
'#title' => $this->t('Filter channels'),
'#default_value' => $config->get('channels'),
'#description' => $this->t("Enter channels separated by ';' to prevent send them to rollbar"),
];
$form['rollbar_js_url'] = [
'#type' => 'textfield',
'#title' => $this->t('Rollbar JS URL'),
'#default_value' => $config->get('rollbar_js_url'),
'#description' => $this->t('The URL to the Rollbar js library'),
];
$form['host_white_list'] = [
'#type' => 'textfield',
'#title' => $this->t('Host white list'),
'#default_value' => $config->get('host_white_list'),
'#description' => $this->t('List of hosts for which rollbar reports errors'),
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('rollbar.settings')
->set('access_token', $form_state->getValue('access_token'))
->set('enabled', $form_state->getValue('enabled'))
->set('capture_uncaught', $form_state->getValue('capture_uncaught'))
->set('capture_unhandled_rejections', $form_state->getValue('capture_unhandled_rejections'))
->set('environment', $form_state->getValue('environment'))
->set('log_level', $form_state->getValue('log_level'))
->set('channels', $form_state->getValue('channels'))
->set('rollbar_js_url', $form_state->getValue('rollbar_js_url'))
->set('host_white_list', $form_state->getValue('host_white_list'))
->save();
parent::submitForm($form, $form_state);
}
}

View file

@ -0,0 +1,103 @@
<?php
namespace Drupal\rollbar\Logger;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LogMessageParserInterface;
use Drupal\Core\Logger\RfcLogLevel;
use Psr\Log\LoggerInterface;
use Drupal\Core\Logger\RfcLoggerTrait;
use Rollbar\Rollbar;
use Rollbar\Payload\Level as RollbarLogLevel;
/**
* Redirects logging messages to Rollbar.
*/
class RollbarLogger implements LoggerInterface {
use RfcLoggerTrait;
/**
* A configuration object containing rollbar settings.
*
* @var \Drupal\Core\Config\Config
*/
protected $config;
/**
* The message's placeholders parser.
*
* @var \Drupal\Core\Logger\LogMessageParserInterface
*/
protected $parser;
/**
* Checks if the Rollbar is initialized.
*
* @var bool
*/
private $isInitialized = FALSE;
/**
* Constructs a Rollbar object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory object.
* @param \Drupal\Core\Logger\LogMessageParserInterface $parser
* The parser to use when extracting message variables.
*/
public function __construct(ConfigFactoryInterface $config_factory, LogMessageParserInterface $parser) {
$this->config = $config_factory->get('rollbar.settings');
$this->parser = $parser;
}
/**
* Initialize rollbar object.
*/
protected function init() {
$token = $this->config->get('access_token');
$environment = $this->config->get('environment');
if (empty($token) || empty($environment)) {
return FALSE;
}
if (!$this->isInitialized) {
Rollbar::init(['access_token' => $token, 'environment' => $environment]);
$this->isInitialized = TRUE;
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function log($level, $message, array $context = []) {
if (!$this->init()) {
return;
}
$enabled_log_levels = $this->config->get('log_level');
$log_level_condition = !in_array($level, $enabled_log_levels);
$omit_channel = array_map('trim', explode(";", $this->config->get('channels')));
$omit_channel_condition = isset($context['channel']) && in_array($context['channel'], $omit_channel);
if ($log_level_condition || $omit_channel_condition) {
return;
}
$level_map = [
RfcLogLevel::EMERGENCY => RollbarLogLevel::critical(),
RfcLogLevel::ALERT => RollbarLogLevel::critical(),
RfcLogLevel::CRITICAL => RollbarLogLevel::critical(),
RfcLogLevel::ERROR => RollbarLogLevel::error(),
RfcLogLevel::WARNING => RollbarLogLevel::warning(),
RfcLogLevel::NOTICE => RollbarLogLevel::info(),
RfcLogLevel::INFO => RollbarLogLevel::info(),
RfcLogLevel::DEBUG => RollbarLogLevel::debug(),
];
// Populate the message placeholders and then replace them in the message.
$message_placeholders = $this->parser->parseMessagePlaceholders($message, $context);
$message = empty($message_placeholders) ? $message : strtr($message, $message_placeholders);
Rollbar::log($level_map[$level], $message, $context);
}
}