Trace inbound Nexus HTTP requests - #11560
Conversation
cf4e7b1 to
f1d6dd9
Compare
f1d6dd9 to
bc7ea0c
Compare
bc7ea0c to
0e3d8cb
Compare
|
Claude finished @stephanos's task in 5m 51s —— View job Reviewed 5 files, 4 findings. small — The inbound Nexus completion callback route is left uninstrumented.
If leaving them out is deliberate scoping, worth saying so in the PR description, since the title reads as covering all inbound Nexus requests. Suggestion: wrap both completion routes with |
| ) | ||
| } | ||
|
|
||
| func getDispatchByEndpointURL(address string, endpoint string) string { |
There was a problem hiding this comment.
Made this a method on the test env instead.
There was a problem hiding this comment.
It looks like all the callers touched passed env.Namespace().String() as the first parameter. Is that always the case? And if so, can we simplify the method to just be dispatchByTaskQueueURL(...)? (And get the test environment's namespace from within the function, just to elide one of the parameters?)
There was a problem hiding this comment.
Good observation! I'll make the simplification.
There was a problem hiding this comment.
(there are still 2 callers that pass explicit namespaces, I'll leave the helper for that but default to the env's ns otherwise)
|
Claude finished @stephanos's task in 6m 54s —— View job Reviewed 8 files, 4 findings. small — The inbound Nexus completion callback route is still uninstrumented.
Re-raising because it is unaddressed, not to relitigate: if the scoping is deliberate, saying so in the PR description is enough, since the title reads as covering all inbound Nexus requests. Suggestion: wrap both completion routes with |
| exporter := tracetest.NewInMemoryExporter() | ||
| env := s.newTestEnv(exporter) | ||
| // Verifies asynchronous start and cancellation connect real History client and Frontend server spans. | ||
| func (s *NexusOTELSuite) TestOperation() { |
There was a problem hiding this comment.
Re-did these to be more end-to-end.
chrsmith
left a comment
There was a problem hiding this comment.
Lots of minor nitpicks and suggestions. Everything seems right, but there's enough moving parts that if we can think of ways to make the code easier to read/reason about it might be helpful down the line.
| } | ||
|
|
||
| // HTTPHandlerWrapper wraps HTTP handlers with the service's tracing configuration. | ||
| type HTTPHandlerWrapper func(http.Handler, string) http.Handler |
There was a problem hiding this comment.
Nit: I don't know if there is a better name, but HTTPHandlerWrapper sounds a little too generic. Like, it's a wrapper, sure. But the fact that it takes an http.Handler and a string seems odd. (Like, maybe "Named HTTP Handler" or something?)
If nothing comes to mind, feel free to keep it as-is. #namingishard
There was a problem hiding this comment.
Yeah I can see what you mean; it's meant to mirror HTTPClientTransportWrapper. But that one is simpler/clearer. Ultimately it does two things: wrap the http.Handler and give the span a name. 🤔
There was a problem hiding this comment.
Maybe?
HTTPClientTransportInstrumenterHTTPServerHandlerInstrumenter
| Handler(h.httpHandlerWrapper.Wrap(http.HandlerFunc(h.dispatchNexusTaskByNamespaceAndTaskQueue), strings.TrimPrefix(configs.DispatchNexusTaskByNamespaceAndTaskQueueAPIName, "/"))) | ||
| r.PathPrefix("/" + commonnexus.RouteDispatchNexusTaskByEndpoint.Representation() + "/"). | ||
| HandlerFunc(h.dispatchNexusTaskByEndpoint) | ||
| Handler(h.httpHandlerWrapper.Wrap(http.HandlerFunc(h.dispatchNexusTaskByEndpoint), strings.TrimPrefix(configs.DispatchNexusTaskByEndpointAPIName, "/"))) |
There was a problem hiding this comment.
Nit: The AI is a fan of having really dense, complex express expressions on a single line. And I'd argue we should be fearless about introducing new local variables when it helps with readability.
e.g. if you think the following is a net-improvement to readability, consider the following:
dispatchTaskByEndpointHandlerFn := http.HandlerFunc(h.dispatchNexusTaskByEndpoint)
dispatchEndpointName := strings.TrimPrefix(configs.DispatchNexusTaskByEndpointAPIName, "/")
r.PathPrefix("/" + commonnexus.RouteDispatchNexusTaskByEndpoint.Representation() + "/").
Handler(h.httpHandlerWrapper.Wrap(dispatchTaskHandlerFn, dispatchEndpointName))
That's still pretty hard to reason about, IMHO. But at least it's a little easier to see since there aren't so many nested expressions.
There was a problem hiding this comment.
I think I nudged it to be denser on that one actually; thinking that those routing expressions are best left compact/inline. But I can see how it's a lot. Let me extract some.
There was a problem hiding this comment.
I added a register helper now.
| ) | ||
| } | ||
|
|
||
| func getDispatchByEndpointURL(address string, endpoint string) string { |
There was a problem hiding this comment.
It looks like all the callers touched passed env.Namespace().String() as the first parameter. Is that always the case? And if so, can we simplify the method to just be dispatchByTaskQueueURL(...)? (And get the test environment's namespace from within the function, just to elide one of the parameters?)
| s.T(), | ||
| handlerEnv.dispatchByEndpointURL(handlerWorkerEndpoint.Id), | ||
| ) | ||
| operationPathSuffix := "/" + service.Name + "/" + operation.Name() |
There was a problem hiding this comment.
Nit: Instead of repeated string concatenation, fmt.Sprintf("/%s/%s", ...) might be easier to read. (Or not.)
There was a problem hiding this comment.
isn't the (less efficient) + approach more readable actually (no backtracking when reading)? 😬 🙈
There was a problem hiding this comment.
Yeah, you are right. Sorry about that. That was just code review muscle memory 😅
I think a case could be made if there were more concatenations and what not. But yeah, the "/" + ... + "/" + ... is probably as clear as it could be.
| RequestId: tv.RequestID(), | ||
| ScheduleToCloseTimeout: durationpb.New(time.Minute), | ||
| requestHeaders := nexus.Header{ | ||
| "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", |
There was a problem hiding this comment.
I don't see us validating that this header was passed through anywhere. Do we want to have a test that the header is found unmodified on the handler-side? (i.e. it wasn't stripped by the Temporal x Nexus machineary, and made its way to the other side?) Or is that outside the scope of what we want to test here?
There was a problem hiding this comment.
I looked at this more closely. requireExportedServerSpan verifies that this traceparent is extracted by the frontend. And nexus_api_test.go verifies that headers are forwarded already. I've added a comment to mention that.
| ) | ||
| } | ||
|
|
||
| func (s *NexusOTELSuite) requireExportedNexusHTTPSpanPair( |
There was a problem hiding this comment.
Can you please add a doc comment for what this is doing? From the logic it isn't immediately clear.
We're looping through the handlerSpans looking for a specific tag, and then making sure to find the child span in the client spans?
Maybe assertSpanPropagated? Or assertSpanParented? And maybe take in the parent span name as another parameter? That would make it a little clearer what's going on.
There was a problem hiding this comment.
Alright, that method is gone now. I made some sweeping changes in the test to make the assertions much more declarative.
| handlerExporter *tracetest.InMemoryExporter, | ||
| pathSuffix string, | ||
| ) { | ||
| s.Await(func(s *NexusOTELSuite) { |
There was a problem hiding this comment.
Nit: Maybe call s.T().Helper() before this? So that error messages would be attributed to the callsite, and not here?
44225a7 to
dcc6e28
Compare
a4ee9dd to
dad7a62
Compare
dad7a62 to
881e4cd
Compare
## What changed? Wrapped the frontend Nexus dispatch routes with the shared OpenTelemetry HTTP handler. ## Why? Nexus HTTP requests need an inbound server span to connect the caller trace. ## How did you test it? - [x] built - [ ] run locally and tested manually - [ ] covered by existing tests - [x] added new unit test(s) - [ ] added new functional test(s)
What changed?
Wrapped the frontend Nexus dispatch routes with the shared OpenTelemetry HTTP handler.
Why?
Nexus HTTP requests need an inbound server span to connect the caller trace.
How did you test it?