diff --git a/composer.json b/composer.json index 9ba2d5b..a7a9726 100755 --- a/composer.json +++ b/composer.json @@ -20,9 +20,15 @@ "autoload": { "psr-4": {"Utopia\\DNS\\": "src/DNS"} }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/utopia-php/span" + } + ], "require": { "php": ">=8.3", - "utopia-php/console": "0.0.*", + "utopia-php/span": "dev-main", "utopia-php/telemetry": "*", "utopia-php/validators": "0.*", "utopia-php/domains": "0.9.*" diff --git a/composer.lock b/composer.lock index 3313d1f..d249beb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "249d124be7cf5af6655965b805e5430d", + "content-hash": "5fb648213436c6830237905f57ff7f19", "packages": [ { "name": "brick/math", @@ -1306,16 +1306,16 @@ }, { "name": "symfony/http-client", - "version": "v7.4.1", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "26cc224ea7103dda90e9694d9e139a389092d007" + "reference": "d01dfac1e0dc99f18da48b18101c23ce57929616" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/26cc224ea7103dda90e9694d9e139a389092d007", - "reference": "26cc224ea7103dda90e9694d9e139a389092d007", + "url": "https://api.github.com/repos/symfony/http-client/zipball/d01dfac1e0dc99f18da48b18101c23ce57929616", + "reference": "d01dfac1e0dc99f18da48b18101c23ce57929616", "shasum": "" }, "require": { @@ -1383,7 +1383,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.1" + "source": "https://github.com/symfony/http-client/tree/v7.4.3" }, "funding": [ { @@ -1403,7 +1403,7 @@ "type": "tidelift" } ], - "time": "2025-12-04T21:12:57+00:00" + "time": "2025-12-23T14:50:43+00:00" }, { "name": "symfony/http-client-contracts", @@ -1868,74 +1868,79 @@ "time": "2025-06-29T15:42:06+00:00" }, { - "name": "utopia-php/console", - "version": "0.0.1", + "name": "utopia-php/cache", + "version": "0.13.2", "source": { "type": "git", - "url": "https://github.com/utopia-php/console.git", - "reference": "f77104e4a888fa9cb3e08f32955ec09479ab7a92" + "url": "https://github.com/utopia-php/cache.git", + "reference": "5768498c9f451482f0bf3eede4d6452ddcd4a0f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/console/zipball/f77104e4a888fa9cb3e08f32955ec09479ab7a92", - "reference": "f77104e4a888fa9cb3e08f32955ec09479ab7a92", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/5768498c9f451482f0bf3eede4d6452ddcd4a0f6", + "reference": "5768498c9f451482f0bf3eede4d6452ddcd4a0f6", "shasum": "" }, "require": { - "php": ">=7.4" + "ext-json": "*", + "ext-memcached": "*", + "ext-redis": "*", + "php": ">=8.0", + "utopia-php/pools": "0.8.*", + "utopia-php/telemetry": "*" }, "require-dev": { "laravel/pint": "1.2.*", - "phpstan/phpstan": "^1.10", + "phpstan/phpstan": "^1.12", "phpunit/phpunit": "^9.3", - "squizlabs/php_codesniffer": "^3.6", - "swoole/ide-helper": "4.8.8" + "vimeo/psalm": "4.13.1" }, "type": "library", "autoload": { "psr-4": { - "Utopia\\": "src/" + "Utopia\\Cache\\": "src/Cache" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Console helpers for logging, prompting, and executing commands", + "description": "A simple cache library to manage application cache storing, loading and purging", "keywords": [ - "cli", - "console", + "cache", + "framework", "php", - "terminal", + "upf", "utopia" ], "support": { - "issues": "https://github.com/utopia-php/console/issues", - "source": "https://github.com/utopia-php/console/tree/0.0.1" + "issues": "https://github.com/utopia-php/cache/issues", + "source": "https://github.com/utopia-php/cache/tree/0.13.2" }, - "time": "2025-10-20T14:41:36+00:00" + "time": "2025-12-17T08:55:43+00:00" }, { "name": "utopia-php/domains", - "version": "0.9.0", + "version": "0.9.2", "source": { "type": "git", "url": "https://github.com/utopia-php/domains.git", - "reference": "2c8a2a2aa43405b591505445c9b360c68b0073eb" + "reference": "52b654f8a0e170bfa2e54cb47755b256822477c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/domains/zipball/2c8a2a2aa43405b591505445c9b360c68b0073eb", - "reference": "2c8a2a2aa43405b591505445c9b360c68b0073eb", + "url": "https://api.github.com/repos/utopia-php/domains/zipball/52b654f8a0e170bfa2e54cb47755b256822477c7", + "reference": "52b654f8a0e170bfa2e54cb47755b256822477c7", "shasum": "" }, "require": { - "php": ">=8.0", - "utopia-php/validators": "0.0.*" + "php": ">=8.2", + "utopia-php/cache": "0.13.*", + "utopia-php/validators": "0.*" }, "require-dev": { - "laravel/pint": "1.2.*", - "phpstan/phpstan": "1.9.x-dev", + "laravel/pint": "^1.18", + "phpstan/phpstan": "^1.12", "phpunit/phpunit": "^9.3" }, "type": "library", @@ -1972,9 +1977,123 @@ ], "support": { "issues": "https://github.com/utopia-php/domains/issues", - "source": "https://github.com/utopia-php/domains/tree/0.9.0" + "source": "https://github.com/utopia-php/domains/tree/0.9.2" }, - "time": "2025-10-21T08:57:03+00:00" + "time": "2025-11-26T12:16:36+00:00" + }, + { + "name": "utopia-php/pools", + "version": "0.8.3", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/pools.git", + "reference": "ad7d6ba946376e81c603204285ce9a674b6502b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/pools/zipball/ad7d6ba946376e81c603204285ce9a674b6502b8", + "reference": "ad7d6ba946376e81c603204285ce9a674b6502b8", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "utopia-php/telemetry": "*" + }, + "require-dev": { + "laravel/pint": "1.*", + "phpstan/phpstan": "1.*", + "phpunit/phpunit": "11.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Pools\\": "src/Pools" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Team Appwrite", + "email": "team@appwrite.io" + } + ], + "description": "A simple library to manage connection pools", + "keywords": [ + "framework", + "php", + "pools", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/pools/issues", + "source": "https://github.com/utopia-php/pools/tree/0.8.3" + }, + "time": "2025-12-17T09:35:18+00:00" + }, + { + "name": "utopia-php/span", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/span.git", + "reference": "3af4ad7dc38a1da2e5fb4e47f296684fb18f8aae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/span/zipball/3af4ad7dc38a1da2e5fb4e47f296684fb18f8aae", + "reference": "3af4ad7dc38a1da2e5fb4e47f296684fb18f8aae", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "laravel/pint": "^1.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^10.0", + "swoole/ide-helper": "^5.0" + }, + "suggest": { + "ext-swoole": "Required for coroutine-based storage" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Span\\": "src/Span/" + } + }, + "autoload-dev": { + "psr-4": { + "Utopia\\Span\\Tests\\": "tests/" + } + }, + "scripts": { + "format": [ + "pint --test" + ], + "format:fix": [ + "pint" + ], + "test": [ + "phpunit" + ], + "analyse": [ + "phpstan analyse" + ] + }, + "license": [ + "MIT" + ], + "description": "Simple span tracing library for PHP with coroutine support", + "support": { + "source": "https://github.com/utopia-php/span/tree/main", + "issues": "https://github.com/utopia-php/span/issues" + }, + "time": "2025-12-31T11:11:21+00:00" }, { "name": "utopia-php/telemetry", @@ -2033,26 +2152,25 @@ }, { "name": "utopia-php/validators", - "version": "0.0.2", + "version": "0.1.0", "source": { "type": "git", "url": "https://github.com/utopia-php/validators.git", - "reference": "894210695c5d35fa248fb65f7fe7237b6ff4fb0b" + "reference": "5c57d5b6cf964f8981807c1d3ea8df620c869080" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/validators/zipball/894210695c5d35fa248fb65f7fe7237b6ff4fb0b", - "reference": "894210695c5d35fa248fb65f7fe7237b6ff4fb0b", + "url": "https://api.github.com/repos/utopia-php/validators/zipball/5c57d5b6cf964f8981807c1d3ea8df620c869080", + "reference": "5c57d5b6cf964f8981807c1d3ea8df620c869080", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.0" }, "require-dev": { - "ext-xdebug": "*", - "laravel/pint": "^1.2", + "laravel/pint": "1.*", "phpstan/phpstan": "1.*", - "phpunit/phpunit": "^9.5.25" + "phpunit/phpunit": "11.*" }, "type": "library", "autoload": { @@ -2073,9 +2191,9 @@ ], "support": { "issues": "https://github.com/utopia-php/validators/issues", - "source": "https://github.com/utopia-php/validators/tree/0.0.2" + "source": "https://github.com/utopia-php/validators/tree/0.1.0" }, - "time": "2025-10-20T21:52:28+00:00" + "time": "2025-11-18T11:05:46+00:00" } ], "packages-dev": [ @@ -2441,16 +2559,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "12.5.1", + "version": "12.5.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "c467c59a4f6e04b942be422844e7a6352fa01b57" + "reference": "4a9739b51cbcb355f6e95659612f92e282a7077b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c467c59a4f6e04b942be422844e7a6352fa01b57", - "reference": "c467c59a4f6e04b942be422844e7a6352fa01b57", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4a9739b51cbcb355f6e95659612f92e282a7077b", + "reference": "4a9739b51cbcb355f6e95659612f92e282a7077b", "shasum": "" }, "require": { @@ -2465,7 +2583,7 @@ "sebastian/environment": "^8.0.3", "sebastian/lines-of-code": "^4.0", "sebastian/version": "^6.0", - "theseer/tokenizer": "^2.0" + "theseer/tokenizer": "^2.0.1" }, "require-dev": { "phpunit/phpunit": "^12.5.1" @@ -2506,7 +2624,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.1" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.2" }, "funding": [ { @@ -2526,7 +2644,7 @@ "type": "tidelift" } ], - "time": "2025-12-08T07:17:58+00:00" + "time": "2025-12-24T07:03:04+00:00" }, { "name": "phpunit/php-file-iterator", @@ -3912,7 +4030,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": { + "utopia-php/span": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/DNS/Server.php b/src/DNS/Server.php index 16f60aa..d5f1cc3 100644 --- a/src/DNS/Server.php +++ b/src/DNS/Server.php @@ -3,8 +3,8 @@ namespace Utopia\DNS; use Throwable; -use Utopia\Console; use Utopia\DNS\Exception\Message\PartialDecodingException; +use Utopia\Span\Span; use Utopia\Telemetry\Adapter as Telemetry; use Utopia\Telemetry\Adapter\None as NoTelemetry; use Utopia\Telemetry\Counter; @@ -148,12 +148,6 @@ public function setDebug(bool $status): self */ protected function handleError(Throwable $error): void { - if (empty($this->errors)) { - // Default error handler - Console::error('[ERROR] ' . $error->getMessage() . ' in ' . $error->getFile() . ' on line ' . $error->getLine() . "\n" . $error->getTraceAsString()); - return; - } - foreach ($this->errors as $handler) { call_user_func($handler, $error); } @@ -170,133 +164,116 @@ protected function handleError(Throwable $error): void */ protected function onPacket(string $buffer, string $ip, int $port): string { - $startTime = microtime(true); - Console::info("[PACKET] Received packet of " . strlen($buffer) . " bytes from $ip:$port"); - - // 1. Parse Message. - $decodeStart = microtime(true); - try { - $query = Message::decode($buffer); - } catch (PartialDecodingException $e) { - Console::error("[ERROR] Failed to decode packet: " . $e->getMessage()); - Console::error("[ERROR] Packet dump: " . bin2hex($buffer)); - Console::error("[ERROR] Processing time: " . (microtime(true) - $startTime) . "s"); - - $this->handleError($e); - - $response = Message::response( - $e->getHeader(), - Message::RCODE_FORMERR, - authoritative: false - ); - return $response->encode(); - } catch (Throwable $e) { - Console::error("[ERROR] Failed to decode packet: " . $e->getMessage()); - Console::error("[ERROR] Packet dump: " . bin2hex($buffer)); - Console::error("[ERROR] Processing time: " . (microtime(true) - $startTime) . "s"); - - $this->handleError($e); - - // Returning SERVFAIL is unsafe here - just drop the packet - return ''; - } - - $question = $query->questions[0] ?? null; - if ($question === null) { - $response = Message::response( - $query->header, - Message::RCODE_FORMERR, - authoritative: false - ); - return $response->encode(); - } + $span = Span::init(); + $span->set('client.ip', $ip); - $this->queriesTotal?->add(1, [ - 'type' => $question->type ?? null, - ]); - $this->duration?->record(microtime(true) - $decodeStart, ['phase' => 'decode']); + $question = null; + $response = null; - // 2. Resolve query - $resolveStart = microtime(true); try { - $response = $this->resolver->resolve($query); - } catch (Throwable $e) { - Console::error("[ERROR] Failed to resolve zone $question->name (Type: $question->type): " . $e->getMessage()); - Console::error("[ERROR] Packet dump: " . bin2hex($buffer)); - - $this->handleError($e); + // 1. Parse Message. + $decodeStart = microtime(true); + try { + $query = Message::decode($buffer); + } catch (PartialDecodingException $e) { + $this->handleError($e); + + $response = Message::response( + $e->getHeader(), + Message::RCODE_FORMERR, + authoritative: false + ); + return $response->encode(); + } catch (Throwable $e) { + $span->setError($e); + $this->handleError($e); + return ''; + } + $decodeDuration = microtime(true) - $decodeStart; + $this->duration?->record($decodeDuration, ['phase' => 'decode']); + $span->set('dns.duration.decode', $decodeDuration); + + $question = $query->questions[0] ?? null; + if ($question === null) { + $response = Message::response( + $query->header, + Message::RCODE_FORMERR, + authoritative: false + ); + return $response->encode(); + } + + $span->set('dns.question.name', $question->name); + $span->set('dns.question.type', $question->type); + + $this->queriesTotal?->add(1, [ + 'type' => $question->type ?? null, + ]); - $this->duration?->record(microtime(true) - $resolveStart, [ + // 2. Resolve query + $resolveStart = microtime(true); + try { + $response = $this->resolver->resolve($query); + } catch (Throwable $e) { + $span->setError($e); + $this->handleError($e); + + $response = Message::response( + $query->header, + Message::RCODE_SERVFAIL, + questions: $query->questions, + authoritative: false + ); + } + $resolveDuration = microtime(true) - $resolveStart; + $this->duration?->record($resolveDuration, [ 'phase' => 'resolve', - 'responseCode' => Message::RCODE_SERVFAIL, + 'responseCode' => $response->header->responseCode, ]); - - $response = Message::response( - $query->header, - Message::RCODE_SERVFAIL, - questions: $query->questions, - authoritative: false - ); - } - - Console::info("[PACKET] DNS Header - ID: {$query->header->id}, Questions: {$query->header->questionCount}, Answers: {$query->header->answerCount}"); - - if ($this->debug) { - Console::info("[QUERY] Query for domain: $question->name (Type: $question->type)"); - } - - if (empty($response->answers)) { - Console::warning("[RESPONSE] No answers found for $question->name ($question->type)"); - } - - // 3. Encode response - $encodeStart = microtime(true); - try { - return $response->encode(); - } catch (Throwable $e) { - Console::error("[ERROR] Failed to encode message: " . $e->getMessage()); - Console::error("[ERROR] Packet dump: " . bin2hex($buffer)); - - $this->handleError($e); - - $response = Message::response( - $query->header, - Message::RCODE_SERVFAIL, - questions: $query->questions, - authoritative: false - ); - return $response->encode(); + $span->set('dns.duration.resolve', $resolveDuration); + + // 3. Encode response + $encodeStart = microtime(true); + try { + return $response->encode(); + } catch (Throwable $e) { + $span->setError($e); + $this->handleError($e); + + $response = Message::response( + $query->header, + Message::RCODE_SERVFAIL, + questions: $query->questions, + authoritative: false + ); + return $response->encode(); + } finally { + $encodeDuration = microtime(true) - $encodeStart; + $this->duration?->record($encodeDuration, [ + 'phase' => 'encode', + 'responseCode' => $response->header->responseCode + ]); + $span->set('dns.duration.encode', $encodeDuration); + } } finally { - $this->responsesTotal?->add(1, [ - 'type' => $question->type ?? null, - 'responseCode' => $response->header->responseCode - ]); - $this->duration?->record(microtime(true) - $encodeStart, [ - 'phase' => 'encode', - 'responseCode' => $response->header->responseCode - ]); - - $fullDuration = microtime(true) - $startTime; - Console::info("[PACKET] Processing completed in $fullDuration\s"); - $this->duration?->record($fullDuration, ['phase' => 'full']); + if ($question !== null) { + $this->responsesTotal?->add(1, [ + 'type' => $question->type ?? null, + 'responseCode' => $response?->header->responseCode + ]); + } + + if ($response !== null) { + $span->set('dns.response.code', $response->header->responseCode); + $span->set('dns.response.answer_count', $response->header->answerCount); + } + $span->finish(); } } public function start(): void { try { - Console::success('[DNS] Starting DNS Server...'); - Console::info('[CONFIG] Adapter: ' . $this->adapter->getName()); - Console::info('[CONFIG] Resolver: ' . $this->resolver->getName()); - Console::info('[CONFIG] Memory Limit: ' . ini_get('memory_limit')); - Console::info('[CONFIG] Max Execution Time: ' . ini_get('max_execution_time') . 's'); - Console::info('[CONFIG] PHP Version: ' . PHP_VERSION); - Console::info('[CONFIG] OS: ' . PHP_OS); - Console::info('[CONFIG] Time: ' . date('Y-m-d H:i:s T')); - Console::info('[CONFIG] Debug Mode: ' . ($this->debug ? 'Enabled' : 'Disabled')); - - Console::success('[DNS] Server is ready to accept connections'); - /** @phpstan-var \Closure(string $buffer, string $ip, int $port):string $onPacket */ $onPacket = $this->onPacket(...); $this->adapter->onPacket($onPacket); diff --git a/tests/resources/server.php b/tests/resources/server.php index 9ebe042..b6e6d6a 100644 --- a/tests/resources/server.php +++ b/tests/resources/server.php @@ -2,17 +2,22 @@ require __DIR__ . '/../../vendor/autoload.php'; -use Utopia\Console; use Utopia\DNS\Server; use Utopia\DNS\Adapter\Swoole; use Utopia\DNS\Message\Record; use Utopia\DNS\Resolver\Memory; use Utopia\DNS\Zone; +use Utopia\Span\Span; +use Utopia\Span\Storage; +use Utopia\Span\Exporter; if (realpath($_SERVER['SCRIPT_FILENAME'] ?? '') !== __FILE__) { return; } +Span::setStorage(new Storage\Coroutine()); +Span::addExporter(new Exporter\Stdout()); + $port = (int) (getenv('PORT') ?: 5300); $server = new Swoole('0.0.0.0', $port); @@ -55,7 +60,10 @@ $dns->setDebug(false); $dns->onWorkerStart(function (Server $server, int $workerId) { - Console::log("Worker $workerId started"); + $span = Span::init(); + $span->set('action', 'dns.worker.start'); + $span->set('worker.id', $workerId); + $span->finish(); }); $dns->start();