Skip to content

Commit

Permalink
Add telemetry wip
Browse files Browse the repository at this point in the history
  • Loading branch information
armando-rodriguez-cko committed Dec 11, 2024
1 parent 730c090 commit 92b381b
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 20 deletions.
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ dependencies {

## How to use the SDK

This SDK can be used with two different pair of API keys provided by Checkout. However, using different API keys imply using specific API features. </br>
This SDK can be used with two different pair of API keys provided by Checkout. However, using different API keys imply using specific API features. </br>
Please find in the table below the types of keys that can be used within this SDK.

| Account System | Public Key (example) | Secret Key (example) |
|----------------|-----------------------------------------|-----------------------------------------|
| Default | pk_pkhpdtvabcf7hdgpwnbhw7r2uic | sk_m73dzypy7cf3gf5d2xr4k7sxo4e |
| Previous | pk_g650ff27-7c42-4ce1-ae90-5691a188ee7b | sk_gk3517a8-3z01-45fq-b4bd-4282384b0a64 |
| Default | pk_abcdef123456ghijkl789mnopqr | sk_123456ghijklm7890abcdefxyz |
| Previous | pk_12345678-abcd-efgh-ijkl-mnopqrstuvwx | sk_abcdef12-3456-ghij-klmn-opqrstuvwxyz |

Note: sandbox keys have a `sbox_` or `test_` identifier, for Default and Previous accounts respectively.

Expand Down Expand Up @@ -207,6 +207,19 @@ The execution of integration tests require the following environment variables s
* For default account systems (OAuth): `CHECKOUT_DEFAULT_OAUTH_CLIENT_ID` & `CHECKOUT_DEFAULT_OAUTH_CLIENT_SECRET`
* For Previous account systems (ABC): `CHECKOUT_PREVIOUS_PUBLIC_KEY` & `CHECKOUT_PREVIOUS_SECRET_KEY`

## Telemetry
Request telemetry is enabled by default in the Java SDK. Request latency is included in the telemetry data. Recording the request latency allows Checkout.com to continuously monitor and improve the merchant experience.

Request telemetry can be disabled by opting out during CheckoutSdk builder step:
```java
final CheckoutApi checkoutApi = CheckoutSdk.builder()
.staticKeys()
.secretKey("secret_key")
.environment(Environment.PRODUCTION)
.recordTelemetry(false)
.build();
```

## Code of Conduct

Please refer to [Code of Conduct](CODE_OF_CONDUCT.md)
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/com/checkout/AbstractCheckoutSdkBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public abstract class AbstractCheckoutSdkBuilder<T extends CheckoutApiClient> {
private EnvironmentSubdomain environmentSubdomain;
private Executor executor = ForkJoinPool.commonPool();
private TransportConfiguration transportConfiguration;
private Boolean recordTelemetry = true;

public AbstractCheckoutSdkBuilder<T> environment(final IEnvironment environment) {
this.environment = environment;
Expand Down Expand Up @@ -48,6 +49,11 @@ protected EnvironmentSubdomain getEnvironmentSubdomain() {
return environmentSubdomain;
}

public AbstractCheckoutSdkBuilder<T> recordTelemetry(final Boolean recordTelemetry) {
this.recordTelemetry = recordTelemetry;
return this;
}

protected abstract SdkCredentials getSdkCredentials();

protected CheckoutConfiguration getCheckoutConfiguration() {
Expand All @@ -62,7 +68,7 @@ protected CheckoutConfiguration getCheckoutConfiguration() {
}

private CheckoutConfiguration buildCheckoutConfiguration(final SdkCredentials sdkCredentials) {
return new DefaultCheckoutConfiguration(sdkCredentials, getEnvironment(), getEnvironmentSubdomain(), httpClientBuilder, executor, transportConfiguration);
return new DefaultCheckoutConfiguration(sdkCredentials, getEnvironment(), getEnvironmentSubdomain(), httpClientBuilder, executor, transportConfiguration, recordTelemetry);
}

public abstract T build();
Expand Down
52 changes: 51 additions & 1 deletion src/main/java/com/checkout/ApacheHttpClientTransport.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;

import com.google.gson.Gson;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
Expand Down Expand Up @@ -57,19 +60,31 @@ class ApacheHttpClientTransport implements Transport {
private static final String FILE = "file";
private static final String PURPOSE = "purpose";
private static final String PATH = "path";

private final URI baseUri;
private final CloseableHttpClient httpClient;
private final Executor executor;
private final TransportConfiguration transportConfiguration;
private final CheckoutConfiguration configuration;

private final ConcurrentLinkedQueue<RequestMetrics> requestMetricsQueue = new ConcurrentLinkedQueue<>();
private final Gson gson = new Gson();

ApacheHttpClientTransport(final URI baseUri, final HttpClientBuilder httpClientBuilder, final Executor executor, final TransportConfiguration transportConfiguration) {
ApacheHttpClientTransport(
final URI baseUri,
final HttpClientBuilder httpClientBuilder,
final Executor executor,
final TransportConfiguration transportConfiguration,
final CheckoutConfiguration configuration
) {
CheckoutUtils.validateParams("baseUri", baseUri, "httpClientBuilder", httpClientBuilder, "executor", executor);
this.baseUri = baseUri;
this.httpClient = httpClientBuilder
.setRedirectStrategy(new CustomAwsRedirectStrategy())
.build();
this.executor = executor;
this.transportConfiguration = transportConfiguration;
this.configuration = configuration;
}

@Override
Expand Down Expand Up @@ -154,15 +169,50 @@ private Response performCall(final SdkAuthorization authorization,
request.setHeader(ACCEPT, getAcceptHeader(clientOperation));
request.setHeader(AUTHORIZATION, authorization.getAuthorizationHeader());

String currentRequestId = null;
RequestMetrics lastRequestMetric = null;

if (configuration.isTelemetryEnabled()) {
currentRequestId = UUID.randomUUID().toString();
lastRequestMetric = requestMetricsQueue.poll();

if (lastRequestMetric != null) {
lastRequestMetric.setRequestId(currentRequestId);
try {
String serializedMetrics = gson.toJson(lastRequestMetric);
String sdkTelemetryHeader = "cko-sdk-telemetry";
request.setHeader(sdkTelemetryHeader, serializedMetrics);
} catch (Exception e) {
log.error("Error serializing telemetry metrics", e);
}
}
}

long startTime = System.currentTimeMillis();

log.info("Request: " + Arrays.toString(sanitiseHeaders(request.getAllHeaders())));
if (requestBody != null && request instanceof HttpEntityEnclosingRequest) {
((HttpEntityEnclosingRequestBase) request).setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
}
try (final CloseableHttpResponse response = httpClient.execute(request)) {
long elapsed = System.currentTimeMillis() - startTime;
log.info("Response: " + response.getStatusLine().getStatusCode() + " " + Arrays.toString(response.getAllHeaders()));
final int statusCode = response.getStatusLine().getStatusCode();
final Map<String, String> headers = Arrays.stream(response.getAllHeaders())
.collect(Collectors.toMap(Header::getName, Header::getValue));

if (configuration.isTelemetryEnabled()) {
int maxCountInTelemetryQueue = 10;
if (requestMetricsQueue.size() < maxCountInTelemetryQueue) {
if (lastRequestMetric == null) {
lastRequestMetric = new RequestMetrics();
}
lastRequestMetric.setPrevRequestDuration(elapsed);
lastRequestMetric.setPrevRequestId(currentRequestId);
requestMetricsQueue.offer(lastRequestMetric);
}
}

if (statusCode != HttpStatus.SC_NOT_FOUND && response.getEntity() != null && response.getEntity().getContent() != null) {
return Response.builder()
.statusCode(statusCode)
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/checkout/ApiClientImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class ApiClientImpl implements ApiClient {

public ApiClientImpl(final CheckoutConfiguration configuration, final UriStrategy uriStrategy) {
this.serializer = new GsonSerializer();
this.transport = new ApacheHttpClientTransport(uriStrategy.getUri(), configuration.getHttpClientBuilder(), configuration.getExecutor(), configuration.getTransportConfiguration());
this.transport = new ApacheHttpClientTransport(uriStrategy.getUri(), configuration.getHttpClientBuilder(), configuration.getExecutor(), configuration.getTransportConfiguration(), configuration);
}

@Override
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/checkout/CheckoutConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ public interface CheckoutConfiguration {

TransportConfiguration getTransportConfiguration();

Boolean isTelemetryEnabled();

}
13 changes: 11 additions & 2 deletions src/main/java/com/checkout/DefaultCheckoutConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,39 @@ class DefaultCheckoutConfiguration implements CheckoutConfiguration {
private final IEnvironment environment;
private final EnvironmentSubdomain environmentSubdomain;
private final TransportConfiguration transportConfiguration;
private final boolean recordTelemetry;

DefaultCheckoutConfiguration(final SdkCredentials sdkCredentials,
final IEnvironment environment,
final HttpClientBuilder httpClientBuilder,
final Executor executor,
final TransportConfiguration transportConfiguration) {
final TransportConfiguration transportConfiguration,
final boolean recordTelemetry) {
validateParams("sdkCredentials", sdkCredentials, "environment", environment, "httpClientBuilder", httpClientBuilder, "executor", executor, "transportConfiguration", transportConfiguration);
this.sdkCredentials = sdkCredentials;
this.httpClientBuilder = httpClientBuilder;
this.executor = executor;
this.environment = environment;
this.environmentSubdomain = null;
this.transportConfiguration = transportConfiguration;
this.recordTelemetry = recordTelemetry;
}

DefaultCheckoutConfiguration(final SdkCredentials sdkCredentials,
final IEnvironment environment,
final EnvironmentSubdomain environmentSubdomain,
final HttpClientBuilder httpClientBuilder,
final Executor executor,
final TransportConfiguration transportConfiguration) {
final TransportConfiguration transportConfiguration,
final Boolean recordTelemetry) {
validateParams("sdkCredentials", sdkCredentials, "environment", environment, "httpClientBuilder", httpClientBuilder, "executor", executor, "transportConfiguration", transportConfiguration);
this.sdkCredentials = sdkCredentials;
this.httpClientBuilder = httpClientBuilder;
this.executor = executor;
this.environment = environment;
this.environmentSubdomain = environmentSubdomain;
this.transportConfiguration = transportConfiguration;
this.recordTelemetry = recordTelemetry;
}

@Override
Expand Down Expand Up @@ -73,4 +78,8 @@ public EnvironmentSubdomain getEnvironmentSubdomain() {
public TransportConfiguration getTransportConfiguration() {
return transportConfiguration;
}

public Boolean isTelemetryEnabled() {

Check notice

Code scanning / CodeQL

Missing Override annotation Note

This method overrides
CheckoutConfiguration.isTelemetryEnabled
; it is advisable to add an Override annotation.
return this.recordTelemetry;
}
}
17 changes: 17 additions & 0 deletions src/main/java/com/checkout/RequestMetrics.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.checkout;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class RequestMetrics {
private String requestId;
private Long prevRequestDuration;
private String prevRequestId;

public RequestMetrics(Long prevRequestDuration, String prevRequestId) {
this.prevRequestDuration = prevRequestDuration;
this.prevRequestId = prevRequestId;
}
}
Loading

0 comments on commit 92b381b

Please sign in to comment.