Java – Logging Exceptions in Multi-Tier Applications

exception handlingjavalogging

I'm building a multi-tier enterprise application using Spring. I have different layers: Controller, Business and Provider. Within the application I've built a custom error handling mini-framework that is based on a single RuntimeException which has an error code to discriminate different kind of errors.

Error codes are enums implementing this interface:

public interface ErrorCode {
    public int getNumber();
    public String getDeveloperMessage();
    public String getHelpURL();
    public boolean isSystemError();
}

So for example I have:

public enum SystemErrorCode implements ErrorCode{
    E_UNKNOWN_ERROR(10000, "Unkwown internal error", true),
    E_MISSING_ARGUMENT(10001, "Missing argument", true),
    E_INVALID_ARGUMENT(10002, "Invalid argument", true),
    E_CONTEXT_NO_TENANT(10003, "No tenant in context", true),
    E_CONTEXT_TENANT_CHANGE(10004, "Tenant change attempt", true),
    E_CONTEXT_ALREADY_INITIALIZED(10005, "Tenant already initialized", true),
    E_NOT_IMPLEMENTED(10005, "Feature not implemented", true),
    ...
}

The exception class itself look like this (getters, constructors, and utility methods are stripped out for brevity):

public class EngineRuntimeException extends RuntimeException {
    private static final long serialVersionUID = 1L;

    private final String incidentReportId;
    private final ErrorCode code;
    private final Map<String, Object> additionalInfo;

    ...

    public static EngineRuntimeException wrap(Throwable exception, ErrorCode errorCode) {
        if(exception instanceof EngineRuntimeException) {
            EngineRuntimeException se = (EngineRuntimeException)exception;
            if(errorCode != null && errorCode != se.getCode()) {
                return new EngineRuntimeException(exception.getMessage(), exception, errorCode);
            }
            return se;
        } else {
            return new EngineRuntimeException(exception.getMessage(), exception, errorCode);
        }
    }


    public static EngineRuntimeException wrap(Throwable exception) {
        return wrap(exception, SystemErrorCode.E_UNKNOWN_ERROR);
    }

Using the wrap utility method I can easily encapsulate exceptions in the different layers without loosing the original stack and without having it dirty by rethrowing or incapsulating exception more than once.

try {
  if(a == null) {
    throw new EngineRuntimeException(SystemErrorCode.E_UNKNOWN_ERROR, "blah, blah, blah");
  }

  // Some other code that can throw exceptions (checked or unchecked),
  // these exceptions will be wrapped inside EngineRuntimeException if they
  // are not already of that type.

} catch (Exception ex) {
    throw EngineRuntimeException.wrap(ex);
}    

To recap, each layer has a safety boundary try-catch that wraps Exceptions into EngineRuntimeException, just rethrowing what is already an EngineRuntimeException.

The question is: where should I log the exception? I want to log the exception only once so I was thinking about do the logging stuff inside the constructor of EngineRuntimeException class itself. Is that a bad idea? Is it better to do the logging stuff only in the catch blocks of the layer boundaries?

Also I don't want to simply log using a log4j but I want to use a service (@Service) injected by spring so that I can decide to do something more than just logging on file system. How can I do that since I can't make spring inject something in a class I have created with the "new" instruction.

Best Answer

If you don't mind having another library dependence, you could use AspectJ to log any exceptions that derive from EngineRuntimeException with the following declaration:

public before() throws EngineRuntimeException: mypointcut() {...}

The reason why this method is preferable is to decouple the logging from the actual exception itself.

You could of course also log directly in EngineRuntimeException and though you shouldn't run into any problems, if you need to create another exception that doesn't derive from EngineRuntimeException, that too would need to reuse the same log whereas with AspectJ, you could have a cleaner solution overall with all "logging" together in one place. You can generalize AspectJ also for other parts of your program in an unintrusive way.

Note that if you don't want AspectJ to impact performance, there are ways you can apply aspectJ to jars while being built in such a way to not require AspectJ in production at all. In this way you wouldn't need to use Spring at all.