*/ class helper_plugin_sentry extends DokuWiki_Plugin { /** * Parse the DSN configuration into its parts * * @return array */ protected function parseDSN() { $parts = parse_url($this->getConf('dsn')); $dsn = []; $dsn['protocol'] = $parts['scheme']; $dsn['public'] = $parts['user']; $dsn['secret'] = $parts['pass']; $dsn['project'] = (int)basename($parts['path']); $dsn['url'] = $parts['host']; if (!empty($parts['port'])) $dsn['url'] .= ':' . $parts['port']; $path = dirname($parts['path']); $path = trim($path, '/'); if (!empty($path)) { $path = '/' . $path; } $dsn['path'] = $path; return $dsn; } /** * Return the API endpoint to store messages * * @return string */ protected function storeAPI() { $dsn = $this->parseDSN(); return $dsn['protocol'] . '://' . $dsn['url'] . $dsn['path'] . '/api/' . $dsn['project'] . '/store/'; } /** * Return the X-Sentry-Auth header * * @return string */ protected function storeAuthHeader() { $dsn = $this->parseDSN(); $header[] = 'Sentry sentry_version=7'; $header[] = 'sentry_client=' . Event::CLIENT . Event::VERSION; $header[] = 'sentry_timestamp=' . time(); $header[] = 'sentry_key=' . $dsn['public']; $header[] = 'sentry_secret=' . $dsn['secret']; return join(', ', $header); } /** * Log an exception * * If you need more control over the logged Event, use logEvent() * * @param \Throwable|\Exception $e */ public function logException($e) { $this->logEvent(Event::fromException($e)); } /** * Log an event * * @param Event $event */ public function logEvent(Event $event) { $this->saveEvent($event); if ($this->sendEvent($event)) $this->deleteEvent($event->getID()); } /** * Log a message and optionally some data to sentry * * @param string $message the raw message string * @param array $extra */ public function logMessage($message, array $extra = []) { $backtrace = debug_backtrace(); array_shift($backtrace); // remove this logMessage method $eventData = [ 'sentry.interfaces.Message' => [ 'message' => $message, ], 'stacktrace' => ['frames' => Event::backTraceFrames($backtrace)], 'extra' => $extra, ]; $event = new Event($eventData); $event->setLogLevel('info'); $this->logEvent($event); } /** * Format an exception for the user in HTML * * @param \Throwable|\Exception $e * @return string the HTML */ public function formatException($e) { global $conf; $html = '
'; $html .= '

An error occured

'; $html .= '

' . hsc(get_class($e)) . ': ' . $e->getMessage() . '

'; if ($conf['allowdebug']) { $html .= '

' . hsc($e->getFile()) . ':' . hsc($e->getLine()) . '

'; $html .= '
' . hsc($e->getTraceAsString()) . '
'; } $html .= '

The error has been logged.

'; $html .= '
'; return $html; } /** * Save the given event to file system * * @param Event $event */ public function saveEvent(Event $event) { global $conf; $cachedir = $conf['cachedir'] . '/_sentry/'; $file = $cachedir . $event->getID() . '.json'; io_makeFileDir($file); file_put_contents($file, $event->getJSON()); } /** * Load a pending event * * @param string $id * @return Event|null */ public function loadEvent($id) { global $conf; $cachedir = $conf['cachedir'] . '/_sentry/'; $file = $cachedir . $id . '.json'; if (!file_exists($file)) return null; $json = file_get_contents($file); return Event::fromJSON($json); } /** * Delete a pending event * * @param string $id */ public function deleteEvent($id) { global $conf; $cachedir = $conf['cachedir'] . '/_sentry/'; $file = $cachedir . $id . '.json'; // the event may have been deleted in the meantime @unlink($file); } /** * Returns a list of event IDs that have not yet been sent * * @return string[] */ public function getPendingEventIDs() { global $conf; $cachedir = $conf['cachedir'] . '/_sentry/'; $files = glob($cachedir . '/*.json'); return array_map(function ($in) { return basename($in, '.json'); }, $files); } /** * Send the given event to sentry * * You most probably want to use logEvent() or logException() instead * * @param Event $event the event * @return bool was the event submitted successfully? */ public function sendEvent(Event $event) { if (class_exists('dokuwiki\HTTP\DokuHTTPClient')) { $http = new dokuwiki\HTTP\DokuHTTPClient(); } else { $http = new DokuHTTPClient(); } $http->timeout = 4; // this should not take long! $http->headers['User-Agent'] = Event::CLIENT . Event::VERSION; $http->headers['X-Sentry-Auth'] = $this->storeAuthHeader(); $http->headers['Content-Type'] = 'application/json'; $ok = $http->post($this->storeAPI(), $event->getJSON()); if (!$ok) dbglog($http->resp_body, 'Sentry returned Error'); return (bool)$ok; } /** * Return the wanted error reporting * * @return int */ public function errorReporting() { $conf = (int) $this->getConf('errors'); if($conf === 0) return error_reporting(); return $conf; } }