diff --git a/.env.example b/.env.example index d073ac542..81c62d235 100644 --- a/.env.example +++ b/.env.example @@ -212,9 +212,40 @@ EMAIL_TEMPLATE_TICKET_BOTTOM_GRAPHIC=https://spaces.fnvirtual.app/emails/REGISTR EMAIL_TEMPLATE_PRIMARY_COLOR="#000000" EMAIL_TEMPLATE_SECONDARY_COLOR="#808080" -#Open Telemetry vars -OTEL_EXPORTER_OTLP_ENDPOINT= +#Open Telemetry +OTEL_SERVICE_ENABLED=true +OTEL_SERVICE_NAME=summit-api +OTEL_PROPAGATORS=tracecontext,baggage +OTEL_EXPORTER_OTLP_PROTOCOL=http/json # Supported values: "grpc", "http/protobuf", "http/json" +OTEL_EXPORTER_OTLP_MAX_RETRIES=3 +OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 TRACE_SPAN_PREFIX=SPAN +OTEL_TRACES_SAMPLER_PARENT=false +# OTEL_TRACES_SAMPLER_TYPE=always_on # Supported values: "always_on", "always_off", "traceidratio" +# OTEL_TRACES_SAMPLER_TRACEIDRATIO_RATIO=0.05 +# OTEL_METRICS_EXPORTER=otlp +# OTEL_TRACES_EXPORTER=otlp +# OTEL_LOGS_EXPORTER=otlp +# OTEL_EXPORTER_OTLP_TIMEOUT=10000 +# OTEL_EXPORTER_OTLP_HEADERS= +# OTEL_EXPORTER_OTLP_TRACES_TIMEOUT=10000 +# OTEL_EXPORTER_OTLP_TRACES_HEADERS= +# OTEL_EXPORTER_OTLP_METRICS_TIMEOUT=10000 +# OTEL_EXPORTER_OTLP_METRICS_HEADERS= +# OTEL_EXPORTER_OTLP_LOGS_TIMEOUT=10000 +# OTEL_EXPORTER_OTLP_LOGS_HEADERS= +# OTEL_EXPORTER_ZIPKIN_ENDPOINT=http://localhost:9411 +# OTEL_EXPORTER_ZIPKIN_TIMEOUT=10000 +# OTEL_EXPORTER_ZIPKIN_MAX_RETRIES=3 +# OTEL_INSTRUMENTATION_HTTP_SERVER=true +# OTEL_INSTRUMENTATION_HTTP_CLIENT=true +# OTEL_INSTRUMENTATION_QUERY=true +# OTEL_INSTRUMENTATION_REDIS=true +# OTEL_INSTRUMENTATION_QUEUE=true +# OTEL_INSTRUMENTATION_CACHE=true +# OTEL_INSTRUMENTATION_VIEW=true +# OTEL_INSTRUMENTATION_LIVEWIRE=true +# OTEL_INSTRUMENTATION_CONSOLE=true # SWAGGER CONFIG diff --git a/app/Http/Middleware/TrackRequestMiddleware.php b/app/Http/Middleware/TrackRequestMiddleware.php index 1700460a8..dafa06632 100644 --- a/app/Http/Middleware/TrackRequestMiddleware.php +++ b/app/Http/Middleware/TrackRequestMiddleware.php @@ -2,99 +2,85 @@ namespace App\Http\Middleware; -use Illuminate\Http\Request; use Closure; -use Illuminate\Support\Str; -use \OpenTelemetry\API\Trace\SpanInterface; +use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; -use Illuminate\Log\LogManager; use Keepsuit\LaravelOpenTelemetry\Facades\Tracer; - +use Illuminate\Log\LogManager; +use OpenTelemetry\API\Baggage\Baggage; +use OpenTelemetry\Context\ScopeInterface; class TrackRequestMiddleware { - /** - * @var LogManager - */ - protected LogManager $logger; + private const ATTRIBUTE_START_TIME = '_start_time'; + private const EVENT_REQUEST_STARTED = 'request.started'; + private const EVENT_REQUEST_FINISHED = 'request.finished'; - /** - * @var float - */ - protected float $startTime = 0; - - /** - * @var SpanInterface - */ - protected SpanInterface $span; + protected LogManager $logger; + private ?ScopeInterface $baggageScope = null; + private bool $shouldTrack; - /** - * Constructor del middleware. - * Laravel inyectará el LogManager aquí. - * - * @param LogManager $logger - */ public function __construct(LogManager $logger) { $this->logger = $logger; + $this->shouldTrack = env('OTEL_SERVICE_ENABLED', false); } - /** - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - */ public function handle(Request $request, Closure $next) { - if(env('APP_ENV') === 'testing') { - // Skip tracking in testing environment + if (!$this->shouldTrack) { return $next($request); } + try { - // generating dynamic id for span with configurable prefix - $spanId = env('TRACE_SPAN_PREFIX', 'SPAN') . '_' . Str::uuid(); - $this->startTime = microtime(true); - $this->span = Tracer::newSpan($spanId)->start(); + $request->attributes->set(self::ATTRIBUTE_START_TIME, microtime(true)); + if ($span = Tracer::activeSpan()) { + if ($ray = $request->header('cf-ray')) { + $span->setAttribute('cloudflare.cf-ray', $ray); + + $baggage = Baggage::getCurrent() + ->toBuilder() + ->set('cf-ray', $ray) + ->set('user_agent', substr($request->userAgent() ?? 'unknown', 0, 100)) + ->build(); - $this->logger->channel('otlp')->info('Request started.', [ - 'endpoint' => $request->url(), - 'method' => $request->method(), - 'timestamp_utc' => now()->toIso8601String(), - ]); + $this->baggageScope = $baggage->activate(); + } + + $span->addEvent(self::EVENT_REQUEST_STARTED, [ + 'method' => $request->method(), + 'url' => $request->fullUrl(), + ]); + } } catch (\Throwable $e) { - // forcing 'single' channel in case otlp log fails - $this->logger->channel('single')->error("Error on request tracking" . $e->getMessage()); + $this->logger->channel('daily')->error("Error on request tracking: " . $e->getMessage()); } - $response = $next($request); - return $response; + return $next($request); } - /** - * @param Request $request - * @param Response $response - * @return void - */ public function terminate(Request $request, Response $response): void { - if(env('APP_ENV') === 'testing') { - // Skip tracking in testing environment + if (!$this->shouldTrack) { return; } + try { - $endTime = microtime(true); - $responseTime = intval(($endTime - $this->startTime) * 1000); - $this->logger->channel('otlp')->info('Request finished.', [ - 'response_time' => $responseTime, - ]); + $start = (float) $request->attributes->get(self::ATTRIBUTE_START_TIME, microtime(true)); + $ms = (int) ((microtime(true) - $start) * 1000); - if (isset($this->span)) { - $this->span->end(); + if ($span = Tracer::activeSpan()) { + $span->setAttribute('app.response_ms', $ms); + $span->setAttribute('http.status_code', $response->getStatusCode()); + $span->addEvent(self::EVENT_REQUEST_FINISHED, ['response_ms' => $ms]); } - } catch (\Throwable $e) { - // forcing 'single' channel in case otlp log fails - $this->logger->channel('single')->error("Error on request tracking: " . $e->getMessage()); + $this->logger->channel('daily')->error("Error on request tracking: " . $e->getMessage()); + } finally { + if ($this->baggageScope) { + $this->baggageScope->detach(); + $this->baggageScope = null; + } } } -} \ No newline at end of file +} diff --git a/app/Services/Apis/MailApi.php b/app/Services/Apis/MailApi.php index c62fea1e6..2919b7885 100644 --- a/app/Services/Apis/MailApi.php +++ b/app/Services/Apis/MailApi.php @@ -19,6 +19,7 @@ use GuzzleHttp\RequestOptions; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Log; +use Keepsuit\LaravelOpenTelemetry\Support\HttpClient\GuzzleTraceMiddleware; use libs\utils\ICacheService; use models\exceptions\ValidationException; /** @@ -58,6 +59,7 @@ public function __construct(ICacheService $cacheService) parent::__construct($cacheService); $stack = HandlerStack::create(); $stack->push(GuzzleRetryMiddleware::factory()); + $stack->push(GuzzleTraceMiddleware::make()); $this->client = new Client([ 'handler' => $stack, diff --git a/app/Services/Utils/RedisCacheService.php b/app/Services/Utils/RedisCacheService.php index ae2bb19b2..22b9122b6 100644 --- a/app/Services/Utils/RedisCacheService.php +++ b/app/Services/Utils/RedisCacheService.php @@ -64,19 +64,25 @@ public function __destruct() if (is_resource($res)) $metadata = @stream_get_meta_data($res); } } catch (\Throwable $ignored) {} - Log::error - ( - sprintf + // Check if Laravel app is still available before logging + if (app()->bound('config')) { + Log::error ( - "RedisCacheService::__destruct %s %s %s", - $ex->getCode(), - $ex->getMessage(), - var_export($metadata, true) - ) - ); + sprintf + ( + "RedisCacheService::__destruct %s %s %s", + $ex->getCode(), + $ex->getMessage(), + var_export($metadata, true) + ) + ); + } } catch(\Exception $ex){ - Log::warning($ex); + // Check if Laravel app is still available before logging + if (app()->bound('config')) { + Log::warning($ex); + } } } @@ -325,7 +331,7 @@ public function ttl($key) return (int)$conn->ttl($key); }, 0); } - + /** * @param string $cache_region_key * @return void diff --git a/composer.json b/composer.json index d037f8fd1..9b23e1dd1 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "google/apiclient": "^2.2", "guzzlehttp/guzzle": "7.8.2", "guzzlehttp/uri-template": "^1.0", - "keepsuit/laravel-opentelemetry": "^1.6", + "keepsuit/laravel-opentelemetry": "^1.11.0", "laminas/laminas-math": "^3.7", "laravel-doctrine/extensions": "2.0.1", "laravel-doctrine/migrations": "3.4.0", @@ -66,10 +66,10 @@ "rector/rector": "*", "spatie/laravel-ignition": "^2.8.0" }, - "suggest":{ + "suggest": { "lib-openssl": "Required to use AES algorithms (except AES GCM)", - "ext-json":"Required to use json algorithms", - "ext-gd":"Required to use json algorithms" + "ext-json": "Required to use json algorithms", + "ext-gd": "Required to use json algorithms" }, "autoload": { "classmap": [ @@ -82,9 +82,15 @@ "vendor/affinipay/chargeio-php/lib" ], "psr-4": { - "App\\": "app/" + "App\\": "app/", + "OpenTelemetry\\SemConv\\Unstable\\Metrics\\": "vendor/open-telemetry/sem-conv/Incubating/Metrics/", + "OpenTelemetry\\" : "vendor/open-telemetry/", + "OpenTelemetry\\API\\": "vendor/open-telemetry/api/" + }, - "files": ["app/Utils/helpers.php"] + "files": [ + "app/Utils/helpers.php" + ] }, "autoload-dev": { "psr-4": { @@ -128,4 +134,4 @@ }, "minimum-stability": "dev", "prefer-stable": true -} +} \ No newline at end of file diff --git a/composer.lock b/composer.lock index 96b8bc574..e152f2f02 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": "421f8844647bf58c468b08e83e30f2d8", + "content-hash": "0fe683ede6eab50b8e08f13b3e2791cf", "packages": [ { "name": "affinipay/chargeio-php", @@ -621,16 +621,16 @@ }, { "name": "composer/semver", - "version": "3.4.3", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { @@ -682,7 +682,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.3" + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { @@ -692,13 +692,9 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2024-09-19T14:15:21+00:00" + "time": "2025-08-20T19:15:30+00:00" }, { "name": "darkaonline/l5-swagger", @@ -1363,33 +1359,32 @@ }, { "name": "doctrine/inflector", - "version": "2.0.10", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11.0", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "^8.5 || ^9.5", - "vimeo/psalm": "^4.25 || ^5.4" + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + "Doctrine\\Inflector\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1434,7 +1429,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.10" + "source": "https://github.com/doctrine/inflector/tree/2.1.0" }, "funding": [ { @@ -1450,7 +1445,7 @@ "type": "tidelift" } ], - "time": "2024-02-18T20:23:39+00:00" + "time": "2025-08-10T19:31:58+00:00" }, { "name": "doctrine/instantiator", @@ -2486,23 +2481,23 @@ }, { "name": "google/protobuf", - "version": "v4.31.1", + "version": "v4.32.1", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "2b028ce8876254e2acbeceea7d9b573faad41864" + "reference": "c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/2b028ce8876254e2acbeceea7d9b573faad41864", - "reference": "2b028ce8876254e2acbeceea7d9b573faad41864", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb", + "reference": "c4ed1c1f9bbc1e91766e2cd6c0af749324fe87cb", "shasum": "" }, "require": { - "php": ">=7.0.0" + "php": ">=8.1.0" }, "require-dev": { - "phpunit/phpunit": ">=5.0.0" + "phpunit/phpunit": ">=5.0.0 <8.5.27" }, "suggest": { "ext-bcmath": "Need to support JSON deserialization" @@ -2524,9 +2519,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.31.1" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.32.1" }, - "time": "2025-05-28T18:52:35+00:00" + "time": "2025-09-14T05:14:52+00:00" }, { "name": "graham-campbell/guzzle-factory", @@ -2783,16 +2778,16 @@ }, { "name": "guzzlehttp/promises", - "version": "2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" + "reference": "481557b130ef3790cf82b713667b43030dc9c957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", - "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", "shasum": "" }, "require": { @@ -2800,7 +2795,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "type": "library", "extra": { @@ -2846,7 +2841,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.2.0" + "source": "https://github.com/guzzle/promises/tree/2.3.0" }, "funding": [ { @@ -2862,20 +2857,20 @@ "type": "tidelift" } ], - "time": "2025-03-27T13:27:01+00:00" + "time": "2025-08-22T14:34:08+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.7.1", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", - "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { @@ -2891,7 +2886,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -2962,7 +2957,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.7.1" + "source": "https://github.com/guzzle/psr7/tree/2.8.0" }, "funding": [ { @@ -2978,20 +2973,20 @@ "type": "tidelift" } ], - "time": "2025-03-27T12:30:47+00:00" + "time": "2025-08-23T21:21:41+00:00" }, { "name": "guzzlehttp/uri-template", - "version": "v1.0.4", + "version": "v1.0.5", "source": { "type": "git", "url": "https://github.com/guzzle/uri-template.git", - "reference": "30e286560c137526eccd4ce21b2de477ab0676d2" + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/uri-template/zipball/30e286560c137526eccd4ce21b2de477ab0676d2", - "reference": "30e286560c137526eccd4ce21b2de477ab0676d2", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1", "shasum": "" }, "require": { @@ -3000,7 +2995,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "phpunit/phpunit": "^8.5.44 || ^9.6.25", "uri-template/tests": "1.0.0" }, "type": "library", @@ -3048,7 +3043,7 @@ ], "support": { "issues": "https://github.com/guzzle/uri-template/issues", - "source": "https://github.com/guzzle/uri-template/tree/v1.0.4" + "source": "https://github.com/guzzle/uri-template/tree/v1.0.5" }, "funding": [ { @@ -3064,7 +3059,7 @@ "type": "tidelift" } ], - "time": "2025-02-03T10:55:03+00:00" + "time": "2025-08-22T14:27:06+00:00" }, { "name": "justinrainbow/json-schema", @@ -3133,26 +3128,26 @@ }, { "name": "keepsuit/laravel-opentelemetry", - "version": "1.6.0", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/keepsuit/laravel-opentelemetry.git", - "reference": "cfa03dbdfdb4b97152e19b20c989209caa8802b0" + "reference": "f37602e38801e598e805646e598d70679082abc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/keepsuit/laravel-opentelemetry/zipball/cfa03dbdfdb4b97152e19b20c989209caa8802b0", - "reference": "cfa03dbdfdb4b97152e19b20c989209caa8802b0", + "url": "https://api.github.com/repos/keepsuit/laravel-opentelemetry/zipball/f37602e38801e598e805646e598d70679082abc1", + "reference": "f37602e38801e598e805646e598d70679082abc1", "shasum": "" }, "require": { - "illuminate/contracts": "^10.0 || ^11.0 || ^12.0", - "illuminate/support": "^10.0 || ^11.0 || ^12.0", - "open-telemetry/api": "^1.1", - "open-telemetry/context": "^1.1", - "open-telemetry/exporter-otlp": "^1.1", - "open-telemetry/sdk": "^1.1", - "open-telemetry/sem-conv": "^1.28.0", + "illuminate/contracts": "^10.14 || ^11.0 || ^12.0", + "illuminate/support": "^10.14 || ^11.0 || ^12.0", + "open-telemetry/api": "^1.4", + "open-telemetry/context": "^1.3", + "open-telemetry/exporter-otlp": "^1.3", + "open-telemetry/sdk": "^1.7", + "open-telemetry/sem-conv": "^1.37", "php": "^8.1", "spatie/laravel-package-tools": "^1.16", "thecodingmachine/safe": "^2.0 || ^3.0" @@ -3164,6 +3159,7 @@ }, "require-dev": { "guzzlehttp/guzzle": "^7.3", + "guzzlehttp/psr7": "^2.0", "guzzlehttp/test-server": "^0.1.0", "larastan/larastan": "^2.9 || ^3.0", "laravel/pint": "^1.18", @@ -3224,9 +3220,9 @@ ], "support": { "issues": "https://github.com/keepsuit/laravel-opentelemetry/issues", - "source": "https://github.com/keepsuit/laravel-opentelemetry/tree/1.6.0" + "source": "https://github.com/keepsuit/laravel-opentelemetry/tree/1.11.0" }, - "time": "2025-06-19T12:37:02+00:00" + "time": "2025-09-15T12:25:50+00:00" }, { "name": "laminas/laminas-math", @@ -3811,16 +3807,16 @@ }, { "name": "laravel/prompts", - "version": "v0.3.5", + "version": "v0.3.6", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "57b8f7efe40333cdb925700891c7d7465325d3b1" + "reference": "86a8b692e8661d0fb308cec64f3d176821323077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/57b8f7efe40333cdb925700891c7d7465325d3b1", - "reference": "57b8f7efe40333cdb925700891c7d7465325d3b1", + "url": "https://api.github.com/repos/laravel/prompts/zipball/86a8b692e8661d0fb308cec64f3d176821323077", + "reference": "86a8b692e8661d0fb308cec64f3d176821323077", "shasum": "" }, "require": { @@ -3864,9 +3860,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.5" + "source": "https://github.com/laravel/prompts/tree/v0.3.6" }, - "time": "2025-02-11T13:34:40+00:00" + "time": "2025-07-07T14:17:42+00:00" }, { "name": "laravel/serializable-closure", @@ -3997,16 +3993,16 @@ }, { "name": "league/commonmark", - "version": "2.7.0", + "version": "2.7.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405" + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", - "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/10732241927d3971d28e7ea7b5712721fa2296ca", + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca", "shasum": "" }, "require": { @@ -4035,7 +4031,7 @@ "symfony/process": "^5.4 | ^6.0 | ^7.0", "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", "unleashedtech/php-coding-standard": "^3.1.1", - "vimeo/psalm": "^4.24.0 || ^5.0.0" + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" }, "suggest": { "symfony/yaml": "v2.3+ required if using the Front Matter extension" @@ -4100,7 +4096,7 @@ "type": "tidelift" } ], - "time": "2025-05-05T12:20:28+00:00" + "time": "2025-07-20T12:47:49+00:00" }, { "name": "league/config", @@ -4277,16 +4273,16 @@ }, { "name": "league/flysystem", - "version": "3.29.1", + "version": "3.30.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319" + "reference": "2203e3151755d874bb2943649dae1eb8533ac93e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/edc1bb7c86fab0776c3287dbd19b5fa278347319", - "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/2203e3151755d874bb2943649dae1eb8533ac93e", + "reference": "2203e3151755d874bb2943649dae1eb8533ac93e", "shasum": "" }, "require": { @@ -4310,13 +4306,13 @@ "composer/semver": "^3.0", "ext-fileinfo": "*", "ext-ftp": "*", - "ext-mongodb": "^1.3", + "ext-mongodb": "^1.3|^2", "ext-zip": "*", "friendsofphp/php-cs-fixer": "^3.5", "google/cloud-storage": "^1.23", "guzzlehttp/psr7": "^2.6", "microsoft/azure-storage-blob": "^1.1", - "mongodb/mongodb": "^1.2", + "mongodb/mongodb": "^1.2|^2", "phpseclib/phpseclib": "^3.0.36", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.5.11|^10.0", @@ -4354,9 +4350,9 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.29.1" + "source": "https://github.com/thephpleague/flysystem/tree/3.30.0" }, - "time": "2024-10-08T08:58:34+00:00" + "time": "2025-06-25T13:29:59+00:00" }, { "name": "league/flysystem-aws-s3-v3", @@ -4430,16 +4426,16 @@ }, { "name": "league/flysystem-local", - "version": "3.29.0", + "version": "3.30.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27" + "reference": "6691915f77c7fb69adfb87dcd550052dc184ee10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27", - "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/6691915f77c7fb69adfb87dcd550052dc184ee10", + "reference": "6691915f77c7fb69adfb87dcd550052dc184ee10", "shasum": "" }, "require": { @@ -4473,9 +4469,9 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.30.0" }, - "time": "2024-08-09T21:24:39+00:00" + "time": "2025-05-21T10:34:19+00:00" }, { "name": "league/mime-type-detection", @@ -5001,16 +4997,16 @@ }, { "name": "nesbot/carbon", - "version": "3.10.0", + "version": "3.10.3", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "c1397390dd0a7e0f11660f0ae20f753d88c1f3d9" + "reference": "8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/c1397390dd0a7e0f11660f0ae20f753d88c1f3d9", - "reference": "c1397390dd0a7e0f11660f0ae20f753d88c1f3d9", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f", + "reference": "8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f", "shasum": "" }, "require": { @@ -5028,13 +5024,13 @@ "require-dev": { "doctrine/dbal": "^3.6.3 || ^4.0", "doctrine/orm": "^2.15.2 || ^3.0", - "friendsofphp/php-cs-fixer": "^3.75.0", + "friendsofphp/php-cs-fixer": "^v3.87.1", "kylekatarnls/multi-tester": "^2.5.3", "phpmd/phpmd": "^2.15.0", "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^2.1.17", - "phpunit/phpunit": "^10.5.46", - "squizlabs/php_codesniffer": "^3.13.0" + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4" }, "bin": [ "bin/carbon" @@ -5102,7 +5098,7 @@ "type": "tidelift" } ], - "time": "2025-06-12T10:24:28+00:00" + "time": "2025-09-06T13:39:36+00:00" }, { "name": "nette/schema", @@ -5168,29 +5164,29 @@ }, { "name": "nette/utils", - "version": "v4.0.7", + "version": "v4.0.8", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2" + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/e67c4061eb40b9c113b218214e42cb5a0dda28f2", - "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2", + "url": "https://api.github.com/repos/nette/utils/zipball/c930ca4e3cf4f17dcfb03037703679d2396d2ede", + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede", "shasum": "" }, "require": { - "php": "8.0 - 8.4" + "php": "8.0 - 8.5" }, "conflict": { "nette/finder": "<3", "nette/schema": "<1.2.2" }, "require-dev": { - "jetbrains/phpstorm-attributes": "dev-master", + "jetbrains/phpstorm-attributes": "^1.2", "nette/tester": "^2.5", - "phpstan/phpstan": "^1.0", + "phpstan/phpstan-nette": "^2.0@stable", "tracy/tracy": "^2.9" }, "suggest": { @@ -5208,6 +5204,9 @@ } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -5248,9 +5247,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.7" + "source": "https://github.com/nette/utils/tree/v4.0.8" }, - "time": "2025-06-03T04:55:08+00:00" + "time": "2025-08-06T21:43:34+00:00" }, { "name": "nikic/php-parser", @@ -5465,16 +5464,16 @@ }, { "name": "open-telemetry/api", - "version": "1.3.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "4e3bb38e069876fb73c2ce85c89583bf2b28cd86" + "reference": "7692075f486c14d8cfd37fba98a08a5667f089e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/4e3bb38e069876fb73c2ce85c89583bf2b28cd86", - "reference": "4e3bb38e069876fb73c2ce85c89583bf2b28cd86", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/7692075f486c14d8cfd37fba98a08a5667f089e5", + "reference": "7692075f486c14d8cfd37fba98a08a5667f089e5", "shasum": "" }, "require": { @@ -5494,7 +5493,7 @@ ] }, "branch-alias": { - "dev-main": "1.1.x-dev" + "dev-main": "1.4.x-dev" } }, "autoload": { @@ -5531,20 +5530,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-05-07T12:32:21+00:00" + "time": "2025-08-07T23:07:38+00:00" }, { "name": "open-telemetry/context", - "version": "1.2.1", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/context.git", - "reference": "1eb2b837ee9362db064a6b65d5ecce15a9f9f020" + "reference": "438f71812242db3f196fb4c717c6f92cbc819be6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/1eb2b837ee9362db064a6b65d5ecce15a9f9f020", - "reference": "1eb2b837ee9362db064a6b65d5ecce15a9f9f020", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/438f71812242db3f196fb4c717c6f92cbc819be6", + "reference": "438f71812242db3f196fb4c717c6f92cbc819be6", "shasum": "" }, "require": { @@ -5590,20 +5589,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-05-07T23:36:50+00:00" + "time": "2025-08-13T01:12:00+00:00" }, { "name": "open-telemetry/exporter-otlp", - "version": "1.3.1", + "version": "1.3.2", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/exporter-otlp.git", - "reference": "8b3ca1f86d01429c73b407bf1a2075d9c187001e" + "reference": "196f3a1dbce3b2c0f8110d164232c11ac00ddbb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/8b3ca1f86d01429c73b407bf1a2075d9c187001e", - "reference": "8b3ca1f86d01429c73b407bf1a2075d9c187001e", + "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/196f3a1dbce3b2c0f8110d164232c11ac00ddbb2", + "reference": "196f3a1dbce3b2c0f8110d164232c11ac00ddbb2", "shasum": "" }, "require": { @@ -5654,7 +5653,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-05-21T12:02:20+00:00" + "time": "2025-06-16T00:24:51+00:00" }, { "name": "open-telemetry/gen-otlp-protobuf", @@ -5721,22 +5720,22 @@ }, { "name": "open-telemetry/sdk", - "version": "1.5.0", + "version": "1.7.1", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "cd0d7367599717fc29e04eb8838ec061e6c2c657" + "reference": "52690d4b37ae4f091af773eef3c238ed2bc0aa06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/cd0d7367599717fc29e04eb8838ec061e6c2c657", - "reference": "cd0d7367599717fc29e04eb8838ec061e6c2c657", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/52690d4b37ae4f091af773eef3c238ed2bc0aa06", + "reference": "52690d4b37ae4f091af773eef3c238ed2bc0aa06", "shasum": "" }, "require": { "ext-json": "*", "nyholm/psr7-server": "^1.1", - "open-telemetry/api": "~1.0 || ~1.1", + "open-telemetry/api": "^1.4", "open-telemetry/context": "^1.0", "open-telemetry/sem-conv": "^1.0", "php": "^8.1", @@ -5749,7 +5748,7 @@ "ramsey/uuid": "^3.0 || ^4.0", "symfony/polyfill-mbstring": "^1.23", "symfony/polyfill-php82": "^1.26", - "tbachert/spi": "^1.0.1" + "tbachert/spi": "^1.0.5" }, "suggest": { "ext-gmp": "To support unlimited number of synchronous metric readers", @@ -5759,6 +5758,13 @@ "type": "library", "extra": { "spi": { + "OpenTelemetry\\API\\Configuration\\ConfigEnv\\EnvComponentLoader": [ + "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderHttpConfig", + "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderPeerConfig" + ], + "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\ResolverInterface": [ + "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\SdkConfigurationResolver" + ], "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" ] @@ -5807,20 +5813,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-05-22T02:33:34+00:00" + "time": "2025-09-05T07:17:06+00:00" }, { "name": "open-telemetry/sem-conv", - "version": "1.32.0", + "version": "1.37.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sem-conv.git", - "reference": "16585cc0dbc3032a318e274043454679430d2ebf" + "reference": "8da7ec497c881e39afa6657d72586e27efbd29a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/16585cc0dbc3032a318e274043454679430d2ebf", - "reference": "16585cc0dbc3032a318e274043454679430d2ebf", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/8da7ec497c881e39afa6657d72586e27efbd29a1", + "reference": "8da7ec497c881e39afa6657d72586e27efbd29a1", "shasum": "" }, "require": { @@ -5864,7 +5870,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-05-05T03:58:53+00:00" + "time": "2025-09-03T12:08:10+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -6222,16 +6228,16 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.3", + "version": "1.9.4", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", "shasum": "" }, "require": { @@ -6239,7 +6245,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" }, "type": "library", "extra": { @@ -6281,7 +6287,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.4" }, "funding": [ { @@ -6293,7 +6299,7 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:41:07+00:00" + "time": "2025-08-21T11:53:16+00:00" }, { "name": "phpseclib/phpseclib", @@ -7194,21 +7200,20 @@ }, { "name": "ramsey/uuid", - "version": "4.8.1", + "version": "4.9.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28" + "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", - "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440", + "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", - "ext-json": "*", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -7267,9 +7272,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.8.1" + "source": "https://github.com/ramsey/uuid/tree/4.9.1" }, - "time": "2025-06-01T06:28:46+00:00" + "time": "2025-09-04T20:59:21+00:00" }, { "name": "s-ichikawa/laravel-sendgrid-driver", @@ -7608,16 +7613,16 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.92.4", + "version": "1.92.7", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "d20b1969f836d210459b78683d85c9cd5c5f508c" + "reference": "f09a799850b1ed765103a4f0b4355006360c49a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/d20b1969f836d210459b78683d85c9cd5c5f508c", - "reference": "d20b1969f836d210459b78683d85c9cd5c5f508c", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/f09a799850b1ed765103a4f0b4355006360c49a5", + "reference": "f09a799850b1ed765103a4f0b4355006360c49a5", "shasum": "" }, "require": { @@ -7657,7 +7662,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.92.4" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.92.7" }, "funding": [ { @@ -7665,7 +7670,7 @@ "type": "github" } ], - "time": "2025-04-11T15:27:14+00:00" + "time": "2025-07-17T15:46:43+00:00" }, { "name": "stripe/stripe-php", @@ -8038,16 +8043,16 @@ }, { "name": "symfony/console", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44" + "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/66c1440edf6f339fd82ed6c7caa76cb006211b44", - "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44", + "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", + "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", "shasum": "" }, "require": { @@ -8112,7 +8117,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.0" + "source": "https://github.com/symfony/console/tree/v7.3.3" }, "funding": [ { @@ -8123,12 +8128,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-24T10:34:04+00:00" + "time": "2025-08-25T06:35:40+00:00" }, { "name": "symfony/css-selector", @@ -8264,16 +8273,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "cf68d225bc43629de4ff54778029aee6dc191b83" + "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/cf68d225bc43629de4ff54778029aee6dc191b83", - "reference": "cf68d225bc43629de4ff54778029aee6dc191b83", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/0b31a944fcd8759ae294da4d2808cbc53aebd0c3", + "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3", "shasum": "" }, "require": { @@ -8321,7 +8330,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.3.0" + "source": "https://github.com/symfony/error-handler/tree/v7.3.2" }, "funding": [ { @@ -8332,25 +8341,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-29T07:19:49+00:00" + "time": "2025-07-07T08:17:57+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "497f73ac996a598c92409b44ac43b6690c4f666d" + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d", - "reference": "497f73ac996a598c92409b44ac43b6690c4f666d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191", + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191", "shasum": "" }, "require": { @@ -8401,7 +8414,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.3" }, "funding": [ { @@ -8412,12 +8425,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-22T09:11:45+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -8497,16 +8514,16 @@ }, { "name": "symfony/finder", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", + "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", "shasum": "" }, "require": { @@ -8541,7 +8558,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.0" + "source": "https://github.com/symfony/finder/tree/v7.3.2" }, "funding": [ { @@ -8552,25 +8569,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-30T19:00:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "4236baf01609667d53b20371486228231eb135fd" + "reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/4236baf01609667d53b20371486228231eb135fd", - "reference": "4236baf01609667d53b20371486228231eb135fd", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/7475561ec27020196c49bb7c4f178d33d7d3dc00", + "reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00", "shasum": "" }, "require": { @@ -8620,7 +8641,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.3.0" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.3" }, "funding": [ { @@ -8631,25 +8652,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-12T14:48:23+00:00" + "time": "2025-08-20T08:04:18+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "ac7b8e163e8c83dce3abcc055a502d4486051a9f" + "reference": "72c304de37e1a1cec6d5d12b81187ebd4850a17b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ac7b8e163e8c83dce3abcc055a502d4486051a9f", - "reference": "ac7b8e163e8c83dce3abcc055a502d4486051a9f", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/72c304de37e1a1cec6d5d12b81187ebd4850a17b", + "reference": "72c304de37e1a1cec6d5d12b81187ebd4850a17b", "shasum": "" }, "require": { @@ -8734,7 +8759,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.3.0" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.3" }, "funding": [ { @@ -8745,25 +8770,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-29T07:47:32+00:00" + "time": "2025-08-29T08:23:45+00:00" }, { "name": "symfony/mailer", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "0f375bbbde96ae8c78e4aa3e63aabd486e33364c" + "reference": "a32f3f45f1990db8c4341d5122a7d3a381c7e575" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/0f375bbbde96ae8c78e4aa3e63aabd486e33364c", - "reference": "0f375bbbde96ae8c78e4aa3e63aabd486e33364c", + "url": "https://api.github.com/repos/symfony/mailer/zipball/a32f3f45f1990db8c4341d5122a7d3a381c7e575", + "reference": "a32f3f45f1990db8c4341d5122a7d3a381c7e575", "shasum": "" }, "require": { @@ -8814,7 +8843,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.0" + "source": "https://github.com/symfony/mailer/tree/v7.3.3" }, "funding": [ { @@ -8825,25 +8854,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-04T09:51:09+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "symfony/mime", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9" + "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", - "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", + "url": "https://api.github.com/repos/symfony/mime/zipball/e0a0f859148daf1edf6c60b398eb40bfc96697d1", + "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1", "shasum": "" }, "require": { @@ -8898,7 +8931,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.3.0" + "source": "https://github.com/symfony/mime/tree/v7.3.2" }, "funding": [ { @@ -8909,16 +8942,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-19T08:51:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -8977,7 +9014,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -8988,6 +9025,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -8997,16 +9038,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { @@ -9055,7 +9096,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -9066,16 +9107,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", @@ -9138,7 +9183,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" }, "funding": [ { @@ -9149,6 +9194,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -9158,7 +9207,7 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -9219,7 +9268,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -9230,6 +9279,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -9239,7 +9292,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -9300,7 +9353,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -9311,6 +9364,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -9320,7 +9377,7 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -9380,7 +9437,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -9391,6 +9448,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -9400,7 +9461,7 @@ }, { "name": "symfony/polyfill-php82", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php82.git", @@ -9456,7 +9517,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php82/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php82/tree/v1.33.0" }, "funding": [ { @@ -9467,6 +9528,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -9476,16 +9541,16 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", "shasum": "" }, "require": { @@ -9532,7 +9597,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" }, "funding": [ { @@ -9543,25 +9608,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-07-08T02:45:35+00:00" }, { "name": "symfony/polyfill-php84", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "000df7860439609837bbe28670b0be15783b7fbf" + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/000df7860439609837bbe28670b0be15783b7fbf", - "reference": "000df7860439609837bbe28670b0be15783b7fbf", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", "shasum": "" }, "require": { @@ -9608,7 +9677,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" }, "funding": [ { @@ -9619,16 +9688,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-20T12:04:08+00:00" + "time": "2025-06-24T13:30:11+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", @@ -9687,7 +9760,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0" }, "funding": [ { @@ -9698,6 +9771,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -9707,16 +9784,16 @@ }, { "name": "symfony/process", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" + "reference": "32241012d521e2e8a9d713adb0812bb773b907f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1", + "reference": "32241012d521e2e8a9d713adb0812bb773b907f1", "shasum": "" }, "require": { @@ -9748,7 +9825,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.0" + "source": "https://github.com/symfony/process/tree/v7.3.3" }, "funding": [ { @@ -9759,25 +9836,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-17T09:11:12+00:00" + "time": "2025-08-18T09:42:54+00:00" }, { "name": "symfony/routing", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "8e213820c5fea844ecea29203d2a308019007c15" + "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/8e213820c5fea844ecea29203d2a308019007c15", - "reference": "8e213820c5fea844ecea29203d2a308019007c15", + "url": "https://api.github.com/repos/symfony/routing/zipball/7614b8ca5fa89b9cd233e21b627bfc5774f586e4", + "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4", "shasum": "" }, "require": { @@ -9829,7 +9910,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.3.0" + "source": "https://github.com/symfony/routing/tree/v7.3.2" }, "funding": [ { @@ -9840,31 +9921,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-24T20:43:28+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/serializer", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "2d86f81b1c506d7e1578789f93280dab4b8411bb" + "reference": "5608b04d8daaf29432d76ecc618b0fac169c2dfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/2d86f81b1c506d7e1578789f93280dab4b8411bb", - "reference": "2d86f81b1c506d7e1578789f93280dab4b8411bb", + "url": "https://api.github.com/repos/symfony/serializer/zipball/5608b04d8daaf29432d76ecc618b0fac169c2dfb", + "reference": "5608b04d8daaf29432d76ecc618b0fac169c2dfb", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "~1.8" + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php84": "^1.30" }, "conflict": { "phpdocumentor/reflection-docblock": "<3.2.2", @@ -9894,7 +9980,7 @@ "symfony/property-access": "^6.4|^7.0", "symfony/property-info": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/type-info": "^7.1", + "symfony/type-info": "^7.1.8", "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "symfony/var-dumper": "^6.4|^7.0", @@ -9927,7 +10013,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v7.3.0" + "source": "https://github.com/symfony/serializer/tree/v7.3.3" }, "funding": [ { @@ -9938,12 +10024,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-12T14:48:23+00:00" + "time": "2025-08-27T11:34:33+00:00" }, { "name": "symfony/service-contracts", @@ -10092,16 +10182,16 @@ }, { "name": "symfony/string", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", + "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", + "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", "shasum": "" }, "require": { @@ -10159,7 +10249,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.0" + "source": "https://github.com/symfony/string/tree/v7.3.3" }, "funding": [ { @@ -10170,25 +10260,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-20T20:19:01+00:00" + "time": "2025-08-25T06:35:40+00:00" }, { "name": "symfony/translation", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "4aba29076a29a3aa667e09b791e5f868973a8667" + "reference": "e0837b4cbcef63c754d89a4806575cada743a38d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/4aba29076a29a3aa667e09b791e5f868973a8667", - "reference": "4aba29076a29a3aa667e09b791e5f868973a8667", + "url": "https://api.github.com/repos/symfony/translation/zipball/e0837b4cbcef63c754d89a4806575cada743a38d", + "reference": "e0837b4cbcef63c754d89a4806575cada743a38d", "shasum": "" }, "require": { @@ -10255,7 +10349,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.3.0" + "source": "https://github.com/symfony/translation/tree/v7.3.3" }, "funding": [ { @@ -10266,12 +10360,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-29T07:19:49+00:00" + "time": "2025-08-01T21:02:37+00:00" }, { "name": "symfony/translation-contracts", @@ -10353,16 +10451,16 @@ }, { "name": "symfony/uid", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "7beeb2b885cd584cd01e126c5777206ae4c3c6a3" + "reference": "a69f69f3159b852651a6bf45a9fdd149520525bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/7beeb2b885cd584cd01e126c5777206ae4c3c6a3", - "reference": "7beeb2b885cd584cd01e126c5777206ae4c3c6a3", + "url": "https://api.github.com/repos/symfony/uid/zipball/a69f69f3159b852651a6bf45a9fdd149520525bb", + "reference": "a69f69f3159b852651a6bf45a9fdd149520525bb", "shasum": "" }, "require": { @@ -10407,7 +10505,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.3.0" + "source": "https://github.com/symfony/uid/tree/v7.3.1" }, "funding": [ { @@ -10423,20 +10521,20 @@ "type": "tidelift" } ], - "time": "2025-05-24T14:28:13+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "548f6760c54197b1084e1e5c71f6d9d523f2f78e" + "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/548f6760c54197b1084e1e5c71f6d9d523f2f78e", - "reference": "548f6760c54197b1084e1e5c71f6d9d523f2f78e", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", + "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", "shasum": "" }, "require": { @@ -10448,7 +10546,6 @@ "symfony/console": "<6.4" }, "require-dev": { - "ext-iconv": "*", "symfony/console": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", "symfony/process": "^6.4|^7.0", @@ -10491,7 +10588,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.0" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.3" }, "funding": [ { @@ -10502,12 +10599,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-27T18:39:23+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "symfony/var-exporter", @@ -10660,16 +10761,16 @@ }, { "name": "tbachert/spi", - "version": "v1.0.3", + "version": "v1.0.5", "source": { "type": "git", "url": "https://github.com/Nevay/spi.git", - "reference": "506a79c98e1a51522e76ee921ccb6c62d52faf3a" + "reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Nevay/spi/zipball/506a79c98e1a51522e76ee921ccb6c62d52faf3a", - "reference": "506a79c98e1a51522e76ee921ccb6c62d52faf3a", + "url": "https://api.github.com/repos/Nevay/spi/zipball/e7078767866d0a9e0f91d3f9d42a832df5e39002", + "reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002", "shasum": "" }, "require": { @@ -10687,7 +10788,7 @@ "extra": { "class": "Nevay\\SPI\\Composer\\Plugin", "branch-alias": { - "dev-main": "0.2.x-dev" + "dev-main": "1.0.x-dev" }, "plugin-optional": true }, @@ -10706,9 +10807,9 @@ ], "support": { "issues": "https://github.com/Nevay/spi/issues", - "source": "https://github.com/Nevay/spi/tree/v1.0.3" + "source": "https://github.com/Nevay/spi/tree/v1.0.5" }, - "time": "2025-04-02T19:38:14+00:00" + "time": "2025-06-29T15:42:06+00:00" }, { "name": "tecnickcom/tcpdf", @@ -13969,7 +14070,10 @@ "ext-json": "*", "ext-openssl": "*", "ext-pdo": "*", - "ext-zlib": "*" + "ext-zlib": "*", + "ext-igbinary": "*", + "ext-redis": "*", + "ext-apcu": "*" }, "platform-dev": {}, "plugin-api-version": "2.6.0" diff --git a/config/opentelemetry.php b/config/opentelemetry.php index 81de53dbb..d79d40af2 100644 --- a/config/opentelemetry.php +++ b/config/opentelemetry.php @@ -16,6 +16,18 @@ */ 'propagators' => env(Variables::OTEL_PROPAGATORS, 'tracecontext'), + /** + * OpenTelemetry Meter configuration + */ + 'metrics' => [ + /** + * Metrics exporter + * This should be the key of one of the exporters defined in the exporters section + * Supported drivers: "otlp", "console", "null" + */ + 'exporter' => env(Variables::OTEL_METRICS_EXPORTER, 'otlp'), + ], + /** * OpenTelemetry Traces configuration */ @@ -78,7 +90,7 @@ /** * OpenTelemetry exporters * - * Here you can configure exports used by traces and logs. + * Here you can configure exports used by metrics, traces and logs. * If you want to use the same protocol with different endpoints, * you can copy the exporter with a different and change the endpoint * @@ -119,6 +131,7 @@ Instrumentation\HttpClientInstrumentation::class => [ 'enabled' => env('OTEL_INSTRUMENTATION_HTTP_CLIENT', true), + 'manual' => false, // When set to true, you need to call `withTrace()` on the request to enable tracing 'allowed_headers' => [], 'sensitive_headers' => [], ], @@ -139,5 +152,10 @@ Instrumentation\ViewInstrumentation::class => env('OTEL_INSTRUMENTATION_VIEW', true), Instrumentation\LivewireInstrumentation::class => env('OTEL_INSTRUMENTATION_LIVEWIRE', true), - ], + + Instrumentation\ConsoleInstrumentation::class => [ + 'enabled' => env('OTEL_INSTRUMENTATION_CONSOLE', true), + 'excluded' => [], + ], + ] ]; diff --git a/docker-compose.yml b/docker-compose.yml index 68218bc6d..bc1e5b55f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -109,9 +109,49 @@ services: env_file: ./.env depends_on: - app + otel-collector: + image: otel/opentelemetry-collector-contrib + volumes: + - ./docker-compose/opentelemetry/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml + - ./docker-compose/opentelemetry/otel-traces.json:/tmp/otel-traces.json + ports: + - 1888:1888 # pprof extension + - 8888:8888 # Prometheus metrics exposed by the Collector + - 8889:8889 # Prometheus exporter metrics + - 13133:13133 # health_check extension + - 4317:4317 # OTLP gRPC receiver + - 4318:4318 # OTLP http receiver + - 55679:55679 # zpages extension + networks: + - summit-api-local-net + depends_on: + - elasticsearch + + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0 + container_name: elasticsearch + environment: + - discovery.type=single-node + - xpack.security.enabled=false + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - cluster.name=summit-api-cluster + - node.name=summit-api-node + ports: + - "9200:9200" + - "9300:9300" + volumes: + - elasticsearch_data:/usr/share/elasticsearch/data + networks: + - summit-api-local-net + healthcheck: + test: [ "CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1" ] + interval: 30s + timeout: 10s + retries: 5 networks: summit-api-local-net: driver: bridge volumes: mysql_summit_api_model: - mysql_summit_api_config: \ No newline at end of file + mysql_summit_api_config: + elasticsearch_data: diff --git a/docker-compose/opentelemetry/.gitignore b/docker-compose/opentelemetry/.gitignore new file mode 100644 index 000000000..e449bb5cb --- /dev/null +++ b/docker-compose/opentelemetry/.gitignore @@ -0,0 +1,39 @@ +/vendor +/node_modules +composer.phar +.DS_Storeapp/storage +.idea/* +app/config/dev/* +app/config/testing/* +app/config/local/* +app/config/production/* +app/config/staging/* +app/config/packages/greggilbert/recaptcha/dev/* +app/config/packages/greggilbert/recaptcha/local/* +app/config/packages/greggilbert/recaptcha/production/* +app/config/packages/greggilbert/recaptcha/staging/* +/bootstrap/compiled.php +/bootstrap/environment.php +.tox +AUTHORS +ChangeLog +doc/build +*.egg +*.egg-info +.env.testing +.env +storage/* +*.log +routes.txt +/storage/logs/ +/ss.sql +phpunit.xml +.phpunit.result.cache +.phpunit.cache/ +docker-compose/mysql/config/*.sql +docker-compose/mysql/model/*.sql +package.xml +.env.dev +rector.php +public/apc.php +docker-compose/opentelemetry/otel-traces.json \ No newline at end of file diff --git a/docker-compose/opentelemetry/otel-collector-config.yaml b/docker-compose/opentelemetry/otel-collector-config.yaml new file mode 100755 index 000000000..74d050717 --- /dev/null +++ b/docker-compose/opentelemetry/otel-collector-config.yaml @@ -0,0 +1,140 @@ +# OpenTelemetry Collector Configuration +# This configuration sets up a basic collector pipeline for traces, metrics, and logs + +receivers: + # OTLP receiver for gRPC and HTTP protocols + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + cors: + allowed_origins: + - "http://localhost" + - "http://localhost:*" + - "https://localhost" + - "https://localhost:*" + allowed_headers: + - "*" + + # Prometheus metrics receiver for scraping metrics + prometheus: + config: + scrape_configs: + - job_name: 'otel-collector' + scrape_interval: 10s + static_configs: + - targets: ['0.0.0.0:8888'] + +processors: + # Batch processor to batch telemetry data before export + batch: + send_batch_size: 1024 + timeout: 1s + send_batch_max_size: 2048 + + # Memory limiter to prevent out-of-memory issues + memory_limiter: + limit_mib: 400 + spike_limit_mib: 100 + check_interval: 1s + + # Resource processor to add/modify resource attributes + resource: + attributes: + - key: service.name + value: summit-api + action: upsert + - key: service.version + value: 1.0.0 + action: upsert + - key: deployment.environment + value: development + action: upsert + + # Attributes processor to modify span/metric attributes + attributes: + actions: + - key: environment + value: development + action: upsert + +exporters: + # Debug exporter for debugging - outputs to stdout + debug: + verbosity: normal + sampling_initial: 5 + sampling_thereafter: 200 + + # Prometheus exporter for metrics + prometheus: + endpoint: "0.0.0.0:8889" + namespace: summit_api + const_labels: + environment: development + + # File exporter for traces (useful for debugging) + file: + path: /tmp/otel-traces.json + + # Elasticsearch exporter for storing traces, metrics, and logs + elasticsearch: + endpoints: [http://elasticsearch:9200] + # Authentication (uncomment if needed) + # auth: + # authenticator: basicauth/elastic + logs_index: otel-logs + traces_index: otel-traces + metrics_index: otel-metrics + # Mapping settings for better performance + mapping: + mode: ecs # Use Elastic Common Schema + +extensions: + # Health check extension + health_check: + endpoint: 0.0.0.0:13133 + + # pprof extension for performance profiling + pprof: + endpoint: 0.0.0.0:1888 + + # ZPages extension for debugging + zpages: + endpoint: 0.0.0.0:55679 + + # Basic auth extension for Elasticsearch (uncomment if needed) + # basicauth/elastic: + # client_auth: + # username: elastic + # password: changeme + +service: + extensions: [health_check, pprof, zpages] + + pipelines: + # Traces pipeline + traces: + receivers: [otlp] + processors: [memory_limiter, resource, attributes, batch] + exporters: [debug, file, elasticsearch] + + # Metrics pipeline + metrics: + receivers: [otlp] + processors: [memory_limiter, resource, batch] + exporters: [debug] + + # Logs pipeline + logs: + receivers: [otlp] + processors: [memory_limiter, resource, attributes, batch] + exporters: [debug, file] + + # Telemetry configuration for the collector itself + telemetry: + logs: + level: "info" + metrics: + level: basic diff --git a/docker-compose/opentelemetry/otel-traces.json b/docker-compose/opentelemetry/otel-traces.json new file mode 100644 index 000000000..e69de29bb diff --git a/phpunit.xml b/phpunit.xml index 51f3bf8a1..a2b78a0b5 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -3,6 +3,12 @@ ./tests/ + ./tests/OpenTelemetry/ + ./tests/TestCase.php + + + ./tests/OpenTelemetry/ + ./tests/OpenTelemetry/ExampleTest.php diff --git a/readme.md b/readme.md index 9eddb4338..b31bc3a36 100644 --- a/readme.md +++ b/readme.md @@ -79,4 +79,22 @@ php artisan queue:work php artisan audit:purge-log _SUMMIT_ID_ _DATE_BACKWARD_FROM_ - SUMMIT_ID: Summit id to clear audit log from -- DATE_BACKWARD_FROM: Maximum date to delete starting from the beginning \ No newline at end of file +- DATE_BACKWARD_FROM: Maximum date to delete starting from the beginning + +## OpenTelemetry Observability + +This application includes OpenTelemetry instrumentation for distributed tracing and monitoring. + +### Quick Setup +```bash +# Enable in .env +OTEL_SERVICE_ENABLED=true +OTEL_SERVICE_NAME=summit-api +OTEL_INSTRUMENTATION_GUZZLE=true + +# Start collector and Elasticsearch +docker compose up -d otel-collector elasticsearch + +# View traces +curl http://localhost:55679/debug/tracez +``` \ No newline at end of file diff --git a/tests/OpenTelemetry/.gitkeep b/tests/OpenTelemetry/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/tests/OpenTelemetry/ExampleTest.php b/tests/OpenTelemetry/ExampleTest.php new file mode 100644 index 000000000..6491fdc48 --- /dev/null +++ b/tests/OpenTelemetry/ExampleTest.php @@ -0,0 +1,17 @@ +assertTrue(true); + } +} diff --git a/tests/OpenTelemetry/GuzzleTracingTest.php b/tests/OpenTelemetry/GuzzleTracingTest.php new file mode 100644 index 000000000..295236c7a --- /dev/null +++ b/tests/OpenTelemetry/GuzzleTracingTest.php @@ -0,0 +1,374 @@ +capturedRequests = []; + } + + protected function tearDown(): void + { + Mockery::close(); + parent::tearDown(); + } + + /** + * Test that verifies the GuzzleTrace middleware works correctly + * Simulates real behavior without depending on global OpenTelemetry configuration + * + * @return void + */ + public function testGuzzleTraceMiddlewareAddsHeaders(): void + { + // Configure baggage with cf-ray simulating the input middleware + $cfRayValue = '8a2e036cae2822-SJC'; + $userAgent = 'TestUserAgent/1.0'; + + // Create an active span to have tracing context + $tracer = $this->app->make(TracerInterface::class); + $span = $tracer->spanBuilder('test-http-request')->startSpan(); + $spanScope = $span->activate(); + + try { + // Configure baggage in the active span context + $baggage = Baggage::getCurrent() + ->toBuilder() + ->set('cf-ray', $cfRayValue) + ->set('user_agent', $userAgent) + ->build(); + + $baggageScope = $baggage->activate(); + + try { + // Create a mock handler to capture requests + $mockHandler = new MockHandler([ + new Response(200, [], '{"success": true}') + ]); + + // Middleware to capture sent headers + $historyMiddleware = Middleware::history($this->capturedRequests); + + // Create handler stack with tracing middleware + $handlerStack = HandlerStack::create($mockHandler); + + // IMPORTANT: First add tracing middleware, THEN history + // This ensures history captures the request after headers are added + $handlerStack->push(GuzzleTraceMiddleware::make()); + + // History middleware goes AFTER to capture the final request with headers + $handlerStack->push($historyMiddleware); // Create Guzzle client + $client = new Client(['handler' => $handlerStack]); + + // Make the request + $response = $client->post('http://example.com/api/test', [ + 'json' => ['test' => 'data'], + 'headers' => [ + 'X-Custom-Header' => 'custom-value' + ] + ]); + + // Verify the response was successful + $this->assertEquals(200, $response->getStatusCode()); + + // Verify exactly one request was captured + $this->assertCount(1, $this->capturedRequests); + + $transaction = $this->capturedRequests[0]; + $request = $transaction['request']; + $headers = []; + + // Convert headers to associative array for easy verification + foreach ($request->getHeaders() as $name => $values) { + $headers[strtolower($name)] = $values; + } + + // Verify tracing headers were added + $this->assertTrue(isset($headers['traceparent']), "Request must have traceparent header"); + $this->assertTrue(isset($headers['baggage']), "Request must have baggage header"); + + // If there's baggage, verify it contains cf-ray + if (isset($headers['baggage'])) { + $baggageHeader = $headers['baggage'][0]; + $this->assertStringContainsString( + "cf-ray=$cfRayValue", + $baggageHeader, + 'Baggage header must contain cf-ray value' + ); + } + + // Verify custom headers are also present + $this->assertArrayHasKey('x-custom-header', $headers); + $this->assertEquals('custom-value', $headers['x-custom-header'][0]); + + } finally { + $baggageScope->detach(); + } + } finally { + $span->end(); + $spanScope->detach(); + } + } + + /** + * Test that verifies behavior when no baggage is configured + * + * @return void + */ + public function testGuzzleWithoutBaggage(): void + { + // Create an active span but WITHOUT configured baggage + $tracer = $this->app->make(TracerInterface::class); + $span = $tracer->spanBuilder('test-without-baggage')->startSpan(); + $spanScope = $span->activate(); + + try { + // Create a mock handler + $mockHandler = new MockHandler([ + new Response(200, [], '{"success": true}') + ]); + + // Middleware to capture sent headers + $historyMiddleware = Middleware::history($this->capturedRequests); + + // Create handler stack + $handlerStack = HandlerStack::create($mockHandler); + + // Important order: trace middleware first, then history + $handlerStack->push(GuzzleTraceMiddleware::make()); + $handlerStack->push($historyMiddleware); + + // Create Guzzle client + $client = new Client(['handler' => $handlerStack]); + + // Make request without configured baggage + $response = $client->get('http://example.com/api/test'); + + // Verify response was successful + $this->assertEquals(200, $response->getStatusCode()); + + // Verify request was captured + $this->assertCount(1, $this->capturedRequests); + + $transaction = $this->capturedRequests[0]; + $request = $transaction['request']; + $headers = []; + + foreach ($request->getHeaders() as $name => $values) { + $headers[strtolower($name)] = $values; + } + + // Must have some tracing header + $this->assertTrue(isset($headers['traceparent']), "Request must have traceparent header"); + + // Should not have baggage with cf-ray if not configured + if (isset($headers['baggage'])) { + $baggageHeader = $headers['baggage'][0]; + $this->assertStringNotContainsString('cf-ray=', $baggageHeader); + } + } finally { + $span->end(); + $spanScope->detach(); + } + } + + /** + * Test that verifies behavior with different cf-ray values + */ + #[\PHPUnit\Framework\Attributes\DataProvider('cfRayProvider')] + public function testGuzzleWithDifferentCfRayValues(string $cfRayValue): void + { + // Create an active span with specific baggage for this cf-ray + $tracer = $this->app->make(TracerInterface::class); + $span = $tracer->spanBuilder('test-different-cfray')->startSpan(); + $spanScope = $span->activate(); + + try { + // Configure baggage with specific cf-ray + $baggage = Baggage::getCurrent() + ->toBuilder() + ->set('cf-ray', $cfRayValue) + ->set('user_agent', 'test-agent') + ->build(); + + $baggageScope = $baggage->activate(); + + try { + $mockHandler = new MockHandler([ + new Response(200, [], '{"success": true}') + ]); + + $historyMiddleware = Middleware::history($this->capturedRequests); + $handlerStack = HandlerStack::create($mockHandler); + + // Important order: trace middleware first, then history + $handlerStack->push(GuzzleTraceMiddleware::make()); + $handlerStack->push($historyMiddleware); + + $client = new Client(['handler' => $handlerStack]); + + $response = $client->post('http://example.com/api/test', [ + 'json' => ['cf_ray_test' => $cfRayValue] + ]); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertCount(1, $this->capturedRequests); + + $transaction = $this->capturedRequests[0]; + $request = $transaction['request']; + $headers = []; + + foreach ($request->getHeaders() as $name => $values) { + $headers[strtolower($name)] = $values; + } + + // Verify tracing headers + $this->assertTrue(isset($headers['traceparent']), "Request must have traceparent header"); + $this->assertTrue(isset($headers['baggage']), "Request must have baggage header"); + + // If there's baggage, verify it contains the correct cf-ray + if (isset($headers['baggage']) && !empty($cfRayValue)) { + $baggageHeader = $headers['baggage'][0]; + $this->assertStringContainsString( + "cf-ray=$cfRayValue", + $baggageHeader, + "Baggage header must contain cf-ray value: $cfRayValue" + ); + } + } finally { + $baggageScope->detach(); + } + } finally { + $span->end(); + $spanScope->detach(); + } + } + + /** + * Data provider for different cf-ray values + */ + public static function cfRayProvider(): array + { + return [ + 'cf-ray standard format' => ['8a2e036cae2822-SJC'], + 'cf-ray different datacenter' => ['7f1b025bdf1733-LAX'], + 'cf-ray another format' => ['9c3f047def3944-LHR'], + 'cf-ray with different hex' => ['ab4e158efg4055-CDG'], + ]; + } + + /** + * Test that verifies multiple requests maintain tracing context + */ + public function testMultipleRequestsWithTracing(): void + { + $cfRayValue = '8a2e036cae2822-SJC'; + + // Create an active span with baggage for all requests + $tracer = $this->app->make(TracerInterface::class); + $span = $tracer->spanBuilder('test-multiple-requests')->startSpan(); + $spanScope = $span->activate(); + + try { + // Configure baggage for multiple requests + $baggage = Baggage::getCurrent() + ->toBuilder() + ->set('cf-ray', $cfRayValue) + ->set('user_agent', 'test-agent') + ->build(); + + $baggageScope = $baggage->activate(); + + try { + // Configure mock for multiple responses + $mockHandler = new MockHandler([ + new Response(200, [], '{"request": 1}'), + new Response(200, [], '{"request": 2}'), + new Response(200, [], '{"request": 3}') + ]); + + $historyMiddleware = Middleware::history($this->capturedRequests); + $handlerStack = HandlerStack::create($mockHandler); + + // Important order: trace middleware first, then history + $handlerStack->push(GuzzleTraceMiddleware::make()); + $handlerStack->push($historyMiddleware); + + $client = new Client(['handler' => $handlerStack]); + + // Make multiple requests + for ($i = 1; $i <= 3; $i++) { + $response = $client->get("http://example.com/api/test/$i"); + $this->assertEquals(200, $response->getStatusCode()); + } + + // Verify all requests were captured + $this->assertCount(3, $this->capturedRequests); + + // Verify all requests have tracing headers + foreach ($this->capturedRequests as $index => $transaction) { + $request = $transaction['request']; + $headers = []; + + foreach ($request->getHeaders() as $name => $values) { + $headers[strtolower($name)] = $values; + } + + // All must have at least tracing headers + $this->assertTrue(isset($headers['traceparent']), "Request $index must have traceparent header"); + $this->assertTrue(isset($headers['baggage']), "Request $index must have baggage header"); + + // Verify baggage contains cf-ray in all requests + if (isset($headers['baggage'])) { + $baggageHeader = $headers['baggage'][0]; + $this->assertStringContainsString( + "cf-ray=$cfRayValue", + $baggageHeader, + "Request $index must have cf-ray in baggage" + ); + } + } + } finally { + $baggageScope->detach(); + } + } finally { + $span->end(); + $spanScope->detach(); + } + } + + /** + * Creates a custom middleware for testing that simulates tracing middleware behavior + */ + private function createCustomTraceMiddleware(string $cfRayValue, string $userAgent): callable + { + return function (callable $handler) use ($cfRayValue, $userAgent) { + return function ($request, array $options) use ($handler, $cfRayValue, $userAgent) { + // Simulate adding tracing headers + $request = $request->withHeader('traceparent', '00-' . str_repeat('0', 32) . '-' . str_repeat('0', 16) . '-01'); + $request = $request->withHeader('baggage', "cf-ray=$cfRayValue,user_agent=" . urlencode($userAgent)); + + return $handler($request, $options); + }; + }; + } +} diff --git a/tests/OpenTelemetry/OpenTelemetryTestCase.php b/tests/OpenTelemetry/OpenTelemetryTestCase.php new file mode 100644 index 000000000..bf9aff8ad --- /dev/null +++ b/tests/OpenTelemetry/OpenTelemetryTestCase.php @@ -0,0 +1,86 @@ +setOpenTelemetryEnvironmentVariables(); + + parent::setUp(); + + // Register the OpenTelemetry testing ServiceProvider + $this->app->register(OpenTelemetryTestingServiceProvider::class); + + // Enable OpenTelemetry in testing environment + config(['opentelemetry.enabled' => true]); + + // Configure necessary instrumentations + config([ + 'opentelemetry.instrumentation' => [ + HttpClientInstrumentation::class => [ + 'enabled' => true, + 'manual' => false, + 'allowed_headers' => [], + 'sensitive_headers' => [], + ], + ] + ]); + } + + /** + * Set OpenTelemetry environment variables for testing + */ + protected function setOpenTelemetryEnvironmentVariables(): void + { + putenv('OTEL_SERVICE_ENABLED=true'); + putenv('OTEL_SERVICE_NAME=summit-api-test'); + putenv('OTEL_PROPAGATORS=tracecontext,baggage'); + putenv('OTEL_INSTRUMENTATION_HTTP_CLIENT=true'); + putenv('OTEL_INSTRUMENTATION_REDIS=false'); + putenv('OTEL_INSTRUMENTATION_QUERY=false'); + putenv('OTEL_INSTRUMENTATION_QUEUE=false'); + putenv('OTEL_INSTRUMENTATION_CACHE=false'); + putenv('OTEL_INSTRUMENTATION_EVENT=false'); + putenv('OTEL_INSTRUMENTATION_VIEW=false'); + putenv('OTEL_INSTRUMENTATION_CONSOLE=false'); + putenv('OTEL_TRACES_EXPORTER=none'); + putenv('OTEL_METRICS_EXPORTER=none'); + } + + protected function tearDown(): void + { + // Clean up environment variables + $this->cleanupOpenTelemetryEnvironmentVariables(); + parent::tearDown(); + } + + /** + * Clean up OpenTelemetry environment variables after testing + */ + protected function cleanupOpenTelemetryEnvironmentVariables(): void + { + putenv('OTEL_SERVICE_ENABLED'); + putenv('OTEL_SERVICE_NAME'); + putenv('OTEL_PROPAGATORS'); + putenv('OTEL_INSTRUMENTATION_HTTP_CLIENT'); + putenv('OTEL_INSTRUMENTATION_REDIS'); + putenv('OTEL_INSTRUMENTATION_QUERY'); + putenv('OTEL_INSTRUMENTATION_QUEUE'); + putenv('OTEL_INSTRUMENTATION_CACHE'); + putenv('OTEL_INSTRUMENTATION_EVENT'); + putenv('OTEL_INSTRUMENTATION_VIEW'); + putenv('OTEL_INSTRUMENTATION_CONSOLE'); + putenv('OTEL_TRACES_EXPORTER'); + putenv('OTEL_METRICS_EXPORTER'); + } +} diff --git a/tests/OpenTelemetry/README.md b/tests/OpenTelemetry/README.md new file mode 100644 index 000000000..4706c151b --- /dev/null +++ b/tests/OpenTelemetry/README.md @@ -0,0 +1,93 @@ +# OpenTelemetry Test Suite for Guzzle HTTP Client + +This test suite verifies that OpenTelemetry works correctly with the Guzzle HTTP client to propagate tracing headers, including Cloudflare's `Cf-Ray` header. + +## Overview + +The tests verify that the `GuzzleTraceMiddleware` works correctly by: +1. **Adding OpenTelemetry tracing headers**: Verifies that `traceparent` and `baggage` headers are automatically added to outgoing HTTP requests. +2. **Propagating baggage context**: When `baggage` contains `cf-ray` and `user-agent` values, they are correctly propagated in the `baggage` header of outgoing requests. +3. **Handling requests without baggage**: Ensures the middleware still adds tracing headers (`traceparent`) even when no baggage is configured. +4. **Supporting different `cf-ray` formats**: Tests with various `cf-ray` value formats to ensure compatibility. +5. **Maintaining context across multiple requests**: Verifies that tracing context is preserved across sequential HTTP requests within the same span. + +## Created Files + +### Tests +- `tests/OpenTelemetry/GuzzleTracingTest.php`: Unit tests that mock HTTP requests to verify middleware behavior + +### Support +- `tests/OpenTelemetry/OpenTelemetryTestCase.php`: Base test case that sets up OpenTelemetry environment and registers the testing ServiceProvider +- `tests/Support/OpenTelemetryTestingServiceProvider.php`: ServiceProvider to configure OpenTelemetry in testing environment +- `tests/OpenTelemetry/Traits/SetsUpOpenTelemetryEnvironment.php`: Trait for environment setup in tests that don't extend the base case + +## Testing Environment Configuration + +### OpenTelemetryTestCase.php +The base test case (`tests/OpenTelemetry/OpenTelemetryTestCase.php`) programmatically sets up OpenTelemetry environment variables: +```php +putenv('OTEL_SERVICE_ENABLED=true'); +putenv('OTEL_SERVICE_NAME=summit-api-test'); +putenv('OTEL_PROPAGATORS=tracecontext,baggage'); +putenv('OTEL_INSTRUMENTATION_HTTP_CLIENT=true'); +// ... more OTEL variables ... +``` + +### Alternative: SetsUpOpenTelemetryEnvironment Trait +Available trait (`tests/OpenTelemetry/Traits/SetsUpOpenTelemetryEnvironment.php`) provides the same environment setup for tests that don't extend `OpenTelemetryTestCase`. + +### OpenTelemetryTestingServiceProvider +The ServiceProvider (`tests/Support/OpenTelemetryTestingServiceProvider.php`) is automatically registered by `OpenTelemetryTestCase` and provides: +- `TracerInterface` binding with NoopSpanProcessor for testing +- Propagators configuration (TraceContext and Baggage) +- Laravel OpenTelemetry `Tracer` facade integration + +The tests use `$this->app->make(TracerInterface::class)` to get the tracer instance configured by this ServiceProvider. + +## Running Tests + +```bash +# Run entire OTEL test suite +docker-compose exec app vendor/bin/phpunit --testsuite="OTEL" + +# Run only Guzzle tracing tests +docker-compose exec app vendor/bin/phpunit tests/OpenTelemetry/GuzzleTracingTest.php + +# Run a specific test +docker-compose exec app vendor/bin/phpunit --filter="testGuzzleTraceMiddlewareAddsHeaders" +``` + +## Included Tests + +1. **testGuzzleTraceMiddlewareAddsHeaders**: Main test that verifies the middleware adds both `traceparent` and `baggage` headers, and that baggage correctly contains `cf-ray` and `user-agent` values from the active baggage context. +2. **testGuzzleWithoutBaggage**: Verifies that when no baggage is configured, the middleware still adds `traceparent` headers but doesn't include `cf-ray` in baggage. +3. **testGuzzleWithDifferentCfRayValues**: Uses a data provider to test the middleware with various `cf-ray` value formats (different datacenters and hex values). +4. **testMultipleRequestsWithTracing**: Verifies that tracing context and baggage are maintained across multiple sequential HTTP requests within the same span. + +## Test Implementation Details + +The tests use mocked HTTP handlers to simulate real HTTP requests: +- **MockHandler**: Provides predefined responses without making actual HTTP calls +- **History Middleware**: Captures outgoing requests with their headers for verification +- **Active Spans**: Creates OpenTelemetry spans to provide the required tracing context +- **Baggage Configuration**: Manually sets baggage values (`cf-ray`, `user-agent`) to simulate real middleware behavior + +## Test Verifications + +Each test performs the following verifications: +- **Header Presence**: Confirms that `traceparent` headers are added to all outgoing requests +- **Baggage Propagation**: When baggage is configured, verifies that `baggage` headers contain the expected `cf-ray` values +- **Header Format**: Validates that baggage headers follow the correct format (`cf-ray`=value,`user-agent`=value) +- **Context Isolation**: Ensures that tests without baggage don't leak `cf-ray` values into headers +- **Custom Headers Preserved**: Verifies that existing request headers are maintained alongside tracing headers +- **Mock Response Handling**: Confirms that mocked responses are properly returned + +## Technical Implementation Notes + +- **Middleware Order**: Critical that `GuzzleTraceMiddleware` is added BEFORE the History middleware to ensure headers are captured after being added by the trace middleware +- **Active Span Requirement**: An active OpenTelemetry span must be present for the `GuzzleTraceMiddleware` to function properly +- **Baggage Scope Management**: Baggage must be activated and properly scoped to ensure values are available during request processing +- **MockHandler Setup**: Tests use `MockHandler` with predefined responses to avoid actual HTTP calls while testing middleware behavior +- **ServiceProvider Integration**: The test suite uses `OpenTelemetryTestingServiceProvider` to configure TracerInterface and propagators in the test environment + +These tests verify that the `GuzzleTraceMiddleware` correctly integrates with OpenTelemetry's tracing system to add proper headers to outgoing HTTP requests. diff --git a/tests/OpenTelemetry/Support/OpenTelemetryTestingServiceProvider.php b/tests/OpenTelemetry/Support/OpenTelemetryTestingServiceProvider.php new file mode 100644 index 000000000..730a5c9e5 --- /dev/null +++ b/tests/OpenTelemetry/Support/OpenTelemetryTestingServiceProvider.php @@ -0,0 +1,94 @@ +registerTracerProvider(); + $this->registerPropagators(); + } + + /** + * Bootstrap services. + */ + public function boot(): void + { + $this->app->bind(TracerInterface::class, function ($app) { + /** @var TracerProvider $tracerProvider */ + $tracerProvider = $app->make(TracerProvider::class); + return $tracerProvider->getTracer('test-tracer', '1.0.0'); + }); + } + + /** + * Register the TraceProvider for testing + */ + protected function registerTracerProvider(): void + { + $this->app->singleton(TracerProvider::class, function () { + $resource = ResourceInfoFactory::emptyResource(); + + return TracerProvider::builder() + ->addSpanProcessor(new NoopSpanProcessor()) + ->setResource($resource) + ->build(); + }); + + // Registrar el Tracer principal que usa el facade + $this->app->singleton(Tracer::class, function ($app) { + return new Tracer( + $app->make(TracerInterface::class), + $app->make(TextMapPropagatorInterface::class) + ); + }); + } + + /** + * Register propagators for testing + */ + protected function registerPropagators(): void + { + $this->app->singleton(TextMapPropagatorInterface::class, function () { + return new MultiTextMapPropagator([ + new TraceContextPropagator(), + new BaggagePropagator(), + ]); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return [ + TracerProvider::class, + TracerInterface::class, + \Keepsuit\LaravelOpenTelemetry\Tracer::class, + TextMapPropagatorInterface::class, + ]; + } +} diff --git a/tests/OpenTelemetry/Traits/SetsUpOpenTelemetryEnvironment.php b/tests/OpenTelemetry/Traits/SetsUpOpenTelemetryEnvironment.php new file mode 100644 index 000000000..2abb66c12 --- /dev/null +++ b/tests/OpenTelemetry/Traits/SetsUpOpenTelemetryEnvironment.php @@ -0,0 +1,49 @@ +