Настройка эффективного логирования PHP приложений
Подходы к логированию PHP сервера
Какое решение обеспечивает гибкое и масштабируемое логирование?
Наиболее эффективным способом считается использование библиотеки Monolog, реализующей стандарт PSR-3. Она позволяет отправлять записи в различные каналы (файлы, syslog, электронная почта, базы данных, внешние сервисы) и настраивать уровни важности, форматирование и ротацию.
// Установка через Composer
composer require monolog/monolog
// Пример базовой настройки
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
$log = new Logger('app');
$log->pushHandler(new StreamHandler('/var/log/php/error.log', Logger::WARNING));
$log->pushHandler(new RotatingFileHandler('/var/log/php/app.log', 7, Logger::DEBUG));
$log->info('Приложение запущено', ['user_id' => 42]);
$log->error('Ошибка базы данных', ['exception' => $e]);
?>
Php server logging (логирование php сервера)
Результат в файле app.log:
[2025-04-05T10:15:30.123+03:00] app.INFO: Приложение запущено {"user_id":42} []
[2025-04-05T10:15:31.456+03:00] app.ERROR: Ошибка базы данных {"exception":"..."} []
Типичные проблемы: нехватка памяти при большом количестве логов; забывают настроить ротацию, что приводит к заполнению диска. Решение: использовать RotatingFileHandler с ограничением количества файлов или BufferHandler для групповой записи.
Как настроить базовое логирование через error_log и php.ini?
Простой способ без внешних библиотек. В php.ini задаются параметры error_log, log_errors, error_reporting. Функция error_log() отправляет сообщения в указанный файл или syslog.
// php.ini
log_errors = On
error_log = /var/log/php/php_errors.log
error_reporting = E_ALL & ~E_DEPRECATED
// В коде PHP
error_log('Пользовательский запрос: ' . json_encode($_POST), 3, '/var/log/php/custom.log');
Результат в custom.log:
[05-Apr-2025 10:20:00 Europe/Moscow] Пользовательский запрос: {"name":"test"}
Проблемы: нет ротации по дате; сложно парсить неструктурированный текст; сообщения разных уровней смешиваются. Решение: дополнить скриптом ротации через logrotate или перейти на Monolog.
Как отправлять логи PHP в системный syslog?
Использование error_log() с типом 0 или SyslogHandler Monolog. Позволяет централизованно собирать логи всех служб.
// Через error_log
error_log('PHP ошибка', 0); // отправляет в syslog
// Через Monolog
use Monolog\Handler\SyslogHandler;
$log->pushHandler(new SyslogHandler('myapp', LOG_USER, Logger::DEBUG));
$log->warning('Предупреждение из PHP');
Проблема: настройки syslog (конфиг /etc/rsyslog.conf) могут игнорировать сообщения; требуется корректный facility. Решение: проверить logger в командной строке и права на запись в /dev/log.
Как хранить логи в базе данных (MySQL, PostgreSQL)?
Подходит для небольших проектов, где нужен быстрый поиск по логам через SQL. Monolog предоставляет PDOHandler.
use Monolog\Handler\PDOHandler;
$pdo = new PDO('mysql:host=localhost;dbname=logs', 'user', 'pass');
$log->pushHandler(new PDOHandler($pdo, 'log_table', Logger::INFO));
$log->notice('Запись в БД', ['data' => 'example']);
Структура таблицы:
CREATE TABLE log_table (
id INT AUTO_INCREMENT PRIMARY KEY,
channel VARCHAR(50),
level INT,
message TEXT,
context TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Проблема: высокая нагрузка на базу при частых записях; сложная ротация. Решение: использовать очереди (RabbitMQ, Redis) и пакетную вставку.
Как организовать централизованный сбор логов через ELK?
Стек Elasticsearch, Logstash, Kibana (Filebeat для сбора файлов). На сервере Filebeat читает файлы логов PHP и отправляет в Logstash или прямо в Elasticsearch.
# filebeat.yml (часть)
filebeat.inputs:
- type: log
paths:
- /var/log/php/*.log
fields:
service: php
output.elasticsearch:
hosts: ["localhost:9200"]
Logstash конфиг для парсинга:
filter {
grok {
match => { "message" => "\[%{TIMESTAMP_ISO8601:timestamp}\] %{WORD:channel}\.%{WORD:level}: %{GREEDYDATA:log_message}" }
}
date {
match => ["timestamp", "ISO8601"]
}
}
Проблема: сложность настройки; большой расход ресурсов; задержка доставки. Решение: использовать Filebeat с harvester_limit и мультилайн.
Как подключить внешний сервис мониторинга ошибок (Sentry, Bugsnag)?
Для критических ошибок удобно получать уведомления в реальном времени. Monolog имеет RavenHandler (Sentry) или аналогичные.
use Monolog\Handler\RavenHandler;
$client = new Raven_Client('https://sentry.io/...');
$handler = new RavenHandler($client, Logger::ERROR);
$log->pushHandler($handler);
$log->emergency('Критическая ошибка', ['trace' => debug_backtrace()]);
Проблема: зависимость от внешнего сервиса; трата трафика; конфиденциальность данных. Решение: фильтровать контекст и использовать локальный кэш при недоступности.
Цели и случаи использования каждого варианта
Базовое error_log подходит для быстрого прототипирования или legacy-проектов. Syslog используется в серверных окружениях с централизованным логированием (systemd-journald). База данных удобна для небольших приложений с потребностью в поиске. ELK рекомендован для микросервисной архитектуры и больших объёмов данных. Внешние сервисы (Sentry) подходят для отслеживания ошибок в production с уведомлениями. Monolog как основа позволяет комбинировать любые каналы одним интерфейсом.
Расширенные примеры логирования PHP
Настройка Monolog с ротацией и форматированием JSON
Пример конфигурации с несколькими обработчиками: запись всех логов в JSON-файл с ротацией по дням, а предупреждения и выше – отдельно в syslog.
<?php
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\SyslogHandler;
use Monolog\Formatter\JsonFormatter;
use Monolog\Processor\IntrospectionProcessor;
use Monolog\Processor\WebProcessor;
$logger = new Logger('api');
// Обработчик для JSON с ротацией (7 дней)
$jsonHandler = new RotatingFileHandler('/var/log/php/api.json', 7, Logger::DEBUG);
$jsonHandler->setFormatter(new JsonFormatter());
$logger->pushHandler($jsonHandler);
// Syslog для ошибок
$syslogHandler = new SyslogHandler('api', LOG_USER, Logger::WARNING);
$logger->pushHandler($syslogHandler);
// Процессоры добавляют контекст
$logger->pushProcessor(new IntrospectionProcessor(Logger::DEBUG, ['Monolog\\']));
$logger->pushProcessor(new WebProcessor());
$logger->info('Запрос обработан', ['method' => $_SERVER['REQUEST_METHOD']]);
$logger->error('Исключение PDO', ['exception' => $e->getMessage()]);
?>
Содержимое api.json (каждая строка – JSON):
{"message":"Запрос обработан","context":{"method":"GET"},"level":200,"level_name":"INFO","channel":"api","datetime":"2025-04-05T10:30:00.123+03:00","extra":{"class":"App\\Controller\\UserController","function":"index","file":"/var/www/app/Controller/UserController.php","line":45,"url":"/users","ip":"192.168.1.1","http_method":"GET","server":"127.0.0.1"}}
{"message":"Исключение PDO","context":{"exception":"SQLSTATE[23000]: Integrity constraint violation"},"level":400,"level_name":"ERROR","channel":"api","datetime":"2025-04-05T10:30:01.456+03:00","extra":{...}}
Проблема: файлы журнала могут стать слишком большими при DEBUG уровне. Решение: настроить WhatFailureGroupHandler для асинхронной записи или использовать BufferHandler с флешем по порогу.
Логирование через SAPI (для встроенного веб-сервера PHP)
Встроенный сервер (php -S) поддерживает только stderr. Чтобы перенаправить логи в файл, используется обёртка.
# Запуск с перенаправлением stderr в файл
php -S 0.0.0.0:8080 -t public 2>> /var/log/php/dev.log
# Альтернатива – использование Monolog с StreamHandler('php://stderr')
$log->pushHandler(new StreamHandler('php://stderr', Logger::DEBUG));
Условное логирование на основе окружения
Часто требуется разный уровень логирования в dev и prod. Используется переменная окружения или константа.
<?php
$level = (getenv('APP_ENV') === 'production') ? Logger::WARNING : Logger::DEBUG;
$handler = new StreamHandler('/var/log/php/app.log', $level);
$log->pushHandler($handler);
// В production можно добавить только критичные ошибки в Sentry
if (getenv('APP_ENV') === 'production') {
$sentryHandler = new RavenHandler(new Raven_Client('DSN'), Logger::ERROR);
$log->pushHandler($sentryHandler);
}
?>
Логирование в формате Graylog (GELF)
Для интеграции с Graylog используется протокол GELF. Monolog имеет GelfHandler, отправляющий UDP-пакеты.
use Monolog\Handler\GelfHandler;
use Gelf\Publisher;
use Gelf\Transport\UdpTransport;
$transport = new UdpTransport('graylog.example.com', 12201);
$publisher = new Publisher($transport);
$handler = new GelfHandler($publisher, Logger::WARNING);
$log->pushHandler($handler);
$log->warning('Высокая нагрузка CPU', ['cpu' => 0.95]);
Бенчмарк времени выполнения с логированием
Monolog поддерживает процессинг времени выполнения через TimerProcessor или ручной замер.
$start = microtime(true);
// ... выполнение кода ...
$log->info('Запрос завершен', ['duration_ms' => (microtime(true) - $start) * 1000]);
Обработка ошибок через пользовательский exception handler
Чтобы все неперехваченные исключения автоматически попадали в лог, устанавливают собственный обработчик.
<?php
function exceptionHandler($e) {
global $log;
$log->critical('Необработанное исключение', [
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
]);
http_response_code(500);
echo 'Внутренняя ошибка сервера';
}
set_exception_handler('exceptionHandler');
// Также можно обработать фатальные ошибки через register_shutdown_function
?>