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]

Reply via email to