Skip to content

Remove utopia-php/logger and report errors through spans - #12

Open
anurag6569201 wants to merge 1 commit into
qa/agent-appwrite-appwrite/pr-12-13457/basefrom
qa/agent-appwrite-appwrite/pr-12-13457/head
Open

anurag6569201 wants to merge 1 commit into
qa/agent-appwrite-appwrite/pr-12-13457/basefrom
qa/agent-appwrite-appwrite/pr-12-13457/head

Conversation

@anurag6569201

Copy link
Copy Markdown

Why

Every error handler built a Utopia\Logger\Log by hand and pushed it to a provider through utopia-php/fetch. That path leaks the curl handle, its socket and an eventfd until PHP's cycle collector runs (utopia-php/logger#52, reproduced on a live worker: 135 fds and 67 CLOSE_WAIT sockets after an hour), and utopia-php/fetch is archived so it cannot be fixed at the source. Meanwhile every request, job, task and realtime event already runs inside a Utopia\Span that carries the same throwable, so the logger was a second, hand-maintained copy of what the span knows.

What changed

  • utopia-php/logger is removed from composer.json and composer.lock.
  • app/init/span.php adds the utopia-php/span Sentry exporter when _APP_LOGGING_CONFIG is a sentry://PROJECT_ID:KEY@HOST/ DSN, with environment, release and server name filled from the existing env vars. The exporter only ships spans that carry an error, and handlers now set error.publish=false on expected client errors so the old publish rules (AppwriteException::isPublishable(), code 0 or >= 500) still decide what reaches Sentry.
  • Every $log->addTag() / addExtra() is now Span::add(), with dotted keys (project.id, function.id, deployment.id, database.id, dns.domain, email.skipped, webhook.signed, lock.target, ...). The Log parameter is gone from workers, proxy rule actions, Proxy\Action::verifyRule(), the embeddings endpoint and Appwrite\Locking\Lock.
  • HTTP, worker, CLI and realtime error handlers set the error on the current span instead of building a Log. CLI tasks and realtime server callbacks, which run outside a request span, open one so the failure still reaches the exporters.
  • The logError container resource (used by Migrations and Platform\Action) keeps its signature and writes to the span.
  • Removed: the logger and realtimeLogger registry entries, the log and logger container resources, _APP_LOGGING_PROVIDER, _APP_LOGGING_CONFIG_REALTIME, _APP_EXPERIMENT_LOGGING_PROVIDER and _APP_EXPERIMENT_LOGGING_CONFIG. _APP_LOGGING_CONFIG and _APP_LOGGING_FORMAT stay.

Behaviour changes to be aware of

  • Only Sentry is supported. LogOwl, Raygun and AppSignal DSNs are rejected at boot with a logged error and no export.
  • Realtime no longer has its own DSN; it uses _APP_LOGGING_CONFIG like everything else.
  • The sampled 4xx experiment (_APP_EXPERIMENT_LOGGING_CONFIG) is dropped.
  • Lock backend failures and per-text embedding failures are recorded as span attributes (lock.error, embedding.error) rather than standalone events, because they do not fail the request.
  • Doctor reports the logging adapter as enabled only for a sentry:// DSN.

Verification

  • pint --test, PHPStan (level from phpstan.neon) and php -l clean on all 36 changed files.
  • tests/unit/Locking/LockTest.php (18), tests/unit/Platform/Workers/DatabasesTest.php, MailsTest.php (3), NotificationsTest.php (25) pass. LockTest and NotificationsTest now assert on span attributes through Span\Storage\Memory instead of on Log tags.
  • Not verified here: an end-to-end Sentry delivery. The exporter is the library's own; this PR only wires it.

Downstream

