DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Kubernetes Ephemeral Containers: Enhancing Security and Streamlining Troubleshooting in Production Clusters
  • Enhanced API Security: Fine-Grained Access Control Using OPA and Kong Gateway
  • Revisiting Observability: A Deep Dive Into the State of Monitoring, Costs, and Data Ownership in Today’s Market
  • Empowering Developers With Scalable, Secure, and Customizable Storage Solutions

Trending

  • Why High-Performance AI/ML Is Essential in Modern Cybersecurity
  • Understanding and Mitigating IP Spoofing Attacks
  • Enhancing Security With ZTNA in Hybrid and Multi-Cloud Deployments
  • Automating Data Pipelines: Generating PySpark and SQL Jobs With LLMs in Cloudera
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Monitoring and Observability
  4. Logging Incoming Requests in Spring WebFlux

Logging Incoming Requests in Spring WebFlux

Enter the realm of logging input requests in Spring WebFlux, a critical foundation for both diagnosing issues and ensuring application security.

By 
Dursun Koç user avatar
Dursun Koç
DZone Core CORE ·
Aug. 21, 23 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
5.9K Views

Join the DZone community and get the full member experience.

Join For Free

In the world of modern software development, meticulous monitoring and robust debugging are paramount. With the rise of reactive programming paradigms, Spring WebFlux has emerged as a powerful framework for building reactive, scalable, and highly performant applications. However, as complexity grows, so does the need for effective logging mechanisms. Enter the realm of logging input requests in Spring WebFlux — a practice that serves as a critical foundation for both diagnosing issues and ensuring application security.

Logging, often regarded as the unsung hero of software development, provides developers with invaluable insights into their applications' inner workings. Through comprehensive logs, developers can peer into the execution flow, troubleshoot errors, and track the journey of each request as it traverses through the intricate layers of their Spring WebFlux application. But logging is not a one-size-fits-all solution; it requires thoughtful configuration and strategic implementation to strike the balance between informative insights and performance overhead.

In this article, we embark on a journey through the landscape of Spring WebFlux and delve into the art of logging input requests. We'll explore the nuances of intercepting and capturing crucial details of incoming requests, all while maintaining security and privacy standards. By the end, you'll be equipped with the knowledge to empower your Spring WebFlux application with insightful logs, fostering enhanced debugging, streamlined monitoring, and a fortified security posture.

So, fasten your seatbelts as we unravel the techniques, best practices, and considerations for logging input requests in Spring WebFlux, and learn how this practice can elevate your application development to new heights.

Action

Although WebFilters are frequently employed to log web requests, we will choose to utilize AspectJ for this scenario.

Assuming that all the endpoints in our project are located within a package named "controller" and that Controller classes end with the term "Controller," we can craft an advice method as depicted below.

@Aspect
@Component
public class RequestLoggingAspect {

 @Around("execution (* my.cool.project.controller.*..*.*Controller.*(..))")
 public Object logInOut(ProceedingJoinPoint joinPoint) {
 Class<?> clazz = joinPoint.getTarget().getClass();
 Logger logger = LoggerFactory.getLogger(clazz);

 Date start = new Date();
 Object result = null;
 Throwable exception = null;
 try {
 result = joinPoint.proceed();
 if (result instanceof Mono<?> monoOut) {
 return logMonoResult(joinPoint, clazz, logger, start, monoOut);
 } else if (result instanceof Flux<?> fluxOut) {
 return logFluxResult(joinPoint, clazz, logger, start, fluxOut);
 } else {
 return result;
 }
 } catch (Throwable e) {
 exception = e;
 throw e;
 } finally {
 if (!(result instanceof Mono<?>) && !(result instanceof Flux<?>)) {
 doOutputLogging(joinPoint, clazz, logger, start, result, exception);
 }
 }
 }
} 


The RequestLoggingAspect stands out for its adept handling of diverse return types, including Flux, Mono, and non-WebFlux, within a Spring WebFlux framework. Employing the AspectJ @Around annotation, it seamlessly intercepts methods in "Controller" classes, offering tailored logging for each return type.

Below is the logMonoResult method, which efficiently logs with contextView to retrieve contextual data from the WebFlux environment. This method adeptly handles Mono return types, capturing various scenarios while maintaining a structured logging approach. It gracefully integrates deferred contextual information and ensures seamless logging of different outcomes. From handling empty results to tracking successes and errors, the logMonoResult method seamlessly facilitates detailed logging within the Spring WebFlux context:

private <T, L> Mono<T> logMonoResult(ProceedingJoinPoint joinPoint, Class<L> clazz, Logger logger, Date start, Mono<T> monoOut) {
 return Mono.deferContextual(contextView ->
 monoOut
 .switchIfEmpty(Mono.<T>empty()
 .doOnSuccess(logOnEmptyConsumer(contextView, () -> doOutputLogging(joinPoint, clazz, logger, start, "[empty]", null))))
 .doOnEach(logOnNext(v -> doOutputLogging(joinPoint, clazz, logger, start, v, null)))
 .doOnEach(logOnError(e -> doOutputLogging(joinPoint, clazz, logger, start, null, e)))
 .doOnCancel(logOnEmptyRunnable(contextView, () -> doOutputLogging(joinPoint, clazz, logger, start, "[cancelled]", null)))
 );
} 


Likewise, the logFluxResult method is presented below. This method orchestrates comprehensive logging while seamlessly incorporating the contextView to obtain contextual information from the WebFlux environment. By accommodating diverse scenarios, such as empty results or cancellations, the logFluxResult method optimizes logging within the Spring WebFlux ecosystem:

private <T> Flux<T> logFluxResult(ProceedingJoinPoint joinPoint, Class<?> clazz, Logger logger, Date start, Flux<T> fluxOut) {
 return Flux.deferContextual(contextView ->
 fluxOut
 .switchIfEmpty(Flux.<T>empty()
 .doOnComplete(logOnEmptyRunnable(contextView, () -> doOutputLogging(joinPoint, clazz, logger, start, "[empty]", null))))
 .doOnEach(logOnNext(v -> doOutputLogging(joinPoint, clazz, logger, start, v, null)))
 .doOnEach(logOnError(e -> doOutputLogging(joinPoint, clazz, logger, start, null, e)))
 .doOnCancel(logOnEmptyRunnable(contextView, () -> doOutputLogging(joinPoint, clazz, logger, start, "[cancelled]", null)))
 );
} 


Let's delve into the details of the logOnNext, logOnError, logOnEmptyConsumer, and logOnEmptyRunnable methods, explaining how they contribute to comprehensive request logging. These methods encapsulate intricate logging procedures and utilize the contextView to maintain contextual information from the WebFlux environment. The combination of MDC (Mapped Diagnostic Context) and signal processing ensures precise logging under various scenarios:

  • logOnNext Method: The logOnNext method is designed to log information when a signal indicates a successful next event. It uses the signal's contextView to extract contextual variables such as transaction ID ( TRX_ID) and path URI ( PATH_URI). Later we will describe how such values can be put to context. These variables are then included in the MDC to enable consistent tracking throughout the logging process. The logging statement is encapsulated within the MDC context, guaranteeing that the correct transaction and path details are associated with the log statement. This approach ensures that successful events are accurately logged within the relevant context.
    private static <T> Consumer<Signal<T>> logOnNext(Consumer<T> logStatement) {
 return signal -> {
 if (!signal.isOnNext()) return;

 String trxIdVar = signal.getContextView().getOrDefault(TRX_ID, "");
 String pathUriVar = signal.getContextView().getOrDefault(PATH_URI, "");
 try (MDC.MDCCloseable trx = MDC.putCloseable(TRX_ID, trxIdVar);
 MDC.MDCCloseable path = MDC.putCloseable(PATH_URI, pathUriVar)) {
 T t = signal.get();
 logStatement.accept(t);
 }
 };
 } 


  • logOnError Method: The logOnError method mirrors the behavior of logOnNext, but it focuses on error events. It extracts the contextual variables from the signal's contextView and places them in the MDC. This ensures that errors are logged in the proper context, making it easier to identify the specific transaction and path associated with the error event. By encapsulating the error log statement within the MDC, this method ensures that error logs are informative and appropriately contextualized.
    public static <T> Consumer<Signal<T>> logOnError(Consumer<Throwable> errorLogStatement) {
 return signal -> {
 if (!signal.isOnError()) return;
 String trxIdVar = signal.getContextView().getOrDefault(TRX_ID, "");
 String pathUriVar = signal.getContextView().getOrDefault(PATH_URI, "");
 try (MDC.MDCCloseable trx = MDC.putCloseable(TRX_ID, trxIdVar);
 MDC.MDCCloseable path = MDC.putCloseable(PATH_URI, pathUriVar)) {
 errorLogStatement.accept(signal.getThrowable());
 }
 };
 } 


  • logOnEmptyConsumer and logOnEmptyRunnable Methods: Both of these methods deal with scenarios where the signal is empty, indicating that there's no result to process. The logOnEmptyConsumer method is designed to accept a Consumer and executes it when the signal is empty. It retrieves the contextual variables from the provided contextView and incorporates them into the MDC before executing the log statement.
    private static <T> Consumer<T> logOnEmptyConsumer(final ContextView contextView, Runnable logStatement) {
 return signal -> {
 if (signal != null) return;
 String trxIdVar = contextView.getOrDefault(TRX_ID, "");
 String pathUriVar = contextView.getOrDefault(PATH_URI, "");
 try (MDC.MDCCloseable trx = MDC.putCloseable(TRX_ID, trxIdVar);
 MDC.MDCCloseable path = MDC.putCloseable(PATH_URI, pathUriVar)) {
 logStatement.run();
 }
 };
 }

 private static Runnable logOnEmptyRunnable(final ContextView contextView, Runnable logStatement) {
 return () -> {
 String trxIdVar = contextView.getOrDefault(TRX_ID, "");
 String pathUriVar = contextView.getOrDefault(PATH_URI, "");
 try (MDC.MDCCloseable trx = MDC.putCloseable(TRX_ID, trxIdVar);
 MDC.MDCCloseable path = MDC.putCloseable(PATH_URI, pathUriVar)) {
 logStatement.run();
 }
 };
 } 


In both cases, these methods ensure that the correct context, including transaction and path details, is established through MDC before executing the log statements. This allows for consistent and meaningful logging even in situations where there is no explicit result to process.

To introduce the transaction ID and path variables into the WebFlux context, consider the following WebFilter configuration. As a @Bean with highest priority, the slf4jMdcFilter extracts the request's unique ID and path URI, incorporating them into the context. This ensures that subsequent processing stages, including the RequestLoggingAspect, can seamlessly access this enriched context for precise and comprehensive request handling.

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
WebFilter slf4jMdcFilter() {
 return (exchange, chain) -> {
 String requestId = exchange.getRequest().getId();
 return chain.filter(exchange)
 .contextWrite(Context.of(Constants.TRX_ID, requestId)
 .put(Constants.PATH_URI, exchange.getRequest().getPath()));
 };
} 


Ultimately, for comprehensive logging of diverse request types, the inclusion of a method named doOutputLogging becomes essential. While a detailed implementation of this method is beyond our scope, it serves as a conduit for logging incoming expressions, either via a tailored logger to match your scenario or potentially routed to a database or alternate platform. This method can be customized to align precisely with your distinct necessities and specifications.

    private <T> void doOutputLogging(final ProceedingJoinPoint joinPoint, final Class<?> clazz, final Logger logger,
 final Date start, final T result, final Throwable exception) {
 //log(...);
 //db.insert(...);
 } 


Summary

In summary, effective request logging in Spring WebFlux is pivotal for debugging and enhancing application performance. By leveraging AspectJ and WebFilters, developers can simplify the process of logging input and output across diverse endpoints. The showcased RequestLoggingAspect efficiently handles different return types, while the slf4jMdcFilter WebFilter enriches logs with transaction and path data.

Although the logMonoResult, logFluxResult, and doOutputLogging methods serve as adaptable templates, they offer customization options to suit specific needs. This empowers developers to tailor logging to their preferences, whether for internal logs or external data storage.

Data storage Requests AspectJ Debug (command) security

Published at DZone with permission of Dursun Koç, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Kubernetes Ephemeral Containers: Enhancing Security and Streamlining Troubleshooting in Production Clusters
  • Enhanced API Security: Fine-Grained Access Control Using OPA and Kong Gateway
  • Revisiting Observability: A Deep Dive Into the State of Monitoring, Costs, and Data Ownership in Today’s Market
  • Empowering Developers With Scalable, Secure, and Customizable Storage Solutions

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!