|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// THE ASSEMBLY BUILT FROM THIS SOURCE FILE HAS BEEN DEPRECATED FOR YEARS. IT IS BUILT ONLY TO PROVIDE
// BACKWARD COMPATIBILITY FOR API USERS WHO HAVE NOT YET MOVED TO UPDATED APIS. PLEASE DO NOT SEND PULL
// REQUESTS THAT CHANGE THIS FILE WITHOUT FIRST CHECKING WITH THE MAINTAINERS THAT THE FIX IS REQUIRED.
using System;
using System.IO;
using System.Collections;
using System.Threading;
using Microsoft.Build.Framework;
using Microsoft.Build.BuildEngine.Shared;
namespace Microsoft.Build.BuildEngine
{
/// <summary>
/// This abstract class defines a logging service, provided by the engine, for internal logging
/// purposes. This class is very different from the IBuildEngine interface which provides logging
/// services for tasks. This class allows for better encapsulation by making it clear when only
/// logging services are needed and not the rest of the engine data and methods. This class allows
/// us to provide different implementations of logging services for engine components that are
/// either in-proc (engine process) or out-of-proc (node process).
/// </summary>
/// <remarks>
/// We have made this an abstract class and not an interface to avoid forcing the "public" access
/// modifier on the implementation of the internal logging services.
/// </remarks>
internal abstract class EngineLoggingServices
{
#region Initialization/Shutdown methods
/// <summary>
/// Initializes the base class data. Sub-classes must call this method in their constructor.
/// </summary>
protected void Initialize(ManualResetEvent flushRequestEventIn)
{
this.loggingQueueOfBuildEvents = new DualQueue<BuildEventArgs>();
this.loggingQueueOfNodeEvents = new DualQueue<NodeLoggingEvent>();
this.lastFlushTime = DateTime.Now.Ticks;
this.flushRequestEvent = flushRequestEventIn;
this.requestedQueueFlush = false;
}
/// <summary>
/// Causes all events to be discarded until EndEatingEvents() is called.
/// </summary>
internal void BeginEatingEvents()
{
paused = true;
}
/// <summary>
/// Ensures events are no longer discarded if BeginEatingEvents() had been called.
/// </summary>
internal void EndEatingEvents()
{
paused = false;
}
/// <summary>
/// Shutdown the logging service as appropriate
/// </summary>
internal virtual void Shutdown()
{
ErrorUtilities.VerifyThrow(false, "This method should be defined in a subclass");
}
#endregion
#region Queue handling methods
/// <summary>
/// Called to add a logging event to the posting queue.
/// </summary>
/// <param name="e"></param>
internal void PostLoggingEvent(BuildEventArgs e)
{
ErrorUtilities.VerifyThrowArgumentNull(e, nameof(e));
if (paused)
{
// Throw out the event
return;
}
if (flushBuildEventsImmediatly)
{
ProcessBuildEvent(e);
}
else
{
// queue the event
loggingQueueOfBuildEvents.Enqueue(e);
if (!requestedQueueFlush && loggingQueueOfBuildEvents.WritingQueueCount > flushQueueSize)
{
requestedQueueFlush = true;
flushRequestEvent.Set();
}
}
}
/// <summary>
/// Called to add logging events to the posting queue.
/// </summary>
/// <param name="eventArray"></param>
internal void PostLoggingEvents(BuildEventArgs[] eventArray)
{
ErrorUtilities.VerifyThrowArgumentNull(eventArray, nameof(eventArray));
if (paused)
{
// Throw out the event
return;
}
if (flushBuildEventsImmediatly)
{
for (int i = 0; i < eventArray.Length; i++)
{
ProcessBuildEvent(eventArray[i]);
}
}
else
{
loggingQueueOfBuildEvents.EnqueueArray(eventArray);
if (!requestedQueueFlush && loggingQueueOfBuildEvents.WritingQueueCount > flushQueueSize)
{
requestedQueueFlush = true;
flushRequestEvent.Set();
}
}
}
/// <summary>
/// Called to add a logging event to the posting queue.
/// </summary>
/// <param name="e"></param>
internal void PostLoggingEvent(NodeLoggingEvent e)
{
ErrorUtilities.VerifyThrowArgumentNull(e, nameof(e));
if (paused)
{
// Throw out the event
return;
}
// queue the event
loggingQueueOfNodeEvents.Enqueue(e);
if (!requestedQueueFlush && loggingQueueOfNodeEvents.WritingQueueCount > flushQueueSize)
{
requestedQueueFlush = true;
flushRequestEvent.Set();
}
}
/// <summary>
/// Called to add logging events to the posting queue.
/// </summary>
/// <param name="eventArray"></param>
internal void PostLoggingEvents(NodeLoggingEvent[] eventArray)
{
ErrorUtilities.VerifyThrowArgumentNull(eventArray, nameof(eventArray));
if (paused)
{
// Throw out the event
return;
}
loggingQueueOfNodeEvents.EnqueueArray(eventArray);
if (!requestedQueueFlush && loggingQueueOfNodeEvents.WritingQueueCount > flushQueueSize)
{
requestedQueueFlush = true;
flushRequestEvent.Set();
}
}
/// <summary>
/// Abstract method that must be implemented for either in-proc or out-of-proc logging.
/// This method is called to process the events batched up in the reading queue. In the
/// in-proc implementation, this will actually raise the events. In the out-of-proc
/// implementation, this will send the events from the node process to the engine.
/// </summary>
internal abstract bool ProcessPostedLoggingEvents();
/// <summary>
/// This method is to process a single build event, by default if this method is used
/// the event should be posted and processed
/// </summary>
/// <param name="buildEventArgs"></param>
internal virtual void ProcessBuildEvent(BuildEventArgs buildEventArgs)
{
PostLoggingEvent(buildEventArgs);
ProcessPostedLoggingEvents();
}
/// <summary>
/// Return true if the queue needs to be flushed
/// </summary>
internal virtual bool NeedsFlush(long currentTickCount)
{
if (this.flushBuildEventsImmediatly)
{
return false;
}
// Force a flush if there are many events accumulated
if (loggingQueueOfBuildEvents.Count > flushQueueSize || loggingQueueOfNodeEvents.Count > flushQueueSize)
{
return true;
}
// Only obtain current time if the timestamp is not passed in to avoid extra native calls
if (currentTickCount == 0)
{
currentTickCount = DateTime.Now.Ticks;
}
return (currentTickCount - lastFlushTime) > flushTimeoutInTicks;
}
#endregion
#region Event based logging methods
/// <summary>
/// Raises ErrorEvent and AnyEvent at all registered loggers.
/// </summary>
/// <owner>t-jeffv, SumedhK</owner>
/// <param name="e"></param>
internal void LogErrorEvent(BuildErrorEventArgs e)
{
// We are intentionally passing in null for the "sender" object, even when
// the event is coming from task. This is because we don't want to allow
// tight coupling between loggers and tasks in this way. It's not good
// for a logger to be able to call back into a task. It could be a security
// issue, and it apparently will also cause us problems if we adopt the
// new Longhorn Add-In Programming Model.
ErrorUtilities.VerifyThrowArgumentNull(e, nameof(e));
PostLoggingEvent(e);
}
/// <summary>
/// Raises MessageEvent and AnyEvent at all registered loggers.
/// </summary>
/// <owner>t-jeffv, SumedhK</owner>
/// <param name="e"></param>
internal void LogMessageEvent(BuildMessageEventArgs e)
{
if (!OnlyLogCriticalEvents)
{
// We are intentionally passing in null for the "sender" object, even when
// the event is coming from task. This is because we don't want to allow
// tight coupling between loggers and tasks in this way. It's not good
// for a logger to be able to call back into a task. It could be a security
// issue, and it apparently will also cause us problems if we adopt the
// new Longhorn Add-In Programming Model.
ErrorUtilities.VerifyThrowArgumentNull(e, nameof(e));
PostLoggingEvent(e);
}
}
/// <summary>
/// Raises WarningEvent and AnyEvent at all registered loggers.
/// </summary>
/// <owner>t-jeffv, SumedhK</owner>
/// <param name="e"></param>
internal void LogWarningEvent(BuildWarningEventArgs e)
{
// We are intentionally passing in null for the "sender" object, even when
// the event is coming from task. This is because we don't want to allow
// tight coupling between loggers and tasks in this way. It's not good
// for a logger to be able to call back into a task. It could be a security
// issue, and it apparently will also cause us problems if we adopt the
// new Longhorn Add-In Programming Model.
ErrorUtilities.VerifyThrowArgumentNull(e, nameof(e));
PostLoggingEvent(e);
}
/// <summary>
/// Raises CustomEvent and AnyEvent at all registered loggers.
/// </summary>
/// <owner>t-jeffv, SumedhK</owner>
/// <param name="e"></param>
internal void LogCustomEvent(CustomBuildEventArgs e)
{
// We are intentionally passing in null for the "sender" object, even when
// the event is coming from task. This is because we don't want to allow
// tight coupling between loggers and tasks in this way. It's not good
// for a logger to be able to call back into a task. It could be a security
// issue, and it apparently will also cause us problems if we adopt the
// new Longhorn Add-In Programming Model.
ErrorUtilities.VerifyThrowArgumentNull(e, nameof(e));
PostLoggingEvent(e);
}
#endregion
#region Log comments
/**************************************************************************************************************************
* WARNING: Do not add overloads that allow raising events without specifying a file. In general ALL events should have a
* file associated with them. We've received a LOT of feedback from dogfooders about the lack of information in our
* events. If an event TRULY does not have an associated file, then String.Empty can be passed in for the file. However,
* that burden should lie on the caller -- these wrapper methods should NOT make it easy to skip the filename.
*************************************************************************************************************************/
/// <summary>
/// Logs a low-priority comment with all registered loggers using the specified resource string.
/// </summary>
internal virtual void LogComment(BuildEventContext buildEventContext, string messageResourceName, params object[] messageArgs)
{
if (!OnlyLogCriticalEvents)
{
LogComment(buildEventContext, MessageImportance.Low, messageResourceName, messageArgs);
}
}
/// <summary>
/// Logs a custom-priority comment with all registered loggers using the specified resource string.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="importance"></param>
/// <param name="messageResourceName"></param>
/// <param name="messageArgs"></param>
internal virtual void LogComment(BuildEventContext buildEventContext, MessageImportance importance, string messageResourceName, params object[] messageArgs)
{
if (!OnlyLogCriticalEvents)
{
ErrorUtilities.VerifyThrow(messageResourceName != null, "Need resource string for comment message.");
LogCommentFromText(buildEventContext, importance, ResourceUtilities.FormatResourceString(messageResourceName, messageArgs));
}
}
/// <summary>
/// Logs a custom-priority comment with all registered loggers using the given text.
/// </summary>
internal virtual void LogCommentFromText(BuildEventContext buildEventContext, MessageImportance importance, string message)
{
if (!OnlyLogCriticalEvents)
{
ErrorUtilities.VerifyThrow(message != null,
"Need comment message.");
BuildMessageEventArgs e = new BuildMessageEventArgs
(
message,
null,
"MSBuild",
importance
);
e.BuildEventContext = buildEventContext;
PostLoggingEvent(e);
}
}
#endregion
#region Log errors
/**************************************************************************************************************************
* WARNING: Do not add overloads that allow raising events without specifying a file. In general ALL events should have a
* file associated with them. We've received a LOT of feedback from dogfooders about the lack of information in our
* events. If an event TRULY does not have an associated file, then String.Empty can be passed in for the file. However,
* that burden should lie on the caller -- these wrapper methods should NOT make it easy to skip the filename.
*************************************************************************************************************************/
/// <summary>
/// Logs an error with all registered loggers using the specified resource string.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="file"></param>
/// <param name="messageResourceName"></param>
/// <param name="messageArgs"></param>
internal virtual void LogError(BuildEventContext location, BuildEventFileInfo file, string messageResourceName, params object[] messageArgs)
{
LogError(location, null, file, messageResourceName, messageArgs);
}
/// <summary>
/// Logs an error with all registered loggers using the specified resource string.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="subcategoryResourceName">Can be null.</param>
/// <param name="file"></param>
/// <param name="messageResourceName"></param>
/// <param name="messageArgs"></param>
internal virtual void LogError(BuildEventContext buildEventContext, string subcategoryResourceName, BuildEventFileInfo file, string messageResourceName, params object[] messageArgs)
{
ErrorUtilities.VerifyThrow(messageResourceName != null, "Need resource string for error message.");
string errorCode;
string helpKeyword;
string message = ResourceUtilities.FormatResourceString(out errorCode, out helpKeyword, messageResourceName, messageArgs);
LogErrorFromText(buildEventContext, subcategoryResourceName, errorCode, helpKeyword, file, message);
}
/// <summary>
/// Logs an error with all registered loggers using the given text.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="subcategoryResourceName">Can be null.</param>
/// <param name="errorCode">Can be null.</param>
/// <param name="helpKeyword">Can be null.</param>
/// <param name="file"></param>
/// <param name="message"></param>
internal virtual void LogErrorFromText(BuildEventContext buildEventContext, string subcategoryResourceName, string errorCode, string helpKeyword, BuildEventFileInfo file, string message)
{
ErrorUtilities.VerifyThrow(file != null, "Must specify the associated file.");
ErrorUtilities.VerifyThrow(message != null, "Need error message.");
string subcategory = null;
if (subcategoryResourceName != null)
{
subcategory = AssemblyResources.GetString(subcategoryResourceName);
}
BuildErrorEventArgs e =
new BuildErrorEventArgs
(
subcategory,
errorCode,
file.File,
file.Line,
file.Column,
file.EndLine,
file.EndColumn,
message,
helpKeyword,
"MSBuild"
);
e.BuildEventContext = buildEventContext;
PostLoggingEvent(e);
}
/// <summary>
/// Logs an error regarding an invalid project file with all registered loggers.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="invalidProjectFileException"></param>
internal virtual void LogInvalidProjectFileError(BuildEventContext buildEventContext, InvalidProjectFileException invalidProjectFileException)
{
ErrorUtilities.VerifyThrow(invalidProjectFileException != null, "Need exception context.");
// Don't log the exception more than once.
if (!invalidProjectFileException.HasBeenLogged)
{
BuildErrorEventArgs e =
new BuildErrorEventArgs
(
invalidProjectFileException.ErrorSubcategory,
invalidProjectFileException.ErrorCode,
invalidProjectFileException.ProjectFile,
invalidProjectFileException.LineNumber,
invalidProjectFileException.ColumnNumber,
invalidProjectFileException.EndLineNumber,
invalidProjectFileException.EndColumnNumber,
invalidProjectFileException.BaseMessage,
invalidProjectFileException.HelpKeyword,
"MSBuild"
);
e.BuildEventContext = buildEventContext;
PostLoggingEvent(e);
invalidProjectFileException.HasBeenLogged = true;
}
}
/// <summary>
/// Logs an error regarding an unexpected build failure with all registered loggers.
/// This will include a stack dump.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="exception"></param>
/// <param name="file"></param>
internal virtual void LogFatalBuildError(BuildEventContext buildEventContext, Exception exception, BuildEventFileInfo file)
{
LogFatalError(buildEventContext, exception, file, "FatalBuildError");
}
/// <summary>
/// Logs an error regarding an unexpected task failure with all registered loggers.
/// This will include a stack dump.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="exception"></param>
/// <param name="file"></param>
/// <param name="taskName"></param>
internal virtual void LogFatalTaskError(BuildEventContext buildEventContext, Exception exception, BuildEventFileInfo file, string taskName)
{
ErrorUtilities.VerifyThrow(taskName != null, "Must specify the name of the task that failed.");
LogFatalError(buildEventContext, exception, file, "FatalTaskError", taskName);
}
/// <summary>
/// Logs an error regarding an unexpected failure with all registered loggers using the specified resource string.
/// This will include a stack dump.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="exception"></param>
/// <param name="file"></param>
/// <param name="messageResourceName"></param>
/// <param name="messageArgs"></param>
internal virtual void LogFatalError(BuildEventContext buildEventContext, Exception exception, BuildEventFileInfo file, string messageResourceName, params object[] messageArgs)
{
ErrorUtilities.VerifyThrow(messageResourceName != null, "Need resource string for error message.");
string errorCode;
string helpKeyword;
string message = ResourceUtilities.FormatResourceString(out errorCode, out helpKeyword, messageResourceName, messageArgs);
#if DEBUG
message += Environment.NewLine + "This is an unhandled exception -- PLEASE OPEN A BUG.";
#endif
if (exception != null)
{
message += Environment.NewLine + exception.ToString();
}
LogErrorFromText(buildEventContext, null, errorCode, helpKeyword, file, message);
}
#endregion
#region Log warnings
/**************************************************************************************************************************
* WARNING: Do not add overloads that allow raising events without specifying a file. In general ALL events should have a
* file associated with them. We've received a LOT of feedback from dogfooders about the lack of information in our
* events. If an event TRULY does not have an associated file, then String.Empty can be passed in for the file. However,
* that burden should lie on the caller -- these wrapper methods should NOT make it easy to skip the filename.
*************************************************************************************************************************/
/// <summary>
/// Logs an warning regarding an unexpected task failure with all registered loggers.
/// This will include a stack dump.
/// </summary>
/// <owner>RGoel</owner>
/// <param name="exception"></param>
/// <param name="file"></param>
/// <param name="taskName"></param>
internal virtual void LogTaskWarningFromException(BuildEventContext buildEventContext, Exception exception, BuildEventFileInfo file, string taskName)
{
ErrorUtilities.VerifyThrow(taskName != null, "Must specify the name of the task that failed.");
ErrorUtilities.VerifyThrow(file != null, "Must specify the associated file.");
string warningCode;
string helpKeyword;
string message = ResourceUtilities.FormatResourceString(out warningCode, out helpKeyword, "FatalTaskError", taskName);
#if DEBUG
message += Environment.NewLine + "This is an unhandled exception -- PLEASE OPEN A BUG.";
#endif
if (exception != null)
{
message += Environment.NewLine + exception.ToString();
}
LogWarningFromText(buildEventContext, null, warningCode, helpKeyword, file, message);
}
/// <summary>
/// Logs a warning with all registered loggers using the specified resource string.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="file"></param>
/// <param name="messageResourceName"></param>
/// <param name="messageArgs"></param>
internal virtual void LogWarning(BuildEventContext buildEventContext, BuildEventFileInfo file, string messageResourceName, params object[] messageArgs)
{
LogWarning(buildEventContext, null, file, messageResourceName, messageArgs);
}
/// <summary>
/// Logs a warning with all registered loggers using the specified resource string.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="subcategoryResourceName">Can be null.</param>
/// <param name="file"></param>
/// <param name="messageResourceName"></param>
/// <param name="messageArgs"></param>
internal virtual void LogWarning(BuildEventContext buildEventContext, string subcategoryResourceName, BuildEventFileInfo file, string messageResourceName, params object[] messageArgs)
{
ErrorUtilities.VerifyThrow(messageResourceName != null, "Need resource string for warning message.");
string warningCode;
string helpKeyword;
string message = ResourceUtilities.FormatResourceString(out warningCode, out helpKeyword, messageResourceName, messageArgs);
LogWarningFromText(buildEventContext, subcategoryResourceName, warningCode, helpKeyword, file, message);
}
/// <summary>
/// Logs a warning with all registered loggers using the given text.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="subcategoryResourceName">Can be null.</param>
/// <param name="warningCode">Can be null.</param>
/// <param name="helpKeyword">Can be null.</param>
/// <param name="file"></param>
/// <param name="message"></param>
internal virtual void LogWarningFromText(BuildEventContext buildEventContext, string subcategoryResourceName, string warningCode, string helpKeyword, BuildEventFileInfo file, string message)
{
ErrorUtilities.VerifyThrow(file != null, "Must specify the associated file.");
ErrorUtilities.VerifyThrow(message != null, "Need warning message.");
string subcategory = null;
if (subcategoryResourceName != null)
{
subcategory = AssemblyResources.GetString(subcategoryResourceName);
}
BuildWarningEventArgs e = new BuildWarningEventArgs
(
subcategory,
warningCode,
file.File,
file.Line,
file.Column,
file.EndLine,
file.EndColumn,
message,
helpKeyword,
"MSBuild"
);
e.BuildEventContext = buildEventContext;
PostLoggingEvent(e);
}
#endregion
#region Log status
/**************************************************************************************************************************
* WARNING: Do not add overloads that allow raising events without specifying a file. In general ALL events should have a
* file associated with them. We've received a LOT of feedback from dogfooders about the lack of information in our
* events. If an event TRULY does not have an associated file, then String.Empty can be passed in for the file. However,
* that burden should lie on the caller -- these wrapper methods should NOT make it easy to skip the filename.
*************************************************************************************************************************/
/// <summary>
/// Logs that the build has started with all loggers (only called on the main node)
/// </summary>
internal virtual void LogBuildStarted()
{
// If we're only logging critical events, don't risk causing all the resources to load by formatting
// a string that won't get emitted anyway.
string message = String.Empty;
if (!OnlyLogCriticalEvents)
{
message = ResourceUtilities.FormatResourceString("BuildStarted");
}
BuildStartedEventArgs e = new BuildStartedEventArgs(message, null /* no help keyword */);
PostLoggingEvent(e);
// Wrap the event to be sent to central loggers
NodeLoggingEventWithLoggerId nodeEventToCentralLoggers =
new NodeLoggingEventWithLoggerId(e, EngineLoggingServicesInProc.ALL_PRIVATE_EVENTSOURCES);
PostLoggingEvent(nodeEventToCentralLoggers);
}
/// <summary>
/// Logs that the build has finished with all loggers, except for forwarding loggers.
/// </summary>
internal virtual void LogBuildStarted(int loggerId)
{
// If we're only logging critical events, don't risk causing all the resources to load by formatting
// a string that won't get emitted anyway.
string message = String.Empty;
if (!OnlyLogCriticalEvents)
{
message = ResourceUtilities.FormatResourceString("BuildStarted");
}
BuildStartedEventArgs e = new BuildStartedEventArgs(message, null /* no help keyword */);
// Wrap the BuildStarted event so it is only sent to the loggers to
// the specified logger id
NodeLoggingEventWithLoggerId nodeEvent =
new NodeLoggingEventWithLoggerId(e, loggerId);
PostLoggingEvent(nodeEvent);
}
/// <summary>
/// Logs that the build has finished with all registered loggers.
/// </summary>
/// <param name="success"></param>
internal virtual void LogBuildFinished(bool success)
{
// If we're only logging critical events, don't risk causing all the resources to load by formatting
// a string that won't get emitted anyway.
string message = String.Empty;
if (!OnlyLogCriticalEvents)
{
message = ResourceUtilities.FormatResourceString(success ? "BuildFinishedSuccess" : "BuildFinishedFailure");
}
BuildFinishedEventArgs e = new BuildFinishedEventArgs(message, null /* no help keyword */, success);
PostLoggingEvent(e);
}
/// <summary>
/// Logs that the build has finished to a particular logger Id
/// </summary>
internal virtual void LogBuildFinished(bool success, int loggerId)
{
// If we're only logging critical events, don't risk causing all the resources to load by formatting
// a string that won't get emitted anyway.
string message = String.Empty;
if (!OnlyLogCriticalEvents)
{
message = ResourceUtilities.FormatResourceString(success ? "BuildFinishedSuccess" : "BuildFinishedFailure");
}
BuildFinishedEventArgs e = new BuildFinishedEventArgs(message, null /* no help keyword */, success);
// Wrap the BuildFinished event so it is only sent to the loggers to
// the specified logger id
NodeLoggingEventWithLoggerId nodeEvent =
new NodeLoggingEventWithLoggerId(e, loggerId);
PostLoggingEvent(nodeEvent);
}
/// <summary>
/// Logs that a project build has started with all registered loggers.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="projectFile">project file</param>
/// <param name="targetNames">target names</param>
/// <param name="properties">properties list</param>
/// <param name="items">items list</param>
internal virtual void LogProjectStarted(int projectId, BuildEventContext parentBuildEventContext, BuildEventContext projectBuildEventContext, string projectFile, string targetNames, IEnumerable properties, IEnumerable items)
{
if (!OnlyLogCriticalEvents)
{
ProjectStartedEventArgs e;
if (!string.IsNullOrEmpty(targetNames))
{
e = new ProjectStartedEventArgs
(
projectId,
ResourceUtilities.FormatResourceString("ProjectStartedPrefixForTopLevelProjectWithTargetNames", Path.GetFileName(projectFile), targetNames),
null, // no help keyword
projectFile,
targetNames,
properties,
items,
parentBuildEventContext
);
}
else
{
e = new ProjectStartedEventArgs
(
projectId,
ResourceUtilities.FormatResourceString("ProjectStartedPrefixForTopLevelProjectWithDefaultTargets", Path.GetFileName(projectFile)),
null, // no help keyword
projectFile,
targetNames,
properties,
items,
parentBuildEventContext
);
}
e.BuildEventContext = projectBuildEventContext;
PostLoggingEvent(e);
}
}
/// <summary>
/// Logs that a project build has finished with all registered loggers.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="projectFile"></param>
/// <param name="success"></param>
internal virtual void LogProjectFinished(BuildEventContext buildEventContext, string projectFile, bool success)
{
if (!OnlyLogCriticalEvents)
{
string message = ResourceUtilities.FormatResourceString(success ? "ProjectFinishedSuccess" : "ProjectFinishedFailure", Path.GetFileName(projectFile));
ProjectFinishedEventArgs e = new ProjectFinishedEventArgs
(
message,
null, // no help keyword
projectFile,
success
);
e.BuildEventContext = buildEventContext;
PostLoggingEvent(e);
}
}
/// <summary>
/// Logs that a target build has started with all registered loggers.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="targetName">Name of target</param>
/// <param name="projectFile">Main project file</param>
/// <param name="projectFileOfTargetElement">Project file actually containing the target</param>
internal virtual void LogTargetStarted(BuildEventContext buildEventContext, string targetName, string projectFile, string projectFileOfTargetElement)
{
if (!OnlyLogCriticalEvents)
{
TargetStartedEventArgs e = new TargetStartedEventArgs
(
ResourceUtilities.FormatResourceString("TargetStarted", targetName, Path.GetFileName(projectFile)),
null, // no help keyword
targetName,
projectFile,
projectFileOfTargetElement
);
e.BuildEventContext = buildEventContext;
PostLoggingEvent(e);
}
}
/// <summary>
/// Logs that a target build has finished with all registered loggers.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="targetName"></param>
/// <param name="projectFile">main project file</param>
/// <param name="success"></param>
/// <param name="projectFileOfTargetElement">project file actually containing the target</param>
internal virtual void LogTargetFinished(BuildEventContext buildEventContext, string targetName, string projectFile, string projectFileOfTargetElement, bool success)
{
if (!OnlyLogCriticalEvents)
{
string message = ResourceUtilities.FormatResourceString(success ? "TargetFinishedSuccess" : "TargetFinishedFailure", targetName, Path.GetFileName(projectFile));
TargetFinishedEventArgs e = new TargetFinishedEventArgs
(
message,
null, // no help keyword
targetName,
projectFile,
projectFileOfTargetElement,
success
);
e.BuildEventContext = buildEventContext;
PostLoggingEvent(e);
}
}
/// <summary>
/// Logs that task execution has started with all registered loggers.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="taskName"></param>
/// <param name="projectFile"></param>
/// <param name="projectFileOfTaskNode">project file actually containing the task</param>
internal virtual void LogTaskStarted(BuildEventContext buildEventContext, string taskName, string projectFile, string projectFileOfTaskNode)
{
if (!OnlyLogCriticalEvents)
{
TaskStartedEventArgs e = new TaskStartedEventArgs
(
ResourceUtilities.FormatResourceString("TaskStarted", taskName),
null, // no help keyword
projectFile,
projectFileOfTaskNode,
taskName
);
e.BuildEventContext = buildEventContext;
PostLoggingEvent(e);
}
}
/// <summary>
/// Logs that a task has finished executing with all registered loggers.
/// </summary>
/// <owner>SumedhK</owner>
/// <param name="taskName"></param>
/// <param name="projectFile"></param>
/// <param name="success"></param>
/// <param name="projectFileOfTaskNode">project file actually containing the task</param>
internal virtual void LogTaskFinished(BuildEventContext buildEventContext, string taskName, string projectFile, string projectFileOfTaskNode, bool success)
{
if (!OnlyLogCriticalEvents)
{
string message = ResourceUtilities.FormatResourceString(success ? "TaskFinishedSuccess" : "TaskFinishedFailure", taskName);
TaskFinishedEventArgs e = new TaskFinishedEventArgs
(
message,
null, // no help keyword
projectFile,
projectFileOfTaskNode,
taskName,
success
);
e.BuildEventContext = buildEventContext;
PostLoggingEvent(e);
}
}
#endregion
#region Properties
/// <summary>
/// When true, only log critical events such as warnings and errors.
/// </summary>
internal bool OnlyLogCriticalEvents
{
get
{
return this.onlyLogCriticalEvents;
}
set
{
this.onlyLogCriticalEvents = value;
}
}
/// <summary>
/// Chains another logging service to this service. All incoming local events will be forwarded to the
/// chained logging service.
/// </summary>
internal EngineLoggingServices ForwardingService
{
get
{
return this.forwardingService;
}
set
{
this.forwardingService = value;
}
}
/// <summary>
/// When true, only log critical events such as warnings and errors.
/// </summary>
internal bool FlushBuildEventsImmediatly
{
get
{
return this.flushBuildEventsImmediatly;
}
set
{
this.flushBuildEventsImmediatly = value;
}
}
#endregion
#region Data
/// <summary>
/// Use to optimize away status messages. When this is set to true, only "critical"
/// events like errors are logged.
/// </summary>
protected bool onlyLogCriticalEvents;
/// <summary>
/// Whether the loggers are paused. If they are paused, we do not pass on any events.
/// </summary>
private bool paused;
// In single proc we would like to flush the logging events right away rather than queueing them up
// we only want to flush build events because node events can only come in a multi proc build
protected bool flushBuildEventsImmediatly;
/// <summary>
/// A dual queue which allows for one reader multiple writer access (used to receive events from
/// the engine and the TEM)
/// </summary>
protected DualQueue<BuildEventArgs> loggingQueueOfBuildEvents;
/// <summary>
/// A dual queue which allows for one reader multiple writer access (used to receive events from
/// other nodes)
/// </summary>
protected DualQueue<NodeLoggingEvent> loggingQueueOfNodeEvents;
/// <summary>
/// If there are multiple logging services hooked up to an engine, the events
/// are forwarding from the top service down.
/// </summary>
protected EngineLoggingServices forwardingService;
/// <summary>
/// Last timestamp when the queue was flushed
/// </summary>
protected long lastFlushTime;
/// <summary>
/// If the number of items in the queue goes up in a spike the logging service can request
/// a flush of the queue. To prevent multiple requests this flag is used to indicate that
/// flush request has already been posted
/// </summary>
protected bool requestedQueueFlush;
/// <summary>
/// An event used to request a flush of the logging service. Typically triggered due to
/// a spike in logging activity.
/// </summary>
protected ManualResetEvent flushRequestEvent;
internal const int flushTimeoutInMS = 500; // flush the queue at least every 1/2 second
internal const int flushTimeoutInTicks = 500 * 10000; // flush the queue at least every 1/2 second
internal const int flushQueueSize = 1000; // flush the queue every time 1000 events accumulate
#endregion
}
}
|