Skip to content

feat(infra): add external API request/response logging interceptor (STA-242)#247

Merged
Puneethkumarck merged 1 commit into
mainfrom
feature/STA-242-external-api-logging-interceptor
Mar 22, 2026
Merged

feat(infra): add external API request/response logging interceptor (STA-242)#247
Puneethkumarck merged 1 commit into
mainfrom
feature/STA-242-external-api-logging-interceptor

Conversation

@Puneethkumarck

@Puneethkumarck Puneethkumarck commented Mar 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add ExternalApiLoggingInterceptor in platform-infra — logs HTTP method, URL, headers (redacted), request/response bodies, and latency
  • Activated via @ConditionalOnProperty(app.external-api.logging.enabled=true)disabled by default in production
  • Wired into all 17 RestClient adapters across 6 services via @Nullable constructor injection
  • Enabled in all 10 sandbox application-sandbox.yml profiles
  • Response body properly buffered (no stream consumption issues)

Design

  • Production: property not set → interceptor bean not created → zero overhead
  • Sandbox: app.external-api.logging.enabled=true → interceptor injected → full DEBUG logging
  • Sensitive headers (Authorization, X-API-KEY) redacted (first 8 chars visible)
  • Body truncated at configurable max length (default 2000 chars)

Files Changed (65)

  • 2 new classes: ExternalApiLoggingInterceptor, ExternalApiLoggingConfig
  • 1 new test: ExternalApiLoggingInterceptorTest
  • 17 adapter files: added @Nullable ExternalApiLoggingInterceptor constructor param
  • 39 test files: added null for new constructor param
  • 10 sandbox YAMLs: added app.external-api.logging.enabled: true

Test plan

  • ExternalApiLoggingInterceptorTest — 5 redaction tests pass
  • All unit tests pass across all 11 modules
  • Pre-push gate: spotless + tests pass

Closes #246

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added external API logging capability that can be enabled in sandbox environments via app.external-api.logging.enabled: true configuration.
    • Logs include request/response details, headers (with sensitive data redaction), and body content.
  • Chores

    • Updated internal adapters across all services to support optional external API logging interceptor.
    • Updated sandbox configuration profiles to enable the new logging feature.

…TA-242)

Add ExternalApiLoggingInterceptor in platform-infra that logs HTTP method,
URL, headers (redacted), request/response bodies, and latency for all
external API calls. Activated via app.external-api.logging.enabled=true
(disabled by default — zero overhead in production).

Wired into all 17 RestClient adapters across 6 services via @nullable
constructor injection. Enabled in all 10 sandbox profiles.

Closes #246

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Puneethkumarck Puneethkumarck added enhancement New feature or request infra Infrastructure / build cross-cutting Affects all services labels Mar 22, 2026
@coderabbitai

coderabbitai Bot commented Mar 22, 2026

Copy link
Copy Markdown

Walkthrough

This PR introduces a unified HTTP request/response logging interceptor (ExternalApiLoggingInterceptor) for external API calls across the platform. It integrates the interceptor into 15+ adapter classes across 6 services via constructor injection and RestClient wiring, adds conditional configuration via YAML, and updates all adapter tests to pass the nullable interceptor parameter.

Changes

Cohort / File(s) Summary
Core Logging Infrastructure
platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingInterceptor.java, ExternalApiLoggingConfig.java
New ClientHttpRequestInterceptor that logs method, URI, headers (with Authorization/API-KEY redaction), request/response bodies (truncated to configurable max length), status, and latency. ExternalApiLoggingConfig conditionally registers the bean when app.external-api.logging.enabled=true, defaulting max-body-length to 2000. Includes static applyTo(...) helper for RestClient wrapping.
Core Logging Tests
platform-infra/src/test/java/.../ExternalApiLoggingInterceptorTest.java
JUnit 5 test suite for header redaction logic, verifying first-8-char preservation for long secrets and full redaction for short values.
Sandbox Configuration
api-gateway-iam/.../application-sandbox.yml, blockchain-custody/.../application-sandbox.yml, compliance-travel-rule/.../application-sandbox.yml, fiat-off-ramp/.../application-sandbox.yml, fiat-on-ramp/.../application-sandbox.yml, fx-liquidity-engine/.../application-sandbox.yml, ledger-accounting/.../application-sandbox.yml, merchant-iam/.../application-sandbox.yml, merchant-onboarding/.../application-sandbox.yml, payment-orchestrator/.../application-sandbox.yml
Added app.external-api.logging.enabled: true to all sandbox profiles.
Adapter Interceptor Wiring (Custody & EVM/Solana)
blockchain-custody/src/main/java/.../DevCustodyAdapter.java, EvmRpcAdapter.java, SolanaRpcAdapter.java, FireblocksCustodyAdapter.java
Constructor updated to accept @Nullable ExternalApiLoggingInterceptor; RestClient.builder() wrapped via ExternalApiLoggingInterceptor.applyTo(...).
Adapter Interceptor Wiring (Compliance)
compliance-travel-rule/src/main/java/.../ChainalysisAmlAdapter.java, NotabeneTravelRuleAdapter.java, SdnListDownloader.java, OnfidoKycAdapter.java, PersonaKycAdapter.java, WorldCheckSanctionsAdapter.java
Same pattern: nullable interceptor parameter added to constructors; RestClient creation wrapped with applyTo(...) for conditional logging attachment.
Adapter Interceptor Wiring (Fiat & FX)
fiat-off-ramp/src/main/java/.../CircleRedemptionAdapter.java, ModulrPayoutAdapter.java
fiat-on-ramp/src/main/java/.../StripePspAdapter.java
fx-liquidity-engine/src/main/java/.../FrankfurterRateAdapter.java, RefinitivRateAdapter.java
Constructor signature extended with @Nullable ExternalApiLoggingInterceptor; RestClient built via applyTo(...) wrapper.
Adapter Interceptor Wiring (Onboarding)
merchant-onboarding/src/main/java/.../CompaniesHouseAdapter.java, OnfidoKybAdapter.java
Nullable interceptor added to constructors; RestClient builder wrapped with applyTo(...).
Adapter Test Updates (Custody)
blockchain-custody/src/test/java/.../{EvmRpcAdapterRetryTest, EvmRpcAdapterSandboxTest, EvmRpcAdapterTest, SolanaRpcAdapterRetryTest, SolanaRpcAdapterSandboxTest, SolanaRpcAdapterTest}.java
.../{FireblocksCustodyAdapterRetryTest, FireblocksCustodyAdapterSandboxTest, FireblocksCustodyAdapterTest}.java
Test adapter instantiation updated from single-arg constructor to (properties, null) to match new signature.
Adapter Test Updates (Compliance)
compliance-travel-rule/src/test/java/.../{ChainalysisAmlAdapterRetryTest, ChainalysisAmlAdapterTest, NotabeneTravelRuleAdapterTest, OfacSdnSanctionsAdapterTest, OnfidoKycAdapterRetryTest, OnfidoKycAdapterSandboxTest, OnfidoKycAdapterTest, PersonaKycAdapterTest, WorldCheckSanctionsAdapterRetryTest, WorldCheckSanctionsAdapterTest}.java
Constructor calls updated to pass nullable interceptor (typically as null for test isolation).
Adapter Test Updates (Fiat & FX)
fiat-off-ramp/src/test/java/.../{CircleRedemptionAdapterRetryTest, CircleRedemptionAdapterSandboxTest, CircleRedemptionAdapterTest, ModulrPayoutAdapterRetryTest, ModulrPayoutAdapterSandboxTest, ModulrPayoutAdapterTest}.java
fiat-on-ramp/src/test/java/.../{StripePspAdapterRetryTest, StripePspAdapterTest}.java
fx-liquidity-engine/src/test/java/.../{FrankfurterRateAdapterSandboxTest, FrankfurterRateAdapterTest, RefinitivRateAdapterRetryTest, RefinitivRateAdapterTest}.java
Constructor instantiations updated from single-arg to dual-arg form with null interceptor.
Adapter Test Updates (Onboarding)
merchant-onboarding/src/test/java/.../{CompaniesHouseAdapterSandboxTest, CompaniesHouseAdapterTest, OnfidoKybAdapterSandboxTest, OnfidoKybAdapterTest}.java
Constructor calls modified to accept nullable interceptor parameter (passed as null).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes


