Search by

adt / request-logger

appsdevteam

Logs HTTP requests (and optionally responses) of a Nette application into request_log / request_log_body tables, sanitized and written in a single transaction.

Package info

github.com/AppsDevTeam/request-logger

pkg:composer/adt/request-logger

Statistics

Installs: 588

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.2 2026-10-24 17:36 UTC

This package is auto-updated.

Last update: 2026-10-24 17:39:02 UTC


README

Logs HTTP requests (and optionally responses) of a Nette application into the request_log and request_log_body tables.

composer require adt/request-logger

Why

When investigating an incident or a customer complaint, you need to know exactly what the client sent and what it got back — including headers and body. The logger stores this in two tables with different retention: the request header (method, URL, status code, IP, response time) is kept for a long time, while the bulky body (headers, parameters, payload, response) is purged sooner.

  • Sensitive data (passwords, tokens, card numbers) is stripped by adt/log-sanitizer before anything is written. The response is our own output, so it is masked without the card number alert (withoutCardNumberAlert()) - the alert is meant for data that someone else sent.
  • The header and the body are written in a single transaction — a concurrent log move/purge never catches an orphaned header without its body.
  • Timestamps are always UTC with microseconds (DATETIME(6) in both tables), for correlation with the audit log and to stay unambiguous across the DST switch.
  • Writes go through a dedicated database connection (outside the Doctrine EntityManager), so rolling back the application's transaction does not discard the log.
  • A logging failure never breaks the request — it is logged via Tracy and the request carries on.

Usage

Register the service (it opens its own connection, so it takes connection parameters, not a Connection):

services:
	- ADT\RequestLogger\RequestLogger(%database%)

Your project's security user implements the minimal ADT\RequestLogger\SecurityUser interface (isLoggedIn(), getId()).

In your base presenter:

protected function shutdown(Nette\Application\Response $response): void
{
	$this->requestLogger->logRequest($this, $response);
}

Only requests of a logged-in user, or requests carrying an API key (RequestLogger::$apiKeyId), are logged. Anonymous traffic is not.

Optional switches

// log the response body as well (mind the volume)
RequestLogger::$logResponse = true;

// the request came with an API key - log it even without a logged-in user
RequestLogger::$apiKeyId = $apiKey->getId();

Labelling a request

Two optional columns the trait already provides; fill them at any point while the request is being processed:

// the id of the operation the request carried - the same value as audit_log.correlation_id,
// so an audit event leads to the request including its payload in a single query
RequestLogger::addValue('correlation_id', $transactionId);

// what happened to the request - a category from a closed set of project constants,
// so requests can be looked up by outcome, not only by URL and status code
RequestLogger::addValue('identifier', 'duplicate_order');

The two are complementary: correlation_id points at one record elsewhere, identifier sorts requests into buckets.

Custom columns

A project can add columns of its own to request_log the same way:

RequestLogger::addValue('device_id', $deviceId);

System columns (created_at, method, url, ip, code, response_time, identity_id, api_key_id) cannot be overridden.

Entities

The package provides interfaces and traits; the entities themselves are declared by the project:

#[ORM\Entity]
class RequestLog implements \ADT\RequestLogger\Entities\RequestLog
{
	use Identifier;
	use \ADT\RequestLogger\Entities\RequestLogTrait;
}

#[ORM\Entity]
class RequestLogBody implements \ADT\RequestLogger\Entities\RequestLogBody
{
	use Identifier;
	use \ADT\RequestLogger\Entities\RequestLogBodyTrait;
}

Indexes

The traits declare their indexes: RequestLogTrait on createdAt, identityId, apiKeyId, ip, correlationId and identifier, RequestLogBodyTrait on createdAt (without it, retention purging scans the whole table). Indexes on project-specific columns belong to the entity.

Doctrine itself reads #[Index] only from the entity class and ignores it on traits. The indexes are added by TraitIndexListener from adt/doctrine-components, which the project registers via its extension:

extensions:
	doctrineComponents: ADT\DoctrineComponents\DI\DoctrineComponentsExtension

adt/fancyadmin registers the listener itself when the extension is missing. Without the listener, the entity has to declare the indexes itself, otherwise the next schema diff drops them.

Tests

composer test