Skip to content

Commit 3898abd

Browse files
Merge pull request #3 from plunkettscott/feat/watchers
New features
2 parents 64c5b62 + d51b028 commit 3898abd

37 files changed

+1477
-348
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@ jobs:
4646
composer update --prefer-dist --no-interaction --no-progress
4747
4848
- name: Execute tests
49-
run: vendor/bin/phpunit
49+
run: vendor/bin/pest

.valetrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
php=8.2

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,14 @@ This package is currently in development and contains the following Watchers:
2323
- [x] HTTP Client Requests
2424
- [x] Database Queries
2525
- [x] Redis Commands
26-
- [ ] Queue Jobs
26+
- [x] Queued Jobs
27+
- [x] Events
28+
- [x] Event Listeners
2729
- [x] Cache Commands
28-
- [ ] View Rendering
30+
- [ ] View Rendering (Exploring Capabilities)
2931
- [x] Exceptions
3032
- [x] Log Messages
33+
- [x] Scheduled Tasks
3134

3235
## Requirements
3336

composer.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,24 @@
1919
"php": "^8.2",
2020
"guzzlehttp/promises": "^1.5",
2121
"laravel/framework": "^10.0",
22-
"open-telemetry/opentelemetry": "^1",
22+
"open-telemetry/api": "^1.0@beta",
23+
"open-telemetry/sdk": "^1.0@beta",
2324
"php-http/message-factory": "^1.0",
2425
"symfony/http-client": "^6.2"
2526
},
2627
"require-dev": {
28+
"guzzlehttp/guzzle": "^7.5",
29+
"laravel/pint": "^1.6",
2730
"orchestra/testbench": "^8.0",
28-
"pestphp/pest": "^1.22",
31+
"pestphp/pest": "^2.0",
32+
"pestphp/pest-plugin-mock": "^2.0",
2933
"phpstan/phpstan": "^1.10",
30-
"phpunit/phpunit": "^9.0",
31-
"laravel/pint": "^1.6"
34+
"phpunit/phpunit": "^10.0"
3235
},
3336
"autoload": {
3437
"psr-4": {
3538
"PlunkettScott\\LaravelOpenTelemetry\\": "src"
36-
},
37-
"files": [
38-
"src/helpers.php"
39-
]
39+
}
4040
},
4141
"autoload-dev": {
4242
"psr-4": {

config/config.php

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use PlunkettScott\LaravelOpenTelemetry\Enums;
44
use PlunkettScott\LaravelOpenTelemetry\Watchers;
5+
use PlunkettScott\LaravelOpenTelemetry\Resolvers;
56

67
return [
78

@@ -72,10 +73,8 @@
7273
'enabled' => env('OTEL_WATCHER_REQUEST_ENABLED', true),
7374
'options' => (new Watchers\RequestWatcherOptions(
7475
continue_trace: env('OTEL_WATCHER_REQUEST_CONTINUE_TRACE', true),
75-
middleware_groups: [
76-
'web',
77-
'api',
78-
],
76+
record_route: true,
77+
record_user: true,
7978
))->toArray(),
8079
],
8180

@@ -97,6 +96,28 @@
9796
ignored: [],
9897
))->toArray(),
9998
],
99+
100+
Watchers\EventWatcher::class => [
101+
'enabled' => env('OTEL_WATCHER_EVENT_ENABLED', true),
102+
'options' => (new Watchers\EventWatcherOptions(
103+
ignored: [],
104+
))->toArray(),
105+
],
106+
107+
Watchers\QueueWatcher::class => [
108+
'enabled' => env('OTEL_WATCHER_QUEUE_ENABLED', true),
109+
'options' => (new Watchers\QueueWatcherOptions(
110+
trace_by_default: true,
111+
ignored: [],
112+
))->toArray(),
113+
],
114+
115+
Watchers\ScheduleWatcher::class => [
116+
'enabled' => env('OTEL_WATCHER_SCHEDULE_ENABLED', true),
117+
'options' => (new Watchers\ScheduleWatcherOptions(
118+
record_output: false,
119+
))->toArray(),
120+
],
100121
],
101122

