Mittwoch, 9. Februar 2011

logging SVN revision numbers with stack trace

did you ever got a stack trace and lack the information regarding the version of the source code?
Using a special annotation referring the SVN revision number and a custom renderer for throwabl's in log4j may help. The annotation may look like:
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Revision {
  public String value();
}
The annotated class:

@Revision("$Revision: 42 $")
public class ExampleClass {
 
public static void main(String[] args) {
    Logger  logger = Logger.getLogger("xxx.example.ExampleClass");
logger.warn("Example exception: ",new Exception());
}
}
Note that the String "$Revision: 42 $" can be set by the SVN keyword substitution.
And the renderer that has to be configured for log4j:

  public static final class RevisionDecoratingThrowableRenderer implements ThrowableRenderer {
    public String[] doRender(final Throwable throwable) {
      StackTraceElement[] elements = throwable.getStackTrace();
      String[] lines = new String[elements.length + 1];
      lines[0] = throwable.toString();
      for (int i = 0; i < elements.length; i++) {
        lines[i + 1] = renderLine(elements[i]);
      }
      return lines;
    }

    private String renderLine(final StackTraceElement element) {
      StringBuilder b = new StringBuilder("\tat ");
      b.append(element);
      b.append(computeRevisionSuffix(element));
      return b.toString();
    }

    protected String computeRevisionSuffix(final StackTraceElement element) {
      String className = element.getClassName();
      Class<?> clazz;
      try {
        clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
      } catch (ClassNotFoundException e1) {
        try {
          clazz = Class.forName(className);
        } catch (ClassNotFoundException e2) {
          try {
            clazz = getClass().getClassLoader().loadClass(className);
          } catch (Throwable t) {
            return "";
          }
        }
      }
      Revision va = clazz.getAnnotation(Revision.class);
      if (va != null) {
        return '[' + (va.value().replaceAll("\\$", "")) + ']';
      }
      return "";
    }
  }
Certainly things can be optimized a bit, but the principle should get clear.