Skip to content
Snippets Groups Projects
Commit fc252185 authored by Patricia Reñones's avatar Patricia Reñones
Browse files

Issue #3485175 by lapaty: Request subscriber can't populate Response data

parent cda99ff5
1 merge request!4Issue #3485175 by lapaty: Request subscriber can't populate Response data
# Archivo .install
enable: false
exclude_routes: ''
omit_empty_body: false
request_methods: []
log_output_template: 'Method: {method}, URL: {url}, Status: {status_code}, Headers: {headers}, Query: {query}, Content: {content}'
log_format: 'json'
# Archivo .schema
jsonapi_request_logger.settings:
type: config_object
label: 'JSON:API Request Logger Settings'
......@@ -27,3 +28,8 @@ jsonapi_request_logger.settings:
label: 'Log output template'
description: 'Template for customizing the log output. Tokens: {method}, {url}, {status_code}, {headers}, {query}, {content}.'
default_value: 'Method: {method}, URL: {url}, Status: {status_code}, Headers: {headers}, Query: {query}, Content: {content}'
log_format:
type: string
label: 'Log format'
description: 'Format of the log output. Options: json.'
default_value: 'json'
......@@ -8,6 +8,7 @@ use Drupal\Core\Config\ConfigFactoryInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
......@@ -16,21 +17,26 @@ use Symfony\Component\HttpKernel\KernelEvents;
final class RequestLoggerSubscriber implements EventSubscriberInterface {
/**
* The config factory.
* Config factory interface for accessing configuration settings.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
private ConfigFactoryInterface $configFactory;
/**
* The logger service.
* Logger interface for logging messages.
*
* @var \Psr\Log\LoggerInterface
*/
private LoggerInterface $logger;
/**
* Constructor.
* Constructs the RequestLoggerSubscriber.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* The configuration factory service.
* @param \Psr\Log\LoggerInterface $logger
* The logger service.
*/
public function __construct(ConfigFactoryInterface $configFactory, LoggerInterface $logger) {
$this->configFactory = $configFactory;
......@@ -38,74 +44,186 @@ final class RequestLoggerSubscriber implements EventSubscriberInterface {
}
/**
* Kernel request event handler.
* Handles the kernel request event.
*
* @param \Symfony\Component\HttpKernel\Event\RequestEvent $event
* The request event.
*/
public function onKernelRequest(RequestEvent $event): void {
$config = $this->configFactory->get('jsonapi_request_logger.settings');
$request = $event->getRequest();
$method = $request->getMethod();
// Check if logging is enabled.
if (!$config->get('enable')) {
return;
}
// Check if the request format is 'api_json'.
$request = $event->getRequest();
if ($request->getRequestFormat() !== 'api_json') {
return;
}
// Exclude routes based on the configured patterns.
$exclude_routes = preg_split('/\r\n|\r|\n/', $config->get('exclude_routes'));
$current_path = $request->getPathInfo();
foreach ($exclude_routes as $pattern) {
if ($this->isExcludedRoute($request->getPathInfo(), $config->get('exclude_routes'))) {
return;
}
if ($this->isMethodNotAllowed($request->getMethod(), $config->get('request_methods'))) {
return;
}
if ($config->get('omit_empty_body') && empty($request->getContent())) {
return;
}
$context = $this->buildRequestContext($request);
$request->attributes->set('jsonapi_request_log_context', $context);
}
/**
* Handles the kernel response event.
*
* @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
* The response event.
*/
public function onKernelResponse(ResponseEvent $event): void {
$config = $this->configFactory->get('jsonapi_request_logger.settings');
if (!$config->get('enable')) {
return;
}
$request = $event->getRequest();
$context = $request->attributes->get('jsonapi_request_log_context');
if ($context === null) {
return;
}
$context['{status_code}'] = $event->getResponse()->getStatusCode();
$log_format = $config->get('log_format') ?? 'json';
$log_template = $config->get('log_output_template');
$formatted_message = $this->formatLogMessage($context, $log_template, $log_format);
$this->logger->info($formatted_message);
}
/**
* Determines if the current route is excluded from logging.
*
* @param string $current_path
* The current request path.
* @param string $exclude_routes
* A list of excluded routes patterns.
*
* @return bool
* TRUE if the route is excluded, FALSE otherwise.
*/
private function isExcludedRoute(string $current_path, string $exclude_routes): bool {
$routes = preg_split('/\r\n|\r|\n/', $exclude_routes);
foreach ($routes as $pattern) {
if (substr($pattern, -1) === '*') {
$pattern = rtrim($pattern, '*');
if (strpos($current_path, $pattern) === 0) {
return;
return TRUE;
}
}
elseif ($pattern === $current_path) {
return;
return TRUE;
}
}
// Filter by request methods.
$allowed_methods = $config->get('request_methods') ?: [];
if (!empty($allowed_methods) && !in_array($method, $allowed_methods, true)) {
return;
}
return FALSE;
}
// Check if the request body is empty and logging is set to omit empty bodies.
if ($config->get('omit_empty_body') && empty($request->getContent())) {
return;
}
// Log the request.
$context = [
'{method}' => $method,
/**
* Checks if the HTTP method is allowed for logging.
*
* @param string $method
* The HTTP method of the request.
* @param array $allowed_methods
* An array of allowed HTTP methods.
*
* @return bool
* TRUE if the method is not allowed, FALSE otherwise.
*/
private function isMethodNotAllowed(string $method, array $allowed_methods): bool {
return !empty($allowed_methods) && !in_array($method, $allowed_methods, true);
}
/**
* Builds the context array for the request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The HTTP request.
*
* @return array
* The context array for logging.
*/
private function buildRequestContext($request): array {
return [
'{method}' => $request->getMethod(),
'{url}' => $request->getUri(),
'{status_code}' => $event->getResponse() ? $event->getResponse()->getStatusCode() : 'N/A',
'{headers}' => json_encode($request->headers->all(), JSON_PRETTY_PRINT),
'{query}' => $request->getQueryString(),
'{content}' => $request->getContent(),
];
}
/**
* Formats the log message based on the specified format.
*
* @param array $context
* The context array with request details.
* @param string $template
* The log message template.
* @param string $format
* The format type for the log message.
*
* @return string
* The formatted log message.
*/
private function formatLogMessage(array $context, string $template, string $format): string {
switch ($format) {
case 'json':
return $this->formatAsJson($context, $template);
default:
return strtr($template, $context);
}
}
// Prepare the log message using the template.
$log_template = $config->get('log_output_template') ?? 'Method: {method}, URL: {url}, Status: {status_code}, Headers: {headers}, Query: {query}, Content: {content}';
$message = strtr($log_template, $context);
/**
* Formats the log message as JSON.
*
* @param array $context
* The context array with request details.
* @param string $template
* The log message template.
*
* @return string
* The formatted JSON log message.
*/
private function formatAsJson(array $context, string $template): string {
$ordered_context = [];
preg_match_all('/\{(.*?)\}/', $template, $matches);
// Write the log entry.
$this->logger->info($message);
foreach ($matches[0] as $token) {
$ordered_context[trim($token, '{}')] = $context[$token] ?? '';
}
return json_encode($ordered_context, JSON_PRETTY_PRINT);
}
/**
* {@inheritdoc}
* Returns an array of subscribed events and their corresponding methods.
*
* @return array
* The array of events with their methods.
*/
public static function getSubscribedEvents(): array {
return [
KernelEvents::REQUEST => ['onKernelRequest'],
KernelEvents::RESPONSE => ['onKernelResponse'],
];
}
}
......@@ -30,6 +30,17 @@ class SettingsForm extends ConfigFormBase {
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('jsonapi_request_logger.settings');
// Select for log format.
$form['log_format'] = [
'#type' => 'select',
'#title' => $this->t('Log format'),
'#description' => $this->t('Choose the format for logging: JSON.'),
'#options' => [
'json' => $this->t('JSON'),
],
'#default_value' => $config->get('log_format') ?? 'json',
];
// Checkbox to enable or disable logging.
$form['enable'] = [
'#type' => 'checkbox',
......@@ -110,6 +121,7 @@ class SettingsForm extends ConfigFormBase {
->set('omit_empty_body', $form_state->getValue('omit_empty_body'))
->set('request_methods', array_filter($form_state->getValue('request_methods')))
->set('log_output_template', $form_state->getValue('log_output_template'))
->set('log_format', $form_state->getValue('log_format'))
->save();
parent::submitForm($form, $form_state);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment