PHPackages                             timdev/stack-logger - 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. timdev/stack-logger

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

timdev/stack-logger
===================

A PSR-3 Logger that can track context.

0.2.1(10mo ago)0251[1 issues](https://github.com/timdev/php-stack-logger/issues)1MITPHPPHP ^8.3CI passing

Since Sep 11Pushed 10mo ago1 watchersCompare

[ Source](https://github.com/timdev/php-stack-logger)[ Packagist](https://packagist.org/packages/timdev/stack-logger)[ RSS](/packages/timdev-stack-logger/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (11)Versions (7)Used By (1)

PHP Stack Logger
================

[](#php-stack-logger)

Wrap your PSR-3 logger with context accumulation and callable context elements.

Inspiration
-----------

[](#inspiration)

Inspired by the [similar functionality](https://getpino.io/#/docs/child-loggers) in [pinojs](https://github.com/pinojs/pino). Design and implementation details differ, but the core idea remains: push scoped context on a logger and have it automatically pop off when the scope ends. This push/pop behavior is why I think of it as a "stack" logger.

Approach
--------

[](#approach)

The provided [implementation](src/Psr3StackLogger.php) decorates any implementation of the [PSR3 LoggerInterface](https://www.php-fig.org/psr/psr-3/), providing implementation of the additional `withContext` and `addContext` methods defined in this library's [StackLogger interface](src/StackLogger.php).

Also provided is [`MonologStackLogger`](src/MonologStackLogger.php), which decorates a `Monolog\Logger` and provides a working [`withName`](https://github.com/Seldaek/monolog/blob/a54cd1f1782f62714e4d28651224316bb5540e08/src/Monolog/Logger.php#L163-L172) implementation.

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

[](#requirements)

- PHP &gt;= 8.3
- A PSR-3 compatible logger, such as [Monolog](https://github.com/seldaek/monolog).

Usage
-----

[](#usage)

### Installation

[](#installation)

```
composer install timdev/stack-logger
```

### Context Stacking

[](#context-stacking)

```
use TimDev\StackLogger\Psr3StackLogger;

// can be anything that implements PSR-3
$yourLogger = $container->get(\Psr\Log\LoggerInterface::class);

// Decorate it
$mainLogger = new Psr3StackLogger($yourLogger);

// it works like a regular PSR-3 logger.
$mainLogger->info("Hello, World.");
// => [2020-10-17 17:40:53] app.INFO: Hello, World.
$mainLogger->info("Have some Context", ['my' => 'context']);
// => [2020-10-17 17:40:53] app.INFO: Have some Context {"my": "context"}

// but you might want to accumulate some context
$child1 = $mainLogger->withContext(['child' => 'context']);
$child1->info('From a child.', ['call-time' => 'context']);
// => [2020-10-17 17:40:53] app.INFO: From a child. {"child":"context","call-time":"context"}

// but $mainLogger is still around, without the additional context.
$mainLogger->info("Still here, with no accumulated context!");
// => [2020-10-17 17:40:53] app.INFO: Still here, with no accumulated context!
```

This can be useful in any situation where want to carry some context through successive calls.

```
/**
 * Imagine this is a long method that logs a bunch of stuff.
 */
function complexProcessing(User $user, \TimDev\StackLogger\StackLogger $logger){
    $logger = $logger->withContext(['user-id' => $user->id]);
    $logger->info('Begin processing');
    // => [2020-10-17 17:40:53] app.INFO: Begin processing. { "user-id": 123 }

    foreach($user->getMemberships() as $membership){
        $l = $logger->withContext(['membership_id'=>$membership->id]);
        $l->info('Checking membership');
        // => [2020-10-17 17:40:53] app.INFO: Checking membership. { "user-id": 123, "membership-id" => 1001 }
        if ($membership->isExpired()){
            $l->info('Membership is expired, stopping early.', ['expired-at' => $membership->expiredAt]);
            // => [2020-10-17 17:40:53] app.INFO: Membership is expired, stopping early. { "user-id": 123, "membership-id" => 1001, "expired-at": "2020-06-30T12:00:00Z' }
            continue;
        }
        // ...
        $l->info('Done handling membership');
        // => [2020-10-17 17:40:53] app.INFO: Done handling membership { "user-id": 123, 'membership-id' => 1001 }
    }
    $logger->info('Finished processing user.');
    // => [2020-10-17 17:40:53] app.INFO: Finished processing user. { "user-id": 123 }
}
```

### Dynamic (Callable) Context

[](#dynamic-callable-context)

The other feature provided here is callable context. Any context elements that are `callable` will be invoked at logging-time, and the result of the computation will be logged. Callables take a single array argument: `function(array $context): mixed`

```
$startTime = microtime(true);
$logger = $logger->withContext([
    'elapsed_ms' => fn() => (microtime(true) - $startTime) * 1000000 * 1000,
    'context_count' => fn($ctx) => count($ctx)
]);
// ... later that day ...
$logger->info('Something happened later.');
// => [2020-10-17 17:40:53] app.INFO: Something happened later. { "elapsed_ms": 1523, "context_count": 2 }
```

**NOTE:** you should carefully consider the performance implications when using callables in your stacked context. Context is processed *before* invoking the wrapped logger's methods. The callables will be invoked on every logging method call, even if the underlying logger is configured to ignore the log-level.

### NullLoggers

[](#nullloggers)

All `StackLogger` implementations provide a static `makeNullLogger()` method, which returns an instance that is configured to discard all log messages. These "null loggers" can be handy in tests, or as a default logger in classes that can optionally accept a real logger:

```
use TimDev\StackLogger\MonologStackLogger;

class SomeService
{
    public function __construct(?MonologStackLogger $logger = null)
    {
        $this->logger = $logger ?? MonologStackLogger::makeNullLogger();
    }
}
```

To Do
-----

[](#to-do)

- Make MonologStackLogger implement Monolog's ResettableInterface?
- Consider how this might play with Laravel, the insanely popular PHP framework that I don't personally use much. PRs welcome.

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance55

Moderate activity, may be stable

Popularity13

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity60

Established project with proven stability

 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 ~281 days

Recently: every ~326 days

Total

6

Last Release

303d ago

PHP version history (4 changes)0.1.0PHP ^8.0

0.1.1PHP ~8.0.0 || ~8.1.0

0.1.2PHP ~8.0.0 || ~8.1.0 || ~8.2.0

0.2.0PHP ^8.3

### Community

Maintainers

![](https://www.gravatar.com/avatar/4eb475c7f4d3d1df5053f83417bf4e756a35b59d1686cd7d379055da2828017f?d=identicon)[timdev](/maintainers/timdev)

---

Top Contributors

[![timdev](https://avatars.githubusercontent.com/u/513999?v=4)](https://github.com/timdev "timdev (88 commits)")

---

Tags

loggingContextloggerPSR3monolog

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleECS

Type Coverage Yes

### Embed Badge

![Health badge](/badges/timdev-stack-logger/health.svg)

```
[![Health](https://phpackages.com/badges/timdev-stack-logger/health.svg)](https://phpackages.com/packages/timdev-stack-logger)
```

###  Alternatives

[analog/analog

Fast, flexible, easy PSR-3-compatible PHP logging package with dozens of handlers.

3451.5M24](/packages/analog-analog)[inpsyde/wonolog

Monolog-based logging package for WordPress.

183617.9k7](/packages/inpsyde-wonolog)[logtail/monolog-logtail

Logtail handler for Monolog

233.2M3](/packages/logtail-monolog-logtail)[atrapalo/monolog-elasticsearch

A Monolog handler and formatter that makes use of the elasticsearch/elasticsearch package

1123.0k](/packages/atrapalo-monolog-elasticsearch)[lefuturiste/monolog-discord-handler

A simple monolog handler for support Discord webhooks

34111.6k4](/packages/lefuturiste-monolog-discord-handler)[markrogoyski/simplelog-php

Powerful PSR-3 logging. So easy, it's simple.

2818.1k4](/packages/markrogoyski-simplelog-php)

PHPackages © 2026

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