Skip to content

Commit

Permalink
[MetricsPower] Added logstash processor
Browse files Browse the repository at this point in the history
  • Loading branch information
fractalzombie committed May 10, 2024
1 parent 0d269d2 commit 49d0215
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 31 deletions.
6 changes: 6 additions & 0 deletions .env.dist
Expand Up @@ -13,6 +13,12 @@
# 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

###> monolog ###
MONOLOG_LOG_LEVEL=debug
MONOLOG_LOG_BUBBLE=true
MONOLOG_SOURCE=default
###< monolog ###

###> sentry ###
SENTRY_DSN=https://KEY@INSTANCE.ingest.us.sentry.io/NAMESPACE
SENTRY_ENVIRONMENT=${APP_ENV}
Expand Down
11 changes: 5 additions & 6 deletions Handler/MetricsHandler.php
Expand Up @@ -18,21 +18,20 @@
use FRZB\Component\DependencyInjection\Attribute\AsService;
use FRZB\Component\MetricsPower\Helper\MetricalHelper;
use FRZB\Component\MetricsPower\OptionsResolver\OptionsResolverLocatorInterface;
use Symfony\Component\Messenger\Event\AbstractWorkerMessageEvent;
use Symfony\Component\Messenger\Event\SendMessageToTransportsEvent;

#[AsService]
class MetricsHandler implements MetricsHandlerInterface
{
public function __construct(
private readonly OptionsResolverLocatorInterface $locator,
) {
}
) {}

public function handle(AbstractWorkerMessageEvent|SendMessageToTransportsEvent $event): void
public function handle(object $event): void
{
foreach (MetricalHelper::getOptions($event->getEnvelope()->getMessage()) as $options) {
$this->locator->get($options)->resolve($event, $options);
if ($resolver = $this->locator->get($options)) {
(new \Fiber($resolver->resolve(...)))->start($event, $options);
}
}
}
}
5 changes: 2 additions & 3 deletions Handler/MetricsHandlerInterface.php
Expand Up @@ -16,11 +16,10 @@
namespace FRZB\Component\MetricsPower\Handler;

use FRZB\Component\DependencyInjection\Attribute\AsAlias;
use Symfony\Component\Messenger\Event\AbstractWorkerMessageEvent;
use Symfony\Component\Messenger\Event\SendMessageToTransportsEvent;

#[AsAlias(MetricsHandler::class)]
interface MetricsHandlerInterface
{
public function handle(AbstractWorkerMessageEvent|SendMessageToTransportsEvent $event): void;
/** @throws \Throwable */
public function handle(object $event): void;
}
64 changes: 64 additions & 0 deletions Logger/Processor/LogstashProcessor.php
@@ -0,0 +1,64 @@
<?php

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\Logger\Processor;

use FRZB\Component\DependencyInjection\Attribute\AsTagged;
use Monolog\LogRecord;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

#[AsTagged('monolog.processor')]
class LogstashProcessor
{
public function __construct(
#[Autowire(env: 'MONOLOG_SOURCE')]
private readonly string $source,
) {}

public function __invoke(LogRecord $record): LogRecord
{
return new LogRecord(
$record->datetime,
$record->channel,
$record->level,
json_validate($record->message) ? $this->formatJson($record->message) : $record->message,
$this->mapContext($record),
$this->mapExtra($record),
json_validate($record->formatted) ? $this->formatJson($record->formatted) : $record->formatted,
);
}

private function formatJson(string $json): string
{
$decodedJson = json_decode($json, true);

return json_encode($decodedJson, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE);
}

private function mapExtra(LogRecord $record): array
{
return [
...$record->extra,
'source' => $this->source,
];
}

private function mapContext(LogRecord $record): array
{
return [
...$record->context,
];
}
}
20 changes: 20 additions & 0 deletions OptionsResolver/Exception/OptionsResolverLocatorException.php
@@ -0,0 +1,20 @@
<?php

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\OptionsResolver\Exception;

use FRZB\Component\MetricsPower\Exception\MetricsPowerException;

final class OptionsResolverLocatorException extends MetricsPowerException {}
31 changes: 18 additions & 13 deletions OptionsResolver/OptionsResolverLocator.php
@@ -1,5 +1,7 @@
<?php

/** @noinspection PhpIncompatibleReturnTypeInspection */

declare(strict_types=1);

/**
Expand All @@ -15,28 +17,31 @@

namespace FRZB\Component\MetricsPower\OptionsResolver;

use Fp\Collections\HashMap;
use FRZB\Component\DependencyInjection\Attribute\AsService;
use FRZB\Component\MetricsPower\Attribute\OptionsInterface;
use FRZB\Component\MetricsPower\OptionsResolver\Exception\OptionsResolverLocatorException;
use FRZB\Component\MetricsPower\OptionsResolver\Resolver\OptionsResolverInterface;
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;

#[AsService]
class OptionsResolverLocator implements OptionsResolverLocatorInterface
{
/** @var HashMap<string, OptionsResolverInterface> */
private readonly HashMap $resolvers;