102123
/*
@@ -116,4 +137,19 @@
116137
Enums\LogContextFields::TRACE_ID => 'trace_id',
117138
],
118139
],
140+
141+
/*
142+
|--------------------------------------------------------------------------
143+
| OpenTelemetry Attribute Resolvers
144+
|--------------------------------------------------------------------------
145+
|
146+
| The following array lists the resolver implementations used to resolve
147+
| various attributes for spans. You are free to customize the default
148+
| list of resolvers to suit your instrumentation needs.
149+
|
150+
*/
151+
152+
'resolvers' => [
153+
'user' => Resolvers\DefaultUserResolver::class,
154+
],
119155
];

phpstan.neon.dist

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
parameters:
2-
paths:
3-
- config
4-
- src
5-
6-
level: 0
2+
level: 0
3+
paths:
4+
- config
5+
- src
6+
- tests
7+
ignoreErrors:
8+
- '#^Undefined variable: \$this$#'

phpunit.xml.dist

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,13 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<phpunit backupGlobals="false"
3-
backupStaticAttributes="false"
4-
beStrictAboutTestsThatDoNotTestAnything="true"
5-
bootstrap="vendor/autoload.php"
6-
colors="true"
7-
convertDeprecationsToExceptions="true"
8-
convertErrorsToExceptions="true"
9-
convertNoticesToExceptions="true"
10-
convertWarningsToExceptions="true"
11-
processIsolation="false"
12-
stopOnError="false"
13-
stopOnFailure="false"
14-
verbose="true"
15-
>
16-
<testsuites>
17-
<testsuite name="OpenTelemetry for Laravel Test Suite">
18-
<directory suffix="Test.php">./tests</directory>
19-
</testsuite>
20-
</testsuites>
21-
<php>
22-
<env name="APP_ENV" value="self-testing"/>
23-
<env name="APP_KEY" value="base64:yk+bUVuZa1p86Dqjk9OjVK2R1pm6XHxC6xEKFq8utH0="/>
24-
<env name="CACHE_DRIVER" value="file"/>
25-
</php>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" beStrictAboutTestsThatDoNotTestAnything="true" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnError="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
3+
<testsuites>
4+
<testsuite name="Tests">
5+
<directory suffix="Test.php">./tests</directory>
6+
</testsuite>
7+
</testsuites>
8+
<php>
9+
<env name="APP_ENV" value="testing"/>
10+
<env name="APP_KEY" value="base64:yk+bUVuZa1p86Dqjk9OjVK2R1pm6XHxC6xEKFq8utH0="/>
11+
<env name="CACHE_DRIVER" value="file"/>
12+
</php>
2613
</phpunit>

src/Concerns/TracesHttpRequests.php

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PlunkettScott\LaravelOpenTelemetry\Concerns;
44

5+
use Exception;
56
use Illuminate\Http\Request;
67
use Illuminate\Support\Str;
78
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
@@ -10,6 +11,7 @@
1011
use OpenTelemetry\Context\ContextInterface;
1112
use OpenTelemetry\Context\ScopeInterface;
1213
use OpenTelemetry\SemConv\TraceAttributes;
14+
use PlunkettScott\LaravelOpenTelemetry\Resolvers\Contracts\UserResolver;
1315