Possibly related PRs

  • PR #127: Introduces StripePspAdapter; this PR adds logging interceptor wiring to the same class.
  • PR #136: Introduces EvmRpcAdapter; this PR injects the optional interceptor into its constructor and RestClient building.
  • PR #233: Introduces FrankfurterRateAdapter; this PR extends it with nullable interceptor injection and applyTo(...) wrapping.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Title check ✅ Passed Title accurately summarizes the main change: adding an external API request/response logging interceptor with clear scope (infra) and issue reference (STA-242).
Description check ✅ Passed Description covers all required sections: summary, design rationale, file changes, test plan, and issue closure. Technical details on conditional activation and redaction strategy included.
Linked Issues check ✅ Passed All AC requirements met: interceptor implemented with configurable truncation [#246], wired into 17 RestClient adapters across 6 services [#246], sensitive header redaction applied [#246], request/response bodies logged at DEBUG, latency in ms, sandbox YAMLs enabled [#246].
Out of Scope Changes check ✅ Passed All changes directly support external API logging feature: 2 new interceptor classes, 17 adapter constructor updates, 39 test updates, 10 sandbox YAML additions. No unrelated refactors or scope creep detected.

✏️ 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 feature/STA-242-external-api-logging-interceptor

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: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/OnfidoKybAdapter.java (2)

66-68: ⚠️ Potential issue | 🟠 Major

Merchant PII legalName must not appear in logs.

Line 67 logs legalName directly, violating the coding guideline that KYB and merchant PII data must never appear in logs. Use only the merchantId.

🔒 Proposed fix
-    log.info("[ONFIDO] Creating applicant for merchant={} legalName={}", merchantId, legalName);
+    log.info("[ONFIDO] Creating applicant for merchant={}", merchantId);

As per coding guidelines: "KYB and merchant PII data must never appear in logs".

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

In
`@merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/OnfidoKybAdapter.java`
around lines 66 - 68, The log in OnfidoKybAdapter.submit currently includes
merchant PII (legalName); remove legalName from the log and only log non-PII
identifiers (merchantId) or a fixed message indicating PII omitted; update the
log statement in the submit method so it does not reference legalName but
preserves merchantId and context (e.g., log.info("[ONFIDO] Creating applicant
for merchant={}", merchantId) or similar).

7-62: ⚠️ Potential issue | 🟠 Major

Remove merchant PII from both adapter and interceptor logging.

The adapter logs legalName directly at line 67, violating the guideline that "KYB and merchant PII data must never appear in logs." Additionally, ExternalApiLoggingInterceptor performs only size-based truncation (2000 chars default) with no field-level PII redaction—merchant names in the request body will be logged in full unless they exceed the truncation limit.

Required fixes:

  1. Remove legalName from the adapter's log statement at line 67; use only merchantId and applicantId.
  2. Implement field-level PII redaction in ExternalApiLoggingInterceptor for JSON body content (e.g., mask first_name, last_name fields) rather than relying on size-based truncation alone.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/OnfidoKybAdapter.java`
around lines 7 - 62, Remove merchant PII from logs and add JSON field-level
redaction: in OnfidoKybAdapter remove or change the log that currently includes
legalName so it only logs merchantId and applicantId (reference:
OnfidoKybAdapter constructor/wherever the log call with legalName is emitted);
in ExternalApiLoggingInterceptor implement JSON-aware redaction before logging
request/response bodies—when Content-Type is application/json parse the body and
mask or replace sensitive keys (e.g., "first_name", "last_name", "legalName",
"merchantName", "company_name") with a fixed redaction token like "[REDACTED]"
prior to size truncation and writing to logs (reference:
ExternalApiLoggingInterceptor.applyTo / logging methods) so field-level PII is
never emitted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api-gateway-iam/api-gateway-iam/src/main/resources/application-sandbox.yml`:
- Around line 9-11: Update the sandbox configuration to disable generic external
API interceptor logging by setting the external-api.logging.enabled flag to
false (or remove the flag) so the gateway does not log OAuth/API-key traffic;
keep it off until a full secret-suppression implementation is in place or strict
masking/no-auth-header logging is enforced for the external-api logging path.

In
`@blockchain-custody/blockchain-custody/src/main/resources/application-sandbox.yml`:
- Around line 25-27: The external-api logging flag
(external-api.logging.enabled) is turned on and will cause interceptors to log
full RPC URLs containing the ${ALCHEMY_API_KEY} secret; change the configuration
to disable this logging (set external-api.logging.enabled to false) or implement
URL redaction in the request-logging interceptor so RPC URLs containing
${ALCHEMY_API_KEY} are masked before being written to logs; update the config
and/or the interceptor that emits RPC URL logs so it either respects
external-api.logging.enabled or strips/masks the ALCHEMY_API_KEY token from any
logged URL.

In
`@blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/infrastructure/provider/solana/SolanaRpcAdapterRetryTest.java`:
- Line 145: The test currently constructs SolanaRpcAdapter with a null
RestClient (return new SolanaRpcAdapter(properties, null)); add an additional
test path that constructs a SolanaRpcAdapter using a non-null RestClient wired
with an interceptor to exercise the interceptor branch. Specifically, create or
reuse a RestClient/mock RestClient configured with at least one interceptor and
pass it into the SolanaRpcAdapter constructor in the test (e.g., instantiate a
RestClient with an interceptor or a Mockito mock that returns an
interceptor-enabled client) and assert normal adapter behavior to ensure the
interceptor-applied branch is exercised.

In
`@compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/notabene/NotabeneTravelRuleAdapter.java`:
- Around line 57-61: mapToRequest is currently populating PII
(originator/beneficiary names and addresses) which will be captured by the
RestClient loggingInterceptor used to build apiClient; change mapToRequest to
include only non-PII identifiers (e.g., originatorId, beneficiaryId) and remove
names/addresses from the request payload, and/or update the loggingInterceptor
usage so request bodies are not logged (disable body logging or mask PII fields)
when building apiClient via applyTo(RestClient.builder(...),
loggingInterceptor). Ensure the Notabene request DTOs/fields referenced in
mapToRequest are adjusted accordingly and any downstream code expects IDs
instead of names/addresses.
- Around line 63-66: The authClient is built with the loggingInterceptor
applied, which will record sensitive OAuth fields (clientSecret in the token
request and accessToken in the response). Fix by not attaching the interceptor
to authClient: when constructing authClient (the RestClient built via
RestClient.builder() passed into applyTo(..., loggingInterceptor).build()),
create a separate RestClient for auth flows that uses the same requestFactory
and headers but omits applyTo/loggingInterceptor, or alternatively extend the
loggingInterceptor to redact body fields named clientSecret and accessToken
before logging; update the NotabeneTravelRuleAdapter construction to use the
non-intercepted client for token requests (authClient) or a redacting variant so
secrets are never emitted.

In
`@compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/worldcheck/WorldCheckSanctionsAdapter.java`:
- Around line 49-54: The ExternalApiLoggingInterceptor attached to the
RestClient in WorldCheckSanctionsAdapter risks logging PII from WorldCheck
responses; either remove/disable the loggingInterceptor when building the
restClient in WorldCheckSanctionsAdapter, or replace it with/update it to a
redacting variant that masks names/addresses/other PII before logging (implement
field-level redaction inside the interceptor or add a
RedactingExternalApiLoggingInterceptor used here); if you choose to keep a
logging option, add a clear comment in WorldCheckSanctionsAdapter that the
interceptor must never be enabled in production and ensure tests cover the
redaction behavior.

In
`@compliance-travel-rule/compliance-travel-rule/src/main/resources/application-sandbox.yml`:
- Around line 12-14: The external-api logging toggle
(external-api.logging.enabled) must not be enabled while PII-safe logging is not
guaranteed; change external-api.logging.enabled from true to false (or
remove/guard the setting) and add a follow-up task to implement PII
redaction/ID-only logging before re-enabling; ensure any code that reads
external-api.logging.enabled enforces redaction (i.e., only logs IDs) before
allowing the flag to be true.

In
`@compliance-travel-rule/compliance-travel-rule/src/test/java/com/stablecoin/payments/compliance/infrastructure/provider/notabene/NotabeneTravelRuleAdapterTest.java`:
- Line 64: Add a new unit test in NotabeneTravelRuleAdapterTest that constructs
NotabeneTravelRuleAdapter with a non-null interceptor (instead of null) and
invokes the adapter path that processes IVMS payloads (same method exercised
elsewhere in the test class). Provide an IVMS payload containing PII
(names/addresses) and an ID-only variant, capture logs using the existing test
logging helper or a TestAppender, and assert that the captured logs do NOT
contain the PII strings but DO contain the IDs (e.g.,
assertFalse(logs.contains("Alice")/addresses) and
assertTrue(logs.contains("payerId"/"beneficiaryId")). Ensure the test name
clearly indicates interceptor-enabled redaction (e.g.,
testInterceptorRedactsPII) and reuse the existing properties and adapter
construction pattern.

In
`@fx-liquidity-engine/fx-liquidity-engine/src/test/java/com/stablecoin/payments/fx/infrastructure/provider/frankfurter/FrankfurterRateAdapterTest.java`:
- Line 42: Add a unit test that constructs FrankfurterRateAdapter with a
non-null ExternalApiLoggingInterceptor (instead of null) to cover the
interceptor constructor path; instantiate the interceptor (or a mock) and pass
it to new FrankfurterRateAdapter(properties, interceptor), then call a public
adapter method (e.g., the rate-fetching method exposed by
FrankfurterRateAdapter) to ensure the RestClient is wired with the interceptor
and request behavior is unchanged—assert expected response or interaction (use a
mock RestClient/HTTP transport or verify logs/interceptor invocation) so the
non-null-interceptor branch is exercised.

In
`@ledger-accounting/ledger-accounting/src/main/resources/application-sandbox.yml`:
- Around line 9-11: Remove the misleading external-api toggle from the sandbox
config when there are no outbound HTTP clients: delete the
external-api.logging.enabled property (app.external-api.logging.enabled) from
application-sandbox.yml, or explicitly set it to false and add a short comment
that it only applies when RestClient/ outbound HTTP calls are present; update
any related docs or config readers that expect this flag to avoid confusion.

In `@merchant-iam/merchant-iam/src/main/resources/application-sandbox.yml`:
- Around line 9-11: The property external-api.logging.enabled is currently true
and must be disabled until secret-safe redaction is implemented; change
external-api.logging.enabled to false (or remove the setting) in the
configuration so request/response logging is not enabled for the IAM service,
and add a TODO comment referencing implementing secret-safe redaction before
re-enabling logging for external-api.

In
`@merchant-onboarding/merchant-onboarding/src/main/resources/application-sandbox.yml`:
- Around line 9-11: The configuration enables raw external API logging via the
external-api.logging.enabled property which may leak KYB/merchant PII; change
external-api.logging.enabled to false (or remove the key) in sandbox config to
disable outbound API logging, or if logging must remain on implement and
reference a sanitization filter that strips/scrubs PII from request/response
bodies and headers before logging (e.g., wire in a logging sanitizer middleware
used by the external API client) and ensure the sanitizer is invoked wherever
external-api logging is emitted.

In
`@payment-orchestrator/payment-orchestrator/src/main/resources/application-sandbox.yml`:
- Around line 9-11: The external-api logging block currently enables body
logging via external-api.logging.enabled: true; add an explicit conservative
payload cap by introducing external-api.logging.max-body-length with a small
integer (e.g., 1024 or 2048) to limit logged request/response bodies, so update
the external-api -> logging section (the external-api.logging.enabled and new
external-api.logging.max-body-length keys) to enforce the max-body-length
alongside enabled: true.

In
`@platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingConfig.java`:
- Around line 18-24: In ExternalApiLoggingConfig, replace the fully-qualified
type in the bean method signature by importing Environment and using it
directly: change the method
externalApiLoggingInterceptor(org.springframework.core.env.Environment env) to
use Environment env (after adding an import for
org.springframework.core.env.Environment) so the code is stylistically
consistent with other Spring imports; keep the rest of the method (retrieving
"app.external-api.logging.max-body-length" and constructing
ExternalApiLoggingInterceptor) unchanged.

In
`@platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingInterceptor.java`:
- Around line 55-65: The intercept method currently always constructs
BufferedResponse (which eagerly reads the body) causing overhead; guard that
creation with the logger: call execution.execute(request, body) first, compute
latency, then if log.isDebugEnabled() create new BufferedResponse(response) and
call logResponse(request, buffered, latency) and return buffered; otherwise skip
creating BufferedResponse, call a lightweight logResponseSummary or
logResponse(request, response, latency) variant that does not read the body, and
return the original response. Update or add a non-buffering
logResponseSummary/logResponse overload if needed and remove any eager reads in
BufferedResponse when debugging is disabled.
- Around line 55-66: The intercept method in ExternalApiLoggingInterceptor
currently doesn't log failures when execution.execute(request, body) throws;
wrap the call to execution.execute in a try/catch, capture elapsed time using
startMs, and in the catch block log the request method/URI and elapsed
milliseconds before rethrowing the exception; use the existing logResponse or a
dedicated error log path consistent with logRequest/logResponse semantics and
return a BufferedResponse only on success (retain BufferedResponse wrapping for
the successful response path).
- Around line 79-88: The logResponse method currently only logs status and body;
update it to also log response headers using the existing logHeaders redaction
path so headers are redacted consistently. Inside logResponse(HttpRequest
request, BufferedResponse response, long latencyMs) call the same
logHeaders(...) helper (passing response.headers() or equivalent) before/after
logging the body, ensuring you reuse the redact logic and keep the debug-level
guard (log.isDebugEnabled()). Reference BufferedResponse, logResponse, and
logHeaders to locate and apply the change.

In
`@platform-infra/src/test/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingInterceptorTest.java`:
- Around line 9-51: Add unit tests covering
ExternalApiLoggingInterceptor.intercept to verify request/response logging,
header handling, and body truncation; create tests that register the interceptor
with a RestTemplate and use MockRestServiceServer (or
MockRestServiceServer-like) to simulate responses, assert that the logger
received redacted values via ExternalApiLoggingInterceptor.redact for sensitive
headers/bodies and that long bodies are truncated (reference intercept in
ExternalApiLoggingInterceptor and helper methods like redact and any
truncateBody/formatting logic), include cases for null bodies, short values,
headers present/missing, and non-2xx responses so logging paths are exercised.
- Around line 18-21: The test should avoid using a realistic-looking API key;
update the fixture in shouldRedactLongValue to use an obviously synthetic secret
(e.g., "sk_test_FAKE_KEY_12345" or similar) instead of
"sk_test_51TCRge3nnME1dfOB" so secret scanners won’t flag it; locate the
assertion that calls ExternalApiLoggingInterceptor.redact and replace only the
input string while keeping the expected output ("sk_test_***") unchanged.

---

Outside diff comments:
In
`@merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/OnfidoKybAdapter.java`:
- Around line 66-68: The log in OnfidoKybAdapter.submit currently includes
merchant PII (legalName); remove legalName from the log and only log non-PII
identifiers (merchantId) or a fixed message indicating PII omitted; update the
log statement in the submit method so it does not reference legalName but
preserves merchantId and context (e.g., log.info("[ONFIDO] Creating applicant
for merchant={}", merchantId) or similar).
- Around line 7-62: Remove merchant PII from logs and add JSON field-level
redaction: in OnfidoKybAdapter remove or change the log that currently includes
legalName so it only logs merchantId and applicantId (reference:
OnfidoKybAdapter constructor/wherever the log call with legalName is emitted);
in ExternalApiLoggingInterceptor implement JSON-aware redaction before logging
request/response bodies—when Content-Type is application/json parse the body and
mask or replace sensitive keys (e.g., "first_name", "last_name", "legalName",
"merchantName", "company_name") with a fixed redaction token like "[REDACTED]"
prior to size truncation and writing to logs (reference:
ExternalApiLoggingInterceptor.applyTo / logging methods) so field-level PII is
never emitted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f099d731-c254-4a05-b86d-f7a3878dc5c0

📥 Commits

Reviewing files that changed from the base of the PR and between 4a8af9b and 9346d4f.

📒 Files selected for processing (65)
  • api-gateway-iam/api-gateway-iam/src/main/resources/application-sandbox.yml
  • blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/provider/dev/DevCustodyAdapter.java
  • blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/provider/evm/EvmRpcAdapter.java
  • blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/provider/fireblocks/FireblocksCustodyAdapter.java
  • blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/provider/solana/SolanaRpcAdapter.java
  • blockchain-custody/blockchain-custody/src/main/resources/application-sandbox.yml
  • blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/infrastructure/provider/evm/EvmRpcAdapterRetryTest.java
  • blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/infrastructure/provider/evm/EvmRpcAdapterSandboxTest.java
  • blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/infrastructure/provider/evm/EvmRpcAdapterTest.java
  • blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/infrastructure/provider/fireblocks/FireblocksCustodyAdapterRetryTest.java
  • blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/infrastructure/provider/fireblocks/FireblocksCustodyAdapterSandboxTest.java
  • blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/infrastructure/provider/fireblocks/FireblocksCustodyAdapterTest.java
  • blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/infrastructure/provider/solana/SolanaRpcAdapterRetryTest.java
  • blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/infrastructure/provider/solana/SolanaRpcAdapterSandboxTest.java
  • blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/infrastructure/provider/solana/SolanaRpcAdapterTest.java
  • compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/chainalysis/ChainalysisAmlAdapter.java
  • compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/notabene/NotabeneTravelRuleAdapter.java
  • compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/SdnListDownloader.java
  • compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/onfido/OnfidoKycAdapter.java
  • compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/persona/PersonaKycAdapter.java
  • compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/worldcheck/WorldCheckSanctionsAdapter.java
  • compliance-travel-rule/compliance-travel-rule/src/main/resources/application-sandbox.yml
  • compliance-travel-rule/compliance-travel-rule/src/test/java/com/stablecoin/payments/compliance/infrastructure/provider/chainalysis/ChainalysisAmlAdapterRetryTest.java
  • compliance-travel-rule/compliance-travel-rule/src/test/java/com/stablecoin/payments/compliance/infrastructure/provider/chainalysis/ChainalysisAmlAdapterTest.java
  • compliance-travel-rule/compliance-travel-rule/src/test/java/com/stablecoin/payments/compliance/infrastructure/provider/notabene/NotabeneTravelRuleAdapterTest.java
  • compliance-travel-rule/compliance-travel-rule/src/test/java/com/stablecoin/payments/compliance/infrastructure/provider/ofacsdn/OfacSdnSanctionsAdapterTest.java
  • compliance-travel-rule/compliance-travel-rule/src/test/java/com/stablecoin/payments/compliance/infrastructure/provider/onfido/OnfidoKycAdapterRetryTest.java
  • compliance-travel-rule/compliance-travel-rule/src/test/java/com/stablecoin/payments/compliance/infrastructure/provider/onfido/OnfidoKycAdapterSandboxTest.java
  • compliance-travel-rule/compliance-travel-rule/src/test/java/com/stablecoin/payments/compliance/infrastructure/provider/onfido/OnfidoKycAdapterTest.java
  • compliance-travel-rule/compliance-travel-rule/src/test/java/com/stablecoin/payments/compliance/infrastructure/provider/persona/PersonaKycAdapterTest.java
  • compliance-travel-rule/compliance-travel-rule/src/test/java/com/stablecoin/payments/compliance/infrastructure/provider/worldcheck/WorldCheckSanctionsAdapterRetryTest.java
  • compliance-travel-rule/compliance-travel-rule/src/test/java/com/stablecoin/payments/compliance/infrastructure/provider/worldcheck/WorldCheckSanctionsAdapterTest.java
  • fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/circle/CircleRedemptionAdapter.java
  • fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrPayoutAdapter.java
  • fiat-off-ramp/fiat-off-ramp/src/main/resources/application-sandbox.yml
  • fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/infrastructure/provider/circle/CircleRedemptionAdapterRetryTest.java
  • fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/infrastructure/provider/circle/CircleRedemptionAdapterSandboxTest.java
  • fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/infrastructure/provider/circle/CircleRedemptionAdapterTest.java
  • fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrPayoutAdapterRetryTest.java
  • fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrPayoutAdapterSandboxTest.java
  • fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/infrastructure/provider/modulr/ModulrPayoutAdapterTest.java
  • fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePspAdapter.java
  • fiat-on-ramp/fiat-on-ramp/src/main/resources/application-sandbox.yml
  • fiat-on-ramp/fiat-on-ramp/src/test/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePspAdapterRetryTest.java
  • fiat-on-ramp/fiat-on-ramp/src/test/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePspAdapterTest.java
  • fx-liquidity-engine/fx-liquidity-engine/src/main/java/com/stablecoin/payments/fx/infrastructure/provider/frankfurter/FrankfurterRateAdapter.java
  • fx-liquidity-engine/fx-liquidity-engine/src/main/java/com/stablecoin/payments/fx/infrastructure/provider/refinitiv/RefinitivRateAdapter.java
  • fx-liquidity-engine/fx-liquidity-engine/src/main/resources/application-sandbox.yml
  • fx-liquidity-engine/fx-liquidity-engine/src/test/java/com/stablecoin/payments/fx/infrastructure/provider/frankfurter/FrankfurterRateAdapterSandboxTest.java
  • fx-liquidity-engine/fx-liquidity-engine/src/test/java/com/stablecoin/payments/fx/infrastructure/provider/frankfurter/FrankfurterRateAdapterTest.java
  • fx-liquidity-engine/fx-liquidity-engine/src/test/java/com/stablecoin/payments/fx/infrastructure/provider/refinitiv/RefinitivRateAdapterRetryTest.java
  • fx-liquidity-engine/fx-liquidity-engine/src/test/java/com/stablecoin/payments/fx/infrastructure/provider/refinitiv/RefinitivRateAdapterTest.java
  • ledger-accounting/ledger-accounting/src/main/resources/application-sandbox.yml
  • merchant-iam/merchant-iam/src/main/resources/application-sandbox.yml
  • merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/CompaniesHouseAdapter.java
  • merchant-onboarding/merchant-onboarding/src/main/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/OnfidoKybAdapter.java
  • merchant-onboarding/merchant-onboarding/src/main/resources/application-sandbox.yml
  • merchant-onboarding/merchant-onboarding/src/test/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/CompaniesHouseAdapterSandboxTest.java
  • merchant-onboarding/merchant-onboarding/src/test/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/CompaniesHouseAdapterTest.java
  • merchant-onboarding/merchant-onboarding/src/test/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/OnfidoKybAdapterSandboxTest.java
  • merchant-onboarding/merchant-onboarding/src/test/java/com/stablecoin/payments/merchant/onboarding/infrastructure/kyb/OnfidoKybAdapterTest.java
  • payment-orchestrator/payment-orchestrator/src/main/resources/application-sandbox.yml
  • platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingConfig.java
  • platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingInterceptor.java
  • platform-infra/src/test/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingInterceptorTest.java

Comment on lines +9 to +11
external-api:
logging:
enabled: true

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

Disable generic external API logging for gateway sandbox until full secret suppression is enforced.

Line 9 enables interceptor logging in the gateway, which processes OAuth/API-key traffic. Partial masking is not sufficient for this module; keep this toggle off here or enforce strict full masking/no-auth-header logging before enabling.

As per coding guidelines, "API keys and OAuth secrets must never be logged."

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

In `@api-gateway-iam/api-gateway-iam/src/main/resources/application-sandbox.yml`
around lines 9 - 11, Update the sandbox configuration to disable generic
external API interceptor logging by setting the external-api.logging.enabled
flag to false (or remove the flag) so the gateway does not log OAuth/API-key
traffic; keep it off until a full secret-suppression implementation is in place
or strict masking/no-auth-header logging is enforced for the external-api
logging path.

Comment on lines +25 to +27
external-api:
logging:
enabled: true

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 | 🔴 Critical

Enabling logging here can leak Alchemy credentials via URL logging.

With Lines 25-27 enabled, interceptor URL logs will include RPC URLs containing ${ALCHEMY_API_KEY} (e.g., Line 34), exposing secrets.

Suggested immediate mitigation
   external-api:
     logging:
-      enabled: true
+      enabled: 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
external-api:
logging:
enabled: true
external-api:
logging:
enabled: false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@blockchain-custody/blockchain-custody/src/main/resources/application-sandbox.yml`
around lines 25 - 27, The external-api logging flag
(external-api.logging.enabled) is turned on and will cause interceptors to log
full RPC URLs containing the ${ALCHEMY_API_KEY} secret; change the configuration
to disable this logging (set external-api.logging.enabled to false) or implement
URL redaction in the request-logging interceptor so RPC URLs containing
${ALCHEMY_API_KEY} are masked before being written to logs; update the config
and/or the interceptor that emits RPC URL logs so it either respects
external-api.logging.enabled or strips/masks the ALCHEMY_API_KEY token from any
logged URL.

@Bean
SolanaRpcAdapter solanaRpcAdapter(SolanaChainProperties properties) {
return new SolanaRpcAdapter(properties);
return new SolanaRpcAdapter(properties, null);

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 | 🔵 Trivial

Add one interceptor-enabled test path for wiring safety.
Line 145 always passes null, so this suite does not exercise the interceptor-applied RestClient branch. Add one test configuration path that injects a non-null interceptor to catch wiring regressions early.

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

In
`@blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/infrastructure/provider/solana/SolanaRpcAdapterRetryTest.java`
at line 145, The test currently constructs SolanaRpcAdapter with a null
RestClient (return new SolanaRpcAdapter(properties, null)); add an additional
test path that constructs a SolanaRpcAdapter using a non-null RestClient wired
with an interceptor to exercise the interceptor branch. Specifically, create or
reuse a RestClient/mock RestClient configured with at least one interceptor and
pass it into the SolanaRpcAdapter constructor in the test (e.g., instantiate a
RestClient with an interceptor or a Mockito mock that returns an
interceptor-enabled client) and assert normal adapter behavior to ensure the
interceptor-applied branch is exercised.

Comment on lines +57 to 61
this.apiClient = applyTo(RestClient.builder()
.baseUrl(properties.baseUrl())
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.requestFactory(requestFactory)
.requestFactory(requestFactory), loggingInterceptor)
.build();

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

Travel Rule request bodies contain PII that will be logged.

The mapToRequest() method (lines 143-189) includes originator/beneficiary names and addresses in the request body. The interceptor will log these, violating the compliance-travel-rule module's PII logging prohibition.

As per coding guidelines: "PII data (names, addresses) must never appear in logs — use IDs only."

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

In
`@compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/notabene/NotabeneTravelRuleAdapter.java`
around lines 57 - 61, mapToRequest is currently populating PII
(originator/beneficiary names and addresses) which will be captured by the
RestClient loggingInterceptor used to build apiClient; change mapToRequest to
include only non-PII identifiers (e.g., originatorId, beneficiaryId) and remove
names/addresses from the request payload, and/or update the loggingInterceptor
usage so request bodies are not logged (disable body logging or mask PII fields)
when building apiClient via applyTo(RestClient.builder(...),
loggingInterceptor). Ensure the Notabene request DTOs/fields referenced in
mapToRequest are adjusted accordingly and any downstream code expects IDs
instead of names/addresses.

Comment on lines +63 to 66
this.authClient = applyTo(RestClient.builder()
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.requestFactory(requestFactory)
.requestFactory(requestFactory), loggingInterceptor)
.build();

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

OAuth credentials may be logged in request/response bodies.

The authClient will log the token request containing clientSecret (line 119) and the response containing accessToken. Header redaction doesn't cover body content. Consider:

  1. Not applying the interceptor to authClient, or
  2. Extending the interceptor to redact sensitive body fields.
Option 1: Skip interceptor for authClient
         this.authClient = applyTo(RestClient.builder()
                 .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
-                .requestFactory(requestFactory), loggingInterceptor)
+                .requestFactory(requestFactory), null)
                 .build();
📝 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
this.authClient = applyTo(RestClient.builder()
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.requestFactory(requestFactory)
.requestFactory(requestFactory), loggingInterceptor)
.build();
this.authClient = applyTo(RestClient.builder()
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.requestFactory(requestFactory), null)
.build();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/notabene/NotabeneTravelRuleAdapter.java`
around lines 63 - 66, The authClient is built with the loggingInterceptor
applied, which will record sensitive OAuth fields (clientSecret in the token
request and accessToken in the response). Fix by not attaching the interceptor
to authClient: when constructing authClient (the RestClient built via
RestClient.builder() passed into applyTo(..., loggingInterceptor).build()),
create a separate RestClient for auth flows that uses the same requestFactory
and headers but omits applyTo/loggingInterceptor, or alternatively extend the
loggingInterceptor to redact body fields named clientSecret and accessToken
before logging; update the NotabeneTravelRuleAdapter construction to use the
non-intercepted client for token requests (authClient) or a redacting variant so
secrets are never emitted.

Comment on lines +55 to +65
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
var startMs = System.currentTimeMillis();
logRequest(request, body);

var response = execution.execute(request, body);

var latencyMs = System.currentTimeMillis() - startMs;
var buffered = new BufferedResponse(response);
logResponse(request, buffered, latencyMs);
return buffered;

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

DEBUG guard is too late; full response buffering runs on every call.

On Line 63, BufferedResponse is always created, and on Line 124 it eagerly reads all bytes. This happens even when DEBUG logging is off, adding avoidable latency/memory pressure to all outbound calls.

Proposed fix
 `@Override`
 public ClientHttpResponse intercept(HttpRequest request, byte[] body,
                                      ClientHttpRequestExecution execution) throws IOException {
+    if (!log.isDebugEnabled()) {
+        return execution.execute(request, body);
+    }
+
     var startMs = System.currentTimeMillis();
     logRequest(request, body);

     var response = execution.execute(request, body);

     var latencyMs = System.currentTimeMillis() - startMs;
     var buffered = new BufferedResponse(response);
     logResponse(request, buffered, latencyMs);
     return buffered;
 }

Also applies to: 122-125

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

In
`@platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingInterceptor.java`
around lines 55 - 65, The intercept method currently always constructs
BufferedResponse (which eagerly reads the body) causing overhead; guard that
creation with the logger: call execution.execute(request, body) first, compute
latency, then if log.isDebugEnabled() create new BufferedResponse(response) and
call logResponse(request, buffered, latency) and return buffered; otherwise skip
creating BufferedResponse, call a lightweight logResponseSummary or
logResponse(request, response, latency) variant that does not read the body, and
return the original response. Update or add a non-buffering
logResponseSummary/logResponse overload if needed and remove any eager reads in
BufferedResponse when debugging is disabled.

Comment on lines +55 to +66
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
var startMs = System.currentTimeMillis();
logRequest(request, body);

var response = execution.execute(request, body);

var latencyMs = System.currentTimeMillis() - startMs;
var buffered = new BufferedResponse(response);
logResponse(request, buffered, latencyMs);
return buffered;
}

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 | 🟡 Minor

Failure path is not logged with latency.

If execution.execute(...) throws, the interceptor emits no response/failure log, which makes external-call triage harder. Add a catch block to log method/URI/elapsed time before rethrowing.

Proposed fix
 `@Override`
 public ClientHttpResponse intercept(HttpRequest request, byte[] body,
                                      ClientHttpRequestExecution execution) throws IOException {
     if (!log.isDebugEnabled()) {
         return execution.execute(request, body);
     }

     var startMs = System.currentTimeMillis();
     logRequest(request, body);
-
-    var response = execution.execute(request, body);
-
-    var latencyMs = System.currentTimeMillis() - startMs;
-    var buffered = new BufferedResponse(response);
-    logResponse(request, buffered, latencyMs);
-    return buffered;
+    try {
+        var response = execution.execute(request, body);
+        var latencyMs = System.currentTimeMillis() - startMs;
+        var buffered = new BufferedResponse(response);
+        logResponse(request, buffered, latencyMs);
+        return buffered;
+    } catch (IOException ex) {
+        var latencyMs = System.currentTimeMillis() - startMs;
+        log.debug("<-- HTTP FAILED {} {} ({}ms): {}",
+                request.getMethod(), request.getURI(), latencyMs, ex.getMessage());
+        throw ex;
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingInterceptor.java`
around lines 55 - 66, The intercept method in ExternalApiLoggingInterceptor
currently doesn't log failures when execution.execute(request, body) throws;
wrap the call to execution.execute in a try/catch, capture elapsed time using
startMs, and in the catch block log the request method/URI and elapsed
milliseconds before rethrowing the exception; use the existing logResponse or a
dedicated error log path consistent with logRequest/logResponse semantics and
return a BufferedResponse only on success (retain BufferedResponse wrapping for
the successful response path).

Comment on lines +79 to +88
private void logResponse(HttpRequest request, BufferedResponse response, long latencyMs) {
if (!log.isDebugEnabled()) {
return;
}
log.debug("<-- {} {} ({}ms)", response.statusCode().value(), request.getURI(), latencyMs);
var responseBody = new String(response.bodyBytes, StandardCharsets.UTF_8);
if (!responseBody.isEmpty()) {
log.debug("<-- Body: {}", truncate(responseBody));
}
}

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

Response headers are missing from response logs.

logResponse(...) logs status/body only. The stated acceptance criteria includes response headers, and this method currently omits them. Reuse the same logHeaders(...) redaction path for response headers as well.

Proposed fix
 private void logResponse(HttpRequest request, BufferedResponse response, long latencyMs) {
     if (!log.isDebugEnabled()) {
         return;
     }
     log.debug("<-- {} {} ({}ms)", response.statusCode().value(), request.getURI(), latencyMs);
+    logHeaders("<--", response.getHeaders());
     var responseBody = new String(response.bodyBytes, StandardCharsets.UTF_8);
     if (!responseBody.isEmpty()) {
         log.debug("<-- Body: {}", truncate(responseBody));
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@platform-infra/src/main/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingInterceptor.java`
around lines 79 - 88, The logResponse method currently only logs status and
body; update it to also log response headers using the existing logHeaders
redaction path so headers are redacted consistently. Inside
logResponse(HttpRequest request, BufferedResponse response, long latencyMs) call
the same logHeaders(...) helper (passing response.headers() or equivalent)
before/after logging the body, ensuring you reuse the redact logic and keep the
debug-level guard (log.isDebugEnabled()). Reference BufferedResponse,
logResponse, and logHeaders to locate and apply the change.

Comment on lines +9 to +51
@DisplayName("ExternalApiLoggingInterceptor")
class ExternalApiLoggingInterceptorTest {

@Nested
@DisplayName("redact")
class Redact {

@Test
@DisplayName("should redact long value keeping first 8 chars")
void shouldRedactLongValue() {
assertThat(ExternalApiLoggingInterceptor.redact("sk_test_51TCRge3nnME1dfOB"))
.isEqualTo("sk_test_***");
}

@Test
@DisplayName("should fully redact short value")
void shouldFullyRedactShortValue() {
assertThat(ExternalApiLoggingInterceptor.redact("short"))
.isEqualTo("***");
}

@Test
@DisplayName("should return *** for null")
void shouldReturnStarsForNull() {
assertThat(ExternalApiLoggingInterceptor.redact(null))
.isEqualTo("***");
}

@Test
@DisplayName("should redact exactly 8-char boundary")
void shouldRedactExactBoundary() {
assertThat(ExternalApiLoggingInterceptor.redact("12345678"))
.isEqualTo("***");
}

@Test
@DisplayName("should keep first 8 chars for 9-char value")
void shouldKeepFirstEightForNineChars() {
assertThat(ExternalApiLoggingInterceptor.redact("123456789"))
.isEqualTo("12345678***");
}
}
}

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 | 🔵 Trivial

Consider adding tests for intercept() method.

Current coverage validates redact() only. The interceptor's core functionality (request/response logging, body truncation, header handling) lacks unit test coverage. MockRestServiceServer or similar could verify logging behavior.

Want me to draft tests for the intercept() method using MockRestServiceServer?

🧰 Tools
🪛 Betterleaks (1.1.1)

[high] 19-19: Found a Stripe Access Token, posing a risk to payment processing services and sensitive financial data.

(stripe-access-token)

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

In
`@platform-infra/src/test/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingInterceptorTest.java`
around lines 9 - 51, Add unit tests covering
ExternalApiLoggingInterceptor.intercept to verify request/response logging,
header handling, and body truncation; create tests that register the interceptor
with a RestTemplate and use MockRestServiceServer (or
MockRestServiceServer-like) to simulate responses, assert that the logger
received redacted values via ExternalApiLoggingInterceptor.redact for sensitive
headers/bodies and that long bodies are truncated (reference intercept in
ExternalApiLoggingInterceptor and helper methods like redact and any
truncateBody/formatting logic), include cases for null bodies, short values,
headers present/missing, and non-2xx responses so logging paths are exercised.

Comment on lines +18 to +21
void shouldRedactLongValue() {
assertThat(ExternalApiLoggingInterceptor.redact("sk_test_51TCRge3nnME1dfOB"))
.isEqualTo("sk_test_***");
}

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 | 🟡 Minor

Replace realistic-looking API key with obviously fake fixture.

"sk_test_51TCRge3nnME1dfOB" matches the Stripe test key pattern and triggers secret scanners. Use a clearly synthetic value.

Proposed fix
     `@Test`
     `@DisplayName`("should redact long value keeping first 8 chars")
     void shouldRedactLongValue() {
-        assertThat(ExternalApiLoggingInterceptor.redact("sk_test_51TCRge3nnME1dfOB"))
-                .isEqualTo("sk_test_***");
+        assertThat(ExternalApiLoggingInterceptor.redact("AAAABBBB_secret_value_here"))
+                .isEqualTo("AAAABBBB***");
     }

As per coding guidelines: "No hardcoded secrets or real PII in test fixtures."

📝 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
void shouldRedactLongValue() {
assertThat(ExternalApiLoggingInterceptor.redact("sk_test_51TCRge3nnME1dfOB"))
.isEqualTo("sk_test_***");
}
void shouldRedactLongValue() {
assertThat(ExternalApiLoggingInterceptor.redact("AAAABBBB_secret_value_here"))
.isEqualTo("AAAABBBB***");
}
🧰 Tools
🪛 Betterleaks (1.1.1)

[high] 19-19: Found a Stripe Access Token, posing a risk to payment processing services and sensitive financial data.

(stripe-access-token)

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

In
`@platform-infra/src/test/java/com/stablecoin/payments/platform/infrastructure/http/ExternalApiLoggingInterceptorTest.java`
around lines 18 - 21, The test should avoid using a realistic-looking API key;
update the fixture in shouldRedactLongValue to use an obviously synthetic secret
(e.g., "sk_test_FAKE_KEY_12345" or similar) instead of
"sk_test_51TCRge3nnME1dfOB" so secret scanners won’t flag it; locate the
assertion that calls ExternalApiLoggingInterceptor.redact and replace only the
input string while keeping the expected output ("sk_test_***") unchanged.

@Puneethkumarck Puneethkumarck merged commit b193e88 into main Mar 22, 2026
46 of 49 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cross-cutting Affects all services enhancement New feature or request infra Infrastructure / build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(infra): add HTTP client request/response logging interceptor for external APIs (STA-242)

1 participant