Телеметрія

Телеметрія Concept Core — збір метрик і відміток життєвого циклу (bootstrap, HTTP, шаблони, SQL) для debug і профілювання. Це не шина доменних подій: ядро не надає EventDispatcherInterface для бізнес-подій на кшталт UserRegistered — таку логіку додають у власному service provider (PSR-14, Symfony тощо).

Див. також: Service providers → TelemetryTrait, життєвий цикл запиту, конфігурація.

Увімкнення

Ключ telemetry.enabled (ConfigKey::TELEMETRY_ENABLED):

// config/telemetry.php
return [
    'telemetry' => [
        'enabled' => false,
        'db_queries' => false,
        'logs' => false,
    ],
];

// config/dev/telemetry.php — override для APP_ENV=dev
return [
    'telemetry' => [
        'enabled' => true,
        'db_queries' => true,
        'logs' => false,
    ],
];

За замовчуванням у skeleton — false; у dev-override — true. Поки прапорець вимкнено, TelemetryTrait::telemetry() повертає null і ядро не записує події (окрім місць, де колектор передають явно — див. нижче).

Компоненти телеметрії

КласРоль
TelemetryServiceProvider Реєструє shared TelemetryCollector у контейнері
TelemetryCollector Зберігання подій: start(), finish(), record(), mark(), items()
TelemetryTrait Доступ до колектора з урахуванням telemetry.enabled (у провайдерах, RouteStrategy)
TelemetryEvent Константи імен подій (рядкові ідентифікатори)
TelemetryItem Один запис: контекст, started_at, finished_at, duration
TelemetryLogHandler Monolog handler: дзеркалить записи логів у LOG_RECORDED за telemetry.logs

TelemetryServiceProvider стоїть рано в bootstrap/providers/app.php (після ConfigServiceProvider і LocaleServiceProvider), щоб інші провайдери могли писати телеметрію під час register().

Вбудовані записи

Ідентифікатори в Concept\Core\Services\Telemetry\TelemetryEvent:

КонстантаЗначенняКоли записується
FRAMEWORK_SERVICE_AWAKENING framework.service_awakening Перше створення сервісу в провайдері (mark з ім’ям класу)
FRAMEWORK_COMPONENT_REGISTERED framework.component_registered ComponentsServiceProvider::boot() — кожен активний компонент
FRAMEWORK_ROUTES_REGISTERED framework.routes_registered HttpServiceProvider після завантаження route-файлів з routes.list
HTTP_ROUTE_INTERCEPTOR_EXECUTED http.route_interceptor_executed Кожен route interceptor у RouteStrategy::runInterceptors()
HTTP_ROUTE_CALLABLE_INVOKED http.route_callable_invoked Виклик handler у RouteStrategy (start/finish)
HTTP_REQUEST_HANDLED http.request_handled Кінець App::run() — method, path, memory, загальна тривалість
TPL_RENDERED tpl.rendered Рендер Twig у TwigView::render() (ім’я шаблону, тривалість)
DB_QUERY_EXECUTED db.query_executed SQL listener у DatabaseServiceProvider (якщо telemetry.db_queries)
LOG_RECORDED log.recorded Запис Monolog через TelemetryLogHandler (якщо telemetry.logs)
HTTP_FORM_REQUEST_VALIDATED http.form_request_validated Зарезервовано в API; наразі ядро не емітує цей запис

API колектора

mark() — миттєва відмітка

Для точкових відміток без вимірювання тривалості (пробудження сервісу):

$this->telemetry()?->mark(
    TelemetryEvent::FRAMEWORK_SERVICE_AWAKENING,
    SessionInterface::class
);

start() / finish() — span

$id = $this->telemetry()?->start(TelemetryEvent::HTTP_ROUTE_CALLABLE_INVOKED, [
    'route' => $route,
    'handler' => 'UserController::index',
]);
try {
    // ...
} finally {
    $this->telemetry()?->finish(TelemetryEvent::HTTP_ROUTE_CALLABLE_INVOKED, (string) $id);
}

record() — миттєвий span

Поєднує start() і finish() в одному виклику (SQL, routes, підсумок HTTP):

$this->telemetry()?->record(
    TelemetryEvent::DB_QUERY_EXECUTED,
    [TelemetryKey::SQL => $query->sql],
    $query->time / 1000
);

Читання даних

/** @var TelemetryCollector $telemetry */
$telemetry = $container->get(TelemetryCollector::class);

$services = $telemetry->items(TelemetryEvent::FRAMEWORK_SERVICE_AWAKENING);
$all = $telemetry->toArray();

Колектор — singleton на запит. Після завершення HTTP-запиту дані не зберігаються між запитами (якщо застосунок не експортує їх сам, наприклад у DebugBar).

TelemetryTrait у провайдерах

Провайдери ядра (HttpServiceProvider, SessionServiceProvider, DatabaseServiceProvider тощо) використовують TelemetryTrait:

private function telemetry(): ?TelemetryCollector
{
    // 1. Є TelemetryCollector у контейнері?
    // 2. telemetry.enabled === true?
    // 3. Інакше — null (запис пропускається)
}

У власному bootable-провайдері можна підключити той самий trait, якщо клас має getContainer() (як у AbstractServiceProvider).

Приклад: DebugBar

Компонент DebugBar читає TelemetryCollector і показує віджети «Services», handler маршруту, останній відрендерений шаблон:

// ServicesDataCollector
$items = $this->telemetryCollector->items(TelemetryEvent::FRAMEWORK_SERVICE_AWAKENING);
foreach ($items as $item) {
    $className = $item->getContext()['name'] ?? 'Unknown';
    // ...
}

Увімкніть telemetry.enabled і компонент DebugBarComponent у config/dev/components.php.

Власні записи

У сервісі або middleware застосунку:

public function __construct(
    private readonly TelemetryCollector $telemetry,
    private readonly ConfigInterface $config,
) {}

public function heavyOperation(): void
{
    if (!$this->config->getBool('telemetry.enabled')) {
        return;
    }

    $id = $this->telemetry->start('app.heavy_operation', ['step' => 'import']);
    try {
        // ...
    } finally {
        $this->telemetry->finish('app.heavy_operation', $id);
    }
}

Префікс імені (app.) — домовленість застосунку; ядро не валідує довільні рядки. Для констант у своєму коді створіть клас на кшталт AppTelemetryEvent.

Чого немає в ядрі

  • Доменні події — реєстрація listener/subscriber для бізнес-подій; підключайте PSR-14 або Symfony EventDispatcher у власному провайдері
  • EventServiceProvider — такого класу в concept-core немає (див. життєвий цикл)
  • Логування SQL vs телеметріяlog.db_queries пише у query.log; telemetry.db_queries наповнює TelemetryCollector. Прапорці незалежні.

Телеметрія призначена для debug і профілювання, не для бізнес-логіки. Не покладайтеся на неї в production-коді як на механізм інтеграції між модулями.