Skip to content

Commit 26d3622

Browse files
feat: add new instrumentation for Guzzle
1 parent 43e27be commit 26d3622

3 files changed

Lines changed: 161 additions & 1 deletion

File tree

.env.example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,9 @@ OTEL_TRACES_SAMPLER_PARENT=false
247247
# OTEL_INSTRUMENTATION_LIVEWIRE=true
248248
# OTEL_INSTRUMENTATION_CONSOLE=true
249249
OTEL_ENHANCE_REQUESTS= true
250-
250+
OTEL_INSTRUMENTATION_GUZZLE = true
251+
OTEL_INSTRUMENTATION_GUZZLE_MANUAL = false
252+
OTEL_INSTRUMENTATION_GUZZLE_ENABLE_LOGS = false
251253
# SWAGGER CONFIG
252254

253255
L5_SWAGGER_CONST_HOST=${APP_URL}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
<?php
2+
3+
namespace App\OpenTelemetry\Instrumentation;
4+
5+
use GuzzleHttp\Client;
6+
use GuzzleHttp\HandlerStack;
7+
use Illuminate\Support\Facades\Log;
8+
use Keepsuit\LaravelOpenTelemetry\Instrumentation\Instrumentation;
9+
use Keepsuit\LaravelOpenTelemetry\Support\HttpClient\GuzzleTraceMiddleware;
10+
11+
class GuzzleInstrumentation implements Instrumentation
12+
{
13+
private bool $logsEnabled;
14+
private static bool $macroRegistered = false;
15+
16+
public function register(array $options): void
17+
{
18+
$this->logsEnabled = $options['enableLogs'] ?? false;
19+
20+
if (!($options['enabled'] ?? true)) {
21+
return;
22+
}
23+
24+
$this->log('info', 'GuzzleInstrumentation::register called', [
25+
'enabled' => $options['enabled'] ?? 'not set',
26+
'manual' => $options['manual'] ?? 'not set',
27+
'enableLogs' => $this->logsEnabled,
28+
'options' => $options
29+
]);
30+
31+
$manual = $options['manual'] ?? false;
32+
33+
if ($manual) {
34+
$this->registerManualMode();
35+
} else {
36+
$this->registerAutoMode();
37+
}
38+
}
39+
40+
protected function registerManualMode(): void
41+
{
42+
$logsEnabled = $this->logsEnabled;
43+
44+
if (!self::$macroRegistered) {
45+
// @phpstan-ignore-next-line
46+
Client::macro('withTrace', function () use ($logsEnabled) {
47+
$config = $this->getConfig();
48+
49+
$handler = $config['handler'] ?? HandlerStack::create();
50+
if (!($handler instanceof HandlerStack)) {
51+
$handler = HandlerStack::create($handler);
52+
}
53+
54+
if (!$this->hasTracingMiddleware($handler)) {
55+
$handler->push(GuzzleTraceMiddleware::make());
56+
if ($logsEnabled) {
57+
Log::debug('[GuzzleInstrumentation] withTrace(): Added tracing middleware - baggage will be injected');
58+
}
59+
} else {
60+
if ($logsEnabled) {
61+
Log::debug('[GuzzleInstrumentation] withTrace(): Tracing middleware already present, skipping');
62+
}
63+
}
64+
65+
$config['handler'] = $handler;
66+
67+
return new Client($config);
68+
});
69+
70+
self::$macroRegistered = true;
71+
}
72+
73+
$this->log('debug', 'GuzzleInstrumentation: MANUAL mode registered - use $client->withTrace()');
74+
}
75+
76+
protected function registerAutoMode(): void
77+
{
78+
$logsEnabled = $this->logsEnabled;
79+
80+
app()->bind(Client::class, function ($app, $parameters) use ($logsEnabled) {
81+
if ($logsEnabled) {
82+
Log::debug('[GuzzleInstrumentation] Creating Guzzle Client with AUTO tracing');
83+
}
84+
85+
$config = $parameters['config'] ?? [];
86+
87+
if (!isset($config['handler'])) {
88+
$stack = HandlerStack::create();
89+
$stack->push(GuzzleTraceMiddleware::make());
90+
$config['handler'] = $stack;
91+
92+
if ($logsEnabled) {
93+
Log::debug('[GuzzleInstrumentation] AUTO mode: Added tracing middleware to new Guzzle client - baggage will be injected');
94+
}
95+
} else {
96+
$handler = $config['handler'];
97+
if ($handler instanceof HandlerStack) {
98+
if (!$this->hasTracingMiddleware($handler)) {
99+
$handler->push(GuzzleTraceMiddleware::make());
100+
if ($logsEnabled) {
101+
Log::debug('[GuzzleInstrumentation] AUTO mode: Added tracing middleware to existing HandlerStack - baggage will be injected');
102+
}
103+
} else {
104+
if ($logsEnabled) {
105+
Log::debug('[GuzzleInstrumentation] AUTO mode: Tracing middleware already present in HandlerStack, skipping');
106+
}
107+
}
108+
}
109+
}
110+
111+
return new Client($config);
112+
});
113+
114+
if ($this->logsEnabled) {
115+
$this->log('debug', 'GuzzleInstrumentation: AUTO mode registered - all new Client() will have tracing');
116+
}
117+
}
118+
119+
protected function hasTracingMiddleware(HandlerStack $stack): bool
120+
{
121+
try {
122+
$reflection = new \ReflectionClass($stack);
123+
$stackProperty = $reflection->getProperty('stack');
124+
$stackProperty->setAccessible(true);
125+
$middlewares = $stackProperty->getValue($stack);
126+
127+
foreach ($middlewares as $middleware) {
128+
if (isset($middleware[0]) && $middleware[0] instanceof \Closure) {
129+
continue;
130+
}
131+
132+
if (strpos(get_class($middleware[0] ?? ''), 'Trace') !== false) {
133+
return true;
134+
}
135+
}
136+
137+
return false;
138+
} catch (\Exception $e) {
139+
$this->log('warning', 'Could not detect existing tracing middleware', ['error' => $e->getMessage()]);
140+
return false;
141+
}
142+
}
143+
144+
private function log(string $level, string $message, array $context = []): void
145+
{
146+
if (!$this->logsEnabled) {
147+
return;
148+
}
149+
150+
Log::$level('[GuzzleInstrumentation] ' . $message, $context);
151+
}
152+
}

config/opentelemetry.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@
136136
'sensitive_headers' => [],
137137
],
138138

139+
\App\OpenTelemetry\Instrumentation\GuzzleInstrumentation::class => [
140+
'enabled' => env('OTEL_INSTRUMENTATION_GUZZLE', true),
141+
'manual' => env('OTEL_INSTRUMENTATION_GUZZLE_MANUAL', false),
142+
'enableLogs' => env('OTEL_INSTRUMENTATION_GUZZLE_ENABLE_LOGS', false),
143+
],
144+
139145
Instrumentation\QueryInstrumentation::class => env('OTEL_INSTRUMENTATION_QUERY', true),
140146

141147
Instrumentation\RedisInstrumentation::class => env('OTEL_INSTRUMENTATION_REDIS', true),

0 commit comments

Comments
 (0)