northrook / logger
Buffering PSR-3 logger that collapses repeated log shapes into shared entries
Requires
- php: >=8.4
- northrook/core-contracts: dev-main
- psr/log: ^3.0
Requires (Dev)
- northrook/php-cs: dev-main
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^11.5
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
PHP>=8.4psr/lognorthrook/core-contracts
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.
| Call | Result |
|---|---|
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 / method | Meaning |
|---|---|
$entries / iterator | LogEntry values keyed by signature |
$count / count() | Unique aggregated entries |
$occurrencesCount | Total 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 (100–600), 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.