Skip to content

feat(http): add http.client.log for logging outgoing HTTP requests#4337

Open
reinkrul wants to merge 4 commits into
masterfrom
feature/http-client-logging
Open

feat(http): add http.client.log for logging outgoing HTTP requests#4337
reinkrul wants to merge 4 commits into
masterfrom
feature/http-client-logging

Conversation

@reinkrul

@reinkrul reinkrul commented Jun 8, 2026

Copy link
Copy Markdown
Member

Summary

Adds a configurable log level for outgoing HTTP requests/responses made by the node, mirroring the existing http.log (which logs incoming/server-side traffic). Reuses the existing http.LogLevel enum and defaults to nothing (no behavior change unless explicitly enabled).

New config option http.client.log:

Level What is logged
nothing (default) Nothing
metadata Request + response method, uri, status, headers
metadata-and-body Above + request/response bodies (loggable content types only)

The log level is the single control — no dependency on the global debug verbosity.

Header masking

Credential-bearing request headers are masked (Authorization, Proxy-Authorization) so tokens don't leak into logs. Response WWW-Authenticate is a challenge, not a credential, so it is left intact.

How it works

  • The logger lives in http/client (loggingTransport), alongside the rest of the client transport code. It logs both the request and the response.
  • Gated by client.LogRequests / client.LogRequestBodies, which are read at request time, not when the client is created. This matters because the HTTP engine is configured last, while other engines (e.g. auth's OpenID4VCI client) build their HTTP clients earlier — a construction-time check would miss those clients. getTransport always installs loggingTransport; whether anything is logged is decided per request.
  • http.configureClient() maps the http.client.log enum to those two flags.
  • The loggable-content-types list is a single source of truth in http/log.IsLoggableContentType, used by both the client logger and the server-side body logger (previously duplicated). http/log only depends on core, so both http and http/client can import it without a cycle.

Notes

  • Body content-type filter only logs application/json, application/did+json, application/vc+json, application/x-www-form-urlencoded.
  • A non-2xx response is logged as a normal request + response with the error status — not as a transport failure. The HTTP client request failed line only appears for connection-level errors (refused/TLS/timeout/strict-mode rejection).

Out of scope (flagged, not changed)

The server-side request logger checks logger.Level >= logrus.DebugLevel on a *logrus.Entry (http/requestlogger.go). Entry.Level is always 0 (panic level), so that "log headers at debug" path is effectively dead. Left untouched here to keep this diff tight; worth a follow-up.

Tests

  • http/client/requestlogger_test.go — disabled, metadata-only (headers present), metadata-and-body, sensitive-header masking, non-loggable content type, transport error.
  • http/client/client_test.go — regression: client created before logging is enabled still logs.
  • http/engine_test.goconfigureClient maps each level to the client flags.

Assisted by AI

reinkrul added 2 commits June 8, 2026 16:01
Adds a configurable log level for outgoing HTTP requests/responses made
by the node, mirroring http.log for incoming traffic. Reuses the existing
http.LogLevel enum (nothing/metadata/metadata-and-body) and defaults to
nothing. Wired up in http.configureClient() via a transport-wrapping hook
on the http client package.

Assisted by AI
The logging transport wrapper was decided when the HTTP client was
created. Engines that build their clients before the HTTP engine is
configured (e.g. auth's OpenID4VCI client) never got the wrapper, since
client.RequestLogger was still nil at construction. The HTTP engine is
registered/configured last, so http.client.log had no effect on those
clients.

Always install a thin loggingTransport indirection in getTransport that
reads RequestLogger per request, mirroring how StrictMode is read at
request time. Clients created before logging is configured now log.

Assisted by AI
@qltysh

qltysh Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

0 new issues

Tool Category Rule Count

Comment thread http/clientlogger.go Fixed
@qltysh

qltysh Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Qlty


Coverage Impact

⬆️ Merging this pull request will increase total coverage on master by 0.03%.

Modified Files with Diff Coverage (7)

RatingFile% DiffUncovered Line #s
Coverage rating: B Coverage rating: B
http/engine.go100.0%
Coverage rating: A Coverage rating: A
http/cmd/cmd.go100.0%
Coverage rating: A Coverage rating: A
http/config.go100.0%
Coverage rating: B Coverage rating: B
http/client/client.go100.0%
Coverage rating: A Coverage rating: A
http/requestlogger.go100.0%
New Coverage rating: F
http/log/contenttype.go0.0%25-34
New Coverage rating: A
http/client/requestlogger.go92.3%72-73, 98-99
Total86.8%
🤖 Increase coverage with AI coding...
In the `feature/http-client-logging` branch, add test coverage for this new code:

- `http/client/requestlogger.go` -- Lines 72-73 and 98-99
- `http/log/contenttype.go` -- Line 25-34

🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

reinkrul added 2 commits June 8, 2026 16:20
Make the log level the single control instead of also depending on the
global debug verbosity:

- Log request and response headers at both 'metadata' and
  'metadata-and-body' (previously headers required debug verbosity).
- Mask credential-bearing request headers (Authorization,
  Proxy-Authorization) so they don't leak into the logs. Response
  WWW-Authenticate is a challenge, not a credential, so it is not masked.

Assisted by AI
Relocate the outgoing-request logger from the http package to http/client,
where the rest of the client transport code lives, and address naming:

- The round tripper is now loggingTransport (it logs both request and
  response), gated by client.LogRequests / client.LogRequestBodies which
  are read at request time. This replaces the clientRequestLogger type and
  the RequestLogger func indirection.
- Extract the loggable-content-types list to http/log.IsLoggableContentType
  as the single source of truth, used by both the client logger and the
  server-side body logger (previously duplicated).

Assisted by AI
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