Skip to content
Viames Marino edited this page Mar 26, 2026 · 3 revisions

Pair framework: Logger class

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 (ErrorLog);
  • send email and Telegram notifications for severe events;
  • register custom error / exception / shutdown handlers;
  • optionally forward errors to Sentry and Insight Hub (Bugsnag).

The logger is designed to be safe in production and helpful in development, while keeping the integration small and predictable.


Getting the logger instance

use Pair\Core\Logger;

$logger = Logger::getInstance();

Logger::getInstance() always returns the same shared instance.


Log levels

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.


What happens when you log

Always: LogBar event

Every call ends up in LogBar:

  • debug, info, noticeLogBar::event(..., 'notice')
  • warningLogBar::event(..., 'warning')
  • error, critical, alert, emergencyLogBar::event(..., 'error')

Only for WARNING or worse: processing (DB + notifications)

If the level is WARNING or worse (<= Logger::WARNING), the logger also:

  1. optionally snapshots the request into the DB (ErrorLog);
  2. optionally sends email notifications (if configured);
  3. optionally sends Telegram notifications (if configured).

Configuration via .env

Logger reads these values from Env during construction:

  • PAIR_LOGGER_DISABLED
    If set to true, 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 is 4 (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 is 4 (error).

Additionally:

  • APP_DEBUG controls handler behavior (see errorHandler()).
  • SENTRY_DSN enables 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/1

Configuring email transport

Email is sent only if:

  • a mail transport has been configured ($logger->useTransport(...));
  • emailRecipients are set;
  • level <= emailThreshold.

Example: SMTP transport

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);

Example: Amazon SES transport

$logger->useTransport('ses', [
    // Passed as-is to AmazonSes transport
    'region' => 'eu-west-1',
    'accessKeyId' => '...',
    'secretAccessKey' => '...',
    'from' => ['no-reply@example.com' => 'Pair'],
]);

Example: Sendmail transport

$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).


Configuring Telegram notifications

Telegram is sent only if:

  • telegramBotToken is set;
  • telegramChatIds contains 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);

Registering global handlers

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'])

Quick usage examples

Basic messages

$logger = Logger::getInstance();

$logger->info('User logged in');
$logger->notice('Profile updated');
$logger->warning('Slow query detected');
$logger->error('Payment gateway failure');

Context placeholders (PSR-3 style)

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.

Manual severity (string or numeric)

$logger->log('error', 'Something went wrong');
$logger->log(Logger::ERROR, 'Something went wrong');

Method reference

public static function getInstance(): self

Returns the singleton instance.

$logger = Logger::getInstance();

public static function registerHandlers(): void

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 error level).
  • If SENTRY_DSN is set, forwards to Sentry.
  • Forwards to Insight Hub handler.
  • If APP_DEBUG is true, returns false so PHP can also display the error.
  • Otherwise returns true to suppress output in production.

Returning false allows the default PHP handler to run, which is useful during development.


public static function exceptionHandler(Throwable $e): void

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.

public static function shutdownHandler(): void

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.

public function disable(): void

Disables the logger (no LogBar, no DB snapshot, no notifications).

Logger::getInstance()->disable();

public function log(mixed $level, Stringable|string $message, array $context = []): void

The main PSR-3 log entrypoint.

  • Accepts:
    • numeric level 1..8, or
    • string level name ("error", "warning", etc).
  • Renders {placeholders} using the provided context.
  • Logs to LogBar.
  • For WARNING or worse, triggers processing (DB + notifications).

Convenience PSR-3 methods

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]);

public function useTransport(string $type, array $config): void

Configures the email transport used for error notifications.

Supported types:

  • smtpSmtpMailer
  • sesAmazonSes
  • sendmailSendmail
$logger->useTransport('smtp', [/* transport config */]);

If the type is unknown, an InvalidArgumentException is thrown.


public function setEmailRecipients(array $recipients): void

Sets the email recipients list. Values are normalized and filtered to valid emails.

$logger->setEmailRecipients(['ops@example.com', 'dev@example.com']);

public function setEmailThreshold(int $level): void

Sets the email threshold level (1..8).

  • If the level is invalid, the logger emits an internal configuration error.
$logger->setEmailThreshold(Logger::ERROR);

public function setTelegramBotToken(string $token): void

Sets the Telegram bot token used by TelegramBotClient.

$logger->setTelegramBotToken('123456:ABCDEF...');

public function setTelegramChatIds(array $chatIds): void

Sets the Telegram chat IDs list. Values are normalized to integers.

$logger->setTelegramChatIds([123456789, 987654321]);

public function setTelegramThreshold(int $level): void

Sets the Telegram threshold level (1..8).

$logger->setTelegramThreshold(Logger::CRITICAL);

Internal methods (implementation details)

These methods are part of the internal implementation and are listed here for completeness.

private function interpolate(string $message, array $context): string

Replaces {key} tokens with scalar or stringable values found in $context.
Non-scalar values are ignored.


private function snapshot(string $description, ?int $level = null): void

Stores a snapshot into the database (via ErrorLog), including:

  • level, user id, router path
  • $_GET, $_POST, $_FILES (cookies intentionally not stored)
  • the first 255 characters of the description
  • referer (with BASE_HREF removed when possible)
  • queued application notifications (user messages)

The snapshot is performed only when the DB is connected and the error is not a DB connection error.


private function process(int $level, string $description, array $context = [], ?int $errorCode = null): void

Runs the “severe log” pipeline for WARNING or worse:

  1. snapshots to DB (when allowed);
  2. sends email notification (if configured);
  3. sends Telegram notification (if configured).

private function ensureMailer(): void

Verifies mailer configuration and throws/logs on misconfiguration.
Called before sending email notifications.


private function setSmtpMailerConfig(array $config): void

private function setSesConfig(array $config): void

private function setSendmailConfig(array $config): void

Transport-specific builders. Each one instantiates the corresponding transport object with $config.


Notes

  • You can pass an errorCode in $context (as ['errorCode' => ...]) to help categorise errors or avoid specific pipelines.
  • DB snapshot is skipped for known DB connection failures to prevent recursion during outages.
  • Sentry integration requires sentry/sdk. Insight Hub integration requires bugsnag/bugsnag.

See also: Log, LogBar, ErrorLog, Env, Application.

Clone this wiki locally