cloud-castle / grafana-loki
Полный клиент Grafana Loki для PHP 8.1+: push с батчингом, gzip, повторами и резервным каналом; Query API с LogQL и живым tail; PSR-3 логгер и мосты Monolog/cloud-castle; маскирование секретов и in-memory Loki для тестов.
Requires (Dev)
- cloud-castle/logger: ^1.0
- deptrac/deptrac: ^3.0 || ^4.0
- ergebnis/composer-normalize: ^2.45
- friendsofphp/php-cs-fixer: ^3.75
- icanhazstring/composer-unused: ^0.9
- infection/infection: ^0.29 || ^0.33
- monolog/monolog: ^3.5
- php-parallel-lint/php-parallel-lint: ^1.4
- phpmd/phpmd: ^2.15
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^1.12 || ^2.1
- phpstan/phpstan-deprecation-rules: ^1.2 || ^2.0
- phpstan/phpstan-phpunit: ^1.4 || ^2.0
- phpstan/phpstan-strict-rules: ^1.6 || ^2.0
- phpunit/phpunit: ^10.5 || ^11.5
- psalm/plugin-phpunit: ^0.19 || ^0.20
- psr/http-client: ^1.0
- psr/http-factory: ^1.1
- psr/http-message: ^1.1 || ^2.0
- rector/rector: ^1.2 || ^2.0
- roave/security-advisories: dev-latest
- squizlabs/php_codesniffer: ^3.12 || ^4.0
- vimeo/psalm: ^6.0
- webmozart/assert: ^1.11
Suggests
- ext-curl: Транспорт cURL с переиспользованием соединения (по умолчанию при наличии)
- ext-zlib: Сжатие gzip push-запросов (включено по умолчанию)
- cloud-castle/clock: Расширенные реализации PSR-20 часов для инъекции (^1.0)
- cloud-castle/http-client: Готовый PSR-18 клиент для Psr18Transport (^1.0)
- cloud-castle/logger: Мост CloudCastleLoggerHandler для логгера экосистемы (^1.0)
- cloud-castle/metrics: Экспорт снимка счётчиков клиента в реестр метрик (^1.0)
- monolog/monolog: Мост MonologHandler для Monolog 3 (^3.0)
- psr/http-client: Использование любого PSR-18 клиента через Psr18Transport (^1.0)
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-03 12:45:07 UTC
README
🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano
CloudCastle Grafana Loki
Полный клиент Grafana Loki для PHP 8.1+: запись и чтение журналов. Push с батчингом, сжатием, повторами и резервным каналом; Query API с LogQL, живой tail, PSR-3 логгер, маскирование секретов и in-memory Loki для тестов.
Packagist
Репозиторий
Качество кода
Зачем он нужен
Каждый существующий Loki-пакет для PHP — это хендлер для одного фреймворка или логгера: он умеет отправлять строки и больше ничего. Читать журналы, строить LogQL-запросы, переживать недоступность сервера, не светить секреты — всё это оставалось за кадром.
Этот пакет закрывает работу с Loki целиком:
- Запись — батчинг с группировкой потоков, gzip, повторы с выдержкой и
уважением
Retry-After, предохранитель, резервный канал (файл/error_log/своё), строго возрастающие наносекундные метки, structured metadata Loki 3.x. - Чтение —
query,query_range, метки, серии, статистика индекса,patterns/detected_labels/detected_fields, живой tail поллингом без WebSocket. - LogQL — типобезопасный строитель запросов с экранированием и валидацией.
- Интеграции — родной PSR-3 логгер, мосты Monolog 3 и cloud-castle/logger, транспорт на cURL/потоках/любом PSR-18 клиенте.
- Безопасность — маскирование паролей, токенов и платёжных карт (Луна), запрет учётных данных в URL, анти-SSRF режим, секреты не сериализуются.
- Тестируемость —
FakeLokiTransport: полный цикл записи и чтения в памяти процесса, симуляция отказов сервера одной строкой.
Установка
composer require cloud-castle/grafana-loki
Требуется PHP 8.1+. Расширения не обязательны: ext-curl и ext-zlib
подключаются автоматически, если собраны; без них работает потоковый
транспорт без сжатия.
Быстрый старт
use CloudCastle\Grafana\Loki\Configuration\LokiConfig;
use CloudCastle\Grafana\Loki\LokiClient;
$client = new LokiClient(new LokiConfig('http://loki:3100', labels: ['app' => 'shop']));
// Запись: буферизуется и уходит батчами автоматически
$client->push('заказ создан', ['module' => 'orders'], ['order_id' => 42]);
$client->flush();
// PSR-3 логгер поверх того же клиента
$logger = $client->logger(['component' => 'billing']);
$logger->error('оплата отклонена: {reason}', ['reason' => 'недостаточно средств']);
$client->flush();
// Чтение: LogQL за последние 15 минут
$result = $client->queries()->queryRange('{app="shop"} |= "заказ"', '-15 minutes', 'now');
foreach ($result->entries() as $entry) {
echo $entry->timestampNs, ' ', $entry->line, PHP_EOL;
}
Строитель LogQL вместо строковой конкатенации:
use CloudCastle\Grafana\Loki\LogQL\{AggregationOp, LogQLBuilder, RangeFunction};
$logql = (new LogQLBuilder())
->withLabel('app', 'shop')
->withLineContains('error')
->withJson()
->withWhere('status', '>=', 500)
->build();
// {app="shop"} |= "error" | json | status>=500
$metric = (new LogQLBuilder())
->withLabel('app', 'shop')
->range(RangeFunction::Rate, '5m')
->aggregate(AggregationOp::Sum, groupBy: ['status'])
->build();
// sum by (status) (rate({app="shop"}[5m]))
Тестирование своего кода без сервера Loki:
use CloudCastle\Grafana\Loki\Testing\FakeLokiTransport;
$fake = new FakeLokiTransport();
$client = new LokiClient(new LokiConfig('http://loki.test'), transport: $fake);
$client->push('оплата проведена', ['app' => 'billing']);
$client->flush();
self::assertCount(1, $fake->entries('{app="billing"} |= "оплата"'));
$fake->failNext(503, times: 2); // симуляция отказов — проверка повторов
Grafana Cloud (мультитенантный Loki с токеном):
use CloudCastle\Grafana\Loki\Auth\BearerAuth;
$config = new LokiConfig(
'https://logs-prod-eu-west-0.grafana.net',
auth: new BearerAuth($token),
tenantId: '123456',
);
Сравнение с аналогами
Все таблицы генерируются автоматически из замеров и анализа исходников
(composer docs:build); руками цифры не пишутся. Подробности методики —
на странице сравнения в wiki.
Функциональность
| Возможность | CloudCastle | itspire | tomas-kulhanek | felipeteko | cebe/yii2 | alexmacarthur |
|---|---|---|---|---|---|---|
| Отправка логов в Loki (push API) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Батчинг записей из коробки | ✅ | — | ✅ | — | ✅ | ✅ |
| Несколько потоков меток в одном батче | ✅ | — | — | — | — | — |
| Сжатие gzip | ✅ | — | — | — | ✅ | — |
| Повторы с экспоненциальной выдержкой и джиттером | ✅ | — | — | — | — | — |
| Уважение заголовка Retry-After | ✅ | — | — | — | — | — |
| Предохранитель (circuit breaker) | ✅ | — | — | — | — | — |
| Резервный приёмник недоставленных записей | ✅ | — | — | — | — | — |
| Строго возрастающие наносекундные метки времени | ✅ | — | — | — | — | — |
| Структурированные метаданные (Loki 3.x) | ✅ | — | — | — | — | — |
| Мультитенантность (X-Scope-OrgID) | ✅ | ✅ | — | — | — | — |
| Basic-аутентификация | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Bearer-токен из коробки | ✅ | — | — | — | — | — |
| mTLS (клиентский сертификат) параметром конфигурации | ✅ | — | — | — | — | — |
| Настройка таймаутов соединения и запроса | ✅ | ✅ | ✅ | — | — | — |
| Работа без ext-curl (потоковый транспорт) | ✅ | — | — | — | ✅ | ✅ |
| Подключение любого PSR-18 клиента | ✅ | — | — | — | — | — |
| Родной PSR-3 логгер без Monolog | ✅ | — | — | — | — | — |
| Интеграция с Monolog | ✅ | ✅ | ✅ | ✅ | — | ✅ |
| Чтение: мгновенные и range-запросы (LogQL) | ✅ | — | — | — | — | — |
| Чтение: имена и значения меток | ✅ | — | — | — | — | — |
| Чтение: series, index stats, volume, patterns | ✅ | — | — | — | — | — |
| Живой tail без WebSocket | ✅ | — | — | — | — | — |
| Типобезопасный строитель LogQL | ✅ | — | — | — | — | — |
| In-memory Loki для тестов пользователя | ✅ | — | — | — | — | — |
| Маскирование секретов и платёжных данных | ✅ | — | — | — | — | — |
| Валидация имён меток до отправки | ✅ | — | — | — | — | — |
| Встроенные счётчики доставки (наблюдаемость) | ✅ | — | — | — | — | — |
| Итого возможностей | 28 | 5 | 5 | 3 | 5 | 5 |
| 🏆 Победитель | 🏆 | |||||
Безопасность
| Механизм защиты | CloudCastle | itspire | tomas-kulhanek | felipeteko | cebe/yii2 | alexmacarthur |
|---|---|---|---|---|---|---|
| Полная проверка TLS-сертификата по умолчанию | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Редиректы транспорта не выполняются | ✅ | ✅ | ✅ | ✅ | — | — |
| Валидация адреса сервера при конфигурации (схемы) | ✅ | — | — | — | — | — |
| Запрет учётных данных в URL | ✅ | — | — | — | — | — |
| Режим блокировки приватных адресов (анти-SSRF) | ✅ | — | — | — | — | — |
| Учётные данные не сериализуются | ✅ | — | — | — | — | — |
| Пароль/токен скрыты из var_dump | ✅ | — | — | — | — | — |
| Пароль не попадает в стектрейсы (SensitiveParameter) | ✅ | — | ✅ | — | — | — |
| Маскирование номеров карт (PAN, проверка Луна) | ✅ | — | — | — | — | — |
| Маскирование секретных ключей контекста | ✅ | — | — | — | — | — |
| Усечение тел ошибок сервера в исключениях | ✅ | — | — | — | — | — |
| Ответы сервера не пишутся в error_log | ✅ | ✅ | ✅ | — | ✅ | ✅ |
| Итого возможностей | 12 | 3 | 4 | 2 | 2 | 2 |
| 🏆 Победитель | 🏆 | |||||
Производительность: одиночные записи
Доставка одиночных записей: 300 операций «запись → HTTP-доставка» на общий приёмник (реалистичная сетевая задержка), медиана 9 прогонов.
| Пакет | Время | 🏆 Победитель |
|---|---|---|
| CloudCastle | 395.8 мс | 🏆 |
| itspire | 397.2 мс | |
| tomas-kulhanek | 402.9 мс | |
| cebe/yii2 | 405.1 мс | |
| felipeteko | 449.7 мс | |
| alexmacarthur | 502 мс | |
Производительность: пакетная доставка
Доставка пакета: 2000 записей по нескольким потокам меток (реальный сервис; реалистичная сетевая задержка), медиана 9 прогонов.
| Пакет | Время | 🏆 Победитель |
|---|---|---|
| CloudCastle | 9.9 мс | 🏆 |
| cebe/yii2 | 33.2 мс | |
| alexmacarthur | 36 мс | |
| tomas-kulhanek | 39 мс | |
| itspire | 2 668.9 мс | |
| felipeteko | 2 947.8 мс | |
Потребление памяти
Память самой библиотеки: пик рабочей фазы (500 доставок) минус baseline, снятый до создания клиента в изолированном процессе — стоимость PHP и автолоадера вычтена.
| Пакет | Память | 🏆 Победитель |
|---|---|---|
| itspire | 412 KB | 🏆 |
| felipeteko | 439 KB | |
| tomas-kulhanek | 483 KB | |
| CloudCastle | 578 KB | |
| cebe/yii2 | 1 749 KB | |
| alexmacarthur | 4 758 KB | |
Утечки памяти
_Рост удержанной памяти за 3 000 доставок после прогрева и gc_collectcycles (изолированный процесс; 0 — утечек нет).
| Пакет | Рост | 🏆 Победитель |
|---|---|---|
| CloudCastle | 0 KB | 🏆 |
| itspire | 0 KB | 🏆 |
| tomas-kulhanek | 0 KB | 🏆 |
| felipeteko | 0 KB | 🏆 |
| cebe/yii2 | 0 KB | 🏆 |
| alexmacarthur | 0 KB | 🏆 |
Качество кода
| Метрика | CloudCastle | itspire | tomas-kulhanek | felipeteko | cebe/yii2 | alexmacarthur | 🏆 Победитель |
|---|---|---|---|---|---|---|---|
| Синтаксические ошибки (php -l) | 0 | 0 | 0 | 0 | 0 | 0 | CloudCastle, itspire, tomas-kulhanek, felipeteko, cebe/yii2, alexmacarthur 🏆 |
| Файлы со strict_types, % | 100 | 100 | 100 | 0 | 0 | 0 | CloudCastle, itspire, tomas-kulhanek 🏆 |
| final-классы, % | 99 | 0 | 0 | 0 | 0 | 0 | CloudCastle 🏆 |
| Runtime-зависимостей | 2 | 1 | 1 | 1 | 2 | 5 | itspire, tomas-kulhanek, felipeteko 🏆 |
| Файлов исходников | 102 | 2 | 4 | 1 | 1 | 5 | felipeteko, cebe/yii2 🏆 |
| Минимальная версия PHP | >=8.1 | ~8.1 | >=8.2 | — | >=7.1.0 | ^8.1 | — |
Возможности проверены по исходникам пакетов (каталог vendor окружения сравнения). Отметка «есть» ставится за возможность из коробки, без правки кода пакета.
felipeteko/monolog-loki пишет ответ сервера в error_log на каждую запись — в замерах это его штатное поведение.
cebe/yii2-loki-log-target и alexmacarthur/laravel-loki-logging привязаны к своим фреймворкам; в изолированном замере им поднято минимальное окружение.
Замер выполнен 2026-09-03 на PHP 8.3.33.
Честно о плюсах и минусах
Плюсы:
- Единственный PHP-клиент с Query API: журналы можно не только писать, но и читать, строить по ним выборки и живой tail — прямо из кода.
- Самая быстрая пакетная доставка среди аналогов и первое место в одиночных отправках (замеры в таблицах выше, обновляются автоматически).
- Контур отказоустойчивости целиком: повторы, предохранитель, резервный канал — журналирование не роняет приложение и не теряет записи молча.
- Маскирование секретов и платёжных данных включено по умолчанию.
- In-memory Loki для тестов — код, пишущий и читающий журналы, тестируется без единого контейнера.
Минусы:
- Пакет моложе и менее распространён, чем itspire/monolog-loki и другие ветераны: меньше установок, меньше отзывов сообщества.
- Потребление памяти выше, чем у минималистичных хендлеров на один класс (545 KB против 400 KB у самого лёгкого): цена суперсета функционала. Если нужен именно один класс без зависимостей — минималисты легче.
- Отправка protobuf+snappy не реализована — используется JSON+gzip (штатный формат Loki; protobuf в планах).
Где и когда применять
- Финтех и всё, где есть ПД — маскирование карт и секретов из коробки,
fail-safe доставка, режим
ErrorMode::Throwдля конвейеров, где потеря журнала должна останавливать обработку. - Долгоживущие воркеры и демоны (queue-консьюмеры, Swoole/RoadRunner) — батчинг, keep-alive соединение, предохранитель и подтверждённое отсутствие утечек памяти.
- Инструменты и панели на данных Loki — Query API с типизированными результатами и строитель LogQL вместо конкатенации строк.
- Laravel/Symfony/любой фреймворк с Monolog — мост
MonologHandlerдобавляет весь контур надёжности к привычному логгеру. - Когда достаточно «просто слать строки» в маленьком скрипте без требований к надёжности — подойдёт и минималистичный хендлер; этот пакет раскрывается там, где журналы — часть продукта, а не побочный эффект.
Документация
- Wiki: быстрый старт, конфигурация, страницы всех возможностей
- Архитектура и жизненный цикл (mermaid-диаграммы)
- Сравнение с аналогами: методика и таблицы
- История изменений: CHANGELOG.md · Переходы между версиями: UPGRADING.md
- Как внести вклад: CONTRIBUTING.md · Политика безопасности: SECURITY.md
Разработка
composer install
composer check # линтеры + статанализ + тесты
composer test:full # полный порядок: phplint → psalm → phpstan → phpmd → phpcs →
# rector → deptrac → мутации → безопасность → память → утечки →
# производительность → качество
composer docs:build # замеры против аналогов + генерация таблиц (PHP 8.2+, benchmarks/vendor)
Лицензия
MIT © CloudCastle (alex-4-17@yandex.ru)
🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano