diff --git a/.env.dist b/.env.dist index 6a62259..1af9ca1 100644 --- a/.env.dist +++ b/.env.dist @@ -22,6 +22,7 @@ SENTRY_PROFILE_SAMPLE_RATE=1.0 ###> prometheus ### PROMETHEUS_NAMESPACE=project-name +PROMETHEUS_STORAGE=in-memory PROMETHEUS_REDIS_HOST=redis PROMETHEUS_REDIS_PORT=6379 PROMETHEUS_REDIS_DATABASE=0 diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..1af9ca1 --- /dev/null +++ b/.env.test @@ -0,0 +1,34 @@ +# In all environments, the following files are loaded if they exist, +# the later taking precedence over the former: +# +# * .env contains default values for the environment variables needed by the app +# * .env.local uncommitted file with local overrides +# * .env.$APP_ENV committed environment-specific defaults +# * .env.$APP_ENV.local uncommitted environment-specific overrides +# +# Real environment variables win over .env files. +# +# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. +# +# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). +# https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration + +###> sentry ### +SENTRY_DSN=https://KEY@INSTANCE.ingest.us.sentry.io/NAMESPACE +SENTRY_ENVIRONMENT=${APP_ENV} +SENTRY_TRACES_SAMPLE_RATE=1.0 +SENTRY_PROFILE_SAMPLE_RATE=1.0 +###< sentry ### + +###> prometheus ### +PROMETHEUS_NAMESPACE=project-name +PROMETHEUS_STORAGE=in-memory +PROMETHEUS_REDIS_HOST=redis +PROMETHEUS_REDIS_PORT=6379 +PROMETHEUS_REDIS_DATABASE=0 +PROMETHEUS_REDIS_TIMEOUT=0.1 +PROMETHEUS_REDIS_READ_TIMEOUT=10 +PROMETHEUS_REDIS_PERSISTENT_CONNECTIONS=false +PROMETHEUS_REDIS_PASSWORD=secret +PROMETHEUS_REDIS_URL=redis://${PROMETHEUS_REDIS_PASSWORD}@${PROMETHEUS_REDIS_HOST} +###< prometheus ### diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..ac0022a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,122 @@ +name: Build + +on: + push: + branches: + - main + - develop + - 'releases/**' + paths-ignore: + - '**/*.md' + - '**/*.gitignore' + - '**/*.gitattributes' + + pull_request: + types: + - published + - created + - edited + - opened + - synchronize + - reopened + paths-ignore: + - '**/*.md' + - '**/*.gitignore' + - '**/*.gitattributes' + +jobs: + build: + name: PHP + runs-on: ubuntu-latest + + strategy: + matrix: + php: + - "8.1" + - "8.2" + - "8.3" + include: + - php-version: "8.1" + composer-options: "--ignore-platform-reqs" + - php-version: "8.2" + composer-options: "--ignore-platform-reqs" + - php-version: "8.3" + composer-options: "--ignore-platform-reqs" + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php }}" + extensions: mbstring, intl + coverage: xdebug + tools: pecl, phpunit, composer + ini-values: post_max_size=256M + + - name: Setup composer + uses: ramsey/composer-install@v1 + with: + composer-options: "${{ matrix.composer-options }}" + + - name: Validate composer files + run: composer validate + + - name: Cache composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer update --prefer-dist --no-progress + + - name: Setup tests directories + run: mkdir -p build/logs + + - name: Execute unit tests + run: ./vendor/bin/pest --colors=always --configuration phpunit.xml.dist --coverage-cobertura tests-cobertura.xml --coverage-clover tests-coverage.xml --log-junit tests-execution.xml + + - name: Prepare coverages and logs + run: cp tests-coverage.xml build/logs/clover.xml && cp tests-cobertura.xml build/logs/cobertura.xml && cp tests-execution.xml build/logs/junit.xml + + - name: Push to Coveralls + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_PARALLEL: true + COVERALLS_FLAG_NAME: php-${{ matrix.php-versions }} + run: | + composer global require php-coveralls/php-coveralls + php-coveralls -v + + finish: + needs: [ build ] + runs-on: ubuntu-latest + + steps: + - name: Coveralls Finished + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + parallel-finished: true + + sonarcloud: + name: SonarCloud + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: SonarCloud Scan + uses: SonarSource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index ad9d1b2..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: CI - -on: - push: - branches: [ dev-main ] - - pull_request: - branches: [ dev-main ] - - page_build: - release: - types: [ published, created, edited ] - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - php: - - "8.1" - include: - - php-version: "8.1" - composer-options: "--ignore-platform-reqs" - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "${{ matrix.php }}" - extensions: mbstring, intl - coverage: xdebug - tools: pecl, phpunit, composer - ini-values: post_max_size=256M - - - name: Setup Composer - uses: ramsey/composer-install@v1 - with: - composer-options: "${{ matrix.composer-options }}" - - - name: Validate composer.json and composer.lock - run: composer validate - - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@v2 - with: - path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php- - - - name: Install dependencies - if: steps.composer-cache.outputs.cache-hit != 'true' - run: composer update --prefer-dist --no-progress - - - name: Setup tests directories - run: mkdir -p build/logs - - - name: Unit Tests - run: ./vendor/bin/phpunit --colors=always --configuration phpunit.xml.dist - - - name: Push to Coveralls - env: - COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_PARALLEL: true - COVERALLS_FLAG_NAME: php-${{ matrix.php-versions }} - run: | - composer global require php-coveralls/php-coveralls - php-coveralls -v - - finish: - needs: [ build ] - runs-on: ubuntu-18.04 - - steps: - - name: Coveralls Finished - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - parallel-finished: true diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 0033992..202cb73 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -4,9 +4,9 @@ build: tests: override: - php-scrutinizer-run - + environment: - php: 8.1.9 + php: 8.2 filter: excluded_paths: diff --git a/EventListener/Prometheus/AbstractMetricsWorkerEventSubscriber.php b/EventListener/Prometheus/AbstractMetricsWorkerEventSubscriber.php deleted file mode 100644 index cfd9f58..0000000 --- a/EventListener/Prometheus/AbstractMetricsWorkerEventSubscriber.php +++ /dev/null @@ -1,70 +0,0 @@ - 'onWorkerMessageReceivedEvent', - WorkerMessageHandledEvent::class => 'onWorkerMessageHandledEvent', - WorkerMessageFailedEvent::class => 'onWorkerMessageFailedEvent', - ]; - } - - abstract public function onWorkerMessageReceivedEvent(WorkerMessageReceivedEvent $event): void; - - abstract public function onWorkerMessageHandledEvent(WorkerMessageHandledEvent $event): void; - - abstract public function onWorkerMessageFailedEvent(WorkerMessageFailedEvent $event): void; - - protected function incrementRegistryCounter(string $worker, string $name, string $help, array $labels, array $values): void - { - $counterName = str_replace('-', '_', sprintf('%s_%s', $worker, $name)); - - try { - $this->registry - ->getOrRegisterCounter($this->namespace, $counterName, $help, $labels) - ->inc($values) - ; - } catch (MetricsRegistrationException $e) { - $this->logError($e); - } - } - - private function logError(MetricsRegistrationException $e): void - { - $this->logger->error( - 'Metrics registration failed, reason: {message}', - ['message' => $e->getMessage(), 'trace' => $e->getTrace()] - ); - } -} diff --git a/EventListener/Prometheus/OnMessageFailedEventListener.php b/EventListener/Prometheus/OnMessageFailedEventListener.php deleted file mode 100644 index 2f4f8e7..0000000 --- a/EventListener/Prometheus/OnMessageFailedEventListener.php +++ /dev/null @@ -1,34 +0,0 @@ -handler->handle($event); - } -} diff --git a/EventListener/Prometheus/OnMessageHandledEventListener.php b/EventListener/Prometheus/OnMessageHandledEventListener.php deleted file mode 100644 index d38e917..0000000 --- a/EventListener/Prometheus/OnMessageHandledEventListener.php +++ /dev/null @@ -1,34 +0,0 @@ -handler->handle($event); - } -} diff --git a/EventListener/Prometheus/OnMessageReceivedEventListener.php b/EventListener/Prometheus/OnMessageReceivedEventListener.php deleted file mode 100644 index 91b2009..0000000 --- a/EventListener/Prometheus/OnMessageReceivedEventListener.php +++ /dev/null @@ -1,34 +0,0 @@ -handler->handle($event); - } -} diff --git a/EventListener/Prometheus/OnMessageRetriedEventListener.php b/EventListener/Prometheus/OnMessageRetriedEventListener.php deleted file mode 100644 index e5d266c..0000000 --- a/EventListener/Prometheus/OnMessageRetriedEventListener.php +++ /dev/null @@ -1,34 +0,0 @@ -handler->handle($event); - } -} diff --git a/EventListener/Prometheus/OnMessageSendEventListener.php b/EventListener/Prometheus/OnWorkerMessageEventListener.php similarity index 55% rename from EventListener/Prometheus/OnMessageSendEventListener.php rename to EventListener/Prometheus/OnWorkerMessageEventListener.php index bd63932..84def52 100644 --- a/EventListener/Prometheus/OnMessageSendEventListener.php +++ b/EventListener/Prometheus/OnWorkerMessageEventListener.php @@ -18,16 +18,25 @@ use FRZB\Component\MetricsPower\Enum\ListenerPriority; use FRZB\Component\MetricsPower\Handler\MetricsHandlerInterface; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; +use Symfony\Component\Messenger\Event\AbstractWorkerMessageEvent; use Symfony\Component\Messenger\Event\SendMessageToTransportsEvent; +use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent; +use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent; +use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent; +use Symfony\Component\Messenger\Event\WorkerMessageRetriedEvent; +#[AsEventListener(WorkerMessageFailedEvent::class, priority: ListenerPriority::HIGHEST)] +#[AsEventListener(WorkerMessageHandledEvent::class, priority: ListenerPriority::HIGHEST)] +#[AsEventListener(WorkerMessageReceivedEvent::class, priority: ListenerPriority::HIGHEST)] +#[AsEventListener(WorkerMessageRetriedEvent::class, priority: ListenerPriority::HIGHEST)] #[AsEventListener(SendMessageToTransportsEvent::class, priority: ListenerPriority::HIGHEST)] -final class OnMessageSendEventListener +final class OnWorkerMessageEventListener { public function __construct( private readonly MetricsHandlerInterface $handler, ) {} - public function __invoke(SendMessageToTransportsEvent $event): void + public function __invoke(AbstractWorkerMessageEvent|SendMessageToTransportsEvent $event): void { $this->handler->handle($event); } diff --git a/Factory/Exception/NotSupportedStorageAdapterException.php b/Factory/Exception/NotSupportedStorageAdapterException.php index 43ff343..7769da9 100644 --- a/Factory/Exception/NotSupportedStorageAdapterException.php +++ b/Factory/Exception/NotSupportedStorageAdapterException.php @@ -15,8 +15,6 @@ namespace FRZB\Component\MetricsPower\Factory\Exception; -use FRZB\Component\MetricsPower\Exception\MetricsPowerException; - final class NotSupportedStorageAdapterException extends StorageAdapterFactoryException { private const MESSAGE_NOT_SUPPORTED_STORAGE = 'Storage type "%s" is not supported'; diff --git a/Factory/Exception/StorageAdapterFactoryException.php b/Factory/Exception/StorageAdapterFactoryException.php index 2f9c7e0..57be72b 100644 --- a/Factory/Exception/StorageAdapterFactoryException.php +++ b/Factory/Exception/StorageAdapterFactoryException.php @@ -2,12 +2,19 @@ declare(strict_types=1); +/** + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * Copyright (c) 2024 Mykhailo Shtanko fractalzombie@gmail.com + * + * For the full copyright and license information, please view the LICENSE.MD + * file that was distributed with this source code. + */ namespace FRZB\Component\MetricsPower\Factory\Exception; - use FRZB\Component\MetricsPower\Exception\MetricsPowerException; -abstract class StorageAdapterFactoryException extends MetricsPowerException -{ -} \ No newline at end of file +abstract class StorageAdapterFactoryException extends MetricsPowerException {} diff --git a/Factory/PrometheusStorageAdapterFactory.php b/Factory/PrometheusStorageAdapterFactory.php index 6a022a8..c3d7f77 100644 --- a/Factory/PrometheusStorageAdapterFactory.php +++ b/Factory/PrometheusStorageAdapterFactory.php @@ -2,12 +2,14 @@ declare(strict_types=1); -/* - * UnBlocker service for routers. +/** + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * - * (c) Mykhailo Shtanko + * Copyright (c) 2024 Mykhailo Shtanko fractalzombie@gmail.com * - * For the full copyright and license information, please view the LICENSE + * For the full copyright and license information, please view the LICENSE.MD * file that was distributed with this source code. */ @@ -40,7 +42,7 @@ public static function createStorageAdapter(array $configuration): Adapter /** @throws NotSupportedStorageAdapterException */ private static function getStorageType(array $configuration): StorageType { - $storage = $configuration['storage'] ?? null; + $storage = $configuration['storage'] ?? StorageType::InMemory->value; return StorageType::tryFrom($storage) ?? throw NotSupportedStorageAdapterException::fromStorageType($storage); } diff --git a/Factory/PrometheusStorageAdapterFactoryInterface.php b/Factory/PrometheusStorageAdapterFactoryInterface.php index 67b6768..17a0665 100644 --- a/Factory/PrometheusStorageAdapterFactoryInterface.php +++ b/Factory/PrometheusStorageAdapterFactoryInterface.php @@ -2,10 +2,19 @@ declare(strict_types=1); +/** + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * Copyright (c) 2024 Mykhailo Shtanko fractalzombie@gmail.com + * + * For the full copyright and license information, please view the LICENSE.MD + * file that was distributed with this source code. + */ namespace FRZB\Component\MetricsPower\Factory; - use FRZB\Component\MetricsPower\Factory\Exception\StorageAdapterFactoryException; use Prometheus\Storage\Adapter; @@ -13,4 +22,4 @@ interface PrometheusStorageAdapterFactoryInterface { /** @throws StorageAdapterFactoryException */ public static function createStorageAdapter(array $configuration): Adapter; -} \ No newline at end of file +} diff --git a/Helper/EnvelopeHelper.php b/Helper/EnvelopeHelper.php index 2b09301..85402fe 100644 --- a/Helper/EnvelopeHelper.php +++ b/Helper/EnvelopeHelper.php @@ -25,6 +25,7 @@ final class EnvelopeHelper { use WithEmptyPrivateConstructor; + public static function wrap(object $message): Envelope { $envelope = Envelope::wrap($message); diff --git a/Helper/MetricalHelper.php b/Helper/MetricalHelper.php index afea98e..206a3aa 100644 --- a/Helper/MetricalHelper.php +++ b/Helper/MetricalHelper.php @@ -38,6 +38,14 @@ public static function getMetrical(object|string $target): array return AttributeHelper::getAttributes($target, Metrical::class); } + public static function getFirstMetrical(object|string $target): ?Metrical + { + return ArrayList::collect(self::getMetrical($target)) + ->firstElement() + ->get() + ; + } + /** @return array