public function __construct(
#[TaggedIterator(OptionsResolverInterface::class, defaultIndexMethod: 'getType')]
iterable $resolvers,
) {
$this->resolvers = HashMap::collect($resolvers);
}
#[TaggedLocator(OptionsResolverInterface::class, defaultIndexMethod: 'getType')]
private readonly ContainerInterface $resolvers,
) {}

public function get(OptionsInterface $option): OptionsResolverInterface
public function get(OptionsInterface $option): ?OptionsResolverInterface
{
return $this->resolvers->get($option::class)->get()
?? $this->resolvers->get(OptionsInterface::class)->getUnsafe();
try {
return $this->resolvers->get($option::class);
} catch (NotFoundExceptionInterface) {
return null;
} catch (ContainerExceptionInterface $e) {
throw OptionsResolverLocatorException::fromThrowable($e);
}
}
}
4 changes: 3 additions & 1 deletion OptionsResolver/OptionsResolverLocatorInterface.php
Expand Up @@ -17,10 +17,12 @@

use FRZB\Component\DependencyInjection\Attribute\AsAlias;
use FRZB\Component\MetricsPower\Attribute\OptionsInterface;
use FRZB\Component\MetricsPower\OptionsResolver\Exception\OptionsResolverLocatorException;
use FRZB\Component\MetricsPower\OptionsResolver\Resolver\OptionsResolverInterface;

#[AsAlias(OptionsResolverLocator::class)]
interface OptionsResolverLocatorInterface
{
public function get(OptionsInterface $option): OptionsResolverInterface;
/** @throws OptionsResolverLocatorException */
public function get(OptionsInterface $option): ?OptionsResolverInterface;
}
10 changes: 8 additions & 2 deletions OptionsResolver/Resolver/PrometheusOptionsResolver.php
Expand Up @@ -53,11 +53,17 @@ public function resolve(AbstractWorkerMessageEvent|SendMessageToTransportsEvent

try {
$this->registry
->getOrRegisterCounter($this->namespace, CounterHelper::makeName($options, postfix: $postfix), $options->help, $options->labels)
->getOrRegisterCounter(
$this->namespace,
CounterHelper::makeName($options, postfix: $postfix),
$options->help,
$options->labels
)
->inc($options->values);
} catch (BaseMetricsRegistrationException $e) {
throw MetricsRegistrationException::fromThrowable($e);
} catch (StorageException $e) {}
} catch (StorageException $e) {
}
}

public static function getType(): string
Expand Down
2 changes: 1 addition & 1 deletion Resources/config/services.yaml
Expand Up @@ -7,7 +7,7 @@ services: &services
autoconfigure: true

FRZB\Component\MetricsPower\:
resource: '../../{Action,EventListener,Handler,Factory,TypeExtractor,Logger,OptionsResolver}'
resource: '../../{Action,EventListener,Handler,Factory,TypeExtractor,Logger,OptionsResolver,Processor}'
exclude: '../../**/{Attribute,Configuration,Data,DependencyInjection,Enum,Exception,Helper,Tests,Traits}'

when@test:
Expand Down
3 changes: 0 additions & 3 deletions Tests/Unit/Handler/MetricsHandlerTest.php
Expand Up @@ -18,7 +18,6 @@
use FRZB\Component\MetricsPower\Attribute\OptionsInterface;
use FRZB\Component\MetricsPower\Attribute\PrometheusOptions;
use FRZB\Component\MetricsPower\Handler\MetricsHandler;
use FRZB\Component\MetricsPower\Logger\MetricsPowerLoggerInterface;
use FRZB\Component\MetricsPower\OptionsResolver\OptionsResolverLocatorInterface;
use FRZB\Component\MetricsPower\OptionsResolver\Resolver\OptionsResolverInterface;
use FRZB\Component\MetricsPower\Tests\Stub\Exception\SomethingGoesWrongException;
Expand All @@ -27,7 +26,6 @@

test('it can handle and log event with message', function (): void {
$locator = \Mockery::mock(OptionsResolverLocatorInterface::class);
$logger = \Mockery::mock(MetricsPowerLoggerInterface::class);
$resolver = \Mockery::mock(OptionsResolverInterface::class);
$event = new SendMessageToTransportsEvent(createTestEnvelope(), [TestConstants::DEFAULT_RECEIVER_NAME]);
$handler = new MetricsHandler($locator);
Expand All @@ -51,7 +49,6 @@

test('it can handle and log when caught', function (): void {
$locator = \Mockery::mock(OptionsResolverLocatorInterface::class);
$logger = \Mockery::mock(MetricsPowerLoggerInterface::class);
$resolver = \Mockery::mock(OptionsResolverInterface::class);
$event = new SendMessageToTransportsEvent(createTestEnvelope(), [TestConstants::DEFAULT_RECEIVER_NAME]);
$handler = new MetricsHandler($locator);
Expand Down
Expand Up @@ -114,6 +114,6 @@
->once()
->andThrow(new StorageException('something goes wrong'));

/** @noinspection PhpUnhandledExceptionInspection */
// @noinspection PhpUnhandledExceptionInspection
$prometheusOptionResolver->resolve($event, $options);
});
});

0 comments on commit 49d0215

Please sign in to comment.