How is it possible, to improve your logging mechanism, by not having the overhead of string concatenations?
Consider the following example:
import java.util.logging.Level;
import java.util.logging.Logger;
public class LoggerTest {
public static void main(String[] args) {
// get logger
Logger log = Logger.getLogger(LoggerTest.class.getName());
// set log level to INFO (so fine will not be logged)
log.setLevel(Level.INFO);
// this line won't log anything, but will evaluate the getValue method
log.fine("Trace value: " + getValue());
}
// example method to get a value with a lot of string concatenation
private static String getValue() {
String val = "";
for (int i = 0; i < 1000; i++) {
val += "foo";
}
return val;
}
}
The log method log.fine(...) will not log anything, because the log level is set to INFO. The problem is, that the method getValue will be evaluated anyway.
And this is a big performance issue in big applications with a lot of debug statements.
So, how to solve this problem?
Since Java8 it is possible to use the new introduced lambda expressions for this scenario.
Here is a modified example of the logging:
LoggerTest.class
import java.util.logging.Level;
import java.util.logging.Logger;
public class LoggerTest {
public static void main(String[] args) {
// get own lambda logger
LambdaLogger log = new LambdaLogger(LoggerTest.class.getName());
// set log level to INFO (so fine will not be logged)
log.setLevel(Level.INFO);
// this line won't log anything, and will also not evaluate the getValue method!
log.fine(()-> "Trace value: " + getValue()); // changed to lambda expression
}
// example method to get a value with a lot of string concatenation
private static String getValue() {
String val = "";
for (int i = 0; i < 1000; i++) {
val += "foo";
}
return val;
}
}
LambdaLogger.class
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
public class LambdaLogger extends Logger {
public LambdaLogger(String name) {
super(name, null);
}
public void fine(Callable<String> message) {
// log only, if it's loggable
if (isLoggable(Level.FINE)) {
try {
// evaluate here the callable method
super.fine(message.call());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
With this modification you can improve the performance of your applications a lot, if you have many log statements, which are only for debugging purposes.
Of course you can use any Logger you want. This is only an example of the java.util.Logger.
#bobbel has explained how to do it.
I'd like to add that while this represents a performance improvement over your original code, the classic way of dealing with this is still faster:
if (log.isLoggable(Level.FINE)) {
log.fine("Trace value: " + getValue());
}
and only marginally more verbose / wordy.
The reason it is faster is that the lambda version has the additional runtime overheads of creating the callable instance (capture cost), and an extra level of method calls.
And finally, there is the issue of creating the LambdaLogger instances. #bobbel's code shows this being done using a constructor, but in reality java.util.logging.Logger objects need to be created by a factory method to avoid proliferation of objects. That implies a bunch of extra infrastructure (and code changes) to get this to work with a custom subclass of Logger.
Apparently Log4j 2.4 includes support for lambda expressions which are exactly useful for your case (and which other answers have replicated manually):
From https://garygregory.wordpress.com/2015/09/16/a-gentle-introduction-to-the-log4j-api-and-lambda-basics/
// Uses Java 8 lambdas to build arguments on demand
logger.debug("I am logging that {} happened.", () -> compute());
Just create wrapper methods for your current logger as:
public static void info(Logger logger, Supplier<String> message) {
if (logger.isLoggable(Level.INFO))
logger.info(message.get());
}
and use it:
info(log, () -> "x: " + x + ", y: " + y);
Reference: JAVA SE 8 for the Really Impatient eBook, pages 48-49.
use a format String, and an array of Supplier<String>. this way no toString methods are called unless the the log record is actually publishable. this way you dont have to bother with ugly if statements about logging in application code.
Related
In game development many methods are called very often (e.g. 60 times per second). Sometimes I'd like to log catched exceptions even in such methods but not that often.
My idea was to only log the first occurrence of an exception that occurend in a specific method but I did not find any convenient or build-in solution to do that (at least in java).
Unless you're using a specific logger that supports this feature, the behavior you're trying to get can be acheived through a flag system.
For example, you could add in your logging class a map like this:
Map<Class, MyTimer> logged = new HashMap<>();
And in your logging function (let's say log(Object objectToLog)):
if (objectToLog instanceof Exception) {
MyTimer t = logged.get(objectToLog.class);
if (t == null || t.getDifferenceTimeWithNow() > MYLOG.DELAY ) {
log(objectToLog);
logged.put(objectToLog.class, t.setToNow());
}
else {
t.setToNow();
}
}
This way, the only call you have to do sixty times a second is a simple Map#get, an if comparaison and a MyTimer#setToNow.
To solve this problem in my home-made game project I decided to log only "new" messages. I used following approach:
Detect log-method call "location".
If the message from this location has changed from the last time then log it, ignore otherwise.
I can't show you my actual code, as it was not on Java and used macros, but with log4j Filters it can be achieved like this:
import java.util.*;
import org.apache.log4j.Level;
import org.apache.log4j.spi.Filter;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.LocationInfo;
public class DuplicateMessagesFilter extends Filter {
Map<String,String> previousMessages = new HashMap<String,String>();
#Override
public int decide(LoggingEvent event) {
LocationInfo locationInfo = event.getLocationInformation();
String location = locationInfo.getFileName() + ":" + locationInfo.getLineNumber();
String previousMessage = previousMessages.get(location);
if(previousMessage != null && previousMessage.equals(event.getMessage())){
return DENY;
}
previousMessages.put(location, event.getMessage());
return ACCEPT;
}
}
Is there a command line tool that can automatically fix non formatting but still seemingly simple CheckStyle issues in Java source code like:
Avoid inline conditionals
Make "xxx" a static method
I know there are various tools to fix formatting and some IDEs have fairly advanced quick fixers but so far I could not find anything that can recursively run on a source code folder or be integrated in a commit hook.
Sounds like a nice challenge, but I was also unable to find an automatic tool that can do this. As you already described, there are plenty of options to change code formatting. For other small issues, you could perhaps run Checkstyle from the command-line and filter out fixable warnings. A library for parsing and changing Java source code could help to actually make the changes, like for example JavaParser. Perhaps you could write a custom tool in a relatively small amount of time using a Java source code manipulation tool like JavaParser.
(There are other tools like ANTLR that could be used; see for more ideas this question on Stack Overflow: Java: parse java source code, extract methods. Some libraries like Roaster and JavaPoet do not parse the body of methods, which makes them less suitable in this situation.)
As a very simple example, assume we have a small Java class for which Checkstyle generates two messages (with a minimalistic checkstyle-checks.xml Checkstyle configuration file that only checks FinalParameters and FinalLocalVariable):
// Example.java:
package q45326752;
public class Example {
public static void main(String[] arguments) {
System.out.println("Hello Checkstyle...");
int perfectNumber = 1 + 2 + 3;
System.out.println("Perfect number: " + perfectNumber);
}
}
Checkstyle warnings:
java -jar checkstyle-8.0-all.jar -c checkstyle-checks.xml Example.java
[ERROR] Example.java:4:29: Parameter arguments should be final. [FinalParameters]
[ERROR] Example.java:7:13: Variable 'perfectNumber' should be declared final. [FinalLocalVariable]
Using JavaParser, these two warnings could be fixed automatically like this (the code tries to demonstrate the idea; some parts have been ignored for now):
// AutomaticCheckstyleFix.java:
package q45326752;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.*;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.stmt.*;
import java.io.File;
import java.io.FileNotFoundException;
public class AutomaticCheckstyleFix {
private MethodDeclaration bestMatchMethod;
private int bestMatchMethodLineNumber;
private Statement statementByLineNumber;
public static void main(final String[] arguments) {
final String filePath = "q45326752\\input\\Example.java";
try {
new AutomaticCheckstyleFix().fixSimpleCheckstyleIssues(new File(filePath));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
private void fixSimpleCheckstyleIssues(File file) throws FileNotFoundException {
CompilationUnit javaClass = JavaParser.parse(file);
System.out.println("Original Java class:\n\n" + javaClass);
System.out.println();
System.out.println();
// Example.java:4:29: Parameter arguments should be final. [FinalParameters]
MethodDeclaration methodIssue1 = getMethodByLineNumber(javaClass, 4);
if (methodIssue1 != null) {
methodIssue1.getParameterByName("arguments")
.ifPresent(parameter -> parameter.setModifier(Modifier.FINAL, true));
}
// Example.java:7:13: Variable 'perfectNumber' should be declared final.
// [FinalLocalVariable]
Statement statementIssue2 = getStatementByLineNumber(javaClass, 7);
if (statementIssue2 instanceof ExpressionStmt) {
Expression expression = ((ExpressionStmt) statementIssue2).getExpression();
if (expression instanceof VariableDeclarationExpr) {
((VariableDeclarationExpr) expression).addModifier(Modifier.FINAL);
}
}
System.out.println("Modified Java class:\n\n" + javaClass);
}
private MethodDeclaration getMethodByLineNumber(CompilationUnit javaClass,
int issueLineNumber) {
bestMatchMethod = null;
javaClass.getTypes().forEach(type -> type.getMembers().stream()
.filter(declaration -> declaration instanceof MethodDeclaration)
.forEach(method -> {
if (method.getTokenRange().isPresent()) {
int methodLineNumber = method.getTokenRange().get()
.getBegin().getRange().begin.line;
if (bestMatchMethod == null
|| (methodLineNumber < issueLineNumber
&& methodLineNumber > bestMatchMethodLineNumber)) {
bestMatchMethod = (MethodDeclaration) method;
bestMatchMethodLineNumber = methodLineNumber;
}
}
})
);
return bestMatchMethod;
}
private Statement getStatementByLineNumber(CompilationUnit javaClass,
int issueLineNumber) {
statementByLineNumber = null;
MethodDeclaration method = getMethodByLineNumber(javaClass, issueLineNumber);
if (method != null) {
method.getBody().ifPresent(blockStmt
-> blockStmt.getStatements().forEach(statement
-> statement.getTokenRange().ifPresent(tokenRange -> {
if (tokenRange.getBegin().getRange().begin.line == issueLineNumber) {
statementByLineNumber = statement;
}
})));
}
return statementByLineNumber;
}
}
Another approach could be to create new Checkstyle plugins based on the ones you are trying to create an automatic fix for. Perhaps you have enough information available to not only give a warning but to also generate a modified version with these issues fixed.
Personally I would hesitate to have issues fixed automatically upon commit. When there are many simple fixes to be made, automation is welcome, but I would like to check these changes before committing them. Running a tool like this and checking the changes could be a very fast way to fix a lot of simple issues.
Some checks that I think could be fixed automatically:
adding static
fixing inline conditionals
FinalParameters and FinalLocalVariable: adding final
ModifierOrder: reordering modifiers (example: final static private)
NeedBraces: adding braces
I want to be able to capture a continuation and resume it several times, such that each such invocation would be independent of the others.
For example, in the following code, I'd want the 2 calls to context.resumeContinuation in the run method to result in the output: 1 1, rather than the current output of 1 2.
As far as I understand, the reason for the resulting output is that I always use the same scope object, which is being modified by the first continuation before being passed to the second one. So it seems that I should resume each continuation with a copy of the original scope, but type Scriptable has no clone method (or anything equivalent), and copying it using serialization/deserialization doesn't help either.
P.S. I am using Rhino version 1.7R5.
Example.java:
import org.mozilla.javascript.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Example {
public void run() throws IOException {
Context context = Context.enter();
context.setOptimizationLevel(-2); // Use interpreter mode.
Scriptable scope = context.initStandardObjects();
scope.put("javaProxy", scope, Context.javaToJS(this, scope));
Object capturedContinuation = null;
try {
String scriptSource =
new String(Files.readAllBytes(Paths.get("example.js")));
String scriptName = "example";
int startLine = 1;
Object securityDomain = null;
Script script =
context.compileString(scriptSource, scriptName, startLine, securityDomain);
context.executeScriptWithContinuations(script, scope);
} catch (ContinuationPending continuationPending) {
capturedContinuation = continuationPending.getContinuation();
}
Object result = "";
context.resumeContinuation(capturedContinuation, scope, result);
context.resumeContinuation(capturedContinuation, scope, result);
Context.exit();
}
public void captureContinuation() {
Context context = Context.enter();
ContinuationPending continuationPending =
context.captureContinuation();
Context.exit();
throw continuationPending;
}
public void print(int i) {
System.out.print(i + " ");
}
public static void main(String[] args) throws IOException {
new Example().run();
}
}
example.js:
var i = 1;
javaProxy.captureContinuation();
javaProxy.print(i);
i = i + 1;
So I came up with a working solution:
Instead of copying the scope object, I should've copied the capturedContinuation object, so the 2 calls to resumeContinuation would be:
context.resumeContinuation(deepCopy(capturedContinuation), scope, result);
context.resumeContinuation(deepCopy(capturedContinuation), scope, result);
This question offers possible imeplementations of the deepCopy method.
A word of caution, though: instances of Rhino's NativeContinuation type (which is the dynamic type of capturedContinuation in the code above) seem to be quite large (~15KB and up when serialized to a byte array), so time/memory consumption implications should be considered when deep copying them in an application.
This is just a guess but I think this might work (Rhino 1.7.6):
NativeContinuation capturedContinuation = ...
final Object stackFrame = capturedContinuation.getImplementation();
// Invoke once...
context.resumeContinuation(capturedContinuation, scope, result);
// Put stack back where it was
capturedContinuation.initImplementation(stackFrame);
// Invoke twice...
context.resumeContinuation(capturedContinuation, scope, result);
Is there a way to give the java compiler some kind of variable that is accessible to the running java code?
In C/C++ I can give the compile -DKEY=VALUE and that would cause the preprocessor to have a #define for KEY equals to VALUE. I can then check this value in compile time to effect what code is being compiled.
I found java's -D, but that puts values give the the java command line in System.getProperty(). I want an argument give in compile time, not invocation time.
javac has the
-Akey[=value]
commandline option to pass information to annotation processors.
With java annotations it is possible to generate additional code on the fly, which can be configured on command line. It allow to produce more source code, configuration files, xml files, ... The main limitation is that you are allowed only to (re)generate new source files, you cannot modify existing ones.
Below is a short tutorial on how to allow from javac command specify parameters which will be visible in Java code. How usefull is that? Ie. you could specify a boolean option which would disable some parts of code, I am preety sure this parts of code could be removed using tools like proguard - or even optimized out by javac. Other uses is to specify new version number. Those use cases are mostly what c++ marcros are used for.
So, you need :
a dummy annotation class which will allow processor to run. It should be specified only once in your application.
a processor class which will run for above dummy annotation, and generate options class. It will also read options from javac command line.
a dummy Main class for testing purposes.
You will have to also compile your processor file before compiling Main class. This of course is done only when processor class is modified. All the three files are at the bottom. Now the compilation looks as follows (I am on windows):
Compile processor:
javac .\com\example\ConfigWritterAnnotationProcessor.java
Then Main.java with additional parameters to processor:
javac -processor com.example.ConfigWritterAnnotationProcessor -AtextToPrint="Hello World!" -AenablePrint=true ./com/example/Main.java
And thats all, now you may run Main.class and it will use Options class generated during compilation with above parameters set. It will look as follows:
package com.example;
public class Options {
public static final String textToPrint = "Hello World!";
public static final boolean enablePrint = true;
}
ProcessorStarterAnnotation.java
package com.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target({ElementType.TYPE})
public #interface ProcessorStarterAnnotation {
}
Main.java
package com.example;
#ProcessorStarterAnnotation
public class Main {
public static void main(String[] args) {
if ( com.example.Options.enablePrint ) {
System.out.println(com.example.Options.textToPrint);
}
else {
System.out.println("Print disabled");
}
}
}
ConfigWritterAnnotationProcessor.java
package com.example;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Map;
import java.util.Set;
#SupportedAnnotationTypes("com.example.ProcessorStarterAnnotation")
#SupportedSourceVersion(SourceVersion.RELEASE_6)
#SupportedOptions({"textToPrint", "enablePrint"})
public class ConfigWritterAnnotationProcessor extends AbstractProcessor {
private Map<String,String> options;
#Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
options = processingEnv.getOptions();
}
#Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment currentRound) {
if (!currentRound.processingOver()) {
// This for-s are because processor is also run on newly created Options class.
for (TypeElement te : annotations) {
for (Element e : currentRound.getElementsAnnotatedWith(te)) {
try {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Creating com.example.Options");
JavaFileObject javaFile = processingEnv.getFiler().createSourceFile("com.example.Options");
Writer w = javaFile.openWriter();
try {
PrintWriter pw = new PrintWriter(w);
pw.println("package com.example;");
pw.println("public class Options {");
pw.println(" public static final String textToPrint = \"" + options.get("textToPrint") + "\";");
pw.println(" public static final boolean enablePrint = " + options.get("enablePrint") + ";");
pw.println("}");
pw.flush();
} finally {
w.close();
}
} catch (IOException x) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
x.toString());
}
}
}
}
return false;
}
}
There is nothing like this in Java. Compile time constants must be declared in source code, and as far as I know, there is no pre-processor.
BTW, you could use flags given to java command line (-D args) to initialize java constants at runtime, that would mimic what you are looking for.
Ex:
class Foo {
private static final String BAR;
static {
String foobar = System.getProperty("foo.bar");
if(foobar != null && foobar.length()>0) {
BAR = foobar;
} else {
BAR = "somedefaultvalue";
}
}
}
Invoke with java Xxx -Dfoo.bar=foobar
As there is no notion of preprocessing in Java, a solution is to design your own.
One may think of using a standard C preprocessor or a custom-made one and compile the preprocessed output, but this has the disadvantage of duplicating the files, so that the project will become more complex, and the support from the development environment will degrade (like ability to jump to a syntax error).
My suggestion is to use annotations via comments that will guide a custom preprocessor and let it do substitutions before compiling.
For example,
public static void main(String[] args) {
int nDisks = 3;
doTowers(nDisks, 'A', 'B', 'C');
}
would become
public static void main(String[] args) {
int nDisks = /*#NDISKS*/ 3 /**/;
doTowers(nDisks, 'A', 'B', 'C');
}
Then your preprocessor would have a definition file such as
NDISKS 5
turning the code in
public static void main(String[] args) {
int nDisks = /*#NDISKS*/ 5 /**/;
doTowers(nDisks, 'A', 'B', 'C');
}
Similarly, you can emulate conditional code compilation with
doTowers(topN - 1, from, to, inter);
/*!PRINT*/
System.out.println("Disk "
+ topN + " from " + from + " to " + to);
/**/
doTowers(topN - 1, inter, from, to);
which could be turned by the preprocessor (with a definition like PRINT OFF) into
doTowers(topN - 1, from, to, inter);
/*!PRINT
System.out.println("Disk "
+ topN + " from " + from + " to " + to);
*/
doTowers(topN - 1, inter, from, to);
You can use alternative syntaxes, but the main ideas are
that the annotated code remains compilable,
that the preprocessor substitutions are reversible.
It would be against the design of the language to have it that way. What -DKEY=VALUE does is that it actually replaces KEY with VALUE in the source during preprocessor in C/C++.
Java does not have a preprocessor so that's mechanism is not available. If you want something "equivalent" you have to question what you mean by that. By not preprocessing the source it wouldn't be really equivalent.
If you on the other hand would like it to mean to set the value of the symbol KEY to the value VALUE you'd run into the problem that you would need to declare the symbol KEY anyway to determine it's type. In this case it would only be yet another constant/variable with the constraints that implies.
This means that even with such a feature it would not actually alter the generated code and you would hardly be better of than defining the value att launch time. That's why supplying the parameter via java would be the way to go.
I need something similar to String.format(...) method, but with lazy evaluation.
This lazyFormat method should return some object whose toString() method would then evaluate the format pattern.
I suspect that somebody has already done this. Is this available in any libararies?
I want to replace this (logger is log4j instance):
if(logger.isDebugEnabled() ) {
logger.debug(String.format("some texts %s with patterns %s", object1, object2));
}
with this:
logger.debug(lazyFormat("some texts %s with patterns %s", object1, object2));
I need lazyFormat to format string only if debug logging is enabled.
if you are looking for a "simple" solution:
public class LazyFormat {
public static void main(String[] args) {
Object o = lazyFormat("some texts %s with patterns %s", "looong string", "another loooong string");
System.out.println(o);
}
private static Object lazyFormat(final String s, final Object... o) {
return new Object() {
#Override
public String toString() {
return String.format(s,o);
}
};
}
}
outputs:
some texts looong string with
patterns another loooong string
you can of course add any isDebugEnabled() statement inside lazyFormat if you will.
It can be done by using parameter substitution in newest log4j 2.X version http://logging.apache.org/log4j/2.x/log4j-users-guide.pdf:
4.1.1.2 Parameter Substitution
Frequently the purpose of logging is to provide information about what is happening in the system, which
requires including information about the objects being manipulated. In
Log4j 1.x this could be accomplished by doing:
if (logger.isDebugEnabled()) {
logger.debug("Logging in user " + user.getName() + " with id " + user.getId());
}
Doing this repeatedly has the effect of making the
code feel like it is more about logging than the actual task at hand.
In addition, it results in the logging level being checked twice; once
on the call to isDebugEnabled and once on the debug method. A better
alternative would be:
logger.debug("Logging in user {} with id {}", user.getName(), user.getId());
With the code above the logging level
will only be checked once and the String construction will only occur
when debug logging is enabled.
if you are looking for lazy concatenation for the sake of efficient logging, take a look at Slf4J
this allows you to write:
LOGGER.debug("this is my long string {}", fatObject);
the string concatenation will only take place if the debug level is set.
IMPORTANT NOTE: It is strongly recommended all logging code be moved to use SLF4J (especially log4j 1.x). It protects you from being stuck with any sort of idiosyncratic issues (i.e. bugs) with specific logging implementations. Not only does it have "fixes" for well know backend implementation issues, it also works with newer faster implementations which have emerged over the years.
In direct response to your question, here what it would look like using SLF4J:
LOGGER.debug("some texts {} with patterns {}", object1, object2);
The most important bit of what you have provided is the fact you are passing two Object instances. The object1.toString() and the object2.toString() methods are not immediately evaluated. More importantly, the toString() methods are only evaluated if the data they return is actually going to be used; i.e. the real meaning of lazy evaluation.
I tried to think of a more general pattern I could use which didn't require my having to override toString() in tons of classes (and there are classes where I don't have access to do the override). I came up with a simple drop-in-place solution. Again, using SLF4J, I compose the string only if/when logging for the level is enabled. Here's my code:
class SimpleSfl4jLazyStringEvaluation {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleSfl4jLazyStringEvaluation.class);
...
public void someCodeSomewhereInTheClass() {
//all the code between here
LOGGER.debug(
"{}"
, new Object() {
#Override
public String toString() {
return "someExpensiveInternalState=" + getSomeExpensiveInternalState();
}
}
//and here can be turned into a one liner
);
}
private String getSomeExpensiveInternalState() {
//do expensive string generation/concatenation here
}
}
And to simplify into the one-liner, you can shorten the LOGGER line in someCodeSomewhereInTheClass() to be:
LOGGER.debug("{}", new Object(){#Override public String toString(){return "someExpensiveInternalState=" + getSomeExpensiveInternalState();}});
I have now refactored all my logging code to follow this simple model. It has tidied things up considerably. And now when I see any logging code which does not use this, I refactor the logging code to use this new pattern even if it is needed yet. That way, if/when a change is made later to need to add some "expensive" operation, the infrastructure boilerplate is already there simplifying the task to just adding the operation.
Building upon Andreas' answer, I can think of a couple of approaches to the issue of only performing the formatting if the Logger.isDebugEnabled returns true:
Option 1: Pass in a "do formatting" flag
One option is to have a method argument that tells whether or not to actually perform the formatting. A use case could be:
System.out.println(lazyFormat(true, "Hello, %s.", "Bob"));
System.out.println(lazyFormat(false, "Hello, %s.", "Dave"));
Where the output would be:
Hello, Bob.
null
The code for lazyFormat is:
private String lazyFormat(boolean format, final String s, final Object... o) {
if (format) {
return String.format(s, o);
}
else {
return null;
}
}
In this case, the String.format is only executed when the format flag is set to true, and if it is set to false it will return a null. This would stop the formatting of the logging message to occur and will just send some "dummy" info.
So a use case with the logger could be:
logger.debug(lazyFormat(logger.isDebugEnabled(), "Message: %s", someValue));
This method doesn't exactly fit the formatting that is asked for in the question.
Option 2: Check the Logger
Another approach is to ask the logger directly if it isDebugEnabled:
private static String lazyFormat(final String s, final Object... o) {
if (logger.isDebugEnabled()) {
return String.format(s, o);
}
else {
return null;
}
}
In this approach, it is expected that logger will be visible in the lazyFormat method. And the benefit of this approach is that the caller will not need to be checking the isDebugEnabled method when lazyFormat is called, so the typical use can be:
logger.debug(lazyFormat("Debug message is %s", someMessage));
You could wrap the Log4J logger instance inside your own Java5-compatible/String.format compatible class. Something like:
public class Log4jWrapper {
private final Logger inner;
private Log4jWrapper(Class<?> clazz) {
inner = Logger.getLogger(clazz);
}
public static Log4jWrapper getLogger(Class<?> clazz) {
return new Log4jWrapper(clazz);
}
public void trace(String format, Object... args) {
if(inner.isTraceEnabled()) {
inner.trace(String.format(format, args));
}
}
public void debug(String format, Object... args) {
if(inner.isDebugEnabled()) {
inner.debug(String.format(format, args));
}
}
public void warn(String format, Object... args) {
inner.warn(String.format(format, args));
}
public void error(String format, Object... args) {
inner.error(String.format(format, args));
}
public void fatal(String format, Object... args) {
inner.fatal(String.format(format, args));
}
}
To use the wrapper, change your logger field declaration to:
private final static Log4jWrapper logger = Log4jWrapper.getLogger(ClassUsingLogging.class);
The wrapper class would need a few extra methods, for example it does not currently handle of logging exceptions (ie logger.debug(message, exception)), but this shouldn't be hard to add.
Using the class would be almost identical to log4j, except strings are formatted:
logger.debug("User {0} is not authorized to access function {1}", user, accessFunction)
Introduced in Log4j 1.2.16 are two classes that will do this for you.
org.apache.log4j.LogMF which uses a java.text.MessageFormat for format you messages and org.apache.log4j.LogSF which uses the "SLF4J pattern syntax" and is said to be faster.
Here are examples:
LogSF.debug(log, "Processing request {}", req);
and
LogMF.debug(logger, "The {0} jumped over the moon {1} times", "cow", 5);
If you like the String.format Syntax better than the {0} Syntax and can use Java 8 / JDK 8 you can use lambdas / Suppliers:
logger.log(Level.FINER, () -> String.format("SomeOperation %s took %04dms to complete", name, duration));
()->... acts as a Supplier here and will be evaluated lazily.
Or you could write it as
debug(logger, "some texts %s with patterns %s", object1, object2);
with
public static void debug(Logger logger, String format, Object... args) {
if(logger.isDebugEnabled())
logger.debug(String.format("some texts %s with patterns %s", args));
}
You could defined a wrapper in order to call the String.format() only if needed.
See this question for a detailed code example.
The same question has also a variadic function example, as suggested in Andreas's answer.