September 19, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Taking on Custom Ant Logging

  • November 16, 2006
  • By Rob Lybarger
  • Send Email »
  • More Articles »

A New XML Logger

Near the outset of this article, I expressed mild dissatisfaction with the built-in XML logger. I now address my complaints by redesigning the format of the XML data itself. If a task has an exception, it will show up as a child of that task. I dispense with the timing information and I write elements as they happen rather than all-at-once upon completion. (It is better, in my opinion and experience, to have half a log than none at all.) To get on-the-fly elements -- and to simplify the mechanics for this article -- I am actually using direct file I/O rather than use a formal XML API. I also dispense with CDATA sections and instead directly write log messages into text nodes. These decisions, of course, suit my individual needs. Before undertaking a full-blown, XML-based custom Ant logger, you should decide what information you need and how you might want to process it before you decide how to structure the XML data itself. (To quote a professor from my collegiate days: "You can do anything you want, but whatever you do, know what you are doing.")

With this all being said, I am only showing snippets of the full logger implementation here. As always, refer to this articles accompanying zip file for the complete version. First, a few instance variables and the implementations for the BuildLogger interface methods:

OutputStreamWriter out;
PrintStream err;
int ouputLevel = Project.MSG_INFO;
public void setMessageOutputLevel(int level) {
  outputLevel = level;
}

public void setOutputPrintStream(PrintStream psOut) {
  out = new OutputStreamWriter(psOut,
                               Charset.forName("UTF-8"));
}

public void setErrorPrintStream(PrintStream psErr) {
  err = psErr;
}

public void setEmacsMode(boolean b) {
  /* ignored */
}

I have simply saved the message output level for use later in the messageLogger method. The error PrintStream reference is saved as-is, but the output PrintStream is instead connected to an OutputStreamWriter with UTF-8 encoding -- a common encoding in most XML files.

From here, I provide a convenience method to write to the output stream and catch wayward IO exceptions. All of the BuildListener interface methods will use this convenience method:

private void writeOutput(String s) {
  try {
    out.write(s);
    out.flush();
  }
  catch (IOException ioe) {
    err.println("Exception encountered: "+ioe);
    ioe.printStackTrace(err);
  }
}

Another convenience method will format a build exception to include the exception's top-level message and the complete stacktrace:

private void writeThrowable(Throwable t) {
  if (t!=null) {
    writeOutput("<exception>n");
    writeOutput("<message>"+t.toString()+"</message>n");
    writeOutput("<stacktrace>n");
    for(StackTraceElement e : t.getStackTrace()) {
      writeOutput("<element>"+e.toString()+"</element>n");
    }
    writeOutput("</stacktrace>n</exception>n");
  }
}

Finally, implementation for the BuildListener interface methods:

int exceptionCount = 0;
boolean inTask = false;

public void buildStarted(BuildEvent be) {
  writeOutput("<?xml version="1.0" charset="utf-8"?>n");
  writeOutput("<build>n");
}

public void buildFinished(BuildEvent be) {
  writeOutput("<buildsummary exceptionCount=""+
               exceptionCount+""/>n");
  writeOutput("</build>n");
}

public void targetStarted(BuildEvent be) {
  String name = be.getTarget().getName();
  writeOutput("<target name=""+name+"">n");
  Enumeration e = be.getTarget().getDependencies();
  if (! e.hasMoreElements()) {
    return;
  }
  writeOutput("<dependencies>n");
  while (e.hasMoreElements()) {
    Object o = e.nextElement();
    writeOutput("<dependency>"+o.toString()+"</dependency>n");
  }
  writeOutput("</dependencies>n");
}

public void targetFinished(BuildEvent be) {
  writeOutput("</target>n");
}

public void taskStarted(BuildEvent be) {
  String name = be.getTask().getTaskName();
  writeOutput("<task name=""+name+"">n");
  inTask = true;
}

public void taskFinished(BuildEvent be) {
  Throwable t = be.getException();
  if (t!=null) {
    writeThrowable(t);
    exceptionCount++;
  }
  writeOutput("</task>n");
  inTask = false;
}

public void messageLogged(BuildEvent be) {
  if (!inTask) { return; }
  int priority = be.getPriority();
  if (priority <= outputLevel) {
    String message = be.getMessage();
    writeOutput("<message priority=""+priority+"">"+
                 message+"</message>n");
  }
}

There you have it -- a complete, if minimalist, XML-formatted custom Ant logger. A few comments are worth discussing.

First, if a task throws a BuildException, then the taskFinished, targetFinished, and buildFinished will all receive BuildEvents that return a non-null Throwable for their getException method. I have decided to only catch possible exceptions at the task level where they originate.

Second, the Enumeration element returned in a Target's getDependencies method stores String objects, not Target objects. If you would rather have the actual Target object, you might experiment with the Project class's getTargets method, which returns a Hashtable. The Ant API docs, however, do not really document what any particular returned Collection is storing though, so let getClass().getName() be your guide. Also seemingly absent is a way to query the Project class for the actual Target the user is requesting to run.

Third, as I mentioned above, I am using the value stored in the outputLevel instance variable and comparing that against the priority level of the BuildEvent in the messageLogger method. If you run an Ant script that, for example, sets an Ant property, you will get additional output when running in debug mode detailing the action of the property task.

Admittedly, this is a fairly minimal starting point for a truly useful XML log file. I did, however, use just such a starting point as this to write a rather complex logger for my current employer. For example, certain tasks such as javac had more tailored output format (such as how many source files were marked for compilation and how many were actually compiled) while other tasks used a generic output format similar to the above. The result of these XML logs were then fed through some XSL transformations and Ant scripts to perform automated post-build diagnostics as well as to create developer-friendly HTML-formatted output files. Your own requirements for a custom logger will assuredly take you in different directions.





Page 3 of 4



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel