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,#}'}'");
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);
}
}