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.
@RestControllerpublic 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.
@Servicepublic 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 agoExternal Call Monitoring:
SELECT average(duration) FROM Span WHERE name = 'Custom/callExternalService' SINCE 1 week agoOverall 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