Skip to content

feat(http): enhance http logging with context-aware access logs and formatters#304

Merged
moshloop merged 2 commits intomasterfrom
pr/http-logging
Mar 25, 2026
Merged

feat(http): enhance http logging with context-aware access logs and formatters#304
moshloop merged 2 commits intomasterfrom
pr/http-logging

Conversation

@moshloop
Copy link
Member

@moshloop moshloop commented Mar 24, 2026

Adds context-aware HTTP logging via request context, new access log middleware with single-line output, form URL encoded formatter for request bodies, NO_COLOR environment variable support, and improved request/response logging configuration through TraceConfigFromString for flexible trace levels.

Summary by CodeRabbit

  • New Features

    • Added NO_COLOR environment variable and command-line flag support to disable ANSI coloring
    • Introduced access log mode for HTTP requests with simplified output format
    • Added form-encoded body formatter for improved request/response logging display
  • Improvements

    • Enhanced request URI and Host header printing in logs
    • Improved protocol display formatting
    • Better preservation of existing HTTP transports when using HAR collection
    • Adjusted log timestamp format for consistency

…ormatters

Adds context-aware HTTP logging via request context, new access log middleware with single-line output, form URL encoded formatter for request bodies, NO_COLOR environment variable support, and improved request/response logging configuration through TraceConfigFromString for flexible trace levels.
@moshloop moshloop enabled auto-merge (rebase) March 24, 2026 20:50
@coderabbitai
Copy link

coderabbitai bot commented Mar 24, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ae4c0b71-d9dd-43f4-ab2e-886a9493d004

📥 Commits

Reviewing files that changed from the base of the PR and between de926ef and f784108.

📒 Files selected for processing (3)
  • http/middlewares/logger.go
  • http/middlewares/trace.go
  • logger/httpretty/printer.go

Walkthrough

Changes across console output, context logger propagation, HTTP client OAuth transport, HTTP request/response logging, and logger formatting. Introduces NO_COLOR environment variable support, context-aware logger selection, improved OAuth token transport wrapping, refactored HTTP logging architecture with access log mode, and enhanced HTTP body formatters.

Changes

Cohort / File(s) Summary
Console NO_COLOR Support
console/console.go
Added IsNoColor() function that checks NO_COLOR, COLOR, TERM, and --no-color CLI flags to determine whether ANSI coloring should be disabled. Updated global isTTY initialization to respect this setting.
Context Logger APIs
context/context.go
Added logger context propagation: GetLogger() method, Value() override to intercept loggerKey lookups, and LoggerFromContext() function to retrieve logger from context with fallback to global logger.
HTTP Client OAuth Transport
http/client.go
Modified OAuth client setup to preserve and wrap pre-existing TokenTransport with HAR middleware instead of unconditionally replacing it, enabling middleware composition.
HTTP Logging Architecture
http/middlewares/logger.go, http/middlewares/trace.go
Refactored NewLogger to support two mutually exclusive logging strategies selected by AccessLog config: context-aware detailed logging via httpretty or lightweight single-line access logs. Added TraceConfigFromString() factory and AccessLog field to TraceConfig. Introduced media-type-specific formatters (JSON, form-urlencoded) and request-context logger selection.
Logger Core and Time Format
logger/default.go, logger/slog.go
Changed IsLevelEnabled() signature to accept any type instead of int and forward directly to logger. Adjusted slog text handler TimeFormat from "15:04:05.999" to "15:04:05.000".
HTTP Pretty Printing Enhancements
logger/httpretty/httpretty.go, logger/httpretty/printer.go
Added FormURLEncodedFormatter for parsing and displaying form-encoded request bodies. Updated request URI printing to handle empty Host with fallback synthesis. Modified protocol display to omit standard HTTP/1.1. Refined Host header handling to avoid redundant overwrites.

Possibly related PRs

Suggested labels

released

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objectives of the changeset, which enhances HTTP logging capabilities with context-aware features and new formatters.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pr/http-logging
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch pr/http-logging

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.

Copy link

