Skip to content

perf(logger): build default log line without fmt.Sprintf - #4925

Open
TheNetherWatcher wants to merge 1 commit into
gin-gonic:masterfrom
TheNetherWatcher:perf/logger-formatter-append
Open

TheNetherWatcher wants to merge 1 commit into
gin-gonic:masterfrom
TheNetherWatcher:perf/logger-formatter-append

Conversation

@TheNetherWatcher

Copy link
Copy Markdown

defaultLogFormatter builds every request log line with one fmt.Sprintf call (13 args, padding verbs, %#v for the path). That costs about a dozen allocations per request, mostly from boxing the arguments into interface{} and the extra Format/String results.

This change appends the same fields into a byte slice instead:

  • time.AppendFormat for the timestamp and strconv.AppendInt for the status code
  • a small appendPadded helper for the %8v, %15s and %-7s fields. It counts runes like fmt does, so µs latencies and non-ASCII values pad the same way
  • strconv.AppendQuote for the path, which is what %#v does for a string

The output is byte-for-byte the same. To check that, I ran a throwaway test comparing the new function with the old Sprintf version over 48,600 combinations: color on and off, negative, short and 5-digit status codes, negative/µs/ms/hour latencies, IPv6 and non-ASCII client IPs, long and non-ASCII methods, and paths containing quotes, backslashes, control bytes, invalid UTF-8 and backticks. Every case matched.

Benchmarks

Apple M-series, Go 1.27.2. Runs alternated old/new, 6 each, medians shown. The machine was under load (load avg around 7), so the ns/op numbers are noisy. The alloc counts are exact.

Benchmark before after
BenchmarkLoggerMiddleware (existing) 560 ns/op, 252 B/op, 9 allocs/op 300 ns/op, 176 B/op, 4 allocs/op
defaultLogFormatter alone (new BenchmarkDefaultLogFormatter) 736 ns/op, 344 B/op, 14 allocs/op 399 ns/op, 144 B/op, 1 allocs/op

Tests

  • Added TestDefaultLogFormatterPadding, which covers rune-counted padding (25µs), values longer than their field width (an IPv6 address, PROPFIND), status 0, quoting of non-ASCII and control characters in the path, and an error message. It passes on unmodified master too, so it pins the existing output.
  • Added BenchmarkDefaultLogFormatter.
  • go test ./..., go test -race on the logger tests, go vet ./... and golangci-lint run all pass. The lint findings it reports are already on master and none are in logger.go.

Note: #4652 also edits logger.go. It changes only the fmt.Fprint call in LoggerWithConfig, so the two don't overlap in the diff. But that call is the last use of fmt in the file after this change, so whichever PR merges second needs to drop the fmt import.

@codecov

codecov Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.72%. Comparing base (0f09c3a) to head (50b159c).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4925      +/-   ##
==========================================
+ Coverage   98.65%   98.72%   +0.07%     
==========================================
  Files          48       48              
  Lines        3188     3220      +32     
==========================================
+ Hits         3145     3179      +34     
+ Misses         43       41       -2     
Flag Coverage Δ
--ldflags="-checklinkname=0" -tags sonic 98.71% <100.00%> (+0.07%) ⬆️
-tags go_json 98.65% <100.00%> (+0.07%) ⬆️
-tags nomsgpack 98.70% <100.00%> (+0.07%) ⬆️
go-1.26 98.41% <100.00%> (+0.07%) ⬆️
go-1.27 98.72% <100.00%> (+0.05%) ⬆️
macos-latest 98.72% <100.00%> (+0.07%) ⬆️
ubuntu-latest 98.72% <100.00%> (+0.07%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 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.

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