Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace Sentry\SentryBundle\Tracing\Cache;

use Sentry\State\HubInterface;
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Contracts\Cache\NamespacedPoolInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;

/**
* This implementation of a cache adapter aware of cache tags supports the
* distributed tracing feature of Sentry.
*
* @internal
*/
final class TraceableTagAwareCacheAdapterForV3WithNamespace implements TagAwareAdapterInterface, TagAwareCacheInterface, NamespacedPoolInterface, PruneableInterface, ResettableInterface

This comment was marked as outdated.

{
/**
* @phpstan-use TraceableCacheAdapterTrait<TagAwareAdapterInterface>
*/
use TraceableCacheAdapterTrait;

/**
* @param HubInterface $hub The current hub
* @param TagAwareAdapterInterface $decoratedAdapter The decorated cache adapter
*/
public function __construct(HubInterface $hub, TagAwareAdapterInterface $decoratedAdapter)
{
$this->hub = $hub;
$this->decoratedAdapter = $decoratedAdapter;
}

/**
* {@inheritdoc}
*
* @param mixed[] $metadata
*/
public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed
{
return $this->traceGet($key, $callback, $beta, $metadata);
}

/**
* {@inheritdoc}
*/
public function invalidateTags(array $tags): bool
{
return $this->traceFunction('cache.invalidate_tags', function () use ($tags): bool {
return $this->decoratedAdapter->invalidateTags($tags);
});
}

public function withSubNamespace(string $namespace): static
{
if (!$this->decoratedAdapter instanceof NamespacedPoolInterface) {
throw new \BadMethodCallException(\sprintf('The %s::withSubNamespace() method is not supported because the decorated adapter does not implement the "%s" interface.', self::class, NamespacedPoolInterface::class));
}

$clone = clone $this;
$clone->decoratedAdapter = $this->decoratedAdapter->withSubNamespace($namespace);

return $clone;
}
}
7 changes: 6 additions & 1 deletion src/aliases.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapter;
use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV2;
use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV3;
use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV3WithNamespace;
use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriver;
use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnection;
use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverConnectionFactory;
Expand Down Expand Up @@ -53,7 +54,11 @@ class_alias(TraceableCacheAdapterForV3::class, TraceableCacheAdapter::class);
}

if (!class_exists(TraceableTagAwareCacheAdapter::class, false)) {
class_alias(TraceableTagAwareCacheAdapterForV3::class, TraceableTagAwareCacheAdapter::class);
if (interface_exists(NamespacedPoolInterface::class)) {
class_alias(TraceableTagAwareCacheAdapterForV3WithNamespace::class, TraceableTagAwareCacheAdapter::class);
} else {
class_alias(TraceableTagAwareCacheAdapterForV3::class, TraceableTagAwareCacheAdapter::class);
}
}
} else {
if (!class_exists(TraceableCacheAdapter::class, false)) {
Expand Down
35 changes: 35 additions & 0 deletions tests/End2End/App/Controller/NamespacedCacheController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Sentry\SentryBundle\Tests\End2End\App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;

class NamespacedCacheController
{
/**
* @var TagAwareCacheInterface
*/
private $cache;

public function __construct(TagAwareCacheInterface $cache)
{
$this->cache = $cache;
}

public function populateNamespacedCache(): Response
{
$namespaced = $this->cache->withSubNamespace('tests');

$namespaced->get('namespaced-key', function (ItemInterface $item) {
$item->tag(['a tag name']);

return 'namespaced-value';
});

return new Response();
}
}
4 changes: 4 additions & 0 deletions tests/End2End/App/routing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ psr_tracing_cache_delete:
path: /tracing/cache/psr/delete
defaults: { _controller: 'Sentry\SentryBundle\Tests\End2End\App\Controller\PsrTracingCacheController::testDelete' }

namespaced_tracing_cache_populate:
path: /tracing/cache/namespaced/populate
defaults: { _controller: 'Sentry\SentryBundle\Tests\End2End\App\Controller\NamespacedCacheController::populateNamespacedCache' }

just_logging:
path: /just-logging
defaults: { _controller: 'Sentry\SentryBundle\Tests\End2End\App\Controller\LoggingController::justLogging' }
Expand Down
6 changes: 6 additions & 0 deletions tests/End2End/App/tracing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,9 @@ services:
autowire: true
tags:
- { name: controller.service_arguments }

Sentry\SentryBundle\Tests\End2End\App\Controller\NamespacedCacheController:
arguments:
$cache: '@cache.app.taggable'
tags:
- { name: controller.service_arguments }
31 changes: 31 additions & 0 deletions tests/End2End/TracingCacheEnd2EndTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,35 @@ public function testPsrCacheDelete(): void
$this->assertEquals('cache.remove', $span->getOp());
$this->assertNull($span->getData('cache.item_size'));
}

public function testNamespacedTagAwareCache(): void
{
if (!interface_exists(\Symfony\Contracts\Cache\NamespacedPoolInterface::class)) {
$this->markTestSkipped('Namespaced caches are not supported by this Symfony version.');
}

$client = static::createClient(['debug' => false]);
$cache = static::getContainer()->get('cache.app.taggable');

// make sure that the configured taggable cache supports namespaces before running this test
if (!$cache instanceof \Symfony\Contracts\Cache\NamespacedPoolInterface) {
$this->markTestSkipped('The configured tag-aware cache pool does not support namespaces.');
}

$client->request('GET', '/tracing/cache/namespaced/populate');
$this->assertSame(200, $client->getResponse()->getStatusCode());

$this->assertCount(1, StubTransport::$events);
$event = StubTransport::$events[0];

$cacheGetSpans = array_values(array_filter($event->getSpans(), static function ($span) {
return 'cache.get' === $span->getOp();
}));
$this->assertNotEmpty($cacheGetSpans);

$cachePutSpans = array_filter($event->getSpans(), static function ($span) {
return 'cache.put' === $span->getOp();
});
$this->assertNotEmpty($cachePutSpans);
}
}
51 changes: 51 additions & 0 deletions tests/Tracing/Cache/TraceableTagAwareCacheAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@
namespace Sentry\SentryBundle\Tests\Tracing\Cache;

