Replies: 32 comments 84 replies
-
Anything that can be forgotten to be written (and isn't caught by the compiler) will be by some people, so I'm not sure this is a great idea. |
Beta Was this translation helpful? Give feedback.
-
Honestly I prefer the look and readability of the Proposed change much more then how it is currently. However from what I can see there is nothing stopping us from having both types. Its fluent so I prefer shorter names or at least no get/set though honestly that is just preference. The main thing is that its much more common for arguments to be added after the message, it will feel wrong to a lot of people if its not. Think of String.format or other similar methods in other systems like Androids Timber or any other single method logger, its not the end all be all but the ability for a user to just know how something works is very powerful in how they feel about something. Both formats can easily be supported though just by having a log(String) that is basically just.
And that would take the decision and put it in the users hands how they want their code formatted while providing a shortcut instead of message and then log. If only one can be supported though I would prefer the second one. and as it is so close to a Builder I believe it won't be confusing for most people as its a common style used. IDEs are also really good at detecting these kinds of mistakes and warning the user as just because something is technically correct doesn't mean its a good idea or what the user wants |
Beta Was this translation helpful? Give feedback.
-
I have the same opinion with @ShadowLordAlpha about the following part:
On the other hand, I feel that calling an empty So I will probably do not use both, use "using fluent API, log message with arguments" instead: logger.atDebug()
.log("Temperature of {} changed from {} to {}", room, oldTemp, newTemp); BTW If the var logBuilder = logger.atDebug()
.message("Temperature of {} changed from {} to {}");
for (var room : rooms) {
logBuilder.arg(room).arg(loadOldTemp(room)).arg(loadNewTemp(room)).log();
} But for now, the |
Beta Was this translation helpful? Give feedback.
-
I like the general direction of a snippet proposed by @KengoTODA, but IMO it should be like this:
|
Beta Was this translation helpful? Give feedback.
-
Having logger.atDebug("Temperature of {} changed from {} to {}")
.arg(room).arg(oldTemp).arg(newTemp)
.log(); Or even I suppose you could still allow |
Beta Was this translation helpful? Give feedback.
-
We need structural logging: to produce NDJson files which then delivered to Elasticsearch by Fluentbit. Writing to file is the "most" resilient way to recover from network / log-collector outages. Oftentimes we are not interested in message but in structured arguments. With old API: log.atInfo().addKeyValue("old", 10).addKeyValue("new", 12).log("ignore me"); With new API we can make message null: log.atInfo().addKeyValue("old", 10).addKeyValue("new", 12).log(); There is a rise of structured logging, the days of formatted messages in their sunset... )) Please consider better name than |
Beta Was this translation helpful? Give feedback.
-
Why not add some overloaded methods to the |
Beta Was this translation helpful? Give feedback.
-
I assume the fluent API necessity is quite a rare case and it is not going to totally replace the standard API, so I'd prefer the existing variant. But |
Beta Was this translation helpful? Give feedback.
-
I'm personally happy with the existing API, as it allows for the following code: logger.atDebug()
.log("Temperature of {} changed from {} to {}", room, oldTemp, newTemp); But I'm aware that this doesn't allow any of the arguments to be lambdas for lazy logging. So I suggest looking at Google's flogger and copying their logger.atDebug()
.log("Temperature of {} changed from {} to {}", lazy(() -> getExpensiveRoom()), oldTemp, newTemp); Then, by my understanding, this would make I've not thought about the key-value use case, though... |
Beta Was this translation helpful? Give feedback.
-
I arrived here when searching in vain for support for an atLevel() call. I consider this the only really useful addition, others being more like syntactic convenience. Yet being able to specify the level as a parameter greatly reduces code clutter and code coverage requirements, as otherwise you end up wrapping 'if' statements around each log line. The traditional API does not have such option. |
Beta Was this translation helpful? Give feedback.
-
I saw on the SLF4J website you are asking for opinions regarding a change to the fluent API. So here are my 2 cents worth. My preference would be something along the lines of: logger.msg("debug message {} {}","arg1","arg2").logDebug();
logger.msg("Warning {}", "arg1").logWarn();
logger.msg("error {}", "arg1").withCause(ex).logError();
logger.msg("error").with("key",value).withCause(ex).logError();
logger.msg("error {}, {}", "a", "b").withCause(ex).logDebug();
logger.msg().withCause(ex).logError(); |
Beta Was this translation helpful? Give feedback.
-
The fluent API is a very welcome addition! I agree that a missing "commit" call is an important hazard. |
Beta Was this translation helpful? Give feedback.
-
I'm also a proponent of structured logging. One thought would be to apply idea of adding key/value pairs to the log message to regular text logs as well. With this in mind, perhaps there is no longer a need for the message template. I think having short method names is important in an api that is likely to get used a lot. Also, I would find some value in using the same method names as flogger where the concepts overlap. These thoughts lead to something like this as a proposal: (I'm not attached to the method names, as long as they are short ) LoggingEventBuilder withCause(Throwable cause);
LoggingEventBuilder marker(Marker marker);
LoggingEventBuilder keyVal(String key, Object value);
LoggingEventBuilder keyVal(String key, Supplier<Object> value);
LoggingEventBuilder val(Object value);
LoggingEventBuilder val(Supplier<Object> value);
// maybe add some overloads to avoid auto-boxing overhead eventually...
void log();
void log(String message); A call like this: logger.atDebug()
.keyVal("room",room)
.keyVal("oldTemp",oldTemp)
.keyVal("newTemp",temp)
.val( ()->getCurrentStats() )
.log("Room temperature changed"); might result in something like this:
This has the attraction of removing the formatting details from each log message, and letting either the logger setup at instantiation, or the backend do it in a consistent manner. Also, it reduces the opportunities for mismatching placeholders and variables. I suspect the use of the With regard to the issue of the missing |
Beta Was this translation helpful? Give feedback.
-
I wonder if there is an appetite for the following... I often find myself doing:
I like this 'cos it actually makes 3 formatted lines in the log. PERHAPS the fluent API might be able to offer something like:
again: 3 formatted lines created by a single logging operation... |
Beta Was this translation helpful? Give feedback.
-
I just want to throw my hat in the ring for something like:
I know I am being lazy, but when starting in on a new block of code (or major change/discovery process)
This forces logging statements to appear, pretty much regardless of how logging is configured (I mean: no-one disables ERROR level, right...right?) Then, when I get nearer to proper code, I change my debugging:
Call me a weirdo or an API abuser if you must, but...I THINK my way of working boils down to: "when I'm 'in the groove' editing a bit of code, I'm happy to edit a variable in the source but I don't want to go off and edit (maybe make a new configuration in) a logback.xml (or whatever) file." I see/accept the issue with changing levels at runtime...with this thought, I'm more focuseed on the typical compile-run-fail-change-repeat DEV lifecycle than how a runtime reconfiguration might play out. |
Beta Was this translation helpful? Give feedback.
-
Reading all the comments and trying to think of what I want from slf4j:
So I think my conclusion is that there are some benefits to the fluent API for me as a user (atLevel, lambdas, custom data that is not in the message), but that my message formatting is usually best served by log("{}", arg1).
The .log method still taking a variable number of arguments (though I'd be happy with that being just one method with Object...). |
Beta Was this translation helpful? Give feedback.
-
So what's the actual initial outcome ?
…On Tue, 7 Jun 2022, 7:41 pm Clément MATHIEU, ***@***.***> wrote:
You pointed to ThreadContext (of Log4j 2) & MDC. At the moment I see
thread local storage as a "broken" concept
Yes, it doesn't play well with async.
I mentioned MDC because with the current alpha API I ended up using it a
few times as a workaround to the lack of being able to add several named
attributes succinctly. It's trivial to write your own utility method that
add multiple attributes to the MDC and clean them up using
try-with-resource. By contrast, as a SLF4J user you cannot add this
functionality to LoggingEventBuilder.
Another major drawback of MDC is that those attributes will be added to
all logging events until they are cleared. It is semantically very
different from being able to pass a set of attributes to a some
LoggingEventBuilders.
I do agree that LoggingEventBuilder should provide such method (that's why
I wrote my initial comment).
—
Reply to this email directly, view it on GitHub
<#280 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AS7LQCEOGW2L6ZYKBOBPD5LVN4KOJANCNFSM5SFYLD6A>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
I have just reread this discussion and found no mention of the following, that I think is worth keeping in mind: the |
Beta Was this translation helpful? Give feedback.
-
True, but that explosion of overloads is what makes it so convenient for the users, so is there a compromise that keeps it both manageable in the slf4j codebase and convenient for the users? |
Beta Was this translation helpful? Give feedback.
-
Our own logger works garbage free and looks like: import namespace.logger.*;
msg("debug message {} {}", args().add("arg1").add("arg2"));
warn("Warning {}", args().add("arg1");
error("error {}", args().add("arg1").add(ex));
error("error {}, {}", args().add("a").add("b").add(ex));
error("", args().add(ex)); The static All these methods are internally written like:
When the logging of for example trace is disabled, the JIT eliminates the call to trace(...), except you start do somehow add strings or alike. The WArgs implementation does have an add method for any scalar type and alike. Simplified it looks like:
We have within the log method (should it be reached) some clever hacks to enable per log level for example stack traces and alike, as we know exactly that we have three method calls ( In fact, internally we only use JSON, there is much more, but I personally like the approach a lot. Feel free to think if you like this as well. |
Beta Was this translation helpful? Give feedback.
-
There are many comments about how a static helper could be introduced, as used in Flogger, to allow lambda arguments with the old API. Seeing that there is precedence and interest, it may be valuable to fork this discussion and start a new thread to propose exactly this: extending the old API with a static helper to ease passing lambdas. This thread is about the new builder-like API, which is an alternative to the old API, not a replacement. The point of the current discussion, as stated in the opening message, is to evaluate moving the set-argument, set-cause, set-marker, put-key-value etc. method calls to after the set-message call, with the purpose of improving readability and with the consequence of requiring a commit-like call at the end. The case of the new builder-like API being unnecessary does not seem to be under discussion, and I think that would be a very difficult case to defend. |
Beta Was this translation helpful? Give feedback.
-
Hello, I'd like to participate in the discussion with my two cents. I think with the new proposal is quite easy to forget to add the .log() and I'd try to avoid that situation. I'd expect that it writes something, even if it's not well formatted, but it writes something instead of doing nothing. I'd go for the proposal made by Adrian Shum in SLF4J-526 with lambda.
And taking the opportunity of the discussion, at the risk that what I'm going to propose was already discussed, I'd like to propose to avoid the parametrized messages in the fluent API in favor of less error prone sintax, leaving the parametrized messages for the traditional API. The proposal is to generate the message with the consecutive calls to the builder avoiding the error of diferent number of brakets and arguments. It would be something like:
In any case, I like the new fluent API and I'm looking forward to have a slf4j stable version including the API :) Thanks! |
Beta Was this translation helpful? Give feedback.
-
Why the
|
Beta Was this translation helpful? Give feedback.
-
Many thoughtful replies here. My personal take, as a long-time user and advocate of SLF4J 1.x who hasn't used 2.0 at all:
My apologies if my ignorance has caused me to suggest things already in place, or already discarded. |
Beta Was this translation helpful? Give feedback.
-
(not in reply to @larrywest42) |
Beta Was this translation helpful? Give feedback.
-
Can you not use the builder to allow both? The general idea is that you always end with a This would utilize varargs for some of the methods, but I would think that to be acceptable as long as the JavaDoc states that this will incur a performance penalty for those methods. All of the following would be ways to build the same thing: logger.debug()
.message("Temperature of {} changed from {} to {}" )
.arg(room).arg(oldTemp).arg(newTemp)
.log();
logger.debug()
.arg(room).arg(oldTemp).arg(newTemp)
.message("Temperature of {} changed from {} to {}" )
.log();
logger.debug()
.arg(room).arg(oldTemp).arg(newTemp)
.log("Temperature of {} changed from {} to {}");
logger.debug()
.log("Temperature of {} changed from {} to {}", room, oldTemp, newTemp);
logger.debug()
.message("Temperature of {} changed from {} to {}" )
.log(room, oldTemp, newTemp); One other note, the logger.debug()
.message("Temperature of {} changed from {} to {}" )
.log("bedroom", oldTemp, newTemp); |
Beta Was this translation helpful? Give feedback.
-
Sorry, I am late to the party. I don't really see a big benefit in having a fluent API here. What I need in formatting can easily be achieved by passing a lambda like If you want to use a fluent API: since message and arguments IMHO belong together, why not just pull both together like But I really have no strong feelings about the fluent API. But I think that this and other things should be settled rather sooner than later because SLF4J really needs a stable version properly supporting jigsaw modules. At least for me, that is currently the biggest problem with SLF4J. |
Beta Was this translation helpful? Give feedback.
-
I've probably missed this discussion somewhere but, are lambdas performant enough to use them for logging? // structured logging of key-value pairs
logger.info(msg -> msg.put("old",10).put("new",12));
// string formatted logging
logger.info("Temperature of {} changed from {} to {}", msg -> msg.arg(room).arg(oldTemp).arg(newTemp));
// naive implementation, you can proabably defer the FluentApiBuilder creation until you actually need it.
public void info(Consumer<FluentApiBuilder> logIt){
FluentApiBuilder builder = new FluentApiBuilder();
logit.accept(builder);
// or defer .build()/.log() until later
logBuilder(Level.INFO, builder.build()));
}
public void info(String template, Consumer<FluentApiBuilder> logIt){
FluentApiBuilder builder = new FluentApiBuilder(template);
logit.accept(builder);
// or defer .build()/.log() until later
logBuilder(Level.INFO, builder.build()));
} The |
Beta Was this translation helpful? Give feedback.
-
SLF4J may define a Java annotation This annotation is also supported by many static analysis tools, so definitely will help not to miss the |
Beta Was this translation helpful? Give feedback.
-
The API methods can be categorized in Intermediate and Terminal operations, similar to jdk Stream API. Intermediate ops would be e.g. the Logger.atXXX() methods. Terminal ones would be the Logger.log(....) methods. Agreed that the no-arg Logger.log() method is not the most user-friendly or error-resisting. Rather, it'd be nicer to have/keep Terminal ops such as:
which means removing the Intermediate APIs such .setMessage(...), .setCause(Throwable t), .... because those, as Intermediate ops, are confusing if the Logger.log(....) methods are now Terminal ones. The other proposed Intermediate ops APIs are nice. |
Beta Was this translation helpful? Give feedback.
-
Hello all,
Preamble
SLF4J version 2.0 adds a fluent API to the Logger interface with the traditional API from version 1.7.x staying as is, untouched.
The fluent API builds a "logging event" object piecemeal using a LoggingEventBuilder. The atTrace(), atDebug(), atInfo(), atWarn() and atError() methods, all new in the org.slf4j.Logger interface, return an instance of LoggingEventBuilder. For disabled log levels, the returned LoggingEventBuilder instance does nothing, thus preserving the nano-second level performance of the traditional logging interface.
Rationale behind the fluent API
The fluent API is intended to reduce the combinatorial explosion in the number of methods required to support multiple inputs such as message, zero or more arguments, zero ore more markers, zero or more key-value-pairs and zero or one throwable. Moreover, some of these values may be passed via lambda expressions adding further method combinations. With just 17 (=5+12) methods the current API supports all possible combinations of the above (instead of 280 methods).
Description of proposal
In SLF4J-526, Adrian Shum proposed to modify the fluent API.
In the current version of the fluent API (SLF4J 2.0), to emit a debug message with two parameters, one has to write the following
Existing variant
We propose that the fluent API be changed as follows.
Proposed variant
The proposed variant is a little easier to write and also to read. However, it entails one inconvenience. Indeed, in case the user forgets to call the no-argument
log()
method, then nothing will happen, i.e. no logging will occur. With the current API, it is harder for the user to forget thelog(String)
method containing the message content.Before going forward with this proposal, we would like to receive input by the user community.
Update (release 2.0.0-beta0)
API was updated in 2.0.0-beta0 by adding the
setMessage(String)
,setMessage(Supplier<String>)
andlog()
methods to theLoggingEventBuilder
interface.Update (release 2.0.0-beta1)
Methods in the
LoggingEventBuilder
interface returning an instance ofLoggingEventBuilder
are now annotated with @CheckReturnValue. This allows compile time detection of missing calls to one of the terminating log() methods. Many thanks to @jreznot and @vlsi their help and suggestions.Beta Was this translation helpful? Give feedback.
All reactions