Skip to content

Log handler execution duration on the "Finished processing" message (closes #3063)#3067

Merged
jeremydmiller merged 1 commit into
mainfrom
feat-3063-log-handler-duration
Jun 10, 2026
Merged

Log handler execution duration on the "Finished processing" message (closes #3063)#3067
jeremydmiller merged 1 commit into
mainfrom
feat-3063-log-handler-duration

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #3063. Adds the handler's execution time to the execution-finished log so it's possible to pinpoint slow handlers in production logs (and filter for them in structured logging).

Change

The _executionFinished log message now reads:

{CorrelationId}: Finished processing {Name}#{Id}, executed in {Duration} ms

{Duration} is also a structured property (Duration), so you can filter for all handlers over some threshold across a time window — which was the actual ask in the issue (metrics are too coarse-grained; per-operation log math can't separate transport time from handler time).

The value comes from Envelope.ExecutionTime, which is set by Envelope.StopTiming() inside IMessageTracker.ExecutionFinished(...). Both executors log _executionFinished after that tracker call (in the finally / post-tracking paths), so the timing is always populated at the log site.

Applied to:

  • Executor.ExecuteAsync — the transport-received path
  • every _executionFinished call site in TracingExecutor (the InvokeTracing.Full path), including the inline-invoke and streaming branches, so the message is consistent wherever "Finished processing" is emitted

Test

execution_finished_logs_duration_3063 routes a deliberately-slow message (Task.Delay(25)) through the worker via SendMessageAndWaitAsync so it exercises Executor.ExecuteAsync (not the inline-invoke path), then asserts both that the rendered message contains executed in … ms and that the structured Duration property is present and > 0.

All 6 related CoreTests pass (the new test + the 5 existing invoke_tracing_mode tests). wolverine.slnx -c Release builds clean (0 warnings, 0 errors).

Note on log level

_executionFinished is emitted at HandlerChain.ProcessingLogLevel, which defaults to Debug — so the duration rides the existing "Finished processing" line rather than the Information-level "Successfully processed" line the issue author mentioned. This keeps the change to a single, already-existing log statement; if we'd rather surface the duration at Information level, that's a follow-up decision.

🤖 Generated with Claude Code

…H-3063)

When browsing production logs it is hard to tell how long an individual handler
took. The execution-finished log now includes the handler's execution time in
milliseconds, both in the rendered message ("...executed in {Duration} ms") and
as a structured "Duration" property so slow handlers can be found with
structured log filters over a time window.

The duration comes from Envelope.ExecutionTime, which is populated by
Envelope.StopTiming() inside IMessageTracker.ExecutionFinished. Both Executor
and TracingExecutor log _executionFinished after that tracker call (in the
finally / post-tracking paths), so the timing is always available at the log
site. Applied to every _executionFinished call site, including TracingExecutor's
inline-invoke and streaming paths.

Adds execution_finished_logs_duration_3063, which routes a (deliberately slow)
message through the worker so it exercises Executor.ExecuteAsync, then asserts
the rendered message carries "executed in ... ms" and that the structured
"Duration" property is present and greater than zero.

Closes #3063

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Log the processing time of each handler

1 participant