diff --git a/backend/composer.json b/backend/composer.json index e75900c..47737a0 100644 --- a/backend/composer.json +++ b/backend/composer.json @@ -58,7 +58,7 @@ "symfony/messenger": "^8.0.8", "symfony/mime": "^8.0.8", "symfony/monolog-bridge": "^8.0.8", - "symfony/monolog-bundle": "^4.0.1", + "symfony/monolog-bundle": "^4.0.2", "symfony/options-resolver": "^8.0.8", "symfony/password-hasher": "^8.0.8", "symfony/process": "^8.0.8", @@ -91,7 +91,7 @@ "laudis/neo4j-php-client": "^3.4.3", "phpdocumentor/reflection": "^6.4.4", "phpstan/phpstan": "^2.1.46", - "phpunit/phpunit": "^13.0.6", + "phpunit/phpunit": "^13.1.0", "symfony/browser-kit": "^8.0.8", "symfony/css-selector": "^8.0.8", "symfony/maker-bundle": "^1.67.0", diff --git a/backend/composer.lock b/backend/composer.lock index 6a4a16f..c6906d9 100644 --- a/backend/composer.lock +++ b/backend/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": "86208a5f6aa6243d1d6b0f8c131fcaea", + "content-hash": "5952f3761198a2d6ebafa4ca9afb2b7b", "packages": [ { "name": "brick/math", @@ -5234,16 +5234,16 @@ }, { "name": "sebastian/diff", - "version": "8.0.0", + "version": "8.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "a2b6d09d7729ee87d605a439469f9dcc39be5ea3" + "reference": "9c957d730257f49c873f3761674559bd90098a7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/a2b6d09d7729ee87d605a439469f9dcc39be5ea3", - "reference": "a2b6d09d7729ee87d605a439469f9dcc39be5ea3", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/9c957d730257f49c873f3761674559bd90098a7d", + "reference": "9c957d730257f49c873f3761674559bd90098a7d", "shasum": "" }, "require": { @@ -5256,7 +5256,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "8.0-dev" + "dev-main": "8.1-dev" } }, "autoload": { @@ -5289,7 +5289,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/8.0.0" + "source": "https://github.com/sebastianbergmann/diff/tree/8.1.0" }, "funding": [ { @@ -5309,7 +5309,7 @@ "type": "tidelift" } ], - "time": "2026-02-06T04:42:27+00:00" + "time": "2026-04-05T12:02:33+00:00" }, { "name": "squizlabs/php_codesniffer", @@ -7867,16 +7867,16 @@ }, { "name": "symfony/monolog-bundle", - "version": "v4.0.1", + "version": "v4.0.2", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bundle.git", - "reference": "3b4ee2717ee56c5e1edb516140a175eb2a72bc66" + "reference": "c012c6aba13129eb02aa7dd61e66e720911d8598" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/3b4ee2717ee56c5e1edb516140a175eb2a72bc66", - "reference": "3b4ee2717ee56c5e1edb516140a175eb2a72bc66", + "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/c012c6aba13129eb02aa7dd61e66e720911d8598", + "reference": "c012c6aba13129eb02aa7dd61e66e720911d8598", "shasum": "" }, "require": { @@ -7922,7 +7922,7 @@ ], "support": { "issues": "https://github.com/symfony/monolog-bundle/issues", - "source": "https://github.com/symfony/monolog-bundle/tree/v4.0.1" + "source": "https://github.com/symfony/monolog-bundle/tree/v4.0.2" }, "funding": [ { @@ -7942,7 +7942,7 @@ "type": "tidelift" } ], - "time": "2025-12-08T08:00:13+00:00" + "time": "2026-04-02T18:27:21+00:00" }, { "name": "symfony/options-resolver", @@ -12079,16 +12079,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "13.0.2", + "version": "14.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2ea1bcdad040326c02edd6519cc9d1c5a9f6c87e" + "reference": "24f1d7300e54e910197ee65e83d27a1e4bdc917e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2ea1bcdad040326c02edd6519cc9d1c5a9f6c87e", - "reference": "2ea1bcdad040326c02edd6519cc9d1c5a9f6c87e", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/24f1d7300e54e910197ee65e83d27a1e4bdc917e", + "reference": "24f1d7300e54e910197ee65e83d27a1e4bdc917e", "shasum": "" }, "require": { @@ -12099,13 +12099,14 @@ "php": ">=8.4", "phpunit/php-text-template": "^6.0", "sebastian/complexity": "^6.0", - "sebastian/environment": "^9.0", + "sebastian/environment": "^9.1", + "sebastian/git-state": "^1.0", "sebastian/lines-of-code": "^5.0", "sebastian/version": "^7.0", "theseer/tokenizer": "^2.0.1" }, "require-dev": { - "phpunit/phpunit": "^13.0" + "phpunit/phpunit": "^13.1" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -12114,7 +12115,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "13.0.x-dev" + "dev-main": "14.0.x-dev" } }, "autoload": { @@ -12143,7 +12144,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/13.0.2" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/14.0.0" }, "funding": [ { @@ -12163,7 +12164,7 @@ "type": "tidelift" } ], - "time": "2026-04-01T14:12:38+00:00" + "time": "2026-04-03T05:11:05+00:00" }, { "name": "phpunit/php-file-iterator", @@ -12460,16 +12461,16 @@ }, { "name": "phpunit/phpunit", - "version": "13.0.6", + "version": "13.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "9e426f7282c313c9138eeb9f25461e1a6be1e647" + "reference": "97f27488f84718f8e7f9a2a31b5ca9f20698b06f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9e426f7282c313c9138eeb9f25461e1a6be1e647", - "reference": "9e426f7282c313c9138eeb9f25461e1a6be1e647", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/97f27488f84718f8e7f9a2a31b5ca9f20698b06f", + "reference": "97f27488f84718f8e7f9a2a31b5ca9f20698b06f", "shasum": "" }, "require": { @@ -12483,7 +12484,7 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.4.1", - "phpunit/php-code-coverage": "^13.0.1", + "phpunit/php-code-coverage": "^14.0", "phpunit/php-file-iterator": "^7.0.0", "phpunit/php-invoker": "^7.0.0", "phpunit/php-text-template": "^6.0.0", @@ -12493,6 +12494,7 @@ "sebastian/diff": "^8.0.0", "sebastian/environment": "^9.1.0", "sebastian/exporter": "^8.0.0", + "sebastian/git-state": "^1.0", "sebastian/global-state": "^9.0.0", "sebastian/object-enumerator": "^8.0.0", "sebastian/recursion-context": "^8.0.0", @@ -12506,7 +12508,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "13.0-dev" + "dev-main": "13.1-dev" } }, "autoload": { @@ -12538,7 +12540,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/13.0.6" + "source": "https://github.com/sebastianbergmann/phpunit/tree/13.1.0" }, "funding": [ { @@ -12546,7 +12548,7 @@ "type": "other" } ], - "time": "2026-03-31T06:44:39+00:00" + "time": "2026-04-03T05:29:00+00:00" }, { "name": "psr/simple-cache", @@ -12670,16 +12672,16 @@ }, { "name": "sebastian/comparator", - "version": "8.0.0", + "version": "8.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "29b232ddc29c2b114c0358c69b3084e7c3da0d58" + "reference": "e35cd9e8bbc43142bc7d6db53ceb2b01c6ad95a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/29b232ddc29c2b114c0358c69b3084e7c3da0d58", - "reference": "29b232ddc29c2b114c0358c69b3084e7c3da0d58", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e35cd9e8bbc43142bc7d6db53ceb2b01c6ad95a3", + "reference": "e35cd9e8bbc43142bc7d6db53ceb2b01c6ad95a3", "shasum": "" }, "require": { @@ -12698,7 +12700,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "8.0-dev" + "dev-main": "8.1-dev" } }, "autoload": { @@ -12738,7 +12740,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/8.0.0" + "source": "https://github.com/sebastianbergmann/comparator/tree/8.1.0" }, "funding": [ { @@ -12758,7 +12760,7 @@ "type": "tidelift" } ], - "time": "2026-02-06T04:40:39+00:00" + "time": "2026-04-06T11:57:51+00:00" }, { "name": "sebastian/complexity", @@ -12832,16 +12834,16 @@ }, { "name": "sebastian/environment", - "version": "9.1.0", + "version": "9.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "c4a2dc54b1a24e13ef1839cbb5947b967cbae853" + "reference": "c0964f624fcac84e318fc9ef0193cbb9809a331a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/c4a2dc54b1a24e13ef1839cbb5947b967cbae853", - "reference": "c4a2dc54b1a24e13ef1839cbb5947b967cbae853", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/c0964f624fcac84e318fc9ef0193cbb9809a331a", + "reference": "c0964f624fcac84e318fc9ef0193cbb9809a331a", "shasum": "" }, "require": { @@ -12856,7 +12858,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "9.1-dev" + "dev-main": "9.2-dev" } }, "autoload": { @@ -12884,7 +12886,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/9.1.0" + "source": "https://github.com/sebastianbergmann/environment/tree/9.2.0" }, "funding": [ { @@ -12904,7 +12906,7 @@ "type": "tidelift" } ], - "time": "2026-03-22T06:31:50+00:00" + "time": "2026-04-05T07:07:20+00:00" }, { "name": "sebastian/exporter", @@ -12996,6 +12998,75 @@ ], "time": "2026-02-06T04:44:28+00:00" }, + { + "name": "sebastian/git-state", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/git-state.git", + "reference": "792a952e0eba55b6960a48aeceb9f371aad1f76b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/git-state/zipball/792a952e0eba55b6960a48aeceb9f371aad1f76b", + "reference": "792a952e0eba55b6960a48aeceb9f371aad1f76b", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for describing the state of a Git checkout", + "homepage": "https://github.com/sebastianbergmann/git-state", + "support": { + "issues": "https://github.com/sebastianbergmann/git-state/issues", + "security": "https://github.com/sebastianbergmann/git-state/security/policy", + "source": "https://github.com/sebastianbergmann/git-state/tree/1.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/git-state", + "type": "tidelift" + } + ], + "time": "2026-03-21T12:54:28+00:00" + }, { "name": "sebastian/global-state", "version": "9.0.0", diff --git a/backend/config/reference.php b/backend/config/reference.php index 8ebd03c..142dfb7 100644 --- a/backend/config/reference.php +++ b/backend/config/reference.php @@ -1134,7 +1134,7 @@ * name?: scalar|Param|null, * description?: scalar|Param|null, * openIdConnectUrl?: scalar|Param|null, - * ... + * ... * }>, * with_attribute?: bool|Param, // whether to filter by attributes // Default: false * disable_default_routes?: bool|Param, // if set disables default routes without attributes // Default: false @@ -1200,7 +1200,7 @@ * use_underscore?: bool|Param, // Default: true * unordered_list_markers?: list, * }, - * ... + * ... * }, * } * @psalm-type SecurityConfig = array{ diff --git a/backend/deptrac.yaml b/backend/deptrac.yaml index 16edce1..cd68a2f 100644 --- a/backend/deptrac.yaml +++ b/backend/deptrac.yaml @@ -47,6 +47,8 @@ deptrac: value: .*Symfony\\.* - name: Supporting collectors: + - type: classLike + value: .*App\\Async\\.* - type: classLike value: .*App\\Auth\\.* - type: classLike @@ -72,8 +74,6 @@ deptrac: value: .*App\\Instrumentation\\.* - type: classLike value: .*App\\Listener\\.* - - type: classLike - value: .*App\\Message\\.* - type: classLike value: .*App\\Repository\\.* - name: Tests diff --git a/backend/src/Async/EventPersisted.php b/backend/src/Async/EventPersisted.php new file mode 100644 index 0000000..dd0a39c --- /dev/null +++ b/backend/src/Async/EventPersisted.php @@ -0,0 +1,46 @@ + $traceContext */ + private function __construct( + public Event $event, + public float $createdAt, + public array $traceContext, + ) { + } + + public static function fromEvent(Event $event): self + { + return new self( + $event, + microtime(true), + self::provideTraceContext() + ); + } + + /** @return array */ + private static function provideTraceContext(): array + { + $carrier = []; + + TraceContextPropagator::getInstance()->inject($carrier); + assert(is_array($carrier)); + + foreach ($carrier as $key => $value) { + assert(is_string($key)); + assert(is_string($value)); + } + + return $carrier; // @phpstan-ignore return.type + } +} diff --git a/backend/src/Message/EventPersistedHandler.php b/backend/src/Async/EventPersistedHandler.php similarity index 93% rename from backend/src/Message/EventPersistedHandler.php rename to backend/src/Async/EventPersistedHandler.php index 93f1094..2a3232d 100644 --- a/backend/src/Message/EventPersistedHandler.php +++ b/backend/src/Async/EventPersistedHandler.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Message; +namespace App\Async; use App\Game\Instrumentation\LoggingInterface; use App\Game\Instrumentation\MetricsInterface; @@ -28,7 +28,7 @@ public function __invoke(EventPersisted $message): void private function tryHandle(EventPersisted $message): void { $tracer = $this->tracing - ->createTracer(__METHOD__, __FILE__, $message->getTraceContext()); + ->createTracer(__METHOD__, __FILE__, $message->traceContext); try { $this->handle($message); diff --git a/backend/src/Message/HandlingFailedException.php b/backend/src/Async/HandlingFailedException.php similarity index 90% rename from backend/src/Message/HandlingFailedException.php rename to backend/src/Async/HandlingFailedException.php index 99e315b..463c0f7 100644 --- a/backend/src/Message/HandlingFailedException.php +++ b/backend/src/Async/HandlingFailedException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Message; +namespace App\Async; final class HandlingFailedException extends \Exception { diff --git a/backend/src/Listener/EventStoredNotifier.php b/backend/src/Listener/EventStoredNotifier.php index 05004f2..ebb9f23 100644 --- a/backend/src/Listener/EventStoredNotifier.php +++ b/backend/src/Listener/EventStoredNotifier.php @@ -4,8 +4,8 @@ namespace App\Listener; +use App\Async\EventPersisted; use App\Entity\Event as EventEntity; -use App\Message\EventPersisted; use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener; use Doctrine\ORM\Event\PostPersistEventArgs; use Doctrine\ORM\Events; diff --git a/backend/src/Message/EventPersisted.php b/backend/src/Message/EventPersisted.php deleted file mode 100644 index 4292bc5..0000000 --- a/backend/src/Message/EventPersisted.php +++ /dev/null @@ -1,52 +0,0 @@ - */ - private array $traceContext; - - private function __construct( - public readonly Event $event, - public readonly float $createdAt, - ) { - $this->traceContext = []; - } - - public static function fromEvent(Event $event): self - { - $obj = new self($event, microtime(true)); - - self::addTraceContext($obj); - - return $obj; - } - - /** @return array */ - public function getTraceContext(): array - { - return $this->traceContext; - } - - private static function addTraceContext(self $message): void - { - $traceContext = []; - - TraceContextPropagator::getInstance()->inject($traceContext); - assert(is_array($traceContext)); - - foreach ($traceContext as $key => $value) { - assert(is_string($key)); - assert(is_string($value)); - $message->traceContext[$key] = $value; - } - } -} diff --git a/dashboard/assets/config.yml b/dashboard/assets/config.yml index 58ba1b4..df34a7a 100644 --- a/dashboard/assets/config.yml +++ b/dashboard/assets/config.yml @@ -39,13 +39,13 @@ services: icon: "fas fa-bolt" url: "http://localhost:5540" target: "_blank" - - name: "Message Queue" - subtitle: "Rabbit MQ" + - name: "Message queue" + subtitle: "RabbitMQ" icon: "fas fa-stream" url: "http://localhost:15672" target: "_blank" - - name: "Monitoring" - subtitle: "Grafana" + - name: "Observabilty" + subtitle: "Loki + Grafana + Tempo + Prometheus" icon: "fas fa-chart-line" url: "http://localhost:3000/dashboards" target: "_blank" @@ -58,8 +58,8 @@ services: icon: "fa-brands fa-github" url: "https://github.com/makomweb/fullstack-symfony-react" target: "_blank" - - name: "Architecture Documentation" - subtitle: "MKDocs" + - name: "Documentation" + subtitle: "arc42 + MKDocs" icon: "fas fa-diagram-project" url: "http://localhost:8005" target: "_blank" @@ -73,8 +73,8 @@ services: icon: "fas fa-shield-halved" url: "/public/coverage/index.html" target: "_blank" - - name: "API Documentation" - subtitle: "Swagger / Open API" + - name: "API documentation" + subtitle: "Swagger / OpenAPI" icon: "fas fa-circle-nodes" url: "http://localhost:8080/api/doc" target: "_blank" diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 58da31e..421663a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,6 +12,7 @@ "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", "@fontsource/roboto": "^5.2.10", + "@makomweb/otel-sdk-react": "^0.1.2", "@mui/icons-material": "^6.5.0", "@mui/material": "^6.5.0", "@opentelemetry/api": "^1.9.1", @@ -55,7 +56,7 @@ "prettier": "^3.8.1", "typescript": "~5.7.2", "typescript-eslint": "^8.58.0", - "vite": "^6.4.1", + "vite": "^6.4.2", "vitest": "^3.0.7" } }, @@ -1572,6 +1573,26 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@makomweb/otel-sdk-react": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@makomweb/otel-sdk-react/-/otel-sdk-react-0.1.2.tgz", + "integrity": "sha512-OGMeWdeDfMtTOtrhkZLqYAcp3BMwMIOL6edZVR3i21dg6Y6UUycDGW4frIbBFbsbdPjVwQSCYhu0ipsaw902Rw==", + "license": "MIT", + "peerDependencies": { + "@opentelemetry/api": ">=1.7.0", + "@opentelemetry/exporter-logs-otlp-http": ">=0.50.0", + "@opentelemetry/exporter-metrics-otlp-http": ">=0.43.0", + "@opentelemetry/exporter-trace-otlp-http": ">=0.43.0", + "@opentelemetry/instrumentation": ">=0.43.0", + "@opentelemetry/instrumentation-document-load": ">=0.33.0", + "@opentelemetry/instrumentation-fetch": ">=0.51.0", + "@opentelemetry/resources": ">=1.17.0", + "@opentelemetry/sdk-logs": ">=0.50.0", + "@opentelemetry/sdk-metrics": ">=1.17.0", + "@opentelemetry/sdk-trace-web": ">=1.17.0", + "react": ">=18.0.0" + } + }, "node_modules/@mui/core-downloads-tracker": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.5.0.tgz", @@ -1845,6 +1866,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.200.0.tgz", "integrity": "sha512-KfWw49htbGGp9s8N4KI8EQ9XuqKJ0VG+yVYVYFiCYSjEV32qpQ5qZ9UZBzOZ6xRb+E16SXOSCT3RkqBVSABZ+g==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", @@ -1864,6 +1886,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.52.1.tgz", "integrity": "sha512-oAHPOy1sZi58bwqXaucd19F/v7+qE2EuVslQOEeLQT94CDuZJJ4tbWzx8DpYBTrOSzKqqrMtx9+PMxkrcbxOyQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "1.25.1", "@opentelemetry/otlp-exporter-base": "0.52.1", @@ -1942,6 +1965,22 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/resources": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz", + "integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.25.1", + "@opentelemetry/semantic-conventions": "1.25.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-logs": { "version": "0.52.1", "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.52.1.tgz", @@ -1990,6 +2029,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.52.1.tgz", "integrity": "sha512-05HcNizx0BxcFKKnS5rwOV+2GevLTVIRA0tRgWYyw4yCgR53Ic/xk83toYKts7kbzcI+dswInUg/4s8oyA+tqg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "1.25.1", "@opentelemetry/otlp-exporter-base": "0.52.1", @@ -2068,6 +2108,22 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/resources": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz", + "integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.25.1", + "@opentelemetry/semantic-conventions": "1.25.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/sdk-logs": { "version": "0.52.1", "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.52.1.tgz", @@ -2116,6 +2172,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.52.1.tgz", "integrity": "sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/api-logs": "0.52.1", "@types/shimmer": "^1.0.2", @@ -2136,6 +2193,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-document-load/-/instrumentation-document-load-0.39.0.tgz", "integrity": "sha512-M8QTHM1fFoJvQ1EYaxAF7V5RJhG4c+o4gWHLSFQl6dvQJuGiSdhM3azenRFcTe88Sn6AmVYRGiUjlac9GSVQ2g==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.52.0", @@ -2179,6 +2237,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fetch/-/instrumentation-fetch-0.52.1.tgz", "integrity": "sha512-EJDQXdv1ZGyBifox+8BK+hP0tg29abNPdScE+lW77bUVrThD5vn2dOo+blAS3Z8Od+eqTUTDzXVDIFjGgTK01w==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "1.25.1", "@opentelemetry/instrumentation": "0.52.1", @@ -2332,50 +2391,42 @@ } }, "node_modules/@opentelemetry/resources": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz", - "integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.1.tgz", + "integrity": "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1" + "@opentelemetry/core": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/core": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz", - "integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", + "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/semantic-conventions": "1.25.1" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", - "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/sdk-logs": { "version": "0.200.0", "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.200.0.tgz", "integrity": "sha512-VZG870063NLfObmQQNtCVcdXXLzI3vOjjrRENmU37HYiPFa0ZXpXVDsTD02Nh3AT3xYJzQaWKl2X2lQ2l7TWJA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", @@ -2409,6 +2460,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.1.tgz", "integrity": "sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/resources": "1.30.1" @@ -2492,6 +2544,22 @@ "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, + "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/resources": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz", + "integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.25.1", + "@opentelemetry/semantic-conventions": "1.25.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/semantic-conventions": { "version": "1.25.1", "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", @@ -2506,6 +2574,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-web/-/sdk-trace-web-1.30.1.tgz", "integrity": "sha512-AUo2e+1uyTGMB36VlbvBqnCogVzQhpC7dRcVVdCrt+cFHLpFRRJcd45J2obGTgs0XiAwNLyq5bhkW3JF2NZA+A==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/sdk-trace-base": "1.30.1", @@ -3210,9 +3279,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", - "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", "license": "MIT", "dependencies": { "undici-types": "~7.18.0" @@ -4012,9 +4081,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.13", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz", - "integrity": "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==", + "version": "2.10.16", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.16.tgz", + "integrity": "sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4142,9 +4211,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001784", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz", - "integrity": "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==", + "version": "1.0.30001786", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001786.tgz", + "integrity": "sha512-4oxTZEvqmLLrERwxO76yfKM7acZo310U+v4kqexI2TL1DkkUEMT8UijrxxcnVdxR3qkVf5awGRX+4Z6aPHVKrA==", "dev": true, "funding": [ { @@ -4949,9 +5018,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.331", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz", - "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==", + "version": "1.5.332", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.332.tgz", + "integrity": "sha512-7OOtytmh/rINMLwaFTbcMVvYXO3AUm029X0LcyfYk0B557RlPkdpTpnH9+htMlfu5dKwOmT0+Zs2Aw+lnn6TeQ==", "dev": true, "license": "ISC" }, @@ -8869,9 +8938,9 @@ } }, "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "dev": true, "license": "MIT", "peer": true, diff --git a/frontend/package.json b/frontend/package.json index 6521a5c..faa4c87 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,6 +17,7 @@ "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", "@fontsource/roboto": "^5.2.10", + "@makomweb/otel-sdk-react": "^0.1.2", "@mui/icons-material": "^6.5.0", "@mui/material": "^6.5.0", "@opentelemetry/api": "^1.9.1", @@ -60,7 +61,7 @@ "prettier": "^3.8.1", "typescript": "~5.7.2", "typescript-eslint": "^8.58.0", - "vite": "^6.4.1", + "vite": "^6.4.2", "vitest": "^3.0.7" } } diff --git a/frontend/src/config/env.ts b/frontend/src/config/env.ts index 7974b2e..d0929fa 100644 --- a/frontend/src/config/env.ts +++ b/frontend/src/config/env.ts @@ -1,4 +1,3 @@ const BACKEND_API_URL = (window as any).BACKEND_API_URL; -const OTEL_COLLECTOR_ADDRESS = (window as any).OTEL_COLLECTOR_ADDRESS; -export { BACKEND_API_URL, OTEL_COLLECTOR_ADDRESS }; +export { BACKEND_API_URL }; diff --git a/frontend/src/config/otel.ts b/frontend/src/config/otel.ts deleted file mode 100644 index 946ece9..0000000 --- a/frontend/src/config/otel.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Resource } from "@opentelemetry/resources"; -import { SEMRESATTRS_SERVICE_NAME } from "@opentelemetry/semantic-conventions"; -import { - SimpleSpanProcessor, - WebTracerProvider, -} from "@opentelemetry/sdk-trace-web"; -import * as OTEL_API from "@opentelemetry/api"; -import * as LOGS_API from "@opentelemetry/api-logs"; -import { FetchInstrumentation } from "@opentelemetry/instrumentation-fetch"; -import { registerInstrumentations } from "@opentelemetry/instrumentation"; -import { DocumentLoadInstrumentation } from "@opentelemetry/instrumentation-document-load"; -import { - MeterProvider, - PeriodicExportingMetricReader, -} from "@opentelemetry/sdk-metrics"; - -import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; -import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http"; -import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http"; -import * as SDK_LOGS from "@opentelemetry/sdk-logs"; -import { BACKEND_API_URL, OTEL_COLLECTOR_ADDRESS } from "./env"; - -const setupOTelSDK = () => { - const resource = Resource.default().merge( - new Resource({ - [SEMRESATTRS_SERVICE_NAME]: "frontend", - }), - ); - - // TRACES - const tracerProvider = new WebTracerProvider({ - resource: resource, - }); - - const traceExporter = new OTLPTraceExporter({ - url: `${OTEL_COLLECTOR_ADDRESS}/v1/traces`, - headers: {}, - }); - - const spanProcessor = new SimpleSpanProcessor(traceExporter); - - // METRICS - const metricExporter = new OTLPMetricExporter({ - url: `${OTEL_COLLECTOR_ADDRESS}/v1/metrics`, - headers: {}, - }); - const metricReader = new PeriodicExportingMetricReader({ - exporter: metricExporter, - // Default is 60000ms (60 seconds). Set to 10 seconds for demonstrative purposes only. - exportIntervalMillis: 10000, - }); - - const meterProvider = new MeterProvider({ - resource: resource, - readers: [metricReader], - }); - - OTEL_API.metrics.setGlobalMeterProvider(meterProvider); - - // LOGS - const logExporter = new OTLPLogExporter({ - url: `${OTEL_COLLECTOR_ADDRESS}/v1/logs`, - headers: {}, - }); - - const logProcessor = new SDK_LOGS.SimpleLogRecordProcessor(logExporter); - const loggerProvider = new SDK_LOGS.LoggerProvider(); - loggerProvider.addLogRecordProcessor(logProcessor); - LOGS_API.logs.setGlobalLoggerProvider(loggerProvider); - - tracerProvider.addSpanProcessor(spanProcessor); - tracerProvider.register(); - OTEL_API.trace.setGlobalTracerProvider(tracerProvider); - - registerInstrumentations({ - instrumentations: [ - new FetchInstrumentation({ - propagateTraceHeaderCorsUrls: [ - new RegExp(`${BACKEND_API_URL.replace("/", "\/")}\/.*`), - ], - }), - new DocumentLoadInstrumentation(), - ], - }); -}; - -export { setupOTelSDK }; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index f3d2204..6e4c3c7 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -6,9 +6,15 @@ import "@fontsource/roboto/300.css"; import "@fontsource/roboto/400.css"; import "@fontsource/roboto/500.css"; import "@fontsource/roboto/700.css"; -import { setupOTelSDK } from "./config/otel.ts"; +import { setupOTelSDK, setupFetchInstrumentation } from "@makomweb/otel-sdk-react"; +import { BACKEND_API_URL } from "./config/env.ts"; -setupOTelSDK(); +// Initialize OTEL infrastructure with explicit collector address +const collectorAddress = (window as any).OTEL_COLLECTOR_ADDRESS || "http://localhost:4318"; +setupOTelSDK(collectorAddress); + +// Set up application-specific fetch tracing with backend URL +setupFetchInstrumentation(BACKEND_API_URL); createRoot(document.getElementById("root")!).render( diff --git a/grafana/dashboards/logs-traces-metrics.json b/grafana/dashboards/logs-traces-metrics.json index 7bd3aea..4c4b8cd 100644 --- a/grafana/dashboards/logs-traces-metrics.json +++ b/grafana/dashboards/logs-traces-metrics.json @@ -85,7 +85,7 @@ "includeNullMetadata": true, "instant": true, "interval": "", - "legendFormat": "Command executed (ms)", + "legendFormat": "Executed (ms)", "range": false, "refId": "Command executed (ms)", "useBackend": false @@ -103,7 +103,7 @@ "fullMetaSearch": false, "includeNullMetadata": true, "instant": true, - "legendFormat": "Command executed (ms)", + "legendFormat": "Handled (ms)", "range": false, "refId": "Message handled (ms)", "useBackend": false