From 787e7aed431309cdf66c9211cd32bfa4849a4253 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 3 Feb 2026 11:59:49 +0530 Subject: [PATCH 1/4] Add Span tracing to Messaging worker Replace Console logging with Span tracing in the Messaging worker for better observability. Initialize Span storage and exporter in worker.php to enable tracing in all workers. --- app/worker.php | 5 ++ src/Appwrite/Platform/Workers/Messaging.php | 60 +++++++++++---------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/app/worker.php b/app/worker.php index 39f0695bb3..56516b16cf 100644 --- a/app/worker.php +++ b/app/worker.php @@ -48,8 +48,13 @@ use Utopia\Storage\Device\Telemetry as TelemetryDevice; use Utopia\System\System; use Utopia\Telemetry\Adapter as Telemetry; use Utopia\Telemetry\Adapter\None as NoTelemetry; +use Utopia\Span\Exporter; +use Utopia\Span\Span; +use Utopia\Span\Storage; Runtime::enableCoroutine(); +Span::setStorage(new Storage\Coroutine()); +Span::addExporter(new Exporter\Stdout()); Server::setResource('register', fn () => $register); diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 7b604c3b19..51755a3c59 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -5,7 +5,7 @@ namespace Appwrite\Platform\Workers; use Appwrite\Event\StatsUsage; use Appwrite\Messaging\Status as MessageStatus; use Swoole\Runtime; -use Utopia\CLI\Console; +use Utopia\Span\Span; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -100,20 +100,31 @@ class Messaging extends Action $type = $payload['type'] ?? ''; - switch ($type) { - case MESSAGE_SEND_TYPE_INTERNAL: - $message = new Document($payload['message'] ?? []); - $recipients = $payload['recipients'] ?? []; + Span::init('messaging'); + Span::add('project', $project->getId()); + Span::add('type', $type); - $this->sendInternalSMSMessage($message, $project, $recipients, $log); - break; - case MESSAGE_SEND_TYPE_EXTERNAL: - $message = $dbForProject->getDocument('messages', $payload['messageId']); + try { + switch ($type) { + case MESSAGE_SEND_TYPE_INTERNAL: + $message = new Document($payload['message'] ?? []); + $recipients = $payload['recipients'] ?? []; - $this->sendExternalMessage($dbForProject, $message, $deviceForFiles, $project, $queueForStatsUsage); - break; - default: - throw new \Exception('Unknown message type: ' . $type); + $this->sendInternalSMSMessage($message, $project, $recipients, $log); + break; + case MESSAGE_SEND_TYPE_EXTERNAL: + $message = $dbForProject->getDocument('messages', $payload['messageId']); + + $this->sendExternalMessage($dbForProject, $message, $deviceForFiles, $project, $queueForStatsUsage); + break; + default: + throw new \Exception('Unknown message type: ' . $type); + } + } catch (\Throwable $e) { + Span::error($e); + throw $e; + } finally { + Span::current()->finish(); } } @@ -129,10 +140,7 @@ class Messaging extends Action $userIds = $message->getAttribute('users', []); $providerType = $message->getAttribute('providerType'); - Console::log(json_encode([ - 'project' => $project->getId(), - 'type' => $providerType, - ])); + Span::add('providerType', $providerType); /** * @var array $allTargets @@ -183,7 +191,7 @@ class Messaging extends Action 'deliveryErrors' => ['No valid recipients found.'] ])); - Console::warning('No valid recipients found.'); + Span::add('error', 'No valid recipients found.'); return; } @@ -198,7 +206,7 @@ class Messaging extends Action 'deliveryErrors' => ['No enabled provider found.'] ])); - Console::warning('No enabled provider found.'); + Span::add('error', 'No enabled provider found.'); return; } @@ -397,7 +405,7 @@ class Messaging extends Action } if ($this->adapter === null) { - Console::warning('Skipped SMS processing. SMS adapter is not set.'); + Span::add('warning', 'Skipped SMS processing. SMS adapter is not set.'); return; } @@ -405,14 +413,10 @@ class Messaging extends Action throw new \Exception('Project not set in payload'); } - Console::log(json_encode([ - 'project' => $project->getId(), - 'type' => 'internal-sms' - ])); $denyList = System::getEnv('_APP_SMS_PROJECTS_DENY_LIST', ''); $denyList = explode(',', $denyList); if (\in_array($project->getId(), $denyList)) { - Console::error('Project is in the deny list. Skipping...'); + Span::add('error', 'Project is in the deny list. Skipping...'); return; } @@ -699,7 +703,7 @@ class Messaging extends Action private function createInternalSMSAdapter(): ?SMSAdapter { if (empty(System::getEnv('_APP_SMS_PROVIDER')) || empty(System::getEnv('_APP_SMS_FROM'))) { - Console::warning('Skipped SMS processing. Missing "_APP_SMS_PROVIDER" or "_APP_SMS_FROM" environment variables.'); + Span::add('warning', 'Skipped SMS processing. Missing "_APP_SMS_PROVIDER" or "_APP_SMS_FROM" environment variables.'); return null; } @@ -745,13 +749,13 @@ class Messaging extends Action $provider = $this->createProviderFromDSN($localDSN); $adapter = $this->getSmsAdapter($provider); } catch (\Exception) { - Console::warning('Unable to create adapter: ' . $localDSN->getHost()); + Span::add('warning', 'Unable to create adapter: ' . $localDSN->getHost()); continue; } $callingCode = $localDSN->getParam('local', ''); if (empty($callingCode)) { - Console::warning('Unable to register adapter: ' . $localDSN->getHost() . '. Missing `local` parameter.'); + Span::add('warning', 'Unable to register adapter: ' . $localDSN->getHost() . '. Missing `local` parameter.'); continue; } From 52cb8a9c56e673216d782851b64234bc15ff1287 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 3 Feb 2026 12:02:15 +0530 Subject: [PATCH 2/4] Improve span data with detailed metrics Add richer span data for better observability: - External messages: messageId, topic/user/target counts, recipient total, provider details, delivery stats (delivered/errors), final status - Internal SMS: recipient count, country codes extracted from phone numbers, sender number, delivery status and count --- app/worker.php | 7 ++-- src/Appwrite/Platform/Workers/Messaging.php | 45 +++++++++++++++++++-- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/app/worker.php b/app/worker.php index 56516b16cf..d0094222a7 100644 --- a/app/worker.php +++ b/app/worker.php @@ -44,18 +44,19 @@ use Utopia\Queue\Message; use Utopia\Queue\Publisher; use Utopia\Queue\Server; use Utopia\Registry\Registry; +use Utopia\Span\Exporter; +use Utopia\Span\Span; +use Utopia\Span\Storage; use Utopia\Storage\Device\Telemetry as TelemetryDevice; use Utopia\System\System; use Utopia\Telemetry\Adapter as Telemetry; use Utopia\Telemetry\Adapter\None as NoTelemetry; -use Utopia\Span\Exporter; -use Utopia\Span\Span; -use Utopia\Span\Storage; Runtime::enableCoroutine(); Span::setStorage(new Storage\Coroutine()); Span::addExporter(new Exporter\Stdout()); +global $register; Server::setResource('register', fn () => $register); Server::setResource('authorization', function () { diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 51755a3c59..76f945a4bb 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -5,7 +5,6 @@ namespace Appwrite\Platform\Workers; use Appwrite\Event\StatsUsage; use Appwrite\Messaging\Status as MessageStatus; use Swoole\Runtime; -use Utopia\Span\Span; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -39,6 +38,7 @@ use Utopia\Messaging\Messages\SMS; use Utopia\Messaging\Priority; use Utopia\Platform\Action; use Utopia\Queue\Message; +use Utopia\Span\Span; use Utopia\Storage\Device; use Utopia\Storage\Device\Local; use Utopia\Storage\Storage; @@ -140,7 +140,11 @@ class Messaging extends Action $userIds = $message->getAttribute('users', []); $providerType = $message->getAttribute('providerType'); + Span::add('messageId', $message->getId()); Span::add('providerType', $providerType); + Span::add('topicsCount', \count($topicIds)); + Span::add('usersCount', \count($userIds)); + Span::add('targetsCount', \count($targetIds)); /** * @var array $allTargets @@ -185,12 +189,15 @@ class Messaging extends Action \array_push($allTargets, ...$targets); } + Span::add('recipientsTotal', \count($allTargets)); + if (empty($allTargets)) { $dbForProject->updateDocument('messages', $message->getId(), $message->setAttributes([ 'status' => MessageStatus::FAILED, 'deliveryErrors' => ['No valid recipients found.'] ])); + Span::add('status', 'failed'); Span::add('error', 'No valid recipients found.'); return; } @@ -206,10 +213,14 @@ class Messaging extends Action 'deliveryErrors' => ['No enabled provider found.'] ])); + Span::add('status', 'failed'); Span::add('error', 'No enabled provider found.'); return; } + Span::add('provider', $default->getAttribute('provider')); + Span::add('providerName', $default->getAttribute('name')); + /** * @var array> $identifiers */ @@ -351,10 +362,15 @@ class Messaging extends Action if (\count($message->getAttribute('deliveryErrors')) > 0) { $message->setAttribute('status', MessageStatus::FAILED); + Span::add('status', 'failed'); } else { $message->setAttribute('status', MessageStatus::SENT); + Span::add('status', 'sent'); } + Span::add('deliveredTotal', $deliveredTotal); + Span::add('errorsTotal', \count($deliveryErrors)); + $message->removeAttribute('to'); foreach ($providers as $provider) { @@ -400,12 +416,29 @@ class Messaging extends Action private function sendInternalSMSMessage(Document $message, Document $project, array $recipients, Log $log): void { + Span::add('recipientsCount', \count($recipients)); + + // Extract country codes from phone numbers + $countryCodes = []; + foreach ($recipients as $recipient) { + if (\str_starts_with($recipient, '+')) { + // Extract country code (1-3 digits after +) + if (\preg_match('/^\+(\d{1,3})/', $recipient, $matches)) { + $countryCodes[$matches[1]] = ($countryCodes[$matches[1]] ?? 0) + 1; + } + } + } + if (!empty($countryCodes)) { + Span::add('countryCodes', \json_encode($countryCodes)); + } + if ($this->adapter === null) { $this->adapter = $this->createInternalSMSAdapter(); } if ($this->adapter === null) { - Span::add('warning', 'Skipped SMS processing. SMS adapter is not set.'); + Span::add('status', 'skipped'); + Span::add('warning', 'SMS adapter is not set.'); return; } @@ -416,11 +449,14 @@ class Messaging extends Action $denyList = System::getEnv('_APP_SMS_PROJECTS_DENY_LIST', ''); $denyList = explode(',', $denyList); if (\in_array($project->getId(), $denyList)) { - Span::add('error', 'Project is in the deny list. Skipping...'); + Span::add('status', 'denied'); + Span::add('error', 'Project is in the deny list.'); return; } $from = System::getEnv('_APP_SMS_FROM', ''); + Span::add('from', $from); + $sms = new SMS( $recipients, $message->getAttribute('data')['content'], @@ -429,7 +465,10 @@ class Messaging extends Action try { $result = $this->adapter->send($sms); + Span::add('status', 'sent'); + Span::add('deliveredTo', $result['deliveredTo'] ?? 0); } catch (\Throwable $th) { + Span::add('status', 'failed'); throw new \Exception('Failed sending to targets with error: ' . $th->getMessage()); } } From e14a026415c64fe67c2690e8c6ee8f156ec0ddbc Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 3 Feb 2026 12:32:21 +0530 Subject: [PATCH 3/4] Add country code extraction for external SMS messages Extract country codes from target identifiers for external SMS messages to match the behavior of internal SMS messages. --- src/Appwrite/Platform/Workers/Messaging.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 76f945a4bb..c1011f2c38 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -191,6 +191,22 @@ class Messaging extends Action Span::add('recipientsTotal', \count($allTargets)); + // Extract country codes for SMS targets + if ($providerType === MESSAGE_TYPE_SMS && !empty($allTargets)) { + $countryCodes = []; + foreach ($allTargets as $target) { + $identifier = $target->getAttribute('identifier', ''); + if (\str_starts_with($identifier, '+')) { + if (\preg_match('/^\+(\d{1,3})/', $identifier, $matches)) { + $countryCodes[$matches[1]] = ($countryCodes[$matches[1]] ?? 0) + 1; + } + } + } + if (!empty($countryCodes)) { + Span::add('countryCodes', \json_encode($countryCodes)); + } + } + if (empty($allTargets)) { $dbForProject->updateDocument('messages', $message->getId(), $message->setAttributes([ 'status' => MessageStatus::FAILED, From ae720c3462254849f40008db581ef659eafe73d4 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 3 Feb 2026 12:36:46 +0530 Subject: [PATCH 4/4] Fix country code extraction using locale config Use the existing locale-phones config to accurately extract country codes from phone numbers. Sorts codes by length descending to match longer codes first (e.g., 1868 Trinidad before 1 USA). --- src/Appwrite/Platform/Workers/Messaging.php | 45 ++++++++++++++++----- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index c1011f2c38..d1001d5e50 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -196,10 +196,9 @@ class Messaging extends Action $countryCodes = []; foreach ($allTargets as $target) { $identifier = $target->getAttribute('identifier', ''); - if (\str_starts_with($identifier, '+')) { - if (\preg_match('/^\+(\d{1,3})/', $identifier, $matches)) { - $countryCodes[$matches[1]] = ($countryCodes[$matches[1]] ?? 0) + 1; - } + $countryCode = $this->extractCountryCode($identifier); + if ($countryCode !== null) { + $countryCodes[$countryCode] = ($countryCodes[$countryCode] ?? 0) + 1; } } if (!empty($countryCodes)) { @@ -437,11 +436,9 @@ class Messaging extends Action // Extract country codes from phone numbers $countryCodes = []; foreach ($recipients as $recipient) { - if (\str_starts_with($recipient, '+')) { - // Extract country code (1-3 digits after +) - if (\preg_match('/^\+(\d{1,3})/', $recipient, $matches)) { - $countryCodes[$matches[1]] = ($countryCodes[$matches[1]] ?? 0) + 1; - } + $countryCode = $this->extractCountryCode($recipient); + if ($countryCode !== null) { + $countryCodes[$countryCode] = ($countryCodes[$countryCode] ?? 0) + 1; } } if (!empty($countryCodes)) { @@ -881,4 +878,34 @@ class Messaging extends Action return $provider; } + + /** + * Extract country calling code from a phone number using known country codes. + */ + private function extractCountryCode(string $phoneNumber): ?string + { + if (!\str_starts_with($phoneNumber, '+')) { + return null; + } + + $number = \substr($phoneNumber, 1); + + if (empty($number)) { + return null; + } + + $phoneCodes = Config::getParam('locale-phones', []); + $codes = \array_unique(\array_values($phoneCodes)); + + // Sort by length descending to match longest codes first (e.g., 1868 before 1) + \usort($codes, fn ($a, $b) => \strlen($b) - \strlen($a)); + + foreach ($codes as $code) { + if (\str_starts_with($number, $code)) { + return $code; + } + } + + return null; + } }