Инструменты логирования в PHP

Раздел: Программирование на PHP -> Логирование PHP

Основное эффективное решение: библиотека Monolog

Логирование в PHP часто выполняется с помощью библиотеки Monolog, соответствующей стандарту PSR-3. Она позволяет направлять сообщения в различные каналы: файлы, базу данных, системный журнал, Slack и другие.

Как реализовать гибкое логирование с ротацией файлов с помощью Monolog?

Установка через Composer:

composer require monolog/monolog

Journals php journal (журналы php)

Пример настройки логгера с ротацией по дням:

use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Formatter\LineFormatter;

$log = new Logger('app');
$handler = new RotatingFileHandler('/var/log/app/app.log', 7, Logger::DEBUG);
$handler->setFormatter(new LineFormatter(
    "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"
));
$log->pushHandler($handler);

$log->info('Приложение запущено');
$log->error('Ошибка соединения', ['db' => 'mysql']);

Типичная проблема: отсутствие прав на запись в директорию логов. Решение: создать директорию и установить права 755 или использовать путь внутри проекта.

Ошибка: сообщение не записывается из-за неправильного уровня логирования. Например, если уровень DEBUG не указан, сообщения ниже INFO игнорируются.

Варианты решения

Как просто записывать логи в файл с помощью встроенной функции error_log?

Функция error_log направляет сообщения в журнал ошибок веб-сервера или в указанный файл.

error_log('Текст ошибки', 3, '/tmp/my_errors.log');

Параметр 3 означает запись в файл. Если не указать файл, сообщение отправится в журнал сервера.

Проблема: отсутствие контекста и форматирования. Решение: использовать sprintf или json_encode для структурирования.

error_log(json_encode(['time' => date('c'), 'msg' => 'Ошибка', 'level' => 'ERROR']) . "\n", 3, 'log.json');

Как создать простой PSR-3 логгер без внешних зависимостей?

Стандарт PSR-3 описывает интерфейс LoggerInterface. Можно реализовать минимальный класс.

class SimpleLogger extends \Psr\Log\AbstractLogger {
    public function log($level, $message, array $context = []): void {
        $entry = sprintf("[%s] %s: %s %s\n",
            date('c'),
            strtoupper($level),
            $message,
            json_encode($context)
        );
        file_put_contents('app.log', $entry, FILE_APPEND | LOCK_EX);
    }
}
$logger = new SimpleLogger();
$logger->warning('Недостаточно памяти', ['memory' => memory_get_usage()]);

Ошибка: одновременная запись из нескольких процессов может привести к конфликтам. Использование LOCK_EX частично решает, но для продакшена лучше использовать Monolog с обработчиками, поддерживающими блокировки.

Как вести журнал в базу данных?

Можно использовать PDO для записи логов в таблицу.

$pdo = new PDO('mysql:host=localhost;dbname=logs', 'user', 'pass');
$stmt = $pdo->prepare(
    'INSERT INTO log (level, message, context, created_at) VALUES (?, ?, ?, NOW())'
);
$stmt->execute(['ERROR', 'Сбой модуля', '{"module":"auth"}']);

Проблема: медленная запись при большом количестве логов. Решение: использовать буферизацию или асинхронную очередь.

Как использовать системный журнал через syslog?

Функция openlog/syslog/closelog отправляет сообщения в системный syslog.

openlog('myapp', LOG_PID, LOG_USER);
syslog(LOG_WARNING, 'Высокая нагрузка');
closelog();

Особенность: настройки системного журнала влияют на дальнейшую обработку. Не все серверы поддерживают syslog.

Расширенные примеры логирования

Пример 1: Многоканальное логирование с Monolog (файл + email для критических ошибок)

Пример
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\NativeMailerHandler;

$log = new Logger('app');
$log->pushHandler(new StreamHandler('info.log', Logger::INFO));
$log->pushHandler(
    (new NativeMailerHandler('admin@example.com', 'Критическая ошибка', 'error@example.com'))
        ->setLevel(Logger::CRITICAL)
);

$log->info('Обычная операция');
$log->critical('База данных недоступна', ['host' => 'db1']);
Результат: в info.log появится запись INFO, а на почту придет письмо с сообщением CRITICAL.

Пример 2: Использование процессоров для добавления контекста (IP, сессия)

Пример
use Monolog\Logger;
use Monolog\Processor\IntrospectionProcessor;
use Monolog\Processor\WebProcessor;

$log = new Logger('web');
$log->pushProcessor(new IntrospectionProcessor());
$log->pushProcessor(new WebProcessor());
$log->pushHandler(new StreamHandler('access.log', Logger::INFO));

$log->info('Страница загружена');
Результат: в лог добавляются файл, строка, класс, метод, а также IP, URI, метод запроса и т.д.

Пример 3: Логирование в Graylog через GELF

Пример
use Monolog\Logger;
use Monolog\Handler\GelfHandler;
use Gelf\Transport\UdpTransport;

$transport = new UdpTransport('graylog.local', 12201);
$handler = new GelfHandler($transport);
$log = new Logger('app');
$log->pushHandler($handler);
$log->error('Соединение потеряно');
Результат: сообщение появляется в Graylog с уровнем ERROR и дополнительными полями.

Пример 4: Собственный обработчик для записи в Redis

Пример
use Monolog\Logger;
use Monolog\Handler\AbstractProcessingHandler;
use Redis;

class RedisHandler extends AbstractProcessingHandler {
    private Redis $redis;
    private string $key;
    
    public function __construct(Redis $redis, string $key = 'logs') {
        parent::__construct();
        $this->redis = $redis;
        $this->key = $key;
    }
    
    protected function write(\Monolog\LogRecord $record): void {
        $this->redis->rPush($this->key, (string)$record->formatted);
    }
}

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$log = new Logger('cache');
$log->pushHandler(new RedisHandler($redis));
$log->info('Кеш очищен');
Результат: запись в список Redis 'logs' в виде строки.

Пример 5: Использование библиотеки Analog для простых проектов

Пример
Analog::handler (Analog\Handler\File::init ('app.log'));
Analog::log ('Сообщение', Analog::WARNING);
Результат: в файл app.log добавляется строка с временем и уровнем.

Пример 6: Логирование с условным форматом для разных обработчиков

Пример
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;

$log = new Logger('json_app');
$handler = new StreamHandler('app.json');
$handler->setFormatter(new JsonFormatter());
$log->pushHandler($handler);
$log->info('Пользователь вошёл', ['user_id' => 42]);
Результат: в файл app.json записывается JSON-строка: {"message":"Пользователь вошёл","context":{"user_id":42},"level":200,"channel":"json_app","datetime":"...","extra":[]}

Журналы PHP - comments

En
Journals php journal (php)