Skip to content

Optimize logging hot paths#17

Open
spa5k wants to merge 4 commits into
mainfrom
codex/logging-performance-api-cleanup
Open

Optimize logging hot paths#17
spa5k wants to merge 4 commits into
mainfrom
codex/logging-performance-api-cleanup

Conversation

@spa5k

@spa5k spa5k commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add typed field storage, field mapper helpers, async sink support, and adapter fast paths for slog/zap/zerolog
  • add prepared config/operation hot-path APIs and lower-allocation HTTP integration paths
  • keep the public API focused by hiding internal fast-path interfaces and removing narrow benchmark-only helpers

Verification

  • just test
  • go test -race ./...
  • go test -race ./... in integration/std, integration/gin, integration/echo
  • go vet ./... in every module
  • git diff --check

Summary by CodeRabbit

  • New Features
    • Added typed field helpers plus direct event/operation field APIs for structured recording.
    • Introduced AsyncSink with buffered background writes, optional drop handling, Flush()/Close(), and dropped-event reporting.
    • Added field enrichment and field mapping (redaction/transformation/drop) hooks.
    • Expanded operation lifecycle APIs, including prepared/fast-path variants.
    • Added new field-oriented sink write methods (including borrowed/slice and completion/operation-completion variants) across adapters.
  • Documentation
    • Updated README with typed fields, enrichment/redaction, async sink usage, and new lifecycle APIs.
  • Performance
    • Expanded benchmarks covering field-oriented and fast-path adapter behavior.
  • Performance / Reliability
    • Added pooled contexts and pooled event reuse to reduce allocations.

Add typed field storage, adapter fast paths, prepared operation APIs, async sink support, and lower-allocation HTTP integration paths. Keep the public surface focused by hiding internal fast-path interfaces and removing narrow benchmark-only helpers.
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@spa5k, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 27 minutes and 18 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 415fad91-0f4c-4a0c-a1ca-b743c876049c

📥 Commits

Reviewing files that changed from the base of the PR and between 181a116 and f64d53a.

📒 Files selected for processing (27)
  • BENCHMARK_REPORT.md
  • README.md
  • adapter/slog/sink.go
  • adapter/zap/benchmark_test.go
  • adapter/zerolog/benchmark_test.go
  • adapter/zerolog/sink_test.go
  • async_sink.go
  • async_sink_test.go
  • bench/happycontext_benchmark_test.go
  • bench/integration/router_benchmark_test.go
  • context.go
  • event.go
  • event_access.go
  • event_access_test.go
  • field_mapper_test.go
  • happycontext_test.go
  • integration/common/config.go
  • integration/common/config_test.go
  • integration/common/lifecycle.go
  • integration/common/request_context.go
  • operation_fields_test.go
  • operation_lifecycle.go
  • operation_policy.go
  • operation_test.go
  • prepared_config.go
  • sink.go
  • sink_write.go
📝 Walkthrough

Walkthrough

Adds typed field and borrowed-field contracts, refactors Event/context and operation lifecycle (prepared starts, finish paths, monotonic timing), introduces AsyncSink and sink routing helpers, extends slog/zap/zerolog adapters, rewires HTTP middleware/response-writer tracking, and updates benchmarks, tests, and docs.

Changes

Runtime contracts and helpers

Layer / File(s) Summary
Field, mapper, prepared-config, time helpers
field.go, field_mapper.go, prepared_config.go, fast_time.go, sink.go
Adds Field/BorrowedField types and constructors, FieldMapper helpers (Redact/Drop/Chain), PreparedConfig/PrepareConfig, monotonic helpers (monotonicNow, durationSinceState), and FieldKind enum with borrowed-field interfaces.

Event, pooled contexts, and direct APIs

Layer / File(s) Summary
Event storage and pool
event.go, event_pool.go, event_access.go
Refactors Event to dual list/map storage with lazy promotion, adds event pool and pooled allocation/release helpers, and updates snapshot/access semantics.
Pooled contexts and context APIs
context.go, event_access_test.go
Adds NewPooledContext / ReleasePooledContext, pooled context wrapper type, and tests validating pooled-context behavior and parent-value delegation.
Event direct APIs
event_fields.go, event_fields_test.go
Adds Event methods for generic and typed field additions plus setters (route/error/message/level) with comprehensive tests.
Context-based typed mutators
field.go, field_test.go
Adds AddField, AddFields, AddString, Add2Strings, and other typed context helpers that use FromContext to access event.

