In this guide, we’ll go over how to monitor a reactive Spring Boot
application using WebFlux, with New Relic’s @Trace
annotation
for detailed transaction tracking, custom parameters, and distributed
tracing for complex service chains.
Prerequisites
-
A Spring Boot WebFlux application.
-
The New Relic Java agent configured in your application.
-
Enable distributed tracing in
newrelic.yml
:
distributed_tracing:
enabled: true
Step 1: Instrument the Main Endpoint
Our main entry point is the processRequest endpoint, which handles
validation, external API calls, and data processing. Here’s how we add
@Trace with dispatcher = true to make it a main transaction.
@RestController
public class SampleController {
private static final Logger logger =
LoggerFactory.getLogger(SampleController.class);
private final SampleService sampleService;
private final WebClient webClient;
public SampleController(SampleService sampleService,
WebClient.Builder webClientBuilder) {
this.sampleService = sampleService;
this.webClient =
webClientBuilder.baseUrl("https://jsonplaceholder.typicode.com/todos/1").build();
}
@GetMapping("/process")
@Trace(dispatcher = true, metricName =
"Custom/processRequest")
public Mono<String> processRequest(@RequestParam
String input) {
NewRelic.addCustomParameter("inputParam",
input);
logger.info("Starting process for Trace
ID: {}", NewRelic.getAgent().getTransaction().getTraceId());
return
sampleService.validateInput(input)
.flatMap(validatedInput ->
callExternalService(validatedInput))
.flatMap(externalData -> sampleService.processData(input,
externalData))
.doOnSuccess(result -> logger.debug("Processing complete with
result: {}", result))
.doOnError(e
-> logger.error("Error processing request", e));
}
@Trace(async = true, metricName =
"Custom/callExternalService")
private Mono<String> callExternalService(String
validatedInput) {
NewRelic.addCustomParameter("validatedInput", validatedInput);
return webClient.get()
.retrieve()
.bodyToMono(String.class)
.doOnNext(data -> NewRelic.addCustomParameter("externalData",
data))
.doOnError(e
-> logger.error("Failed to call external service", e));
}
}
Step 2: Instrument Sub-Methods for Traceability
Here, we add @Trace on the validateInput and processData methods.
This allows each method to show up as segments within the main
transaction trace. Setting async = true supports WebFlux’s
non-blocking nature.
@Service
public class SampleService {
@Trace(async = true, metricName =
"Custom/validateInput")
public Mono<String> validateInput(String input)
{
NewRelic.addCustomParameter("validatedInput", input);
return Mono.just("Validated: " +
input);
}
@Trace(async = true, metricName =
"Custom/processData")
public Mono<String> processData(String input,
String externalData) {
NewRelic.addCustomParameter("combinedData", input + " " +
externalData);
return Mono.just("Processed Data: " +
input + " at " + LocalTime.now());
}
}
Step 3: Viewing Results in New Relic
Main Transaction: In New Relic APM under Transactions, locate
Custom/processRequest to analyze the main endpoint’s
performance.
Sub-Method Tracking: Under Distributed Traces or Transaction Traces,
view the breakdown of validateInput, callExternalService, and
processData as segments within each transaction.
Custom Parameters: View specific data by setting custom parameters
(inputParam, validatedInput, etc.), which appear in Transaction
Attributes and allow deeper filtering in NRQL queries.
Step 4: Using NRQL to Query Performance Data
With New Relic’s NRQL, query average durations and segment-specific
data:
Average Duration of Validation:
NRQL
SELECT average(duration)
FROM Span
WHERE name = 'Custom/validateInput'
SINCE 1 week ago
External Call Monitoring:
SELECT average(duration)
FROM Span
WHERE name = 'Custom/callExternalService'
SINCE 1 week ago
Overall Transaction Time:
SELECT average(duration)
FROM Transaction
WHERE name = 'Custom/processRequest'
SINCE 1 week ago
Ref doc for Newrelic account setup & Integration
https://docs.newrelic.com/install/java/?deployment=gradle&framework=springboot
Let me know if you need a codebase as well.
newrelic.yml
:
distributed_tracing:
enabled: true
Step 1: Instrument the Main Endpoint
Our main entry point is the processRequest endpoint, which handles
validation, external API calls, and data processing. Here’s how we add
@Trace with dispatcher = true to make it a main transaction.
@RestController
public class SampleController {
private static final Logger logger =
LoggerFactory.getLogger(SampleController.class);
private final SampleService sampleService;
private final WebClient webClient;
public SampleController(SampleService sampleService,
WebClient.Builder webClientBuilder) {
this.sampleService = sampleService;
this.webClient =
webClientBuilder.baseUrl("https://jsonplaceholder.typicode.com/todos/1").build();
}
@GetMapping("/process")
@Trace(dispatcher = true, metricName =
"Custom/processRequest")
public Mono<String> processRequest(@RequestParam
String input) {
NewRelic.addCustomParameter("inputParam",
input);
logger.info("Starting process for Trace
ID: {}", NewRelic.getAgent().getTransaction().getTraceId());
return
sampleService.validateInput(input)
.flatMap(validatedInput ->
callExternalService(validatedInput))
.flatMap(externalData -> sampleService.processData(input,
externalData))
.doOnSuccess(result -> logger.debug("Processing complete with
result: {}", result))
.doOnError(e
-> logger.error("Error processing request", e));
}
@Trace(async = true, metricName =
"Custom/callExternalService")
private Mono<String> callExternalService(String
validatedInput) {
NewRelic.addCustomParameter("validatedInput", validatedInput);
return webClient.get()
.retrieve()
.bodyToMono(String.class)
.doOnNext(data -> NewRelic.addCustomParameter("externalData",
data))
.doOnError(e
-> logger.error("Failed to call external service", e));
}
}
Step 2: Instrument Sub-Methods for Traceability
Here, we add @Trace on the validateInput and processData methods.
This allows each method to show up as segments within the main
transaction trace. Setting async = true supports WebFlux’s
non-blocking nature.
@Service
public class SampleService {
@Trace(async = true, metricName =
"Custom/validateInput")
public Mono<String> validateInput(String input)
{
NewRelic.addCustomParameter("validatedInput", input);
return Mono.just("Validated: " +
input);
}
@Trace(async = true, metricName =
"Custom/processData")
public Mono<String> processData(String input,
String externalData) {
NewRelic.addCustomParameter("combinedData", input + " " +
externalData);
return Mono.just("Processed Data: " +
input + " at " + LocalTime.now());
}
}
Step 3: Viewing Results in New Relic
Main Transaction: In New Relic APM under Transactions, locate
Custom/processRequest to analyze the main endpoint’s
performance.
Sub-Method Tracking: Under Distributed Traces or Transaction Traces,
view the breakdown of validateInput, callExternalService, and
processData as segments within each transaction.
Custom Parameters: View specific data by setting custom parameters
(inputParam, validatedInput, etc.), which appear in Transaction
Attributes and allow deeper filtering in NRQL queries.
Step 4: Using NRQL to Query Performance Data
With New Relic’s NRQL, query average durations and segment-specific
data:
Average Duration of Validation:
NRQL
SELECT average(duration)
FROM Span
WHERE name = 'Custom/validateInput'
SINCE 1 week ago
External Call Monitoring:
SELECT average(duration)
FROM Span
WHERE name = 'Custom/callExternalService'
SINCE 1 week ago
Overall Transaction Time:
SELECT average(duration)
FROM Transaction
WHERE name = 'Custom/processRequest'
SINCE 1 week ago
Ref doc for Newrelic account setup & Integration
https://docs.newrelic.com/install/java/?deployment=gradle&framework=springboot
Let me know if you need a codebase as well.
Comments
Post a Comment