Skip to content
Snippets Groups Projects
Commit b364e026 authored by vcarrero's avatar vcarrero
Browse files

#3482681: Adds the ability to refine filtering

parent 80ab6ac6
Branches
Tags 1.0.1
1 merge request!1#3482681: Adds the ability to refine filtering
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}'
......@@ -6,3 +6,24 @@ jsonapi_request_logger.settings:
type: boolean
label: 'Enable JSON:API request logging'
default_value: false
exclude_routes:
type: text
label: 'Exclude routes from logging'
description: 'Routes that should not be logged. Use * as a wildcard.'
omit_empty_body:
type: boolean
label: 'Omit requests with empty body'
description: 'If enabled, requests with an empty body will not be logged.'
default_value: false
request_methods:
type: sequence
label: 'Allowed HTTP request methods'
description: 'Filter requests to log by HTTP method. Leave empty to log all methods.'
sequence:
type: string
label: 'HTTP method'
log_output_template:
type: string
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}'
name: jsonapi_request_logger
name: JSON:API Request Logger
type: module
description: Allows you to parse all incoming JSON:API requests.
package: Development
......@@ -6,3 +6,8 @@ core_version_requirement: ^9 || ^10
dependencies:
- drupal:dblog
- drupal:jsonapi
# Information added by Drupal.org packaging script on 2024-08-23
version: '1.0.0'
project: 'jsonapi_request_logger'
datestamp: 1724404294
......@@ -9,15 +9,12 @@ use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Contracts\Translation\TranslatorTrait;
/**
* Class that records detailed information of each JSON:API HTTP request.
*/
final class RequestLoggerSubscriber implements EventSubscriberInterface {
use TranslatorTrait;
/**
* The config factory.
*
......@@ -45,28 +42,61 @@ final class RequestLoggerSubscriber implements EventSubscriberInterface {
*/
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'.
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 (substr($pattern, -1) === '*') {
$pattern = rtrim($pattern, '*');
if (strpos($current_path, $pattern) === 0) {
return;
}
}
elseif ($pattern === $current_path) {
return;
}
}
// Filter by request methods.
$allowed_methods = $config->get('request_methods') ?: [];
if (!empty($allowed_methods) && !in_array($method, $allowed_methods, true)) {
return;
}
if ($config->get('enable') && $event->getRequest()->getRequestFormat() === 'api_json') {
$request = $event->getRequest();
$context['@request_headers'] = json_encode($request->headers->all(), JSON_PRETTY_PRINT);
$context['@request_query'] = $request->getQueryString();
$context['@request_content'] = $request->getContent();
$message = <<<REQUEST
<dl>
<dt>Headers</dt>
<dd><pre>{$context['@request_headers']}</pre></dd>
<dt>Query</dt>
<dd><pre>{$context['@request_query']}</pre></dd>
<dt>Content</dt>
<dd><pre>{$context['@request_content']}</pre></dd>
</dl>
REQUEST;
$this->logger->info(t('JSON:API request received:') . $message);
// 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,
'{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(),
];
// 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);
// Write the log entry.
$this->logger->info($message);
}
/**
......
......@@ -29,6 +29,8 @@ class SettingsForm extends ConfigFormBase {
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('jsonapi_request_logger.settings');
// Checkbox to enable or disable logging.
$form['enable'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable JSON:API request logging'),
......@@ -36,6 +38,65 @@ class SettingsForm extends ConfigFormBase {
'#default_value' => $config->get('enable'),
];
// Checkbox to omit requests with an empty body.
$form['omit_empty_body'] = [
'#type' => 'checkbox',
'#title' => $this->t('Omit requests with empty body'),
'#description' => $this->t('If enabled, requests with an empty body will not be logged.'),
'#default_value' => $config->get('omit_empty_body'),
];
// Multi-selection for HTTP request methods.
$form['request_methods'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Request methods to log'),
'#description' => $this->t('Select the HTTP methods that should be logged. If none are selected, all methods will be logged.'),
'#options' => [
'GET' => $this->t('GET'),
'POST' => $this->t('POST'),
'PATCH' => $this->t('PATCH'),
'DELETE' => $this->t('DELETE'),
],
'#default_value' => $config->get('request_methods') ?? [],
];
// Textarea for excluded routes.
$form['exclude_routes'] = [
'#type' => 'textarea',
'#title' => $this->t('Exclude routes'),
'#description' => $this->t('Enter routes to exclude from logging. Use * as a wildcard to match multiple routes.'),
'#default_value' => $config->get('exclude_routes'),
];
// Textfield for log output template with token explanations.
$form['log_output_template'] = [
'#type' => 'textfield',
'#title' => $this->t('Log output template'),
'#description' => $this->t('Customize the log output using the following tokens: {method}, {url}, {status_code}, {headers}, {query}, {content}.'),
'#default_value' => $config->get('log_output_template'),
'#size' => 100,
];
// Collapsible details section explaining the replacement tokens.
$form['token_details'] = [
'#type' => 'details',
'#title' => $this->t('Available replacement tokens'),
'#description' => $this->t('The following tokens can be used in the log output template to customize the logged information:'),
'#open' => FALSE, // Details is collapsed by default.
'token_explanation' => [
'#markup' => '
<ul>
<li><strong>{method}</strong>: The HTTP method of the request (e.g., GET, POST).</li>
<li><strong>{url}</strong>: The full URL of the request.</li>
<li><strong>{status_code}</strong>: The HTTP status code of the response.</li>
<li><strong>{headers}</strong>: The request headers in JSON format.</li>
<li><strong>{query}</strong>: The query string of the request.</li>
<li><strong>{content}</strong>: The body of the request (if available).</li>
</ul>
',
],
];
return parent::buildForm($form, $form_state);
}
......@@ -45,7 +106,12 @@ class SettingsForm extends ConfigFormBase {
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('jsonapi_request_logger.settings')
->set('enable', $form_state->getValue('enable'))
->set('exclude_routes', $form_state->getValue('exclude_routes'))
->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'))
->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