Java SDK logging and error handling

Ask about this Page
Copy for LLM
View as Markdown

Configure logging and handle errors and exceptions in the Java SDK.

For best observability, use an Application Performance Monitoring (APM) service. For server-side logs, integrate Platform Insights with your APM provider.

Exceptions

The Java SDK uses exceptions from the Java Development Kit (such as IllegalArgumentException) and provides its own exceptions which inherit from BaseException.

Errors

If a command cannot run due to unfulfilled preconditions, one error response with multiple errors can be returned (for more information, see HTTP API Errors). The Java SDK then adds a BadRequestException into a CompletionStage.

Custom HttpExceptionFactory

The Java SDK treats responses with HTTP status codes 4xx and 5xx as errors and raises exceptions. The DefaultHttpExceptionFactory creates these exceptions. To customize the handling, you must implement this interface and override its default methods as needed.
Implement a custom HttpExceptionFactoryjava
public class HttpExceptionFactoryTest {

  static class CustomExceptionFactory implements HttpExceptionFactory {

    @Override
    public ApiHttpException create(
      ApiHttpRequest request,
      ApiHttpResponse<byte[]> response
    ) {
      return new ApiHttpException(
        response.getStatusCode(),
        request.getSecuredBody(),
        response.getHeaders(),
        "something bad happened",
        response,
        request
      );
    }

    @Override
    public ResponseSerializer getResponseSerializer() {
      return ResponseSerializer.of();
    }
  }

  @Test
  public void customFactory() {
    ProjectApiRoot client = ApiRootBuilder
      .of()
      .defaultClient(ServiceRegion.GCP_EUROPE_WEST1.getApiUrl())
      .withHttpExceptionFactory(CustomExceptionFactory::new)
      .build("my-project-key");

    assertThatExceptionOfType(ApiHttpException.class)
      .isThrownBy(() -> client.get().executeBlocking())
      .withMessageStartingWith("detailMessage: something bad happened");
  }
}

Logging

The default logger middleware logs the following information per level:

  • Error: logs HTTP method, URI, and response status code for ApiHttpException, and cause and exceptions for all other exceptions.
  • Information: logs HTTP method, URI, response status code, and any deprecation notices. You can configure the log level when instantiating the InternalLoggerMiddleware.
  • Debug: logs string representations of the request and the response, including headers and body, with sensitive data like auth tokens and passwords, redacted.
  • Trace: Logs request and response with pretty-printed output, with sensitive information redacted.

Logger hierarchy

