config = $config->get('wmsentry.settings'); $this->parser = $parser; $this->eventDispatcher = $eventDispatcher; $this->moduleHandler = $moduleHandler; $this->entityTypeManager = $entityTypeManager; $this->client = $this->getClient(); /** * Replace the Drupal error handler * @see _wmsentry_error_handler_real */ $this->moduleHandler->loadInclude('wmsentry', 'module'); set_error_handler('_wmsentry_error_handler_real'); } public function onBeforeSend(Event $event): ?Event { /** @var SentryBeforeSendEvent $beforeSendEvent */ $beforeSendEvent = $this->eventDispatcher->dispatch(WmsentryEvents::BEFORE_SEND, new SentryBeforeSendEvent($event)); return $beforeSendEvent->isExcluded() ? null : $beforeSendEvent->getEvent(); } public function onBeforeBreadcrumb(Breadcrumb $breadcrumb): ?Breadcrumb { /** @var SentryBeforeBreadcrumbEvent $beforeBreadcrumbEvent */ $beforeBreadcrumbEvent = $this->eventDispatcher->dispatch(WmsentryEvents::BEFORE_BREADCRUMB, new SentryBeforeBreadcrumbEvent($breadcrumb)); return $beforeBreadcrumbEvent->isExcluded() ? null : $beforeBreadcrumbEvent->getBreadcrumb(); } public function log($level, $message, array $context = []) { if (!$this->isLogLevelIncluded($level)) { return; } $scope = $this->buildScope($context); $payload = [ 'level' => $this->getLogLevel($level), 'message' => $this->formatMessage($message, $context), 'logger' => $context['channel'], 'stacktrace' => $this->buildStacktrace($context), ]; $this->client->captureEvent($payload, $scope); } protected function getClient(): ?ClientInterface { if (isset($this->client)) { return $this->client; } $integrations = []; $options = new Options([ 'dsn' => $this->config->get('dsn'), 'attach_stacktrace' => true, 'before_send' => [$this, 'onBeforeSend'], 'before_breadcrumb' => [$this, 'onBeforeBreadcrumb'], ]); if ($value = $this->config->get('release')) { $options->setRelease($value); } if ($value = $this->config->get('environment')) { $options->setEnvironment($value); } if ($value = $this->config->get('excluded_exceptions')) { $integrations[] = new IgnoreErrorsIntegration([ 'ignore_exceptions' => $value ]); } $options->setIntegrations($integrations); $this->eventDispatcher->dispatch(WmsentryEvents::OPTIONS_ALTER, new SentryOptionsAlterEvent($options)); return $this->client = (new ClientBuilder($options))->getClient(); } protected function getLogLevel(int $rfc): ?string { $levels = [ RfcLogLevel::EMERGENCY => Severity::FATAL, RfcLogLevel::ALERT => Severity::FATAL, RfcLogLevel::CRITICAL => Severity::FATAL, RfcLogLevel::ERROR => Severity::ERROR, RfcLogLevel::WARNING => Severity::WARNING, RfcLogLevel::NOTICE => Severity::INFO, RfcLogLevel::INFO => Severity::INFO, RfcLogLevel::DEBUG => Severity::DEBUG, ]; return $levels[$rfc] ?? null; } protected function formatMessage(string $message, array $context): string { /** @see errors.inc */ if (isset($context['@message'])) { return $context['@message']; } $placeholders = $this->parser->parseMessagePlaceholders($message, $context); if (empty($placeholders)) { return $message; } return strtr($message, $placeholders); } protected function buildStacktrace(array $context): Stacktrace { if (!empty($context['backtrace'])) { $backtrace = $context['backtrace']; } else { $backtrace = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); $finder = new ClassFinder(); $toIgnore = array_map( function (string $className) use ($finder) { return realpath($finder->findFile($className)); }, [ self::class, \Drupal\Core\Logger\LoggerChannel::class, \Psr\Log\LoggerTrait::class, ] ); while (!empty($backtrace) && in_array($backtrace[0]['file'], $toIgnore, true)) { array_shift($backtrace); } } $stacktrace = new Stacktrace( $this->client->getOptions(), new Serializer($this->client->getOptions()), new RepresentationSerializer($this->client->getOptions()) ); return array_reduce( $backtrace, function (Stacktrace $stacktrace, array $frame) { $file = $frame['file'] ?? '[internal]'; $line = $frame['line'] ?? 0; if (!$this->config->get('include_stacktrace_func_args')) { $frame['args'] = []; } $stacktrace->addFrame($file, $line, $frame); return $stacktrace; }, $stacktrace ); } protected function buildScope(array $context): Scope { $scope = new Scope; foreach (['channel', '%type'] as $key) { if (isset($context[$key])) { $scope->setTag(ltrim($key, '%@'), $context[$key]); } } foreach (['link', 'referer', 'request_uri'] as $key) { if (!empty($context[$key])) { $scope->setExtra($key, $context[$key]); } } $scope->setUser($this->getUserData($context)); $this->eventDispatcher->dispatch(WmsentryEvents::SCOPE_ALTER, new SentryScopeAlterEvent($scope, $context)); return $scope; } protected function getUserData(array $context): array { $data = [ 'id' => (string) ($context['uid'] ?? '0'), 'ip_address' => $context['ip'], ]; if (!isset($context['uid'])) { return $data; } /* @var UserInterface $user */ $user = $this->entityTypeManager->getStorage('user')->load($context['uid']); if ($user) { $data['username'] = $user->getDisplayName(); $data['email'] = $user->getEmail(); } return $data; } protected function isLogLevelIncluded(int $level): bool { $index = $level + 1; return !empty($this->config->get("log_levels.{$index}")); } }