source: other-projects/trunk/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/ant/XmlLogger.java@ 14627

Last change on this file since 14627 was 14627, checked in by oranfry, 17 years ago

initial import of the gs3-release-maker

File size: 17.4 KB
Line 
1/*
2 * Copyright 2000-2004 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18package org.apache.tools.ant;
19
20import java.io.FileOutputStream;
21import java.io.IOException;
22import java.io.OutputStream;
23import java.io.OutputStreamWriter;
24import java.io.PrintStream;
25import java.io.Writer;
26import java.util.Hashtable;
27import java.util.Stack;
28import java.util.Enumeration;
29import javax.xml.parsers.DocumentBuilder;
30import javax.xml.parsers.DocumentBuilderFactory;
31import org.apache.tools.ant.util.DOMElementWriter;
32import org.apache.tools.ant.util.StringUtils;
33import org.w3c.dom.Document;
34import org.w3c.dom.Element;
35import org.w3c.dom.Text;
36
37/**
38 * Generates a file in the current directory with
39 * an XML description of what happened during a build.
40 * The default filename is "log.xml", but this can be overridden
41 * with the property <code>XmlLogger.file</code>.
42 *
43 * This implementation assumes in its sanity checking that only one
44 * thread runs a particular target/task at a time. This is enforced
45 * by the way that parallel builds and antcalls are done - and
46 * indeed all but the simplest of tasks could run into problems
47 * if executed in parallel.
48 *
49 * @see Project#addBuildListener(BuildListener)
50 */
51public class XmlLogger implements BuildLogger {
52
53 private int msgOutputLevel = Project.MSG_DEBUG;
54 private PrintStream outStream;
55
56 /** DocumentBuilder to use when creating the document to start with. */
57 private static DocumentBuilder builder = getDocumentBuilder();
58
59 /**
60 * Returns a default DocumentBuilder instance or throws an
61 * ExceptionInInitializerError if it can't be created.
62 *
63 * @return a default DocumentBuilder instance.
64 */
65 private static DocumentBuilder getDocumentBuilder() {
66 try {
67 return DocumentBuilderFactory.newInstance().newDocumentBuilder();
68 } catch (Exception exc) {
69 throw new ExceptionInInitializerError(exc);
70 }
71 }
72
73 /** XML element name for a build. */
74 private static final String BUILD_TAG = "build";
75 /** XML element name for a target. */
76 private static final String TARGET_TAG = "target";
77 /** XML element name for a task. */
78 private static final String TASK_TAG = "task";
79 /** XML element name for a message. */
80 private static final String MESSAGE_TAG = "message";
81 /** XML attribute name for a name. */
82 private static final String NAME_ATTR = "name";
83 /** XML attribute name for a time. */
84 private static final String TIME_ATTR = "time";
85 /** XML attribute name for a message priority. */
86 private static final String PRIORITY_ATTR = "priority";
87 /** XML attribute name for a file location. */
88 private static final String LOCATION_ATTR = "location";
89 /** XML attribute name for an error description. */
90 private static final String ERROR_ATTR = "error";
91 /** XML element name for a stack trace. */
92 private static final String STACKTRACE_TAG = "stacktrace";
93
94 /** The complete log document for this build. */
95 private Document doc = builder.newDocument();
96 /** Mapping for when tasks started (Task to TimedElement). */
97 private Hashtable tasks = new Hashtable();
98 /** Mapping for when targets started (Task to TimedElement). */
99 private Hashtable targets = new Hashtable();
100 /**
101 * Mapping of threads to stacks of elements
102 * (Thread to Stack of TimedElement).
103 */
104 private Hashtable threadStacks = new Hashtable();
105 /**
106 * When the build started.
107 */
108 private TimedElement buildElement = null;
109
110 /** Utility class representing the time an element started. */
111 private static class TimedElement {
112 /**
113 * Start time in milliseconds
114 * (as returned by <code>System.currentTimeMillis()</code>).
115 */
116 private long startTime;
117 /** Element created at the start time. */
118 private Element element;
119 public String toString() {
120 return element.getTagName() + ":" + element.getAttribute("name");
121 }
122 }
123
124 /**
125 * Constructs a new BuildListener that logs build events to an XML file.
126 */
127 public XmlLogger() {
128 }
129
130 /**
131 * Fired when the build starts, this builds the top-level element for the
132 * document and remembers the time of the start of the build.
133 *
134 * @param event Ignored.
135 */
136 public void buildStarted(BuildEvent event) {
137 buildElement = new TimedElement();
138 buildElement.startTime = System.currentTimeMillis();
139 buildElement.element = doc.createElement(BUILD_TAG);
140 }
141
142 /**
143 * Fired when the build finishes, this adds the time taken and any
144 * error stacktrace to the build element and writes the document to disk.
145 *
146 * @param event An event with any relevant extra information.
147 * Will not be <code>null</code>.
148 */
149 public void buildFinished(BuildEvent event) {
150 long totalTime = System.currentTimeMillis() - buildElement.startTime;
151 buildElement.element.setAttribute(TIME_ATTR,
152 DefaultLogger.formatTime(totalTime));
153
154 if (event.getException() != null) {
155 buildElement.element.setAttribute(ERROR_ATTR,
156 event.getException().toString());
157 // print the stacktrace in the build file it is always useful...
158 // better have too much info than not enough.
159 Throwable t = event.getException();
160 Text errText = doc.createCDATASection(StringUtils.getStackTrace(t));
161 Element stacktrace = doc.createElement(STACKTRACE_TAG);
162 stacktrace.appendChild(errText);
163 buildElement.element.appendChild(stacktrace);
164 }
165
166 String outFilename = event.getProject().getProperty("XmlLogger.file");
167 if (outFilename == null) {
168 outFilename = "log.xml";
169 }
170 String xslUri
171 = event.getProject().getProperty("ant.XmlLogger.stylesheet.uri");
172 if (xslUri == null) {
173 xslUri = "log.xsl";
174 }
175 Writer out = null;
176 try {
177 // specify output in UTF8 otherwise accented characters will blow
178 // up everything
179 OutputStream stream = outStream;
180 if (stream == null) {
181 stream = new FileOutputStream(outFilename);
182 }
183 out = new OutputStreamWriter(stream, "UTF8");
184 out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
185 if (xslUri.length() > 0) {
186 out.write("<?xml-stylesheet type=\"text/xsl\" href=\""
187 + xslUri + "\"?>\n\n");
188 }
189 (new DOMElementWriter()).write(buildElement.element, out, 0, "\t");
190 out.flush();
191 } catch (IOException exc) {
192 throw new BuildException("Unable to write log file", exc);
193 } finally {
194 if (out != null) {
195 try {
196 out.close();
197 } catch (IOException e) {
198 // ignore
199 }
200 }
201 }
202 buildElement = null;
203 }
204
205 /**
206 * Returns the stack of timed elements for the current thread.
207 * @return the stack of timed elements for the current thread
208 */
209 private Stack getStack() {
210 Stack threadStack = (Stack) threadStacks.get(Thread.currentThread());
211 if (threadStack == null) {
212 threadStack = new Stack();
213 threadStacks.put(Thread.currentThread(), threadStack);
214 }
215 /* For debugging purposes uncomment:
216 org.w3c.dom.Comment s = doc.createComment("stack=" + threadStack);
217 buildElement.element.appendChild(s);
218 */
219 return threadStack;
220 }
221
222 /**
223 * Fired when a target starts building, this pushes a timed element
224 * for the target onto the stack of elements for the current thread,
225 * remembering the current time and the name of the target.
226 *
227 * @param event An event with any relevant extra information.
228 * Will not be <code>null</code>.
229 */
230 public void targetStarted(BuildEvent event) {
231 Target target = event.getTarget();
232 TimedElement targetElement = new TimedElement();
233 targetElement.startTime = System.currentTimeMillis();
234 targetElement.element = doc.createElement(TARGET_TAG);
235 targetElement.element.setAttribute(NAME_ATTR, target.getName());
236 targets.put(target, targetElement);
237 getStack().push(targetElement);
238 }
239
240 /**
241 * Fired when a target finishes building, this adds the time taken
242 * and any error stacktrace to the appropriate target element in the log.
243 *
244 * @param event An event with any relevant extra information.
245 * Will not be <code>null</code>.
246 */
247 public void targetFinished(BuildEvent event) {
248 Target target = event.getTarget();
249 TimedElement targetElement = (TimedElement) targets.get(target);
250 if (targetElement != null) {
251 long totalTime
252 = System.currentTimeMillis() - targetElement.startTime;
253 targetElement.element.setAttribute(TIME_ATTR,
254 DefaultLogger.formatTime(totalTime));
255
256 TimedElement parentElement = null;
257 Stack threadStack = getStack();
258 if (!threadStack.empty()) {
259 TimedElement poppedStack = (TimedElement) threadStack.pop();
260 if (poppedStack != targetElement) {
261 throw new RuntimeException("Mismatch - popped element = "
262 + poppedStack
263 + " finished target element = "
264 + targetElement);
265 }
266 if (!threadStack.empty()) {
267 parentElement = (TimedElement) threadStack.peek();
268 }
269 }
270 if (parentElement == null) {
271 buildElement.element.appendChild(targetElement.element);
272 } else {
273 parentElement.element.appendChild(targetElement.element);
274 }
275 }
276 targets.remove(target);
277 }
278
279 /**
280 * Fired when a task starts building, this pushes a timed element
281 * for the task onto the stack of elements for the current thread,
282 * remembering the current time and the name of the task.
283 *
284 * @param event An event with any relevant extra information.
285 * Will not be <code>null</code>.
286 */
287 public void taskStarted(BuildEvent event) {
288 TimedElement taskElement = new TimedElement();
289 taskElement.startTime = System.currentTimeMillis();
290 taskElement.element = doc.createElement(TASK_TAG);
291
292 Task task = event.getTask();
293 String name = event.getTask().getTaskName();
294 if (name == null) {
295 name = "";
296 }
297 taskElement.element.setAttribute(NAME_ATTR, name);
298 taskElement.element.setAttribute(LOCATION_ATTR,
299 event.getTask().getLocation().toString());
300 tasks.put(task, taskElement);
301 getStack().push(taskElement);
302 }
303
304 /**
305 * Fired when a task finishes building, this adds the time taken
306 * and any error stacktrace to the appropriate task element in the log.
307 *
308 * @param event An event with any relevant extra information.
309 * Will not be <code>null</code>.
310 */
311 public void taskFinished(BuildEvent event) {
312 Task task = event.getTask();
313 TimedElement taskElement = (TimedElement) tasks.get(task);
314 if (taskElement != null) {
315 long totalTime = System.currentTimeMillis() - taskElement.startTime;
316 taskElement.element.setAttribute(TIME_ATTR,
317 DefaultLogger.formatTime(totalTime));
318 Target target = task.getOwningTarget();
319 TimedElement targetElement = null;
320 if (target != null) {
321 targetElement = (TimedElement) targets.get(target);
322 }
323 if (targetElement == null) {
324 buildElement.element.appendChild(taskElement.element);
325 } else {
326 targetElement.element.appendChild(taskElement.element);
327 }
328 Stack threadStack = getStack();
329 if (!threadStack.empty()) {
330 TimedElement poppedStack = (TimedElement) threadStack.pop();
331 if (poppedStack != taskElement) {
332 throw new RuntimeException("Mismatch - popped element = "
333 + poppedStack + " finished task element = "
334 + taskElement);
335 }
336 }
337 tasks.remove(task);
338 } else {
339 throw new RuntimeException("Unknown task " + task + " not in " + tasks);
340 }
341 }
342
343
344 /**
345 * Get the TimedElement associated with a task.
346 *
347 * Where the task is not found directly, search for unknown elements which
348 * may be hiding the real task
349 */
350 private TimedElement getTaskElement(Task task) {
351 TimedElement element = (TimedElement) tasks.get(task);
352 if (element != null) {
353 return element;
354 }
355
356 for (Enumeration e = tasks.keys(); e.hasMoreElements();) {
357 Task key = (Task) e.nextElement();
358 if (key instanceof UnknownElement) {
359 if (((UnknownElement) key).getTask() == task) {
360 return (TimedElement) tasks.get(key);
361 }
362 }
363 }
364
365 return null;
366 }
367
368 /**
369 * Fired when a message is logged, this adds a message element to the
370 * most appropriate parent element (task, target or build) and records
371 * the priority and text of the message.
372 *
373 * @param event An event with any relevant extra information.
374 * Will not be <code>null</code>.
375 */
376 public void messageLogged(BuildEvent event) {
377 int priority = event.getPriority();
378 if (priority > msgOutputLevel) {
379 return;
380 }
381 Element messageElement = doc.createElement(MESSAGE_TAG);
382
383 String name = "debug";
384 switch (event.getPriority()) {
385 case Project.MSG_ERR:
386 name = "error";
387 break;
388 case Project.MSG_WARN:
389 name = "warn";
390 break;
391 case Project.MSG_INFO:
392 name = "info";
393 break;
394 default:
395 name = "debug";
396 break;
397 }
398 messageElement.setAttribute(PRIORITY_ATTR, name);
399
400 Text messageText = doc.createCDATASection(event.getMessage());
401 messageElement.appendChild(messageText);
402
403 TimedElement parentElement = null;
404
405 Task task = event.getTask();
406
407 Target target = event.getTarget();
408 if (task != null) {
409 parentElement = getTaskElement(task);
410 }
411 if (parentElement == null && target != null) {
412 parentElement = (TimedElement) targets.get(target);
413 }
414
415 /*
416 if (parentElement == null) {
417 Stack threadStack
418 = (Stack) threadStacks.get(Thread.currentThread());
419 if (threadStack != null) {
420 if (!threadStack.empty()) {
421 parentElement = (TimedElement) threadStack.peek();
422 }
423 }
424 }
425 */
426
427 if (parentElement != null) {
428 parentElement.element.appendChild(messageElement);
429 } else {
430 buildElement.element.appendChild(messageElement);
431 }
432 }
433
434 // -------------------------------------------------- BuildLogger interface
435
436 /**
437 * Set the logging level when using this as a Logger
438 *
439 * @param level the logging level -
440 * see {@link org.apache.tools.ant.Project#MSG_ERR Project}
441 * class for level definitions
442 */
443 public void setMessageOutputLevel(int level) {
444 msgOutputLevel = level;
445 }
446
447 /**
448 * Set the output stream to which logging output is sent when operating
449 * as a logger.
450 *
451 * @param output the output PrintStream.
452 */
453 public void setOutputPrintStream(PrintStream output) {
454 this.outStream = new PrintStream(output, true);
455 }
456
457 /**
458 * Ignore emacs mode, as it has no meaning in XML format
459 *
460 * @param emacsMode true if logger should produce emacs compatible
461 * output
462 */
463 public void setEmacsMode(boolean emacsMode) {
464 }
465
466 /**
467 * Ignore error print stream. All output will be written to
468 * either the XML log file or the PrintStream provided to
469 * setOutputPrintStream
470 *
471 * @param err the stream we are going to ignore.
472 */
473 public void setErrorPrintStream(PrintStream err) {
474 }
475
476}
Note: See TracBrowser for help on using the repository browser.