-
Notifications
You must be signed in to change notification settings - Fork 641
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Enhancement] Use Fluent Logging API to provide accurate, concise, and elegant logging #4697
Comments
Spring connector introduced log4j-to-slf4j-2.17.1.jar (according to 03aa825). You have excluded it from
|
The main problem with fluent logging APIs is that they are considerably slower than the classic API on disabled log statements. I did some benchmarks last year (cf. apache/logging-log4j2#1203) for the Log4j API. The results for SLF4J must be similar. Although the difference is in nanoseconds, I would use the classical API when possible. |
@ppkarwasz Sure, I will encapsulate Fluent Logging API and Supplier into LogUtils and use it only when the passed logging parameter includes time-consuming method calls. |
@Pil0tXia |
@pandaapo I've just wrote a Javadoc: Compared to the performance gap exhibited by the Fluent Logging API at the billion-per-second level, the overhead of myMethod() is more significant. How do you feel after adding this Javadoc statement? |
@Pil0tXia |
@pandaapo, the fluent API is slower, but you also must compare the order of magnitude:
So my advice would be: if the classic API has a method, use the classic API. If the classic API lacks a method (like for |
@ppkarwasz |
@Pil0tXia |
I think the encapsulated LogUtils is simpler than the original Fluent Logging API. LogUtils: LogUtil.debug(log, "A time-consuming method: {}", () -> myMethod()); The original Fluent Logging API: log.atDebug().setMessage("A time-consuming method: {}").addArgument(() -> myMethod()).log(); or: log.atDebug().addArgument(() -> myMethod()).log("A time-consuming method: {}"); |
I am still convinced that Log4j API provides the best of both approaches. Using log.debug("A time-consuming method: {}", () -> myMethod()); There is not need for |
@ppkarwasz I'd like to write that way however IDEA told me that It seems SLF4J does not support suppliers. |
@ppkarwasz I have replaced |
Since |
@ppkarwasz Resolved. A dependency transfer issue. I think the supplier usage of log4j2 is the most concise one. However, is it necessary for us to replace all the |
@ppkarwasz I noticed that both Slf4j and Log4j2 don't support try {
......
} catch (Exception e) {
log.debug("A time-consuming method: {}", () -> myMethod(), e);
} If encapsulation is still needed, I may prefer to continue using Slf4j. |
In the next release of Regarding passing a try {
......
} catch (Exception e) {
log.debug("A time-consuming method: {}", () -> myMethod(), () -> e);
} [all the arguments must be |
@ppkarwasz When rewriting the supplier usage of Log4j2 to Slf4j using rewrite-logging-frameworks, how would the following code be modified?
|
So, to replace some of the following writing: if (log.isDebugEnabled()) {
log.debug("A time-consuming method: {}", myMethod());
} we now have three options:
log.debug("A time-consuming method: {}", () -> myMethod()); Pro: concise; able to remove LogUtils; we are using Con: If one day we no longer use Log4j2, there may be a need to switch to a different logging artifact. Which one of these three options is not bad, but for the developer's habit, the writing of "Use @pandaapo Which one do you prefer? |
@ppkarwasz prefers to use a purer Log4j API. You prefer to use encapsulated LogUtils that contains Fluent Logging API (SLF4J). I prefer to use SLF4J's native API. As you said "these three options are not bad", so I sincerely believe that there is still a need for another reviewer to express views on these options. |
Currently
This is a common misconception about Log4j 2.x: Log4j API and Log4j Core (both developed by the Apache Logging Services team) are separate products (cf. API separation) the same way SLF4J and Logback (both developed by Ceki Gülcü) are separate products. If you were using the Log4j API, you can:
These changes do not require changes in the code. Regarding the advantages of the Log4j API, Remko wrote a nice StackOverflow answer some time ago. There is also one advantage Remko does not mention: the Log4j API uses the Apache development process. It's easier for the community to suggest changes in the API. |
Regarding whether EventMesh should be programmed against Slf4j or Log4j2 APIs, we need to consider two aspects. On one hand, when EventMesh is used as a client to access a distributed log collection platform, we need to check whether the log collection platform supports EventMesh's log framework. On the other hand, when EventMesh provides an SDK for users, we need to ensure that the provided SDK supports the user's log framework. Framework Dependency of Log Collection PlatformsThe logging services of most cloud service providers directly support underlying implementations such as Log4j2/Logback, skipping the Slf4j API layer:
Except for Google Cloud Functions, which only accepts Therefore, for EventMesh as a client accessing a distributed log collection platform, using Log4j2 or Slf4j+Log4j2 is both acceptable. SDK Provided by EventMeshSimilarly, referring to cloud service providers, most product SDKs depend on Slf4j and allow users to switch underlying implementations to Log4j2/Logback:
The advantage of this approach is that users can change the log format of the SDK, unifying it with the log format of their application. Some SDKs include built-in log implementations, allowing users to print SDK logs without adding log implementation dependencies to the client application: There are also a few SDKs that force users to use Slf4j+Log4j2:
Currently, the EventMesh SDK only depends on slf4j-api, and the specific implementation depends on the client application. If the EventMesh SDK uses log4j-api and the client application uses Slf4j+Logback, then the client application needs to additionally include log4j-to-slf4j. Therefore, I would prefer to retain Slf4j for the SDK. |
I have reviewed all the logs in EventMesh, most of them involve object references, a few of them being string operations. and few time-consuming methods. However, the main drawback of the Fluent Logging API is that, in terms of simplicity, it is not only inferior to LogUtils but even less so than the original usage with For this section of code in if (log.isDebugEnabled()) {
LogUtils.debug(log, "close tcp client failed.|remote address={}", channel.remoteAddress(), e);
} The Fluent Logging API would require the following: log.atDebug().setMessage("close tcp client failed.|remote address={}")
.addArgument(() -> channel.remoteAddress())
.setCause(e).log(); In terms of both lines of code and readability, it is not as favorable as the former. However, with LogUtils, you can write it as follows: LogUtil.debug(log, "close tcp client failed.|remote address={}", () -> channel.remoteAddress(), e); @pandaapo The existing issue with LogUtils is excessive wrapping and being too bulky. I would remove LogUtils' wrapping for all slf4j normal usage and redundant |
For these three options, I think we have already clearly explained our respective reasons. In cases where consensus cannot be reached among multiple feasible solutions, I believe there is a need for another reviewer to express views on these three options as I have mentioned several times before. |
…d elegant logging (#4698) * upgrade log4j and slf4j * recommanded example * add known dependencies * remove log4j-to-slf4j * Revert "recommanded example" This reverts commit 9c23c8b. * Deprecate `LogUtils` for removal The `LogUtils` class is not useful because: * it wraps every logger call in `isLevelEnabled`. These are not necessary since the arguments to the logger calls are simple variables and not computationally complex expressions, * currently it breaks location detection of the logger calls, which will always show `LogUtils` as origin, * it prevents the project from using source code rewrite tools like [`rewrite-logging-frameworks`](https://github.com/openrewrite/rewrite-logging-frameworks). This commit adds deprecates the class for removal in the next major version and adds Error Prone's `@InlineMe` annotations, to help users automatically rewrite code based on `LogUtils`. * Add minimal `errorPronePatch` task This task patches the codebase based on Error Prone suggestions (cf. [patching](https://errorprone.info/docs/patching), without adding Error Prone to the main compile task. It is a simplified version of the `gradle-errorprone-plugin` (cf. [installing](https://errorprone.info/docs/installation) and only works on a clean tree using JDK 11. Use as: ./gradlew clean errorPronePatch errorPronePatchTest * Rewrite `LogUtils` call sites This commit contains exclusively code changes performed by Error Prone and rewrites all the callsites to `LogUtils` to use the `Logger` object directly. * add suppliers * Revert "Rewrite `LogUtils` call sites" This reverts commit e78fcef. * Replace the time-consuming method parms with the supplier usage of LogUtil * Execute errorProne scripts and remove blank line by IDEA optimize import * Remove non-supplier methods in LogUtils * Remove unused trace and error log level * Rename LogUtils to LogUtil to save space * Fix checkstyle * Replace isXXXXEnabled to normal use * Rename messageLogger to static final MESSAGE_LOGGER * Fix all checkstyle warnings * run spotlessApply * Fix JDK11 Javadoc task failed * Add {} and fix javadoc task * Revert "Add minimal `errorPronePatch` task" This reverts commit 3e4dc11. * Turn back to surpress javadoc only on JDK8 * Fix logging lack param * Merge branch 'master' into pil0txia_enhance_4697 --------- Co-authored-by: Piotr P. Karwasz <[email protected]>
Search before asking
Enhancement Request
This issue is a sub-task of #4681.
The purpose of this issue is to provide syntactic support for resolving the logging problem #4681 by upgrading the versions of log4j and slf4j.
Background Knowledge
Disscussion: apache/logging-log4j2#2133
The additional
isDebugEnabled()
check beforelog.debug()
is performed in order to avoid invoking the methods referenced in themessage
field, thus improving logging performance. For example:Even when the log level is set to
info
, theformatMsg(rawMsg)
method will still be called and return a result before being passed as an argument tolog.debug()
. This takes time.Describe the solution you'd like
Solution Selection
Since slf4j does not support passing arguments through the Supplier introduced in log4j 2.13.0 (discussed in qos-ch/slf4j#70, no need to read though), we will not adopt the solution of lazy invocation by passing the Supplier parameter. Additionally, the usage of the Supplier appears redundant, and most contributors cannot understand the difference between using a Supplier or not without prior knowledge.
The Fluent Logging API since slf4j 2.0.0 provides a more elegant and space-saving way of writing code. The modifications to the
WatchFileManager
class demonstrate the recommended approach.Scenarios where Fluent Logging API is unnecessary
Only the scenario mentioned in the Background Knowledge section require the use of Fluent Logging API. For example, in the case of the changes in
Codec
class L224, where no object parameters are passed or existing objects are referenced, there is no additional performance overhead, and there is no need to pre-evaluate log levels. The simplest usage of slf4j can be directly applied.Are you willing to submit PR?
Code of Conduct
The text was updated successfully, but these errors were encountered: