Skip to content

Commit 662e25a

Browse files
authored
More signals for cakephp auto instrumentation (#274)
* 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
1 parent 7bf707e commit 662e25a

23 files changed

+1475
-194
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/vendor/
2+
/tests/Integration/App/tmp/

src/Instrumentation/CakePHP/composer.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,16 @@
2525
"phpstan/phpstan-phpunit": "^1.0",
2626
"psalm/plugin-phpunit": "^0.18.4",
2727
"open-telemetry/sdk": "^1.0",
28-
"phpunit/phpunit": "^9.5",
28+
"phpunit/phpunit": "^9.5|^10.5",
2929
"vimeo/psalm": "^5.24",
3030
"symfony/http-client": "^6 || ^7"
3131
},
32+
"suggest": {
33+
"open-telemetry/opentelemetry-auto-psr3": "OpenTelemetry auto-instrumentation for PSR-3 (Logger Interface)",
34+
"open-telemetry/opentelemetry-auto-psr18": "OpenTelemetry auto-instrumentation for PSR-18 (HTTP Client)",
35+
"open-telemetry/opentelemetry-auto-psr15": "OpenTelemetry auto-instrumentation for PSR-15 (HTTP Server Request Handlers)",
36+
"open-telemetry/opentelemetry-auto-pdo": "OpenTelemetry auto-instrumentation for PDO"
37+
},
3238
"autoload": {
3339
"psr-4": {
3440
"OpenTelemetry\\Contrib\\Instrumentation\\CakePHP\\": "src/"
@@ -48,4 +54,4 @@
4854
"php-http/discovery": true
4955
}
5056
}
51-
}
57+
}
Lines changed: 20 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,22 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
3-
<phpunit
4-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5-
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
6-
backupGlobals="false"
7-
backupStaticAttributes="false"
8-
cacheResult="false"
9-
colors="false"
10-
convertErrorsToExceptions="true"
11-
convertNoticesToExceptions="true"
12-
convertWarningsToExceptions="true"
13-
forceCoversAnnotation="false"
14-
processIsolation="false"
15-
stopOnError="false"
16-
stopOnFailure="false"
17-
stopOnIncomplete="false"
18-
stopOnSkipped="false"
19-
stopOnRisky="false"
20-
timeoutForSmallTests="1"
21-
timeoutForMediumTests="10"
22-
timeoutForLargeTests="60"
23-
verbose="true">
24-
25-
<coverage processUncoveredFiles="true" disableCodeCoverageIgnore="false">
26-
<include>
27-
<directory>src</directory>
28-
</include>
29-
</coverage>
30-
31-
<php>
32-
<ini name="date.timezone" value="UTC" />
33-
<ini name="display_errors" value="On" />
34-
<ini name="display_startup_errors" value="On" />
35-
<ini name="error_reporting" value="E_ALL" />
36-
</php>
37-
38-
<testsuites>
39-
<testsuite name="unit">
40-
<directory>tests/Unit</directory>
41-
</testsuite>
42-
<testsuite name="integration">
43-
<directory>tests/Integration</directory>
44-
</testsuite>
45-
</testsuites>
46-
2+
<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">
3+
<php>
4+
<ini name="date.timezone" value="UTC"/>
5+
<ini name="display_errors" value="On"/>
6+
<ini name="display_startup_errors" value="On"/>
7+
<ini name="error_reporting" value="E_ALL"/>
8+
</php>
9+
<testsuites>
10+
<testsuite name="unit">
11+
<directory>tests/Unit</directory>
12+
</testsuite>
13+
<testsuite name="integration">
14+
<directory>tests/Integration</directory>
15+
</testsuite>
16+
</testsuites>
17+
<source>
18+
<include>
19+
<directory>src</directory>
20+
</include>
21+
</source>
4722
</phpunit>

src/Instrumentation/CakePHP/psalm.xml.dist

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
<directory name="tests"/>
1313
<ignoreFiles>
1414
<directory name="tests/Integration/App" />
15+
<directory name="vendor"/>
1516
</ignoreFiles>
1617
</projectFiles>
1718
<plugins>
1819
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
1920
</plugins>
20-
</psalm>
21+
</psalm>

src/Instrumentation/CakePHP/src/CakePHPInstrumentation.php

Lines changed: 7 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,11 @@
44

55
namespace OpenTelemetry\Contrib\Instrumentation\CakePHP;
66

7-
use Cake\Controller\Controller;
8-
use OpenTelemetry\API\Globals;
97
use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
10-
use OpenTelemetry\API\Trace\Span;
11-
use OpenTelemetry\API\Trace\SpanKind;
12-
use OpenTelemetry\API\Trace\StatusCode;
13-
use OpenTelemetry\Context\Context;
14-
use function OpenTelemetry\Instrumentation\hook;
8+
use OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\Cake\Command\Command;
9+
use OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\Cake\Controller\Controller;
10+
use OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\Cake\Http\Server;
1511
use OpenTelemetry\SemConv\TraceAttributes;
16-
use Psr\Http\Message\ResponseInterface;
17-
use Throwable;
1812

1913
class CakePHPInstrumentation
2014
{
@@ -27,60 +21,8 @@ public static function register(): void
2721
null,
2822
'https://opentelemetry.io/schemas/1.24.0'
2923
);
30-
31-
hook(
32-
Controller::class,
33-
'invokeAction',
34-
pre: static function (Controller $app, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) {
35-
$request = $app->getRequest();
36-
/** @psalm-suppress ArgumentTypeCoercion */
37-
$builder = $instrumentation->tracer()
38-
->spanBuilder($request->getMethod())
39-
->setSpanKind(SpanKind::KIND_SERVER)
40-
->setAttribute(TraceAttributes::CODE_FUNCTION, $function)
41-
->setAttribute(TraceAttributes::CODE_NAMESPACE, $class)
42-
->setAttribute(TraceAttributes::CODE_FILEPATH, $filename)
43-
->setAttribute(TraceAttributes::CODE_LINENO, $lineno);
44-
45-
$parent = Globals::propagator()->extract($request->getHeaders());
46-
$span = $builder
47-
->setParent($parent)
48-
->setAttribute(TraceAttributes::URL_FULL, $request->getUri()->__toString())
49-
->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, $request->getMethod())
50-
->setAttribute(TraceAttributes::HTTP_REQUEST_BODY_SIZE, $request->getHeaderLine('Content-Length'))
51-
->setAttribute(TraceAttributes::USER_AGENT_ORIGINAL, $request->getHeaderLine('User-Agent'))
52-
->setAttribute(TraceAttributes::SERVER_ADDRESS, $request->getUri()->getHost())
53-
->setAttribute(TraceAttributes::SERVER_PORT, $request->getUri()->getPort())
54-
->setAttribute(TraceAttributes::URL_SCHEME, $request->getUri()->getScheme())
55-
->setAttribute(TraceAttributes::URL_PATH, $request->getUri()->getPath())
56-
->startSpan();
57-
58-
Context::storage()->attach($span->storeInContext($parent));
59-
},
60-
post: static function (Controller $app, array $params, ?ResponseInterface $response, ?Throwable $exception) {
61-
$scope = Context::storage()->scope();
62-
if (!$scope) {
63-
return;
64-
}
65-
$scope->detach();
66-
$span = Span::fromContext($scope->context());
67-
$response = $app->getResponse();
68-
if ($exception) {
69-
$span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]);
70-
$span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage());
71-
}
72-
/** @var ResponseInterface|null $response */
73-
if ($response) {
74-
if ($response->getStatusCode() >= 400) {
75-
$span->setStatus(StatusCode::STATUS_ERROR);
76-
}
77-
$span->setAttribute(TraceAttributes::HTTP_RESPONSE_STATUS_CODE, $response->getStatusCode());
78-
$span->setAttribute(TraceAttributes::NETWORK_PROTOCOL_VERSION, $response->getProtocolVersion());
79-
$span->setAttribute(TraceAttributes::HTTP_RESPONSE_BODY_SIZE, $response->getHeaderLine('Content-Length') ?: null);
80-
}
81-
82-
$span->end();
83-
},
84-
);
24+
Server::hook($instrumentation);
25+
Controller::hook($instrumentation);
26+
Command::hook($instrumentation);
8527
}
86-
}
28+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\Cake\Command;
6+
7+
use OpenTelemetry\API\Trace\Span;
8+
use OpenTelemetry\API\Trace\StatusCode;
9+
use OpenTelemetry\Context\Context;
10+
use OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\CakeHook;
11+
use OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\CakeHookTrait;
12+
use function OpenTelemetry\Instrumentation\hook;
13+
use OpenTelemetry\SemConv\TraceAttributes;
14+
use Throwable;
15+
16+
class Command implements CakeHook
17+
{
18+
use CakeHookTrait;
19+
20+
public function instrument(): void
21+
{
22+
hook(
23+
\Cake\Command\Command::class,
24+
'execute',
25+
pre: function (\Cake\Command\Command $command, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
26+
$builder = $this->instrumentation
27+
->tracer()
28+
->spanBuilder(sprintf('Command %s', $command->getName() ?: 'unknown'))
29+
->setAttribute(TraceAttributes::CODE_FUNCTION, $function)
30+
->setAttribute(TraceAttributes::CODE_NAMESPACE, $class)
31+
->setAttribute(TraceAttributes::CODE_FILEPATH, $filename)
32+
->setAttribute(TraceAttributes::CODE_LINENO, $lineno);
33+
34+
$parent = Context::getCurrent();
35+
$span = $builder->startSpan();
36+
Context::storage()->attach($span->storeInContext($parent));
37+
38+
return $params;
39+
},
40+
post: function (\Cake\Command\Command $command, array $params, ?int $exitCode, ?Throwable $exception) {
41+
$scope = Context::storage()->scope();
42+
if (!$scope) {
43+
return;
44+
}
45+
46+
$span = Span::fromContext($scope->context());
47+
$span->addEvent('command finished', [
48+
'exit-code' => $exitCode,
49+
]);
50+
51+
$scope->detach();
52+
$span = Span::fromContext($scope->context());
53+
54+
if ($exception) {
55+
$span->recordException($exception, [
56+
TraceAttributes::EXCEPTION_ESCAPED => true,
57+
]);
58+
$span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage());
59+
}
60+
61+
$span->end();
62+
63+
},
64+
);
65+
}
66+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\Cake\Controller;
6+
7+
use Cake\Controller\Controller as CakeController;
8+
use OpenTelemetry\API\Trace\StatusCode;
9+
use OpenTelemetry\Context\Context;
10+
use OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\CakeHook;
11+
use OpenTelemetry\Contrib\Instrumentation\CakePHP\Hooks\CakeHookTrait;
12+
use function OpenTelemetry\Instrumentation\hook;
13+
use OpenTelemetry\SemConv\TraceAttributes;
14+
use Throwable;
15+
16+
class Controller implements CakeHook
17+
{
18+
use CakeHookTrait;
19+
20+
public function instrument(): void
21+
{
22+
hook(
23+
CakeController::class,
24+
'invokeAction',
25+
pre: function (CakeController $app, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
26+
$request = $app->getRequest();
27+
$request = $this->buildSpan($request, $class, $function, $filename, $lineno);
28+
$app->setRequest($request);
29+
},
30+
post: static function (CakeController $app, array $params, $return, ?Throwable $exception) {
31+
$scope = Context::storage()->scope();
32+
if (!$scope) {
33+
return;
34+
}
35+
$scope->detach();
36+
$span = \OpenTelemetry\API\Trace\Span::fromContext($scope->context());
37+
if ($exception) {
38+
$span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]);
39+
$span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage());
40+
}
41+
$response = $app->getResponse();
42+
if ($response->getStatusCode() >= 400) {
43+
$span->setStatus(StatusCode::STATUS_ERROR);
44+
}
45+
$span->setAttribute(TraceAttributes::HTTP_RESPONSE_STATUS_CODE, $response->getStatusCode());
46+
$span->setAttribute(TraceAttributes::NETWORK_PROTOCOL_VERSION, $response->getProtocolVersion());
47+
$span->setAttribute(TraceAttributes::HTTP_RESPONSE_BODY_SIZE, $response->getHeaderLine('Content-Length'));
48+
49+
$span->end();
50+
},
51+
);
52+
}
53+
}

0 commit comments

Comments
 (0)