PHPackages                             meritum/structured-logging - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Logging &amp; Monitoring](/categories/logging)
4. /
5. meritum/structured-logging

ActiveLibrary[Logging &amp; Monitoring](/categories/logging)

meritum/structured-logging
==========================

Meritum module for structured exception logging — domain exception model, translation pipeline, PSR-3 reporting, and correlation ID enrichment

1.0.0(today)00MITPHPPHP ^8.4CI passing

Since Jun 10Pushed todayCompare

[ Source](https://github.com/MeritumIO/structured-logging)[ Packagist](https://packagist.org/packages/meritum/structured-logging)[ RSS](/packages/meritum-structured-logging/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (6)Versions (2)Used By (0)

meritum/structured-logging
==========================

[](#meritumstructured-logging)

Structured exception logging for the Meritum ecosystem. Provides a domain exception model, a translation pipeline that converts arbitrary exceptions into structured domain exceptions, PSR-3 reporting, and correlation ID enrichment.

Installation
------------

[](#installation)

```
composer require meritum/structured-logging
```

A PSR-3 logger must be registered in your kernel before adding this module. [`meritum/logger`](https://github.com/MeritumIO/logger) is a zero-dependency option.

Module registration
-------------------

[](#module-registration)

```
use Meritum\StructuredLogging\StructuredLoggingModule;

$kernel->addModule(new StructuredLoggingModule());
```

The module registers the following services:

ServiceNotes`CorrelationId`Singleton. Auto-generates a UUID v4 on first resolution.`CorrelationIdEnricher`Tagged as `log.context.enrichers`. Adds `correlation_id` to every log entry.`ExceptionTranslator`Collects all `exception.translator.handlers` tagged services.`ExceptionReporter`Translates, then logs via the decorated `LoggerInterface`.`LoggerInterface`Decorated with `ContextEnrichingLogger` to apply registered enrichers.Domain exceptions
-----------------

[](#domain-exceptions)

Define your own domain exceptions by extending `DomainException`:

```
use Meritum\StructuredLogging\Exception\DomainException;
use Meritum\StructuredLogging\Severity;

final class OrderNotFoundException extends DomainException
{
    public function getErrorCode(): string
    {
        return 'ORDER_' . $this->generateErrorCodeSuffix(1);
    }
}

throw new OrderNotFoundException(
    message: 'Order not found',
    severity: Severity::Warning,
    context: ['order_id' => $orderId],
    retryable: false,
);
```

Every domain exception exposes a `structuredData` property containing the full structured payload, which lands in the PSR-3 log context:

```
[
    'error_code'  => 'ORDER_0001',
    'message'     => 'Order not found',
    'severity'    => 'warning',
    'retryable'   => false,
    'occurred_at' => '2026-06-10T12:00:00+00:00',
    'detail'      => ['order_id' => 42],
    'metadata'    => ['file' => '...', 'line' => 42, 'class' => '...'],
]
```

The `detail` key carries the per-exception contextual data passed in `$context`. This is deliberately named to avoid a collision with the PSR-3 context array wrapper that some loggers produce.

Translation pipeline
--------------------

[](#translation-pipeline)

The translator converts arbitrary `Throwable` instances into domain exceptions. Handlers are registered as tagged kernel services — the translator collects them automatically at boot.

### Defining a handler

[](#defining-a-handler)

Implement `TranslationHandler` and tag it as `exception.translator.handlers` in your module:

```
use Throwable;
use Meritum\StructuredLogging\Severity;
use Meritum\StructuredLogging\TranslationHandler;
use Meritum\StructuredLogging\Exception\DomainException;

final class DatabaseExceptionHandler implements TranslationHandler
{
    public function matches(Throwable $exception): bool
    {
        return $exception instanceof \PDOException;
    }

    public function handle(Throwable $exception): DomainException
    {
        return new DatabaseException(
            message: 'A database error occurred',
            severity: Severity::Error,
            context: ['pdo_code' => $exception->getCode()],
            previous: $exception,
        );
    }

    public function priority(): int
    {
        return 0;
    }
}
```

```
// In your module's register() method:
$kernel->define(DatabaseExceptionHandler::class, fn() => new DatabaseExceptionHandler())
       ->tag('exception.translator.handlers');
```

Higher `priority()` values win when multiple handlers match the same exception. If no handler matches, the translator wraps the exception in an `UnknownException` and logs it at `error` severity.

### Using the translator directly

[](#using-the-translator-directly)

```
use Meritum\StructuredLogging\ExceptionTranslator;

$domain = $translator->translate($e);
```

If `$e` is already a `DomainException`, it is returned as-is.

Reporting
---------

[](#reporting)

`ExceptionReporter::report()` runs the full pipeline — translate, enrich, log — and returns the resulting `DomainException` for the caller to act on (render a response, rethrow, etc.):

```
use Meritum\StructuredLogging\ExceptionReporter;

$domain = $reporter->report($e);
```

Context enrichment
------------------

[](#context-enrichment)

Enrichers add data to the PSR-3 log context on every log call through the decorated logger. `CorrelationIdEnricher` is registered automatically. Add your own by implementing `ContextEnricher` and tagging it:

```
use Meritum\StructuredLogging\ContextEnricher;

final class AppVersionEnricher implements ContextEnricher
{
    public function enrich(array $context): array
    {
        return $context + ['app_version' => '1.4.2'];
    }
}
```

```
$kernel->define(AppVersionEnricher::class, fn() => new AppVersionEnricher())
       ->tag('log.context.enrichers');
```

Use the `+` operator rather than `array_merge` so that context values already set by the caller are not overwritten.

Correlation ID
--------------

[](#correlation-id)

`CorrelationId` is a singleton auto-generated at boot. It is automatically added to every log entry via `CorrelationIdEnricher`.

To overwrite the generated ID from an incoming HTTP request header (e.g. in a PSR-15 middleware):

```
use Meritum\StructuredLogging\CorrelationId;

final class CorrelationIdMiddleware implements MiddlewareInterface
{
    public function __construct(private readonly CorrelationId $correlationId) {}

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $header = $request->getHeaderLine('X-Correlation-ID');

        $this->correlationId->set($header);

        return $handler->handle($request);
    }
}
```

`set()` validates the value as a UUID v4. Invalid or missing values are silently ignored and the auto-generated ID is preserved — a garbage header from a client is not an exceptional condition.

Severity levels
---------------

[](#severity-levels)

CasePSR-3 level`Severity::Critical``critical``Severity::Error``error``Severity::Warning``warning``Severity::Info``info``Severity::Debug``debug`

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/c8147ce1d901e9fa2ec95d6408e5862025211f9ae60131e41fb115a5a0916ce4?d=identicon)[georgeff](/maintainers/georgeff)

---

Top Contributors

[![MikeGeorgeff](https://avatars.githubusercontent.com/u/6169468?v=4)](https://github.com/MikeGeorgeff "MikeGeorgeff (1 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/meritum-structured-logging/health.svg)

```
[![Health](https://phpackages.com/badges/meritum-structured-logging/health.svg)](https://phpackages.com/packages/meritum-structured-logging)
```

###  Alternatives

[sentry/sentry

PHP SDK for Sentry (http://sentry.io)

1.9k240.0M312](/packages/sentry-sentry)[matomo/matomo

Matomo is the leading Free/Libre open analytics platform

21.6k38.2k](/packages/matomo-matomo)[illuminate/log

The Illuminate Log package.

6225.0M597](/packages/illuminate-log)[tempest/framework

The PHP framework that gets out of your way.

2.2k31.1k11](/packages/tempest-framework)[api-platform/metadata

API Resource-oriented metadata attributes and factories

244.5M181](/packages/api-platform-metadata)[pagemachine/typo3-formlog

Form log for TYPO3

23233.9k7](/packages/pagemachine-typo3-formlog)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
