Skip to content

Client core: logging exceptions best practices

Liudmila Molkova edited this page May 22, 2025 · 4 revisions

This guidance extends the general Azure SDK exception policy.

  1. DO log exceptions when creating new instances of exceptions.

    For example, write

    // good
    throw logger.throwableAtError().log("This is a test exception", CoreException::from);

    instead of

    // bad
    throw new RuntimeException("This is a test exception");
  2. It's not required to log trivial input parameter validation errors.

    For example, write

    public MyClientOptions setName(String name) {
    
         // this will throw NullPointerException implicitly
         Objects.requireNotNull("'name' cannot be null.");
    
         if (name.length() == 0) {
             throw new IllegalArgumentException("'name' cannot be empty");
         }
    }

    DO log exceptions in non-trivial cases, for example when validating network responses.

  3. DO add all important context about the error into the exception message and log record using logger.throwableAt*().addKeyValue(key, value) methods.

    For example, write

    // good
    try {
        uploadBlob(containerId, blobId, data);
    } catch (RuntimeException ex) {
        throw logger.throwableAtError()
            .addKeyValuePair("blobId", blobId)
            .addKeyValuePair("containerId", containerId)
            .addKeyValuePair("endpoint", endpoint)
            .log("Error when uploading blob", ex, CoreException::from);
    }

    This code produces an exception message similar to

    io.clientcore.core.models.CoreException$CoreExceptionImpl: Error when uploading blob; {"endpoint":"www.xyz.com",
    "blobId":"foo", "containerId":"bar","cause.type":"java.net.UnknownHostException", "cause.message":"Unable to resolve host www.xyz.com"}
    

    which is consistent with the structured log record

    {"message":"Error when uploading blob", "endpoint":"www.xyz.com", "exception.type":"io.clientcore.core.models.CoreException.CoreExceptionImpl", 
     "blobId":"foo", "containerId":"bar", "cause.type":"java.net.UnknownHostException", "cause.message":"Unable to resolve host www.xyz.com"}

    Avoid providing dynamic messages - this makes log analysis and grepping more complicated.

  4. DO log exceptions that you're rethrowing only if they came from an external dependency. DO make the best effort not to log the same exception as it bubbles up through the stack trace.

    For example, write:

    // good
    try {
        return createIfNotExist(resourceId);
    } catch (HttpResponseException ex) {
        if (ex.getResponse().getStatusCode() == 409) {
            return ex.getResponse();
        }
    
        // HttpResponseException should be logged at creation time, no need to log it again
        // unless you're wrapping it into a new error or want to provide additional
        // details
        throw ex;
    }
  5. DO NOT create exceptions that you aren't throwing:

    For example, don't create fake exception causes. Write

    // good
    throw logger.throwableAtError().log("File does not exist", CoreException::from);

    instead of

    // bad
    throw logger.throwableAtError().log("File does not exist", new FileNotFoundException(), CoreException::from);
    throw UncheckedIOException(new FileNotFoundException());

    You can always log error without exception:

    // good
    logger.atError()
        .addKeyValue("providerType", providerType)
        .addKeyValue("supportedTypes", KNOWN_TYPES)
        .log("Unknown provider type.");
Clone this wiki locally