buildframework/helium/sf/java/legacy/src/com/nokia/ant/XmlLogger.java
changeset 628 7c4a911dc066
parent 588 c7c26511138f
equal deleted inserted replaced
588:c7c26511138f 628:7c4a911dc066
     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);
   168 
   154 
   169         String outFilename = event.getProject().getProperty("XmlLogger.file");
   155         String outFilename = event.getProject().getProperty("XmlLogger.file");
   170         if (outFilename == null) {
   156         if (outFilename == null) {
   171             outFilename = "log.xml";
   157             outFilename = "log.xml";
   172         }
   158         }
   173         String xslUri
   159         String xslUri = event.getProject().getProperty("ant.XmlLogger.stylesheet.uri");
   174                 = event.getProject().getProperty("ant.XmlLogger.stylesheet.uri");
       
   175         if (xslUri == null) {
   160         if (xslUri == null) {
   176             xslUri = "log.xsl";
   161             xslUri = "log.xsl";
   177         }
   162         }
   178         Writer out = null;
   163         Writer out = null;
   179         try {
   164         try {
   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;
   290 
   254 
   291         return null;
   255         return null;
   292     }
   256     }
   293 
   257 
   294     /**
   258     /**
   295      * Fired when a message is logged, this adds a message element to the
   259      * Fired when a message is logged, this adds a message element to the most appropriate parent
   296      * most appropriate parent element (task, target or build) and records
   260      * element (task, target or build) and records the priority and text of the message.
   297      * the priority and text of the message.
   261      * 
   298      *
   262      * @param event An event with any relevant extra information. Will not be <code>null</code>.
   299      * @param event An event with any relevant extra information.
       
   300      *              Will not be <code>null</code>.
       
   301      */
   263      */
   302     public void messageLogged(BuildEvent event) {
   264     public void messageLogged(BuildEvent event) {
   303         int priority = event.getPriority();
   265         int priority = event.getPriority();
   304         if (priority > msgOutputLevel) {
   266         if (priority > msgOutputLevel) {
   305             return;
   267             return;
   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