Skip to content

Commit

Permalink
More signals for cakephp auto instrumentation (#274)
Browse files Browse the repository at this point in the history
* refactor: move controller hook into a separate class

* feat: add http.route attribute to controller span

* feat: add traces and metrics for Server::run calls

* feat(CakePHP): add traces for Command::execute calls

* style(CakePHP): fix code style

* chore: add suggestions for other auto-instrumentation libraries

* fix: add schema URL to cached instrumentation

* fix: remove metrics because of issues with shared-nothing setups

* fix: use TraceAttributes::SCHEMA_URL instead of string constant
  • Loading branch information
eriksamstag authored Aug 28, 2024
1 parent 7bf707e commit 662e25a
Show file tree
Hide file tree
Showing 23 changed files with 1,475 additions and 194 deletions.
1 change: 1 addition & 0 deletions src/Instrumentation/CakePHP/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/vendor/
/tests/Integration/App/tmp/
10 changes: 8 additions & 2 deletions src/Instrumentation/CakePHP/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,16 @@
"phpstan/phpstan-phpunit": "^1.0",
"psalm/plugin-phpunit": "^0.18.4",
"open-telemetry/sdk": "^1.0",
"phpunit/phpunit": "^9.5",
"phpunit/phpunit": "^9.5|^10.5",
"vimeo/psalm": "^5.24",
"symfony/http-client": "^6 || ^7"
},
"suggest": {
"open-telemetry/opentelemetry-auto-psr3": "OpenTelemetry auto-instrumentation for PSR-3 (Logger Interface)",
"open-telemetry/opentelemetry-auto-psr18": "OpenTelemetry auto-instrumentation for PSR-18 (HTTP Client)",
"open-telemetry/opentelemetry-auto-psr15": "OpenTelemetry auto-instrumentation for PSR-15 (HTTP Server Request Handlers)",
"open-telemetry/opentelemetry-auto-pdo": "OpenTelemetry auto-instrumentation for PDO"
},
"autoload": {
"psr-4": {
"OpenTelemetry\\Contrib\\Instrumentation\\CakePHP\\": "src/"
Expand All @@ -48,4 +54,4 @@
"php-http/discovery": true
}
}
}
}
65 changes: 20 additions & 45 deletions src/Instrumentation/CakePHP/phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,47 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>

<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
cacheResult="false"
colors="false"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
forceCoversAnnotation="false"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
stopOnSkipped="false"
stopOnRisky="false"
timeoutForSmallTests="1"
timeoutForMediumTests="10"
timeoutForLargeTests="60"
verbose="true">

<coverage processUncoveredFiles="true" disableCodeCoverageIgnore="false">
<include>
<directory>src</directory>
</include>
</coverage>

<php>
<ini name="date.timezone" value="UTC" />
<ini name="display_errors" value="On" />
<ini name="display_startup_errors" value="On" />
<ini name="error_reporting" value="E_ALL" />
</php>

<testsuites>
<testsuite name="unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="integration">
<directory>tests/Integration</directory>
</testsuite>
</testsuites>

<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" backupGlobals="false" bootstrap="tests/bootstrap.php" cacheResult="false" colors="false" processIsolation="false" stopOnError="false" stopOnFailure="false" stopOnIncomplete="false" stopOnSkipped="false" stopOnRisky="false" timeoutForSmallTests="1" timeoutForMediumTests="10" timeoutForLargeTests="60" cacheDirectory=".phpunit.cache" backupStaticProperties="false" requireCoverageMetadata="false">
<php>
<ini name="date.timezone" value="UTC"/>
<ini name="display_errors" value="On"/>
<ini name="display_startup_errors" value="On"/>
<ini name="error_reporting" value="E_ALL"/>
</php>
<testsuites>
<testsuite name="unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="integration">
<directory>tests/Integration</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>src</directory>
</include>
</source>
</phpunit>
3 changes: 2 additions & 1 deletion src/Instrumentation/CakePHP/psalm.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
<directory name="tests"/>
<ignoreFiles>
<directory name="tests/Integration/App" />
<directory name="vendor"/>
</ignoreFiles>
</projectFiles>
<plugins>
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
</plugins>
</psalm>
</psalm>
72 changes: 7 additions & 65 deletions src/Instrumentation/CakePHP/src/CakePHPInstrumentation.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,11 @@

namespace OpenTelemetry\Contrib\Instrumentation\CakePHP;

use Cake\Controller\Controller;
use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\Context\Context;
use function OpenTelemetry\Instrumentation\hook;
use OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\Cake\Command\Command;
use OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\Cake\Controller\Controller;
use OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\Cake\Http\Server;
use OpenTelemetry\SemConv\TraceAttributes;
use Psr\Http\Message\ResponseInterface;
use Throwable;

class CakePHPInstrumentation
{
Expand All @@ -27,60 +21,8 @@ public static function register(): void
null,
'https://opentelemetry.io/schemas/1.24.0'
);

