Taking on Custom Ant Logging
This article is the third and final article in my series on Ant customizations. My first article, Introduction to Custom Ant Tasks, explained the basic mechanics of writing a simple custom task in Java, packaging the Java class file and a Java properties file into a JAR file, and using this in your own Ant script. My second article, More on Custom Ant Tasks, discussed the details of custom "types" as well as the mechanics and issues associated with nesting custom elements and standard Ant elements together.
In this article, I will show you how to take control of Ant's default logging mechanism and write your own log message handlers. Specifically, I will discuss:
- Ant's Built-in Logging Facilities
- Loggers versus Listeners
- A Custom Syslog Listener
- A Custom XML Logger
- Warnings on Sub-builds and Loggers
Of course, I will also try to add some insight about how Ant works with these concepts based on my own "lessons learned" and my own forays into the Ant API documentation.
I am assuming you have a moderate level of experience with Ant on your platform-of-choice. (I use Windows 2000 during the day and Mac OS X when I'm at home.) Also, my code examples use some of the newer Java syntax bits such as enhanced-for and generics.
Ant's Built-in Logging Facilities
Most people use Ant content with the output that results from the default logger... that is, log messages are plain text and look similar to the following:
Buildfile: build.xml demo: Hello, World! BUILD SUCCESSFUL Total time: 0 seconds
Note: For this, I am using the Ant script from my first "hello world" custom task example. Refer to my first article in this series.
Following a banner which includes the Ant script file being executed, target names are shown on their own line with a colon following them, and output from specific tasks are shown on their own lines. This is usually enough for simple projects, but the output from different tasks is left more to the whims of the task writer, so there is not a lot of structural consistency. Also you might wonder whether that "Hello, World!" message is the result of a simple echo tag, or something else? (Where does one tag stop and the next tag start?)
Ant does, however, provide a few built-in logging options, and you can control exactly which logging option you want to use on the command line via the "-logger" command line option. When you use this command line option, follow it with the fully resolved name of some Java class that you want to use to handle the logging output. The built-in options include org.apache.tools.ant.DefaultLogger, which we have just seen, and org.apache.tools.ant.XmlLogger which looks like the following:
<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="log.xsl"?> <build time="0 seconds"> <task location="/Article1/ex1_files/build.xml:3: " name="taskdef" time="0 seconds"></task> <target name="demo" time="0 seconds"> <task location="/Article1/ex1_files/build.xml:6: " name="helloworld" time="0 seconds"> <message priority="info"><![CDATA[Hello, World!]]></message> </task> </target> </build>
Larger build projects can definitely benefit from some form of an XML-based log output, especially if any downstream processing is needed. However, my personal opinion about Ant's built-in XML logger is that it really isn't quite as useful as I would like: when something goes wrong, the Ant-level error messages are placed in elements that are siblings to, not children of, the task elements that caused them, and the only way to relate which error belongs to which task is to read into a CDATA section. This alone makes automated log file analysis more cumbersome that it needs to be. You also only get one stacktrace element for the entire build, even if you are running Ant in "keep going" mode and several problems result. (Specifically, you get a stacktrace element for the first problem.)
This isn't to say you can't get what you want, just that the default XML-based logger currently included with Ant might not suit your needs. Fortunately, taking control of the logging facility to generate log messages in a format you require is rather easy. In fact, the need to perform hands-off, post-build analysis and decision making in a nightly build system eventually led me to my first experience with custom XML-based logging.
Loggers versus Listeners
In addition to a logger, Ant also has a log message handling category known as a listener. A logger basically is a listener. The first key difference is that a logger is given the ability to write to the log output device, which is either the console or some file specified with the "-logfile" command line argument. The second key difference results from the first: your Ant script can have multiple listeners but only one logger. The logger is that which Ant connects to the System.out and System.err streams. In fact, the logger neither knows nor cares about the exact nature or destination of these streams. Given this, I shall reiterate the Ant manual's warning to not directly write to these streams -- your logger might cause an infinite loop.
Developer's Diversion: Ant's Project class has a method called getBuildListeners which returns a Vector. The logger in use seems to be the first element in this vector, and additional listeners, if any, comprise the remainder of the vector. However, I am not one of the Ant developer's, so this is not an authoritative statement.
In addition to the two built-in loggers mentioned above, you can use the "-listener" command line argument to connect your Ant script to these built-in listener classes: org.apache.tools.ant.listener.CommonsLoggingListener, and org.apache.tools.ant.listener.Log4Listener. These listeners connect to the Apache "commons logging" facility and the Log4j logging facility, respectively. I will not be discussing how to configure those logging facilities as they are separate projects, each likely meriting its own article series. I mainly mention the existence of these built-in listeners more for completeness, since the standard Ant manual doesn't cover them too obviously outside of the API documentation itself. Also apparently not clear to a few people I've encountered on forums: if you want multiple listeners, add each with their own "-listener" argument.
A Custom Syslog Listener
Ant Listener Overview
Implementing a custom listener is a matter of creating a Java class that implements the following methods from the org.apache.tools.ant.BuildListener interface:
buildStarted(BuildEvent e) buildFinished(BuildEvent e) targetStarted(BuildEvent e) targetFinished(BuildEvent e) taskStarted(BuildEvent e) taskFinished(BuildEvent e) messageLogged(BuildEvent e)
Of course, your implementation for some of these methods can simply "do nothing" if you are only concerned with capturing certain log information. Regardless, with an implementation class available somewhere in the classpath, just run your Ant script and use the "-listener" argument to specify a listener which should receive logging messages.
Note: A custom listener is being discussed. A custom logger will be discussed later. Your listener will function in addition to whichever logger is being used.
The BuildEvent argument to all of the interface methods is another Ant class. This class allows you to get a reference to the Project, Target, and Task the script is running in, the actual message text itself, the Ant-level priority of the message (verbose, warning, etc.) as emitted by the task code, and a Throwable object. By convention, this Throwable will be null under normal circumstances. When it is non-null, this is generally due to a BuildException having been emitted by task code. You definitely want to watch for a non-null Throwable and send information about it somewhere as an error-level message, but you should really only expect to receive non-null throwables on the xxxFinished interface methods.
To provide some value to the discussion, assume we want to implement a custom listener that sends messages to a remote "syslog" server via UDP packets.