Export (0) Print
Expand All

How To: Write a Logger

Loggers provide a way for you to customize the output of your build and display messages, errors, or warnings in response to specific build events. Each logger is implemented as a .NET class that implements the ILogger interface, which is defined in the Microsoft.Build.Framework.dll assembly.

There are two approaches you can use when implementing a logger:

  • Implement the ILogger interface directly.

  • Derive your class from the helper class, Logger, which is defined in the Microsoft.Build.Utilities.dll assembly. Logger implements ILogger and provides default implementations of some ILogger members.

This topic will explain how to write a simple logger that derives from Logger, and displays messages on the console in response to certain build events.

The purpose of a logger is to gather information on build progress as it is reported by the build engine, and then report that information in a useful way. All loggers must override the Initialize method, which is where the logger registers for events. In this example, the logger registers for the TargetStarted, ProjectStarted, and ProjectFinished events.

public class MySimpleLogger : Logger
{
    public override void Initialize(Microsoft.Build.Framework.IEventSource eventSource)
    {
        //Register for the ProjectStarted, TargetStarted, and ProjectFinished events
        eventSource.ProjectStarted += new ProjectStartedEventHandler(eventSource_ProjectStarted);
        eventSource.TargetStarted += new TargetStartedEventHandler(eventSource_TargetStarted);
        eventSource.ProjectFinished += new ProjectFinishedEventHandler(eventSource_ProjectFinished);
    }

Now that the logger is registered for specific events, it needs to handle those events when they occur. For the ProjectStarted, and ProjectFinished events, the logger simply writes a short phrase and the name of the project file involved in the event. All messages from the logger are written to the console window.


void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e)
{
    Console.WriteLine("Project Started: " + e.ProjectFile);            
}

void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e)
{
    Console.WriteLine("Project Finished: " + e.ProjectFile);
}

In some cases, you may want to only log information from an event if the MSBuild.exe /verbosity switch contains a certain value. In this example, the TargetStarted event handler only logs a message if the Verbosity property, which is set by the /verbosity switch, is equal to Detailed.

void eventSource_TargetStarted(object sender, TargetStartedEventArgs e)
{
    if (Verbosity == LoggerVerbosity.Detailed)
    {
        Console.WriteLine("Target Started: " + e.TargetName);
    }
}

Once the logger is compiled into an assembly, you need to tell MSBuild to use that logger during builds. This is done using the /logger switch with MSBuild.exe. For more information on the switches available for MSBuild.exe, see MSBuild Command Line Reference.

The following command line builds the project MyProject.csproj and uses the logger class implemented in SimpleLogger.dll. The /nologo switch hides the banner and copyright message and the /noconsolelogger switch disables the default MSBuild console logger.

MSBuild /nologo /noconsolelogger /logger:SimpleLogger.dll

The following command line builds the project with the same logger, but with a Verbosity level of Detailed.

MSBuild /nologo /noconsolelogger /logger:SimpleLogger.dll /verbosity:Detailed

The following example contains the complete code for the logger.

using System;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;

namespace SimpleLogger
{

	public class MySimpleLogger : Logger
	{
		public override void Initialize(Microsoft.Build.Framework.IEventSource eventSource)
		{
			//Register for the ProjectStarted, TargetStarted, and ProjectFinished events
			eventSource.ProjectStarted += new ProjectStartedEventHandler(eventSource_ProjectStarted);
			eventSource.TargetStarted += new TargetStartedEventHandler(eventSource_TargetStarted);
			eventSource.ProjectFinished += new ProjectFinishedEventHandler(eventSource_ProjectFinished);
		}

		void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e)
		{
			Console.WriteLine("Project Started: " + e.ProjectFile);			
		}

		void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e)
		{
			Console.WriteLine("Project Finished: " + e.ProjectFile);
		}
		void eventSource_TargetStarted(object sender, TargetStartedEventArgs e)
		{
			if (Verbosity == LoggerVerbosity.Detailed)
			{
				Console.WriteLine("Target Started: " + e.TargetName);
			}
		}
	}
}

The following example shows how to implement a logger that writes the log to a file rather than displaying it in the console window.