hook(
Controller::class,
'invokeAction',
pre: static function (Controller $app, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
$request = $app->getRequest();
/** @psalm-suppress ArgumentTypeCoercion */
$builder = $instrumentation->tracer()
->spanBuilder($request->getMethod())
->setSpanKind(SpanKind::KIND_SERVER)
->setAttribute(TraceAttributes::CODE_FUNCTION, $function)
->setAttribute(TraceAttributes::CODE_NAMESPACE, $class)
->setAttribute(TraceAttributes::CODE_FILEPATH, $filename)
->setAttribute(TraceAttributes::CODE_LINENO, $lineno);

$parent = Globals::propagator()->extract($request->getHeaders());
$span = $builder
->setParent($parent)
->setAttribute(TraceAttributes::URL_FULL, $request->getUri()->__toString())
->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, $request->getMethod())
->setAttribute(TraceAttributes::HTTP_REQUEST_BODY_SIZE, $request->getHeaderLine('Content-Length'))
->setAttribute(TraceAttributes::USER_AGENT_ORIGINAL, $request->getHeaderLine('User-Agent'))
->setAttribute(TraceAttributes::SERVER_ADDRESS, $request->getUri()->getHost())
->setAttribute(TraceAttributes::SERVER_PORT, $request->getUri()->getPort())
->setAttribute(TraceAttributes::URL_SCHEME, $request->getUri()->getScheme())
->setAttribute(TraceAttributes::URL_PATH, $request->getUri()->getPath())
->startSpan();

Context::storage()->attach($span->storeInContext($parent));
},
post: static function (Controller $app, array $params, ?ResponseInterface $response, ?Throwable $exception) {
$scope = Context::storage()->scope();
if (!$scope) {
return;
}
$scope->detach();
$span = Span::fromContext($scope->context());
$response = $app->getResponse();
if ($exception) {
$span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]);
$span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage());
}
/** @var ResponseInterface|null $response */
if ($response) {
if ($response->getStatusCode() >= 400) {
$span->setStatus(StatusCode::STATUS_ERROR);
}
$span->setAttribute(TraceAttributes::HTTP_RESPONSE_STATUS_CODE, $response->getStatusCode());
$span->setAttribute(TraceAttributes::NETWORK_PROTOCOL_VERSION, $response->getProtocolVersion());
$span->setAttribute(TraceAttributes::HTTP_RESPONSE_BODY_SIZE, $response->getHeaderLine('Content-Length') ?: null);
}

$span->end();
},
);
Server::hook($instrumentation);
Controller::hook($instrumentation);
Command::hook($instrumentation);
}
}
}
66 changes: 66 additions & 0 deletions src/Instrumentation/CakePHP/src/Hooks/Cake/Command/Command.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\Cake\Command;

use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\Context\Context;
use OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\CakeHook;
use OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\CakeHookTrait;
use function OpenTelemetry\Instrumentation\hook;
use OpenTelemetry\SemConv\TraceAttributes;
use Throwable;

class Command implements CakeHook
{
use CakeHookTrait;

public function instrument(): void
{
hook(
\Cake\Command\Command::class,
'execute',
pre: function (\Cake\Command\Command $command, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
$builder = $this->instrumentation
->tracer()
->spanBuilder(sprintf('Command %s', $command->getName() ?: 'unknown'))
->setAttribute(TraceAttributes::CODE_FUNCTION, $function)
->setAttribute(TraceAttributes::CODE_NAMESPACE, $class)
->setAttribute(TraceAttributes::CODE_FILEPATH, $filename)
->setAttribute(TraceAttributes::CODE_LINENO, $lineno);

$parent = Context::getCurrent();
$span = $builder->startSpan();
Context::storage()->attach($span->storeInContext($parent));

return $params;
},
post: function (\Cake\Command\Command $command, array $params, ?int $exitCode, ?Throwable $exception) {
$scope = Context::storage()->scope();
if (!$scope) {
return;
}

$span = Span::fromContext($scope->context());
$span->addEvent('command finished', [
'exit-code' => $exitCode,
]);

$scope->detach();
$span = Span::fromContext($scope->context());

if ($exception) {
$span->recordException($exception, [
TraceAttributes::EXCEPTION_ESCAPED => true,
]);
$span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage());
}

$span->end();

},
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\Cake\Controller;

use Cake\Controller\Controller as CakeController;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\Context\Context;
use OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\CakeHook;
use OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\CakeHookTrait;
use function OpenTelemetry\Instrumentation\hook;
use OpenTelemetry\SemConv\TraceAttributes;
use Throwable;

class Controller implements CakeHook
{
use CakeHookTrait;

public function instrument(): void
{
hook(
CakeController::class,
'invokeAction',
pre: function (CakeController $app, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
$request = $app->getRequest();
$request = $this->buildSpan($request, $class, $function, $filename, $lineno);
$app->setRequest($request);
},
post: static function (CakeController $app, array $params, $return, ?Throwable $exception) {
$scope = Context::storage()->scope();
if (!$scope) {
return;
}
$scope->detach();
$span = \OpenTelemetry\API\Trace\Span::fromContext($scope->context());
if ($exception) {
$span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]);
$span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage());
}
$response = $app->getResponse();
if ($response->getStatusCode() >= 400) {
$span->setStatus(StatusCode::STATUS_ERROR);
}
$span->setAttribute(TraceAttributes::HTTP_RESPONSE_STATUS_CODE, $response->getStatusCode());
$span->setAttribute(TraceAttributes::NETWORK_PROTOCOL_VERSION, $response->getProtocolVersion());
$span->setAttribute(TraceAttributes::HTTP_RESPONSE_BODY_SIZE, $response->getHeaderLine('Content-Length'));

$span->end();
},
);
}
}
Loading

0 comments on commit 662e25a

Please sign in to comment.