Provides structured logging with support for correlation tracking and configurable sensitive data redaction.
Built on top of PSR-3, the library can be used anywhere a LoggerInterface is
expected.
composer require tiny-blocks/loggerCreate a logger with StructuredLogger::create() and use the fluent builder to configure it. All PSR-3 log levels are
supported: debug, info, notice, warning, error, critical, alert, and emergency.
use TinyBlocks\Logger\StructuredLogger;
$logger = StructuredLogger::create()
->withComponent(component: 'order-service')
->build();
$logger->info(message: 'order.placed', context: ['orderId' => 42]);Output (default template, written to STDERR):
2026-02-21T16:00:00+00:00 component=order-service correlation_id= level=INFO key=order.placed data={"orderId":42}
A correlation ID can be attached at creation time or derived later using withContext. The original instance is never
mutated.
use TinyBlocks\Logger\LogContext;
use TinyBlocks\Logger\StructuredLogger;
$logger = StructuredLogger::create()
->withContext(context: LogContext::from(correlationId: 'req-abc-123'))
->withComponent(component: 'payment-service')
->build();
$logger->info(message: 'payment.started', context: ['amount' => 100.50]);use TinyBlocks\Logger\LogContext;
use TinyBlocks\Logger\StructuredLogger;
$logger = StructuredLogger::create()
->withComponent(component: 'payment-service')
->build();
$contextual = $logger->withContext(context: LogContext::from(correlationId: 'req-abc-123'));
$contextual->info(message: 'payment.started', context: ['amount' => 100.50]);Redaction is optional and configurable. Built-in redaction strategies are provided for common sensitive fields. Each strategy accepts multiple field name variations and a configurable masking length.
Masks all characters except the last N (default: 3).
use TinyBlocks\Logger\StructuredLogger;
use TinyBlocks\Logger\Redactions\DocumentRedaction;
$logger = StructuredLogger::create()
->withComponent(component: 'kyc-service')
->withRedactions(DocumentRedaction::default())
->build();
$logger->info(message: 'kyc.verified', context: ['document' => '12345678900']);
# document → "********900"With custom fields and visible length:
use TinyBlocks\Logger\Redactions\DocumentRedaction;
DocumentRedaction::from(fields: ['cpf', 'cnpj'], visibleSuffixLength: 5);
# cpf "12345678900" → "******78900"
# cnpj "12345678000199" → "*********00199"Preserves the first N characters of the local part (default: 2) and the full domain.
use TinyBlocks\Logger\StructuredLogger;
use TinyBlocks\Logger\Redactions\EmailRedaction;
$logger = StructuredLogger::create()
->withComponent(component: 'user-service')
->withRedactions(EmailRedaction::default())
->build();
$logger->info(message: 'user.registered', context: ['email' => 'john@example.com']);
# email → "jo**@example.com"With custom fields:
use TinyBlocks\Logger\Redactions\EmailRedaction;
EmailRedaction::from(fields: ['email', 'contact_email', 'recoveryEmail'], visiblePrefixLength: 2);Masks all characters except the last N (default: 4).
use TinyBlocks\Logger\StructuredLogger;
use TinyBlocks\Logger\Redactions\PhoneRedaction;
$logger = StructuredLogger::create()
->withComponent(component: 'notification-service')
->withRedactions(PhoneRedaction::default())
->build();
$logger->info(message: 'sms.sent', context: ['phone' => '+5511999887766']);
# phone → "**********7766"With custom fields:
use TinyBlocks\Logger\Redactions\PhoneRedaction;
PhoneRedaction::from(fields: ['phone', 'mobile', 'whatsapp'], visibleSuffixLength: 4);use TinyBlocks\Logger\StructuredLogger;
use TinyBlocks\Logger\Redactions\DocumentRedaction;
use TinyBlocks\Logger\Redactions\EmailRedaction;
use TinyBlocks\Logger\Redactions\PhoneRedaction;
$logger = StructuredLogger::create()
->withComponent(component: 'user-service')
->withRedactions(
DocumentRedaction::default(),
EmailRedaction::default(),
PhoneRedaction::default()
)
->build();
$logger->info(message: 'user.registered', context: [
'document' => '12345678900',
'email' => 'john@example.com',
'phone' => '+5511999887766',
'name' => 'John'
]);
# document → "********900"
# email → "jo**@example.com"
# phone → "**********7766"
# name → "John" (unchanged)Implement the Redaction interface to create your own strategy:
use TinyBlocks\Logger\Redaction;
final readonly class TokenRedaction implements Redaction
{
public function redact(array $data): array
{
foreach ($data as $key => $value) {
if (is_array($value)) {
$data[$key] = $this->redact(data: $value);
continue;
}
if ($key === 'token' && is_string($value)) {
$data[$key] = '***REDACTED***';
}
}
return $data;
}
}Then add it to the logger:
use TinyBlocks\Logger\StructuredLogger;
$logger = StructuredLogger::create()
->withComponent(component: 'auth-service')
->withRedactions(new TokenRedaction())
->build();
$logger->info(message: 'user.logged_in', context: ['token' => 'abc123']);
# token → "***REDACTED***"The default output template is:
%s component=%s correlation_id=%s level=%s key=%s data=%s
You can replace it with any sprintf compatible template that accepts six string arguments (timestamp, component,
correlationId, level, key, data):
use TinyBlocks\Logger\StructuredLogger;
$logger = StructuredLogger::create()
->withComponent(component: 'custom-service')
->withTemplate(template: "[%s] %s | %s | %s | %s | %s\n")
->build();
$logger->info(message: 'custom.event', context: ['value' => 42]);
# [2026-02-21T16:00:00+00:00] custom-service | | INFO | custom.event | {"value":42}Logger is licensed under MIT.
Please follow the contributing guidelines to contribute to the project.