Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ if test "$PHP_DDTRACE" != "no"; then
tracer/priority_sampling/priority_sampling.c \
tracer/profiling.c \
tracer/random.c \
tracer/routing_cache.c \
tracer/rule_matching.c \
tracer/serializer.c \
tracer/standalone_limiter.c \
Expand Down
1 change: 1 addition & 0 deletions config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ if (PHP_DDTRACE != 'no') {
DDTRACE_TRACER_SOURCES += " tracer_otel_config.c";
DDTRACE_TRACER_SOURCES += " profiling.c";
DDTRACE_TRACER_SOURCES += " random.c";
DDTRACE_TRACER_SOURCES += " routing_cache.c";
DDTRACE_TRACER_SOURCES += " rule_matching.c";
DDTRACE_TRACER_SOURCES += " serializer.c";
DDTRACE_TRACER_SOURCES += " span.c";
Expand Down
14 changes: 13 additions & 1 deletion src/DDTrace/Integrations/CakePHP/CakePHPIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,19 @@ public static function init(): int

$rootSpan = \DDTrace\root_span();
if ($rootSpan !== null) {
$rootSpan->meta[Tag::HTTP_ROUTE] = $app->template;
$template = $app->template;
$rootSpan->meta[Tag::HTTP_ROUTE] = $template;
$cacheKey = $template;
$normalizedRoute = \DDTrace\routing_cache_get($cacheKey);
if ($normalizedRoute === false) {
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromCakePHP($template);
if ($normalizedRoute !== null) {
\DDTrace\routing_cache_set($cacheKey, $normalizedRoute);
}
}
if ($normalizedRoute !== null && $normalizedRoute !== false) {
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
}
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,20 @@ function (SpanData $span, $args, $retval, $ex) use ($adapter, $service) {
/*
* Replicate CodeIgniter's route parsing, as matching key is never stored or returned in the framework.
*/
private static function setNormalizedRoute($rootSpan, string $pattern) {
$cacheKey = $pattern;
$normalizedRoute = \DDTrace\routing_cache_get($cacheKey);
if ($normalizedRoute === false) {
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromCodeIgniter($pattern);
if ($normalizedRoute !== null) {
\DDTrace\routing_cache_set($cacheKey, $normalizedRoute);
}
}
if ($normalizedRoute !== null && $normalizedRoute !== false) {
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
}
}

private static function setHttpRoute($router, $rootSpan) {
// Turn the segment array into a URI string
$uri = implode('/', $router->uri->segments);
Expand All @@ -230,6 +244,7 @@ private static function setHttpRoute($router, $rootSpan) {
if (isset($router->routes[$uri]))
{
$rootSpan->meta[Tag::HTTP_ROUTE] = $uri;
self::setNormalizedRoute($rootSpan, $uri);
return;
}

Expand All @@ -244,12 +259,14 @@ private static function setHttpRoute($router, $rootSpan) {
if (preg_match('#^'.$key.'$#', $uri))
{
$rootSpan->meta[Tag::HTTP_ROUTE] = $origKey;
self::setNormalizedRoute($rootSpan, $origKey);
return;
}
}

// If we got this far it means we didn't encounter a
// matching route so we'll set the site default route
$rootSpan->meta[Tag::HTTP_ROUTE] = $uri;
self::setNormalizedRoute($rootSpan, $uri);
}
}
19 changes: 17 additions & 2 deletions src/DDTrace/Integrations/Laminas/LaminasIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,24 @@ static function (SpanData $span) use ($controller, $action) {
&& $routeName !== null
&& $routeName !== ''
) {
$httpRoute = LaminasIntegration::httpRouteTemplateFromNamedRouteStack($this, (string) $routeName);
if ($httpRoute !== null && $httpRoute !== '') {
$cacheKey = (string) $routeName;
$cachedRoute = \DDTrace\routing_cache_get($cacheKey);
if ($cachedRoute !== false) {
$httpRoute = $cachedRoute;
} else {
$httpRoute = LaminasIntegration::httpRouteTemplateFromNamedRouteStack($this, (string) $routeName);
if ($httpRoute !== null && $httpRoute !== '') {
\DDTrace\routing_cache_set($cacheKey, $httpRoute);
}
}
if ($httpRoute !== null && $httpRoute !== false && $httpRoute !== '') {
$rootSpan->meta[Tag::HTTP_ROUTE] = $httpRoute;
$allParams = method_exists($routeMatch, 'getParams') ? ($routeMatch->getParams() ?? []) : [];
$urlPath = method_exists($request, 'getUri') ? $request->getUri()->getPath() : null;
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromLaminas($httpRoute, $allParams, $urlPath);
if ($normalizedRoute !== null) {
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
}
}
}

Expand Down
14 changes: 13 additions & 1 deletion src/DDTrace/Integrations/Laravel/LaravelIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,19 @@ static function ($This, $scope, $args, $route) {
$rootSpan->meta[Tag::HTTP_URL] = \DDTrace\Util\Normalizer::urlSanitize($request->fullUrl());
}
if (\method_exists($route, 'uri')) {
$rootSpan->meta[Tag::HTTP_ROUTE] = $route->uri();
$httpRoute = $route->uri();
$rootSpan->meta[Tag::HTTP_ROUTE] = $httpRoute;
$normalizedRoute = \DDTrace\routing_cache_get($httpRoute);
if ($normalizedRoute === false) {
$matchedParams = \method_exists($route, 'parameters') ? ($route->parameters() ?? []) : [];
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromLaravel($httpRoute, $matchedParams);
if ($normalizedRoute !== null) {
\DDTrace\routing_cache_set($httpRoute, $normalizedRoute);
}
}
if ($normalizedRoute !== null && $normalizedRoute !== false) {
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
}
}
if (\method_exists($route, 'parameters') && function_exists('\datadog\appsec\push_addresses')) {
$parameters = $route->parameters();
Expand Down
60 changes: 57 additions & 3 deletions src/DDTrace/Integrations/Slim/SlimIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,22 @@ static function ($errorMiddleware, $self, $args) use ($rootSpan, $integration) {
null,
static function ($router, $scope, $args, $return) use ($rootSpan) {
/** @var \Slim\Interfaces\RouteInterface $return */
$rootSpan->meta[Tag::HTTP_ROUTE] = $return->getPattern();
$pattern = $return->getPattern();
$rootSpan->meta[Tag::HTTP_ROUTE] = $pattern;
$normalizedRoute = \DDTrace\routing_cache_get($pattern);
if ($normalizedRoute === false) {
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromSlim($pattern);
if ($normalizedRoute !== null) {
\DDTrace\routing_cache_set($pattern, $normalizedRoute);
}
}
if ($normalizedRoute !== null && $normalizedRoute !== false) {
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
}

if (dd_trace_env_config("DD_HTTP_SERVER_ROUTE_BASED_NAMING")) {
$rootSpan->resource =
$_SERVER['REQUEST_METHOD'] . ' ' . ($return->getName() ?: $return->getPattern());
$_SERVER['REQUEST_METHOD'] . ' ' . ($return->getName() ?: $pattern);
}
}
);
Expand All @@ -92,7 +103,18 @@ static function ($router, $scope, $args, $return) use ($rootSpan) {
static function ($router, $scope, $args, $return) use ($rootSpan) {
/** @var \Slim\Interfaces\RouteInterface $route */
$route = $return;
$rootSpan->meta[Tag::HTTP_ROUTE] = $route->getPattern();
$pattern = $route->getPattern();
$rootSpan->meta[Tag::HTTP_ROUTE] = $pattern;
$normalizedRoute = \DDTrace\routing_cache_get($pattern);
if ($normalizedRoute === false) {
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromSlim($pattern);
if ($normalizedRoute !== null) {
\DDTrace\routing_cache_set($pattern, $normalizedRoute);
}
}
if ($normalizedRoute !== null && $normalizedRoute !== false) {
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
}
}
);
}
Expand Down Expand Up @@ -131,10 +153,42 @@ static function ($router, $scope, $args, $return) use ($rootSpan) {
$span->meta['slim.route.name'] = $routeName;
$rootSpan->meta['slim.route.name'] = $routeName;
}
// Refine normalized route now that matched params are available
$matchedParams = method_exists($route, 'getArguments') ? ($route->getArguments() ?? []) : [];
$pattern = isset($rootSpan->meta[Tag::HTTP_ROUTE]) ? $rootSpan->meta[Tag::HTTP_ROUTE] : '';
if ($pattern !== '') {
$urlPath = $request->getUri()->getPath();
$normalizedRoute = \DDTrace\routing_cache_get($pattern);
if ($normalizedRoute === false) {
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromSlim($pattern, $matchedParams, $urlPath);
if ($normalizedRoute !== null) {
\DDTrace\routing_cache_set($pattern, $normalizedRoute);
}
}
if ($normalizedRoute !== null && $normalizedRoute !== false) {
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
}
}
}
} else {
$rootSpan->meta['slim.route.controller'] = $callableName;
$span->name = 'slim.route.controller';
// Refine normalized route now that matched params are available (Slim 3)
$matchedParams = isset($args[3]) && is_array($args[3]) ? $args[3] : [];
$pattern = isset($rootSpan->meta[Tag::HTTP_ROUTE]) ? $rootSpan->meta[Tag::HTTP_ROUTE] : '';
if ($pattern !== '') {
$urlPath = $request->getUri()->getPath();
$normalizedRoute = \DDTrace\routing_cache_get($pattern);
if ($normalizedRoute === false) {
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromSlim($pattern, $matchedParams, $urlPath);
if ($normalizedRoute !== null) {
\DDTrace\routing_cache_set($pattern, $normalizedRoute);
}
}
if ($normalizedRoute !== null && $normalizedRoute !== false) {
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
}
}
}
};

Expand Down
65 changes: 56 additions & 9 deletions src/DDTrace/Integrations/Symfony/SymfonyIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -442,20 +442,35 @@ static function() {
return;
}

/** @var ContainerInterface $container */
$container = self::$kernel->getContainer();
$path = EndpointCatalog::pathForRoute($route_name, $container);

// Try with locale suffix (Symfony i18n routing convention)
if ($path === null) {
$locale = $request->attributes->get('_locale');
if ($locale !== null) {
$path = EndpointCatalog::pathForRoute($route_name . '.' . $locale, $container);
$cacheKey = $route_name;
$cachedPath = \DDTrace\routing_cache_get($cacheKey);
if ($cachedPath !== false) {
$path = $cachedPath;
} else {
/** @var ContainerInterface $container */
$container = self::$kernel->getContainer();
$path = EndpointCatalog::pathForRoute($route_name, $container);

// Try with locale suffix (Symfony i18n routing convention)
if ($path === null) {
$locale = $request->attributes->get('_locale');
if ($locale !== null) {
$path = EndpointCatalog::pathForRoute($route_name . '.' . $locale, $container);
}
}

if ($path !== null) {
\DDTrace\routing_cache_set($cacheKey, $path);
}
}

if ($path !== null) {
$rootSpan->meta[Tag::HTTP_ROUTE] = $path;
$matchedParams = self::inferSymfonyRouteParams($path, $request->getPathInfo());
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromSymfony($path, $matchedParams);
if ($normalizedRoute !== null) {
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
}
}
};
} else {
Expand Down Expand Up @@ -770,4 +785,36 @@ public static function injectActionInfo($event, $eventName, SpanData $requestSpa

return true;
}

/**
* Walk the path template and URL path together to determine which {param}
* placeholders were actually present in the URL (vs filled from route defaults).
* Each template segment is either static text or a single {param}; trailing
* params with no corresponding URL segment are considered absent.
*
* @return array Map of param name → URL value for params present in the URL
*/
private static function inferSymfonyRouteParams(string $template, string $urlPath): array
{
$templateSegments = array_values(array_filter(explode('/', $template)));
$urlSegments = array_values(array_filter(explode('/', $urlPath)));

$matched = [];
$urlIdx = 0;

foreach ($templateSegments as $seg) {
if (preg_match('/^\{([a-zA-Z_][a-zA-Z0-9_]*)\}$/', $seg, $m)) {
if ($urlIdx < count($urlSegments)) {
$matched[$m[1]] = $urlSegments[$urlIdx];
$urlIdx++;
}
// else: param is beyond end of URL → absent (default-filled)
} else {
// Static segment — always advance the URL position
$urlIdx++;
}
}

return $matched;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,19 @@ static function (HookData $hook) use (
function_exists('is_404') && is_404() === false) {
$rootSpan = \DDTrace\root_span();
if (\property_exists($This, 'matched_rule')) {
$rootSpan->meta[Tag::HTTP_ROUTE] = $This->matched_rule;
$matchedRule = $This->matched_rule;
$rootSpan->meta[Tag::HTTP_ROUTE] = $matchedRule;
$urlPath = \property_exists($This, 'request') ? $This->request : null;
$normalizedRoute = \DDTrace\routing_cache_get($matchedRule);
if ($normalizedRoute === false) {
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromWordPress($matchedRule, $urlPath);
if ($normalizedRoute !== null) {
\DDTrace\routing_cache_set($matchedRule, $normalizedRoute);
}
}
if ($normalizedRoute !== null && $normalizedRoute !== false) {
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
}
}
}
});
Expand Down
11 changes: 11 additions & 0 deletions src/DDTrace/Integrations/Yii/YiiIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,17 @@ function (SpanData $span, $args) use (&$firstController) {

$rootSpan->meta['app.route.path'] = $routePath;
$rootSpan->meta[Tag::HTTP_ROUTE] = $routePath;
$cacheKey = $routePath;
$normalizedRoute = \DDTrace\routing_cache_get($cacheKey);
if ($normalizedRoute === false) {
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromYii($routePath);
if ($normalizedRoute !== null) {
\DDTrace\routing_cache_set($cacheKey, $normalizedRoute);
}
}
if ($normalizedRoute !== null && $normalizedRoute !== false) {
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
}

if (dd_trace_env_config("DD_HTTP_SERVER_ROUTE_BASED_NAMING")) {
$resourceName = \str_replace(
Expand Down
Loading
Loading