Sink routing, AsyncSink, and adapters

Layer / File(s) Summary
Sink contracts and write routing
sink.go, sink_write.go
Adds FieldKind/BorrowedField and multiple internal sink interfaces for field/completion/operation variants; implements writeEventToSink routing for borrowed-field, typed-field, and unsafe map paths; adds operation field helpers.
AsyncSink wrapper
async_sink.go, async_sink_test.go
Implements AsyncSink with buffered queue, DropWhenFull/OnDrop, background dispatch, Flush/Close, dropped counter, and unit tests for field copy, ordering, and drop behavior.
Adapter sink extensions
adapter/slog/sink.go, adapter/zap/sink.go, adapter/zerolog/sink.go
Extends adapters with WriteUnsafe, WriteFields, WriteBorrowedFields, and completion/operation-completion methods; adds mapping helpers and deterministic key ordering support.
Adapter benchmarks
adapter/slog/benchmark_test.go, adapter/zap/benchmark_test.go, adapter/zerolog/benchmark_test.go
Adds benchmarks exercising field-slice write APIs (WriteFields, WriteFieldsWithCompletion, WriteFieldsWithOperationCompletion) for small and medium field sets.

Operation lifecycle and policies

Layer / File(s) Summary
Operation types and prepared starts
operation_types.go, prepared_operation.go
Adds operation internal flags (startMono/defer/unsafe/noTiming), extends OperationFinish with StartComplete and UnsafeEvent, and implements PreparedOperationStart with local/in-place/no-timing start variants.
Operation field APIs
operation_fields.go, operation_fields_test.go
Adds Operation methods mirroring Event typed-field helpers (Add/Add2/AddField/AddString/etc.) and mutation methods (Error/SetMessage/SetLevel) with tests.
Finish pipeline refactor
operation_lifecycle.go, operation_policy.go, operation_test.go
Adds Finish/FinishPrepared, FinishPreparedOperation, consolidates finish logic into prepared path with sampling/enricher/mapper points; updates sampling helpers and tests covering prepared/local/in-place/no-timing behaviors and HTTP status-based built-in sampling.

HTTP integrations and response tracking

Layer / File(s) Summary
Request prepared config and lifecycle
integration/common/config.go, integration/common/lifecycle.go, integration/common/config_test.go
Adds PreparedRequestConfig, PrepareRequestConfig, and FinalizePreparedRequest tying prepared config into request finalization with route and status updates.
Unsafe request-context swap
integration/common/request_context.go, integration/common/lifecycle_test.go
Implements SwapRequestContextUnsafe using reflection/unsafe to swap the internal http.Request context; adds tests validating swap/restore behavior and finalization correctness.
Tracked response writer and std middleware
integration/std/response_writer.go, integration/std/middleware.go
Adds pooled tracked response-writer wrappers preserving optional interfaces (Flusher/Hijacker/Pusher/ReaderFrom/CloseNotify) and status tracking; std middleware now uses wrapper + prepared finalization, removing httpsnoop dependency.
Framework middleware updates
integration/gin/middleware.go, integration/echo/middleware.go
Both frameworks now use PrepareRequestConfig, attempt SwapRequestContextUnsafe, conditionally restore original context, and call FinalizePreparedRequest.
Middleware validation tests
integration/std/middleware_test.go, integration/gin/middleware_test.go, integration/echo/middleware_test.go, integration/worker/worker_test.go
Adds tests for request-context restoration, optional-interface preservation, and updates worker event field assertions.

Benchmarks, docs, and cleanup

