Home > database >  Logging in reactive streams
Logging in reactive streams

Time:10-01

I'm working on a reactive streams application using spring webflux. I have 1 single chain/pipeline of code that goes from Controller methods to Services to DAOs and returns back the response. During this pipeline I have a lot of Publishers. I would like to add logging support for the operators that are there in the pipeline. Say I have the following code,

public class Example {

 @Autowired
 private GreetingService gs;

 public Mono<String> getGreetingMessage(String name) {
  return Mono.just(name)
             .map(name -> name.toUpperCase())
             .map(name -> name.toLowerCase())
             .onErrorResume(ex -> Mono.just("guest")
             .flatMap(name -> gs.getGreeting(name)); 
 }

}

@Service
public class GreetingService {
 public Mono<String> getGreeting(String name) {
  return Mono.just("Welcome, "   name ".");
 }
}

I know this a contrived example but the actual code is a lot lengthier than this with a lot of calls to different classes within the pipeline. How exactly do we perform the logging in this scenario? What is an elegant way to do logging?

Note: Since there are multiple publishers like in flatMap, onErrorResume I dont think we can use a simple log() because log() would only be applied to that particular publisher for which we use it.

CodePudding user response:

You have a couple of options here:

1. Inlined in map()

return Mono.just(name)
     .map(name -> {
        logger.info("whatever");
        name.toUpperCase();
     })
     .map(name -> name.toLowerCase())
     .onErrorResume(ex -> Mono.just("guest")
     .flatMap(name -> gs.getGreeting(name)); 

2. Use doOnNext()

return Mono.just(name)
     .map(name -> name.toUpperCase())
     .doOnNext(logger.info("whatever"))
     .map(name -> name.toLowerCase())
     .onErrorResume(ex -> Mono.just("guest")
     .flatMap(name -> gs.getGreeting(name)); 

CodePudding user response:

You can choose one of the logging frameworks such as Log4J or Logback.

Note that you must take care to configure the underlying logging framework to use its most asynchronous. For example, an AsyncAppender in Logback.

Here is logback.xml sample configuration that might help:

<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%d %5p %t %c{15} - %m%n</pattern>
    </encoder>
</appender>

<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="console"/>
    <queueSize>10000</queueSize>
    <maxFlushTime>5000</maxFlushTime>
    <neverBlock>true</neverBlock>
</appender>

<logger name="com.my.package" level="trace"/>
<root level="info">
    <appender-ref ref="async"/>
</root>

You can use both log() and doOn* operator

log() will give you subscriber-level information like the onComplete signal, requested demand etc. On the other hand, doOn* operators could be used to log the actual data values, for instance:

Flux.from(messages)
      .doOnNext(message -> logger.trace("Sending "   message))

or

.flatMap(instance -> doNotify(event, instance))
.doOnError(ex -> logger.error("Couldn't notify for event {} ", event, ex))
  • Related