codeconsole opened a new pull request, #15750:
URL: https://github.com/apache/grails-core/pull/15750
## What
Grails dispatches requests through its own URL mappings rather than Spring
MVC handler methods. Two consequences for observability:
1. Spring never sets the observation **path pattern**, so the
`http.server.requests` metric's `uri` tag (and the request span) falls back to
**`UNKNOWN`** — every endpoint collapses into a single metric series, so you
can't do "p95 by route" / "error rate for `/login`".
2. The request lifecycle **between** the root HTTP server span and the leaf
DB/GSP spans is untraced — there's no span for the controller action or the
view render.
This PR adds three things. All are **best-effort and fully guarded** (a
failure can never affect request handling) and **NOOP-fast-pathed** when
observations are disabled, so there's zero overhead in that case. No new
dependencies — the `ObservationRegistry` is resolved from the
`ApplicationContext` (Spring Boot already provides it).
### 1. Populate the `uri` route
`UrlMappingsHandlerMapping` adds an `ObservationRouteHandler`
(`HandlerInterceptor`). In `preHandle` — where the observation context is
attached to the request and the controller/action are resolved — it calls
`ServerRequestObservationContext.setPathPattern("/<controller>/<action>")` (low
cardinality). The `uri` tag becomes e.g. `/book/show`.
> Note: doing this during URL-mapping match (`getHandlerInternal`) is too
early — the observation context isn't on the request yet — which is why it's a
`preHandle` interceptor.
### 2. `grails.controller` span
`UrlMappingsInfoHandlerAdapter` wraps the controller-action invocation
(`controllerClass.invoke`) in an `Observation` (`grails.controller`, with
`grails.controller`/`grails.action` key-values). It's a child of the HTTP
server span and the **parent of any DB/cache spans the action triggers** (the
scope is open across `invoke`).
### 3. `grails.render` span
`GrailsDispatcherServlet` overrides `render()` to wrap the view-render phase
(view resolution + sitemesh decoration + response write) in an `Observation`
(`grails.render`). It becomes the **parent of the existing `gsp.view` spans**,
so "render excluding GSP" falls out as `grails.render` minus its GSP children;
non-view (e.g. JSON) renders are captured on their own.
## Result
A trace becomes a clean breakdown — `http(route) → security → {controller,
db, render → gsp}`:
```
• http get /login/index [103ms] uri=/login/index
• secured request [98ms]
• grails.controller [0ms] grails.controller=login
• mongodb find [0ms]
• grails.render [74ms] grails.view=index
• gsp.view /login/index.gsp [3ms]
• gsp.view /layouts/main.gsp [70ms]
```
Before this change the same request shows `uri=UNKNOWN` and no
`grails.controller` / `grails.render` spans.
## Notes / follow-ups
- Default-action resolution uses an `'index'` fallback when the mapping
doesn't carry an action; resolving a controller's configured `defaultAction`
precisely would be a small refinement.
- The inline `Observation`s could be refactored into the same convention +
`ObservationDocumentation` kit used by the GSP rendering observations, for
consistency.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]