|
4 | 4 |
|
5 | 5 | namespace OpenTelemetry\Aws; |
6 | 6 |
|
| 7 | +use Aws\CommandInterface; |
7 | 8 | use Aws\Middleware; |
8 | 9 | use Aws\ResultInterface; |
| 10 | +use Closure; |
| 11 | +use Error; |
| 12 | +use GuzzleHttp\Promise; |
9 | 13 | use OpenTelemetry\API\Instrumentation\InstrumentationInterface; |
10 | 14 | use OpenTelemetry\API\Instrumentation\InstrumentationTrait; |
11 | 15 | use OpenTelemetry\API\Trace\SpanKind; |
| 16 | +use OpenTelemetry\API\Trace\StatusCode; |
12 | 17 | use OpenTelemetry\API\Trace\TracerInterface; |
13 | 18 | use OpenTelemetry\API\Trace\TracerProviderInterface; |
14 | 19 | use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface; |
| 20 | +use Psr\Http\Message\RequestInterface; |
| 21 | +use Throwable; |
15 | 22 |
|
16 | 23 | /** |
17 | 24 | * @experimental |
@@ -114,37 +121,86 @@ public function activate(): bool |
114 | 121 | ]); |
115 | 122 | }), 'instrumentation'); |
116 | 123 |
|
117 | | - $client->getHandlerList()->appendSign(Middleware::mapResult(function (ResultInterface $result) use ($hash) { |
118 | | - if (empty($this->spanStorage[$hash])) { |
119 | | - return $result; |
120 | | - } |
121 | | - [$span, $scope] = $this->spanStorage[$hash]; |
122 | | - unset($this->spanStorage[$hash]); |
123 | | - |
124 | | - /* |
125 | | - * Some AWS SDK Functions, such as S3Client->getObjectUrl() do not actually perform on the wire comms |
126 | | - * with AWS Servers, and therefore do not return with a populated AWS\Result object with valid @metadata |
127 | | - * Check for the presence of @metadata before extracting status code as these calls are still |
128 | | - * instrumented. |
129 | | - */ |
130 | | - if (isset($result['@metadata'])) { |
131 | | - $span->setAttributes([ |
132 | | - 'http.status_code' => $result['@metadata']['statusCode'], // @phan-suppress-current-line PhanTypeMismatchDimFetch |
133 | | - ]); |
134 | | - } |
135 | | - |
136 | | - $span->end(); |
137 | | - $scope->detach(); |
138 | | - |
139 | | - return $result; |
140 | | - }), 'end_instrumentation'); |
| 124 | + $client->getHandlerList()->appendSign(function (callable $handler) use ($hash) { |
| 125 | + return $this->endSpanMiddleware($handler, $hash); |
| 126 | + }, 'end_instrumentation'); |
141 | 127 |
|
142 | 128 | $this->instrumentedClients[$hash] = 1; |
143 | 129 | } |
144 | | - } catch (\Throwable $e) { |
| 130 | + } catch (Throwable $e) { |
145 | 131 | return false; |
146 | 132 | } |
147 | 133 |
|
148 | 134 | return true; |
149 | 135 | } |
| 136 | + |
| 137 | + private function endSpanMiddleware(callable $handler, string $hash): Closure |
| 138 | + { |
| 139 | + $onFulfilled = function (ResultInterface $result) use ($hash) { |
| 140 | + if (empty($this->spanStorage[$hash])) { |
| 141 | + return $result; |
| 142 | + } |
| 143 | + [$span, $scope] = $this->spanStorage[$hash]; |
| 144 | + unset($this->spanStorage[$hash]); |
| 145 | + |
| 146 | + /* |
| 147 | + * Some AWS SDK Functions, such as S3Client->getObjectUrl() do not actually perform on the wire comms |
| 148 | + * with AWS Servers, and therefore do not return with a populated AWS\Result object with valid @metadata |
| 149 | + * Check for the presence of @metadata before extracting status code as these calls are still |
| 150 | + * instrumented. |
| 151 | + */ |
| 152 | + if (isset($result['@metadata'])) { |
| 153 | + $span->setAttributes([ |
| 154 | + 'http.status_code' => $result['@metadata']['statusCode'], // @phan-suppress-current-line PhanTypeMismatchDimFetch |
| 155 | + ]); |
| 156 | + } |
| 157 | + |
| 158 | + $span->end(); |
| 159 | + $scope->detach(); |
| 160 | + |
| 161 | + return $result; |
| 162 | + }; |
| 163 | + |
| 164 | + $onRejected = function ($reason) use ($hash) { |
| 165 | + if (empty($this->spanStorage[$hash])) { |
| 166 | + return Promise\Create::rejectionFor($reason); |
| 167 | + } |
| 168 | + [$span, $scope] = $this->spanStorage[$hash]; |
| 169 | + unset($this->spanStorage[$hash]); |
| 170 | + |
| 171 | + $span->setStatus(StatusCode::STATUS_ERROR, $this->normalizeReason($reason)); |
| 172 | + |
| 173 | + if ($reason instanceof Throwable) { |
| 174 | + $span->recordException($reason); |
| 175 | + } |
| 176 | + |
| 177 | + $span->end(); |
| 178 | + $scope->detach(); |
| 179 | + |
| 180 | + return Promise\Create::rejectionFor($reason); |
| 181 | + }; |
| 182 | + |
| 183 | + return function ( |
| 184 | + CommandInterface $command, |
| 185 | + ?RequestInterface $request = null, |
| 186 | + ) use ($handler, $onFulfilled, $onRejected) { |
| 187 | + return $handler($command, $request)->then( |
| 188 | + $onFulfilled, |
| 189 | + $onRejected, |
| 190 | + ); |
| 191 | + }; |
| 192 | + } |
| 193 | + |
| 194 | + private function normalizeReason(mixed $reason): ?string |
| 195 | + { |
| 196 | + if ($reason instanceof Throwable) { |
| 197 | + return $reason->getMessage(); |
| 198 | + } |
| 199 | + |
| 200 | + try { |
| 201 | + return strval($reason); |
| 202 | + } catch (Error) { |
| 203 | + return null; |
| 204 | + } |
| 205 | + } |
150 | 206 | } |
0 commit comments