Записуйте кожен запуск як спан

PHP

Спан, який SDK пише на кожен запуск і кожне очікування, — його поля, куди він іде і як потрапляє у ваші логи чи трасування.

6 хв читання

Спан — запис про один запуск так, як його бачить інструмент трасування: виклик, чим він закінчився, час, ідентифікатори й гроші — але ніколи промпт чи відповідь моделі. SDK пише по одному спану на кожен запуск і кожне очікування й передає його записувачеві, якого ви йому дали. Без записувача спани нікуди не пишуться.

Дати SDK записувач

Записувач нижче пише кожен спан у лог. Логер тут стоїть замість логера вашого застосунку.

PHP
use Psr\Log\AbstractLogger;
use Slen\Sdk\Observability\Psr3SpanRecorder;

// Stands in for the PSR-3 logger of your application: it prints the fields this page talks about.
$logger = new class extends AbstractLogger {
    public function log($level, string|\Stringable $message, array $context = []): void
    {
        echo "{$message}: {$context['operation']} {$context['outcome']} {$context['status']} {$context['operation_code']} {$context['execution_id']} {$context['estimated']}", PHP_EOL;
    }
};

$recorder = new Psr3SpanRecorder($logger);
PHP
use Slen\Sdk\SlenConfig;
use Slen\Sdk\SlenSdk;

$slen = SlenSdk::create(new SlenConfig(
    apiKey: (string) getenv('SLEN_API_KEY'),
    spanRecorder: $recorder,
));
PHP
use Slen\Sdk\Modules\Operations\Options\ExecuteOptions;

$execution = $slen->operations()->execute(
    'generate_email',
    ['messages' => [['role' => 'user', 'content' => 'Write a short follow-up email to Ada.']]],
    new ExecuteOptions(idempotencyKey: 'order-61:email'),
);

Запуск пише один спан, і логер друкує:

slen.span: operations.execute ok completed generate_email 74438200973594710 0.00061590

SDK має такі записувачі:

  • Psr3SpanRecorder пише спан повідомленням slen.span з його полями в контексті через логер PSR-3, на рівні info, якщо ви не назвали інший. Невідомий рівень відхиляється з InvalidArgumentException під час збирання записувача.

  • RecordingSpanRecorder тримає спани в пам’яті, для тесту.

Щоб надсилати спани деінде, реалізуйте SpanRecorderInterface: він має один метод, record(). У застосунку на Laravel записувач вибирає файл конфігурації SDK — Інтеграція з Laravel.

Записувача викликають, поки ваш виклик ще повертається до вас. Те, що він кидає, ковтається: зламаний записувач ніколи не ламає запуск. Тримайте record() швидким і надсилайте спани далі у фоні.

Поля спану

Спан має десять полів, однакових в обох SDK і в цьому порядку. Рядок логу й спан OpenTelemetry несуть їх як атрибути, під іменами другої колонки, і пропускають поле, що дорівнює null.

ПолеАтрибутЩо в ньому
operationoperationІм’я спану — нижче
outcomeoutcomeok, refused або error — нижче
statusstatusСтатус виконання або null, коли виклик кинув помилку
durationMsduration_msЧас виклику в мілісекундах, разом із повторами SDK; для очікування — усе очікування
executionIdexecution_idВиконання
operationCodeoperation_codeКод операції, який назвав запуск; для очікування — той, який називає його відповідь
estimatedestimatedСкільки, за оцінкою, коштували виклики моделей у запуску, десятковим рядком
actualactualСкільки вони коштували, десятковим рядком, коли це відомо
errorCodeerror_codeКод помилки або виконання, яке не завершилося
cacheHitcache_hitДля виконання, яке має відповідь, — чи взято її з кешу. false зберігається як значення
JSON
[
  { "operation": "operations.execute", "outcome": "ok", "status": "completed", "duration_ms": 1843, "execution_id": "74438200973594710", "operation_code": "generate_email", "estimated": "0.00061590", "cache_hit": false },
  { "operation": "operations.execute", "outcome": "refused", "duration_ms": 96, "operation_code": "generate_email", "error_code": "budget_exceeded" },
  { "operation": "executions.wait", "outcome": "error", "status": "failed", "duration_ms": 30512, "execution_id": "74438200973594800", "operation_code": "summarize_report", "error_code": "flow_runtime_error", "cache_hit": false }
]

Жодне поле не може зберігати те, що сказали моделі. Це правило типу: спан не має поля для повідомлення, промпту чи відповіді.

Чим закінчився запуск

  • ok — запуск завершився або ще не закінчився. Очікування, якому забракло часу на незакінченому виконанні, теж ok.

  • refused — Slen сказав «ні»: будь-який 402, політика чи заблокований клієнт, або флоу, який закінчився зі статусом rejected. Це рішення, а не поломка.

  • error — усе інше: мережа, провайдер, неправильний запит, відхилений ключ, ліміт запитів ключа — і виконання, яке закінчилося зі статусом failed або uncertain.

Імена спанів

Ім’яХто пише
operations.executeoperations.execute(): запуск, який чекає на свою відповідь
operations.execute_asyncoperations.executeAsync(): запуск, надісланий у фон. Його перша відповідь каже queued, і нічого ще не списано
executions.waitexecutions.wait(): один спан, коли очікування закінчується, скільки б разів воно не читало статус

Жоден інший виклик спан не пише: ні читання, ні виклик клієнтів, ні вебхук.

Порахувати вартість виконання

Одне виконання може лишити кілька спанів: запуск, кожне очікування на ньому, кожен повтор його ключа ідемпотентності. Кожен із них повторює estimated і actual, які мало виконання, коли спан писався. Якщо їх скласти, вартість вийде завищеною. Беріть вартість один раз на кожен execution_id, з його останнього спану.

Надіслати спани через OTLP

PHP SDK не має експортера OTLP. Реалізуйте SpanRecorderInterface поверх клієнта вашого інструмента трасування або пишіть спани в лог через Psr3SpanRecorder, і нехай їх несе ваш конвеєр логів.

Перед завершенням процесу

Записувачі PHP SDK пишуть кожен спан одразу, тому перед завершенням процесу надсилати нічого.

Що читати далі