using System;
using System.IO;
using System.Security;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace MyLoggers
{
	// This logger will derive from the Microsoft.Build.Utilities.Logger class,
	// which provides it with getters and setters for Verbosity and Parameters,
	// and a default empty Shutdown() implementation.
	public class BasicFileLogger : Logger
	{
		/// <summary>
		/// Initialize is guaranteed to be called by MSBuild at the start of the build
		/// before any events are raised.
		/// </summary>
		public override void Initialize(IEventSource eventSource)
		{
			// The name of the log file should be passed as the first item in the
			// "parameters" specification in the /logger switch.  It is required
			// to pass a log file to this logger. Other loggers may have zero or more than 
			// one parameters.
			if (null == Parameters)
			{
				throw new LoggerException("Log file was not set.");
			}
			string[] parameters = Parameters.Split(';');
			
			string logFile = parameters[0];
			if (String.IsNullOrEmpty(logFile))
			{
				throw new LoggerException("Log file was not set.");
			}
			
			if (parameters.Length > 1)
			{
				throw new LoggerException("Too many parameters passed.");
			}
			
			try
			{
				// Open the file
				this.streamWriter = new StreamWriter(logFile);
			}
			catch (Exception ex)
			{
				if
				(
					ex is UnauthorizedAccessException
					|| ex is ArgumentNullException
					|| ex is PathTooLongException
					|| ex is DirectoryNotFoundException
					|| ex is NotSupportedException
					|| ex is ArgumentException
					|| ex is SecurityException
					|| ex is IOException
				)
				{
					throw new LoggerException("Failed to create log file: " + ex.Message);
				}
				else
				{
					// Unexpected failure
					throw;
				}
			}

			// For brevity, we'll only register for certain event types. Loggers can also
			// register to handle TargetStarted/Finished and other events.
			eventSource.ProjectStarted += new ProjectStartedEventHandler(eventSource_ProjectStarted);
			eventSource.TaskStarted += new TaskStartedEventHandler(eventSource_TaskStarted);
			eventSource.MessageRaised += new BuildMessageEventHandler(eventSource_MessageRaised);
			eventSource.WarningRaised += new BuildWarningEventHandler(eventSource_WarningRaised);
			eventSource.ErrorRaised += new BuildErrorEventHandler(eventSource_ErrorRaised);
			eventSource.ProjectFinished += new ProjectFinishedEventHandler(eventSource_ProjectFinished);
		}

		void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e)
		{
			// BuildErrorEventArgs adds LineNumber, ColumnNumber, File, amongst other parameters
			string line = String.Format(": ERROR {0}({1},{2}): ", e.File, e.LineNumber, e.ColumnNumber);
			WriteLineWithSenderAndMessage(line, e);
		}
		
		void eventSource_WarningRaised(object sender, BuildWarningEventArgs e)
		{
			// BuildWarningEventArgs adds LineNumber, ColumnNumber, File, amongst other parameters
			string line = String.Format(": Warning {0}({1},{2}): ", e.File, e.LineNumber, e.ColumnNumber);
			WriteLineWithSenderAndMessage(line, e);
		}

		void eventSource_MessageRaised(object sender, BuildMessageEventArgs e)
		{
			// BuildMessageEventArgs adds Importance to BuildEventArgs
			// Let's take account of the verbosity setting we've been passed in deciding whether to log the message
			if ((e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal))
				|| (e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal))
				|| (e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed))				
				)
			{
				WriteLineWithSenderAndMessage(String.Empty, e);
			}
		}

		void eventSource_TaskStarted(object sender, TaskStartedEventArgs e)
		{
			// TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName
			// To keep this log clean, this logger will ignore these events.
		}
		
		void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e)
		{
			// ProjectStartedEventArgs adds ProjectFile, TargetNames
			// Just the regular message string is good enough here, so just display that.
			WriteLine(String.Empty, e);
			indent++;
		}

		void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e)
		{
			// The regular message string is good enough here too.
			indent--;
			WriteLine(String.Empty, e);
		}
		
		/// <summary>
		/// Write a line to the log, adding the SenderName and Message
		/// (these parameters are on all MSBuild event argument objects)
		/// </summary>
		private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e)
		{
			if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/))
			{
				// Well, if the sender name is MSBuild, let's leave it out for prettiness
				WriteLine(line, e);
			}
			else
			{
				WriteLine(e.SenderName + ": " + line, e);
			}
		}
		
		/// <summary>
		/// Just write a line to the log
		/// </summary>
		private void WriteLine(string line, BuildEventArgs e)
		{
			for (int i = indent; i > 0; i--)
			{
				streamWriter.Write("\t");
			}
			streamWriter.WriteLine(line + e.Message);
		}
		
		/// <summary>
		/// Shutdown() is guaranteed to be called by MSBuild at the end of the build, after all 
		/// events have been raised.
		/// </summary>
		public override void Shutdown()
		{
			// Done logging, let go of the file
			streamWriter.Close();
		}

		private StreamWriter streamWriter;
		private int indent;
	}
}

Community Additions

ADD
Show:
© 2014 Microsoft