X

Best Practices from Oracle Development's A‑Team

API Platform Logging Policy correlation

Andy Knight
Principal Solution Architect

Use-case

You have an API defined wherein you want to use the Logging Policy (LP) in both the Request and Response pipelines and the LP is defined in both pipelines to use the same log file.

The problem

It is highly likely that when reviewing the log output that you will want to be able to relate whatever was logged in the Request to whatever's logged in the Response (for the same API invocation instance). However, due to traditional multi-threading and varying durations of execution of the Service Endpoint, there is no guarantee that any two adjacent log entries belong to the same API invocation.

The solution

Along with your logged data, you need some kind of unique identifier that is common to both the Request and Response and is unique within the log file.

It may be that the API payload contains some unique datum (e.g. an invoice number). If that's the case then just be sure to log that information in both phases (Request/Response).

However, if there is no such convenient value in the payload, you'll need to create something.

The challenge

The LP limits logging to payload, headers and constant data (literal text).

Therefore, we need to create an identifier and either add it to the headers or payload. Whilst adding an identifier to the payload is feasible, it's somewhat cumbersome to manage. Therefore, my recommendation is to create a custom header.

A simple scenario

The API is invoked with a simple payload as follows:-

{ "CustomerID": "FOO" }

The Service Endpoint is a customer lookup API which returns the customer name linked to the CustomerID. Something like:-

{ "CustomerName": "Acme Software" }

We'll need some Groovy Policies

In the Request pipeline, create a Groovy Policy (GP) with just one line of code:-

context.setAttribute("UUID", UUID.randomUUID().toString())

Note that we initially just create the UUID and save it as a context attribute. That's because we'll be re-using it later.

Also, bear in mind that the GP code template contains several Java import statements; one of which is:-

import java.util.*;

...and thus the UUID class can be referenced directly - i.e. without full package qualification. I mention this only because there are several libraries available at runtime for which there are no explicit import statements - e.g. org.json.*, com.nimbusds.jose.*

Now create another GP containing this code:-

Map<String, List<String>> headers = context.ApiRequest.Headers
ArrayList<String> a = new ArrayList<>()
a.add((String)context.getAttribute("UUID"))
headers.put("UUID", a)

As in [almost] all cases when writing a GP, you'll need to be familiar with the API Platform SDK and I therefore recommend that you review those documents in order to understand this code.

Now we have a custom header called "UUID" that we can refer to in our LP; the definition of which might look something like this:-

Request ${headers.UUID} - ${payload.CustomerID}

Now let's move on to the Response pipeline where we'll create another GP:-

Map<String, List<String>> headers = context.ServiceResponse.Headers
ArrayList<String> a = new ArrayList<>()
a.add((String)context.getAttribute("UUID"))
headers.put("UUID", a)

Notice the difference between what we do here and what we did in the Request pipeline. We simply replace the reference to ApiRequest with ServiceResponse. Note that both class instances are implementations of IncomingRequest.

Now we just need the LP for the Response:-

Response ${headers.UUID} - ${payload.CustomerName}

The result

As a result of our efforts, we have two entries in our log file which we can visually or even programmatically correlate based on the UUID and also in terms of either Request or Response.

One last thing

As we've set a custom header in the Response pipeline, our API consumer will "see" that header should they care to look for it. Its existence may be benign but to save any possible confusion I recommend that you implement a Redaction Policy to remove the header before responding to the consumer.

 

 

 

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha