Skip to content

Commit 1c5a312

Browse files
committed
add required metric http.client.request.duration
1 parent 39f190a commit 1c5a312

File tree

1 file changed

+69
-22
lines changed

1 file changed

+69
-22
lines changed

src/Instrumentation/ReactPHP/src/ReactPHPInstrumentation.php

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Composer\InstalledVersions;
88
use GuzzleHttp\Psr7\Query;
9+
use OpenTelemetry\API\Common\Time\Clock;
910
use OpenTelemetry\API\Globals;
1011
use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
1112
use OpenTelemetry\API\Trace\Span;
@@ -102,24 +103,28 @@ public static function register(): void
102103
$request = $request->withoutHeader($field);
103104
}
104105

105-
/** @var non-empty-string|null */
106-
$method = self::canonizeMethod($request->getMethod());
106+
/** @psalm-var array{'http.request.method':non-empty-string|null,'server.address':non-empty-string,'server.port':int} */
107+
$requestMeta = [
108+
'http.request.method' => self::canonizeMethod($request->getMethod()),
109+
'server.address' => $request->getUri()->getHost(),
110+
'server.port' => $request->getUri()->getPort() ?? ($request->getUri()->getScheme() === 'https' ? 443 : 80),
111+
];
107112

108113
$spanBuilder = $instrumentation
109114
->tracer()
110115
// https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-client-span
111-
->spanBuilder($method ?? self::HTTP_REQUEST_METHOD_HTTP)
116+
->spanBuilder($requestMeta['http.request.method'] ?? self::HTTP_REQUEST_METHOD_HTTP)
112117
->setParent($parentContext)
113118
->setSpanKind(SpanKind::KIND_CLIENT)
114-
->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, $method ?? TraceAttributeValues::HTTP_REQUEST_METHOD_OTHER)
115-
->setAttribute(TraceAttributes::SERVER_ADDRESS, $request->getUri()->getHost())
116-
->setAttribute(TraceAttributes::SERVER_PORT, $request->getUri()->getPort() ?? ($request->getUri()->getScheme() === 'https' ? 443 : 80))
119+
->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, $requestMeta['http.request.method'] ?? TraceAttributeValues::HTTP_REQUEST_METHOD_OTHER)
120+
->setAttribute(TraceAttributes::SERVER_ADDRESS, $requestMeta['server.address'])
121+
->setAttribute(TraceAttributes::SERVER_PORT, $requestMeta['server.port'])
117122
->setAttribute(TraceAttributes::URL_FULL, self::sanitizeUrl($request->getUri()))
118123
// https://opentelemetry.io/docs/specs/semconv/code/
119124
->setAttribute(TraceAttributes::CODE_FUNCTION_NAME, sprintf('%s::%s', $class, $function));
120125

121126
// https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-client-span
122-
if ($method === null) {
127+
if ($requestMeta['http.request.method'] === null) {
123128
$spanBuilder->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD_ORIGINAL, $request->getMethod());
124129
}
125130

@@ -133,7 +138,8 @@ public static function register(): void
133138
$spanBuilder->setAttribute(TraceAttributes::CODE_LINE_NUMBER, $lineno);
134139
}
135140

136-
$span = $spanBuilder->startSpan();
141+
$requestStart = Clock::getDefault()->now();
142+
$span = $spanBuilder->setStartTimestamp($requestStart)->startSpan();
137143
$context = $span->storeInContext($parentContext);
138144
$propagator->inject($request, HeadersPropagator::instance(), $context);
139145

@@ -146,11 +152,13 @@ public static function register(): void
146152
}
147153
}
148154

149-
Context::storage()->attach($context);
155+
$scope = Context::storage()->attach($context);
156+
$scope->offsetSet('requestMeta', $requestMeta);
157+
$scope->offsetSet('requestStart', $requestStart);
150158

