-
Notifications
You must be signed in to change notification settings - Fork 2
Logger
The Logger class is a singleton implementing the PSR-3 LoggerInterface.
It provides a unified way to:
- log messages to LogBar (runtime log viewer);
- persist warnings and errors into the database (
LogEvent) through a structured log-event writer; - send email and Telegram notifications for severe events;
- register custom error / exception / shutdown handlers;
- optionally forward errors to Sentry and OpenTelemetry-compatible observability adapters.
The logger is designed to be safe in production and helpful in development, while keeping the integration small and predictable.
use Pair\Core\Logger;
$logger = Logger::getInstance();Logger::getInstance() always returns the same shared instance.
Pair uses numeric levels compatible with PSR-3 severity ordering:
| Constant | Value | Name |
|---|---|---|
Logger::EMERGENCY |
1 | emergency |
Logger::ALERT |
2 | alert |
Logger::CRITICAL |
3 | critical |
Logger::ERROR |
4 | error |
Logger::WARNING |
5 | warning |
Logger::NOTICE |
6 | notice |
Logger::INFO |
7 | info |
Logger::DEBUG |
8 | debug |
Lower number = more severe.
The Logger::LEVEL_NAMES constant maps numeric levels to their string names.
Every call ends up in LogBar:
-
debug,info,notice→LogBar::event(..., 'notice') -
warning→LogBar::event(..., 'warning') -
error,critical,alert,emergency→LogBar::event(..., 'error')
If the level is WARNING or worse (<= Logger::WARNING), the logger also:
- forwards a structured
ObservabilityEventwhen an event-aware adapter is installed; - snapshots the request and log event into the DB (
LogEvent); - optionally sends email notifications (if configured);
- optionally sends Telegram notifications (if configured).
The database row includes the rendered message and sanitized context, plus release, environment, trace, request, user-agent, exception, and grouping metadata.
Logger reads these values from Env during construction:
-
PAIR_LOGGER_DISABLED
If set totrue, the logger is disabled. -
PAIR_LOGGER_EMAIL_RECIPIENTS
Comma-separated emails. Example:ops@example.com,dev@example.com -
PAIR_LOGGER_EMAIL_THRESHOLD
Numeric level from 1 to 8. Default is4(error). -
TELEGRAM_BOT_TOKEN
Bot token used to send Telegram messages. -
PAIR_LOGGER_TELEGRAM_CHAT_IDS
Comma-separated chat IDs. Example:123456789,987654321 -
PAIR_LOGGER_TELEGRAM_THRESHOLD
Numeric level from 1 to 8. Default is4(error).
Additionally:
-
APP_DEBUGcontrols handler behavior (seeerrorHandler()). -
SENTRY_DSNenables forwarding errors to Sentry (when SDK functions exist).
Example .env:
APP_DEBUG=false
PAIR_LOGGER_DISABLED=false
PAIR_LOGGER_EMAIL_RECIPIENTS=ops@example.com,dev@example.com
PAIR_LOGGER_EMAIL_THRESHOLD=4
TELEGRAM_BOT_TOKEN=123456:ABCDEF_your_token_here
PAIR_LOGGER_TELEGRAM_CHAT_IDS=123456789,987654321
PAIR_LOGGER_TELEGRAM_THRESHOLD=3
SENTRY_DSN=https://public@sentry.example/1Email is sent only if:
- a mail transport has been configured (
$logger->useTransport(...)); -
emailRecipientsare set; -
level <= emailThreshold.
use Pair\Core\Logger;
$logger = Logger::getInstance();
$logger->useTransport('smtp', [
// The config array is passed to the transport constructor.
// Keys depend on the transport implementation.
'host' => 'smtp.example.com',
'port' => 587,
'username' => 'user',
'password' => 'secret',
'encryption' => 'tls',
'from' => ['no-reply@example.com' => 'Pair'],
]);
$logger->setEmailRecipients(['ops@example.com']);
$logger->setEmailThreshold(Logger::ERROR);$logger->useTransport('ses', [
// Passed as-is to AmazonSes transport
'region' => 'eu-west-1',
'accessKeyId' => '...',
'secretAccessKey' => '...',
'from' => ['no-reply@example.com' => 'Pair'],
]);$logger->useTransport('sendmail', [
// Passed as-is to Sendmail transport
'from' => ['no-reply@example.com' => 'Pair'],
]);If email transport is not configured, logs still work (LogBar + DB snapshot + Telegram if configured).
Telegram is sent only if:
-
telegramBotTokenis set; -
telegramChatIdscontains at least one chat ID; -
telegramThreshold >= level(meaning: send on severe events only).
use Pair\Core\Logger;
$logger = Logger::getInstance();
$logger->setTelegramBotToken(Env::get('TELEGRAM_BOT_TOKEN'));
$logger->setTelegramChatIds([123456789, 987654321]);
$logger->setTelegramThreshold(Logger::CRITICAL);Pair can route PHP errors, uncaught exceptions, and fatal shutdown errors to the logger.
use Pair\Core\Logger;
Logger::registerHandlers();This sets:
set_error_handler([Logger::class, 'errorHandler'])set_exception_handler([Logger::class, 'exceptionHandler'])register_shutdown_function([Logger::class, 'shutdownHandler'])
$logger = Logger::getInstance();
$logger->info('User logged in');
$logger->notice('Profile updated');
$logger->warning('Slow query detected');
$logger->error('Payment gateway failure');Logger replaces {placeholders} with scalar / stringable values from $context.
$logger->error(
'Order {orderId} failed for user {userId}',
['orderId' => 1001, 'userId' => 55]
);This is done safely: non-scalar values are ignored.
For warning/error records, the logger also stores sanitized context in LogEvent and promotes selected values to first-class columns.
Useful standard keys:
-
eventName: stable event name such asorder.export.failed. -
correlationId: request or job correlation ID. -
exception: aThrowableinstance, or exception details prepared by the caller.
appVersion and environment are read from the Pair runtime (APP_VERSION and Application::getEnvironment()). Trace ID is read from an incoming W3C traceparent server value when available, otherwise Pair derives a trace-compatible ID from the correlation ID.
Request arrays and context payloads are sanitized by LogEventSanitizer before being stored. Sensitive keys such as authorization, cookie, password, secret, token, and API keys are redacted.
$logger->log('error', 'Something went wrong');
$logger->log(Logger::ERROR, 'Something went wrong');Returns the singleton instance.
$logger = Logger::getInstance();Registers the error, exception and shutdown handlers.
Logger::registerHandlers();public static function errorHandler(int $errno, string $errstr, ?string $errfile = null, ?int $errline = null): bool
Custom PHP error handler.
- Logs the error internally (as
errorlevel). - If
SENTRY_DSNis set, forwards to Sentry. - Forwards to Insight Hub handler.
- If
APP_DEBUGistrue, returnsfalseso PHP can also display the error. - Otherwise returns
trueto suppress output in production.
Returning
falseallows the default PHP handler to run, which is useful during development.
Custom uncaught exception handler.
- Forwards to Sentry (if enabled and SDK exists).
- Forwards the exception to Insight Hub.
- Logs the exception message and location internally.
Shutdown handler for fatal errors.
- Reads
error_get_last(). - If the last error is fatal (
E_ERROR,E_CORE_ERROR,E_COMPILE_ERROR,E_USER_ERROR):- logs it;
- forwards to Sentry (if enabled);
- forwards to Insight Hub.
Disables the logger (no LogBar, no DB snapshot, no notifications).
Logger::getInstance()->disable();The main PSR-3 log entrypoint.
- Accepts:
- numeric level
1..8, or - string level name (
"error","warning", etc).
- numeric level
- Renders
{placeholders}using the provided context. - Logs to LogBar.
- For WARNING or worse, triggers processing (DB + notifications).
Each of these calls log() with the corresponding level:
emergency(Stringable|string $message, array $context = [])alert(Stringable|string $message, array $context = [])critical(Stringable|string $message, array $context = [])error(Stringable|string $message, array $context = [])warning(Stringable|string $message, array $context = [])notice(Stringable|string $message, array $context = [])info(Stringable|string $message, array $context = [])debug(Stringable|string $message, array $context = [])
Example:
$logger->critical('Database is not responding', ['errorCode' => 123]);Configures the email transport used for error notifications.
Supported types:
-
smtp→SmtpMailer -
ses→AmazonSes -
sendmail→Sendmail
$logger->useTransport('smtp', [/* transport config */]);If the type is unknown, an InvalidArgumentException is thrown.
Sets the email recipients list. Values are normalized and filtered to valid emails.
$logger->setEmailRecipients(['ops@example.com', 'dev@example.com']);Sets the email threshold level (1..8).
- If the level is invalid, the logger emits an internal configuration error.
$logger->setEmailThreshold(Logger::ERROR);Sets the Telegram bot token used by TelegramBotClient.
$logger->setTelegramBotToken('123456:ABCDEF...');Sets the Telegram chat IDs list. Values are normalized to integers.
$logger->setTelegramChatIds([123456789, 987654321]);Sets the Telegram threshold level (1..8).
$logger->setTelegramThreshold(Logger::CRITICAL);These methods are part of the internal implementation and are listed here for completeness.
Replaces {key} tokens with scalar or stringable values found in $context.
Non-scalar values are ignored.
Stores a structured snapshot into the database by delegating to DatabaseLogWriter, including:
- level;
- event name, app version, and environment;
- correlation ID, trace ID, and fingerprint;
- user id, router path, request method, referer, client IP, and user agent;
- sanitized request payload, context, selected
$_SERVERvalues, and queued application notifications inside context data; - exception class, file, line, and trace when a
Throwableis available;
Cookies are intentionally not stored. The snapshot is performed only when the DB is connected and the error is not a known DB connection/query failure, so logging does not recursively amplify database outages.
private function process(int $level, string $description, array $context = [], ?int $errorCode = null): void
Runs the “severe log” pipeline for WARNING or worse:
- snapshots to DB (when allowed);
- sends email notification (if configured);
- sends Telegram notification (if configured).
Verifies mailer configuration and throws/logs on misconfiguration.
Called before sending email notifications.
Transport-specific builders. Each one instantiates the corresponding transport object with $config.
- You can pass an
errorCodein$context(as['errorCode' => ...]) to help categorise errors or avoid specific pipelines. - DB snapshot is skipped for known DB connection and query failures to prevent recursion during outages.
- New
LogEventpayloads are stored as JSON. The model still reads legacy PHP-serialized payloads. - Sentry integration requires
sentry/sdk. Insight Hub integration requiresbugsnag/bugsnag.
See also: Log, LogBar, LogEvent, ErrorLog, DatabaseLogWriter, LogEventSanitizer, LogEventFingerprint, Observability, Env, Application.