views:

666

answers:

6

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.

+6  A: 

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.

Andreas Petersson
the problem of lazy evaluation was exactly why slf4j's {} syntax was introduced
Andreas Petersson
Otherwise good, but I am of stuck with log4j. I have kind of complex logging setup, so I can't just drop slf4j in.
Juha Syrjälä
SLF4J has a binding for log4j. So whatever your "complex logging setup" is, SLF4J will handle it nicely. You could continue to use log4j directly and SLF4J only when lazy string evaluation is necessary.
Ceki
+12  A: 

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.

Andreas Petersson
A: 

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.

VonC
+2  A: 

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));
coobird
Shouldn't the last example be logger.debug(lazyFormat("Debug message is %s", someMessage)); ?
Juha Syrjälä
@Juha S.: Yes, you're correct. I noticed the error a little bit after I posted the answer, so it has been fixed.
coobird
+1  A: 

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)
Andrew Newdigate
A: 

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));
}
Peter Lawrey