Skip to content

Commit b193e88

Browse files
feat(infra): add external API request/response logging interceptor (STA-242) (#247)
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>
1 parent 4a8af9b commit b193e88

65 files changed

Lines changed: 452 additions & 96 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api-gateway-iam/api-gateway-iam/src/main/resources/application-sandbox.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
app:
77
security:
88
enabled: false
9+
external-api:
10+
logging:
11+
enabled: true
912

1013
api-gateway-iam:
1114
jwt:

blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/provider/dev/DevCustodyAdapter.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
import com.stablecoin.payments.custody.domain.port.SignRequest;
55
import com.stablecoin.payments.custody.domain.port.SignResult;
66
import com.stablecoin.payments.custody.domain.port.TransactionStatus;
7+
import com.stablecoin.payments.platform.infrastructure.http.ExternalApiLoggingInterceptor;
78
import lombok.extern.slf4j.Slf4j;
89
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
910
import org.springframework.boot.context.properties.EnableConfigurationProperties;
1011
import org.springframework.http.MediaType;
1112
import org.springframework.http.client.JdkClientHttpRequestFactory;
13+
import org.springframework.lang.Nullable;
1214
import org.springframework.stereotype.Component;
1315
import org.springframework.web.client.RestClient;
1416
import org.web3j.crypto.Credentials;
@@ -25,6 +27,8 @@
2527
import java.util.UUID;
2628
import java.util.concurrent.ConcurrentHashMap;
2729

30+
import static com.stablecoin.payments.platform.infrastructure.http.ExternalApiLoggingInterceptor.applyTo;
31+
2832
@Slf4j
2933
@Component
3034
@ConditionalOnProperty(name = "app.custody.provider", havingValue = "dev")
@@ -38,10 +42,14 @@ public class DevCustodyAdapter implements CustodyEngine {
3842
private final DevCustodyProperties properties;
3943
private final Map<String, RestClient> restClients;
4044
private final Map<String, TxMapping> txMappings;
45+
@Nullable
46+
private final ExternalApiLoggingInterceptor loggingInterceptor;
4147

4248
@org.springframework.beans.factory.annotation.Autowired
43-
public DevCustodyAdapter(DevCustodyProperties properties) {
49+
public DevCustodyAdapter(DevCustodyProperties properties,
50+
@Nullable ExternalApiLoggingInterceptor loggingInterceptor) {
4451
this.properties = properties;
52+
this.loggingInterceptor = loggingInterceptor;
4553
this.restClients = new ConcurrentHashMap<>();
4654
this.txMappings = new ConcurrentHashMap<>();
4755

@@ -50,6 +58,7 @@ public DevCustodyAdapter(DevCustodyProperties properties) {
5058

5159
DevCustodyAdapter(DevCustodyProperties properties, Map<String, RestClient> restClients) {
5260
this.properties = properties;
61+
this.loggingInterceptor = null;
5362
this.restClients = new ConcurrentHashMap<>(restClients);
5463
this.txMappings = new ConcurrentHashMap<>();
5564
}
@@ -226,9 +235,9 @@ private RestClient createRestClient(String rpcUrl) {
226235
var requestFactory = new JdkClientHttpRequestFactory(httpClient);
227236
requestFactory.setReadTimeout(Duration.ofMillis(properties.readTimeoutMs()));
228237

229-
return RestClient.builder()
238+
return applyTo(RestClient.builder()
230239
.baseUrl(rpcUrl)
231-
.requestFactory(requestFactory)
240+
.requestFactory(requestFactory), loggingInterceptor)
232241
.build();
233242
}
234243

blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/provider/evm/EvmRpcAdapter.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.stablecoin.payments.custody.domain.model.ChainId;
44
import com.stablecoin.payments.custody.domain.port.ChainRpcProvider;
55
import com.stablecoin.payments.custody.domain.port.TransactionReceipt;
6+
import com.stablecoin.payments.platform.infrastructure.http.ExternalApiLoggingInterceptor;
67
import io.github.resilience4j.bulkhead.annotation.Bulkhead;
78
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
89
import io.github.resilience4j.retry.annotation.Retry;
@@ -11,6 +12,7 @@
1112
import org.springframework.boot.context.properties.EnableConfigurationProperties;
1213
import org.springframework.http.MediaType;
1314
import org.springframework.http.client.JdkClientHttpRequestFactory;
15+
import org.springframework.lang.Nullable;
1416
import org.springframework.stereotype.Component;
1517
import org.springframework.web.client.RestClient;
1618
import tools.jackson.databind.JsonNode;
@@ -25,6 +27,8 @@
2527
import java.util.Map;
2628
import java.util.concurrent.ConcurrentHashMap;
2729

30+
import static com.stablecoin.payments.platform.infrastructure.http.ExternalApiLoggingInterceptor.applyTo;
31+
2832
@Slf4j
2933
@Component
3034
@ConditionalOnProperty(name = "app.custody.evm.enabled", havingValue = "true")
@@ -38,7 +42,8 @@ public class EvmRpcAdapter implements ChainRpcProvider {
3842
private final Map<String, RestClient> restClients;
3943
private final EvmChainProperties properties;
4044

41-
public EvmRpcAdapter(EvmChainProperties properties) {
45+
public EvmRpcAdapter(EvmChainProperties properties,
46+
@Nullable ExternalApiLoggingInterceptor loggingInterceptor) {
4247
this.properties = properties;
4348
this.restClients = new ConcurrentHashMap<>();
4449

@@ -51,9 +56,9 @@ public EvmRpcAdapter(EvmChainProperties properties) {
5156
var requestFactory = new JdkClientHttpRequestFactory(httpClient);
5257
requestFactory.setReadTimeout(Duration.ofMillis(config.readTimeoutMs()));
5358

54-
var client = RestClient.builder()
59+
var client = applyTo(RestClient.builder()
5560
.baseUrl(config.rpcUrl())
56-
.requestFactory(requestFactory)
61+
.requestFactory(requestFactory), loggingInterceptor)
5762
.build();
5863

5964
restClients.put(chainName, client);

blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/provider/fireblocks/FireblocksCustodyAdapter.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.stablecoin.payments.custody.domain.port.SignRequest;
77
import com.stablecoin.payments.custody.domain.port.SignResult;
88
import com.stablecoin.payments.custody.domain.port.TransactionStatus;
9+
import com.stablecoin.payments.platform.infrastructure.http.ExternalApiLoggingInterceptor;
910
import io.github.resilience4j.bulkhead.annotation.Bulkhead;
1011
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
1112
import io.github.resilience4j.retry.annotation.Retry;
@@ -14,6 +15,7 @@
1415
import org.springframework.boot.context.properties.EnableConfigurationProperties;
1516
import org.springframework.http.MediaType;
1617
import org.springframework.http.client.JdkClientHttpRequestFactory;
18+
import org.springframework.lang.Nullable;
1719
import org.springframework.stereotype.Component;
1820
import org.springframework.web.client.RestClient;
1921
import tools.jackson.databind.json.JsonMapper;
@@ -32,6 +34,8 @@
3234
import java.util.Map;
3335
import java.util.UUID;
3436

37+
import static com.stablecoin.payments.platform.infrastructure.http.ExternalApiLoggingInterceptor.applyTo;
38+
3539
@Slf4j
3640
@Component
3741
@ConditionalOnProperty(name = "app.custody.provider", havingValue = "fireblocks")
@@ -50,7 +54,8 @@ public class FireblocksCustodyAdapter implements CustodyEngine {
5054
private final FireblocksProperties properties;
5155
private final RSAPrivateKey privateKey;
5256

53-
public FireblocksCustodyAdapter(FireblocksProperties properties) {
57+
public FireblocksCustodyAdapter(FireblocksProperties properties,
58+
@Nullable ExternalApiLoggingInterceptor loggingInterceptor) {
5459
this.properties = properties;
5560

5661
var httpClient = HttpClient.newBuilder()
@@ -61,9 +66,9 @@ public FireblocksCustodyAdapter(FireblocksProperties properties) {
6166
var requestFactory = new JdkClientHttpRequestFactory(httpClient);
6267
requestFactory.setReadTimeout(Duration.ofSeconds(properties.timeoutSeconds()));
6368

64-
this.restClient = RestClient.builder()
69+
this.restClient = applyTo(RestClient.builder()
6570
.baseUrl(properties.baseUrl())
66-
.requestFactory(requestFactory)
71+
.requestFactory(requestFactory), loggingInterceptor)
6772
.build();
6873

6974
this.privateKey = parsePrivateKey(properties.apiSecret());

blockchain-custody/blockchain-custody/src/main/java/com/stablecoin/payments/custody/infrastructure/provider/solana/SolanaRpcAdapter.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.stablecoin.payments.custody.domain.model.ChainId;
44
import com.stablecoin.payments.custody.domain.port.ChainRpcProvider;
55
import com.stablecoin.payments.custody.domain.port.TransactionReceipt;
6+
import com.stablecoin.payments.platform.infrastructure.http.ExternalApiLoggingInterceptor;
67
import io.github.resilience4j.bulkhead.annotation.Bulkhead;
78
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
89
import io.github.resilience4j.retry.annotation.Retry;
@@ -11,6 +12,7 @@
1112
import org.springframework.boot.context.properties.EnableConfigurationProperties;
1213
import org.springframework.http.MediaType;
1314
import org.springframework.http.client.JdkClientHttpRequestFactory;
15+
import org.springframework.lang.Nullable;
1416
import org.springframework.stereotype.Component;
1517
import org.springframework.web.client.RestClient;
1618
import tools.jackson.databind.JsonNode;
@@ -23,6 +25,8 @@
2325
import java.time.Duration;
2426
import java.util.Map;
2527

28+
import static com.stablecoin.payments.platform.infrastructure.http.ExternalApiLoggingInterceptor.applyTo;
29+
2630
@Slf4j
2731
@Component
2832
@ConditionalOnProperty(name = "app.custody.solana.enabled", havingValue = "true")
@@ -36,7 +40,8 @@ public class SolanaRpcAdapter implements ChainRpcProvider {
3640
private final RestClient restClient;
3741
private final SolanaChainProperties properties;
3842

39-
public SolanaRpcAdapter(SolanaChainProperties properties) {
43+
public SolanaRpcAdapter(SolanaChainProperties properties,
44+
@Nullable ExternalApiLoggingInterceptor loggingInterceptor) {
4045
this.properties = properties;
4146

4247
var httpClient = HttpClient.newBuilder()
@@ -47,9 +52,9 @@ public SolanaRpcAdapter(SolanaChainProperties properties) {
4752
var requestFactory = new JdkClientHttpRequestFactory(httpClient);
4853
requestFactory.setReadTimeout(Duration.ofMillis(properties.readTimeoutMs()));
4954

50-
this.restClient = RestClient.builder()
55+
this.restClient = applyTo(RestClient.builder()
5156
.baseUrl(properties.rpcUrl())
52-
.requestFactory(requestFactory)
57+
.requestFactory(requestFactory), loggingInterceptor)
5358
.build();
5459

5560
log.info("[SOLANA-RPC] Configured RestClient rpcUrl={} commitment={} usdcMint={}",

blockchain-custody/blockchain-custody/src/main/resources/application-sandbox.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ spring:
2222
app:
2323
security:
2424
enabled: false
25+
external-api:
26+
logging:
27+
enabled: true
2528
custody:
2629
provider: dev
2730
evm:

blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/infrastructure/provider/evm/EvmRpcAdapterRetryTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ EvmChainProperties evmChainProperties() {
142142

143143
@Bean
144144
EvmRpcAdapter evmRpcAdapter(EvmChainProperties properties) {
145-
return new EvmRpcAdapter(properties);
145+
return new EvmRpcAdapter(properties, null);
146146
}
147147
}
148148
}

blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/infrastructure/provider/evm/EvmRpcAdapterSandboxTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ static void setUp() {
4848
"https://eth-sepolia.g.alchemy.com/v2/" + apiKey,
4949
11155111L, ETH_SEPOLIA_USDC, 10000, 30000)
5050
));
51-
adapter = new EvmRpcAdapter(properties);
51+
adapter = new EvmRpcAdapter(properties, null);
5252
}
5353

5454
@Nested

blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/infrastructure/provider/evm/EvmRpcAdapterTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ void setUp(WireMockRuntimeInfo wmRuntimeInfo) {
3838
wmRuntimeInfo.getHttpBaseUrl(), 84532L,
3939
USDC_CONTRACT, 5000, 10000)
4040
));
41-
adapter = new EvmRpcAdapter(properties);
41+
adapter = new EvmRpcAdapter(properties, null);
4242
}
4343

4444
@Nested

blockchain-custody/blockchain-custody/src/test/java/com/stablecoin/payments/custody/infrastructure/provider/fireblocks/FireblocksCustodyAdapterRetryTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ FireblocksProperties fireblocksProperties() {
232232

233233
@Bean
234234
FireblocksCustodyAdapter fireblocksCustodyAdapter(FireblocksProperties properties) {
235-
return new FireblocksCustodyAdapter(properties);
235+
return new FireblocksCustodyAdapter(properties, null);
236236
}
237237
}
238238
}

0 commit comments

Comments
 (0)