Skip to content

fix: record propagated errors once per request span - #200

Merged
Tony133 merged 1 commit into
mainfrom
fix/duplicate-exception-recording
Oct 25, 2026
Merged

Tony133 merged 1 commit into
mainfrom
fix/duplicate-exception-recording

Conversation

@mcollina

@mcollina mcollina commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Summary

Fixes #196.

When an instrumented handler or hook throws, the same error was recorded as an exception event on both the child span where it was thrown and the parent request span (via the request's onError hook). Backends that map each exception event to an error occurrence (e.g. Elastic APM) counted a single failure twice.

Changes

  • Track errors already recorded on a child (handler/hook) span in a Set scoped to the request.
  • In the request span's recordErrorInSpanHook, skip span.recordException when the propagated error was already recorded on a child span, while still marking the request span as ERROR with the error message.
  • Errors that never pass through a wrapped handler/hook — such as reply.send(err) or done(err) — are still recorded on the request span as before.
  • The per-request tracking also handles primitive throws (e.g. throw 'foo') and does not leak state across requests.

Tests

Added tests covering:

  • A thrown handler error produces one exception event (on the handler span), not on the request span.
  • A thrown hook error produces one exception event (on the hook span), not on the request span.
  • An error sent via reply.send(err) is still recorded on the request span.
  • A primitive throw is recorded once.

All tests pass for Fastify v4 and v5 with 100% coverage, TypeScript checks, and lint.

A single error thrown by an instrumented handler or hook was recorded as an
exception event on both the child span (where it was thrown) and the parent
request span via the onError hook, which backends count as two failures.

Track errors already recorded on a child (handler/hook) span in a Set scoped
to the request, and skip re-recording the same propagated exception on the
request span. The request span still gets an ERROR status with the error
message, and errors that never pass through a wrapped handler/hook (e.g.
reply.send(err), done(err)) are still recorded on the request span.

Fixes #196

@Tony133 Tony133 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@Tony133
Tony133 merged commit 6dbabe1 into main Oct 25, 2026
20 checks passed
@Tony133
Tony133 deleted the fix/duplicate-exception-recording branch October 25, 2026 10:43
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.

Same thrown exception recorded on both handler and request spans

3 participants