|
11 | 11 | use OpenTelemetry\API\Trace\SpanKind;
|
12 | 12 | use OpenTelemetry\API\Trace\StatusCode;
|
13 | 13 | use OpenTelemetry\Context\Context;
|
| 14 | +use Symfony\Component\HttpKernel\KernelEvents; |
14 | 15 | use function OpenTelemetry\Instrumentation\hook;
|
15 | 16 | use OpenTelemetry\SemConv\TraceAttributes;
|
| 17 | +use Symfony\Component\EventDispatcher\EventDispatcher; |
16 | 18 | use Symfony\Component\HttpFoundation\Request;
|
17 | 19 | use Symfony\Component\HttpFoundation\Response;
|
18 | 20 | use Symfony\Component\HttpKernel\HttpKernel;
|
@@ -141,5 +143,160 @@ public static function register(): void
|
141 | 143 | $span->end();
|
142 | 144 | }
|
143 | 145 | );
|
| 146 | + |
| 147 | + /** |
| 148 | + * Extract symfony event dispatcher known events and trace the controller |
| 149 | + * |
| 150 | + * Adapted from https://github.com/DataDog/dd-trace-php/blob/master/src/DDTrace/Integrations/Symfony/SymfonyIntegration.php |
| 151 | + */ |
| 152 | + hook( |
| 153 | + EventDispatcher::class, |
| 154 | + 'dispatch', |
| 155 | + pre: static function ( |
| 156 | + EventDispatcher $dispatcher, |
| 157 | + array $params, |
| 158 | + string $class, |
| 159 | + string $function, |
| 160 | + ?string $filename, |
| 161 | + ?int $lineno, |
| 162 | + ) use ($instrumentation): array { |
| 163 | + if (!isset($args[0])) { |
| 164 | + return $params; |
| 165 | + } |
| 166 | + |
| 167 | + if (\is_object($args[0])) { |
| 168 | + // dispatch($event, string $eventName = null) |
| 169 | + $event = $args[0]; |
| 170 | + $eventName = isset($args[1]) && \is_string($args[1]) ? $args[1] : \get_class($event); |
| 171 | + } elseif (\is_string($args[0])) { |
| 172 | + // dispatch($eventName, Event $event = null) |
| 173 | + $eventName = $args[0]; |
| 174 | + $event = isset($args[1]) && \is_object($args[1]) ? $args[1] : null; |
| 175 | + } else { |
| 176 | + return $params; |
| 177 | + } |
| 178 | + |
| 179 | + if ($eventName === 'kernel.controller' && \method_exists($event, 'getController')) { |
| 180 | + $controller = $event->getController(); |
| 181 | + if (!($controller instanceof \Closure)) { |
| 182 | + if (\is_callable($controller, false, $controllerName) && $controllerName !== null) { |
| 183 | + if (\strpos($controllerName, '::') > 0) { |
| 184 | + list($class, $method) = \explode('::', $controllerName); |
| 185 | + if (isset($class, $method)) { |
| 186 | + hook( |
| 187 | + $class, |
| 188 | + $method, |
| 189 | + pre: static function ( |
| 190 | + object $controller, |
| 191 | + array $params, |
| 192 | + string $class, |
| 193 | + string $function, |
| 194 | + ?string $filename, |
| 195 | + ?int $lineno, |
| 196 | + ) use ($instrumentation) { |
| 197 | + $parent = Context::getCurrent(); |
| 198 | + $builder = $instrumentation |
| 199 | + ->tracer() |
| 200 | + ->spanBuilder(sprintf('%s::%s', $class, $function)) |
| 201 | + ->setParent($parent); |
| 202 | + $span = $builder->startSpan(); |
| 203 | + $parent = Context::getCurrent(); |
| 204 | + Context::storage()->attach($span->storeInContext($parent)); |
| 205 | + }, |
| 206 | + post: static function ( |
| 207 | + object $controller, |
| 208 | + array $params, |
| 209 | + $result, |
| 210 | + ?\Throwable $exception |
| 211 | + ) { |
| 212 | + $scope = Context::storage()->scope(); |
| 213 | + if (null === $scope) { |
| 214 | + return; |
| 215 | + } |
| 216 | + |
| 217 | + $scope->detach(); |
| 218 | + $span = Span::fromContext($scope->context()); |
| 219 | + |
| 220 | + if (null !== $exception) { |
| 221 | + $span->recordException($exception, [ |
| 222 | + TraceAttributes::EXCEPTION_ESCAPED => true, |
| 223 | + ]); |
| 224 | + $span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage()); |
| 225 | + } |
| 226 | + |
| 227 | + $span->end(); |
| 228 | + } |
| 229 | + ); |
| 230 | + } |
| 231 | + } |
| 232 | + } |
| 233 | + } |
| 234 | + } |
| 235 | + |
| 236 | + $parent = Context::getCurrent(); |
| 237 | + $span = $instrumentation->tracer() |
| 238 | + ->spanBuilder('symfony.' . $eventName) |
| 239 | + ->setParent($parent) |
| 240 | + ->startSpan(); |
| 241 | + |
| 242 | + Context::storage()->attach($span->storeInContext($parent)); |
| 243 | + |
| 244 | + if ($event === null) { |
| 245 | + return $params; |
| 246 | + } |
| 247 | + |
| 248 | + self::setControllerNameAsSpanName($event, $eventName); |
| 249 | + |
| 250 | + return $params; |
| 251 | + }, |
| 252 | + post: static function ( |
| 253 | + EventDispatcher $dispatcher, |
| 254 | + array $params, |
| 255 | + $result, |
| 256 | + ?\Throwable $exception |
| 257 | + ) use ($instrumentation): void { |
| 258 | + $scope = Context::storage()->scope(); |
| 259 | + if (null === $scope) { |
| 260 | + return; |
| 261 | + } |
| 262 | + |
| 263 | + $scope->detach(); |
| 264 | + $span = Span::fromContext($scope->context()); |
| 265 | + |
| 266 | + if (null !== $exception) { |
| 267 | + $span->recordException($exception, [ |
| 268 | + TraceAttributes::EXCEPTION_ESCAPED => true, |
| 269 | + ]); |
| 270 | + $span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage()); |
| 271 | + } |
| 272 | + |
| 273 | + $span->end(); |
| 274 | + } |
| 275 | + ); |
| 276 | + } |
| 277 | + |
| 278 | + private static function setControllerNameAsSpanName($event, $eventName): void |
| 279 | + { |
| 280 | + if ( |
| 281 | + !\defined("\Symfony\Component\HttpKernel\KernelEvents::CONTROLLER") |
| 282 | + || $eventName !== KernelEvents::CONTROLLER |
| 283 | + || !method_exists($event, 'getController') |
| 284 | + ) { |
| 285 | + return; |
| 286 | + } |
| 287 | + |
| 288 | + /** @var callable $controllerAndAction */ |
| 289 | + $controllerAndAction = $event->getController(); |
| 290 | + |
| 291 | + if ( |
| 292 | + !is_array($controllerAndAction) |
| 293 | + || count($controllerAndAction) !== 2 |
| 294 | + || !is_object($controllerAndAction[0]) |
| 295 | + ) { |
| 296 | + return; |
| 297 | + } |
| 298 | + |
| 299 | + $action = get_class($controllerAndAction[0]) . '::' . $controllerAndAction[1]; |
| 300 | + Span::getCurrent()->updateName($action); |
144 | 301 | }
|
145 | 302 | }
|
0 commit comments