Настройка эффективного логирования 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
?>

Логирование PHP сервера - comments

En
Php server logging (php)