diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 565e223..8f60aed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: [ '8.1', '8.2', '8.3', '8.4', '8.5' ] + php-version: [ '8.2', '8.3', '8.4', '8.5' ] dependencies: ['highest'] include: - php-version: '8.1' diff --git a/composer.json b/composer.json index 978d367..0f16903 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ ], "require": { "php": "^8.1", - "cakephp/cakephp": "^5.1.0", + "cakephp/cakephp": "^5.3", "sentry/sentry": "^4.15" }, "require-dev": { diff --git a/src/Event/CacheEventListener.php b/src/Event/CacheEventListener.php new file mode 100644 index 0000000..96da680 --- /dev/null +++ b/src/Event/CacheEventListener.php @@ -0,0 +1,225 @@ + 'onCacheAfterGet', + CacheAfterSetEvent::NAME => 'onCacheAfterSet', + CacheAfterAddEvent::NAME => 'onCacheAfterAdd', + CacheAfterIncrementEvent::NAME => 'onCacheAfterIncrement', + CacheAfterDeleteEvent::NAME => 'onCacheAfterDelete', + CacheClearedEvent::NAME => 'onCacheCleared', + ]; + } + + /** + * @param \Cake\Cache\Event\CacheAfterGetEvent $event + * @return void + */ + public function onCacheAfterGet(CacheAfterGetEvent $event): void + { + $value = $this->serializedEventValue($event->getValue()); + $this->addGetSpan($event->getKey(), $value); + } + + /** + * @param \Cake\Cache\Event\CacheAfterSetEvent $event + * @return void + */ + public function onCacheAfterSet(CacheAfterSetEvent $event): void + { + $ttl = $event->getTtl(); + if ($ttl instanceof DateInterval) { + $now = new DateTime(); + $expiry = $now->add($ttl); + $ttl = $expiry->getTimestamp() - $now->getTimestamp(); + } + $value = $this->serializedEventValue($event->getValue()); + $this->addPutSpan($event->getKey(), $value, $ttl); + } + + /** + * @param \Cake\Cache\Event\CacheAfterAddEvent $event + * @return void + */ + public function onCacheAfterAdd(CacheAfterAddEvent $event): void + { + $ttl = $event->getTtl(); + if ($ttl instanceof DateInterval) { + $now = new DateTime(); + $expiry = $now->add($ttl); + $ttl = $expiry->getTimestamp() - $now->getTimestamp(); + } + $value = $this->serializedEventValue($event->getValue()); + $this->addPutSpan($event->getKey(), $value, $ttl); + } + + /** + * @param \Cake\Cache\Event\CacheAfterIncrementEvent $event + * @return void + */ + public function onCacheAfterIncrement(CacheAfterIncrementEvent $event): void + { + $value = $this->serializedEventValue($event->getValue()); + $this->addPutSpan($event->getKey(), $value); + } + + /** + * @param \Cake\Cache\Event\CacheAfterDeleteEvent $event + * @return void + */ + public function onCacheAfterDelete(CacheAfterDeleteEvent $event): void + { + $this->addRemoveSpan($event->getKey()); + } + + /** + * @param \Cake\Cache\Event\CacheClearedEvent $event + * @return void + */ + public function onCacheCleared(CacheClearedEvent $event): void + { + $this->addClearSpan(); + } + + /** + * @param mixed $value + * @return string + */ + private function serializedEventValue(mixed $value): string + { + if (is_array($value) || is_object($value)) { + return serialize($value); + } + + return (string)$value; + } + + /** + * @param string $key + * @param string|null $value + * @return void + */ + private function addGetSpan(string $key, ?string $value): void + { + $parentSpan = SentrySdk::getCurrentHub()->getSpan(); + if ($parentSpan === null) { + return; + } + $context = SpanContext::make() + ->setDescription($key) + ->setOp('cache.get'); + $span = $parentSpan->startChild($context); + SentrySdk::getCurrentHub()->setSpan($span); + + $span->setData([ + 'cache.key' => $key, + ]); + if ($value !== null) { + $span->setData([ + 'cache.hit' => true, + 'cache.item_size' => strlen($value), + ]); + } else { + $span->setData([ + 'cache.hit' => false, + ]); + } + $span->finish(); + SentrySdk::getCurrentHub()->setSpan($parentSpan); + } + + /** + * @param string $key + * @param string $value + * @param int|null $ttl + * @return void + */ + private function addPutSpan(string $key, string $value, ?int $ttl = null): void + { + $parentSpan = SentrySdk::getCurrentHub()->getSpan(); + if ($parentSpan === null) { + return; + } + $context = SpanContext::make() + ->setDescription($key) + ->setOp('cache.put'); + $span = $parentSpan->startChild($context); + SentrySdk::getCurrentHub()->setSpan($span); + + $span->setData([ + 'cache.key' => $key, + 'cache.item_size' => strlen($value), + ]); + if ($ttl !== null) { + $span->setData([ + 'cache.ttl' => $ttl, + ]); + } + $span->finish(); + SentrySdk::getCurrentHub()->setSpan($parentSpan); + } + + /** + * @param string $key + * @return void + */ + private function addRemoveSpan(string $key): void + { + $parentSpan = SentrySdk::getCurrentHub()->getSpan(); + if ($parentSpan === null) { + return; + } + $context = SpanContext::make() + ->setDescription($key) + ->setOp('cache.remove'); + $span = $parentSpan->startChild($context); + SentrySdk::getCurrentHub()->setSpan($span); + + $span + ->setData([ + 'cache.key' => $key, + ]) + ->finish(); + SentrySdk::getCurrentHub()->setSpan($parentSpan); + } + + /** + * @return void + */ + private function addClearSpan(): void + { + $parentSpan = SentrySdk::getCurrentHub()->getSpan(); + if ($parentSpan === null) { + return; + } + $context = SpanContext::make() + ->setDescription('Cache flushed') + ->setOp('cache.flush'); + $span = $parentSpan->startChild($context); + SentrySdk::getCurrentHub()->setSpan($span); + + $span->finish(); + SentrySdk::getCurrentHub()->setSpan($parentSpan); + } +} diff --git a/src/Event/HttpEventListener.php b/src/Event/HttpEventListener.php index b80ce52..42a285b 100644 --- a/src/Event/HttpEventListener.php +++ b/src/Event/HttpEventListener.php @@ -33,25 +33,25 @@ public function handlebeforeSend(ClientEvent $event): void /** @var \Cake\Http\Client\Request $request */ $request = $event->getRequest(); $parentSpan = SentrySdk::getCurrentHub()->getSpan(); + if ($parentSpan === null) { + return; + } + $span = SpanContext::make() + ->setOp('http.client'); - if ($parentSpan !== null) { - $span = SpanContext::make() - ->setOp('http.client'); - - $uri = $request->getUri(); - $fullUrl = sprintf('%s://%s%s/', $uri->getScheme(), $uri->getHost(), $uri->getPath()); - $span - ->setDescription(sprintf('%s %s', $request->getMethod(), $fullUrl)) - ->setData([ - 'url' => $fullUrl, - 'http.query' => $uri->getQuery(), - 'http.fragment' => $uri->getFragment(), - 'http.request.method' => $request->getMethod(), - 'http.request.body.size' => $request->getBody()->getSize(), - ]); + $uri = $request->getUri(); + $fullUrl = sprintf('%s://%s%s/', $uri->getScheme(), $uri->getHost(), $uri->getPath()); + $span + ->setDescription(sprintf('%s %s', $request->getMethod(), $fullUrl)) + ->setData([ + 'url' => $fullUrl, + 'http.query' => $uri->getQuery(), + 'http.fragment' => $uri->getFragment(), + 'http.request.method' => $request->getMethod(), + 'http.request.body.size' => $request->getBody()->getSize(), + ]); - $this->pushSpan($parentSpan->startChild($span)); - } + $this->pushSpan($parentSpan->startChild($span)); } /** @@ -62,15 +62,16 @@ public function handleAfterSend(ClientEvent $event): void { $response = $event->getResult(); $span = $this->popSpan(); - - if ($span !== null) { - $span - ->setHttpStatus($response?->getStatusCode() ?? 0) - ->setData([ - 'http.response.body.size' => $response?->getBody()?->getSize(), - 'http.response.status_code' => $response?->getStatusCode() ?? 0, - ]) - ->finish(); + if ($span === null) { + return; } + + $span + ->setHttpStatus($response?->getStatusCode() ?? 0) + ->setData([ + 'http.response.body.size' => $response?->getBody()?->getSize(), + 'http.response.status_code' => $response?->getStatusCode() ?? 0, + ]) + ->finish(); } } diff --git a/src/Middleware/CakeSentryPerformanceMiddleware.php b/src/Middleware/CakeSentryPerformanceMiddleware.php index 7c461f8..b2eb6ce 100644 --- a/src/Middleware/CakeSentryPerformanceMiddleware.php +++ b/src/Middleware/CakeSentryPerformanceMiddleware.php @@ -18,6 +18,7 @@ use Cake\Event\EventManager; use Cake\Http\Server; use CakeSentry\Database\Log\CakeSentryLog; +use CakeSentry\Event\CacheEventListener; use CakeSentry\Event\HttpEventListener; use CakeSentry\EventListener; use CakeSentry\QuerySpanTrait; @@ -80,6 +81,9 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $listener = new EventListener(); EventManager::instance()->on($listener); EventManager::instance()->on(new HttpEventListener()); + if (class_exists('\Cake\Cache\Event\CacheAfterAddEvent')) { + EventManager::instance()->on(new CacheEventListener()); + } $response = $handler->handle($request);