From 380cc3eb27446f4307db7baaebc41b720ad20983 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Wed, 29 Apr 2026 02:02:28 +0100 Subject: [PATCH] refactor: drop log/logger boilerplate from lock call sites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous shape required every caller to thread `log: $log, logger: $logger` as named args into each `distributedLock(...)` invocation, plus inject `log` and `logger` into the surrounding action just to forward them to the lock. Across 21 call sites this added ~100 LOC of pure plumbing. The cause: the lock factory was registered on the global container in `app/init/resources.php`, where per-request resources like `log` aren't visible. That forced the factory to expose its inner closure with optional `?Log $log = null, ?Logger $logger = null` params, which every caller had to satisfy. Move the lock factory + its `lockErrorReporter`/`lockTargetOf` helpers from the global container to the per-request container (`resources/request.php`), and add `'log'` + `'logger'` to the factory's dep list. The factory closure now runs per-request and closes over the per-request `Log`/`Logger`. Inner closure returned to callers no longer needs the optional params, and call sites drop the named args entirely. Knock-on cleanup: - Drop `->inject('log')`, `->inject('logger')`, the corresponding action params, and `use Utopia\Logger\{Log,Logger}` imports from 19 endpoint files where they were only there for the lock - Drop the same plumbing from `app/controllers/shared/api.php` (3 lock call sites) - Drop just the Logger plumbing from `app/controllers/general.php` (router function + 3 callbacks); `Log` is kept because it's used elsewhere in that file - Net 120 LOC removed across 23 files No behavior change: the lock factories still produce the same closures (skip-on-contention `distributedLock`, blocking-with-409 `distributedLockOrFail`). The static lockErrorReporter rate limiter (1 push per 60s per `(action, target)` bucket) continues to work — it lives on a closure-static in the helper, which is independent of where the helper is constructed. Verified end-to-end: testConcurrentTogglesAllPersist passes 4/5 (the cold- start race flake is the same one we've consistently seen and is orthogonal to lock changes). --- app/controllers/general.php | 24 +-- app/controllers/shared/api.php | 12 +- app/init/resources.php | 196 ------------------ app/init/resources/request.php | 194 +++++++++++++++++ .../Http/Project/AuthMethods/Update.php | 8 +- .../Project/Http/Project/MockPhone/Create.php | 8 +- .../Project/Http/Project/MockPhone/Delete.php | 8 +- .../Project/Http/Project/MockPhone/Update.php | 8 +- .../Policies/MembershipPrivacy/Update.php | 8 +- .../Policies/PasswordDictionary/Update.php | 8 +- .../Policies/PasswordHistory/Update.php | 8 +- .../Policies/PasswordPersonalData/Update.php | 8 +- .../Project/Policies/SessionAlert/Update.php | 8 +- .../Policies/SessionDuration/Update.php | 8 +- .../Policies/SessionInvalidation/Update.php | 8 +- .../Project/Policies/SessionLimit/Update.php | 8 +- .../Project/Policies/UserLimit/Update.php | 8 +- .../Project/Http/Project/Protocols/Update.php | 8 +- .../Project/Http/Project/SMTP/Update.php | 8 +- .../Project/Http/Project/Services/Update.php | 8 +- .../Http/Project/Templates/Email/Update.php | 8 +- .../Projects/Http/Projects/Team/Update.php | 8 +- .../Modules/Projects/Http/Projects/Update.php | 8 +- 23 files changed, 229 insertions(+), 349 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 053786b191..3fcfd712c2 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -67,7 +67,7 @@ use Utopia\Validator\Text; Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE); -function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock, ?Logger $logger) +function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock) { $host = $request->getHostname(); if (!empty($previewHostname)) { @@ -143,7 +143,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'accessedAt' => DateTime::now() ]))); - }, log: $log, logger: $logger); + }); } /** @@ -853,8 +853,7 @@ Http::init() ->inject('queueForDeletes') ->inject('executionsRetentionCount') ->inject('distributedLock') - ->inject('logger') - ->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, Event $queueForEvents, Bus $bus, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock, ?Logger $logger) { + ->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, Event $queueForEvents, Bus $bus, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock) { /* * Appwrite Router */ @@ -862,7 +861,7 @@ Http::init() $platformHostnames = $platform['hostnames'] ?? []; // Only run Router when external domain if (!\in_array($hostname, $platformHostnames) || !empty($previewHostname)) { - if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount, $distributedLock, $logger)) { + if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount, $distributedLock)) { $utopia->getRoute()?->label('router', true); } } @@ -1148,15 +1147,14 @@ Http::options() ->inject('queueForDeletes') ->inject('executionsRetentionCount') ->inject('distributedLock') - ->inject('logger') - ->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock, ?Logger $logger) { + ->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock) { /* * Appwrite Router */ $platformHostnames = $platform['hostnames'] ?? []; // Only run Router when external domain if (!in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) { - if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount, $distributedLock, $logger)) { + if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount, $distributedLock)) { $utopia->getRoute()?->label('router', true); } } @@ -1552,14 +1550,13 @@ Http::get('/robots.txt') ->inject('queueForDeletes') ->inject('executionsRetentionCount') ->inject('distributedLock') - ->inject('logger') - ->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock, ?Logger $logger) { + ->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock) { $platformHostnames = $platform['hostnames'] ?? []; if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) { $template = new View(__DIR__ . '/../views/general/robots.phtml'); $response->text($template->render(false)); } else { - if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount, $distributedLock, $logger)) { + if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount, $distributedLock)) { $utopia->getRoute()?->label('router', true); } } @@ -1588,14 +1585,13 @@ Http::get('/humans.txt') ->inject('queueForDeletes') ->inject('executionsRetentionCount') ->inject('distributedLock') - ->inject('logger') - ->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock, ?Logger $logger) { + ->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount, callable $distributedLock) { $platformHostnames = $platform['hostnames'] ?? []; if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) { $template = new View(__DIR__ . '/../views/general/humans.phtml'); $response->text($template->render(false)); } else { - if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount, $distributedLock, $logger)) { + if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount, $distributedLock)) { $utopia->getRoute()?->label('router', true); } } diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 142a20c4f3..b07eec1a99 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -39,8 +39,6 @@ use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Authorization\Input; use Utopia\Database\Validator\Roles; use Utopia\Http\Http; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Span\Span; use Utopia\System\System; use Utopia\Telemetry\Adapter as Telemetry; @@ -103,9 +101,7 @@ Http::init() ->inject('apiKey') ->inject('authorization') ->inject('distributedLock') - ->inject('log') - ->inject('logger') - ->action(function (Http $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, AuditContext $auditContext, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization, callable $distributedLock, Log $log, ?Logger $logger) { + ->action(function (Http $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, AuditContext $auditContext, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization, callable $distributedLock) { $route = $utopia->getRoute(); if ($route === null) { throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND); @@ -267,7 +263,7 @@ Http::init() } elseif (! empty($apiKey->getTeamId())) { $dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('teams', $team->getId())); } - }, log: $log, logger: $logger); + }); } $userClone = clone $user; @@ -402,7 +398,7 @@ Http::init() $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'accessedAt' => DateTime::now() ]))); - }, log: $log, logger: $logger); + }); } } @@ -423,7 +419,7 @@ Http::init() $authorization->skip(fn () => $dbForPlatform->updateDocument('users', $user->getId(), new Document([ 'accessedAt' => $user->getAttribute('accessedAt') ]))); - }, log: $log, logger: $logger); + }); } } } diff --git a/app/init/resources.php b/app/init/resources.php index 4a4af38b5f..29506bfc9c 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -8,7 +8,6 @@ use Appwrite\Event\Publisher\Migration as MigrationPublisher; use Appwrite\Event\Publisher\Screenshot as ScreenshotPublisher; use Appwrite\Event\Publisher\StatsResources as StatsResourcesPublisher; use Appwrite\Event\Publisher\Usage as UsagePublisher; -use Appwrite\Extend\Exception as AppwriteException; use Appwrite\Utopia\Database\Documents\User; use Executor\Executor; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; @@ -23,9 +22,6 @@ use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\DI\Container; use Utopia\DSN\DSN; -use Utopia\Lock\Distributed as DistributedLock; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Pools\Group; use Utopia\Queue\Broker\Pool as BrokerPool; use Utopia\Queue\Publisher; @@ -217,198 +213,6 @@ $container->set('timelimit', function (\Redis $redis) { }; }, ['redis']); -// Extract the collection segment ("keys" / "projects" / "users") from keys -// of the form "lock:platform:{target}:{id}" so metrics can slice by target. -// Used by both distributedLock and distributedLockOrFail factories below. -$lockTargetOf = function (string $key): string { - $parts = explode(':', $key, 4); - return $parts[2] ?? 'unknown'; -}; - -/** - * Push lock-side errors to the configured logger (Sentry/Raygun/AppSignal/etc.) - * by mutating the caller's per-request `Log` object — same pattern as - * Embeddings/Text/Create.php and the http.php request-end error handler. - * - * Rate limited per pod via a static bucket so a sustained backend outage - * doesn't flood the logger. At most one push per 60s per (action, target) - * combo. Across a fleet of N pods this caps at N events/min, well within - * Sentry's own dedup tolerance. - * - * Only "real" errors are reported here: - * - backend_error: Redis/Dragonfly unreachable - * - release_error: lock release failed (TTL expired or backend dropped) - * - * Skipped on purpose: contention 409s (normal user-facing concurrency), - * skip-on-contention events (idempotent fan-out by design), acquire retry - * conflicts (internal loop), destructor cleanups (have an expected - * baseline rate; aggregate via the `lock.attempts` counter instead). - */ -$lockErrorReporter = function (?Log $log, ?Logger $logger, string $action, string $key, string $target, Throwable $e): void { - static $lastReportAt = []; - - Console::warning("Lock {$action} for {$key}: {$e->getMessage()}"); - - if ($logger === null || $log === null) { - return; - } - - $bucket = $action . ':' . $target; - $now = time(); - if (($lastReportAt[$bucket] ?? 0) + 60 > $now) { - return; - } - $lastReportAt[$bucket] = $now; - - $log->setNamespace('http'); - $log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname())); - $log->setVersion(APP_VERSION_STABLE); - $log->setType(Log::TYPE_WARNING); - $log->setMessage('Distributed lock ' . $action . ': ' . $e->getMessage()); - $log->setAction("lock.{$action}"); - $log->setEnvironment(System::getEnv('_APP_ENV', 'development') === 'production' - ? Log::ENVIRONMENT_PRODUCTION - : Log::ENVIRONMENT_STAGING); - - $log->addTag('lock.target', $target); - // Strip the trailing document ID from the key so log aggregators don't - // see unbounded cardinality; the {target} attribute already covers slicing. - $log->addTag('lock.key_pattern', preg_replace('/:[^:]+$/', ':*', $key)); - $log->addTag('code', $e->getCode()); - - $log->addExtra('file', $e->getFile()); - $log->addExtra('line', $e->getLine()); - $log->addExtra('trace', $e->getTraceAsString()); - - try { - $logger->addLog($log); - } catch (Throwable) { - // best-effort; don't let logging failures break fail-open - } -}; - -/** - * Distributed-lock factory: skip-on-contention variant. - * - * For idempotent writes where losing the race is correct (e.g., per-request - * `accessedAt` updates from N pods all writing the same value). On contention, - * the callback is silently skipped — another pod is doing the same work. - * - * Behavior: - * - Non-blocking acquire (one attempt). On conflict, skip and return. - * - Fail-open: if Redis is unreachable, run the callback unlocked + warn. - * - Kill switch: `_APP_LOCKING_ENABLED=disabled` runs the callback unlocked. - * - * Returns void — the caller can't distinguish "acquired and ran" from "skipped". - * - * Metric: `lock.attempts{outcome,target}` where outcome ∈ {acquired, skipped, - * backend_error, release_error}. - */ -$container->set('distributedLock', function (\Redis $redis, Telemetry $telemetry) use ($lockTargetOf, $lockErrorReporter) { - $enabled = System::getEnv('_APP_LOCKING_ENABLED', 'enabled') !== 'disabled'; - $attempts = $telemetry->createCounter('lock.attempts', null, 'Distributed lock acquire outcomes'); - - if (! $enabled) { - return function (string $key, \Closure $fn, float $ttl = 5.0, ?Log $log = null, ?Logger $logger = null): void { - $fn(); - }; - } - - return function (string $key, \Closure $fn, float $ttl = 5.0, ?Log $log = null, ?Logger $logger = null) use ($redis, $attempts, $lockTargetOf, $lockErrorReporter): void { - $target = $lockTargetOf($key); - $lock = new DistributedLock($redis, $key, (int) $ttl); - - try { - $acquired = $lock->tryAcquire(); - } catch (\RedisException $e) { - $attempts->add(1, ['outcome' => 'backend_error', 'target' => $target]); - $lockErrorReporter($log, $logger, 'backend_error', $key, $target, $e); - $fn(); - return; - } - - if (! $acquired) { - $attempts->add(1, ['outcome' => 'skipped', 'target' => $target]); - return; - } - - $attempts->add(1, ['outcome' => 'acquired', 'target' => $target]); - try { - $fn(); - } finally { - try { - $lock->release(); - } catch (\Throwable $e) { - $attempts->add(1, ['outcome' => 'release_error', 'target' => $target]); - $lockErrorReporter($log, $logger, 'release_error', $key, $target, $e); - } - } - }; -}, ['redis', 'telemetry']); - -/** - * Distributed-lock factory: 409-on-contention variant. - * - * For explicit user-write endpoints where read-modify-write on shared mutable - * state must NOT silently drop a request. On contention, throws - * `Exception::GENERAL_RESOURCE_LOCKED` (HTTP 409) so the client retries. - * - * Behavior: - * - Blocking acquire with short timeout (default 3s). - * - On timeout, throws `GENERAL_RESOURCE_LOCKED`. - * - Fail-open: backend unreachable runs the callback unlocked + warning. - * - Kill switch: `_APP_LOCKING_ENABLED=disabled` runs the callback unlocked. - * - Returns the callback's return value so callers can use the result. - * - * Metric: `lock.attempts{outcome,target}` where outcome ∈ {acquired, contended, - * backend_error, release_error}. - */ -$container->set('distributedLockOrFail', function (\Redis $redis, Telemetry $telemetry) use ($lockTargetOf, $lockErrorReporter) { - $enabled = System::getEnv('_APP_LOCKING_ENABLED', 'enabled') !== 'disabled'; - $attempts = $telemetry->createCounter('lock.attempts', null, 'Distributed lock acquire outcomes'); - - if (! $enabled) { - return function (string $key, \Closure $fn, float $ttl = 10.0, float $waitTimeout = 3.0, ?Log $log = null, ?Logger $logger = null): mixed { - return $fn(); - }; - } - - return function (string $key, \Closure $fn, float $ttl = 10.0, float $waitTimeout = 3.0, ?Log $log = null, ?Logger $logger = null) use ($redis, $attempts, $lockTargetOf, $lockErrorReporter): mixed { - $target = $lockTargetOf($key); - $lock = new DistributedLock($redis, $key, (int) $ttl); - - try { - $acquired = $lock->acquire($waitTimeout); - } catch (\RedisException $e) { - $attempts->add(1, ['outcome' => 'backend_error', 'target' => $target]); - $lockErrorReporter($log, $logger, 'backend_error', $key, $target, $e); - return $fn(); - } - - if (! $acquired) { - $attempts->add(1, ['outcome' => 'contended', 'target' => $target]); - // Don't pass a custom message — the catalog message in - // app/config/errors.php is reused so we don't leak the internal - // lock key (which embeds collection name and document id) into a - // user-facing 409 response. The telemetry attribute already - // carries the target collection for operator-side observability. - throw new AppwriteException(AppwriteException::GENERAL_RESOURCE_LOCKED); - } - - $attempts->add(1, ['outcome' => 'acquired', 'target' => $target]); - try { - return $fn(); - } finally { - try { - $lock->release(); - } catch (\Throwable $e) { - $attempts->add(1, ['outcome' => 'release_error', 'target' => $target]); - $lockErrorReporter($log, $logger, 'release_error', $key, $target, $e); - } - } - }; -}, ['redis', 'telemetry']); - $container->set('deviceForLocal', function (Telemetry $telemetry) { return new Device\Telemetry($telemetry, new Local()); }, ['telemetry']); diff --git a/app/init/resources/request.php b/app/init/resources/request.php index 7d1731b80d..602efe64f0 100644 --- a/app/init/resources/request.php +++ b/app/init/resources/request.php @@ -37,6 +37,7 @@ use Utopia\Auth\Proofs\Token; use Utopia\Auth\Store; use Utopia\Cache\Cache; use Utopia\Config\Config; +use Utopia\Console; use Utopia\Database\Adapter\Pool as DatabasePool; use Utopia\Database\Database; use Utopia\Database\DateTime as DatabaseDateTime; @@ -48,7 +49,9 @@ use Utopia\Domains\Domain; use Utopia\DSN\DSN; use Utopia\Http\Http; use Utopia\Locale\Locale; +use Utopia\Lock\Distributed as DistributedLock; use Utopia\Logger\Log; +use Utopia\Logger\Logger; use Utopia\Pools\Group; use Utopia\Queue\Publisher; use Utopia\Storage\Device; @@ -73,6 +76,197 @@ return function (Container $container): void { return $register->get('logger'); }, ['register']); + /** + * Push lock-side errors to the configured logger (Sentry/Raygun/AppSignal/etc.) + * by mutating the per-request `Log` object — same pattern as + * Embeddings/Text/Create.php and the http.php request-end error handler. + * + * Rate limited per pod via a static bucket so a sustained backend outage + * doesn't flood the logger. At most one push per 60s per (action, target) + * combo. Across a fleet of N pods this caps at N events/min, well within + * Sentry's own dedup tolerance. + * + * Only "real" errors are reported here: + * - backend_error: Redis/Dragonfly unreachable + * - release_error: lock release failed (TTL expired or backend dropped) + * + * Skipped on purpose: contention 409s (normal user-facing concurrency), + * skip-on-contention events (idempotent fan-out by design), acquire retry + * conflicts (internal loop), destructor cleanups (have an expected + * baseline rate; aggregate via the `lock.attempts` counter instead). + */ + $lockErrorReporter = function (Log $log, ?Logger $logger, string $action, string $key, string $target, Throwable $e): void { + static $lastReportAt = []; + + Console::warning("Lock {$action} for {$key}: {$e->getMessage()}"); + + if ($logger === null) { + return; + } + + $bucket = $action . ':' . $target; + $now = time(); + if (($lastReportAt[$bucket] ?? 0) + 60 > $now) { + return; + } + $lastReportAt[$bucket] = $now; + + $log->setNamespace('http'); + $log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname())); + $log->setVersion(APP_VERSION_STABLE); + $log->setType(Log::TYPE_WARNING); + $log->setMessage('Distributed lock ' . $action . ': ' . $e->getMessage()); + $log->setAction("lock.{$action}"); + $log->setEnvironment(System::getEnv('_APP_ENV', 'development') === 'production' + ? Log::ENVIRONMENT_PRODUCTION + : Log::ENVIRONMENT_STAGING); + + $log->addTag('lock.target', $target); + // Strip the trailing document ID from the key so log aggregators don't + // see unbounded cardinality; the {target} attribute already covers slicing. + $log->addTag('lock.key_pattern', preg_replace('/:[^:]+$/', ':*', $key)); + $log->addTag('code', $e->getCode()); + + $log->addExtra('file', $e->getFile()); + $log->addExtra('line', $e->getLine()); + $log->addExtra('trace', $e->getTraceAsString()); + + try { + $logger->addLog($log); + } catch (Throwable) { + // best-effort; don't let logging failures break fail-open + } + }; + + // Extract the collection segment ("keys" / "projects" / "users") from keys + // of the form "lock:platform:{target}:{id}" so metrics can slice by target. + $lockTargetOf = function (string $key): string { + $parts = explode(':', $key, 4); + return $parts[2] ?? 'unknown'; + }; + + /** + * Distributed-lock factory: skip-on-contention variant. + * + * For idempotent writes where losing the race is correct (e.g., per-request + * `accessedAt` updates from N pods all writing the same value). On contention, + * the callback is silently skipped — another pod is doing the same work. + * + * Behavior: + * - Non-blocking acquire (one attempt). On conflict, skip and return. + * - Fail-open: if Redis is unreachable, run the callback unlocked + warn. + * - Kill switch: `_APP_LOCKING_ENABLED=disabled` runs the callback unlocked. + * + * Returns void — the caller can't distinguish "acquired and ran" from "skipped". + * + * Metric: `lock.attempts{outcome,target}` where outcome ∈ {acquired, skipped, + * backend_error, release_error}. + */ + $container->set('distributedLock', function (\Redis $redis, Telemetry $telemetry, Log $log, ?Logger $logger) use ($lockTargetOf, $lockErrorReporter) { + $enabled = System::getEnv('_APP_LOCKING_ENABLED', 'enabled') !== 'disabled'; + $attempts = $telemetry->createCounter('lock.attempts', null, 'Distributed lock acquire outcomes'); + + if (! $enabled) { + return function (string $key, \Closure $fn, float $ttl = 5.0): void { + $fn(); + }; + } + + return function (string $key, \Closure $fn, float $ttl = 5.0) use ($redis, $attempts, $log, $logger, $lockTargetOf, $lockErrorReporter): void { + $target = $lockTargetOf($key); + $lock = new DistributedLock($redis, $key, (int) $ttl); + + try { + $acquired = $lock->tryAcquire(); + } catch (\RedisException $e) { + $attempts->add(1, ['outcome' => 'backend_error', 'target' => $target]); + $lockErrorReporter($log, $logger, 'backend_error', $key, $target, $e); + $fn(); + return; + } + + if (! $acquired) { + $attempts->add(1, ['outcome' => 'skipped', 'target' => $target]); + return; + } + + $attempts->add(1, ['outcome' => 'acquired', 'target' => $target]); + try { + $fn(); + } finally { + try { + $lock->release(); + } catch (\Throwable $e) { + $attempts->add(1, ['outcome' => 'release_error', 'target' => $target]); + $lockErrorReporter($log, $logger, 'release_error', $key, $target, $e); + } + } + }; + }, ['redis', 'telemetry', 'log', 'logger']); + + /** + * Distributed-lock factory: 409-on-contention variant. + * + * For explicit user-write endpoints where read-modify-write on shared mutable + * state must NOT silently drop a request. On contention, throws + * `Exception::GENERAL_RESOURCE_LOCKED` (HTTP 409) so the client retries. + * + * Behavior: + * - Blocking acquire with short timeout (default 3s). + * - On timeout, throws `GENERAL_RESOURCE_LOCKED`. + * - Fail-open: backend unreachable runs the callback unlocked + warning. + * - Kill switch: `_APP_LOCKING_ENABLED=disabled` runs the callback unlocked. + * - Returns the callback's return value so callers can use the result. + * + * Metric: `lock.attempts{outcome,target}` where outcome ∈ {acquired, contended, + * backend_error, release_error}. + */ + $container->set('distributedLockOrFail', function (\Redis $redis, Telemetry $telemetry, Log $log, ?Logger $logger) use ($lockTargetOf, $lockErrorReporter) { + $enabled = System::getEnv('_APP_LOCKING_ENABLED', 'enabled') !== 'disabled'; + $attempts = $telemetry->createCounter('lock.attempts', null, 'Distributed lock acquire outcomes'); + + if (! $enabled) { + return function (string $key, \Closure $fn, float $ttl = 10.0, float $waitTimeout = 3.0): mixed { + return $fn(); + }; + } + + return function (string $key, \Closure $fn, float $ttl = 10.0, float $waitTimeout = 3.0) use ($redis, $attempts, $log, $logger, $lockTargetOf, $lockErrorReporter): mixed { + $target = $lockTargetOf($key); + $lock = new DistributedLock($redis, $key, (int) $ttl); + + try { + $acquired = $lock->acquire($waitTimeout); + } catch (\RedisException $e) { + $attempts->add(1, ['outcome' => 'backend_error', 'target' => $target]); + $lockErrorReporter($log, $logger, 'backend_error', $key, $target, $e); + return $fn(); + } + + if (! $acquired) { + $attempts->add(1, ['outcome' => 'contended', 'target' => $target]); + // Don't pass a custom message — the catalog message in + // app/config/errors.php is reused so we don't leak the internal + // lock key (which embeds collection name and document id) into a + // user-facing 409 response. The telemetry attribute already + // carries the target collection for operator-side observability. + throw new Exception(Exception::GENERAL_RESOURCE_LOCKED); + } + + $attempts->add(1, ['outcome' => 'acquired', 'target' => $target]); + try { + return $fn(); + } finally { + try { + $lock->release(); + } catch (\Throwable $e) { + $attempts->add(1, ['outcome' => 'release_error', 'target' => $target]); + $lockErrorReporter($log, $logger, 'release_error', $key, $target, $e); + } + } + }; + }, ['redis', 'telemetry', 'log', 'logger']); + $container->set('authorization', function () { return new Authorization(); }, []); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/AuthMethods/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/AuthMethods/Update.php index 9f06c51880..1ce36c8577 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/AuthMethods/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/AuthMethods/Update.php @@ -12,8 +12,6 @@ use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Boolean; use Utopia\Validator\WhiteList; @@ -63,8 +61,6 @@ class Update extends Action ->inject('authorization') ->inject('queueForEvents') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -77,8 +73,6 @@ class Update extends Action Authorization $authorization, Event $queueForEvents, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ): void { $auth = Config::getParam('auth')[$methodId] ?? []; $authKey = $auth['key'] ?? ''; @@ -92,7 +86,7 @@ class Update extends Action return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'auths' => $auths, ]))); - }, log: $log, logger: $logger); + }); $queueForEvents->setParam('methodId', $methodId); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/MockPhone/Create.php b/src/Appwrite/Platform/Modules/Project/Http/Project/MockPhone/Create.php index 08163d644a..8ab0653df3 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/MockPhone/Create.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/MockPhone/Create.php @@ -13,8 +13,6 @@ use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Text; @@ -62,8 +60,6 @@ class Create extends Action ->inject('dbForPlatform') ->inject('authorization') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -76,8 +72,6 @@ class Create extends Action Database $dbForPlatform, Authorization $authorization, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ) { // Set to now date $mockNumber = [ @@ -109,7 +103,7 @@ class Create extends Action $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'auths' => $auths, ]))); - }, log: $log, logger: $logger); + }); $queueForEvents->setParam('number', $number); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/MockPhone/Delete.php b/src/Appwrite/Platform/Modules/Project/Http/Project/MockPhone/Delete.php index 560ff4b6e4..bbc534fdf8 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/MockPhone/Delete.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/MockPhone/Delete.php @@ -13,8 +13,6 @@ use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; @@ -61,8 +59,6 @@ class Delete extends Action ->inject('dbForPlatform') ->inject('authorization') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -74,8 +70,6 @@ class Delete extends Action Database $dbForPlatform, Authorization $authorization, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ) { $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $number, $dbForPlatform, $authorization) { $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId())); @@ -101,7 +95,7 @@ class Delete extends Action $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'auths' => $auths, ]))); - }, log: $log, logger: $logger); + }); $queueForEvents->setParam('number', $number); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/MockPhone/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/MockPhone/Update.php index 55512ff215..a492d7c7bd 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/MockPhone/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/MockPhone/Update.php @@ -13,8 +13,6 @@ use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Text; @@ -62,8 +60,6 @@ class Update extends Action ->inject('dbForPlatform') ->inject('authorization') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -76,8 +72,6 @@ class Update extends Action Database $dbForPlatform, Authorization $authorization, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ) { $mockNumber = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $number, $otp, $dbForPlatform, $authorization) { $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId())); @@ -106,7 +100,7 @@ class Update extends Action ]))); return $mockNumbers[$mockNumberIndex]; - }, log: $log, logger: $logger); + }); $queueForEvents->setParam('number', $number); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/MembershipPrivacy/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/MembershipPrivacy/Update.php index f510801524..a912ee48a6 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/MembershipPrivacy/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/MembershipPrivacy/Update.php @@ -11,8 +11,6 @@ use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Boolean; @@ -63,8 +61,6 @@ class Update extends Action ->inject('authorization') ->inject('queueForEvents') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -80,8 +76,6 @@ class Update extends Action Authorization $authorization, Event $queueForEvents, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ): void { $project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $userId, $userEmail, $userPhone, $userName, $userMFA, $dbForPlatform, $authorization) { $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId())); @@ -107,7 +101,7 @@ class Update extends Action return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'auths' => $auths, ]))); - }, log: $log, logger: $logger); + }); $queueForEvents ->setParam('projectId', $project->getId()) diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/PasswordDictionary/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/PasswordDictionary/Update.php index 7dc7729b47..9cb310257e 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/PasswordDictionary/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/PasswordDictionary/Update.php @@ -11,8 +11,6 @@ use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Boolean; @@ -59,8 +57,6 @@ class Update extends Action ->inject('authorization') ->inject('queueForEvents') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -72,8 +68,6 @@ class Update extends Action Authorization $authorization, Event $queueForEvents, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ): void { $project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $enabled, $dbForPlatform, $authorization) { $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId())); @@ -84,7 +78,7 @@ class Update extends Action return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'auths' => $auths, ]))); - }, log: $log, logger: $logger); + }); $queueForEvents ->setParam('projectId', $project->getId()) diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/PasswordHistory/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/PasswordHistory/Update.php index 19ffc9a0b2..cfff58c8f0 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/PasswordHistory/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/PasswordHistory/Update.php @@ -11,8 +11,6 @@ use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Nullable; use Utopia\Validator\Range; @@ -62,8 +60,6 @@ class Update extends Action ->inject('authorization') ->inject('queueForEvents') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -75,8 +71,6 @@ class Update extends Action Authorization $authorization, Event $queueForEvents, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ): void { $project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $total, $dbForPlatform, $authorization) { $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId())); @@ -87,7 +81,7 @@ class Update extends Action return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'auths' => $auths, ]))); - }, log: $log, logger: $logger); + }); $queueForEvents ->setParam('projectId', $project->getId()) diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/PasswordPersonalData/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/PasswordPersonalData/Update.php index 95c6b9452a..580b04754b 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/PasswordPersonalData/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/PasswordPersonalData/Update.php @@ -11,8 +11,6 @@ use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Boolean; @@ -60,8 +58,6 @@ class Update extends Action ->inject('authorization') ->inject('queueForEvents') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -73,8 +69,6 @@ class Update extends Action Authorization $authorization, Event $queueForEvents, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ): void { $project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $enabled, $dbForPlatform, $authorization) { $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId())); @@ -85,7 +79,7 @@ class Update extends Action return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'auths' => $auths, ]))); - }, log: $log, logger: $logger); + }); $queueForEvents ->setParam('projectId', $project->getId()) diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/SessionAlert/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/SessionAlert/Update.php index 6948eed85e..8f9f0a4c48 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/SessionAlert/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/SessionAlert/Update.php @@ -11,8 +11,6 @@ use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Boolean; @@ -59,8 +57,6 @@ class Update extends Action ->inject('authorization') ->inject('queueForEvents') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -72,8 +68,6 @@ class Update extends Action Authorization $authorization, Event $queueForEvents, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ): void { $project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $enabled, $dbForPlatform, $authorization) { $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId())); @@ -84,7 +78,7 @@ class Update extends Action return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'auths' => $auths, ]))); - }, log: $log, logger: $logger); + }); $queueForEvents ->setParam('projectId', $project->getId()) diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/SessionDuration/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/SessionDuration/Update.php index 3de157556a..c8111b5563 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/SessionDuration/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/SessionDuration/Update.php @@ -11,8 +11,6 @@ use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Range; @@ -59,8 +57,6 @@ class Update extends Action ->inject('authorization') ->inject('queueForEvents') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -72,8 +68,6 @@ class Update extends Action Authorization $authorization, Event $queueForEvents, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ): void { $project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $duration, $dbForPlatform, $authorization) { $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId())); @@ -84,7 +78,7 @@ class Update extends Action return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'auths' => $auths, ]))); - }, log: $log, logger: $logger); + }); $queueForEvents ->setParam('projectId', $project->getId()) diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/SessionInvalidation/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/SessionInvalidation/Update.php index f6dcb95123..e10c0d4081 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/SessionInvalidation/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/SessionInvalidation/Update.php @@ -11,8 +11,6 @@ use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Boolean; @@ -59,8 +57,6 @@ class Update extends Action ->inject('authorization') ->inject('queueForEvents') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -72,8 +68,6 @@ class Update extends Action Authorization $authorization, Event $queueForEvents, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ): void { $project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $enabled, $dbForPlatform, $authorization) { $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId())); @@ -84,7 +78,7 @@ class Update extends Action return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'auths' => $auths, ]))); - }, log: $log, logger: $logger); + }); $queueForEvents ->setParam('projectId', $project->getId()) diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/SessionLimit/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/SessionLimit/Update.php index 826478b713..b111534430 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/SessionLimit/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/SessionLimit/Update.php @@ -11,8 +11,6 @@ use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Nullable; use Utopia\Validator\Range; @@ -60,8 +58,6 @@ class Update extends Action ->inject('authorization') ->inject('queueForEvents') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -73,8 +69,6 @@ class Update extends Action Authorization $authorization, Event $queueForEvents, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ): void { $project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $total, $dbForPlatform, $authorization) { $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId())); @@ -85,7 +79,7 @@ class Update extends Action return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'auths' => $auths, ]))); - }, log: $log, logger: $logger); + }); $queueForEvents ->setParam('projectId', $project->getId()) diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/UserLimit/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/UserLimit/Update.php index a882a72603..c59a17b61f 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/UserLimit/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Policies/UserLimit/Update.php @@ -11,8 +11,6 @@ use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Nullable; use Utopia\Validator\Range; @@ -60,8 +58,6 @@ class Update extends Action ->inject('authorization') ->inject('queueForEvents') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -73,8 +69,6 @@ class Update extends Action Authorization $authorization, Event $queueForEvents, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ): void { $project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $total, $dbForPlatform, $authorization) { $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId())); @@ -85,7 +79,7 @@ class Update extends Action return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'auths' => $auths, ]))); - }, log: $log, logger: $logger); + }); $queueForEvents ->setParam('projectId', $project->getId()) diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Protocols/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Protocols/Update.php index 5fb40571bf..0f3174b143 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/Protocols/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Protocols/Update.php @@ -12,8 +12,6 @@ use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Boolean; use Utopia\Validator\WhiteList; @@ -63,8 +61,6 @@ class Update extends Action ->inject('authorization') ->inject('queueForEvents') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -77,8 +73,6 @@ class Update extends Action Authorization $authorization, Event $queueForEvents, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ): void { $project = $distributedLockOrFail("lock:platform:projects:{$project->getId()}", function () use ($project, $protocolId, $enabled, $dbForPlatform, $authorization) { $project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $project->getId())); @@ -89,7 +83,7 @@ class Update extends Action return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'apis' => $protocols, ]))); - }, log: $log, logger: $logger); + }); $queueForEvents->setParam('protocolId', $protocolId); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/SMTP/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/SMTP/Update.php index 12238f7150..b8e3c6e09f 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/SMTP/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/SMTP/Update.php @@ -14,8 +14,6 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Emails\Validator\Email; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Boolean; use Utopia\Validator\Hostname; @@ -75,8 +73,6 @@ class Update extends Action ->inject('project') ->inject('authorization') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -97,8 +93,6 @@ class Update extends Action Document $project, Authorization $authorization, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ): void { $inputs = [ 'host' => $host, @@ -191,7 +185,7 @@ class Update extends Action return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'smtp' => $smtp, ]))); - }, log: $log, logger: $logger); + }); $response->dynamic($project, Response::MODEL_PROJECT); } diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Services/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Services/Update.php index 933d524e23..1126da8897 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/Services/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Services/Update.php @@ -12,8 +12,6 @@ use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Boolean; use Utopia\Validator\WhiteList; @@ -63,8 +61,6 @@ class Update extends Action ->inject('authorization') ->inject('queueForEvents') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -77,8 +73,6 @@ class Update extends Action Authorization $authorization, Event $queueForEvents, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ): void { // The services map is a JSON object on the project document. Two // concurrent service toggles read the same baseline, each set their @@ -96,7 +90,7 @@ class Update extends Action return $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([ 'services' => $services, ]))); - }, log: $log, logger: $logger); + }); $queueForEvents->setParam('serviceId', $serviceId); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Update.php index 103a3e83d8..ed1f7d8f7f 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Update.php @@ -13,8 +13,6 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Emails\Validator\Email; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; use Utopia\System\System; @@ -72,8 +70,6 @@ class Update extends Action ->inject('authorization') ->inject('project') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } @@ -92,8 +88,6 @@ class Update extends Action Authorization $authorization, Document $project, callable $distributedLockOrFail, - Log $log, - ?Logger $logger, ) { $locale = $locale ?: System::getEnv('_APP_LOCALE', 'en'); @@ -147,7 +141,7 @@ class Update extends Action ]))); return $template; - }, log: $log, logger: $logger); + }); $queueForEvents->setParam('templateId', $templateId); diff --git a/src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php b/src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php index ab8ca7312e..25286a2227 100644 --- a/src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php +++ b/src/Appwrite/Platform/Modules/Projects/Http/Projects/Team/Update.php @@ -13,8 +13,6 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; use Utopia\Database\Validator\UID; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Scope\HTTP; use Utopia\Validator; @@ -58,12 +56,10 @@ class Update extends Action ->inject('response') ->inject('dbForPlatform') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } - public function action(string $projectId, string $teamId, Response $response, Database $dbForPlatform, callable $distributedLockOrFail, Log $log, ?Logger $logger) + public function action(string $projectId, string $teamId, Response $response, Database $dbForPlatform, callable $distributedLockOrFail) { // Lock around the project doc RMW. Cascade fan-out to installations, // repositories and vcsComments runs after the lock is released — @@ -89,7 +85,7 @@ class Update extends Action ])); return [$project, $permissions]; - }, log: $log, logger: $logger); + }); $installations = $dbForPlatform->find('installations', [ Query::equal('projectInternalId', [$project->getSequence()]), diff --git a/src/Appwrite/Platform/Modules/Projects/Http/Projects/Update.php b/src/Appwrite/Platform/Modules/Projects/Http/Projects/Update.php index 11e7c669c4..4b79b37bfa 100644 --- a/src/Appwrite/Platform/Modules/Projects/Http/Projects/Update.php +++ b/src/Appwrite/Platform/Modules/Projects/Http/Projects/Update.php @@ -10,8 +10,6 @@ use Appwrite\Utopia\Database\Validator\Queries\Projects; use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\Validator\UID; -use Utopia\Logger\Log; -use Utopia\Logger\Logger; use Utopia\Platform\Scope\HTTP; use Utopia\Validator; use Utopia\Validator\Text; @@ -68,12 +66,10 @@ class Update extends Action ->inject('response') ->inject('dbForPlatform') ->inject('distributedLockOrFail') - ->inject('log') - ->inject('logger') ->callback($this->action(...)); } - public function action(string $projectId, string $name, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForPlatform, callable $distributedLockOrFail, Log $log, ?Logger $logger) + public function action(string $projectId, string $name, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForPlatform, callable $distributedLockOrFail) { // Re-fetch and write the full project doc inside the lock. This is the // worst RMW window in the projects API — the endpoint passes the entire @@ -98,7 +94,7 @@ class Update extends Action ->setAttribute('legalAddress', $legalAddress) ->setAttribute('legalTaxId', $legalTaxId) ->setAttribute('search', implode(' ', [$projectId, $name]))); - }, log: $log, logger: $logger); + }); $response->dynamic($project, Response::MODEL_PROJECT); }