Does anyone know if there is a way to generate different code in the catch block automatically depending on the exception?
The Eclipse function 'Surround with try/catch' generates a try/catch block which just includes dumping a stack trace.
I'm doing a bunch of similar things in the code and so most of my exceptions will boil down to probably three or so different types. I'd like to have different catch block code for each one and have eclipse auto format based on the exception.
For example:
if my code generates a RemoteConnectionException I'd like to display a dialog to the user to reconnect.
If it generates a RemoteContentException I'd like to log it.
(I made these up.)
Thanks in advance
UPDATE:
I've been poking around and have two potential solutions.
1) I've found something called the fast code plugin which might do what I'm looking for.
http://fast-code.sourceforge.net/index.htm
2) For specifically handling exceptions I'll probably just write a generic exception handler and modify the catch block code to pass the exception to that instead of printing the stack trace. Then the java code will determine which action to take based on exception type.
Templating has it's limits. However your problem can be solved very elegantly with Aspect. ( http://www.eclipse.org/aspectj/ ) Just create a new annotation for every type of "template-case" you need and use an around advice.
Ps: don't use printStackTrace() to syserr/sysout. There are so many production grade, lightweight logging frameworks.... pleeeaseee... don't abuse poor little System.out/err :)
EDIT:
Some example for a logging / benchmarking advice. (note: I'm using spring AOP for aspects, and lombok for easy access to the logging framework. The getCurrentUser() code is not really relevant here, it's just for getting the current user from Spring Security)
package com.XXXXXXXX.aspects;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
#Component
#Aspect
#Slf4j
public class LoggerAspect {
private final static String DOMAIN = "XXXXXXXX";
private static String getCurrentUser() {
String username = "Unknown";
try {
Object principal = SecurityContextHolder.getContext().
getAuthentication().
getPrincipal();
if (principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername();
} else {
username = principal.toString();
}
} catch (Exception e) {
}
return username;
}
#Pointcut("within(com.XXXXXXXX.services..*)")
public void inServiceLayer() {
}
#Pointcut("execution(* getMatcherInfo(..)) || execution(* resetCounter(..))")
public void notToAdvise() {
}
#Around("com.XXXXXXXX.aspects.LoggerAspect.inServiceLayer() && !com.XXXXXXXX.aspects.LoggerAspect.notToAdvise()")
public Object doLogging(ProceedingJoinPoint pjp)
throws Throwable {
long start = System.nanoTime();
StringBuilder sb = new StringBuilder(DOMAIN);
sb.append('/').
append(getCurrentUser()).
append(" accessing ").
append(pjp.getSignature().
getDeclaringTypeName()).
append('.').
append(pjp.getSignature().
getName());
log.trace("START: " + sb.toString());
Object retVal = pjp.proceed(pjp.getArgs());
long duration = System.nanoTime() - start;
log.trace("STOP: " + duration / 1000000 + " msec. " + sb.toString());
return retVal;
}
}
I'm not sure if there is such an option available in eclipse. I've been using the surround with try/catch option for quite sometime now and it always dumps the default e.printStackTrace() line in the catch block for the Exception e.
Related
ORIGINAL QUESTION:
I have a method annotated with two aspects:
#Aspect1Annotation
#Aspect2Annotation
public SomeResult handle() {
// some code
return null;
}
I would like the first annotaion to execute and based on its results prevent execution of the second aspects logic.
How to do it with AspectJ? Please help.
EDIT:
I am developing an application which has multiple methods with these annotations:
#Monitored
#Secured(Permission.SOME_PERMISSION)
#Audited
public SomeResult handle() {
// some code
return null;
}
The MonitoringAspect logic must always run despite security permissions.
The permission check should be done after the MonitoringAspect and if access is denied, a runtime exception should be thrown.
AuditAspect logic should only be executed if access is allowed.
You want to use a combination of
#Around advice in your aspects and
#DeclarePrecedence for declaring aspect precedence.
This way, you can chain advice execution exactly the way you want to and dynamically decide whether each advice should
proceed to the next advice in the chain depending on its precedence (or to the target method, if there is no more advice in the chain), or
not to proceed but return a result calculated by the advice itself, or
to throw an exception instead of returning something.
Of course, you can combine these things, e.g. proceed but discard or modify the result or proceed, but depending on the result still throw an exception.
If you just want to decide to either throw an exception or just pass through the call and its result unchanged, depending on authorisation status, of course a simple #Before advice will also do and #Around is unnecessary.
Here is a simple example with 2 aspects, which you can easily adapt to your situation. Feel free to ask directly related follow-up questions in comments, if anything is unclear about the sample code.
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(" " + doSomething());
}
}
public static String doSomething() {
return "doing something";
}
}
import java.util.Random;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class FirstAspect {
private static final Random RANDOM = new Random();
#Around("execution(String doSomething())")
public Object myAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("FirstAspect");
switch (RANDOM.nextInt(3)) {
// Do not proceed to 2nd aspect, create own return value
case 0: return "1st aspect";
// Proceed to 2nd aspect, modify response
case 1: return joinPoint.proceed() + " - 1st aspect";
// Proceed to 2nd aspect, return response unchanged
default: return joinPoint.proceed();
}
}
}
package de.scrum_master.aspect;
import java.util.Random;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclarePrecedence;
#Aspect
#DeclarePrecedence("FirstAspect, SecondAspect")
public class SecondAspect {
private static final Random RANDOM = new Random();
#Around("execution(String doSomething())")
public Object myAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("SecondAspect");
switch (RANDOM.nextInt(3)) {
// Do not proceed to target method, create own return value
case 0: return "2nd aspect";
// Proceed to target method, but modify return value
case 1: return joinPoint.proceed() + " - 2nd aspect";
// Proceed to target method, return response unchanged
default: return joinPoint.proceed();
}
}
}
The console log might look like this:
FirstAspect
SecondAspect
doing something - 2nd aspect - 1st aspect
FirstAspect
1st aspect
FirstAspect
SecondAspect
2nd aspect - 1st aspect
FirstAspect
SecondAspect
doing something - 1st aspect
FirstAspect
1st aspect
FirstAspect
SecondAspect
doing something
FirstAspect
1st aspect
FirstAspect
SecondAspect
doing something - 1st aspect
FirstAspect
1st aspect
FirstAspect
SecondAspect
doing something - 2nd aspect
You can see how sometimes the result is passed through unchanged by both aspects, sometimes only one of the aspect or both aspects modify the result or how sometimes the second aspect is not even called, because the first one does not proceed. Instead of not proceeding, throwing an exception would also be possible.
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 am trying to use AspectJ in a standalone application but does not seem to work.
Here are the classes I created-
package oata.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class AspectJTest {
#Around("execution(* *..*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("around fired");
jp.proceed();
}
}
package oata;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(value = ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public #interface AspectTest {
}
package oata;
import oata.AspectTest;
public class TestAspect {
public void doItWithout(int i) {
double s = Math.acos(i);
}
#AspectTest
public void doItAnnotated(int i) {
double s = Math.acos(i);
}
public void doItAspect(int i) {
double s = Math.acos(i);
}
}
package oata;
import java.util.Date;
public class Test {
public Test() {
}
public static void main(String arg[]) {
// performance testing
// invoke method without aspect
long t1 = new Date().getTime();
for (int i = 0; i < 1; i++) {
new TestAspect().doItWithout(i);
}
System.out.println("Invoke without aspect:"
+ (new Date().getTime() - t1));
// invoke method with annotated aspect
t1 = new Date().getTime();
for (int i = 0; i < 1; i++) {
new TestAspect().doItAnnotated(i);
}
System.out.println("Invoke annotated aspect method:"
+ (new Date().getTime() - t1));
// invoke method with aspect but not annotated
t1 = new Date().getTime();
for (int i = 0; i < 1; i++) {
new TestAspect().doItAspect(i);
}
System.out.println("Invoke aspect method:"
+ (new Date().getTime() - t1));
}
}
Also under src/META_INF folder I have created aop.xml file
<aspectj>
<aspects>
<aspect name="oata.aspect.AspectJTest" />
</aspects>
<weaver>
<include within="oata.*" />
</weaver>
</aspectj>
Then from the command line when I try running the Test.java using the below command the System.out.println in the advice does not get printed-
\TestAspectJ\bin>java -javaagent:D:\Project\workspaces\RCS_3.2.1\TestAspectJ\src\aspectjweaver-1.6.10.jar oata.Test
Can anyone please let me know what is it that I am doing wrong.
Thanks
AA
Few things:
Is your META-INF/* folder definitely being copied to your bin folder where you are running the app from?
You are specifying an include of oata.*, that will only include direct classes in the oata package, if you want further sub packages (and I think you do) you need oata..*
Have you tried specifying weaver options="-verbose" - does that show you anything? If it shows you nothing the aop.xml file is not being found. If it does show you something it will tell you which aspects are being turned on. Perhaps then augment it with -debug to see more about what is going on.
I might include a !within(AspectJTest) complaint to your pointcut or you could end up self advising and failing with a stack overflow when it does start working.
Finally, I know you aren't using it now but if you intend to use that annotation for matching with AspectJ you will need to change it from SOURCE retention because AspectJ works at the byte code level and won't see if it has source retention.
Andy is right with everything he said. Because you seem to be a beginner in AspectJ as well as Java, I have refactored your sample code a bit in order to help you get started. Things I noticed along the way:
You use a very old AspectJ version 1.6.10. It is from 2010 and not even the latest 1.6 version (which would be 1.6.12). How about using the current AspectJ version 1.8.6?
I am a clean code guy and noticed that your class names are rather obfuscating what you want to demonstrate with the sample code. So I renamed them:
Test → Application
TestAspect → Helper
AspectTest → MyAnnotation
AspectJTest → MethodInterceptor
I also changed the Helper methods' return types so as to return something other than void in order to demonstrate the next issue.
Your #Around advice has a return type of void. This way it does not work if the pointcut hits a non-void method. I changed the return type to Object and the code to return the result of proceed() in order to show how this can be done in a more generic way.
Your #Around advice always logs the same message. I updated it to log the actual joinpoint info (before and after the proceed() call) so we can see what is happening on the console log.
As Andy said, obviously you plan to use the annotation in order to match annotated methods with a pointcut. Thus, I changed the retention scope to RUNTIME.
Your pointcut targets all method executions including Application.main and Helper.doItWithout. I changed the pointcut to only target methods either bearing #MyAnnotation or with a substring "Aspect" in their method name.
You seem to wish to profile method execution times and compare methods with aspects applied to methods not targeted by aspects. Instead of creating lots of Date instances and calling new Date().getTime() (returns milliseconds) you can just use System.nanoTime() (returns nanoseconds).
When profiling, you want to measure method execution time, not object creation time. Thus, I changed the code to just create one Helper instance which is then reused throughout the main method.
The Application class does not need an empty default constructor because it will be generated automatically by the JVM.
In order to get meaningful profiling results you should use a bigger number of repetitions (such as a million). I introduced a constant named LOOP_COUNT in order to simplify this for all three loops.
Attention! If you want to measure method execution times you should not print anything in your aspect because then you would also be measuring the time it takes to write something to the console. Thus, I have commented out the printing statements in the aspect. You can still activate them for smaller numbers of repetitions in order to see what is going on.
Refactored code:
package oata;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(value = ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAnnotation {}
package oata;
import oata.MyAnnotation;
public class Helper {
public double doItWithout(int i) {
return Math.acos(i);
}
#MyAnnotation
public double doItAnnotated(int i) {
return Math.acos(i);
}
public double doItAspect(int i) {
return Math.acos(i);
}
}
package oata;
public class Application {
private static final int LOOP_COUNT = 100000000;
public static void main(String arg[]) {
Helper helper = new Helper();
System.out.printf(
"Profiling statistics for %,d repetitions%n%n",
LOOP_COUNT
);
long startTime = System.nanoTime();
for (int i = 0; i < LOOP_COUNT; i++)
helper.doItWithout(i);
System.out.printf(
"Method not targeted by aspect:%n %,15d ns%n",
System.nanoTime() - startTime
);
startTime = System.nanoTime();
for (int i = 0; i < LOOP_COUNT; i++)
helper.doItAnnotated(i);
System.out.printf(
"Method targeted by aspect because it is annotated:%n %,15d ns%n",
System.nanoTime() - startTime
);
startTime = System.nanoTime();
for (int i = 0; i < LOOP_COUNT; i++)
helper.doItAspect(i);
System.out.printf(
"Method targeted by aspect because of its name:%n %,15d ns%n",
System.nanoTime() - startTime
);
}
}
package oata.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class MethodInterceptor {
#Around("execution(#oata.MyAnnotation * *(..)) || execution(* *Aspect*(..))")
public Object around(ProceedingJoinPoint jp) throws Throwable {
// System.out.println("BEFORE " + jp);
Object result = jp.proceed();
// System.out.println("AFTER " + jp);
return result;
}
}
Sample console log for 1 repetition with aspect log statements enabled:
Profiling statistics for 1 repetitions
Method not targeted by aspect:
153.893 ns
BEFORE execution(double oata.Helper.doItAnnotated(int))
AFTER execution(double oata.Helper.doItAnnotated(int))
Method targeted by aspect because it is annotated:
3.102.128 ns
BEFORE execution(double oata.Helper.doItAspect(int))
AFTER execution(double oata.Helper.doItAspect(int))
Method targeted by aspect because of its name:
55.295 ns
As you can see here, the results are not very conclusive with just one call per method.
Sample console log for 100,000,000 (a hundred million) repetitions with aspect log statements disabled:
Profiling statistics for 100.000.000 repetitions
Method not targeted by aspect:
843.407.034 ns
Method targeted by aspect because it is annotated:
1.219.571.173 ns
Method targeted by aspect because of its name:
1.175.436.574 ns
Now the result is more conclusive: The method not targeted by any aspect is executed more quickly than the next two methods which have about equal execution time of 1.2 seconds, which was to be expected because the pointcuts used can be determined statically during compilation time (for CTW) or weaving time (for LTW).
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.