PHPackages                             lezhnev74/psr-logging-masking-middleware - 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. [HTTP &amp; Networking](/categories/http)
4. /
5. lezhnev74/psr-logging-masking-middleware

ActiveLibrary[HTTP &amp; Networking](/categories/http)

lezhnev74/psr-logging-masking-middleware
========================================

A logging middleware for any PSR-7 HTTP client that logs every request/response through a PSR-3 logger, with headers, query args and body keys masked. Built on PSR-7, PSR-17 and PSR-3.

1.1.2(1mo ago)0601MITPHPPHP ^8.1CI passing

Since Jul 2Pushed 1mo agoCompare

[ Source](https://github.com/lezhnev74/psr-logging-masking-middleware)[ Packagist](https://packagist.org/packages/lezhnev74/psr-logging-masking-middleware)[ RSS](/packages/lezhnev74-psr-logging-masking-middleware/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (15)Versions (5)Used By (1)

PSR-compatible Logging Masking Middleware
=========================================

[](#psr-compatible-logging-masking-middleware)

[![tests](https://github.com/lezhnev74/psr-logging-masking-middleware/actions/workflows/tests.yml/badge.svg)](https://github.com/lezhnev74/psr-logging-masking-middleware/actions/workflows/tests.yml)[![codecov](https://camo.githubusercontent.com/dbf55a7643993291c0a9d13e2b72990408710e2cb758908e7be630189f94caf5/68747470733a2f2f636f6465636f762e696f2f67682f6c657a686e657637342f7073722d6c6f6767696e672d6d61736b696e672d6d6964646c65776172652f6272616e63682f6d61696e2f67726170682f62616467652e737667)](https://codecov.io/gh/lezhnev74/psr-logging-masking-middleware)[![PHPStan](https://camo.githubusercontent.com/14995ff65edea59395c224e37e4fc66f91c1e601c1a58311e3c6f38c4fe37feb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c2532306d61782d627269676874677265656e)](https://phpstan.org/)

A logging middleware for any [PSR-7](https://www.php-fig.org/psr/psr-7/) HTTP client that logs every request and response for later debugging, with **secrets redacted** - headers, query-string args and body keys masked per message. Built on PSR-7 (messages), [PSR-17](https://www.php-fig.org/psr/psr-17/) (factories, auto-discovered) and [PSR-3](https://www.php-fig.org/psr/psr-3/) (logging), so it works with any PSR-7 impl and PSR-3 logger - none a hard dependency.

Requirements
------------

[](#requirements)

- PHP 8.1 - 8.5
- Any PSR-7 / PSR-17 implementation in your app (e.g. `guzzlehttp/psr7` or `nyholm/psr7`), discovered via [php-http/discovery](https://docs.php-http.org/en/latest/discovery.html).

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

[](#installation)

```
composer require lezhnev74/psr-logging-masking-middleware
```

Usage
-----

[](#usage)

The core is a `MessageLogger`: give it a PSR-3 logger and a `Masker` - the config-bound masking policy. It masks each message and writes the exchange as a single debug record. Bodies are masked by content-type - JSON by key (recursively), `x-www-form-urlencoded` by field name, any other type replaced with a `` note so an opaque body never leaks.

```
use Lezhnev74\PsrLoggingMaskingMiddleware\MaskingConfig;
use Lezhnev74\PsrLoggingMaskingMiddleware\MessageLogger;
use Lezhnev74\PsrLoggingMaskingMiddleware\MessageMasker;

$logger = new MessageLogger(
    $psr3Logger,                        // inject your app's logger
    new MessageMasker(
        MaskingConfig::create(          // applied to request AND response
            headerNames: ['Authorization', 'Set-Cookie'],
            queryNames: ['api_key'],
            bodyKeys: ['password'],
        ),
    ),
);
```

One config masks both messages - list every secret name wherever it may appear. `MessageMasker`'s further constructor arguments pin a PSR-17 stream factory or customize the replacement string; pass a `NullMasker`to the logger to log exchanges unmasked. See [tests/MessageLoggerTest.php](tests/MessageLoggerTest.php).

### Fluent builder

[](#fluent-builder)

`MessageLoggerBuilder::for($psr3Logger)` wires the same `MessageLogger` in one chain - no hand-built `MaskingConfig` or `MessageMasker`:

```
use Lezhnev74\PsrLoggingMaskingMiddleware\MaskingConfig;
use Lezhnev74\PsrLoggingMaskingMiddleware\MaskTarget;
use Lezhnev74\PsrLoggingMaskingMiddleware\MessageLoggerBuilder;
use Psr\Log\LogLevel;

$logger = MessageLoggerBuilder::for($psr3Logger)
    ->withMaskingConfig(MaskingConfig::create(
        headerNames: ['Authorization', 'Set-Cookie'],
        queryNames: ['api_key'],
        bodyKeys: ['password', 'card.number'],
    ))
    ->placeholder('[redacted]')          // or ->replaceWith(fn (MaskTarget $t) => '***')
    ->logLevel(LogLevel::INFO)           // defaults to debug
    // ->streamFactory($psr17Factory)    // optional; discovered when omitted
    ->build();
```

Call `withMaskingConfig()` more than once to merge configs (deduped case-insensitively), and `placeholder()`/`replaceWith()` share one slot so the last one set wins. See [tests/MessageLoggerBuilderTest.php](tests/MessageLoggerBuilderTest.php).

### Guzzle

[](#guzzle)

`HandlerMiddleware::for($logger)` returns a generic `fn(callable): callable`middleware for any handler stack. Push it onto Guzzle's:

```
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Lezhnev74\PsrLoggingMaskingMiddleware\HandlerMiddleware;

$stack = HandlerStack::create();
$stack->push(HandlerMiddleware::for($logger));

$client = new Client(['handler' => $stack]);
```

See [tests/GuzzleClientTest.php](tests/GuzzleClientTest.php) and [tests/HandlerMiddlewareTest.php](tests/HandlerMiddlewareTest.php).

### More

[](#more)

Each is a green test - the executable, always-current spec:

- **Per-message masking** - subclass `MessageLogger` and override the single `resolveMasker()` seam to vary the masking by path, method, headers, etc.; it receives the message being masked and the exchange's request, so it can key on either (return a `NullMasker` = unmasked): [tests/MessageLoggerTest.php](tests/MessageLoggerTest.php).
- **Custom replacement** - pass a `replacer:` closure to `MessageMasker`; it receives a `MaskTarget` (message, kind, path, value) and returns the string to substitute (default `'***'`): [tests/MessageMaskerTest.php](tests/MessageMaskerTest.php).
- **Laravel `Http` facade** - pass the same handler stack via `Http::withOptions(['handler' => $stack])` (or `Http::globalOptions(...)` in a provider): [tests/LaravelHttpFacadeTest.php](tests/LaravelHttpFacadeTest.php).
- **Any PSR-18 client** (no handler stack) - wrap it with the `LoggingClient`decorator: [tests/LoggingClientTest.php](tests/LoggingClientTest.php).
- **Standalone masking (no logging)** - the `Masker` interface is the masking contract (`mask(MessageInterface): MessageInterface`, returns a masked clone) that `MessageLogger` itself consumes. `MessageMasker` is its canonical implementation, bound to one `MaskingConfig`; `NullMasker` is the no-op implementation. Build the masker with `preserveUnknownBodies: true` to keep a body whose media type has no built-in masker byte-for-byte instead of collapsing it to a size note - for consumers (e.g. traffic recorders) that must keep bodies faithful: [tests/MaskerContractTest.php](tests/MaskerContractTest.php).

Local development
-----------------

[](#local-development)

QA tooling runs in a PHP 8.5 Docker container (with Xdebug) via the `dev`wrapper - no host PHP needed. `./dev ` is `composer ` in Docker; anything unrecognized is passed to `composer`.

```
cp .env.dist .env  # set DOCKER_USER_ID / DOCKER_GROUP_ID (see `id -u` / `id -g`)
./dev install      # composer install
./dev test         # run the test suite
./dev check        # code style + static analysis + tests
./dev cs-fix       # auto-fix code style
```

Contributing
------------

[](#contributing)

- **TDD.** Every new branch needs a covering test; the masking/serialization suite must stay green against both Guzzle and Nyholm PSR-7 impls.
- **Conventional Commits** ([spec](https://www.conventionalcommits.org/)) drive **semantic versioning** (`fix` → patch, `feat` → minor, `!`/`BREAKING CHANGE`→ major).
- Run `./dev check` before opening a PR.

License
-------

[](#license)

MIT - see [LICENSE](LICENSE).

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance90

Actively maintained with recent releases

Popularity12

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity45

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

Every ~1 days

Total

4

Last Release

49d ago

### Community

Maintainers

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

---

Top Contributors

[![lezhnev74](https://avatars.githubusercontent.com/u/10206110?v=4)](https://github.com/lezhnev74 "lezhnev74 (34 commits)")

---

Tags

psrpsr-7psr-3middlewarepsr-17loggingredactionMasking

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Rector

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/lezhnev74-psr-logging-masking-middleware/health.svg)

```
[![Health](https://phpackages.com/badges/lezhnev74-psr-logging-masking-middleware/health.svg)](https://phpackages.com/packages/lezhnev74-psr-logging-masking-middleware)
```

###  Alternatives

[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

86337.5k](/packages/flow-php-flow)[tempest/framework

The PHP framework that gets out of your way.

2.3k37.6k21](/packages/tempest-framework)[telnyx/telnyx-php

Official Telnyx PHP SDK — APIs for Voice, SMS, MMS, WhatsApp, Fax, SIP Trunking, Wireless IoT, Call Control, and more. Build global communications on Telnyx's private carrier-grade network.

36826.2k2](/packages/telnyx-telnyx-php)[cakephp/cakephp

The CakePHP framework

8.9k20.0M1.9k](/packages/cakephp-cakephp)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k6.0M780](/packages/sylius-sylius)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)

PHPackages © 2026

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