Files
appwrite/app/init/realtime/span.php
Prem Palanisamy 2c185d1a9e refactor(realtime): split error reporting — span for per-action, logger for ad-hoc
Spans model per-action operations (open/message/close); they're a poor fit for ad-hoc
events with no operation lifecycle. Split logError to dispatch by whether a span is
active:

- Active span (realtime.open / realtime.message / realtime.close catches) -> attach
  the error to the existing span; the Sentry span exporter ships it with full operation
  context (attributes, duration, trace_id).
- No active span (pub/sub subscriber, onStart, Swoole error handler, updateWorkerDocument)
  -> push a utopia/logger Log via the realtimeLogger registry. Goes to Sentry as an event
  via the Sentry logger adapter (or to logOwl / Raygun / AppSignal). Same dedicated
  Realtime project either way.

Restores the realtimeLogger registry (dropped in the previous "spans only" pass), inlines
the now-single-caller $createLogger closure into the logger registry, and drops the
recordRealtimeErrorSpan helper — logError is the only function on this path now.

Also registers a Pretty span exporter in app/init/realtime/span.php for non-self-hosted
editions so Realtime spans are visible in the container's stdout (on self-hosted the
existing app/init/span.php already provides it; gating avoids duplicate output).
2026-05-13 09:32:21 +01:00

51 lines
2.0 KiB
PHP

<?php
use Utopia\Console;
use Utopia\DSN\DSN;
use Utopia\Span\Exporter;
use Utopia\Span\Span;
use Utopia\System\System;
/**
* Export Realtime error spans to a dedicated Sentry project (`_APP_LOGGING_CONFIG_REALTIME`, falling
* back to `_APP_LOGGING_CONFIG`) — only here, not in app/init/span.php, so the HTTP/worker/CLI
* servers keep reporting to the default project. Only error-bearing spans are sampled.
*/
$loggingConfig = System::getEnv('_APP_LOGGING_CONFIG_REALTIME', '') ?: System::getEnv('_APP_LOGGING_CONFIG', '');
if (empty($loggingConfig)) {
return;
}
try {
$dsn = new DSN($loggingConfig);
if ($dsn->getScheme() === 'sentry') {
// utopia/logger DSN (`sentry://PROJECT_ID:KEY@HOST/`) -> standard Sentry DSN (`https://KEY@HOST/PROJECT_ID`).
$sentryDsn = 'https://' . ($dsn->getPassword() ?? '') . '@' . $dsn->getHost() . '/' . ($dsn->getUser() ?? '');
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
Span::addExporter(
new Exporter\Sentry(
dsn: $sentryDsn,
environment: $isProduction ? 'production' : 'staging',
release: System::getEnv('_APP_VERSION', 'UNKNOWN'),
serverName: System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()),
),
sampler: static fn (Span $span): bool => $span->getError() !== null,
);
}
} catch (Throwable $error) {
Console::warning('Failed to register Realtime Sentry span exporter: ' . $error->getMessage());
}
// Print spans to stdout for local visibility on editions where app/init/span.php — which already
// installs a Pretty exporter — isn't loaded (it's gated on `_APP_EDITION === 'self-hosted'`).
if (System::getEnv('_APP_EDITION', 'self-hosted') !== 'self-hosted') {
Span::addExporter(new Exporter\Pretty(), function (Span $span): bool {
if (\str_starts_with($span->getAction(), 'listener.')) {
return $span->getError() !== null;
}
return true;
});
}