1416
trait TracesHttpRequests
1517
{
@@ -25,19 +27,9 @@ public function extractParentFromRequest(Request $request): ContextInterface
2527

2628
public function createAndActivateRootSpan(bool $continueTrace, Request $request): void
2729
{
28-
$route = $request->route();
29-
30-
$routeParameters = [];
31-
foreach ($route->parameterNames() as $parameterName) {
32-
$routeParameters[$parameterName] = $route->parameter($parameterName);
33-
}
34-
3530
$attributes = [
3631
TraceAttributes::HTTP_METHOD => $request->method(),
3732
TraceAttributes::HTTP_URL => $request->fullUrl(),
38-
TraceAttributes::HTTP_ROUTE => $route->uri,
39-
'http.route_name' => $request->route()?->getName() ?? null,
40-
'http.route_parameters' => json_encode($routeParameters),
4133
TraceAttributes::HTTP_TARGET => $request->path(),
4234
TraceAttributes::HTTP_HOST => $request->getHost(),
4335
TraceAttributes::HTTP_SCHEME => $request->getScheme(),
@@ -75,6 +67,10 @@ public function terminateRootSpan(Request $request, mixed $response): void
7567
return;
7668
}
7769

70+
$this->recordRouteInformation($request);
71+
72+
$this->recordAuthenticatedUser($request);
73+
7874
if (method_exists($response, 'getStatusCode')) {
7975
$this->span->setAttribute(TraceAttributes::HTTP_STATUS_CODE, $response->getStatusCode());
8076
}
@@ -85,17 +81,72 @@ public function terminateRootSpan(Request $request, mixed $response): void
8581
$this->span->setAttribute('http.response_content_encoding', $response->headers->get('Content-Encoding'));
8682
}
8783

84+
$this->span->updateName($this->calculateSpanName($request));
85+
8886
$this->span->end();
87+
$this->scope->detach();
88+
}
89+
90+
private function recordRouteInformation(Request $request): void
91+
{
92+
if (! $this->options->record_route) {
93+
return;
94+
}
95+
96+
$route = $request->route();
8997

90-
if (isset($this->scope)) {
91-
$this->scope->detach();
98+
if (is_null($route)) {
99+
return;
100+
}
101+
102+
$routeParameters = [];
103+
104+
foreach ($route->parameterNames() as $parameterName) {
105+
$routeParameters[$parameterName] = $route->parameter($parameterName);
106+
}
107+
108+
$this->span->setAttributes([
109+
TraceAttributes::HTTP_ROUTE => Str::startsWith($route->uri, '/')
110+
? $route->uri
111+
: '/' . $route->uri,
112+
'http.route_name' => $route->getName(),
113+
'http.route_parameters' => json_encode($routeParameters),
114+
]);
115+
}
116+
117+
private function recordAuthenticatedUser(Request $request): void
118+
{
119+
if (! $this->options->record_user) {
120+
return;
121+
}
122+
123+
/** @var UserResolver|null $userResolver */
124+
$userResolver = app(UserResolver::class);
125+
if (is_null($userResolver)) {
126+
return;
127+
}
128+
129+
try {
130+
if ($user = $userResolver->resolve($request)) {
131+
$user->addToSpan($this->span);
132+
}
133+
} catch (Exception $e) {
134+
$this->span->addEvent('Failed to resolve EndUser using ['.get_class($userResolver).'::resolve()] method.');
135+
$this->span->recordException($e);
92136
}
93137
}
94138

95139
private function calculateSpanName(Request $request): string
96140
{
97141
$route = $request->route();
98-
$routeName = $route?->getName();
142+
if (is_null($route)) {
143+
// Early on in the request lifecycle, the route may not be available yet.
144+
return $request->method().' '.$request->path();
145+
}
146+
147+
// If the route is available, let's do a more generic span name that also
148+
// includes the name of the route, if it has one.
149+
$routeName = $route->getName();
99150

100151
if (Str::startsWith($routeName, 'generated::')) {
101152
// This is a route name generated by for a closure when caching
@@ -104,7 +155,9 @@ private function calculateSpanName(Request $request): string
104155
$routeName = null;
105156
}
106157

107-
$routePath = $route?->uri ?? $request->path();
158+
$routePath = Str::startsWith($route->uri, '/')
159+
? $route->uri
160+
: '/' . $route->uri;
108161

109162
return "{$request->method()} $routePath".($routeName ? " ($routeName)" : '');
110163
}

src/Contracts/NotTraceAware.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace PlunkettScott\LaravelOpenTelemetry\Contracts;
4+
5+
interface NotTraceAware
6+
{
7+
8+
}

src/Contracts/TraceAware.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace PlunkettScott\LaravelOpenTelemetry\Contracts;
4+
5+
interface TraceAware
6+
{
7+
8+
}

0 commit comments

Comments
 (0)