Skip to main content

OPA Java Tutorial: Measuring Latency

This tutorial will show you how you can utilize the OPALatencyMeasuringHTTPClient implementation of HTTPClient with the OPA Java SDK to measure latency information about each request the SDK makes.

Setup

This tutorial picks up where the "Hello World" tutorial leaves off. If you have not already, follow the steps from that tutorial.

Add The OPALatencyMeasuringHTTPClient Client To The SDK

To enable latency measurements, the OPALatencyMeasuringHTTPClient class in com.styra.opa.utils can be used in lieu of the default HTTP client. Internally, it wraps the default client, but also uses the Java standard library logger to record latency information for each request. The following changes adapt the "Hello World" sample application to capture latency measurements and send them to the console.

/*
* This source file was generated by the Gradle 'init' task
*/
package org.example;

import com.styra.opa.OPAClient;
import com.styra.opa.OPAException;
import com.styra.opa.utils.OPALatencyMeasuringHTTPClient;

import java.util.logging.Level;
import java.util.Map;
import java.util.List;

import static java.util.Map.entry;


public class App {
public String getGreeting() {
return "Hello World!";
}

public static void main(String[] args) throws OPAException {
System.out.println(new App().getGreeting());

// Create the latency measuring HTTP client, and ask it to log messages
// at the INFO level (FINE is the default).
OPALatencyMeasuringHTTPClient httpc = new OPALatencyMeasuringHTTPClient();
httpc.setLatencyMeasurementLogLevel(Level.INFO);

// Create an OPA instance, this handles any state needed for interacting
// with OPA, and can be re-used for multiple requests if needed.
String opaURL = "http://localhost:8181";
OPAClient opa = new OPAClient(opaURL);
OPAClient opa = new OPAClient(opaURL, httpc);

// This will be the input to our policy.
java.util.Map<String,Object> input = java.util.Map.ofEntries(
entry("subject", "alice"),
entry("action", "read"),
entry("resource", "/finance/reports/fy2038_budget.csv")
);

boolean allowed = false;

// Perform the request against OPA.
try {
allowed = opa.check("authz/allow", input);
} catch (OPAException e ) {
// Note that OPAException usually wraps other exception types, in
// case you need to do more complex error handling.
System.out.println("exception while making request against OPA: " + e);
throw e; // crash the program
}

System.out.println("allowed: " + allowed);
}
}

Now, we can run the modified sample app like so:

./gradlew run

> Task :app:run
Hello World!
Aug 06, 2024 5:24:29 PM com.styra.opa.utils.OPALatencyMeasuringHTTPClient send
INFO: path='/v1/data/authz/allow' latency=137ms
allowed: true

BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed

Notice the lines logging the latency of the call to OPA.

Configure Message Format

The latency measuring client supports customizing the format of the log messages using the setLatencyMeasurementFormat method. This accepts a format string that will be used with java.text.MessageFormat.

Add the call to setLatencyMeasurementFormat to turn JSON format the latency log.

        OPALatencyMeasuringHTTPClient httpc = new OPALatencyMeasuringHTTPClient();
httpc.setLatencyMeasurementLogLevel(Level.INFO);
httpc.setLatencyMeasurementFormat("'{'\"type\": \"latency-measurement\", \"path\": \"{1}\", \"latency\": {0,number,#}'}'");
note

This could produce malformed JSON if the path string contains quotes, since they will not be escaped. This format string should not be used in such situations.

If we run the app again, we can see the new format taking effect:

./gradlew run

> Task :app:run
Hello World!
Aug 06, 2024 5:23:59 PM com.styra.opa.utils.OPALatencyMeasuringHTTPClient send
INFO: {"type": "latency-measurement", "path": "/v1/data/authz/allow", "latency": 134}
allowed: true

BUILD SUCCESSFUL in 1s
2 actionable tasks: 1 executed, 1 up-to-date

Final Code

app/src/main/java/org/example/App.java:

/*
* This source file was generated by the Gradle 'init' task
*/
package org.example;

import com.styra.opa.OPAClient;
import com.styra.opa.OPAException;
import com.styra.opa.utils.OPALatencyMeasuringHTTPClient;

import java.util.List;
import java.util.Map;
import java.util.logging.Level;

import static java.util.Map.entry;

public class App {
public String getGreeting() {
return "Hello World!";
}

public static void main(String[] args) throws OPAException {
System.out.println(new App().getGreeting());

// Create the latency measuring HTTP client, and ask it to log messages
// at the INFO level (FINE is the default).
OPALatencyMeasuringHTTPClient httpc = new OPALatencyMeasuringHTTPClient();
httpc.setLatencyMeasurementLogLevel(Level.INFO);
httpc.setLatencyMeasurementFormat("'{'\"type\": \"latency-measurement\", \"path\": \"{1}\", \"latency\": {0,number,#}'}'");

// Create an OPA instance, this handles any state needed for
// interacting with OPA, and can be re-used for multiple requests if
// needed. Notice that the custom HTTP client is passed to the
// constructor.
String opaURL = "http://localhost:8181";
OPAClient opa = new OPAClient(opaURL, httpc);

// This will be the input to our policy.
java.util.Map<String,Object> input = java.util.Map.ofEntries(
entry("subject", "alice"),
entry("action", "read"),
entry("resource", "/finance/reports/fy2038_budget.csv")
);

boolean allowed = false;

// Perform the request against OPA.
try {
allowed = opa.check("authz/allow", input);
} catch (OPAException e ) {
// Note that OPAException usually wraps other exception types, in
// case you need to do more complex error handling.
System.out.println("exception while making request against OPA: " + e);
throw e; // crash the program
}

System.out.println("allowed: " + allowed);
}
}