Simplify lazy logging with SLF4J
Asked Answered
D

5

5

I am using SLF4J with Logback in a Spring Boot application. I was interested in using lazy logging and after some research, I came up with this solution.

This works as expected and does not invoke methods if the logging level is not matched.

logger.debug("Begin proceed aspect Method {} : initiator={} | recepient={} | tx_type={}",
                new Object() {@Override public String toString() { return proceedingJoinPoint.getSignature().getName(); }},
                new Object() {@Override public String toString() { return request.getAgentAlias(); }},
                new Object() {@Override public String toString() { return request.getSubscriberMobile(); }},
                new Object() {@Override public String toString() { return request.getTxType(); }});

As you can see, I am creating new Objects and overriding the toString method over and over. I don't want to do this. Is there a better way to do this?

I am using the SLF4J 1.7.28 version which came bundled with Spring Boot starter. Please note I prefer using the SLF4J over other logging frameworks.

I tried slf4j-api 2.0.0-alpha1 version along with slf4j-simple binding since it has logger.atDebug.log() implementation. But I couldn't make it work properly.

Thank you.

Deuteronomy answered 15/4, 2020 at 10:15 Comment(0)
I
6

With SLF4J V2, this can also be done through the fluent API, for example:

logger.atDebug().setMessage("some expensive evaluation: {}").addArgument(this::doSomethingExpensive).log();

If you develop a Spring Boot app and depend on Spring for dependency management, this would mean Spring Boot 3.0 or higher as 2.x are bundled with SLF4J V1.x

Insolvable answered 11/8, 2023 at 2:29 Comment(0)
I
2

If you like to add some condition lines, you could add an if statement to check if the log level is enabled and then log the message.

Lazy loading is preserved but you're going to get something like:

if(log.isDebugEnabled()) {
 log.debug(....);
}
Instil answered 15/4, 2020 at 10:25 Comment(0)
A
1

Better way to do that:

if (logger.isDebugEnabled()) {
    logger.debug("Begin proceed aspect Method {} : initiator={} | recepient={} | tx_type={}",
                 proceedingJoinPoint.getSignature().getName(),
                 request.getAgentAlias(),
                 request.getSubscriberMobile(),
                 request.getTxType());
}

Or:

if (logger.isDebugEnabled()) {
    logger.debug("Begin proceed aspect Method " + proceedingJoinPoint.getSignature().getName() +
                 " : initiator=" + request.getAgentAlias() +
                 " | recepient=" + request.getSubscriberMobile() +
                 " | tx_type=" + request.getTxType());
}
Aceous answered 15/4, 2020 at 10:44 Comment(2)
That fits in old times before fluent API appeared. The usage if(logger.isDebugEnabled()) drawback is need to keep in sync if statement with logger statement same level. Which I find several time changed in harry or by newbies. So i suggest using fluent apiSkirl
This approach also hurts you if your company uses any automated test coverage checks, as this is one meaningless tests that needs to be written per log messageInsolvable
P
0

@Simas Joneliunas' answer was the right hint, but his example would only log the String representation of the lambda expression itself literally (like "DEBUG: Some expensive operation: () -> java.String"). The correct way of doing it would be for instance:

logger.atDebug()
    .setMessage("Some expensive operation: {}")
    .addArgument(() -> someExpensiveOperation())
    .log();

Further reading (thanks to him anyway): https://www.slf4j.org/manual.html#fluent

Pocahontas answered 22/5, 2024 at 11:42 Comment(0)
B
-1

On the lines of Simas Jonelius's answer if we do not wish to change any dependencies, we could do lazy evaluation by defining suppliers for each parameter.

Supplier<String> initiator = ()-> proceedingJoinPoint.getSignature().getName();
Supplier<String> recepient = ()-> request.getAgentAlias();
Supplier<String> subscriberMobile = ()-> request.getSubscriberMobile();
Supplier<String> tx_type = ()-> request.getTxType(); ;

logger.debug("Begin proceed aspect Method {} : initiator={} | recepient={} | tx_type={}",
                initiator.get(),
                recepient.get(),
                subscriberMobile.get(),
                tx_type.get());
Blok answered 12/8, 2023 at 6:10 Comment(2)
This will resolve the suppliers even before logger.debug gets called.Pocahontas
We are just declaring the suppliers and its method won't get invoked unless log.debug is called.E.g:Supplier<String> s = ()-> {System.out.println("called");return "d";}; System.out.println("End");Blok

© 2022 - 2025 — McMap. All rights reserved.