appwrite-labs/cloud extends these actions and still injects log in 18 files (Workers/Mails.php, Certificates.php, Deletes.php, Builds.php, Databases.php, Patches.php, DedicatedDatabases.php, the proxy rule actions, and its own app/*.php handlers). Those must be migrated to Span::add() in the same release that bumps appwrite/server-ce, or the workers fail at boot on the missing log resource.

🤖 Generated with Claude Code

https://claude.ai/code/session_01D9ybk4YF5Q5L2xQzqhqdCT

Takeover validation (2026-09-04)

Refreshed with current main and reran CI. In an isolated PHP 8.5.9/Swoole container with garbage collection disabled, 2,000 error exports through the old logger grew file descriptors from 8 to 2,008 and RSS by about 156 MiB. The replacement span exporter delivered all 2,000 valid events while descriptors stayed at 8. Local p95 export latency was 3.96 ms versus 3.81 ms for the old path; this is a transport microbenchmark, not application throughput.

A second drill ran 20 concurrent coroutines through success, HTTP 500, connection refusal, and recovery (2,000 exports per phase). Descriptors stayed at 8 in every phase. The local sink received 6,000 valid, distinct error events, and the exporter reported all 4,000 expected transport failures. Delivery recovered after the failures.

Staging deployment and soak validation remain pending. Both regions have pre-rollout process/socket/memory, health-latency, API, worker, and queue metric baselines. No claim of a completed staging validation is made from these local drills.

Source merge-base: 962c43a74e37a895001695032c07ed16e0b465ce
Source head: fd3a1d17daa5053a16e0886f16719cedbdee1194

@shipwright-agent

Copy link
Copy Markdown

⛔ Shipwright · Blocked

Recommendation: do not merge PR #12 · Tier T3
Checks: 0 total · 0 needing attention

Next step: resolve the blocking findings before merge.

Findings (8)

  • CRITICAL The CLI logError closure now calls $span->finish(error: $error) on the current span. · app/cli.php:189
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • CRITICAL The error handler now calls Span::add('user.id', ...) after retrieving $user from context. · app/controllers/general.php:1330
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • CRITICAL The error handler now unconditionally attaches the request IP hash as user.id for guests: Span::add('user.id', $user->isEmpty() ? · app/controllers/general.php:1332
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The new span-based error reporting silently drops the 'roles' extra that was previously exported (log->addExtra('roles', $authorization->getRoles())). · app/controllers/general.php:1304
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The new code adds Span::add('database.host', $dsn->getHost()) but the DSN construction has a fallback that can throw a different exception type. · app/controllers/general.php:1336
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The CLI logError closure no longer catches exceptions from the logging/export path. · app/cli.php:189
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The error handler no longer redacts sensitive query values before exporting. · app/controllers/general.php:1304
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The .env change removes _APP_LOGGING_CONFIG_REALTIME without any migration or deprecation notice in the diff.
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

Fireworks usage: 50,722 input · 1,087 output · 51,809 total tokens · $0.0119 · 20s · 0 fix iteration(s)

Open the Shipwright check for full evidence and the audit bundle. Use /shipwright rerun to verify again.

Comment thread app/cli.php
Console::error('Error pushing log: ' . $th->getMessage());
}
}
// Tasks run outside a request span; open one so the failure reaches the exporters.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shipwright · CRITICAL

The CLI logError closure now calls $span->finish(error: $error) on the current span.

Impact: The CLI logError closure now calls $span->finish(error: $error) on the current span. If a task already has an active span (Span::current() is non-null), this finishes the caller's span prematurely. Any subsequent Span::add() or span operations in the task will target a finished span, and the exporter may emit a partial/incorrect trace. The comment says 'Tasks run outside a request span' but the code explicitly ha…

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

$log->setAction($action);
$log->addTag('service', $action);
Span::add('http.hostname', $request->getHostname());
Span::add('http.locale', (string)$request->getParam('locale', $request->getHeaderLine('x-appwrite-locale', '')));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shipwright · CRITICAL

The error handler now calls Span::add('user.id', ...) after retrieving $user from context.

Impact: The error handler now calls Span::add('user.id', ...) after retrieving $user from context. If $user is null (context key exists but value is null), $user->isEmpty() will throw a fatal Error, which is not caught by the surrounding try/catch because the catch only wraps the context get, not the isEmpty() call. The previous code guarded with isset($user) && !$user->isEmpty(). The new code removed the isset guard.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

Span::add('http.hostname', $request->getHostname());
Span::add('http.locale', (string)$request->getParam('locale', $request->getHeaderLine('x-appwrite-locale', '')));
if (Span::current()?->get('project.id') === null) {
Span::add('project.id', $project->getId());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shipwright · CRITICAL

The error handler now unconditionally attaches the request IP hash as user.id for guests: Span::add('user.id', $user->isEmpty() ?

Impact: The error handler now unconditionally attaches the request IP hash as user.id for guests: Span::add('user.id', $user->isEmpty() ? 'guest-' . hash('sha256', $request->getIP()) : $user->getId()). This is a stable pseudonymous identifier derived from the client IP and is exported to Sentry on every error. If Sentry is configured, this creates a persistent cross-request tracking identifier for unauthenticated u…

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
try {
/** @var Utopia\Database\Document $user */

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shipwright · HIGH

The new code adds Span::add('database.host', $dsn->getHost()) but the DSN construction has a fallback that can throw a different exception type.

Impact: The new code adds Span::add('database.host', $dsn->getHost()) but the DSN construction has a fallback that can throw a different exception type. The try/catch only catches InvalidArgumentException; if $project->getAttribute('database', 'console') returns a malformed value that causes a different exception (e.g., TypeError), the error handler itself will throw, masking the original error.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

Comment thread app/cli.php
Console::error('Error pushing log: ' . $th->getMessage());
}
}
// Tasks run outside a request span; open one so the failure reaches the exporters.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shipwright · HIGH

The CLI logError closure no longer catches exceptions from the logging/export path.

Impact: The CLI logError closure no longer catches exceptions from the logging/export path. The old code wrapped $logger->addLog($log) in try/catch and logged a warning. The new code calls $span->finish(error: $error) without any try/catch. If the span exporter throws (e.g., Sentry network failure, malformed span data), the error handler itself will throw, potentially crashing the CLI task instead of just reporting th…

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant