This question already has answers here:
Is there a need to do a if(log.isDebugEnabled()) { ... } check? [duplicate]
(5 answers)
Closed 7 years ago.
When I was going through some code, I noticed the use of logger as follows,
if(logger.isDebugEnabled())
logger.debug("Something..");
But in some codes, I observed like this.
logger.debug("Something..");
When I looked at the source of log4j, in the debug() method of Logger itself if(logger.isDebugEnabled()) was checked. Then why do we need this unnecessary overhead if(logger.isDebugEnabled())??
It's useful when the String your passing to logger.debug(...) takes time to evaluate, in that case you can skip this evaluation if debug is not enabled.
if(logger.isDebugEnabled()) {
logger.debug("The meaning of life is " + calculateMeaningOfLife());
}
IMO this makes the code a lot less readable so it should only be used when there's a significant performance improvement.
isDebugEnabled is typically used to avoid unnecessary String concatination, eg this call
logger.debug("Line number = " + n);
first invokes Strings concatination then debug() and only then Logger detects that debug is not enabled and simply returns. This may significantly affect app performance.
This problem is solved in SLF4J which has formatted logging methods like this
public void debug(String format, Object arg);
Java must first resolve the string parameter passed to the debug method before it can call it.
logger.debug("Something.. var1=" + variable1 + " var2=" + variable2);
The above code will result in multiple String objects being created as each + creates another String so you'll have about 5 or more objects created before calling the method.
Debugging will mostly not be enabled so it's more efficient to check if debugging is enabled than to resolve the parameters all the time.
The statement:
if(log.isDebugEnabled()){
Is used just for performance reasons. It's use is optional since it is called by the log method internally.
But now you ask if this check is made internally, so why should I use it? It's very simple: if you log something as simple as this:
log.debug("ResultSet rs is retrieved from OracleTypes");
Then you don't need to do any check. If you compose a string to log using the append operator (+) like this:
log.debug("[" + System.getTimeInMillis() + "] ResultSet rs is retrieved from OracleTypes");
In this case you should check if the log is enabled or not, because if it isn't, even if the log is not made, the string composition is. And I must remind you that the use of operator "+" to concatenate strings is very inefficient.
SLF4J implementation (checked on version 1.7.10) calls isDebugEnabled() in some methods like:
public void debug(String format, Object... arguments) {
if(this.log.isDebugEnabled()) {
FormattingTuple ft = MessageFormatter.arrayFormat(format, arguments);
this.log.debug(ft.getMessage(), ft.getThrowable());
}
}
but there are also method overloads which doesn't internally check whether given loggin level is enabled, like in:
public void debug(String msg, Throwable t) {
this.log.debug(msg, t);
}
Another thing is that Logger implementation can be changed so if you want to be always sure your logger is called according to it's logging level, then you might want to consider using isDebugEnabled() method.
Related
I need a print statement. But sonar not allowed this type.
String txt="Something";
System.out.println("Print: "+txt);
Expected output:
Print: Something
I tried this format logger.log().its not working for our requirement.
Sonarqube does not like it if you use System.out, or if you have string concatenation or other computation in the arguments you pass to the logging function. Construct the full message on a separate line before logging it:
String txt = "Print: " + "Something";
String logMessage = "Print: " + txt;
logger.log(Level.INFO, logMessage);
If logger is not meeting your requirements, make it to do so by configuring it.
There are some widely used logging frameworks that are highly configurable and I doubt that they will not be able to do whatever you are doing with plain System.out.println
For example, check out very popular SLF4J and Logback as logging provider.
http://www.slf4j.org/
http://logback.qos.ch/
The following part of code raises a major bug at SonarQube :
"Invoke method(s) only conditionally."
How am I supposed to fix this?
if(us != null){
logger.info("Log this: {}", us.toString());
}
The call to us.toString() is redundant, toString() method will be called regardless the configured log level. You should pass only us as an argument to info without an if statement.
logger.info("Log this: {}", us);
As stated at the comments of the question, another working answer is:
if(logger.isInfoEnabled() && us != null){
logger.info("Log this: {}", us.toString());
}
You can just ignore this but it might be good to handle this scenario if possible, It would help us to handle and cutoff unnecessary computations.
One thing what it suggests here is to check if the Log Level that you are going to use is enabled or not.
if(logger.isInfoEnabled() && us != null){
// this inner code will only get executed if the above is true
logger.info("Log this: {}", us.toString());
}
Imagine having a complex task running inside, it would be a waste of time to do that if you are not going to log it anyways, if the log level is disabled. Logger will internally check that for you but doing it now before invoking the .info() will save you some cycles.
Passing message arguments that require further evaluation into a Guava com.google.common.base.Preconditions check can result in a performance penalty. That's because whether or not they're needed, each argument must be resolved before the method is actually called.
Similarly, passing concatenated strings into a logging method can also incur a needless performance hit because the concatenation will be performed every time the method is called, whether or not the log level is low enough to show the message.
Instead, you should structure your code to pass static or pre-computed values into Preconditions conditions check and logging calls.
Specifically, the built-in string formatting should be used instead of a string concatenation, and if the message is the result of a method call, then Preconditions should be skipped altogether, and the relevant exception should be conditionally thrown instead.
Noncompliant Code Example
logger.log(Level.DEBUG, "Something went wrong: " + message);
// Noncompliant; string concatenation performed even when log level too high to show DEBUG messages
logger.fine("An exception occurred with message: " + message);
// Noncompliant
LOG.error("Unable to open file " + csvPath, e); // Noncompliant
Preconditions.checkState(a > 0, "Arg must be positive, but got " + a);
// Noncompliant. String concatenation performed even when a > 0
Preconditions.checkState(condition, formatMessage()); // Noncompliant. formatMessage() invoked regardless of condition
Preconditions.checkState(condition, "message: %s", formatMessage());
// Noncompliant
Compliant Solution
logger.log(Level.SEVERE, "Something went wrong: {0} ", message);
// String formatting only applied if needed
logger.fine("An exception occurred with message: {}", message);
// SLF4J, Log4j
logger.log(Level.SEVERE, () -> "Something went wrong: " + message);
// since Java 8, we can use Supplier , which will be evaluated lazily
LOG.error("Unable to open file {0}", csvPath, e);
if (LOG.isDebugEnabled() {
LOG.debug("Unable to open file " + csvPath, e);
// this is compliant, because it will not evaluate if log level is above debug.
}
Preconditions.checkState(arg > 0, "Arg must be positive, but got %d", a); // String formatting only applied if needed
if (!condition) {
throw new IllegalStateException(formatMessage()); /
/ formatMessage() only invoked conditionally
}
if (!condition) {
throw new IllegalStateException("message: " + formatMessage());
}
Exceptions
catch blocks are ignored because the performance penalty is unimportant on exceptional paths (catch block should not be a part of standard program flow). Getters are ignored as well as methods called on annotations which can be considered as getters. This rule accounts for explicit test-level testing with SLF4J methods isXXXEnabled and ignores the bodies of such if statements.
It's related to performance issues. They recomend just putting a pure String or a final variable defined previously.
final var message = "Log this: " + us.toString();
logger.info(message);
https://sonarcloud.io/organizations/default/rules?languages=java&open=java%3AS2629&q=S2629
Short easy answer: just delete the .toString from your code since the formatter will take care of changing it to String for you.
I am using log4j v1.2.14 for logging in my project and I am also using Java 7 String.format() to put variables in my output. Currently I am writing
LOGGER.info(String.format("Your var is [%s] and you are [%s]", myVar, myVar1));
Is this really the best way to output strings? I feel that log4j should have this implemented implicitly as below:
LOGGER.info("Your var is [%s] and you are [%s]", myVar, myVar1);
Have I missed something? Further, are there any Java logging frameworks that support this?
slf4j's api provides "parameterized logging", which allows you to do exactly that, although with a slightly different syntax. The example there is:
logger.debug("Value {} was inserted between {} and {}.", newVal, below, above);
For an implementation, you can use Logback which implements slf4j natively, or the slf4j bindings to connect with log4j or other loggers. The User Manual explains that, along with a short example.
Using String.format, +, or a string formatter other than the one provided by your logging system (log4j for example) is considered as a bad practice.
Usually, in the code there are lots of low level logs (debug, info) you don't want to see in production. If you use for example String.format to format the string to log, then you will allocate on the heap and format a new String, which can be very long and consume resources, even if at the end nothing will be logged (for example if the log4j min level is set to warning or error).
By using the logger formatter system (like the one from log4j), you allow your logger to avoid the generation of the formatted string if it doesn't need to be logged.
This may make a great difference in some cases.
Log4j supports internal formatting. I haven't found it documented anywhere, but I saw an example of it here:
https://logging.apache.org/log4j/2.x/manual/markers.html
I tried it out and it works! I'm on log4j 2.11.2.
int i = 42;
String str1 = "the answer";
String str2 = "life, the universe, and everything";
console.info("{} is {} to {}", i, str1, str2);
Looking at the javadoc for Logger, I'd say it was introduced in Lo4j 2, and supports up to 10 parameters.
https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/Logger.html
Btw, In this scenario there is not much difference between using + to add your variables to the string and String.format - unless you really want to reuse the "Your var is..." in all your logs.
slf4j lets you log as
log.info("Your var is {} and you are {}", myVar, myVar1);
Note the use of {} instead of print formatters. Also this requires Java >= 1.5
I upvoted the "use logger.debug(format, varargs)" approach first, because it doesn't allocate extra String when it's not needed.
But then it occurred to me that logger.debug(String format, Object... varargs) still allocates an Array for the varargs.
Tried this on https://godbolt.org/noscript/java
class VarargTest {
static boolean debugging = false;
static void debug(String format, Object... args) {
if (debugging) {
System.out.println(String.format(format, args));
}
}
static void logVarargs(int n) {
debug("debug message {}", n);
}
static void logIf(int n) {
if (VarargTest.debugging) {
debug("debug message 2 " + n);
}
}
}
and indeed, the resulting debug() call allocates an Array.
So the fastest code should be
if (logger.isDebugEnabled()) {
logger.debug("format {}", arg);
}
If it's not super performance critical, a more readable and reasonably fast code would be simply
logger.debug("format {}", arg);
I'll put the question upfront:
Is there a logger available in Java that does encryption(preferably 128-bit AES or better)?
I've done a lot of searching for this over the last couple of days. There's a few common themes to what I've found:
Dissecting information between log4j and log4j2 is giving me headaches(but mostly unrelated to the task at hand)
Most threads are dated, including the ones here on SO. This one is probably the best I've found on SO, and one of the newer answers links to a roll-your-own version.
The most common answer is "roll-your-own", but these answers are also a few years old at this point.
A lot of people question why I or anyone would do this in Java anyway, since it's simple enough to analyze Java code even without the source.
For the last point, it's pretty much a moot point for my project. We also use a code obfuscator and could employ other obfuscation techniques. The point of using encryption is simply to raise the bar of figuring out our logs above "trivially easy", even if it's only raised to "mildly time-consuming". A slightly relevant aside - the kind of logging we're going to encrypt is intended merely for alpha/beta, and will likely only include debug, warn, and error levels of logging(so the number of messages to encrypt should be fairly low).
The best I've found for Log4j2 is in their documentation:
KeyProviders
Some components within Log4j may provide the ability to perform data encryption. These components require a secret key to perform the encryption. Applications may provide the key by creating a class that implements the SecretKeyProvider interface.
But I haven't really found anything other than wispy statements along the lines of 'plug-ins are able of doing encryption'. I haven't found a plug-in that actually has that capability.
I have also just started trying to find other loggers for Java to see if they have one implemented, but nothing is really jumping out for searches like 'java logging encryption'.
Basically log encryption is not best practise there are limited situations where you can need this functionality. As mainly people which have access to logs have also access to JVM, and in JVM all the logs are at least generated as Strings so even if you encrypt them in the log file or console the real values will be available in JVM String Pool, so if anyone will every need to hack your logs it will be as easy as have a look in string pool.
But anyway if you need a way to encrypt the logs, and as there is no generic way for this, the best way in my opinion is to go with Aspect J. This will have minimum impact on you sources, you will write code as you have done before, but the logs will be encrypted. Following is a simple application code which will encrypt all the logs from all the compiled sources using Aspctj, and Slf4j as logging facade and Log4j2 as logging implementation.
The simple class which logs the "Hello World"
public class Main {
private static final transient Logger LOG = LoggerFactory
.getLogger(Main.class);
public static void main(String[] args) {
LOG.info("Hello World");
LOG.info("Hello {0}", "World 2");
}
}
Aspect which encrypts (in this case just edits the text)
#Aspect
public class LogEncryptAspect {
#Around("call(* org.slf4j.Logger.info(..))")
public Object encryptLog (ProceedingJoinPoint thisJoinPoint) throws Throwable{
Object[] arguments = thisJoinPoint.getArgs();
if(arguments[0] instanceof String){
String encryptedLog = encryptLogMessage ((String) arguments[0], arguments.length > 1 ? Arrays.copyOfRange(arguments, 1, arguments.length) : null);
arguments[0] = encryptedLog;
}
return thisJoinPoint.proceed(arguments);
}
// TODO change this to apply some kind of encryption
public final String encryptLogMessage (String message, Object... args){
if(args != null){
return MessageFormat.format(message, args) + " encrypted";
}
return message + " encrypted";
}
}
The output is :
[main] INFO xxx.Main - Hello World encrypted
[main] INFO xxx.Main - Hello World 2 encrypted
I define a Util class to log using log4j, and a method is like below:
public static void info(String msg) {
if (logger.isInfoEnabled())
logger.info(msg);
}
But if the log level is set to "Error" (and thus logger.isInfoEnabled() is false), and I use the below to log, will the two String instances be created?
LogUtil.info("String instance" + stringParam);
even though these two String instances may be collected by gc, construct a instance will be still costs.
I guess if I check the log level before calling logger.info(msg) may help , but this may the code not clean, that's the reason why I define Util class.
How can I to write a clean code that if I do not set the level to info, it will neither log anything nor create the String instance?
Don't define the Util class and you can avoid some of the string creation overhead.
Quite apart from anything else you're isInfoEnabled check is completely pointless as logger itself will do the check.
Use:
logger.log(Level.INFO, "Message {0} with {1}", new Object[] { param1, param2 });
And you will avoid most of the string creation overhead as it will only toString() param1 and param2 and construct the final message if the INFO level is turned on.
There is the cost of allocating the new Object[] array but that is tiny
I'm afraid your util class is completely useless, as the logger already does the log level check internally. The idea of doing these checks manually outside is to reduce the overhead caused by unnecessary code that prepares the log message (mostly String concatenation) even if then it is not logged due to the current log level:
Suppose you want to log the user input - which is a Date object. Here, date-to-string conversion and string concatenation is done even if the log level is lower than info:
logger.info("user input: " + dateFormatter.format(userInput));
So the solution is to check the log level first:
if (logger.isInfoEnabled()) {
logger.info("user input: " + dateFormatter.format(userInput));
}
But if you now move the check into a utililty method that gets the full message as parameter, you'll end up having the same problem than above!
Others already explained in their answers how to use the extended log methods to let the logger internally do the formatting:
logger.log(Level.INFO, "Current user input: {0}", userInput);
But also here, if the parameter has to be specially formatted first, it does not solve the overhead problem. Again, you'll have to do an additional log-level check first.
Since Java 8, you could write a utility-method that can be used effeciently and smartly by using lambda expressions:
public static void info(Supplier<String> messageSupplier) {
if (logger.isInfoEnabled()) logger.info(messageSupplier.get());
}
Usage:
LoggerUtil.info(() -> "user input: " + dateFormatter.format(userInput));
Here, the lambda expression will only be executed if info logging is enabled!
You could also profit from Slf4J logger (defacto new standard in the industry), that has friendlier syntax:
logger.info("Configuration directory: {}, {}", param1, param2);
It is also possible to configure Slf4J with different back-ends including Log4J. Hope this helps
even though this String instance will be collected by gc, -
In logger.info("String instance"); , "String instance" is a String Literal and will be created on the String Pool. Unfortunately, it will persist as long as the program runs (i.e, until the JVM shuts down). Use a StringBuilder or String(char[]) to prevent adding the String to the String Pool.