This blog explains how to add a custom logger to the teambuild process in the Beta3 version (which will be releasing shortly) of TeamFoundation. The TeamBuild logger currently logs certain events such as solutions/projects built, their status, completion times etc. to a database and displays this in the build report. This information is derived from events raised by msbuild. In teambuild all events raised by msbuild are not stored/shown in the build report. If a user wants to capture these other events raised by msbuild during the build process (say information about tasks that are executed), then she can write her own custom logger and write them to a different file and then write her own custom report that displays this information.
Let us say we want to log all events raised by msbuild in an xml format. We can do this in the following way - We can add each event recieved as an xml element with the most basic information such as timestamp, status, message etc. Each event type raised by msbuild (eg Build, Project, Message etc.) can be used as an xml tagname to describe that element type. The properties of the event that we are interested can be logged as attributes of that element.
The following is the code for such a simple logger
(note: this code is only for illustration purpose and doesn't have adequate exception handling)
using System;
using Microsoft.Build.Framework;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Text;
public class XMLLogger : ILogger
{
private XmlTextWriter w;
public void Initialize(IEventSource source)
{
source.BuildFinished += new BuildFinishedEventHandler(BuildFinishHandler);
source.BuildStarted += new BuildStartedEventHandler(BuildStartHandler);
source.ProjectStarted += new ProjectStartedEventHandler(ProjectStartHandler);
source.ProjectFinished += new ProjectFinishedEventHandler(ProjectFinishHandler);
source.TargetStarted += new TargetStartedEventHandler(TargetStartedHandler);
source.TargetFinished += new TargetFinishedEventHandler(TargetFinishedHandler);;
source.TaskStarted += new TaskStartedEventHandler(TaskStartedHandler);
source.TaskFinished += new TaskFinishedEventHandler(TaskFinishedHandler);
source.ErrorRaised += new BuildErrorEventHandler(ErrorHandler);
source.WarningRaised += new BuildWarningEventHandler(WarningHandler);
source.MessageRaised += new BuildMessageEventHandler(MessageHandler);
w = new XmlTextWriter("BuildLog.xml", Encoding.ASCII);
w.WriteStartDocument();
w.WriteStartElement("BuildLog");
}
void BuildStartHandler(Object sender, BuildStartedEventArgs arg)
{
w.WriteStartElement("BuildStarted");
w.WriteAttributeString("Time", arg.Timestamp.ToString());
w.WriteEndElement();
}
void BuildFinishHandler(Object sender, BuildFinishedEventArgs arg)
{
w.WriteStartElement("BuildFinished");
w.WriteAttributeString("Time", arg.Timestamp.ToString());
w.WriteAttributeString("Status", arg.Succeeded.ToString());
w.WriteEndElement();
}
void ProjectStartHandler(Object sender, ProjectStartedEventArgs arg)
{
w.WriteStartElement("ProjectStarted");
w.WriteAttributeString("Name", Path.GetFileName(arg.ProjectFile));
w.WriteAttributeString("Time", arg.Timestamp.ToString());
w.WriteEndElement();
}
void ProjectFinishHandler(Object sender, ProjectFinishedEventArgs arg)
{
w.WriteStartElement("ProjectFinished");
w.WriteAttributeString("Name", Path.GetFileName(arg.ProjectFile));
w.WriteAttributeString("Time", arg.Timestamp.ToString());
w.WriteAttributeString("Status", arg.Succeeded.ToString());
w.WriteEndElement();
}
void TargetStartedHandler(Object sender, TargetStartedEventArgs arg)
{
w.WriteStartElement("TargetStarted");
w.WriteAttributeString("Name", arg.TargetName);
w.WriteAttributeString("Time", arg.Timestamp.ToString());
w.WriteEndElement();
}
void TargetFinishedHandler(Object sender, TargetFinishedEventArgs arg)
{
w.WriteStartElement("TargetFinished");
w.WriteAttributeString("Name", arg.TargetName);
w.WriteAttributeString("Time", arg.Timestamp.ToString());
w.WriteAttributeString("Status", arg.Succeeded.ToString());
w.WriteEndElement();
}
void TaskStartedHandler(Object sender, TaskStartedEventArgs arg)
{
w.WriteStartElement("TaskStarted");
w.WriteAttributeString("Name", arg.TaskName);
w.WriteAttributeString("Time", arg.Timestamp.ToString());
w.WriteEndElement();
}
void TaskFinishedHandler(Object sender, TaskFinishedEventArgs arg)
{
w.WriteStartElement("TaskFinished");
w.WriteAttributeString("Name", arg.TaskName);
w.WriteAttributeString("Time", arg.Timestamp.ToString());
w.WriteAttributeString("Status", arg.Succeeded.ToString());
w.WriteEndElement();
}
void ErrorHandler(Object sender, BuildErrorEventArgs arg)
{
w.WriteStartElement("Error");
w.WriteAttributeString("Message", arg.Message);
w.WriteEndElement();
}
void WarningHandler(Object sender, BuildWarningEventArgs arg)
{
w.WriteStartElement("Warning");
w.WriteAttributeString("Message", arg.Message);
w.WriteEndElement();
}
void MessageHandler(Object sender, BuildMessageEventArgs arg)
{
w.WriteStartElement("Message");
w.WriteAttributeString("Message", arg.Message);
w.WriteEndElement();
}
public void Shutdown()
{
if (w != null)
{
w.WriteEndElement();
w.Close();
}
}
private LoggerVerbosity verbosity;
public LoggerVerbosity Verbosity
{
get
{
return verbosity;
}
set
{
verbosity = value;
}
}
private String parameters;
public String Parameters
{
get
{
return parameters;
}
set
{
parameters = value;
}
}
}
After building this logger as a library(say xmllogger.dll) we need to include it in the teambuild process. This can be done using the TfsBuild.rsp file. Additional Msbuild options can be passed via teambuild by including them in this file. (Check out http://blogs.msdn.com/manishagarwal/default.aspx for more uses of the .rsp file). This file is located at the following source control folder: $<Your Team Project>\TeamBuildTypes\<Your BuildTypeName>. We edit this file
and add the following line to this file
/l:XMLLogger,xmllogger.dll
We checkin this file and also the xmllogger.dll to the same directory. After we do this msbuild will use this logger and log events in xml format to the "BuildLog.xml" file.
After this we need to do one more thing which is to drop this BuildLog.xml file to the drop location along with the rest of the log files and binaries. This can be done by overriding the "AfterDropBuild" Target in the Microsoft.TeamFoundation.Build.Targets file (located at %Programfiles%\Msbuild\Microsoft\VisualStudio\v8.0\TeamBuild). (Again manish has a blog on how to do this at http://blogs.msdn.com/manishagarwal/default.aspx. ) This target can be overriden in the TfsBuild.proj file which is also located in same folder as the TfsBuild.rsp file. We edit the TfsBuild.Proj file and call the copy task in this target as shown below.
<Target Name="AfterDropBuild">
<Copy
SourceFiles="$(MSBuildProjectDirectory)\BuildLog.xml"
DestinationFolder="$(DropLocation)\$(BuildNumber)"
ContinueOnError="true" />
</Target>
After all the binaries are dropped to the dropsite, this step will drop the Buildlog.xml to the droplocation.