Layer / File(s) Summary
Operation and sink benchmarks
bench/happycontext_benchmark_test.go, bench/integration/router_benchmark_test.go
Adds operation lifecycle benchmarks (fast-sink and sampled-out variants) covering StartOperationLocal/StartOperationInPlace/PreparedOperationStart; introduces discardFieldSink; updates router benchmarks to use hc.AddString typed helpers.
Docs and benchmark report
README.md, BENCHMARK_REPORT.md
Documents typed field helpers, Enrichers and FieldMapper, AsyncSink usage and Flush semantics, new hot-path operation APIs (local/in-place/prepared), monotonic-only timing behavior, and adds a focused benchmark update section.
Module cleanup
bench/go.mod, cmd/examples/go.mod, integration/std/go.mod
Removes indirect github.com/felixge/httpsnoop dependency references.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • happytoolin/happycontext#2: Sampling API refresh work that complements the sampling-decision refactoring in this PR's operation_policy.go.
  • happytoolin/happycontext#3: Core context field-ingestion API changes that are directly related to the Add method variadic kv handling changes in this PR's context.go.
  • happytoolin/happycontext#15: Operation lifecycle refactoring extended by this PR's prepared-operation and finish pipeline changes.

Poem

🐰 A rabbit hops through fields well-typed and bright,
With borrowed data flowing left and right,
AsyncSinks hum as adapters spin their song,
Middleware swaps contexts all day long,
Events hop out, prepared and all OK!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.40% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title "Optimize logging hot paths" accurately summarizes the main objective of the PR, which focuses on performance optimizations for logging operations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/logging-performance-api-cleanup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (4)
async_sink.go (1)

77-91: 💤 Low value

Optional: Avoid redundant field map copy on drop path.

When an entry is dropped, dropFields() copies the map again (line 90), but fields is already a copy from line 83. Since the dropped entry won't be used, you can directly return the existing copy to the OnDrop callback.