use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapter;
use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV3WithNamespace;
use Sentry\Tracing\Transaction;
use Sentry\Tracing\TransactionContext;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
use Symfony\Contracts\Cache\NamespacedPoolInterface;

if (interface_exists(NamespacedPoolInterface::class)) {
interface TagAwareNamespacedPoolInterface extends TagAwareAdapterInterface, NamespacedPoolInterface
{
}
}

/**
* @phpstan-extends AbstractTraceableCacheAdapterTest<TraceableTagAwareCacheAdapter, TagAwareAdapterInterface>
Expand Down Expand Up @@ -42,6 +50,49 @@ public function testInvalidateTags(): void
$this->assertNotNull($spans[1]->getEndTimestamp());
}

public function testWithSubNamespaceThrowsWhenNotNamespaced(): void
{
if (!interface_exists(NamespacedPoolInterface::class)) {
$this->markTestSkipped('Namespaced caches are not supported by this Symfony version.');
}

$decoratedAdapter = $this->createMock(TagAwareAdapterInterface::class);
$adapter = new TraceableTagAwareCacheAdapterForV3WithNamespace($this->hub, $decoratedAdapter);

$this->expectException(\BadMethodCallException::class);
$this->expectExceptionMessage('withSubNamespace() method is not supported');

$adapter->withSubNamespace('foo');
}

public function testWithSubNamespaceReturnsNamespacedAdapter(): void
{
if (!interface_exists(NamespacedPoolInterface::class)) {
$this->markTestSkipped('Namespaced caches are not supported by this Symfony version.');
}

$decoratedAdapter = $this->createMock(TagAwareNamespacedPoolInterface::class);
$namespacedAdapter = $this->createMock(TagAwareNamespacedPoolInterface::class);

$decoratedAdapter
->expects($this->once())
->method('withSubNamespace')
->with('foo')
->willReturn($namespacedAdapter);

$adapter = new TraceableTagAwareCacheAdapterForV3WithNamespace($this->hub, $decoratedAdapter);

$result = $adapter->withSubNamespace('foo');

$this->assertInstanceOf(NamespacedPoolInterface::class, $result);
$this->assertNotSame($adapter, $result);

$ref = new \ReflectionProperty($result, 'decoratedAdapter');
$ref->setAccessible(true);

$this->assertSame($namespacedAdapter, $ref->getValue($result));
}

/**
* {@inheritdoc}
*/
Expand Down
Loading