1125 lines
46 KiB
C#
1125 lines
46 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Globalization;
|
|
using System.Management.Automation.Runspaces;
|
|
using System.Management.Automation.Tracing;
|
|
using System.Security;
|
|
using System.Threading;
|
|
|
|
namespace System.Management.Automation
|
|
{
|
|
/// <summary>
|
|
/// Monad Logging in general is a two layer architecture. At the upper layer are the
|
|
/// Msh Log Engine and Logging Api. At the lower layer is the Provider Interface
|
|
/// and Log Providers. This architecture is adopted to achieve independency between
|
|
/// Monad logging and logging details of different logging technology.
|
|
///
|
|
/// This file implements the upper layer of the Monad Logging architecture.
|
|
/// Lower layer of Msh Log architecture is implemented in LogProvider.cs file.
|
|
///
|
|
/// Logging Api is made up of following five sets
|
|
/// 1. Engine Health Event
|
|
/// 2. Engine Lifecycle Event
|
|
/// 3. Command Lifecycle Event
|
|
/// 4. Provider Lifecycle Event
|
|
/// 5. Settings Event
|
|
///
|
|
/// Msh Log Engine provides features in following areas,
|
|
/// 1. Loading and managing logging providers. Based on some "Provider Catalog", engine will try to
|
|
/// load providers. First provider that is successfully loaded will be used for low level logging.
|
|
/// If no providers can be loaded, a dummy provider will be used, which will essentially do nothing.
|
|
/// 2. Implementation of logging api functions. These api functions is implemented by calling corresponding
|
|
/// functions in provider interface.
|
|
/// 3. Sequence Id Generation. Unique id are generated in this class. These id's will be attached to events.
|
|
/// 4. Monad engine state management. Engine state is stored in ExecutionContext class but managed here.
|
|
/// Later on, this feature may be moved to engine itself (where it should belongs to) when sophisticated
|
|
/// engine state model is established.
|
|
/// 5. Logging policy support. Events are logged or not logged based on logging policy settings (which is stored
|
|
/// in session state of the engine.
|
|
///
|
|
/// MshLog class is defined as a static class. This essentially make the logging api to be a static api.
|
|
///
|
|
/// We want to provide sufficient synchronization for static functions calls.
|
|
/// This is not needed for now because of following two reasons,
|
|
/// a. Currently, only one monad engine can be running in one process. So logically only one
|
|
/// event will be log at a time.
|
|
/// b. Even in the case of multiple events are logged, underlining logging media should
|
|
/// provide synchronization.
|
|
/// </summary>
|
|
internal static class MshLog
|
|
{
|
|
#region Initialization
|
|
|
|
/// <summary>
|
|
/// A static dictionary to keep track of log providers for different shellId's.
|
|
///
|
|
/// The value of this dictionary is never empty. A value of type DummyProvider means
|
|
/// no logging.
|
|
/// </summary>
|
|
private static readonly ConcurrentDictionary<string, Collection<LogProvider>> s_logProviders =
|
|
new ConcurrentDictionary<string, Collection<LogProvider>>();
|
|
|
|
private const string _crimsonLogProviderAssemblyName = "MshCrimsonLog";
|
|
private const string _crimsonLogProviderTypeName = "System.Management.Automation.Logging.CrimsonLogProvider";
|
|
|
|
private static readonly Collection<string> s_ignoredCommands = new Collection<string>();
|
|
|
|
/// <summary>
|
|
/// Static constructor.
|
|
/// </summary>
|
|
static MshLog()
|
|
{
|
|
s_ignoredCommands.Add("Out-Lineoutput");
|
|
s_ignoredCommands.Add("Format-Default");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Currently initialization is done in following sequence
|
|
/// a. Try to load CrimsonLogProvider (in the case of Longhorn)
|
|
/// b. If a fails, use the DummyLogProvider instead. (in low-level OS)
|
|
///
|
|
/// In the longer turn, we may need to use a "Provider Catalog" for
|
|
/// log provider loading.
|
|
/// </summary>
|
|
/// <param name="shellId"></param>
|
|
/// <returns></returns>
|
|
private static IEnumerable<LogProvider> GetLogProvider(string shellId)
|
|
{
|
|
return s_logProviders.GetOrAdd(shellId, CreateLogProvider);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get Log Provider based on Execution Context.
|
|
/// </summary>
|
|
/// <param name="executionContext"></param>
|
|
/// <returns></returns>
|
|
private static IEnumerable<LogProvider> GetLogProvider(ExecutionContext executionContext)
|
|
{
|
|
if (executionContext == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(nameof(executionContext));
|
|
}
|
|
|
|
string shellId = executionContext.ShellID;
|
|
|
|
return GetLogProvider(shellId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get Log Provider based on Log Context.
|
|
/// </summary>
|
|
/// <param name="logContext"></param>
|
|
/// <returns></returns>
|
|
private static IEnumerable<LogProvider> GetLogProvider(LogContext logContext)
|
|
{
|
|
System.Diagnostics.Debug.Assert(logContext != null);
|
|
System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(logContext.ShellId));
|
|
|
|
return GetLogProvider(logContext.ShellId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a log provider based on a shell Id.
|
|
/// </summary>
|
|
/// <param name="shellId"></param>
|
|
/// <returns></returns>
|
|
private static Collection<LogProvider> CreateLogProvider(string shellId)
|
|
{
|
|
Collection<LogProvider> providers = new Collection<LogProvider>();
|
|
// Porting note: Linux does not support ETW
|
|
|
|
try
|
|
{
|
|
#if !CORECLR // TODO:CORECLR EventLogLogProvider not handled yet
|
|
LogProvider eventLogLogProvider = new EventLogLogProvider(shellId);
|
|
providers.Add(eventLogLogProvider);
|
|
#endif
|
|
|
|
#if UNIX
|
|
LogProvider sysLogProvider = new PSSysLogProvider();
|
|
providers.Add(sysLogProvider);
|
|
#else
|
|
LogProvider etwLogProvider = new PSEtwLogProvider();
|
|
providers.Add(etwLogProvider);
|
|
#endif
|
|
|
|
return providers;
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
}
|
|
catch (SecurityException)
|
|
{
|
|
// This exception will happen if we try to create an event source
|
|
// (corresponding to the current running minishell)
|
|
// when running as non-admin user. In that case, we will default
|
|
// to dummy log.
|
|
}
|
|
|
|
providers.Add(new DummyLogProvider());
|
|
return providers;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This will set the current log provider to be dummy log.
|
|
/// </summary>
|
|
/// <param name="shellId"></param>
|
|
internal static void SetDummyLog(string shellId)
|
|
{
|
|
Collection<LogProvider> providers = new Collection<LogProvider> { new DummyLogProvider() };
|
|
s_logProviders.AddOrUpdate(shellId, providers, (key, value) => providers);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Engine Health Event Logging Api
|
|
|
|
/// <summary>
|
|
/// LogEngineHealthEvent: Log an engine health event. If engine state is changed, a engine
|
|
/// lifecycle event will be logged also.
|
|
///
|
|
/// This is the basic form of EngineHealthEvent logging api, in which all parameters are provided.
|
|
///
|
|
/// Variant form of this function is defined below, which will make parameters additionalInfo
|
|
/// and newEngineState optional.
|
|
/// </summary>
|
|
/// <param name="executionContext">Execution context for the engine that is running.</param>
|
|
/// <param name="eventId">EventId for the event to be logged.</param>
|
|
/// <param name="exception">Exception associated with this event.</param>
|
|
/// <param name="severity">Severity of this event.</param>
|
|
/// <param name="additionalInfo">Additional information for this event.</param>
|
|
/// <param name="newEngineState">New engine state.</param>
|
|
internal static void LogEngineHealthEvent(ExecutionContext executionContext,
|
|
int eventId,
|
|
Exception exception,
|
|
Severity severity,
|
|
Dictionary<string, string> additionalInfo,
|
|
EngineState newEngineState)
|
|
{
|
|
if (executionContext == null)
|
|
{
|
|
PSTraceSource.NewArgumentNullException(nameof(executionContext));
|
|
return;
|
|
}
|
|
|
|
if (exception == null)
|
|
{
|
|
PSTraceSource.NewArgumentNullException(nameof(exception));
|
|
return;
|
|
}
|
|
|
|
InvocationInfo invocationInfo = null;
|
|
IContainsErrorRecord icer = exception as IContainsErrorRecord;
|
|
if (icer != null && icer.ErrorRecord != null)
|
|
invocationInfo = icer.ErrorRecord.InvocationInfo;
|
|
foreach (LogProvider provider in GetLogProvider(executionContext))
|
|
{
|
|
if (NeedToLogEngineHealthEvent(provider, executionContext))
|
|
{
|
|
provider.LogEngineHealthEvent(GetLogContext(executionContext, invocationInfo, severity), eventId, exception, additionalInfo);
|
|
}
|
|
}
|
|
|
|
if (newEngineState != EngineState.None)
|
|
{
|
|
LogEngineLifecycleEvent(executionContext, newEngineState, invocationInfo);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is a variation of LogEngineHealthEvent api to make additionalInfo and newEngineState
|
|
/// optional.
|
|
/// </summary>
|
|
/// <param name="executionContext"></param>
|
|
/// <param name="eventId"></param>
|
|
/// <param name="exception"></param>
|
|
/// <param name="severity"></param>
|
|
internal static void LogEngineHealthEvent(ExecutionContext executionContext,
|
|
int eventId,
|
|
Exception exception,
|
|
Severity severity)
|
|
{
|
|
LogEngineHealthEvent(executionContext, eventId, exception, severity, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is a variation of LogEngineHealthEvent api to make eventid, additionalInfo and newEngineState
|
|
/// optional.
|
|
///
|
|
/// A default event id for engine health event will be used.
|
|
/// </summary>
|
|
/// <param name="executionContext"></param>
|
|
/// <param name="exception"></param>
|
|
/// <param name="severity"></param>
|
|
internal static void LogEngineHealthEvent(ExecutionContext executionContext,
|
|
Exception exception,
|
|
Severity severity)
|
|
{
|
|
LogEngineHealthEvent(executionContext, 100, exception, severity, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is a variation of LogEngineHealthEvent api to make newEngineState
|
|
/// optional.
|
|
/// </summary>
|
|
/// <param name="executionContext"></param>
|
|
/// <param name="eventId"></param>
|
|
/// <param name="exception"></param>
|
|
/// <param name="severity"></param>
|
|
/// <param name="additionalInfo"></param>
|
|
internal static void LogEngineHealthEvent(ExecutionContext executionContext,
|
|
int eventId,
|
|
Exception exception,
|
|
Severity severity,
|
|
Dictionary<string, string> additionalInfo)
|
|
{
|
|
LogEngineHealthEvent(executionContext, eventId, exception, severity, additionalInfo, EngineState.None);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is a variation of LogEngineHealthEvent api to make additionalInfo
|
|
/// optional.
|
|
/// </summary>
|
|
/// <param name="executionContext"></param>
|
|
/// <param name="eventId"></param>
|
|
/// <param name="exception"></param>
|
|
/// <param name="severity"></param>
|
|
/// <param name="newEngineState"></param>
|
|
internal static void LogEngineHealthEvent(ExecutionContext executionContext,
|
|
int eventId,
|
|
Exception exception,
|
|
Severity severity,
|
|
EngineState newEngineState)
|
|
{
|
|
LogEngineHealthEvent(executionContext, eventId, exception, severity, null, newEngineState);
|
|
}
|
|
|
|
/// <summary>
|
|
/// LogEngineHealthEvent: This is an API for logging engine health event while execution context
|
|
/// is not available. In this case, caller of this API will directly construct LogContext
|
|
/// instance.
|
|
///
|
|
/// This API is currently used only by runspace before engine start.
|
|
/// </summary>
|
|
/// <param name="logContext">LogContext to be.</param>
|
|
/// <param name="eventId">EventId for the event to be logged.</param>
|
|
/// <param name="exception">Exception associated with this event.</param>
|
|
/// <param name="additionalInfo">Additional information for this event.</param>
|
|
internal static void LogEngineHealthEvent(LogContext logContext,
|
|
int eventId,
|
|
Exception exception,
|
|
Dictionary<string, string> additionalInfo
|
|
)
|
|
{
|
|
if (logContext == null)
|
|
{
|
|
PSTraceSource.NewArgumentNullException(nameof(logContext));
|
|
return;
|
|
}
|
|
|
|
if (exception == null)
|
|
{
|
|
PSTraceSource.NewArgumentNullException(nameof(exception));
|
|
return;
|
|
}
|
|
|
|
// Here execution context doesn't exist, we will have to log this event regardless.
|
|
// Don't check NeedToLogEngineHealthEvent here.
|
|
foreach (LogProvider provider in GetLogProvider(logContext))
|
|
{
|
|
provider.LogEngineHealthEvent(logContext, eventId, exception, additionalInfo);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Engine Lifecycle Event Logging Api
|
|
|
|
/// <summary>
|
|
/// LogEngineLifecycleEvent: Log an engine lifecycle event.
|
|
///
|
|
/// This is the basic form of EngineLifecycleEvent logging api, in which all parameters are provided.
|
|
///
|
|
/// Variant form of this function is defined below, which will make parameter additionalInfo
|
|
/// optional.
|
|
/// </summary>
|
|
/// <param name="executionContext">Execution context for current engine instance.</param>
|
|
/// <param name="engineState">New engine state.</param>
|
|
/// <param name="invocationInfo">InvocationInfo for current command that is running.</param>
|
|
internal static void LogEngineLifecycleEvent(ExecutionContext executionContext,
|
|
EngineState engineState,
|
|
InvocationInfo invocationInfo)
|
|
{
|
|
if (executionContext == null)
|
|
{
|
|
PSTraceSource.NewArgumentNullException(nameof(executionContext));
|
|
return;
|
|
}
|
|
|
|
EngineState previousState = GetEngineState(executionContext);
|
|
if (engineState == previousState)
|
|
return;
|
|
|
|
foreach (LogProvider provider in GetLogProvider(executionContext))
|
|
{
|
|
if (NeedToLogEngineLifecycleEvent(provider, executionContext))
|
|
{
|
|
provider.LogEngineLifecycleEvent(GetLogContext(executionContext, invocationInfo), engineState, previousState);
|
|
}
|
|
}
|
|
|
|
SetEngineState(executionContext, engineState);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is a variation of basic LogEngineLifeCycleEvent api which makes invocationInfo
|
|
/// optional.
|
|
/// </summary>
|
|
/// <param name="executionContext"></param>
|
|
/// <param name="engineState"></param>
|
|
internal static void LogEngineLifecycleEvent(ExecutionContext executionContext,
|
|
EngineState engineState)
|
|
{
|
|
LogEngineLifecycleEvent(executionContext, engineState, null);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Command Health Event Logging Api
|
|
|
|
/// <summary>
|
|
/// LogProviderHealthEvent: Log a command health event.
|
|
/// </summary>
|
|
/// <param name="executionContext">Execution context for the engine that is running.</param>
|
|
/// <param name="exception">Exception associated with this event.</param>
|
|
/// <param name="severity">Severity of this event.</param>
|
|
internal static void LogCommandHealthEvent(ExecutionContext executionContext,
|
|
Exception exception,
|
|
Severity severity
|
|
)
|
|
{
|
|
if (executionContext == null)
|
|
{
|
|
PSTraceSource.NewArgumentNullException(nameof(executionContext));
|
|
return;
|
|
}
|
|
|
|
if (exception == null)
|
|
{
|
|
PSTraceSource.NewArgumentNullException(nameof(exception));
|
|
return;
|
|
}
|
|
|
|
InvocationInfo invocationInfo = null;
|
|
IContainsErrorRecord icer = exception as IContainsErrorRecord;
|
|
if (icer != null && icer.ErrorRecord != null)
|
|
invocationInfo = icer.ErrorRecord.InvocationInfo;
|
|
foreach (LogProvider provider in GetLogProvider(executionContext))
|
|
{
|
|
if (NeedToLogCommandHealthEvent(provider, executionContext))
|
|
{
|
|
provider.LogCommandHealthEvent(GetLogContext(executionContext, invocationInfo, severity), exception);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Command Lifecycle Event Logging Api
|
|
|
|
/// <summary>
|
|
/// LogCommandLifecycleEvent: Log a command lifecycle event.
|
|
///
|
|
/// This is the only form of CommandLifecycleEvent logging api.
|
|
/// </summary>
|
|
/// <param name="executionContext">Execution Context for the current running engine.</param>
|
|
/// <param name="commandState">New command state.</param>
|
|
/// <param name="invocationInfo">Invocation data for current command that is running.</param>
|
|
internal static void LogCommandLifecycleEvent(ExecutionContext executionContext,
|
|
CommandState commandState,
|
|
InvocationInfo invocationInfo)
|
|
{
|
|
if (executionContext == null)
|
|
{
|
|
PSTraceSource.NewArgumentNullException(nameof(executionContext));
|
|
return;
|
|
}
|
|
|
|
if (invocationInfo == null)
|
|
{
|
|
PSTraceSource.NewArgumentNullException(nameof(invocationInfo));
|
|
return;
|
|
}
|
|
|
|
if (s_ignoredCommands.Contains(invocationInfo.MyCommand.Name))
|
|
{
|
|
return;
|
|
}
|
|
|
|
LogContext logContext = null;
|
|
foreach (LogProvider provider in GetLogProvider(executionContext))
|
|
{
|
|
if (NeedToLogCommandLifecycleEvent(provider, executionContext))
|
|
{
|
|
provider.LogCommandLifecycleEvent(
|
|
() => logContext ??= GetLogContext(executionContext, invocationInfo),
|
|
commandState);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// LogCommandLifecycleEvent: Log a command lifecycle event.
|
|
///
|
|
/// This is a form of CommandLifecycleEvent which takes a commandName instead
|
|
/// of invocationInfo. It is likely that invocationInfo is not available if
|
|
/// the command failed security check.
|
|
/// </summary>
|
|
/// <param name="executionContext">Execution Context for the current running engine.</param>
|
|
/// <param name="commandState">New command state.</param>
|
|
/// <param name="commandName">Current command that is running.</param>
|
|
internal static void LogCommandLifecycleEvent(ExecutionContext executionContext,
|
|
CommandState commandState,
|
|
string commandName)
|
|
{
|
|
if (executionContext == null)
|
|
{
|
|
PSTraceSource.NewArgumentNullException(nameof(executionContext));
|
|
return;
|
|
}
|
|
|
|
LogContext logContext = null;
|
|
foreach (LogProvider provider in GetLogProvider(executionContext))
|
|
{
|
|
if (NeedToLogCommandLifecycleEvent(provider, executionContext))
|
|
{
|
|
provider.LogCommandLifecycleEvent(
|
|
() =>
|
|
{
|
|
if (logContext == null)
|
|
{
|
|
logContext = GetLogContext(executionContext, null);
|
|
logContext.CommandName = commandName;
|
|
}
|
|
|
|
return logContext;
|
|
}, commandState);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Pipeline Execution Detail Event Logging Api
|
|
|
|
/// <summary>
|
|
/// LogPipelineExecutionDetailEvent: Log a pipeline execution detail event.
|
|
/// </summary>
|
|
/// <param name="executionContext">Execution Context for the current running engine.</param>
|
|
/// <param name="detail">Detail to be logged for this pipeline execution detail.</param>
|
|
/// <param name="invocationInfo">Invocation data for current command that is running.</param>
|
|
internal static void LogPipelineExecutionDetailEvent(ExecutionContext executionContext,
|
|
List<string> detail,
|
|
InvocationInfo invocationInfo)
|
|
|
|
{
|
|
if (executionContext == null)
|
|
{
|
|
PSTraceSource.NewArgumentNullException(nameof(executionContext));
|
|
return;
|
|
}
|
|
|
|
foreach (LogProvider provider in GetLogProvider(executionContext))
|
|
{
|
|
if (NeedToLogPipelineExecutionDetailEvent(provider, executionContext))
|
|
{
|
|
provider.LogPipelineExecutionDetailEvent(GetLogContext(executionContext, invocationInfo), detail);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// LogPipelineExecutionDetailEvent: Log a pipeline execution detail event.
|
|
///
|
|
/// This is a form of PipelineExecutionDetailEvent which takes a scriptName and commandLine
|
|
/// instead of invocationInfo. This will save the need to fill in the commandName for
|
|
/// this event.
|
|
/// </summary>
|
|
/// <param name="executionContext">Execution Context for the current running engine.</param>
|
|
/// <param name="detail">Detail to be logged for this pipeline execution detail.</param>
|
|
/// <param name="scriptName">Script that is currently running.</param>
|
|
/// <param name="commandLine">Command line that is currently running.</param>
|
|
internal static void LogPipelineExecutionDetailEvent(ExecutionContext executionContext,
|
|
List<string> detail,
|
|
string scriptName,
|
|
string commandLine)
|
|
{
|
|
if (executionContext == null)
|
|
{
|
|
PSTraceSource.NewArgumentNullException(nameof(executionContext));
|
|
return;
|
|
}
|
|
|
|
LogContext logContext = GetLogContext(executionContext, null);
|
|
logContext.CommandLine = commandLine;
|
|
logContext.ScriptName = scriptName;
|
|
|
|
foreach (LogProvider provider in GetLogProvider(executionContext))
|
|
{
|
|
if (NeedToLogPipelineExecutionDetailEvent(provider, executionContext))
|
|
{
|
|
provider.LogPipelineExecutionDetailEvent(logContext, detail);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Provider Health Event Logging Api
|
|
|
|
/// <summary>
|
|
/// LogProviderHealthEvent: Log a Provider health event.
|
|
/// </summary>
|
|
/// <param name="executionContext">Execution context for the engine that is running.</param>
|
|
/// <param name="providerName">Name of the provider.</param>
|
|
/// <param name="exception">Exception associated with this event.</param>
|
|
/// <param name="severity">Severity of this event.</param>
|
|
internal static void LogProviderHealthEvent(ExecutionContext executionContext,
|
|
string providerName,
|
|
Exception exception,
|
|
Severity severity
|
|
)
|
|
{
|
|
if (executionContext == null)
|
|
{
|
|
PSTraceSource.NewArgumentNullException(nameof(executionContext));
|
|
return;
|
|
}
|
|
|
|
if (exception == null)
|
|
{
|
|
PSTraceSource.NewArgumentNullException(nameof(exception));
|
|
return;
|
|
}
|
|
|
|
InvocationInfo invocationInfo = null;
|
|
IContainsErrorRecord icer = exception as IContainsErrorRecord;
|
|
if (icer != null && icer.ErrorRecord != null)
|
|
invocationInfo = icer.ErrorRecord.InvocationInfo;
|
|
foreach (LogProvider provider in GetLogProvider(executionContext))
|
|
{
|
|
if (NeedToLogProviderHealthEvent(provider, executionContext))
|
|
{
|
|
provider.LogProviderHealthEvent(GetLogContext(executionContext, invocationInfo, severity), providerName, exception);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Provider Lifecycle Event Logging Api
|
|
|
|
/// <summary>
|
|
/// LogProviderLifecycleEvent: Log a provider lifecycle event.
|
|
///
|
|
/// This is the only form of ProviderLifecycleEvent logging api.
|
|
/// </summary>
|
|
/// <param name="executionContext">Execution Context for current engine that is running.</param>
|
|
/// <param name="providerName">Provider name.</param>
|
|
/// <param name="providerState">New provider state.</param>
|
|
internal static void LogProviderLifecycleEvent(ExecutionContext executionContext,
|
|
string providerName,
|
|
ProviderState providerState)
|
|
{
|
|
if (executionContext == null)
|
|
{
|
|
PSTraceSource.NewArgumentNullException(nameof(executionContext));
|
|
return;
|
|
}
|
|
|
|
foreach (LogProvider provider in GetLogProvider(executionContext))
|
|
{
|
|
if (NeedToLogProviderLifecycleEvent(provider, executionContext))
|
|
{
|
|
provider.LogProviderLifecycleEvent(GetLogContext(executionContext, null), providerName, providerState);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Settings Event Logging Api
|
|
|
|
/// <summary>
|
|
/// LogSettingsEvent: Log a settings event
|
|
///
|
|
/// This is the basic form of LoggingSettingsEvent API. Variation of this function defined
|
|
/// below will make parameter invocationInfo optional.
|
|
/// </summary>
|
|
/// <param name="executionContext">Execution context for current running engine.</param>
|
|
/// <param name="variableName">Variable name.</param>
|
|
/// <param name="newValue">New value for the variable.</param>
|
|
/// <param name="previousValue">Previous value for the variable.</param>
|
|
/// <param name="invocationInfo">Invocation data for the command that is currently running.</param>
|
|
internal static void LogSettingsEvent(ExecutionContext executionContext,
|
|
string variableName,
|
|
string newValue,
|
|
string previousValue,
|
|
InvocationInfo invocationInfo)
|
|
{
|
|
if (executionContext == null)
|
|
{
|
|
PSTraceSource.NewArgumentNullException(nameof(executionContext));
|
|
return;
|
|
}
|
|
|
|
foreach (LogProvider provider in GetLogProvider(executionContext))
|
|
{
|
|
if (NeedToLogSettingsEvent(provider, executionContext))
|
|
{
|
|
provider.LogSettingsEvent(GetLogContext(executionContext, invocationInfo), variableName, newValue, previousValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is a variation of basic LogSettingsEvent to make "invocationInfo" optional.
|
|
/// </summary>
|
|
/// <param name="executionContext"></param>
|
|
/// <param name="variableName"></param>
|
|
/// <param name="newValue"></param>
|
|
/// <param name="previousValue"></param>
|
|
internal static void LogSettingsEvent(ExecutionContext executionContext,
|
|
string variableName,
|
|
string newValue,
|
|
string previousValue)
|
|
{
|
|
LogSettingsEvent(executionContext, variableName, newValue, previousValue, null);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Functions
|
|
|
|
/// <summary>
|
|
/// Get current engine state for the engine instance corresponding to executionContext
|
|
/// passed in.
|
|
///
|
|
/// Engine state is stored in ExecutionContext.
|
|
/// </summary>
|
|
/// <param name="executionContext"></param>
|
|
/// <returns></returns>
|
|
private static EngineState GetEngineState(ExecutionContext executionContext)
|
|
{
|
|
return executionContext.EngineState;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set current engine state for the engine instance corresponding to executionContext
|
|
/// passed in.
|
|
///
|
|
/// Engine state is stored in ExecutionContext.
|
|
/// </summary>
|
|
/// <param name="executionContext"></param>
|
|
/// <param name="engineState"></param>
|
|
private static void SetEngineState(ExecutionContext executionContext, EngineState engineState)
|
|
{
|
|
executionContext.EngineState = engineState;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate LogContext structure based on executionContext and invocationInfo passed in.
|
|
///
|
|
/// LogContext structure is used in log provider interface.
|
|
/// </summary>
|
|
/// <param name="executionContext"></param>
|
|
/// <param name="invocationInfo"></param>
|
|
/// <returns></returns>
|
|
internal static LogContext GetLogContext(ExecutionContext executionContext, InvocationInfo invocationInfo)
|
|
{
|
|
return GetLogContext(executionContext, invocationInfo, Severity.Informational);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate LogContext structure based on executionContext and invocationInfo passed in.
|
|
///
|
|
/// LogContext structure is used in log provider interface.
|
|
/// </summary>
|
|
/// <param name="executionContext"></param>
|
|
/// <param name="invocationInfo"></param>
|
|
/// <param name="severity"></param>
|
|
/// <returns></returns>
|
|
private static LogContext GetLogContext(ExecutionContext executionContext, InvocationInfo invocationInfo, Severity severity)
|
|
{
|
|
if (executionContext == null)
|
|
return null;
|
|
|
|
LogContext logContext = new LogContext();
|
|
|
|
string shellId = executionContext.ShellID;
|
|
|
|
logContext.ExecutionContext = executionContext;
|
|
logContext.ShellId = shellId;
|
|
logContext.Severity = severity.ToString();
|
|
|
|
if (executionContext.EngineHostInterface != null)
|
|
{
|
|
logContext.HostName = executionContext.EngineHostInterface.Name;
|
|
logContext.HostVersion = executionContext.EngineHostInterface.Version.ToString();
|
|
logContext.HostId = (string)executionContext.EngineHostInterface.InstanceId.ToString();
|
|
}
|
|
|
|
logContext.HostApplication = string.Join(" ", Environment.GetCommandLineArgs());
|
|
|
|
if (executionContext.CurrentRunspace != null)
|
|
{
|
|
logContext.EngineVersion = executionContext.CurrentRunspace.Version.ToString();
|
|
logContext.RunspaceId = executionContext.CurrentRunspace.InstanceId.ToString();
|
|
|
|
Pipeline currentPipeline = ((RunspaceBase)executionContext.CurrentRunspace).GetCurrentlyRunningPipeline();
|
|
if (currentPipeline != null)
|
|
{
|
|
logContext.PipelineId = currentPipeline.InstanceId.ToString(CultureInfo.CurrentCulture);
|
|
}
|
|
}
|
|
|
|
logContext.SequenceNumber = NextSequenceNumber;
|
|
|
|
try
|
|
{
|
|
if (executionContext.LogContextCache.User == null)
|
|
{
|
|
logContext.User = Environment.UserDomainName + "\\" + Environment.UserName;
|
|
executionContext.LogContextCache.User = logContext.User;
|
|
}
|
|
else
|
|
{
|
|
logContext.User = executionContext.LogContextCache.User;
|
|
}
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
logContext.User = Logging.UnknownUserName;
|
|
}
|
|
|
|
System.Management.Automation.Remoting.PSSenderInfo psSenderInfo =
|
|
executionContext.SessionState.PSVariable.GetValue("PSSenderInfo") as System.Management.Automation.Remoting.PSSenderInfo;
|
|
if (psSenderInfo != null)
|
|
{
|
|
logContext.ConnectedUser = psSenderInfo.UserInfo.Identity.Name;
|
|
}
|
|
|
|
logContext.Time = DateTime.Now.ToString(CultureInfo.CurrentCulture);
|
|
|
|
if (invocationInfo == null)
|
|
return logContext;
|
|
|
|
logContext.ScriptName = invocationInfo.ScriptName;
|
|
logContext.CommandLine = invocationInfo.Line;
|
|
|
|
if (invocationInfo.MyCommand != null)
|
|
{
|
|
logContext.CommandName = invocationInfo.MyCommand.Name;
|
|
logContext.CommandType = invocationInfo.MyCommand.CommandType.ToString();
|
|
|
|
switch (invocationInfo.MyCommand.CommandType)
|
|
{
|
|
case CommandTypes.Application:
|
|
logContext.CommandPath = ((ApplicationInfo)invocationInfo.MyCommand).Path;
|
|
break;
|
|
case CommandTypes.ExternalScript:
|
|
logContext.CommandPath = ((ExternalScriptInfo)invocationInfo.MyCommand).Path;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return logContext;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Logging Policy
|
|
|
|
/// <summary>
|
|
/// NeedToLogEngineHealthEvent: check whether logging engine health event is necessary.
|
|
/// Whether to log engine event is controled by session variable "LogEngineHealthEvent"
|
|
/// The default value for this is true (?).
|
|
/// Reading a session variable from execution context for
|
|
/// every single logging call may be expensive. We may need to use a different
|
|
/// approach for this:
|
|
/// a. ExecutionContext will cache the value for variable "LogEngineHealthEvent"
|
|
/// b. If this variable is changed, a notification function will change the cached
|
|
/// value in engine correspondently.
|
|
/// This applies to other logging preference variable also.
|
|
/// </summary>
|
|
/// <param name="logProvider"></param>
|
|
/// <param name="executionContext"></param>
|
|
/// <returns></returns>
|
|
private static bool NeedToLogEngineHealthEvent(LogProvider logProvider, ExecutionContext executionContext)
|
|
{
|
|
if (!logProvider.UseLoggingVariables())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return LanguagePrimitives.IsTrue(executionContext.GetVariableValue(SpecialVariables.LogEngineHealthEventVarPath, true));
|
|
}
|
|
|
|
/// <summary>
|
|
/// NeedToLogEngineLifecycleEvent: check whether logging engine lifecycle event is necessary.
|
|
/// Whether to log engine lifecycle event is controled by session variable "LogEngineLifecycleEvent"
|
|
/// The default value for this is false (?).
|
|
/// </summary>
|
|
/// <param name="logProvider"></param>
|
|
/// <param name="executionContext"></param>
|
|
/// <returns></returns>
|
|
private static bool NeedToLogEngineLifecycleEvent(LogProvider logProvider, ExecutionContext executionContext)
|
|
{
|
|
if (!logProvider.UseLoggingVariables())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return LanguagePrimitives.IsTrue(executionContext.GetVariableValue(SpecialVariables.LogEngineLifecycleEventVarPath, true));
|
|
}
|
|
|
|
/// <summary>
|
|
/// NeedToLogCommandHealthEvent: check whether logging command health event is necessary.
|
|
/// Whether to log command health event is controled by session variable "LogCommandHealthEvent"
|
|
/// The default value for this is false (?).
|
|
/// </summary>
|
|
/// <param name="logProvider"></param>
|
|
/// <param name="executionContext"></param>
|
|
/// <returns></returns>
|
|
private static bool NeedToLogCommandHealthEvent(LogProvider logProvider, ExecutionContext executionContext)
|
|
{
|
|
if (!logProvider.UseLoggingVariables())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return LanguagePrimitives.IsTrue(executionContext.GetVariableValue(SpecialVariables.LogCommandHealthEventVarPath, false));
|
|
}
|
|
|
|
/// <summary>
|
|
/// NeedToLogCommandLifecycleEvent: check whether logging command event is necessary.
|
|
/// Whether to log command lifecycle event is controled by session variable "LogCommandLifecycleEvent"
|
|
/// The default value for this is false (?).
|
|
/// </summary>
|
|
/// <param name="logProvider"></param>
|
|
/// <param name="executionContext"></param>
|
|
/// <returns></returns>
|
|
private static bool NeedToLogCommandLifecycleEvent(LogProvider logProvider, ExecutionContext executionContext)
|
|
{
|
|
if (!logProvider.UseLoggingVariables())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return LanguagePrimitives.IsTrue(executionContext.GetVariableValue(SpecialVariables.LogCommandLifecycleEventVarPath, false));
|
|
}
|
|
|
|
/// <summary>
|
|
/// NeedToLogPipelineExecutionDetailEvent: check whether logging pipeline execution detail event is necessary.
|
|
///
|
|
/// Whether to log command lifecycle event is controled by PSSnapin set up.
|
|
///
|
|
/// Should we use session variable "LogPipelineExecutionEvent" to control this also?
|
|
///
|
|
/// Currently we return true always since pipeline processor already check for whether to log
|
|
/// logic from PSSnapin already. This may need to be changed.
|
|
/// </summary>
|
|
/// <param name="logProvider"></param>
|
|
/// <param name="executionContext"></param>
|
|
/// <returns></returns>
|
|
private static bool NeedToLogPipelineExecutionDetailEvent(LogProvider logProvider, ExecutionContext executionContext)
|
|
{
|
|
if (!logProvider.UseLoggingVariables())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
// return LanguagePrimitives.IsTrue(executionContext.GetVariable("LogPipelineExecutionDetailEvent", false));
|
|
}
|
|
|
|
/// <summary>
|
|
/// NeedToLogProviderHealthEvent: check whether logging Provider health event is necessary.
|
|
/// Whether to log Provider health event is controled by session variable "LogProviderHealthEvent"
|
|
/// The default value for this is true.
|
|
/// </summary>
|
|
/// <param name="logProvider"></param>
|
|
/// <param name="executionContext"></param>
|
|
/// <returns></returns>
|
|
private static bool NeedToLogProviderHealthEvent(LogProvider logProvider, ExecutionContext executionContext)
|
|
{
|
|
if (!logProvider.UseLoggingVariables())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return LanguagePrimitives.IsTrue(executionContext.GetVariableValue(SpecialVariables.LogProviderHealthEventVarPath, true));
|
|
}
|
|
|
|
/// <summary>
|
|
/// NeedToLogProviderLifecycleEvent: check whether logging Provider lifecycle event is necessary.
|
|
/// Whether to log Provider lifecycle event is controled by session variable "LogProviderLifecycleEvent"
|
|
/// The default value for this is true.
|
|
/// </summary>
|
|
/// <param name="logProvider"></param>
|
|
/// <param name="executionContext"></param>
|
|
/// <returns></returns>
|
|
private static bool NeedToLogProviderLifecycleEvent(LogProvider logProvider, ExecutionContext executionContext)
|
|
{
|
|
if (!logProvider.UseLoggingVariables())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return LanguagePrimitives.IsTrue(executionContext.GetVariableValue(SpecialVariables.LogProviderLifecycleEventVarPath, true));
|
|
}
|
|
|
|
/// <summary>
|
|
/// NeedToLogSettingsEvent: check whether logging settings event is necessary.
|
|
/// Whether to log settings event is controled by session variable "LogSettingsEvent"
|
|
/// The default value for this is false (?).
|
|
/// </summary>
|
|
/// <param name="logProvider"></param>
|
|
/// <param name="executionContext"></param>
|
|
/// <returns></returns>
|
|
private static bool NeedToLogSettingsEvent(LogProvider logProvider, ExecutionContext executionContext)
|
|
{
|
|
if (!logProvider.UseLoggingVariables())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return LanguagePrimitives.IsTrue(executionContext.GetVariableValue(SpecialVariables.LogSettingsEventVarPath, true));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Sequence Id Generator
|
|
|
|
private static int s_nextSequenceNumber = 0;
|
|
|
|
/// <summary>
|
|
/// Generate next sequence id to be attached to current event.
|
|
/// </summary>
|
|
/// <value></value>
|
|
private static string NextSequenceNumber
|
|
{
|
|
get
|
|
{
|
|
return Convert.ToString(Interlocked.Increment(ref s_nextSequenceNumber), CultureInfo.CurrentCulture);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region EventId Constants
|
|
|
|
// General health issues.
|
|
internal const int EVENT_ID_GENERAL_HEALTH_ISSUE = 100;
|
|
|
|
// Dependency. resource not available
|
|
internal const int EVENT_ID_RESOURCE_NOT_AVAILABLE = 101;
|
|
// Connectivity. network connection failure
|
|
internal const int EVENT_ID_NETWORK_CONNECTIVITY_ISSUE = 102;
|
|
// Settings. fail to set some configuration settings
|
|
internal const int EVENT_ID_CONFIGURATION_FAILURE = 103;
|
|
// Performance. system is experiencing some performance issues
|
|
internal const int EVENT_ID_PERFORMANCE_ISSUE = 104;
|
|
// Security: system is experiencing some security issues
|
|
internal const int EVENT_ID_SECURITY_ISSUE = 105;
|
|
// Workload. system is overloaded.
|
|
internal const int EVENT_ID_SYSTEM_OVERLOADED = 106;
|
|
|
|
// Beta 1 only -- Unexpected Exception
|
|
internal const int EVENT_ID_UNEXPECTED_EXCEPTION = 195;
|
|
|
|
#endregion EventId Constants
|
|
}
|
|
|
|
/// <summary>
|
|
/// Log context cache.
|
|
/// </summary>
|
|
internal class LogContextCache
|
|
{
|
|
internal string User { get; set; } = null;
|
|
}
|
|
|
|
#region Command State and Provider State
|
|
|
|
/// <summary>
|
|
/// Severity of the event.
|
|
/// </summary>
|
|
internal enum Severity
|
|
{
|
|
/// <summary>
|
|
/// Undefined severity.
|
|
/// </summary>
|
|
None,
|
|
/// <summary>
|
|
/// Critical event causing engine not to work.
|
|
/// </summary>
|
|
Critical,
|
|
|
|
/// <summary>
|
|
/// Error causing engine partially work.
|
|
/// </summary>
|
|
Error,
|
|
|
|
/// <summary>
|
|
/// Problem that may not cause an immediate problem.
|
|
/// </summary>
|
|
Warning,
|
|
|
|
/// <summary>
|
|
/// Informational.
|
|
/// </summary>
|
|
Informational
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enum for command states.
|
|
/// </summary>
|
|
internal enum CommandState
|
|
{
|
|
/// <summary>
|
|
/// </summary>
|
|
Started = 0,
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
Stopped = 1,
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
Terminated = 2
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enum for provider states.
|
|
/// </summary>
|
|
internal enum ProviderState
|
|
{
|
|
/// <summary>
|
|
/// </summary>
|
|
Started = 0,
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
Stopped = 1,
|
|
}
|
|
|
|
#endregion
|
|
}
|