@coderabbitai coderabbitai bot left a comment

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 (1)
http/middlewares/logger.go (1)

27-39: Redundant JSON validation logic.

The check on lines 28-31 is logically questionable: if json.Valid(src) returns false, json.Unmarshal will also fail since both use the same underlying validation. The unmarshal attempt appears to serve no purpose.

♻️ Proposed simplification
 func (j *jsonFormatter) Format(w io.Writer, src []byte) error {
-	if !json.Valid(src) {
-		if err := json.Unmarshal(src, &json.RawMessage{}); err != nil {
-			return err
-		}
+	if !json.Valid(src) {
+		return fmt.Errorf("invalid JSON")
 	}
 	var indented bytes.Buffer
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@http/middlewares/logger.go` around lines 27 - 39, The pre-validation using
json.Valid and the subsequent json.Unmarshal in jsonFormatter.Format is
redundant and should be removed; instead, let json.Indent handle invalid JSON
and return its error directly. Edit the jsonFormatter.Format function to remove
the if !json.Valid(...) { ... } block (the json.Unmarshal call) so the function
simply attempts json.Indent into the indented buffer and returns any error from
json.Indent, then writes the formatted output with api.CodeBlock(...).ANSI().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@console/console.go`:
- Around line 54-75: The NO_COLOR check is only applied to console.isTTY via the
package-level isTTY variable, but logger/slog.go uses its own detection (isTTY,
flags.color, tint.Options.NoColor), so NO_COLOR is not globally honored; fix by
centralizing the no-color decision: expose and use console.IsNoColor (or provide
a shared function like console.NoColorEnabled) from logger/slog.go and ensure
logger's color logic consults that before applying colors (i.e., short-circuit
flags.color/tint.Options.NoColor when IsNoColor() returns true), or move the
isTTY/no-color detection into a shared utility used by both console.isTTY and
logger/slog.go (referencing IsNoColor, isTTY, flags.color, and
tint.Options.NoColor to locate the related logic).

In `@http/middlewares/accesslog.go`:
- Around line 3-20: Replace direct fmt.Printf usage in NewAccessLog /
RoundTripperFunc with the request-scoped logger obtained from req.Context()
(e.g., logger := logger.FromContext(req.Context()) or the project's equivalent)
and emit access entries via that logger at the appropriate level; also sanitize
the URI before logging by removing userinfo and sensitive query parameters (or
use a redacted URL representation) instead of logging req.URL verbatim, and
include elapsed time and resp.StatusCode or error in the structured log entry.

In `@http/middlewares/logger.go`:
- Around line 134-137: The log statements in logger.go use
console.Bluef(req.Method) which treats req.Method as a format string and can
panic if it contains '%' — change both calls to use a constant format string
like "%s" and pass req.Method as an argument (e.g., console.Bluef("%s",
req.Method)), doing the same for console.Yellowf("%s", req.URL) and the
statusColor(resp.StatusCode) call so all formatting uses constant format strings
(keep the surrounding log.Infof calls intact).
- Around line 81-113: The shared bytes.Buffer `buf` created once for the
middleware causes a data race across concurrent RoundTrip calls; fix by
allocating a new buffer per request and wiring it into the logger before
invoking the inner RoundTripper — e.g., inside the RoundTripperFunc create a
local `bytes.Buffer`, call `l.SetOutput(&buf)` on a per-request copy of the
logger if necessary (or otherwise obtain a fresh logger instance), then use that
local msg for the timing/suffix logic and call `getLogger(req).Infof(...)`;
alternatively, if reusing buffers is desired for performance, use a `sync.Pool`
to borrow/return buffers per request instead of sharing a single `buf`.

In `@http/middlewares/trace.go`:
- Around line 88-142: TraceConfigFromString currently returns early for exact
string cases (e.g., "access", "debug"/"headers", "body",
"trace"/"all"/"response") which skips the property-based overrides; refactor so
the function first selects a base TraceConfig (use traceHeaders, traceAll, or
TraceConfig literals depending on the switch) but does not return immediately,
then apply the property checks using properties.On(false, "http.body.disabled")
and properties.On(false, "http.headers.disabled") to mutate the chosen config
(clearing Body/Response and Headers/ResponseHeaders as appropriate), and finally
return the modified config; keep references to traceHeaders, traceAll and
properties.On in your changes and ensure TraceConfigFromString always applies
those global overrides.

In `@logger/httpretty/printer.go`:
- Around line 619-629: The code currently falls back to a hardcoded "https" when
req.URL.Scheme is empty, which can mislabel HTTP requests; change the fallback
to inspect req.TLS and set scheme = "https" if req.TLS != nil, otherwise scheme
= "http" (keeping the existing logic that uses req.Host / req.Header.Get("Host")
and builds uri via fmt.Sprintf). Update the block that assigns uri (referencing
uri, req.URL.Scheme, req.TLS, req.Host) so the scheme is determined from req.TLS
instead of always using "https".

---

Nitpick comments:
In `@http/middlewares/logger.go`:
- Around line 27-39: The pre-validation using json.Valid and the subsequent
json.Unmarshal in jsonFormatter.Format is redundant and should be removed;
instead, let json.Indent handle invalid JSON and return its error directly. Edit
the jsonFormatter.Format function to remove the if !json.Valid(...) { ... }
block (the json.Unmarshal call) so the function simply attempts json.Indent into
the indented buffer and returns any error from json.Indent, then writes the
formatted output with api.CodeBlock(...).ANSI().
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 10eb9467-2256-44a3-a3e0-e38657bf8b9e

📥 Commits

Reviewing files that changed from the base of the PR and between 11fd585 and de926ef.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (10)
  • console/console.go
  • context/context.go
  • http/client.go
  • http/middlewares/accesslog.go
  • http/middlewares/logger.go
  • http/middlewares/trace.go
  • logger/default.go
  • logger/httpretty/httpretty.go
  • logger/httpretty/printer.go
  • logger/slog.go

Comment on lines +54 to +75
isTTY = is.TTY() && !IsNoColor()
)

func IsNoColor() bool {
if os.Getenv("NO_COLOR") != "" {
return true
}
if v := strings.ToLower(os.Getenv("COLOR")); v == "no" || v == "false" {
return true
}
if os.Getenv("TERM") == "dumb" {
return true
}

// also scan os.Args for --no-color in case env vars are not set but the flag is used (e.g., in tests)
for _, arg := range os.Args[1:] {
if arg != "--no-color=false" && (arg == "--no-color" || arg == "-no-color") {
return true
}
}
return false
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

NO_COLOR only disables the console helpers right now.

Line 54 gates console.isTTY, but the main slog path still decides color independently in logger/slog.go via its own isTTY, flags.color, and tint.Options.NoColor. That means normal logger output can still be colored even when NO_COLOR is set, so the feature is only partially implemented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/console.go` around lines 54 - 75, The NO_COLOR check is only applied
to console.isTTY via the package-level isTTY variable, but logger/slog.go uses
its own detection (isTTY, flags.color, tint.Options.NoColor), so NO_COLOR is not
globally honored; fix by centralizing the no-color decision: expose and use
console.IsNoColor (or provide a shared function like console.NoColorEnabled)
from logger/slog.go and ensure logger's color logic consults that before
applying colors (i.e., short-circuit flags.color/tint.Options.NoColor when
IsNoColor() returns true), or move the isTTY/no-color detection into a shared
utility used by both console.isTTY and logger/slog.go (referencing IsNoColor,
isTTY, flags.color, and tint.Options.NoColor to locate the related logic).

Comment on lines +3 to +20
import (
"fmt"
"net/http"
"time"
)

func NewAccessLog() Middleware {
return func(rt http.RoundTripper) http.RoundTripper {
return RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
start := time.Now()
resp, err := rt.RoundTrip(req)
elapsed := time.Since(start)
if err != nil {
fmt.Printf("%s %s error %s\n", req.Method, req.URL, elapsed.Truncate(time.Millisecond))
return nil, err
}
fmt.Printf("%s %s %d %s\n", req.Method, req.URL, resp.StatusCode, elapsed.Truncate(time.Millisecond))
return resp, nil
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use the request-scoped logger for access logs.

Lines 16-19 write straight to stdout, so these entries bypass logger level/JSON/color settings and any logger attached to req.Context(). They also log the URL verbatim, which can leak userinfo or query-token data. Please emit this through the same logger path used elsewhere and sanitize the URI before logging.

Proposed direction
 import (
-	"fmt"
 	"net/http"
 	"time"
+
+	commonsctx "github.com/flanksource/commons/context"
 )
 
 func NewAccessLog() Middleware {
 	return func(rt http.RoundTripper) http.RoundTripper {
 		return RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
 			start := time.Now()
 			resp, err := rt.RoundTrip(req)
-			elapsed := time.Since(start)
+			elapsed := time.Since(start).Truncate(time.Millisecond)
+			log := commonsctx.LoggerFromContext(req.Context())
+			uri := req.URL.Redacted()
 			if err != nil {
-				fmt.Printf("%s %s error %s\n", req.Method, req.URL, elapsed.Truncate(time.Millisecond))
+				log.Infof("%s %s error %s err=%v", req.Method, uri, elapsed, err)
 				return nil, err
 			}
-			fmt.Printf("%s %s %d %s\n", req.Method, req.URL, resp.StatusCode, elapsed.Truncate(time.Millisecond))
+			log.Infof("%s %s %d %s", req.Method, uri, resp.StatusCode, elapsed)
 			return resp, nil
 		})
 	}
 }

As per coding guidelines, "Use the logger package for all logging needs; avoid fmt.Print statements."

📝 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
import (
"fmt"
"net/http"
"time"
)
func NewAccessLog() Middleware {
return func(rt http.RoundTripper) http.RoundTripper {
return RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
start := time.Now()
resp, err := rt.RoundTrip(req)
elapsed := time.Since(start)
if err != nil {
fmt.Printf("%s %s error %s\n", req.Method, req.URL, elapsed.Truncate(time.Millisecond))
return nil, err
}
fmt.Printf("%s %s %d %s\n", req.Method, req.URL, resp.StatusCode, elapsed.Truncate(time.Millisecond))
return resp, nil
import (
"net/http"
"time"
commonsctx "github.com/flanksource/commons/context"
)
func NewAccessLog() Middleware {
return func(rt http.RoundTripper) http.RoundTripper {
return RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
start := time.Now()
resp, err := rt.RoundTrip(req)
elapsed := time.Since(start).Truncate(time.Millisecond)
log := commonsctx.LoggerFromContext(req.Context())
uri := req.URL.Redacted()
if err != nil {
log.Infof("%s %s error %s err=%v", req.Method, uri, elapsed, err)
return nil, err
}
log.Infof("%s %s %d %s", req.Method, uri, resp.StatusCode, elapsed)
return resp, nil
})
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@http/middlewares/accesslog.go` around lines 3 - 20, Replace direct fmt.Printf
usage in NewAccessLog / RoundTripperFunc with the request-scoped logger obtained
from req.Context() (e.g., logger := logger.FromContext(req.Context()) or the
project's equivalent) and emit access entries via that logger at the appropriate
level; also sanitize the URI before logging by removing userinfo and sensitive
query parameters (or use a redacted URL representation) instead of logging
req.URL verbatim, and include elapsed time and resp.StatusCode or error in the
structured log entry.

Remove standalone access log middleware and consolidate into context logger. Move logger initialization inside request handler to avoid state reuse. Simplify trace config parsing with early returns. Detect request scheme dynamically instead of defaulting to https.
@moshloop moshloop merged commit ecb698c into master Mar 25, 2026
4 of 6 checks passed
@moshloop moshloop deleted the pr/http-logging branch March 25, 2026 07:52
@github-actions
Copy link

🎉 This PR is included in version 1.49.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@coderabbitai coderabbitai bot mentioned this pull request Mar 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant