Skip to content

perf: single-pass chain traversal in LogValue/ToMap/formatVerbose - #124

Merged
samber merged 3 commits into
mainfrom
perf/single-pass-snapshot
May 28, 2026
Merged

perf: single-pass chain traversal in LogValue/ToMap/formatVerbose#124
samber merged 3 commits into
mainfrom
perf/single-pass-snapshot

Conversation

@samber

@samber samber commented May 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • LogValue(), ToMap(), and formatVerbose() each called ~13 independent accessors (Code(), Time(), Domain(), Tags(), Trace(), Hint(), Public(), Owner(), Context(), User(), Tenant(), request(), response()), each traversing the full error chain — O(n×h) traversals per render call
  • Add snapshot(OopsError) OopsError (package-private function) that walks the chain once, collecting scalars with deepest-non-zero semantics, merging maps, deduplicating tags, and resolving explicit-vs-auto trace priority
  • The three rendering methods call snapshot() at entry and read fields directly — O(h) instead of O(n×h)
  • Extract mergeMaps() from mergeNestedErrorMap() so the merge logic is shared

Benchmark results (1-level chain, Apple M3)

Before After Δ
BenchmarkToMap allocs/op 86 72 −16%
BenchmarkToMap B/op 10319 5533 −46%
BenchmarkLogValue allocs/op 84 70 −17%
BenchmarkLogValue B/op 10332 5546 −46%
BenchmarkMarshalJSON allocs/op 106 92 −13%
BenchmarkMarshalJSON B/op 11033 6241 −43%

Gains scale with chain depth.

Add snapshot(OopsError) OopsError that walks the error chain once,
collecting and merging all attributes (scalars, tags, maps). The three
hot-path rendering methods — LogValue, ToMap, formatVerbose — call
snapshot() at entry and read fields directly, reducing chain traversals
from O(n×h) to O(h) per render call.

Also extract mergeMaps() from mergeNestedErrorMap() so the merge logic
is shared between the existing public accessors and snapshot().

Results on a 1-level chain: -46% bytes/op, -14 allocs/op on ToMap and
LogValue. Gains scale with chain depth.
Copilot AI review requested due to automatic review settings May 28, 2026 14:20
@codecov

codecov Bot commented May 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.24324% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.49%. Comparing base (5c4605b) to head (cf57628).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
error.go 91.75% 4 Missing and 4 partials ⚠️
kv.go 96.07% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #124      +/-   ##
==========================================
+ Coverage   89.30%   89.49%   +0.19%     
==========================================
  Files          15       15              
  Lines        1159     1209      +50     
==========================================
+ Hits         1035     1082      +47     
- Misses        100      102       +2     
- Partials       24       25       +1     
Flag Coverage Δ
unittests 89.49% <93.24%> (+0.19%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Refactors LogValue, ToMap, and formatVerbose to traverse the OopsError chain a single time via a new package-private snapshot() helper, replacing ~13 independent O(h) accessor calls per render with one O(h) walk. Also extracts mergeMaps() from mergeNestedErrorMap() so the snapshot can reuse the merge logic without re-walking the chain.

Changes:

  • Add snapshot(OopsError) OopsError in error.go that resolves all chain-derived fields (deepest-non-zero scalars, merged/lazy/dereferenced maps, deduped tags, explicit-vs-auto trace) in a single recursive pass.
  • Rewrite LogValue, ToMap, and formatVerbose to call snapshot once and read fields directly from the resolved struct.
  • Extract mergeMaps([]map[string]any) from mergeNestedErrorMap in kv.go so it can be shared with snapshot.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
error.go Adds snapshot(); rewrites LogValue, ToMap, formatVerbose to use the single-pass result.
kv.go Extracts mergeMaps() from mergeNestedErrorMap() for reuse by snapshot().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@samber
samber merged commit b7740ed into main May 28, 2026
10 checks passed
@samber
samber deleted the perf/single-pass-snapshot branch May 28, 2026 14:27
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.

2 participants