151159
return [$request];
152160
},
153-
post: static function (Transaction $transaction, array $params, PromiseInterface $promise): PromiseInterface {
161+
post: static function (Transaction $transaction, array $params, PromiseInterface $promise) use ($instrumentation): PromiseInterface {
154162
$scope = Context::storage()->scope();
155163
$scope?->detach();
156164

@@ -164,17 +172,29 @@ public static function register(): void
164172
return $promise;
165173
}
166174

175+
/** @psalm-var array{'http.request.method':non-empty-string|null,'server.address':non-empty-string,'server.port':int} */
176+
$requestMeta = $scope->offsetGet('requestMeta');
177+
$requestMeta['http.request.method'] ??= '_OTHER';
178+
$requestStart = $scope->offsetGet('requestStart');
179+
167180
return $promise->then(
168-
onFulfilled: function (ResponseInterface $response) use ($span) {
181+
onFulfilled: function (ResponseInterface $response) use ($instrumentation, $requestMeta, $requestStart, $span) {
182+
$requestEnd = Clock::getDefault()->now();
183+
/** @psalm-var array{'http.response.status_code':int,'network.protocol.version':non-empty-string,'error.type'?:non-empty-string} */
184+
$responseMeta = [
185+
'http.response.status_code' => $response->getStatusCode(),
186+
'network.protocol.version' => $response->getProtocolVersion(),
187+
];
169188
// https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-client-span
170189
$span
171-
->setAttribute(TraceAttributes::HTTP_RESPONSE_STATUS_CODE, $response->getStatusCode())
172-
->setAttribute(TraceAttributes::NETWORK_PROTOCOL_VERSION, $response->getProtocolVersion());
190+
->setAttribute(TraceAttributes::HTTP_RESPONSE_STATUS_CODE, $responseMeta['http.response.status_code'])
191+
->setAttribute(TraceAttributes::NETWORK_PROTOCOL_VERSION, $responseMeta['network.protocol.version']);
173192

174-
if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 600) {
193+
if ($responseMeta['http.response.status_code'] >= 400 && $responseMeta['http.response.status_code'] < 600) {
175194
$span
176195
->setStatus(StatusCode::STATUS_ERROR)
177-
->setAttribute(TraceAttributes::ERROR_TYPE, (string) $response->getStatusCode());
196+
->setAttribute(TraceAttributes::ERROR_TYPE, (string) $responseMeta['http.response.status_code']);
197+
$responseMeta['error.type'] = (string) $responseMeta['http.response.status_code'];
178198
}
179199

180200
foreach (explode(',', $_ENV[self::ENV_HTTP_RESPONSE_HEADERS] ?? '') as $header) {
@@ -186,19 +206,34 @@ public static function register(): void
186206
}
187207
}
188208

189-
$span->end();
209+
$span->end($requestEnd);
210+
211+
//https://opentelemetry.io/docs/specs/semconv/http/http-metrics/#http-client
212+
$instrumentation->meter()->createHistogram(
213+
'http.client.request.duration',
214+
's',
215+
'Duration of HTTP client requests.',
216+
['ExplicitBucketBoundaries' => [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10]]
217+
)->record($requestEnd - $requestStart, array_merge($requestMeta, $responseMeta));
190218

191219
return $response;
192220
},
193-
onRejected: function (Throwable $t) use ($span) {
221+
onRejected: function (Throwable $t) use ($instrumentation, $requestMeta, $requestStart, $span) {
222+
$requestEnd = Clock::getDefault()->now();
194223
$span->recordException($t);
195224
if (is_a($t, ResponseException::class)) {
225+
/** @psalm-var array{'http.response.status_code':int,'network.protocol.version':non-empty-string,'error.type':non-empty-string} */
226+
$responseMeta = [
227+
'error.type' => (string) $t->getCode(),
228+
'http.response.status_code' => $t->getCode(),
229+
'network.protocol.version' => $t->getResponse()->getProtocolVersion(),
230+
];
196231
// https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-client-span
197232
$span
198233
->setStatus(StatusCode::STATUS_ERROR)
199-
->setAttribute(TraceAttributes::ERROR_TYPE, (string) $t->getCode())
200-
->setAttribute(TraceAttributes::HTTP_RESPONSE_STATUS_CODE, $t->getCode())
201-
->setAttribute(TraceAttributes::NETWORK_PROTOCOL_VERSION, $t->getResponse()->getProtocolVersion());
234+
->setAttribute(TraceAttributes::ERROR_TYPE, $responseMeta['error.type'])
235+
->setAttribute(TraceAttributes::HTTP_RESPONSE_STATUS_CODE, $responseMeta['http.response.status_code'])
236+
->setAttribute(TraceAttributes::NETWORK_PROTOCOL_VERSION, $responseMeta['network.protocol.version']);
202237

203238
foreach (explode(',', $_ENV[self::ENV_HTTP_RESPONSE_HEADERS] ?? '') as $header) {
204239
if ($t->getResponse()->hasHeader($header)) {
@@ -209,12 +244,24 @@ public static function register(): void
209244
}
210245
}
211246
} else {
247+
/** @psalm-var array{'error.type':non-empty-string} */
248+
$responseMeta = [
249+
'error.type' => $t::class,
250+
];
212251
$span
213252
->setStatus(StatusCode::STATUS_ERROR, $t->getMessage())
214-
->setAttribute(TraceAttributes::ERROR_TYPE, $t::class);
253+
->setAttribute(TraceAttributes::ERROR_TYPE, $responseMeta['error.type']);
215254
}
216255

217-
$span->end();
256+
$span->end($requestEnd);
257+
258+
//https://opentelemetry.io/docs/specs/semconv/http/http-metrics/#http-client
259+
$instrumentation->meter()->createHistogram(
260+
'http.client.request.duration',
261+
's',
262+
'Duration of HTTP client requests.',
263+
['ExplicitBucketBoundaries' => [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10]]
264+
)->record($requestEnd - $requestStart, array_merge($requestMeta, $responseMeta));
218265

219266
throw $t;
220267
}

0 commit comments

Comments
 (0)