Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions src/Tracing/Cache/TraceableCacheAdapterForV3WithNamespace.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public function withSubNamespace(string $namespace): static

$clone = clone $this;
$clone->decoratedAdapter = $this->decoratedAdapter->withSubNamespace($namespace);
$clone->namespace = null === $this->namespace
? $namespace
: \sprintf('%s.%s', $this->namespace, $namespace);

return $clone;
}
Expand Down
51 changes: 40 additions & 11 deletions src/Tracing/Cache/TraceableCacheAdapterTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ trait TraceableCacheAdapterTrait
*/
private $decoratedAdapter;

/**
* @var string|null
*/
protected $namespace;

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -200,16 +205,23 @@ private function traceFunction(string $spanOperation, \Closure $callback, ?strin
try {
$result = $callback();

// Necessary for static analysis. Otherwise, the TResult type is assumed to be CacheItemInterface.
if (!$result instanceof CacheItemInterface) {
return $result;
$data = [];

if ($result instanceof CacheItemInterface) {
$data['cache.hit'] = $result->isHit();
if ($result->isHit()) {
$data['cache.item_size'] = static::getCacheItemSize($result->get());
}
}

$namespace = $this->getCacheNamespace();
if (null !== $namespace) {
$data['cache.namespace'] = $namespace;
}

$data = ['cache.hit' => $result->isHit()];
if ($result->isHit()) {
$data['cache.item_size'] = static::getCacheItemSize($result->get());
if ([] !== $data) {
$span->setData($data);
}
$span->setData($data);

return $result;
} finally {
Expand Down Expand Up @@ -282,10 +294,15 @@ private function traceGet(string $key, callable $callback, ?float $beta = null,

$now = microtime(true);

$getSpan->setData([
$getData = [
'cache.hit' => !$wasMiss,
'cache.item_size' => self::getCacheItemSize($value),
]);
];
$namespace = $this->getCacheNamespace();
if (null !== $namespace) {
$getData['cache.namespace'] = $namespace;
}
$getSpan->setData($getData);

// If we got a timestamp here we know that we missed
if (null !== $saveStartTimestamp) {
Expand All @@ -296,9 +313,13 @@ private function traceGet(string $key, callable $callback, ?float $beta = null,
->setDescription(urldecode($key));
$saveSpan = $parentSpan->startChild($saveContext);
$saveSpan->setStartTimestamp($saveStartTimestamp);
$saveSpan->setData([
$saveData = [
'cache.item_size' => self::getCacheItemSize($value),
]);
];
if (null !== $namespace) {
$saveData['cache.namespace'] = $namespace;
}
$saveSpan->setData($saveData);
$saveSpan->finish($now);
} else {
$getSpan->finish();
Expand Down Expand Up @@ -343,4 +364,12 @@ private function setCallbackWrapper(callable $callback, string $key): callable
return $callback($this->decoratedAdapter->getItem($key));
};
}

/**
* @return string|null
*/
protected function getCacheNamespace(): ?string
{
return $this->namespace;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?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);
$clone->namespace = null === $this->namespace
? $namespace
: \sprintf('%s.%s', $this->namespace, $namespace);

return $clone;
}
}
12 changes: 7 additions & 5 deletions src/aliases.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use Sentry\SentryBundle\Tracing\Cache\TraceableCacheAdapter;
use Sentry\SentryBundle\Tracing\Cache\TraceableCacheAdapterForV2;
use Sentry\SentryBundle\Tracing\Cache\TraceableCacheAdapterForV3;
use Sentry\SentryBundle\Tracing\Cache\TraceableCacheAdapterForV3WithNamespace;
use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapter;
use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV2;
use Sentry\SentryBundle\Tracing\Cache\TraceableTagAwareCacheAdapterForV3;
Expand Down Expand Up @@ -39,21 +38,24 @@
use Symfony\Component\Cache\DoctrineProvider;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Response\StreamableInterface;
use Symfony\Contracts\Cache\NamespacedPoolInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

if (interface_exists(AdapterInterface::class)) {
if (!class_exists(DoctrineProvider::class, false) && version_compare(\PHP_VERSION, '8.0.0', '>=')) {
if (!class_exists(TraceableCacheAdapter::class, false)) {
if (interface_exists(NamespacedPoolInterface::class)) {
class_alias(TraceableCacheAdapterForV3WithNamespace::class, TraceableCacheAdapter::class);
if (interface_exists('Symfony\\Contracts\\Cache\\NamespacedPoolInterface')) {
class_alias('Sentry\\SentryBundle\\Tracing\\Cache\\TraceableCacheAdapterForV3WithNamespace', TraceableCacheAdapter::class);
} else {
class_alias(TraceableCacheAdapterForV3::class, TraceableCacheAdapter::class);
}
}

if (!class_exists(TraceableTagAwareCacheAdapter::class, false)) {
class_alias(TraceableTagAwareCacheAdapterForV3::class, TraceableTagAwareCacheAdapter::class);
if (interface_exists('Symfony\\Contracts\\Cache\\NamespacedPoolInterface')) {
class_alias('Sentry\\SentryBundle\\Tracing\\Cache\\TraceableTagAwareCacheAdapterForV3WithNamespace', 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);
}
}
Loading
Loading