The loggers form a hierarchy separated by a period. The root logger is commercetools. The child loggers of commercetools are the endpoints. For example commercetools.categories for Categories and commercetools.product-types for Product Types.
The grandchild loggers refer to the action. commercetools.categories.request refers to performing requests per HTTPS to commercetools for Categories, commercetools.categories.response refers to the responses.
The logger makes use of different log levels, so for example commercetools.categories.response logs the HTTP response on debug level:
Example debug log outputjava
 [OkHttp https://api.europe-west1.gcp.commercetools.com/...] DEBUG commercetools.categories.response - io.vrap.rmf.base.client.ApiHttpResponse@33e37acd[statusCode=200,headers=...,textInterpretedBody={"limit":20,"offset":0,"count":4,"total":4,"results":[{"id":"ca7f64dc-ab41-4e7f-b65b-bfc25bf01111","version":1,"createdAt":"2021-01-06T09:21:55.445Z","lastModifiedAt":"2021-01-06T09:21:55.445Z","key":"random-key-15aab6ee-9a48-4fdc-a8c3-8ff9ff390d60","name":{"key-6bc3cc62-1995-43f4":"value-random-string-c3ff7f1a-6371-4c0c-a3b9-5060eca08b8a"},"slug":{"key-6bc3cc62-1995-43f4":"value-random-string-c3ff7f1a-6371-4c0c-a3b9-5060eca08b8a"},"description":{"key-6bc3cc62-1995-43f4":"value-random-string-c3ff7f1a-6371-4c0c-a3b9-5060eca08b8a"},"ancestors":[],"orderHint":"random-string-a99e6941-3225-4766-923a-4a654652532f","externalId":"random-id-100b3851-29b4-47c1-aef3-860b4cd28f54"}]}]
Additionally, commercetools.categories.response logs the formatted HTTP response on trace level:
Example trace log outputjava
[OkHttp https://api.europe-west1.gcp.commercetools.com/...] TRACE commercetools.categories.response - 200
 {
   "limit" : 20,
   "offset" : 0,
   "count" : 4,
   "total" : 4,
   "results" : [ {
     "id" : "bcb15128-b69e-47b6-81c4-1ea5f9a63b83",
     "version" : 1,
     "createdAt" : "2021-01-06T09:21:50.485Z",
     "lastModifiedAt" : "2021-01-06T09:21:50.485Z",
     "createdBy" : {
       "clientId" : "h-QvaF3NpsjPBWeXa6TUOnq0",
       "isPlatformClient" : false
     },
     "key" : "random-key-f45535f5-1111-4bfb-98ac-164234ac2c73",
     "name" : {
       "key-b7d87008-9526-47b3-8b80" : "value-random-string-baf1a1ae-3726-4573-bec5-2c53c7d1114a"
     },
     "slug" : {
       "key-b7d87008-9526-47b3-8b80" : "value-random-string-baf1a1ae-3726-4573-bec5-2c53c7d1114a"
     },
     "description" : {
       "key-b7d87008-9526-47b3-8b80" : "value-random-string-baf1a1ae-3726-4573-bec5-2c53c7d1114a"
     },
     "ancestors" : [ ],
     "orderHint" : "random-string-6b88f299-d4ca-491a-9d7c-3463e718c8d2",
     "externalId" : "random-id-b98a5ab4-5413-40b3-8119-eb5ac2b5afbb",
     "metaTitle" : {
       "key-b7d87008-9526-47b3-8b80" : "value-random-string-baf1a1ae-3726-4573-bec5-2c53c7d1114a"
     },
     "metaKeywords" : {
       "key-b7d87008-9526-47b3-8b80" : "value-random-string-baf1a1ae-3726-4573-bec5-2c53c7d1114a"
     },
     "metaDescription" : {
       "key-b7d87008-9526-47b3-8b80" : "value-random-string-baf1a1ae-3726-4573-bec5-2c53c7d1114a"
     }
   }, {
   ...
   } ]
 }
commercetools.products.responses.queries only logs HTTP GET requests and commercetools.products.responses.commands only logs HTTP POST/DELETE requests.
Example log output for commands and queriesjava
[pool-1-thread-1] DEBUG commercetools.products.request.commands - io.vrap.rmf.base.client.ApiHttpRequest@1d2c6948[method=POST,uri="https://api.europe-west1.gcp.commercetools.com/test-php-dev-integration-1/products",headers=[...],textInterpretedBody={"productType":{"id":"cda39953-23af-4e85-abb0-5b89517ec5f2","typeId":"product-type"},"name":{"random-string-b66de021-d2fa-4262-8837-94a6992a8cdc":"random-string-da28a010-e66b-49d4-a18b-f1bfc145d2f6"},...}]
 [pool-1-thread-1] DEBUG commercetools.products.request.queries - io.vrap.rmf.base.client.ApiHttpRequest@53667fdb[method=GET,uri="https://api.europe-west1.gcp.commercetools.com/test-php-dev-integration-1/products/ef745227-b115-4132-ba2c-4e46db80df79",headers=[...],textInterpretedBody=empty body]

Configure logging

The ClientBuilder allows customization of the log levels used for different events. By default, responses are logged at INFO level and errors at ERROR level. Use the withInternalLoggerFactory() method to change these defaults:
Configure log levelsjava
ProjectApiRoot apiRoot = ApiRootBuilder
  .of()
  .defaultClient(
    ClientCredentials
      .of()
      .withClientId("your-client-id")
      .withClientSecret("your-client-secret")
      .build(),
    ServiceRegion.GCP_EUROPE_WEST1
  )
  .withInternalLoggerFactory(
    InternalLoggerFactory.of(Level.DEBUG),
    Level.DEBUG,
    Level.WARN
  )
  .build("my-project-key");

Mapped Diagnostic Context (MDC)

The SDK uses CompletableFutures extensively, which means futures can execute on different threads. If your application uses SLF4J MDC, the context may be lost when switching threads.
The SDK supports attaching a Context to a client, requests, and responses through the ContextAware interface. ProjectApiRoot allows adding a Context to create a context-aware root from a global instance:
Create a context-aware API root for MDC propagationjava
Map<String, String> mdcContext = MDC.getCopyOfContextMap();
MDCContext context = MDCContext.of(mdcContext);
ProjectApiRoot contextAwareRoot = apiRoot.withContext(context);
The InternalLoggerMiddleware restores the MDC before logging and cleans up afterwards to avoid polluting threads with unrelated context data. The same applies to ConcurrentModificationMiddleware.