northrook/logger

Buffering PSR-3 logger that collapses repeated log shapes into shared entries

Maintainers

Package info

codeberg.org/northrook/logger

Issues

pkg:composer/northrook/logger

Transparency log

Statistics

Installs: 400

Dependents: 6

Suggesters: 0

dev-main 2026-07-23 10:28 UTC

This package is auto-updated.

Last update: 2026-07-23 09:28:22 UTC


README

Buffering PSR-3 logger that collapses repeated log shapes into shared entries.

Requirements

Installation

composer require northrook/logger

Quick start

use Northrook\Logger;

$logger = new Logger();
$logger->info('User login', ['userId' => 1]);
$logger->info('User login', ['userId' => 2]); // same shape → 1 entry, 2 occurrences
$logger->warning('Cache miss');

$logger->count();        // 2 unique entries
$logger->occurrences();  // 3 log calls

foreach ($logger->getEntries() as $entry) {
    $entry->level;
    $entry->message;
    $entry->getContext(); // all occurrence contexts
}

Aggregation

Identity is level + message + context keys (order-insensitive). Context values are stored per occurrence on the shared entry.

CallResult
info('hi', ['userId' => 1]) then info('hi', ['userId' => 2])One entry, two occurrences
info('hi') then info('hi', ['userId' => 1])Two entries (empty ≠ keyed)
info('hi', ['b' => 1, 'a' => 2]) then info('hi', ['a' => 9, 'b' => 8])One entry (key order ignored)

Messages are stored as-is — no {placeholder} interpolation.

LogEntry::$timestamp is the last-seen occurrence.

Reading entries

getEntries() returns a frozen LoggerEntries snapshot, keyed by signature. The cache is cleared on the next log() call.

Property / methodMeaning
$entries / iteratorLogEntry values keyed by signature
$count / count()Unique aggregated entries
$occurrencesCountTotal log() calls at export time
LogEntry::getContext()Context from every occurrence
LogEntry::getContext(true)Context from the latest occurrence only

Levels

log() accepts LogLevel, numeric values (100600), or PSR-3 / case-name strings (debug, DEBUG) via LogLevel::resolve().

use Northrook\Logger\LogLevel;

$logger->log(LogLevel::WARNING, 'Slow query');
$logger->log('warning', 'Slow query');
$logger->log(300, 'Slow query');

Immediate emit

Pass an optional emit callback to the constructor. Level methods and log() accept $immediate = false; when true and a callback is set, that hit is pushed out immediately and its LogOccurrence::$emitted is set so a later writer can skip duplicates. Hits still accumulate either way.

$logger = new Logger(
    static function (LogLevel $level, string $message, array $context): void {
        // e.g. write to stderr / disk
    },
);

$logger->error('Disk full', ['path' => '/tmp'], immediate: true);
$logger->info('Still buffered'); // callback not called; emitted = false

$immediate with no callback is a no-op for output; the occurrence stays emitted: false.

Development

composer test
composer phpstan

License

BSD-3-Clause. See LICENSE.