♻️ Suggested simplification
 func (a *AsyncSink) Write(level Level, message string, fields map[string]any) {
 	if a == nil || a.sink == nil {
 		return
 	}

 	fields = copyFieldMap(fields)
 	entry := asyncEntry{
 		level:   level,
 		message: message,
 		fields:  fields,
 	}

-	a.enqueue(entry, func() map[string]any { return copyFieldMap(fields) })
+	a.enqueue(entry, func() map[string]any { return fields })
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@async_sink.go` around lines 77 - 91, The Write method creates a copy of
fields with copyFieldMap and then hands an OnDrop callback that redundantly
calls copyFieldMap again via dropFields; update AsyncSink.Write so the enqueue
OnDrop callback returns the already-copied fields directly (return the local
variable fields) instead of calling copyFieldMap a second time, referencing
AsyncSink.Write, enqueue, dropFields and copyFieldMap to locate the change.
event.go (1)

472-482: 💤 Low value

Remove unused fieldValueLocked method.

Static analysis indicates this method is unused. Consider removing it to reduce code maintenance burden, or add a test/usage if it's intended for future use.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@event.go` around lines 472 - 482, Remove the unused method
Event.fieldValueLocked: locate the method named fieldValueLocked on the Event
type (it references e.fieldMap and e.fieldList) and delete the entire function
implementation; if the functionality is intended to be kept, instead add a unit
test or caller that uses Event.fieldValueLocked so static analysis no longer
reports it as unused. Ensure any references to the method are updated/removed
accordingly.

Source: Linters/SAST tools

integration/std/response_writer.go (1)

22-39: 💤 Low value

Consider adding a code generation comment for the wrapper combinatorics.

The 16 wrapper types and pools correctly cover all combinations of Flusher, Hijacker, Pusher, and ReaderFrom. While this approach is necessary to preserve optional interfaces, a brief comment noting the combinatorial pattern (or indicating this could be code-generated) would help future maintainers understand the structure.

Also applies to: 41-81, 83-134

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integration/std/response_writer.go` around lines 22 - 39, Add a brief
code-generation comment above the block that declares the responseWriter*
sync.Pool variables (e.g., responseWriterPool ... responseWriterFHPRPool) and
the corresponding wrapper types to explain these 16 combinatorial wrappers
preserve optional interfaces (Flusher, Hijacker, Pusher, ReaderFrom) and were
intentionally enumerated rather than composed at runtime; note that the pattern
is combinatorial and could be generated by tooling to aid future maintainers.
integration/common/lifecycle_test.go (1)

31-31: ⚡ Quick win

Use httptest.NewRequestWithContext to satisfy noctx linter rule.

The static analysis tool flags this line. Since you're immediately calling .WithContext(base), using NewRequestWithContext is cleaner:

-	req := httptest.NewRequest(http.MethodGet, "/x", nil).WithContext(base)
+	req := httptest.NewRequestWithContext(base, http.MethodGet, "/x", nil)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integration/common/lifecycle_test.go` at line 31, Replace the
httptest.NewRequest(...).WithContext(base) call with
httptest.NewRequestWithContext to satisfy the noctx linter; update the creation
of req in the test (the line that currently uses httptest.NewRequest and then
.WithContext(base)) to call httptest.NewRequestWithContext(http.MethodGet, "/x",
base) so it directly embeds the base context.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@adapter/slog/benchmark_test.go`:
- Around line 36-38: The benchmark is pre-populating completion-like keys in the
shared field slices (created by fieldListFromMap from benchFieldsSmall and
medium and stored in smallFields and mediumFields) which then get passed again
as completion metadata in the completion-path sub-benchmarks; remove those
completion-related keys (e.g. "http.status", "duration_ms" or any completion
metadata) from the maps used to build smallFields/mediumFields or create
separate field maps for the completion-path benchmarks so the completion
parameters passed to the OperationStart/OperationEnd code are the only source of
completion metadata; update the sub-benchmarks that call
fieldListFromMap(benchFieldsSmall)/fieldListFromMap(medium) to use the
non-completion versions (or new maps) to avoid duplicate/conflicting fields.

In `@adapter/zap/benchmark_test.go`:
- Around line 41-43: The benchmark inputs smallFields and mediumFields are built
from maps (benchFieldsSmall and medium) that include completion-related keys
like "http.status" and "duration_ms", which contaminates the completion-path
benchmarks; update the setup used by the completion benchmarks (where you call
fieldListFromMap to create smallFields and mediumFields and where the completion
metadata is passed) to filter out completion-specific keys (e.g., "http.status",
"duration_ms", or any completion metadata) before creating the slices or else
create dedicated maps for completion benchmarks without those keys so completion
measurements use only non-completion fields.

In `@adapter/zerolog/benchmark_test.go`:
- Around line 35-37: The benchmark is mixing completion metadata into the
baseline field sets: change how smallFields and mediumFields are built so they
don't include completion-like keys (e.g., "http.status", "duration_ms") by
creating separate baseline maps (or stripping those keys from benchFieldsSmall
and medium) and call fieldListFromMap(baselineSmall) /
fieldListFromMap(baselineMedium) for smallFields/mediumFields; keep completion
metadata passed only as explicit args in the completion benchmarks (refer to
fieldListFromMap, benchFieldsSmall, medium, smallFields, mediumFields and
hc.OperationStart).

In `@adapter/zerolog/sink.go`:
- Around line 139-215: The completion write methods
(WriteBorrowedFieldsWithCompletion, WriteFieldsWithOperationCompletion,
WriteBorrowedFieldsWithOperationCompletion) currently append fields in insertion
order and ignore z.deterministicOrder; modify each to check z.deterministicOrder
and, if true, collect field keys/values, sort by key, and append in sorted order
(using appendField/appendBorrowedField for each sorted entry), otherwise keep
the existing loop; ensure appendOperationFields and the duration/op.*/Msg chain
remain unchanged and are applied after appending the (sorted or unsorted)
fields.

In `@event_fields_test.go`:
- Line 10: Tests currently pass nil for a context (triggering SA1012); update
the test calls that use NewContext(nil) to use context.Background() instead
(import context if not present) so replace occurrences like NewContext(nil) in
event_fields_test.go (and any other test calls at the same locations) with
NewContext(context.Background()) to satisfy the linter.

In `@operation_lifecycle.go`:
- Around line 349-351: The sampling decision currently uses result.Code
everywhere and ignores state.statusCode, which can drop StartComplete HTTP
events with 5xx statuses; change the logic in the complete-start fast path to
prefer state.statusCode when present: compute a statusCodeToUse (use
state.statusCode if non-zero/available, otherwise fall back to result.Code), use
that statusCodeToUse when setting hasError (instead of result.Code) and pass
statusCodeToUse into shouldWriteOperationDefaultPrepared (replace both
result.Code args) so sampling reflects the event's actual HTTP status; refer to
state.statusCode, hasError, result.Code, shouldWriteOperationDefaultPrepared,
prepared, OperationPolicy, outcome, and level when making the change.

---

Nitpick comments:
In `@async_sink.go`:
- Around line 77-91: The Write method creates a copy of fields with copyFieldMap
and then hands an OnDrop callback that redundantly calls copyFieldMap again via
dropFields; update AsyncSink.Write so the enqueue OnDrop callback returns the
already-copied fields directly (return the local variable fields) instead of
calling copyFieldMap a second time, referencing AsyncSink.Write, enqueue,
dropFields and copyFieldMap to locate the change.

In `@event.go`:
- Around line 472-482: Remove the unused method Event.fieldValueLocked: locate
the method named fieldValueLocked on the Event type (it references e.fieldMap
and e.fieldList) and delete the entire function implementation; if the
functionality is intended to be kept, instead add a unit test or caller that
uses Event.fieldValueLocked so static analysis no longer reports it as unused.
Ensure any references to the method are updated/removed accordingly.

In `@integration/common/lifecycle_test.go`:
- Line 31: Replace the httptest.NewRequest(...).WithContext(base) call with
httptest.NewRequestWithContext to satisfy the noctx linter; update the creation
of req in the test (the line that currently uses httptest.NewRequest and then
.WithContext(base)) to call httptest.NewRequestWithContext(http.MethodGet, "/x",
base) so it directly embeds the base context.

In `@integration/std/response_writer.go`:
- Around line 22-39: Add a brief code-generation comment above the block that
declares the responseWriter* sync.Pool variables (e.g., responseWriterPool ...
responseWriterFHPRPool) and the corresponding wrapper types to explain these 16
combinatorial wrappers preserve optional interfaces (Flusher, Hijacker, Pusher,
ReaderFrom) and were intentionally enumerated rather than composed at runtime;
note that the pattern is combinatorial and could be generated by tooling to aid
future maintainers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cdd9ccec-ea97-4fb5-a108-08032f709536

📥 Commits

Reviewing files that changed from the base of the PR and between c3f1364 and 284399c.

⛔ Files ignored due to path filters (3)
  • bench/go.sum is excluded by !**/*.sum
  • cmd/examples/go.sum is excluded by !**/*.sum
  • integration/std/go.sum is excluded by !**/*.sum
📒 Files selected for processing (53)
  • BENCHMARK_REPORT.md
  • README.md
  • adapter/slog/benchmark_test.go
  • adapter/slog/sink.go
  • adapter/zap/benchmark_test.go
  • adapter/zap/sink.go
  • adapter/zerolog/benchmark_test.go
  • adapter/zerolog/sink.go
  • async_sink.go
  • async_sink_test.go
  • bench/go.mod
  • bench/happycontext_benchmark_test.go
  • bench/integration/router_benchmark_test.go
  • cmd/examples/go.mod
  • config.go
  • context.go
  • event.go
  • event_access.go
  • event_access_test.go
  • event_fields.go
  • event_fields_test.go
  • event_pool.go
  • fast_time.go
  • field.go
  • field_mapper.go
  • field_mapper_test.go
  • field_test.go
  • happycontext.go
  • integration/common/config.go
  • integration/common/config_test.go
  • integration/common/lifecycle.go
  • integration/common/lifecycle_test.go
  • integration/common/request_context.go
  • integration/echo/middleware.go
  • integration/echo/middleware_test.go
  • integration/gin/middleware.go
  • integration/gin/middleware_test.go
  • integration/std/go.mod
  • integration/std/middleware.go
  • integration/std/middleware_test.go
  • integration/std/response_writer.go
  • integration/worker/worker_test.go
  • operation_fields.go
  • operation_fields_test.go
  • operation_lifecycle.go
  • operation_policy.go
  • operation_test.go
  • operation_types.go
  • options.go
  • prepared_config.go
  • prepared_operation.go
  • sink.go
  • sink_write.go
💤 Files with no reviewable changes (3)
  • bench/go.mod
  • cmd/examples/go.mod
  • integration/std/go.mod

Comment thread adapter/slog/benchmark_test.go Outdated
Comment thread adapter/zap/benchmark_test.go Outdated
Comment thread adapter/zerolog/benchmark_test.go Outdated
Comment thread adapter/zerolog/sink.go Outdated
Comment thread event_fields_test.go Outdated
Comment thread operation_lifecycle.go
Comment on lines +349 to +351
hasError := state.hasError || result.Code >= 500 || outcome != OutcomeSuccess
if !shouldWriteOperationDefaultPrepared(prepared, OperationPolicy{}, hasError, result.Code, result.Code, outcome, level) {
return true, false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use event status when deciding default sampling in complete-start fast path.

This branch ignores state.statusCode and always evaluates sampling with result.Code, so a StartComplete=true HTTP event can be dropped even when http.status is 5xx.

💡 Proposed fix
-	hasError := state.hasError || result.Code >= 500 || outcome != OutcomeSuccess
-	if !shouldWriteOperationDefaultPrepared(prepared, OperationPolicy{}, hasError, result.Code, result.Code, outcome, level) {
+	statusCode := result.Code
+	if state.hasStatus {
+		statusCode = state.statusCode
+	}
+	hasError := state.hasError || result.Code >= 500 || statusCode >= 500 || outcome != OutcomeSuccess
+	if !shouldWriteOperationDefaultPrepared(prepared, OperationPolicy{}, hasError, result.Code, statusCode, outcome, level) {
 		return true, false
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
hasError := state.hasError || result.Code >= 500 || outcome != OutcomeSuccess
if !shouldWriteOperationDefaultPrepared(prepared, OperationPolicy{}, hasError, result.Code, result.Code, outcome, level) {
return true, false
statusCode := result.Code
if state.hasStatus {
statusCode = state.statusCode
}
hasError := state.hasError || result.Code >= 500 || statusCode >= 500 || outcome != OutcomeSuccess
if !shouldWriteOperationDefaultPrepared(prepared, OperationPolicy{}, hasError, result.Code, statusCode, outcome, level) {
return true, false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@operation_lifecycle.go` around lines 349 - 351, The sampling decision
currently uses result.Code everywhere and ignores state.statusCode, which can
drop StartComplete HTTP events with 5xx statuses; change the logic in the
complete-start fast path to prefer state.statusCode when present: compute a
statusCodeToUse (use state.statusCode if non-zero/available, otherwise fall back
to result.Code), use that statusCodeToUse when setting hasError (instead of
result.Code) and pass statusCodeToUse into shouldWriteOperationDefaultPrepared
(replace both result.Code args) so sampling reflects the event's actual HTTP
status; refer to state.statusCode, hasError, result.Code,
shouldWriteOperationDefaultPrepared, prepared, OperationPolicy, outcome, and
level when making the change.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
adapter/zerolog/sink.go (1)

141-158: ⚡ Quick win

Avoid creating an unused zerolog event on deterministic paths.

On Line 141, Line 171, Line 201, and Line 233, z.newEvent(level) is called before checking z.deterministicOrder, but the deterministic branch immediately delegates to z.Write(...) (which creates its own event). Move event creation after the deterministic branch to avoid extra hot-path work.

💡 Suggested refactor
 func (z *Sink) WriteFieldsWithCompletion(level hc.Level, message string, fields []hc.Field, durationMS int64, code int, outcome hc.Outcome) {
-	event := z.newEvent(level)
-	if event == nil {
-		return
-	}
 	if message == "" {
 		message = hc.DefaultMessage
 	}
 	if z.deterministicOrder {
 		m := mapFromFields(fields)
 		if m == nil {
 			m = make(map[string]any, 3)
 		}
 		m["duration_ms"] = durationMS
 		m["op.code"] = code
 		m["op.outcome"] = string(outcome)
 		z.Write(level, message, m)
 		return
 	}
+	event := z.newEvent(level)
+	if event == nil {
+		return
+	}
 
 	for _, field := range fields {
 		event = appendField(event, field.Key, field.Value)
 	}

Apply the same reordering pattern to:

  • WriteBorrowedFieldsWithCompletion
  • WriteFieldsWithOperationCompletion
  • WriteBorrowedFieldsWithOperationCompletion

Also applies to: 171-188, 201-219, 233-251

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adapter/zerolog/sink.go` around lines 141 - 158, The code currently calls
z.newEvent(level) before checking z.deterministicOrder, causing an unused
zerolog event on deterministic paths; move the z.newEvent(level) call to after
the deterministicOrder check so the deterministic branch immediately builds the
map and calls z.Write(...) without allocating an event. Apply this change in the
four affected functions—WriteBorrowedFieldsWithCompletion,
WriteFieldsWithOperationCompletion, WriteBorrowedFieldsWithOperationCompletion
and the other similar writer—so each function checks z.deterministicOrder first,
handles m := mapFromFields(fields)/m initialization, sets
duration_ms/op.code/op.outcome and calls z.Write(level, message, m), and only
calls z.newEvent(level) when deterministicOrder is false.
integration/std/response_writer.go (1)

16-18: 💤 Low value

Note: http.CloseNotifier is deprecated.

The CloseNotify interface is deprecated since Go 1.11 in favor of http.Request.Context(). The preservation here is reasonable for backward compatibility with older middleware or servers, but consider adding a code comment documenting this deprecation context.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integration/std/response_writer.go` around lines 16 - 18, The closeNotifier
interface that defines CloseNotify() is using the deprecated http.CloseNotifier
API; update the code by adding a concise comment above the type declaring that
CloseNotify is deprecated since Go 1.11 and recommending use of
http.Request.Context() for cancellation, mention why the compatibility shim
remains (backward compatibility with older middleware/servers) and reference the
closeNotifier type and CloseNotify method in that comment so future readers know
to prefer request.Context() when possible.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@adapter/zerolog/sink.go`:
- Around line 141-158: The code currently calls z.newEvent(level) before
checking z.deterministicOrder, causing an unused zerolog event on deterministic
paths; move the z.newEvent(level) call to after the deterministicOrder check so
the deterministic branch immediately builds the map and calls z.Write(...)
without allocating an event. Apply this change in the four affected
functions—WriteBorrowedFieldsWithCompletion, WriteFieldsWithOperationCompletion,
WriteBorrowedFieldsWithOperationCompletion and the other similar writer—so each
function checks z.deterministicOrder first, handles m := mapFromFields(fields)/m
initialization, sets duration_ms/op.code/op.outcome and calls z.Write(level,
message, m), and only calls z.newEvent(level) when deterministicOrder is false.

In `@integration/std/response_writer.go`:
- Around line 16-18: The closeNotifier interface that defines CloseNotify() is
using the deprecated http.CloseNotifier API; update the code by adding a concise
comment above the type declaring that CloseNotify is deprecated since Go 1.11
and recommending use of http.Request.Context() for cancellation, mention why the
compatibility shim remains (backward compatibility with older
middleware/servers) and reference the closeNotifier type and CloseNotify method
in that comment so future readers know to prefer request.Context() when
possible.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0c34bd3b-6130-4f2e-b3ef-26e516fc6738

📥 Commits

Reviewing files that changed from the base of the PR and between 284399c and 5eaa8ed.

📒 Files selected for processing (9)
  • adapter/zerolog/sink.go
  • adapter/zerolog/sink_test.go
  • event.go
  • event_fields_test.go
  • event_pool.go
  • integration/common/lifecycle.go
  • integration/common/lifecycle_test.go
  • integration/std/middleware_test.go
  • integration/std/response_writer.go
💤 Files with no reviewable changes (1)
  • event.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • event_pool.go
  • integration/std/middleware_test.go
  • event_fields_test.go

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.

1 participant