Thursday, February 12, 2009

A Spring Pattern for Error Logging

Looking for an effective error logging solution? I recently found this pattern from the Pro Spring 2.5 team and it has several very elegant features which include:
  • It is an AOP based solution so it will prohibit you from infecting your business logic with error logging details. This concept isn’t new. The next two features I thought were more interesting.
  • The log details are written to a database. In fact, the exception will only get written once. If an identical exception occurs the count of that occurrence will be incremented. This reduces the redundancy that we see in file based solutions.
  • For reporting purposes, the following exception details will be captured:
    • number of occurrences (nice feature)
    • exception name
    • class and method name
    • method arguments (nice feature)
    • stack trace
    • timestamp of last occurrence



An example report may look like:


There are two components necessary to complete this task. An aspect for identifying the level at which you want to capture exception logging. And secondly, a data access object to manage the persistence. Here's an example of the Spring Aspect that will intercept all Service method calls:

@Aspect
@Component
public class ErrorLoggingAroundAspect {

@Autowired
private ErrorLogDao errorLogDao;

@Around("execution(* com.mycompany.myproject.service.*Service*.*(..))")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
pjp.getSignature().getName();
try {
return pjp.proceed();
} catch (Throwable t) {
insertThrowable(t, pjp);
throw t;
}
}


private void insertThrowable(Throwable t, ProceedingJoinPoint pjp) throws IOException {
StringWriter stackTrace = new StringWriter();
t.printStackTrace(new PrintWriter(stackTrace));
stackTrace.close();
StringBuilder methodArgs = new StringBuilder();
for (Object argument : pjp.getArgs()) {
if (methodArgs.length() > 0) methodArgs.append(",");
methodArgs.append(argument);
}
methodArgs.insert(0, "(");
methodArgs.insert(0, pjp.getSignature().getName());
ErrorLog errorLog = new ErrorLog(
t.getClass().toString(), stackTrace.toString(), methodArgs.toString());
this.errorLogDao.insert(errorLog);
}
}


public class ErrorLog {
private String exceptionName;
private String stackTrace;
private String method;
private int count = 1;

public ErrorLog(String exceptionName, String stackTrace, String method) {
this.exceptionName = exceptionName;
this.stackTrace = stackTrace;
this.method = method;
}
}



Here's the example DAO that will persist the exception to memory. A real solution would persist to a database.

/** Persists to memory.*/
@Repository
public class ErrorLogDaoImpl implements ErrorLogDao {

private List<ErrorLog> errors = Collections.synchronizedList(new LinkedList<ErrorLog>());

public void insert(ErrorLog errorLog) {
for (ErrorLog log : this.errors) {
if (log.getMethod().equals(errorLog.getMethod()) &&
log.getStackTrace().equals(errorLog.getStackTrace())) {
log.setCount(log.getCount() + 1);
return;
}
}
this.errors.add(errorLog);
}

public List<ErrorLog> getAll() {
return this.errors;
}
}


In addition to the code above, you will also need to include <aop:aspectj-autoproxy/> within your Spring configuration file. This tag scans for @Aspect's and turns them into proxies.

Again, I like this solution because it keeps the business-tier clean of logging details, there are no redundant error log messages like we see with file-based solutions, and the persisted data allows for much simpler error reporting. In addition to error logging, I also see this pattern being a good solution for business auditing too.

No comments :

Post a Comment