1 /* |
1 /* |
2 * Copyright (c) 2007-2008 Nokia Corporation and/or its subsidiary(-ies). |
2 * Copyright (c) 2007-2008 Nokia Corporation and/or its subsidiary(-ies). |
3 * All rights reserved. |
3 * All rights reserved. |
4 * This component and the accompanying materials are made available |
4 * This component and the accompanying materials are made available |
5 * under the terms of the License "Eclipse Public License v1.0" |
5 * under the terms of the License "Eclipse Public License v1.0" |
6 * which accompanies this distribution, and is available |
6 * which accompanies this distribution, and is available |
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
8 * |
8 * |
9 * Initial Contributors: |
9 * Initial Contributors: |
10 * Nokia Corporation - initial contribution. |
10 * Nokia Corporation - initial contribution. |
11 * |
11 * |
12 * Contributors: |
12 * Contributors: |
13 * |
13 * |
14 * Description: |
14 * Description: |
15 * |
15 * |
16 */ |
16 */ |
17 |
17 |
18 package com.nokia.ant; |
18 package com.nokia.ant; |
19 |
19 |
20 import java.io.FileOutputStream; |
20 import java.io.FileOutputStream; |
21 import java.io.IOException; |
21 import java.io.IOException; |
22 import java.io.OutputStream; |
22 import java.io.OutputStream; |
23 import java.io.OutputStreamWriter; |
23 import java.io.OutputStreamWriter; |
24 import java.io.PrintStream; |
24 import java.io.PrintStream; |
25 import java.io.Writer; |
25 import java.io.Writer; |
|
26 import java.util.Enumeration; |
26 import java.util.Hashtable; |
27 import java.util.Hashtable; |
27 import java.util.Stack; |
28 import java.util.Stack; |
28 import java.util.Enumeration; |
29 |
29 import javax.xml.parsers.DocumentBuilder; |
30 import javax.xml.parsers.DocumentBuilder; |
30 import javax.xml.parsers.DocumentBuilderFactory; |
31 import javax.xml.parsers.DocumentBuilderFactory; |
31 import javax.xml.parsers.ParserConfigurationException; |
32 import javax.xml.parsers.ParserConfigurationException; |
|
33 |
|
34 import org.apache.tools.ant.BuildEvent; |
|
35 import org.apache.tools.ant.BuildException; |
|
36 import org.apache.tools.ant.BuildLogger; |
|
37 import org.apache.tools.ant.Project; |
|
38 import org.apache.tools.ant.Target; |
|
39 import org.apache.tools.ant.Task; |
|
40 import org.apache.tools.ant.UnknownElement; |
32 import org.apache.tools.ant.util.DOMElementWriter; |
41 import org.apache.tools.ant.util.DOMElementWriter; |
|
42 import org.apache.tools.ant.util.DateUtils; |
33 import org.apache.tools.ant.util.StringUtils; |
43 import org.apache.tools.ant.util.StringUtils; |
34 import org.apache.tools.ant.*; |
|
35 import org.apache.tools.ant.util.DateUtils; |
|
36 import org.w3c.dom.Document; |
44 import org.w3c.dom.Document; |
37 import org.w3c.dom.Element; |
45 import org.w3c.dom.Element; |
38 import org.w3c.dom.Text; |
46 import org.w3c.dom.Text; |
39 |
47 |
40 /** |
48 /** |
41 * Generates a file in the current directory with |
49 * Generates a file in the current directory with an XML description of what happened during a |
42 * an XML description of what happened during a build. |
50 * build. The default filename is "log.xml", but this can be overridden with the property |
43 * The default filename is "log.xml", but this can be overridden |
51 * <code>XmlLogger.file</code>. |
44 * with the property <code>XmlLogger.file</code>. |
52 * |
45 * |
53 * This implementation assumes in its sanity checking that only one thread runs a particular |
46 * This implementation assumes in its sanity checking that only one |
54 * target/task at a time. This is enforced by the way that parallel builds and antcalls are done - |
47 * thread runs a particular target/task at a time. This is enforced |
55 * and indeed all but the simplest of tasks could run into problems if executed in parallel. |
48 * by the way that parallel builds and antcalls are done - and |
|
49 * indeed all but the simplest of tasks could run into problems |
|
50 * if executed in parallel. |
|
51 * |
|
52 * @see Project#addBuildListener(BuildListener) |
|
53 */ |
56 */ |
54 public class XmlLogger implements BuildLogger { |
57 public class XmlLogger implements BuildLogger { |
55 |
58 |
56 /** XML element name for a build. */ |
59 /** XML element name for a build. */ |
57 private static final String BUILD_TAG = "build"; |
60 private static final String BUILD_TAG = "build"; |
58 /** XML element name for a target. */ |
|
59 private static final String TARGET_TAG = "target"; |
|
60 /** XML element name for a task. */ |
|
61 private static final String TASK_TAG = "task"; |
|
62 /** XML element name for a message. */ |
61 /** XML element name for a message. */ |
63 private static final String MESSAGE_TAG = "message"; |
62 private static final String MESSAGE_TAG = "message"; |
64 /** XML attribute name for a name. */ |
|
65 private static final String NAME_ATTR = "name"; |
|
66 /** XML attribute name for a time. */ |
63 /** XML attribute name for a time. */ |
67 private static final String TIME_ATTR = "time"; |
64 private static final String TIME_ATTR = "time"; |
68 /** XML attribute name for a message priority. */ |
65 /** XML attribute name for a message priority. */ |
69 private static final String PRIORITY_ATTR = "priority"; |
66 private static final String PRIORITY_ATTR = "priority"; |
70 /** XML attribute name for a file location. */ |
|
71 private static final String LOCATION_ATTR = "location"; |
|
72 /** XML attribute name for an error description. */ |
67 /** XML attribute name for an error description. */ |
73 private static final String ERROR_ATTR = "error"; |
68 private static final String ERROR_ATTR = "error"; |
74 /** XML element name for a stack trace. */ |
69 /** XML element name for a stack trace. */ |
75 private static final String STACKTRACE_TAG = "stacktrace"; |
70 private static final String STACKTRACE_TAG = "stacktrace"; |
76 |
71 |
77 /** DocumentBuilder to use when creating the document to start with. */ |
72 /** DocumentBuilder to use when creating the document to start with. */ |
78 private static DocumentBuilder builder = getDocumentBuilder(); |
73 private static DocumentBuilder builder = getDocumentBuilder(); |
79 |
74 |
80 private int msgOutputLevel = Project.MSG_ERR; |
75 private int msgOutputLevel = Project.MSG_ERR; |
81 private PrintStream outStream; |
76 private PrintStream outStream; |
82 |
77 |
83 /** The complete log document for this build. */ |
78 /** The complete log document for this build. */ |
84 private Document doc = builder.newDocument(); |
79 private Document doc = builder.newDocument(); |
85 /** Mapping for when tasks started (Task to TimedElement). */ |
80 /** Mapping for when tasks started (Task to TimedElement). */ |
86 private Hashtable tasks = new Hashtable(); |
81 private Hashtable tasks = new Hashtable(); |
87 /** Mapping for when targets started (Task to TimedElement). */ |
82 /** Mapping for when targets started (Task to TimedElement). */ |
88 private Hashtable targets = new Hashtable(); |
83 private Hashtable targets = new Hashtable(); |
89 /** |
84 /** |
90 * Mapping of threads to stacks of elements |
85 * Mapping of threads to stacks of elements (Thread to Stack of TimedElement). |
91 * (Thread to Stack of TimedElement). |
|
92 */ |
86 */ |
93 private Hashtable<Thread, Stack> threadStacks = new Hashtable<Thread, Stack>(); |
87 private Hashtable<Thread, Stack> threadStacks = new Hashtable<Thread, Stack>(); |
94 /** |
88 /** |
95 * When the build started. |
89 * When the build started. |
96 */ |
90 */ |
97 private TimedElement buildElement; |
91 private TimedElement buildElement; |
98 |
92 |
99 /** |
93 /** |
100 * Constructs a new BuildListener that logs build events to an XML file. |
94 * Returns a default DocumentBuilder instance or throws an ExceptionInInitializerError if it |
101 */ |
95 * can't be created. |
102 public XmlLogger() { |
96 * |
103 } |
|
104 |
|
105 /** |
|
106 * Returns a default DocumentBuilder instance or throws an |
|
107 * ExceptionInInitializerError if it can't be created. |
|
108 * |
|
109 * @return a default DocumentBuilder instance. |
97 * @return a default DocumentBuilder instance. |
110 */ |
98 */ |
111 private static DocumentBuilder getDocumentBuilder() { |
99 private static DocumentBuilder getDocumentBuilder() { |
112 try { |
100 try { |
113 return DocumentBuilderFactory.newInstance().newDocumentBuilder(); |
101 return DocumentBuilderFactory.newInstance().newDocumentBuilder(); |
114 } catch (ParserConfigurationException exc) { |
102 } |
|
103 catch (ParserConfigurationException exc) { |
115 throw new ExceptionInInitializerError(exc.getMessage()); |
104 throw new ExceptionInInitializerError(exc.getMessage()); |
116 } |
105 } |
117 } |
106 } |
118 |
107 |
119 /** Utility class representing the time an element started. */ |
108 /** Utility class representing the time an element started. */ |
120 private static class TimedElement { |
109 private static class TimedElement { |
121 /** |
110 /** |
122 * Start time in milliseconds |
111 * Start time in milliseconds (as returned by <code>System.currentTimeMillis()</code>). |
123 * (as returned by <code>System.currentTimeMillis()</code>). |
|
124 */ |
112 */ |
125 private long startTime; |
113 private long startTime; |
126 /** Element created at the start time. */ |
114 /** Element created at the start time. */ |
127 private Element element; |
115 private Element element; |
|
116 |
128 public String toString() { |
117 public String toString() { |
129 return element.getTagName() + ":" + element.getAttribute("name"); |
118 return element.getTagName() + ":" + element.getAttribute("name"); |
130 } |
119 } |
131 } |
120 } |
132 |
121 |
133 /** |
122 /** |
134 * Fired when the build starts, this builds the top-level element for the |
123 * Fired when the build starts, this builds the top-level element for the document and remembers |
135 * document and remembers the time of the start of the build. |
124 * the time of the start of the build. |
136 * |
125 * |
137 * @param event Ignored. |
126 * @param event Ignored. |
138 */ |
127 */ |
139 public void buildStarted(BuildEvent event) { |
128 public void buildStarted(BuildEvent event) { |
140 buildElement = new TimedElement(); |
129 buildElement = new TimedElement(); |
141 buildElement.startTime = System.currentTimeMillis(); |
130 buildElement.startTime = System.currentTimeMillis(); |
142 buildElement.element = doc.createElement(BUILD_TAG); |
131 buildElement.element = doc.createElement(BUILD_TAG); |
143 } |
132 } |
144 |
133 |
145 /** |
134 /** |
146 * Fired when the build finishes, this adds the time taken and any |
135 * Fired when the build finishes, this adds the time taken and any error stacktrace to the build |
147 * error stacktrace to the build element and writes the document to disk. |
136 * element and writes the document to disk. |
148 * |
137 * |
149 * @param event An event with any relevant extra information. |
138 * @param event An event with any relevant extra information. Will not be <code>null</code>. |
150 * Will not be <code>null</code>. |
|
151 */ |
139 */ |
152 public void buildFinished(BuildEvent event) { |
140 public void buildFinished(BuildEvent event) { |
153 long totalTime = System.currentTimeMillis() - buildElement.startTime; |
141 long totalTime = System.currentTimeMillis() - buildElement.startTime; |
154 buildElement.element.setAttribute(TIME_ATTR, |
142 buildElement.element.setAttribute(TIME_ATTR, DateUtils.formatElapsedTime(totalTime)); |
155 DateUtils.formatElapsedTime(totalTime)); |
|
156 |
143 |
157 if (event.getException() != null) { |
144 if (event.getException() != null) { |
158 buildElement.element.setAttribute(ERROR_ATTR, |
145 buildElement.element.setAttribute(ERROR_ATTR, event.getException().toString()); |
159 event.getException().toString()); |
|
160 // print the stacktrace in the build file it is always useful... |
146 // print the stacktrace in the build file it is always useful... |
161 // better have too much info than not enough. |
147 // better have too much info than not enough. |
162 Throwable exception = event.getException(); |
148 Throwable exception = event.getException(); |
163 Text errText = doc.createCDATASection(StringUtils.getStackTrace(exception)); |
149 Text errText = doc.createCDATASection(StringUtils.getStackTrace(exception)); |
164 Element stacktrace = doc.createElement(STACKTRACE_TAG); |
150 Element stacktrace = doc.createElement(STACKTRACE_TAG); |
184 stream = new FileOutputStream(outFilename); |
169 stream = new FileOutputStream(outFilename); |
185 } |
170 } |
186 out = new OutputStreamWriter(stream, "UTF8"); |
171 out = new OutputStreamWriter(stream, "UTF8"); |
187 out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); |
172 out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); |
188 if (xslUri.length() > 0) { |
173 if (xslUri.length() > 0) { |
189 out.write("<?xml-stylesheet type=\"text/xsl\" href=\"" |
174 out.write("<?xml-stylesheet type=\"text/xsl\" href=\"" + xslUri + "\"?>\n\n"); |
190 + xslUri + "\"?>\n\n"); |
|
191 } |
175 } |
192 (new DOMElementWriter()).write(buildElement.element, out, 0, "\t"); |
176 (new DOMElementWriter()).write(buildElement.element, out, 0, "\t"); |
193 out.flush(); |
177 out.flush(); |
194 } catch (IOException exc) { |
178 } |
|
179 catch (IOException exc) { |
195 throw new BuildException("Unable to write log file" + exc.getMessage()); |
180 throw new BuildException("Unable to write log file" + exc.getMessage()); |
196 } finally { |
181 } |
|
182 finally { |
197 if (out != null) { |
183 if (out != null) { |
198 try { |
184 try { |
199 out.close(); |
185 out.close(); |
200 } catch (IOException e) { |
186 } |
|
187 catch (IOException e) { |
201 // We are Ignoring the errors as no need to fail the build. |
188 // We are Ignoring the errors as no need to fail the build. |
202 event.getProject().log("Not able to close the file handler " + e.getMessage(), Project.MSG_WARN); |
189 event.getProject().log("Not able to close the file handler " + e.getMessage(), Project.MSG_WARN); |
203 e = null; // ignore |
190 e = null; // ignore |
204 } |
191 } |
205 } |
192 } |
206 } |
193 } |
207 buildElement = null; |
194 buildElement = null; |
208 } |
195 } |
209 |
196 |
210 /** |
197 /** |
211 * Returns the stack of timed elements for the current thread. |
198 * Fired when a target starts building, this pushes a timed element for the target onto the |
212 * @return the stack of timed elements for the current thread |
199 * stack of elements for the current thread, remembering the current time and the name of the |
213 */ |
200 * target. |
214 private Stack getStack() { |
201 * |
215 Stack threadStack = threadStacks.get(Thread.currentThread()); |
202 * @param event An event with any relevant extra information. Will not be <code>null</code>. |
216 if (threadStack == null) { |
|
217 threadStack = new Stack(); |
|
218 threadStacks.put(Thread.currentThread(), threadStack); |
|
219 } |
|
220 /* For debugging purposes uncomment: |
|
221 org.w3c.dom.Comment s = doc.createComment("stack=" + threadStack); |
|
222 buildElement.element.appendChild(s); |
|
223 */ |
|
224 return threadStack; |
|
225 } |
|
226 |
|
227 /** |
|
228 * Fired when a target starts building, this pushes a timed element |
|
229 * for the target onto the stack of elements for the current thread, |
|
230 * remembering the current time and the name of the target. |
|
231 * |
|
232 * @param event An event with any relevant extra information. |
|
233 * Will not be <code>null</code>. |
|
234 */ |
203 */ |
235 public void targetStarted(BuildEvent event) { |
204 public void targetStarted(BuildEvent event) { |
236 } |
205 } |
237 |
206 |
238 /** |
207 /** |
239 * Fired when a target finishes building, this adds the time taken |
208 * Fired when a target finishes building, this adds the time taken and any error stacktrace to |
240 * and any error stacktrace to the appropriate target element in the log. |
209 * the appropriate target element in the log. |
241 * |
210 * |
242 * @param event An event with any relevant extra information. |
211 * @param event An event with any relevant extra information. Will not be <code>null</code>. |
243 * Will not be <code>null</code>. |
|
244 */ |
212 */ |
245 public void targetFinished(BuildEvent event) { |
213 public void targetFinished(BuildEvent event) { |
246 } |
214 } |
247 |
215 |
248 /** |
216 /** |
249 * Fired when a task starts building, this pushes a timed element |
217 * Fired when a task starts building, this pushes a timed element for the task onto the stack of |
250 * for the task onto the stack of elements for the current thread, |
218 * elements for the current thread, remembering the current time and the name of the task. |
251 * remembering the current time and the name of the task. |
219 * |
252 * |
220 * @param event An event with any relevant extra information. Will not be <code>null</code>. |
253 * @param event An event with any relevant extra information. |
|
254 * Will not be <code>null</code>. |
|
255 */ |
221 */ |
256 public void taskStarted(BuildEvent event) { |
222 public void taskStarted(BuildEvent event) { |
257 } |
223 } |
258 |
224 |
259 /** |
225 /** |
260 * Fired when a task finishes building, this adds the time taken |
226 * Fired when a task finishes building, this adds the time taken and any error stacktrace to the |
261 * and any error stacktrace to the appropriate task element in the log. |
227 * appropriate task element in the log. |
262 * |
228 * |
263 * @param event An event with any relevant extra information. |
229 * @param event An event with any relevant extra information. Will not be <code>null</code>. |
264 * Will not be <code>null</code>. |
|
265 */ |
230 */ |
266 public void taskFinished(BuildEvent event) { |
231 public void taskFinished(BuildEvent event) { |
267 } |
232 } |
268 |
233 |
269 |
|
270 /** |
234 /** |
271 * Get the TimedElement associated with a task. |
235 * Get the TimedElement associated with a task. |
272 * |
236 * |
273 * Where the task is not found directly, search for unknown elements which |
237 * Where the task is not found directly, search for unknown elements which may be hiding the |
274 * may be hiding the real task |
238 * real task |
275 */ |
239 */ |
276 private TimedElement getTaskElement(Task task) { |
240 private TimedElement getTaskElement(Task task) { |
277 TimedElement element = (TimedElement) tasks.get(task); |
241 TimedElement element = (TimedElement) tasks.get(task); |
278 if (element != null) { |
242 if (element != null) { |
279 return element; |
243 return element; |
344 if (parentElement == null && target != null) { |
306 if (parentElement == null && target != null) { |
345 parentElement = (TimedElement) targets.get(target); |
307 parentElement = (TimedElement) targets.get(target); |
346 } |
308 } |
347 |
309 |
348 /* |
310 /* |
349 if (parentElement == null) { |
311 * if (parentElement == null) { Stack threadStack = (Stack) |
350 Stack threadStack |
312 * threadStacks.get(Thread.currentThread()); if (threadStack != null) { if |
351 = (Stack) threadStacks.get(Thread.currentThread()); |
313 * (!threadStack.empty()) { parentElement = (TimedElement) threadStack.peek(); } } } |
352 if (threadStack != null) { |
314 */ |
353 if (!threadStack.empty()) { |
|
354 parentElement = (TimedElement) threadStack.peek(); |
|
355 } |
|
356 } |
|
357 } |
|
358 */ |
|
359 |
315 |
360 if (parentElement != null) { |
316 if (parentElement != null) { |
361 parentElement.element.appendChild(messageElement); |
317 parentElement.element.appendChild(messageElement); |
362 } else { |
318 } |
|
319 else { |
363 buildElement.element.appendChild(messageElement); |
320 buildElement.element.appendChild(messageElement); |
364 } |
321 } |
365 } |
322 } |
366 |
323 |
367 // -------------------------------------------------- BuildLogger interface |
324 // -------------------------------------------------- BuildLogger interface |
368 |
325 |
369 /** |
326 /** |
370 * Set the logging level when using this as a Logger |
327 * Set the logging level when using this as a Logger |
371 * |
328 * |
372 * @param level the logging level - |
329 * @param level the logging level - see {@link org.apache.tools.ant.Project#MSG_ERR Project} |
373 * see {@link org.apache.tools.ant.Project#MSG_ERR Project} |
|
374 * class for level definitions |
330 * class for level definitions |
375 */ |
331 */ |
376 public void setMessageOutputLevel(int level) { |
332 public void setMessageOutputLevel(int level) { |
377 msgOutputLevel = level; |
333 msgOutputLevel = level; |
378 } |
334 } |
379 |
335 |
380 /** |
336 /** |
381 * Set the output stream to which logging output is sent when operating |
337 * Set the output stream to which logging output is sent when operating as a logger. |
382 * as a logger. |
338 * |
383 * |
|
384 * @param output the output PrintStream. |
339 * @param output the output PrintStream. |
385 */ |
340 */ |
386 public void setOutputPrintStream(PrintStream output) { |
341 public void setOutputPrintStream(PrintStream output) { |
387 this.outStream = new PrintStream(output, true); |
342 this.outStream = new PrintStream(output, true); |
388 } |
343 } |
389 |
344 |
390 /** |
345 /** |
391 * Ignore emacs mode, as it has no meaning in XML format |
346 * Ignore emacs mode, as it has no meaning in XML format |
392 * |
347 * |
393 * @param emacsMode true if logger should produce emacs compatible |
348 * @param emacsMode true if logger should produce emacs compatible output |
394 * output |
|
395 */ |
349 */ |
396 public void setEmacsMode(boolean emacsMode) { |
350 public void setEmacsMode(boolean emacsMode) { |
397 } |
351 } |
398 |
352 |
399 /** |
353 /** |
400 * Ignore error print stream. All output will be written to |
354 * Ignore error print stream. All output will be written to either the XML log file or the |
401 * either the XML log file or the PrintStream provided to |
355 * PrintStream provided to setOutputPrintStream |
402 * setOutputPrintStream |
356 * |
403 * |
|
404 * @param err the stream we are going to ignore. |
357 * @param err the stream we are going to ignore. |
405 */ |
358 */ |
406 public void setErrorPrintStream(PrintStream err) { |
359 public void setErrorPrintStream(PrintStream err) { |
407 } |
360 } |
408 |
361 |