зеркало из https://github.com/microsoft/Omex.git
Merged PR 311207: GitHub Forward Integration - cd92be8a7a
GitHub forward integration Pull Request for commit cd92be8a7a
This commit is contained in:
Коммит
530ddd23ae
|
@ -43,5 +43,8 @@
|
|||
<MicrosoftAspNetCoreHttpVersionCore>2.2.0</MicrosoftAspNetCoreHttpVersionCore>
|
||||
<MicrosoftAzureCosmosDBBulkExecutorVersionCore>2.2.0-preview2</MicrosoftAzureCosmosDBBulkExecutorVersionCore>
|
||||
<WindowsAzureStorageVersionCore>9.3.3</WindowsAzureStorageVersionCore>
|
||||
<SystemThreadingTasks>4.0.11</SystemThreadingTasks>
|
||||
<MSTestAdapterVersion>1.4.0</MSTestAdapterVersion>
|
||||
<MSTestFrameworkVersion>1.4.0</MSTestFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
26
Omex.sln
26
Omex.sln
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.28729.10
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.28307.757
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Omex.System", "src\System\Microsoft.Omex.System.csproj", "{6E08AF9B-BACC-47DB-BC47-5B818C4FB262}"
|
||||
EndProject
|
||||
|
@ -42,6 +42,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Omex.Gating.UnitT
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Omex.Gating.UnitTests", "tests\Gating.UnitTests\Microsoft.Omex.Gating.UnitTests.csproj", "{60BE8066-9FD8-4E69-AC31-5FE0FBD665C8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Omex.System.AspNetCore", "src\System.AspNetCore\Microsoft.Omex.System.AspNetCore.csproj", "{07E79934-29BD-4E03-8533-C086CD1DC50F}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Omex.System.AspNetCore.UnitTests", "tests\System.AspNetCore.UnitTests\Microsoft.Omex.System.AspNetCore.UnitTests.csproj", "{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CodeGenerators", "CodeGenerators", "{C5BA6376-60EB-43E5-A9EF-AD2792C8D2A8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Omex.CodeGenerators.GateGen", "src\CodeGenerators\gategen\Microsoft.Omex.CodeGenerators.GateGen.csproj", "{D3B05497-4540-4D95-A829-E6CD42EE09D3}"
|
||||
|
@ -118,6 +122,22 @@ Global
|
|||
{60BE8066-9FD8-4E69-AC31-5FE0FBD665C8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{60BE8066-9FD8-4E69-AC31-5FE0FBD665C8}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{60BE8066-9FD8-4E69-AC31-5FE0FBD665C8}.Release|x64.Build.0 = Release|Any CPU
|
||||
{07E79934-29BD-4E03-8533-C086CD1DC50F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{07E79934-29BD-4E03-8533-C086CD1DC50F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{07E79934-29BD-4E03-8533-C086CD1DC50F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{07E79934-29BD-4E03-8533-C086CD1DC50F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{07E79934-29BD-4E03-8533-C086CD1DC50F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{07E79934-29BD-4E03-8533-C086CD1DC50F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{07E79934-29BD-4E03-8533-C086CD1DC50F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{07E79934-29BD-4E03-8533-C086CD1DC50F}.Release|x64.Build.0 = Release|Any CPU
|
||||
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{D3B05497-4540-4D95-A829-E6CD42EE09D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D3B05497-4540-4D95-A829-E6CD42EE09D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D3B05497-4540-4D95-A829-E6CD42EE09D3}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
|
@ -139,6 +159,8 @@ Global
|
|||
{C79F8F76-F62C-4F46-8D54-4B0E44BD53A6} = {3249ADDB-50EC-4C21-A8F0-65EF444662EE}
|
||||
{D4799136-2642-4268-86DE-4909F84FB29E} = {3249ADDB-50EC-4C21-A8F0-65EF444662EE}
|
||||
{60BE8066-9FD8-4E69-AC31-5FE0FBD665C8} = {551C93F8-6E89-4954-8905-7F5AC7173285}
|
||||
{07E79934-29BD-4E03-8533-C086CD1DC50F} = {3249ADDB-50EC-4C21-A8F0-65EF444662EE}
|
||||
{C7A072C5-8F04-4A80-8B76-7E08CED9BC5A} = {551C93F8-6E89-4954-8905-7F5AC7173285}
|
||||
{C5BA6376-60EB-43E5-A9EF-AD2792C8D2A8} = {3249ADDB-50EC-4C21-A8F0-65EF444662EE}
|
||||
{D3B05497-4540-4D95-A829-E6CD42EE09D3} = {C5BA6376-60EB-43E5-A9EF-AD2792C8D2A8}
|
||||
EndGlobalSection
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.Gating.UnitTests.Shared
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.Omex.System.Context;
|
||||
using Microsoft.Omex.System.Diagnostics;
|
||||
using Microsoft.Omex.System.TimedScopes;
|
||||
|
||||
namespace Microsoft.Omex.System.AspNetCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Call context manager class
|
||||
/// </summary>
|
||||
public class CallContextManager : ICallContextManager
|
||||
{
|
||||
/// <summary>
|
||||
/// An overridable call context for unit test purposes
|
||||
/// </summary>
|
||||
public ICallContext CallContextOverride { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The call context
|
||||
/// Depending on the 'UseLogicalCallContext' app setting it will either use classic - OperationCallContext -> ThreadCallContext chain
|
||||
/// or the new LogicalCallContext as backup
|
||||
/// </summary>
|
||||
private Lazy<ICallContext> s_callContext(IMachineInformation machineInformation)
|
||||
{
|
||||
return new Lazy<ICallContext>(() => new HttpCallContext(useLogicalCallContext: false, machineInformation: machineInformation), LazyThreadSafetyMode.PublicationOnly);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Current call context
|
||||
/// </summary>
|
||||
public ICallContext CallContextHandler(IMachineInformation machineInformation)
|
||||
{
|
||||
return CallContextOverride ?? new HttpCallContext(useLogicalCallContext: false, machineInformation: machineInformation);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,365 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Omex.System.Context;
|
||||
using Microsoft.Omex.System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Omex.System.AspNetCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Context data stored on HttpContext
|
||||
/// </summary>
|
||||
public class HttpCallContext : ICallContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="useLogicalCallContext">Should LogicalCallContext be used as backup context</param>
|
||||
/// <param name="machineInformation"></param>
|
||||
public HttpCallContext(bool useLogicalCallContext = true, IMachineInformation machineInformation = null)
|
||||
{
|
||||
m_backupCallContext = useLogicalCallContext ? (ICallContext)new LogicalCallContext(machineInformation) : new OperationCallContext();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public HttpCallContext() : this(true)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used when there is no http call context
|
||||
/// </summary>
|
||||
private readonly ICallContext m_backupCallContext;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of data stored on the call context
|
||||
/// </summary>
|
||||
public IDictionary<string, object> Data
|
||||
{
|
||||
get
|
||||
{
|
||||
HttpContext current = HttpContextWrapper.Current;
|
||||
if (current != null)
|
||||
{
|
||||
if (current.Items.ContainsKey(UseBackupKey))
|
||||
{
|
||||
return m_backupCallContext.Data;
|
||||
}
|
||||
|
||||
if (current.Items.ContainsKey(DictionaryKey))
|
||||
{
|
||||
if (!(current.Items[DictionaryKey] is Dictionary<string, object> dictionary))
|
||||
{
|
||||
dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
current.Items[DictionaryKey] = dictionary;
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Attempting to access data for a HttpCallContext that has not been started");
|
||||
}
|
||||
}
|
||||
|
||||
return m_backupCallContext.Data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of data stored on the call context and shared between derived call contexts
|
||||
/// </summary>
|
||||
public ConcurrentDictionary<string, object> SharedData
|
||||
{
|
||||
get
|
||||
{
|
||||
HttpContext current = HttpContextWrapper.Current;
|
||||
if (current != null)
|
||||
{
|
||||
if (current.Items.ContainsKey(UseBackupKey))
|
||||
{
|
||||
return m_backupCallContext.SharedData;
|
||||
}
|
||||
|
||||
return current.Items[DataKey] as ConcurrentDictionary<string, object>;
|
||||
}
|
||||
|
||||
return m_backupCallContext.SharedData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set data to http context
|
||||
/// </summary>
|
||||
/// <param name="key">key</param>
|
||||
/// <param name="value">value</param>
|
||||
public void AddContextValue(string key, object value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HttpContext current = HttpContextWrapper.Current;
|
||||
if (current != null && !current.Items.ContainsKey(UseBackupKey))
|
||||
{
|
||||
current.Items[key.Trim()] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_backupCallContext.AddContextValue(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Start the call context
|
||||
/// </summary>
|
||||
/// <returns>id of the context node</returns>
|
||||
public Guid? StartCallContext()
|
||||
{
|
||||
HttpContext current = HttpContextWrapper.Current;
|
||||
if (current != null)
|
||||
{
|
||||
if (current.Items.ContainsKey(UseBackupKey))
|
||||
{
|
||||
return m_backupCallContext.StartCallContext();
|
||||
}
|
||||
|
||||
if (!current.Items.ContainsKey(DataKey))
|
||||
{
|
||||
current.Items[DataKey] = new ConcurrentDictionary<string, object>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
if (current.Items.ContainsKey(DictionaryKey))
|
||||
{
|
||||
Stack<Dictionary<string, object>> dataStack = null;
|
||||
if (current.Items.ContainsKey(ContextStack))
|
||||
{
|
||||
dataStack = current.Items[ContextStack] as Stack<Dictionary<string, object>>;
|
||||
}
|
||||
else
|
||||
{
|
||||
dataStack = new Stack<Dictionary<string, object>>();
|
||||
current.Items[ContextStack] = dataStack;
|
||||
}
|
||||
|
||||
dataStack.Push(current.Items[DictionaryKey] as Dictionary<string, object>);
|
||||
}
|
||||
|
||||
current.Items[DictionaryKey] = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_backupCallContext.StartCallContext();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// End the call context
|
||||
/// </summary>
|
||||
/// <param name="threadId">id of the thread on which to end the context</param>
|
||||
/// <param name="nodeId">id of the context node</param>
|
||||
public void EndCallContext(int? threadId = null, Guid? nodeId = null)
|
||||
{
|
||||
HttpContext current = HttpContextWrapper.Current;
|
||||
if (current != null)
|
||||
{
|
||||
if (current.Items.ContainsKey(UseBackupKey))
|
||||
{
|
||||
m_backupCallContext.EndCallContext(threadId, nodeId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (current.Items.ContainsKey(DictionaryKey))
|
||||
{
|
||||
current.Items.Remove(DictionaryKey);
|
||||
|
||||
if (current.Items.ContainsKey(ContextStack))
|
||||
{
|
||||
if (current.Items[ContextStack] is Stack<Dictionary<string, object>> dataStack && dataStack.Count > 0)
|
||||
{
|
||||
current.Items[DictionaryKey] = dataStack.Pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Attempting to end a HttpCallContext that has not been started.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
m_backupCallContext.EndCallContext(threadId, nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the existing call context if there is one
|
||||
/// </summary>
|
||||
/// <param name="threadId">id of the thread from which to take the context</param>
|
||||
/// <returns>the call context that is being used</returns>
|
||||
public ICallContext ExistingCallContext(int? threadId = null)
|
||||
{
|
||||
HttpContext current = HttpContextWrapper.Current;
|
||||
if (current != null)
|
||||
{
|
||||
if (current.Items.ContainsKey(UseBackupKey))
|
||||
{
|
||||
return m_backupCallContext.ExistingCallContext(threadId);
|
||||
}
|
||||
|
||||
if (current.Items.ContainsKey(DictionaryKey))
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return m_backupCallContext.ExistingCallContext(threadId);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Move data to backup context
|
||||
/// </summary>
|
||||
public void MoveCurrentContextToBackup()
|
||||
{
|
||||
HttpContext current = HttpContextWrapper.Current;
|
||||
if (current != null && m_backupCallContext is LogicalCallContext)
|
||||
{
|
||||
IDictionary<string, object> dictionary = GetData(current);
|
||||
ConcurrentDictionary<string, object> sharedDictionary = GetSharedData(current);
|
||||
|
||||
// Start call context on the backup context
|
||||
int threadId = Thread.CurrentThread.ManagedThreadId;
|
||||
Guid? nodeId = m_backupCallContext.StartCallContext();
|
||||
|
||||
foreach (KeyValuePair<string, object> item in dictionary)
|
||||
{
|
||||
m_backupCallContext.Data.Add(item);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, object> item in sharedDictionary)
|
||||
{
|
||||
m_backupCallContext.SharedData.AddOrUpdate(item.Key, _ => item.Value, (_, __) => item.Value);
|
||||
}
|
||||
|
||||
// Mark this context as copied over to backup
|
||||
// This will result in a fallthrough to backup context
|
||||
current.Items[UseBackupKey] = Tuple.Create(threadId, nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve data from backup context
|
||||
/// </summary>
|
||||
public void RetrieveCurrentContextFromBackup()
|
||||
{
|
||||
HttpContext current = HttpContextWrapper.Current;
|
||||
if (current != null && m_backupCallContext is LogicalCallContext)
|
||||
{
|
||||
// Do not use Data or SharedData to avoid falling through to BackupContext
|
||||
IDictionary<string, object> dictionary = GetData(current);
|
||||
ConcurrentDictionary<string, object> sharedDictionary = GetSharedData(current);
|
||||
|
||||
foreach (KeyValuePair<string, object> item in m_backupCallContext.Data)
|
||||
{
|
||||
dictionary[item.Key] = item.Value;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, object> item in m_backupCallContext.SharedData)
|
||||
{
|
||||
sharedDictionary.AddOrUpdate(item.Key, _ => item.Value, (_, __) => item.Value);
|
||||
}
|
||||
|
||||
// End call context on the backup context
|
||||
Tuple<int, Guid?> contextKey = current.Items[UseBackupKey] as Tuple<int, Guid?>;
|
||||
m_backupCallContext.EndCallContext(contextKey?.Item1, contextKey?.Item2);
|
||||
|
||||
// Unmark this context
|
||||
current.Items.Remove(UseBackupKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static IDictionary<string, object> GetData(HttpContext currentContext)
|
||||
{
|
||||
Dictionary<string, object> dictionary = null;
|
||||
if (currentContext.Items.ContainsKey(DictionaryKey))
|
||||
{
|
||||
dictionary = currentContext.Items[DictionaryKey] as Dictionary<string, object>;
|
||||
}
|
||||
|
||||
if (dictionary == null)
|
||||
{
|
||||
dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
currentContext.Items[DictionaryKey] = dictionary;
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
|
||||
private static ConcurrentDictionary<string, object> GetSharedData(HttpContext currentContext)
|
||||
{
|
||||
ConcurrentDictionary<string, object> sharedDictionary = null;
|
||||
if (currentContext.Items.ContainsKey(DataKey))
|
||||
{
|
||||
sharedDictionary = currentContext.Items[DataKey] as ConcurrentDictionary<string, object>;
|
||||
}
|
||||
|
||||
if (sharedDictionary == null)
|
||||
{
|
||||
sharedDictionary = new ConcurrentDictionary<string, object>(StringComparer.Ordinal);
|
||||
currentContext.Items[DataKey] = sharedDictionary;
|
||||
}
|
||||
|
||||
return sharedDictionary;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Key used to signify that the context should fall through and use backup context instead
|
||||
/// </summary>
|
||||
private const string UseBackupKey = "ThreadCallContext.UseBackup";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Key used to store data shared between multiple related contexts
|
||||
/// </summary>
|
||||
private const string DataKey = "ThreadCallContext.Data";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Key used to store data on the underlying context
|
||||
/// </summary>
|
||||
private const string DictionaryKey = "ThreadCallContext.Dictionary";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Key used to store nested call contexts
|
||||
/// </summary>
|
||||
private const string ContextStack = "ThreadCallContext.Stack";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.Validation;
|
||||
|
||||
namespace Microsoft.Omex.System.AspNetCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Http context wrapper
|
||||
/// </summary>
|
||||
public class HttpContextWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="contextAccessor">Http context accessor</param>
|
||||
public static void Configure(IHttpContextAccessor contextAccessor)
|
||||
{
|
||||
s_contextAccessor = Code.ExpectsArgument(contextAccessor, nameof(contextAccessor), TaggingUtilities.ReserveTag(0x23817743 /* tag_96x3d */));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The current http context
|
||||
/// </summary>
|
||||
public static HttpContext Current => s_contextAccessor?.HttpContext;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Http context accessor
|
||||
/// </summary>
|
||||
private static IHttpContextAccessor s_contextAccessor;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,381 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.Omex.System.Context;
|
||||
using Microsoft.Omex.System.Diagnostics;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
|
||||
namespace Microsoft.Omex.System.AspNetCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Context data stored on LogicalCallContext
|
||||
/// </summary>
|
||||
public class LogicalCallContext : ICallContext
|
||||
{
|
||||
private static readonly AsyncLocal<ConcurrentDictionary<string, object>> s_dataAsyncLocal = new AsyncLocal<ConcurrentDictionary<string, object>>();
|
||||
|
||||
|
||||
private static readonly AsyncLocal<IImmutableDictionary<string, object>> s_dictionaryAsyncLocal = new AsyncLocal<IImmutableDictionary<string, object>>();
|
||||
|
||||
|
||||
private static readonly AsyncLocal<IImmutableStack<IImmutableDictionary<string, object>>> s_stackAsyncLocal = new AsyncLocal<IImmutableStack<IImmutableDictionary<string, object>>>();
|
||||
|
||||
|
||||
public LogicalCallContext(IMachineInformation machineInformation)
|
||||
{
|
||||
MachineInformation = machineInformation;
|
||||
}
|
||||
|
||||
|
||||
/// <summary> Gets or sets the context data </summary>
|
||||
protected ConcurrentDictionary<string, object> ContextData
|
||||
{
|
||||
get => s_dataAsyncLocal.Value;
|
||||
set => s_dataAsyncLocal.Value = value;
|
||||
}
|
||||
|
||||
|
||||
/// <summary> Gets or sets the context dictionary </summary>
|
||||
protected IImmutableDictionary<string, object> ContextDictionary
|
||||
{
|
||||
get => s_dictionaryAsyncLocal.Value;
|
||||
set => s_dictionaryAsyncLocal.Value = value;
|
||||
}
|
||||
|
||||
|
||||
/// <summary> Gets or sets the context stack </summary>
|
||||
protected IImmutableStack<IImmutableDictionary<string, object>> ContextStack
|
||||
{
|
||||
get => s_stackAsyncLocal.Value;
|
||||
set => s_stackAsyncLocal.Value = value;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of data stored on the call context
|
||||
/// </summary>
|
||||
public IDictionary<string, object> Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ContextData == null)
|
||||
{
|
||||
throw new InvalidOperationException("Attempting to access data for a LogicalCallContext that has not been started");
|
||||
}
|
||||
|
||||
IImmutableDictionary<string, object> dictionary = ContextDictionary;
|
||||
if (dictionary == null)
|
||||
{
|
||||
dictionary = new SerializableImmutableDictionary<string, object>(ImmutableDictionary<string, object>.Empty.WithComparers(StringComparer.Ordinal));
|
||||
ContextDictionary = dictionary;
|
||||
}
|
||||
|
||||
return new LogicalCallContextDictionary(this, dictionary);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of data stored on the call context and shared between derived call contexts
|
||||
/// </summary>
|
||||
public ConcurrentDictionary<string, object> SharedData
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ContextData == null)
|
||||
{
|
||||
throw new InvalidOperationException("Attempting to access shared data for a LogicalCallContext that has not been started");
|
||||
}
|
||||
|
||||
return ContextData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set data to http context
|
||||
/// </summary>
|
||||
/// <param name="key">key</param>
|
||||
/// <param name="value">value</param>
|
||||
public void AddContextValue(string key, object value)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Start the call context
|
||||
/// </summary>
|
||||
/// <returns>id of the context node</returns>
|
||||
public Guid? StartCallContext()
|
||||
{
|
||||
if (ContextData == null)
|
||||
{
|
||||
ContextData = new ConcurrentDictionary<string, object>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
if (ContextDictionary != null)
|
||||
{
|
||||
IImmutableStack<IImmutableDictionary<string, object>> dataStack = null;
|
||||
if (ContextStack != null)
|
||||
{
|
||||
dataStack = ContextStack;
|
||||
}
|
||||
else
|
||||
{
|
||||
dataStack = new SerializableImmutableStack<IImmutableDictionary<string, object>>(ImmutableStack<IImmutableDictionary<string, object>>.Empty);
|
||||
ContextStack = dataStack;
|
||||
}
|
||||
|
||||
ContextStack = dataStack.Push(ContextDictionary);
|
||||
}
|
||||
|
||||
ContextDictionary = new SerializableImmutableDictionary<string, object>(ImmutableDictionary<string, object>.Empty.WithComparers(StringComparer.Ordinal));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// End the call context
|
||||
/// </summary>
|
||||
/// <param name="threadId">id of the thread on which to end the context</param>
|
||||
/// <param name="nodeId">id of the context node</param>
|
||||
public void EndCallContext(int? threadId = null, Guid? nodeId = null)
|
||||
{
|
||||
if (ContextDictionary != null)
|
||||
{
|
||||
ContextDictionary = null;
|
||||
|
||||
if (ContextStack != null)
|
||||
{
|
||||
IImmutableStack<IImmutableDictionary<string, object>> dataStack = ContextStack;
|
||||
if (dataStack != null && !dataStack.IsEmpty)
|
||||
{
|
||||
ContextDictionary = dataStack.Peek();
|
||||
ContextStack = dataStack.Pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!s_testEnvironment.Value)
|
||||
{
|
||||
ULSLogging.LogTraceTag(0x23817742 /* tag_96x3c */, Categories.Infrastructure, Levels.Warning,
|
||||
"Attempting to end a LogicalCallContext that has not been started.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Attempting to end a LogicalCallContext that has not been started.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static IMachineInformation MachineInformation { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Current environment
|
||||
/// </summary>
|
||||
private static readonly Lazy<bool> s_testEnvironment = new Lazy<bool>(() =>
|
||||
MachineInformation.IsPrivateDeployment,
|
||||
LazyThreadSafetyMode.PublicationOnly);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the existing call context if there is one
|
||||
/// </summary>
|
||||
/// <param name="threadId">id of the thread from which to take the context</param>
|
||||
/// <returns>the call context that is being used</returns>
|
||||
public ICallContext ExistingCallContext(int? threadId = null)
|
||||
{
|
||||
if (ContextDictionary != null)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Mutable dictionary which ensures that all changes are persisted correctly
|
||||
/// </summary>
|
||||
private class LogicalCallContextDictionary : IDictionary<string, object>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="context">Instance of logical context to set dictionary</param>
|
||||
/// <param name="innerDictionary">Items storage</param>
|
||||
public LogicalCallContextDictionary(LogicalCallContext context, IImmutableDictionary<string, object> innerDictionary)
|
||||
{
|
||||
m_context = context;
|
||||
m_innerDictionary = innerDictionary;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Items storage
|
||||
/// </summary>
|
||||
private IImmutableDictionary<string, object> m_innerDictionary;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the immutable dictionary.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => m_innerDictionary.GetEnumerator();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the immutable dictionary.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds an element with the provided key and value to the immutable dictionary.
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
public void Add(KeyValuePair<string, object> item)
|
||||
{
|
||||
Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Clears this instance.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
IImmutableDictionary<string, object> dict = m_innerDictionary = m_innerDictionary.Clear();
|
||||
m_context.ContextDictionary = dict;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this immutable dictionary contains the specified key/value pair.
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
public bool Contains(KeyValuePair<string, object> item) => m_innerDictionary.Contains(item);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Copies the elements of the dictionary to an array, starting at a particular array index.
|
||||
/// </summary>
|
||||
/// <param name="array">Array</param>
|
||||
/// <param name="arrayIndex">Array index</param>
|
||||
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
|
||||
{
|
||||
((IDictionary<string, object>)m_innerDictionary).CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item from the dictionary
|
||||
/// </summary>
|
||||
/// <param name="item">Item to remove</param>
|
||||
/// <returns>True if items was removed from the dictionary</returns>
|
||||
public bool Remove(KeyValuePair<string, object> item) => Remove(item.Key);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of elements contained in the dictionary
|
||||
/// </summary>
|
||||
public int Count => m_innerDictionary.Count;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the dictionary is read only
|
||||
/// </summary>
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the dictinoary contains an element with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key</param>
|
||||
/// <returns>True if dictionary contains specified key</returns>
|
||||
public bool ContainsKey(string key) => m_innerDictionary.ContainsKey(key);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds an element with the provided key and value to the Dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">Key</param>
|
||||
/// <param name="value">Value</param>
|
||||
public void Add(string key, object value)
|
||||
{
|
||||
m_innerDictionary = m_innerDictionary.SetItem(key, value);
|
||||
m_context.ContextDictionary = m_innerDictionary;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes the element with the specified key from the dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">Key</param>
|
||||
/// <returns>True if item was removed</returns>
|
||||
public bool Remove(string key)
|
||||
{
|
||||
bool result = ContainsKey(key);
|
||||
if (!result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_innerDictionary = m_innerDictionary.Remove(key);
|
||||
m_context.ContextDictionary = m_innerDictionary;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value associated with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key</param>
|
||||
/// <param name="value">Value</param>
|
||||
/// <returns>True if key exisited in the dictionary</returns>
|
||||
public bool TryGetValue(string key, out object value) => m_innerDictionary.TryGetValue(key, out value);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the element with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to set or get</param>
|
||||
/// <returns>Value associated with the key</returns>
|
||||
public object this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_innerDictionary[key];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Collection containing keys of the dictionary
|
||||
/// </summary>
|
||||
public ICollection<string> Keys => m_innerDictionary.Keys.ToList();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Collection containing values of the dictionary
|
||||
/// </summary>
|
||||
public ICollection<object> Values => m_innerDictionary.Values.ToList();
|
||||
|
||||
|
||||
private readonly LogicalCallContext m_context;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net461;netstandard2.0</TargetFrameworks>
|
||||
<Description>Contains common classes for Omex libraries in AspNetCore.</Description>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.ServiceModel" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpVersionCore)" />
|
||||
<PackageReference Include="System.Threading.Tasks" Version="$(SystemThreadingTasks)"/>
|
||||
<PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersionCore)" />
|
||||
<PackageReference Include="System.ServiceModel.Primitives" Version="$(SystemServiceModelPrimitivesVersionCore)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\System\Microsoft.Omex.System.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,186 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ServiceModel;
|
||||
using Microsoft.Omex.System.Context;
|
||||
|
||||
namespace Microsoft.Omex.System.AspNetCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Context data specific to the current call connected with an OperationContext (WCF)
|
||||
/// </summary>
|
||||
public class OperationCallContext : ICallContext
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Used when there is no operation call context
|
||||
/// </summary>
|
||||
private readonly ICallContext m_backupCallContext = new ThreadCallContext();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of data stored on the call context
|
||||
/// </summary>
|
||||
public IDictionary<string, object> Data
|
||||
{
|
||||
get
|
||||
{
|
||||
OperationContext context = OperationContext.Current;
|
||||
if (context != null)
|
||||
{
|
||||
Stack<Dictionary<string, object>> dataStack;
|
||||
if (s_contextData.TryGetValue(context, out dataStack) && dataStack.Count > 0)
|
||||
{
|
||||
Dictionary<string, object> data = dataStack.Peek();
|
||||
if (data == null)
|
||||
{
|
||||
data = new Dictionary<string, object>();
|
||||
dataStack.Pop();
|
||||
dataStack.Push(data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Attempting to access data for an OperationCallContext that has not been started");
|
||||
}
|
||||
}
|
||||
|
||||
return m_backupCallContext.Data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of data stored on the call context and shared between derived call contexts
|
||||
/// </summary>
|
||||
public ConcurrentDictionary<string, object> SharedData
|
||||
{
|
||||
get
|
||||
{
|
||||
OperationContext context = OperationContext.Current;
|
||||
if (context != null)
|
||||
{
|
||||
return s_sharedContextData.GetOrAdd(context, _ => new ConcurrentDictionary<string, object>(StringComparer.Ordinal));
|
||||
}
|
||||
|
||||
return m_backupCallContext.SharedData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Add data to context
|
||||
/// </summary>
|
||||
/// <param name="key">key</param>
|
||||
/// <param name="value">value</param>
|
||||
public void AddContextValue(string key, object value)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Start the call context
|
||||
/// </summary>
|
||||
/// <returns>id of the context node</returns>
|
||||
public Guid? StartCallContext()
|
||||
{
|
||||
OperationContext context = OperationContext.Current;
|
||||
if (context != null)
|
||||
{
|
||||
Stack<Dictionary<string, object>> dataStack = s_contextData.GetOrAdd(context, (operationContext) => new Stack<Dictionary<string, object>>());
|
||||
dataStack.Push(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_backupCallContext.StartCallContext();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// End the call context
|
||||
/// </summary>
|
||||
/// <param name="threadId">id of the thread on which to end the context</param>
|
||||
/// <param name="nodeId">id of the context node</param>
|
||||
public void EndCallContext(int? threadId = null, Guid? nodeId = null)
|
||||
{
|
||||
OperationContext context = OperationContext.Current;
|
||||
if (context != null)
|
||||
{
|
||||
Stack<Dictionary<string, object>> dataStack;
|
||||
if (s_contextData.TryGetValue(context, out dataStack) && dataStack.Count > 0)
|
||||
{
|
||||
dataStack.Pop();
|
||||
if (dataStack.Count == 0)
|
||||
{
|
||||
s_contextData.TryRemove(context, out dataStack);
|
||||
|
||||
ConcurrentDictionary<string, object> _;
|
||||
s_sharedContextData.TryRemove(context, out _);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Attempting to end an OperationCallContext that has not been started");
|
||||
}
|
||||
}
|
||||
|
||||
m_backupCallContext.EndCallContext(threadId, nodeId);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the existing call context if there is one
|
||||
/// </summary>
|
||||
/// <param name="threadId">id of the thread from which to take the context</param>
|
||||
/// <returns>the call context that is being used</returns>
|
||||
public ICallContext ExistingCallContext(int? threadId = null)
|
||||
{
|
||||
OperationContext context = OperationContext.Current;
|
||||
if (context != null)
|
||||
{
|
||||
Stack<Dictionary<string, object>> dataStack;
|
||||
if (s_contextData.TryGetValue(context, out dataStack) && dataStack.Count > 0)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return m_backupCallContext.ExistingCallContext(threadId);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Data stored per operation context
|
||||
/// </summary>
|
||||
/// <remarks>Concurrency level is set to 100 instead of the default 4*number of processors. This gives a more
|
||||
/// granular lock for processors using high contention. The initial capacity is set to avoid capacity to grow
|
||||
/// which is expensive.
|
||||
/// </remarks>
|
||||
private static readonly ConcurrentDictionary<OperationContext, Stack<Dictionary<string, object>>> s_contextData =
|
||||
new ConcurrentDictionary<OperationContext, Stack<Dictionary<string, object>>>(100, 1000);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Data stored per operation context and shared between all child contexts
|
||||
/// </summary>
|
||||
/// <remarks>Concurrency level is set to 100 instead of the default 4*number of processors. This gives a more
|
||||
/// granular lock for processors using high contention. The initial capacity is set to avoid capacity to grow
|
||||
/// which is expensive.
|
||||
/// </remarks>
|
||||
private static readonly ConcurrentDictionary<OperationContext, ConcurrentDictionary<string, object>> s_sharedContextData =
|
||||
new ConcurrentDictionary<OperationContext, ConcurrentDictionary<string, object>>(100, 1000);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Xml.Serialization;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.Validation;
|
||||
|
||||
namespace Microsoft.Omex.System.AspNetCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializable implementation of the ImmutableDictionary
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">Key type</typeparam>
|
||||
/// <typeparam name="TValue">Value type</typeparam>
|
||||
[Serializable]
|
||||
public class SerializableImmutableDictionary<TKey, TValue> : MarshalByRefObject, IImmutableDictionary<TKey, TValue>, ISerializable, IDeserializationCallback
|
||||
{
|
||||
[XmlIgnore]
|
||||
[NonSerialized]
|
||||
private ImmutableDictionary<TKey, TValue> m_store;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Stores serilization info received in the constructor until
|
||||
/// the deserilization callback is called and the object can be deserilized
|
||||
/// </summary>
|
||||
[XmlIgnore]
|
||||
[NonSerialized]
|
||||
private SerializationInfo m_serializationInfo;
|
||||
|
||||
|
||||
private ImmutableDictionary<TKey, TValue> Store => m_store;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="store">Data store</param>
|
||||
public SerializableImmutableDictionary(ImmutableDictionary<TKey, TValue> store)
|
||||
{
|
||||
Code.ExpectsArgument(store, nameof(store), TaggingUtilities.ReserveTag(0x23817723 /* tag_96x29 */));
|
||||
|
||||
m_store = store;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public SerializableImmutableDictionary()
|
||||
{
|
||||
m_store = ImmutableDictionary<TKey, TValue>.Empty;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor used for deserialization.
|
||||
/// </summary>
|
||||
/// <param name="info">Serialization info</param>
|
||||
/// <param name="context">Serialization context - unused</param>
|
||||
/// <remarks>Stores serialization info in a field to allow deserialization callback to complete deserialization</remarks>
|
||||
protected SerializableImmutableDictionary(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
Code.ExpectsArgument(info, nameof(info), TaggingUtilities.ReserveTag(0x23817740 /* tag_96x3a */));
|
||||
|
||||
m_serializationInfo = info;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the immutable dictionary.
|
||||
/// </summary>
|
||||
/// <returns>Enumerator</returns>
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => Store.GetEnumerator();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the immutable dictionary.
|
||||
/// </summary>
|
||||
/// <returns>Enumerator</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of key/value pairs in the immutable dictionary.
|
||||
/// </summary>
|
||||
/// <returns>Number of key/value pairs in the immutable dictionary</returns>
|
||||
public int Count => Store.Count;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this immutable dictionary contains the specified key/value pair.
|
||||
/// </summary>
|
||||
/// <param name="key">Key</param>
|
||||
/// <returns>True if dictionary contains the key</returns>
|
||||
public bool ContainsKey(TKey key) => Store.ContainsKey(key);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value associated with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key</param>
|
||||
/// <param name="value">Value</param>
|
||||
/// <returns>The value associated with the specified key</returns>
|
||||
public bool TryGetValue(TKey key, out TValue value) => Store.TryGetValue(key, out value);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value associated with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key</param>
|
||||
/// <returns>Value associated with the specified key</returns>
|
||||
public TValue this[TKey key] => Store[key];
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keys in the immutable dictionary.
|
||||
/// </summary>
|
||||
public IEnumerable<TKey> Keys => Store.Keys;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the values in the immutable dictionary.
|
||||
/// </summary>
|
||||
public IEnumerable<TValue> Values => Store.Values;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an empty immutable dictionary that has the same ordering and key/value comparison rules as this dictionary instance.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IImmutableDictionary<TKey, TValue> Clear()
|
||||
=> new SerializableImmutableDictionary<TKey, TValue>(Store.Clear());
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds an element with the specified key and value to the immutable dictionary.
|
||||
/// </summary>
|
||||
/// <param name="key">Key</param>
|
||||
/// <param name="value">Value</param>
|
||||
/// <returns>Immutable dictionary with the added value</returns>
|
||||
public IImmutableDictionary<TKey, TValue> Add(TKey key, TValue value)
|
||||
=> new SerializableImmutableDictionary<TKey, TValue>(Store.Add(key, value));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified key/value pairs to the immutable dictionary.
|
||||
/// </summary>
|
||||
/// <param name="pairs">Key/Value pairs</param>
|
||||
/// <returns>Immutable dictionary with the pairs added</returns>
|
||||
public IImmutableDictionary<TKey, TValue> AddRange(IEnumerable<KeyValuePair<TKey, TValue>> pairs)
|
||||
=> new SerializableImmutableDictionary<TKey, TValue>(Store.AddRange(pairs));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified key and value in the immutable dictionary, possibly overwriting an existing value for the key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key</param>
|
||||
/// <param name="value">Value</param>
|
||||
/// <returns>Immutable dictionary with the item set</returns>
|
||||
public IImmutableDictionary<TKey, TValue> SetItem(TKey key, TValue value)
|
||||
=> new SerializableImmutableDictionary<TKey, TValue>(Store.SetItem(key, value));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified key/value pairs in the immutable dictionary, possibly overwriting existing values for the keys.
|
||||
/// </summary>
|
||||
/// <param name="items">Items to set</param>
|
||||
/// <returns>Immutable dictionary with those items set</returns>
|
||||
public IImmutableDictionary<TKey, TValue> SetItems(IEnumerable<KeyValuePair<TKey, TValue>> items)
|
||||
=> new SerializableImmutableDictionary<TKey, TValue>(Store.SetItems(items));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes the elements with the specified keys from the immutable dictionary.
|
||||
/// </summary>
|
||||
/// <param name="keys">Keys</param>
|
||||
/// <returns>Immutable dictionary with the keys removed</returns>
|
||||
public IImmutableDictionary<TKey, TValue> RemoveRange(IEnumerable<TKey> keys)
|
||||
=> new SerializableImmutableDictionary<TKey, TValue>(Store.RemoveRange(keys));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes the element with the specified key from the immutable dictionary.
|
||||
/// </summary>
|
||||
/// <param name="key">Key</param>
|
||||
/// <returns>Immutable dictionary with the key removed</returns>
|
||||
public IImmutableDictionary<TKey, TValue> Remove(TKey key)
|
||||
=> new SerializableImmutableDictionary<TKey, TValue>(Store.Remove(key));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this immutable dictionary contains the specified key/value pair.
|
||||
/// </summary>
|
||||
/// <param name="pair">Pair to check</param>
|
||||
/// <returns>True if dictionary contains the pair</returns>
|
||||
public bool Contains(KeyValuePair<TKey, TValue> pair) => Store.Contains(pair);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this dictionary contains a specified key.
|
||||
/// </summary>
|
||||
/// <param name="equalKey">Key</param>
|
||||
/// <param name="actualKey">Value</param>
|
||||
/// <returns>True if the key is in the dictionary</returns>
|
||||
public bool TryGetKey(TKey equalKey, out TKey actualKey) => Store.TryGetKey(equalKey, out actualKey);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Populates a SerializationInfo with the data needed to serialize the target object.
|
||||
/// </summary>
|
||||
/// <param name="info">Serialization info</param>
|
||||
/// <param name="context">Serialization context - unused</param>
|
||||
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
Code.ExpectsArgument(info, nameof(info), TaggingUtilities.ReserveTag(0x23817741 /* tag_96x3b */));
|
||||
|
||||
info.AddValue(SerializedFieldName, m_store.ToArray(), typeof(KeyValuePair<TKey, TValue>[]));
|
||||
info.AddValue(KeyComparerFieldName, m_store.KeyComparer, typeof(IEqualityComparer<TKey>));
|
||||
info.AddValue(ValueComparerFieldName, m_store.ValueComparer, typeof(IEqualityComparer<TValue>));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Runs when the entire object graph has been deserialized.
|
||||
/// </summary>
|
||||
/// <param name="sender">Unused</param>
|
||||
public void OnDeserialization(object sender)
|
||||
{
|
||||
KeyValuePair<TKey, TValue>[] deserializedValues =
|
||||
(KeyValuePair<TKey, TValue>[])m_serializationInfo.GetValue(SerializedFieldName, typeof(KeyValuePair<TKey, TValue>[]));
|
||||
|
||||
IEqualityComparer<TKey> keyComparer =
|
||||
(IEqualityComparer<TKey>)m_serializationInfo.GetValue(KeyComparerFieldName, typeof(IEqualityComparer<TKey>));
|
||||
|
||||
IEqualityComparer<TValue> valueComparer =
|
||||
(IEqualityComparer<TValue>)m_serializationInfo.GetValue(ValueComparerFieldName, typeof(IEqualityComparer<TValue>));
|
||||
|
||||
m_store = deserializedValues.ToImmutableDictionary().WithComparers(keyComparer, valueComparer);
|
||||
|
||||
m_serializationInfo = null;
|
||||
}
|
||||
|
||||
|
||||
private const string SerializedFieldName = "Store";
|
||||
|
||||
|
||||
private const string KeyComparerFieldName = "KeyComparer";
|
||||
|
||||
|
||||
private const string ValueComparerFieldName = "ValueComparer";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Xml.Serialization;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.Validation;
|
||||
|
||||
namespace Microsoft.Omex.System.AspNetCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializable implementation of the ImmutableStack
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">Value type</typeparam>
|
||||
[Serializable]
|
||||
public class SerializableImmutableStack<TValue> : MarshalByRefObject, IImmutableStack<TValue>, ISerializable, IDeserializationCallback
|
||||
{
|
||||
[XmlIgnore]
|
||||
[NonSerialized]
|
||||
private IImmutableStack<TValue> m_store;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Stores serilization info received in the constructor until
|
||||
/// the deserilization callback is called and the object can be deserilized
|
||||
/// </summary>
|
||||
[XmlIgnore]
|
||||
[NonSerialized]
|
||||
private SerializationInfo m_serializationInfo;
|
||||
|
||||
|
||||
private IImmutableStack<TValue> Store => m_store;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="store">Data store</param>
|
||||
public SerializableImmutableStack(IImmutableStack<TValue> store)
|
||||
{
|
||||
Code.ExpectsArgument(store, nameof(store), TaggingUtilities.ReserveTag(0x23817720 /* tag_96x26 */));
|
||||
|
||||
m_store = store;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public SerializableImmutableStack()
|
||||
{
|
||||
m_store = ImmutableStack<TValue>.Empty;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor used for deserialization.
|
||||
/// </summary>
|
||||
/// <remarks>Stores serialization info in a field to allow deserialization callback to complete deserialization</remarks>
|
||||
protected SerializableImmutableStack(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
Code.ExpectsArgument(info, nameof(info), TaggingUtilities.ReserveTag(0x23817721 /* tag_96x27 */));
|
||||
|
||||
m_serializationInfo = info;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the immutable stack.
|
||||
/// </summary>
|
||||
/// <returns>Enumerator</returns>
|
||||
public IEnumerator<TValue> GetEnumerator() => Store.GetEnumerator();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the immutable stack.
|
||||
/// </summary>
|
||||
/// <returns>Enumerator</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes all objects from the immutable stack.
|
||||
/// </summary>
|
||||
/// <returns>Empty stack</returns>
|
||||
public IImmutableStack<TValue> Clear()
|
||||
=> new SerializableImmutableStack<TValue>(Store.Clear());
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Inserts an object at the top of the immutable stack and returns the new stack.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to push</param>
|
||||
/// <returns>Immutable stack with the value at the top</returns>
|
||||
public IImmutableStack<TValue> Push(TValue value)
|
||||
=> new SerializableImmutableStack<TValue>(Store.Push(value));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified element from the immutable stack and returns the stack after the removal.
|
||||
/// </summary>
|
||||
/// <returns>Immutable stack with the top element removed</returns>
|
||||
public IImmutableStack<TValue> Pop()
|
||||
=> new SerializableImmutableStack<TValue>(Store.Pop());
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the object at the top of the stack without removing it.
|
||||
/// </summary>
|
||||
/// <returns>Object at the top of the stack</returns>
|
||||
public TValue Peek() => Store.Peek();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether this instance of the immutable stack is empty.
|
||||
/// </summary>
|
||||
public bool IsEmpty => Store.IsEmpty;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Populates a SerializationInfo with the data needed to serialize the target object.
|
||||
/// </summary>
|
||||
/// <param name="info">Serialization info</param>
|
||||
/// <param name="context">Serialization context - unused</param>
|
||||
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
Code.ExpectsArgument(info, nameof(info), TaggingUtilities.ReserveTag(0x23817722 /* tag_96x28 */));
|
||||
|
||||
info.AddValue(SerializedFieldName, new Stack<TValue>(m_store), typeof(Stack<TValue>));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Runs when the entire object graph has been deserialized.
|
||||
/// </summary>
|
||||
/// <param name="sender">Unused</param>
|
||||
public void OnDeserialization(object sender)
|
||||
{
|
||||
Stack<TValue> deserializedValues =
|
||||
(Stack<TValue>)m_serializationInfo.GetValue(SerializedFieldName, typeof(Stack<TValue>));
|
||||
|
||||
m_store = ImmutableStack.CreateRange(deserializedValues);
|
||||
m_serializationInfo = null;
|
||||
}
|
||||
|
||||
|
||||
private const string SerializedFieldName = "Store";
|
||||
}
|
||||
}
|
|
@ -1,15 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.Omex.System.Configuration.DataSets;
|
||||
using Microsoft.Omex.System.Data;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.Shared.Configuration.DataSets
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Microsoft.Omex.System.Configuration.DataSets;
|
||||
using Microsoft.Omex.System.Data;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.Shared.Configuration.DataSets
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -12,8 +10,6 @@ using Microsoft.Omex.System.Data.FileSystem;
|
|||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.UnitTests.Shared.Data.FileSystem;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.Shared.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using Microsoft.Omex.System.Data.FileSystem;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.Shared.Data.FileSystem
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Omex.System.Data;
|
||||
using Microsoft.Omex.System.UnitTests.Shared.Configuration;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.Shared.Data
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.Shared.Data.FileSystem
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -8,12 +8,10 @@
|
|||
<ItemGroup Condition="'$(TargetFramework)' != '$(NetStandardVersion)'">
|
||||
<PackageReference Include="xunit.assert" Version="$(XunitAssertVersion)" />
|
||||
<PackageReference Include="xunit.core" Version="$(XunitCoreVersion)" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersion)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == '$(NetStandardVersion)'">
|
||||
<PackageReference Include="xunit.assert" Version="$(XunitAssertVersionCore)" />
|
||||
<PackageReference Include="xunit.core" Version="$(XunitCoreVersionCore)" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersionCore)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\System\Microsoft.Omex.System.csproj" />
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Omex.System.TimedScopes;
|
||||
using Microsoft.Omex.System.TimedScopes.ReplayEventLogging;
|
||||
using Microsoft.Omex.System.UnitTests.Shared.Diagnostics;
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.Shared.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Bridge for tests accessing internal classes.
|
||||
/// </summary>
|
||||
public static class TestHooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Default scope name.
|
||||
/// </summary>
|
||||
public static string DefaultTimedScopeName = TimedScopes.DefaultScope.Name;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a default timed scope.
|
||||
/// </summary>
|
||||
/// <param name="scopeLogger"></param>
|
||||
/// <param name="replayEventConfigurator"></param>
|
||||
/// <param name="initialResult">Initial scope result.</param>
|
||||
/// <param name="startScope">Start scope implicitly.</param>
|
||||
/// <returns>The created scope.</returns>
|
||||
public static TimedScope CreateDefaultTimedScope(
|
||||
ITimedScopeLogger scopeLogger = null,
|
||||
IReplayEventConfigurator replayEventConfigurator = null,
|
||||
bool? initialResult = null,
|
||||
bool startScope = true)
|
||||
{
|
||||
CorrelationData data = new CorrelationData();
|
||||
|
||||
return UnitTestTimedScopes.DefaultScope.Create(data, new UnitTestMachineInformation(), initialResult, startScope, scopeLogger, replayEventConfigurator);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of the test counters timed scope.
|
||||
/// </summary>
|
||||
/// <param name="initialResult">Initial scope result.</param>
|
||||
/// <param name="startScope">Start scope implicitly.</param>
|
||||
/// <param name="scopeLogger"></param>
|
||||
/// <param name="replayEventConfigurator"></param>
|
||||
/// <returns>The created scope.</returns>
|
||||
public static TimedScope CreateTestCountersUnitTestTimedScope(
|
||||
bool? initialResult = null,
|
||||
bool startScope = true,
|
||||
ITimedScopeLogger scopeLogger = null,
|
||||
IReplayEventConfigurator replayEventConfigurator = null)
|
||||
{
|
||||
|
||||
CorrelationData data = new CorrelationData();
|
||||
|
||||
return UnitTestTimedScopes.TestCounters.UnitTest.Create(data, new UnitTestMachineInformation(), initialResult, startScope, scopeLogger, replayEventConfigurator);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using Microsoft.Omex.System.TimedScopes;
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.Shared.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents TimedScope event for easier validating TimedScopes in UTs
|
||||
/// </summary>
|
||||
public class TimedScopeLogEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Scope name
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Scope subtype
|
||||
/// </summary>
|
||||
public string SubType { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Scope metadata
|
||||
/// </summary>
|
||||
public string MetaData { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// User hash
|
||||
/// </summary>
|
||||
public string UserHash { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Duration
|
||||
/// </summary>
|
||||
public TimeSpan Duration { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Scope result
|
||||
/// </summary>
|
||||
public TimedScopeResult Result { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Failure Description (strongly typed)
|
||||
/// </summary>
|
||||
public Enum FailureDescriptionEnum { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Failure Description
|
||||
/// </summary>
|
||||
public string FailureDescription => FailureDescriptionEnum?.ToString();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="name">Scope name</param>
|
||||
/// <param name="subtype">Subtype</param>
|
||||
/// <param name="metadata">Metadata</param>
|
||||
/// <param name="result">Scope result</param>
|
||||
/// <param name="failureDescription">Failure description</param>
|
||||
/// <param name="userHash">User hash</param>
|
||||
/// <param name="duration">Duration</param>
|
||||
public TimedScopeLogEvent(string name, string subtype, string metadata,
|
||||
TimedScopeResult result, Enum failureDescription, string userHash,
|
||||
TimeSpan duration)
|
||||
{
|
||||
Name = name;
|
||||
SubType = subtype;
|
||||
Result = result;
|
||||
MetaData = metadata;
|
||||
FailureDescriptionEnum = failureDescription;
|
||||
UserHash = userHash;
|
||||
Duration = duration;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.CodeDom.Compiler;
|
||||
using Microsoft.Omex.System.TimedScopes;
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.Shared.TimedScopes
|
||||
{
|
||||
internal static class UnitTestTimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// TryGetHash Timed Scope
|
||||
/// </summary>
|
||||
public static TimedScopeDefinition DefaultScope => new TimedScopeDefinition("OmexDefaultTimedScope",
|
||||
"Omex default timed scope");
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Test counters
|
||||
/// </summary>
|
||||
public static class TestCounters
|
||||
{
|
||||
/// <summary>
|
||||
/// Unit test timed scope
|
||||
/// </summary>
|
||||
public static TimedScopeDefinition UnitTest => new TimedScopeDefinition("UnitTest",
|
||||
"Omex unit test timed scope");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Timed scopes
|
||||
/// </summary>
|
||||
internal static class TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// TryGetHash Timed Scope
|
||||
/// </summary>
|
||||
public static TimedScopeDefinition DefaultScope => new TimedScopeDefinition("OmexDefaultTimedScope",
|
||||
"Tries to retrieve a dictionary of values from Omex Redis Cache.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Omex.System.TimedScopes;
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.Shared.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Unit test implementation of ITimedScopeLogger
|
||||
/// </summary>
|
||||
public class UnitTestTimedScopeLogger : ITimedScopeLogger
|
||||
{
|
||||
private readonly ConcurrentQueue<TimedScopeLogEvent> m_events = new ConcurrentQueue<TimedScopeLogEvent>();
|
||||
|
||||
/// <summary>
|
||||
/// Logs the scope start
|
||||
/// </summary>
|
||||
/// <param name="scope">Scope to log</param>
|
||||
public void LogScopeStart(TimedScope scope)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Logs the scope end
|
||||
/// </summary>
|
||||
/// <param name="scope">Scope to log</param>
|
||||
/// <param name="data">Correlation data</param>
|
||||
public void LogScopeEnd(TimedScope scope, CorrelationData data)
|
||||
{
|
||||
if (scope == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TimedScopeLogEvent evt = new TimedScopeLogEvent(scope.Name, scope.SubType,
|
||||
scope.MetaData, scope.Result, scope.FailureDescription,
|
||||
data.Data(TimedScopeDataKeys.InternalOnly.UserHash),
|
||||
scope.Duration ?? TimeSpan.Zero);
|
||||
m_events.Enqueue(evt);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last scope event of given name logged
|
||||
/// </summary>
|
||||
/// <param name="scopeName">Scope name</param>
|
||||
/// <returns>The last timed scope log event, null if there is no timed scope event</returns>
|
||||
public TimedScopeLogEvent LastTimedScopeEvent(string scopeName)
|
||||
{
|
||||
return m_events.LastOrDefault(evt => string.Equals(evt.Name, scopeName, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the single scope event of given name logged
|
||||
/// </summary>
|
||||
/// <param name="scopeName">Scope name</param>
|
||||
/// <returns>The single timed scope log event, null if there is no or more than one scope event of given name</returns>
|
||||
public TimedScopeLogEvent SingleTimedScopeEvent(string scopeName)
|
||||
{
|
||||
return m_events.SingleOrDefault(evt => string.Equals(evt.Name, scopeName, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Logged events
|
||||
/// </summary>
|
||||
public IEnumerable<TimedScopeLogEvent> Events => m_events;
|
||||
|
||||
}
|
||||
}
|
|
@ -1,12 +1,8 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.Caching
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.Validation;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.Caching
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Omex.System.Data;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.Validation;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.Configuration.DataSets
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.Validation;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.Configuration.DataSets
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Omex.System.Data;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.Configuration.DataSets
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using Directives
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
|
@ -11,8 +9,6 @@ using System.Collections.Specialized;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.Context
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.Data.FileSystem
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.Data
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.Data
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
|
@ -10,8 +8,6 @@ using Microsoft.Omex.System.Extensions;
|
|||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.Validation;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.Data
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">$(NetStandardVersion)</TargetFrameworks>
|
||||
<Description>Contains common classes for Omex libraries.</Description>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Bond.Core.CSharp" Version="$(BondCoreCSharpVersion)" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersion)" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="NuGet Properties">
|
||||
<Title>Microsoft.Omex.System</Title>
|
||||
<DevelopmentDependency>True</DevelopmentDependency>
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using Directives
|
||||
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.Model.Types
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.Monads
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.Validation;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.Monads
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -0,0 +1,310 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using Microsoft.Omex.System.Context;
|
||||
using Microsoft.Omex.System.Diagnostics;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.Validation;
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Correlation class, handle in-memory correlation data for logging purposes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Allows correlation data to be set and retrieved on a calling context. ULS logging (when hooked up)
|
||||
/// only allows for the correlation id to be retrieved, this allows for additional data to be retrieved.
|
||||
/// Correlation data by default only applies to the current thread and is lost when a thread hop
|
||||
/// occurs (be it ASP.NET underlying thread hop or an explicit thread change). By calling
|
||||
/// CorrelationStart with cloned CorrelationData, the correlation can be transferred to the new
|
||||
/// thread.
|
||||
/// </remarks>
|
||||
public sealed class Correlation
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="correlationHandler">correlation handler, used to store correlation data in underlying logging system</param>
|
||||
/// <param name="callContextManager">call context manager, to handle context operations</param>
|
||||
/// <param name="machineInformation">machine information</param>
|
||||
public Correlation(ICorrelationStorage correlationHandler, ICallContextManager callContextManager, IMachineInformation machineInformation)
|
||||
{
|
||||
Code.ExpectsArgument(correlationHandler, nameof(correlationHandler), TaggingUtilities.ReserveTag(0x2381771b /* tag_96x21 */));
|
||||
Code.ExpectsArgument(callContextManager, nameof(callContextManager), TaggingUtilities.ReserveTag(0x2381771c /* tag_96x22 */));
|
||||
Code.ExpectsArgument(machineInformation, nameof(machineInformation), TaggingUtilities.ReserveTag(0x2381771d /* tag_96x23 */));
|
||||
|
||||
CorrelationHandler = correlationHandler;
|
||||
CallContextManager = callContextManager;
|
||||
MachineInformation = machineInformation;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Current call context
|
||||
/// </summary>
|
||||
private ICallContext CallContext => CallContextManager.CallContextHandler(MachineInformation);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the correlation has started
|
||||
/// </summary>
|
||||
public event EventHandler<CorrelationEventArgs> CorrelationStarted;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when correlation data has been added
|
||||
/// </summary>
|
||||
public event EventHandler<CorrelationEventArgs> CorrelationDataAdded;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the correlation has ended
|
||||
/// </summary>
|
||||
public event EventHandler<CorrelationEventArgs> CorrelationEnded;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Key used to store the correlation context in the current HttpContext
|
||||
/// </summary>
|
||||
private const string CorrelationContextKey = "ULSLogging.correlations";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Key used to store if direct logging should be used
|
||||
/// </summary>
|
||||
private const string LoggingContextKey = "ULSLogging.directLogging";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the current correlation if set
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidCastException">Data exists on the CallContext
|
||||
/// but it isn't a valid CorrelationData object.</exception>
|
||||
public CorrelationData CurrentCorrelation
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetCorrelationData();
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
ICallContext existingContext = CallContext.ExistingCallContext();
|
||||
if (existingContext != null)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
existingContext.Data[CorrelationContextKey] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
existingContext.Data.Remove(CorrelationContextKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Should log directly to the underlying log handler
|
||||
/// </summary>
|
||||
public bool ShouldLogDirectly
|
||||
{
|
||||
get
|
||||
{
|
||||
CorrelationData correlation = CurrentCorrelation;
|
||||
if (correlation?.ShouldLogDirectly == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ICallContext existingContext = CallContext.ExistingCallContext();
|
||||
if (existingContext != null)
|
||||
{
|
||||
object data;
|
||||
return existingContext.Data.TryGetValue(LoggingContextKey, out data) && data != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
CorrelationData correlation = CurrentCorrelation;
|
||||
if (correlation != null)
|
||||
{
|
||||
correlation.ShouldLogDirectly = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
ICallContext existingContext = CallContext.ExistingCallContext();
|
||||
if (existingContext != null)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
existingContext.Data[LoggingContextKey] = new object();
|
||||
}
|
||||
else
|
||||
{
|
||||
existingContext.Data.Remove(LoggingContextKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The correlation handler used to store the correlation
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Calling methods on this interface bypasses the
|
||||
/// event handlers for correlation and commits the correlation
|
||||
/// directly to the correlation handler.
|
||||
/// </remarks>
|
||||
public ICorrelationStorage CorrelationHandler { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The correlation handler used to store the correlation
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Calling methods on this interface bypasses the
|
||||
/// event handlers for correlation and commits the correlation
|
||||
/// directly to the correlation handler.
|
||||
/// </remarks>
|
||||
public ICallContextManager CallContextManager { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The correlation handler used to store the correlation
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Calling methods on this interface bypasses the
|
||||
/// event handlers for correlation and commits the correlation
|
||||
/// directly to the correlation handler.
|
||||
/// </remarks>
|
||||
public IMachineInformation MachineInformation { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Start a correlation
|
||||
/// </summary>
|
||||
/// <param name="data">correlation to set on the thread, null for default (new) correlation</param>
|
||||
public void CorrelationStart(CorrelationData data)
|
||||
{
|
||||
data = CorrelationHandler.CorrelationStart(data);
|
||||
if (data != null)
|
||||
{
|
||||
data.ParentCorrelation = CurrentCorrelation;
|
||||
if (data.ParentCorrelation == null)
|
||||
{
|
||||
data.ShouldLogDirectly = ShouldLogDirectly;
|
||||
}
|
||||
|
||||
CurrentCorrelation = data;
|
||||
|
||||
// Note: Creating a copy of the event handler to avoid multi-threaded race conditions
|
||||
// Not creating extension methods to avoid unnecessary creating of arguments if not set
|
||||
EventHandler<CorrelationEventArgs> correlationStarted = CorrelationStarted;
|
||||
if (correlationStarted != null)
|
||||
{
|
||||
correlationStarted(this, new CorrelationEventArgs(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Add data to the current correlation
|
||||
/// </summary>
|
||||
/// <remarks>If there is no current correlation, starts a new correlation</remarks>
|
||||
/// <param name="key">key (name) of the correlation</param>
|
||||
/// <param name="value">value of the added correlation data</param>
|
||||
public void CorrelationAdd(string key, string value)
|
||||
{
|
||||
Code.ExpectsNotNullOrWhiteSpaceArgument(key, nameof(key), TaggingUtilities.ReserveTag(0x2381771e /* tag_96x24 */));
|
||||
Code.ExpectsNotNullOrWhiteSpaceArgument(value, nameof(value), TaggingUtilities.ReserveTag(0x2381771f /* tag_96x25 */));
|
||||
|
||||
CorrelationData data = CurrentCorrelation;
|
||||
if (data == null)
|
||||
{
|
||||
CorrelationStart(null);
|
||||
data = CurrentCorrelation;
|
||||
}
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
string oldData = data.Data(key);
|
||||
|
||||
CorrelationHandler.CorrelationAdd(key, value, data);
|
||||
|
||||
EventHandler<CorrelationEventArgs> correlationDataAdded = CorrelationDataAdded;
|
||||
if (correlationDataAdded != null)
|
||||
{
|
||||
correlationDataAdded(this, new CorrelationEventArgs(data, key, oldData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get correlation data from a thread
|
||||
/// </summary>
|
||||
/// <param name="threadId">Id of the thread</param>
|
||||
/// <returns>Correlation data</returns>
|
||||
private CorrelationData GetCorrelationData(int? threadId = null)
|
||||
{
|
||||
ICallContext existingContext = CallContext.ExistingCallContext(threadId);
|
||||
if (existingContext != null)
|
||||
{
|
||||
object data;
|
||||
if (existingContext.Data.TryGetValue(CorrelationContextKey, out data))
|
||||
{
|
||||
return data as CorrelationData;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// End the correlation
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the thread</param>
|
||||
/// <param name="invokeEventHandler">Should we invoke the correlation ended event handler</param>
|
||||
public void CorrelationEnd(int? id = null, bool invokeEventHandler = true)
|
||||
{
|
||||
CorrelationData correlationData = GetCorrelationData(id);
|
||||
if (correlationData != null)
|
||||
{
|
||||
CurrentCorrelation = correlationData.ParentCorrelation;
|
||||
|
||||
CorrelationHandler.CorrelationEnd(correlationData);
|
||||
|
||||
if (invokeEventHandler)
|
||||
{
|
||||
EventHandler<CorrelationEventArgs> correlationEnded = CorrelationEnded;
|
||||
if (correlationEnded != null)
|
||||
{
|
||||
correlationEnded(this, new CorrelationEventArgs(correlationData));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Clear all correlations from the current thread
|
||||
/// </summary>
|
||||
public void CorrelationClear()
|
||||
{
|
||||
while (CurrentCorrelation != null)
|
||||
{
|
||||
CorrelationEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,562 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Specialized;
|
||||
using System.Threading;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.Validation;
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Correlation data used for logging purposes
|
||||
/// </summary>
|
||||
public sealed class CorrelationData
|
||||
{
|
||||
/// <summary>
|
||||
/// Dictionary storing additional correlation data, only used when
|
||||
/// non-standard correlation data (AddData) is used.
|
||||
/// </summary>
|
||||
/// <remarks>ListDictionary choosen since the correlation data
|
||||
/// should be small (less than 10 items).</remarks>
|
||||
private ListDictionary m_additionalData;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Parent correlation for nested correlations.
|
||||
/// </summary>
|
||||
private CorrelationData m_parentCorrelation;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Should log directly to the underlying log handler
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This forces any logging with this correlation data to bypass
|
||||
/// any logging handlers.
|
||||
/// </remarks>
|
||||
private bool m_shouldLogDirectly;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Is this correlation running in ULS replay mode
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When first set, all previous ULS traces are replayed under the tag_repl event Id, then
|
||||
/// subsequent events are also replayed until the end of the correlation; all events are written at Info level
|
||||
/// </remarks>
|
||||
private bool m_ulsReplay;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Information if previously cached ULS events for this correlation already been replayed
|
||||
/// </summary>
|
||||
private int m_cachedUlsEventsReplayed;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Event sequence number for guarenteed logging order within a correlation
|
||||
/// </summary>
|
||||
private long m_eventSequenceNumber;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Log event cache reference for triggering replay events.
|
||||
/// </summary>
|
||||
private readonly ILogEventCache m_logEventCache;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Root scopes have this sequence number (with high probability),
|
||||
/// non-root scopes have always a larger sequence number.
|
||||
/// </summary>
|
||||
public const int RootScopeBoundary = 1000;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Transaction Id key
|
||||
/// </summary>
|
||||
public const string TransactionIdKey = "TransactionId";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the correlation
|
||||
/// </summary>
|
||||
/// <remarks>Seting this value only modifies the in memory representation
|
||||
/// of the correlation data, to modify the actual correlation data, use
|
||||
/// Correlation.CorrelationStart(CorrelationData correlation).</remarks>
|
||||
public Guid VisibleId { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Transaction id for the correlation
|
||||
/// </summary>
|
||||
/// <remarks>If non-zero, indicates that the correlation data
|
||||
/// is used as part of a specific monitored transaction</remarks>
|
||||
public uint TransactionId { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Id of a registered ITransactionContext to apply
|
||||
/// </summary>
|
||||
public uint TransactionContextId { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Step number for a multi-step Transaction
|
||||
/// </summary>
|
||||
public uint TransactionStep { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Is this a fallback call
|
||||
/// </summary>
|
||||
public bool IsFallbackCall { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tracks the depth of the correlation through proxying layers
|
||||
/// Original caller is Depth 0, subsequent hops are +1 each time
|
||||
/// </summary>
|
||||
public uint CallDepth { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user hash for the current correlation. Only call the setter directly
|
||||
/// if you already have a hashed value, use ComputeUserHash to compute the hash
|
||||
/// of a user string and set it.
|
||||
/// This is set by:
|
||||
/// 1. External web service calls where we have direct access to information
|
||||
/// such as user PUID (e.g. POSA provisioning calls)
|
||||
/// 2. Microsoft.Office.Web.OfficeMarketplace.WebSite.RequestProcessHandler.StartRequest.
|
||||
/// 3. Microsoft.Office.Web.OfficeMarketplace.WebService.RequestProcessHandler.StartRequest.
|
||||
/// Its value is read and logged by <see cref="TimedScope.EndScope"/>.
|
||||
/// </summary>
|
||||
/// <remarks>Used to count timed scope hits by user.</remarks>
|
||||
public string UserHash { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Current event sequence number for this correlation
|
||||
/// </summary>
|
||||
public long EventSequenceNumber
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_eventSequenceNumber;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
m_eventSequenceNumber = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// All correlation data keys
|
||||
/// </summary>
|
||||
/// <remarks>Null if there are no correlation data keys</remarks>
|
||||
public ICollection Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_additionalData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_additionalData.Keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Does the correlation have any correlation data
|
||||
/// </summary>
|
||||
public bool HasData
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_additionalData == null ? false : m_additionalData.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Parent correlation for nested correlation
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Attempting to set
|
||||
/// the parent correlation when it is already set, or setting
|
||||
/// the parent correlation causes circular reference</exception>
|
||||
/// <remarks>Implemented as a parent node rather than stack
|
||||
/// as most correlation will never have a parent correlation.</remarks>
|
||||
public CorrelationData ParentCorrelation
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_parentCorrelation;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (this.ParentCorrelation != null)
|
||||
{
|
||||
throw new InvalidOperationException("Parent correlation is already set.");
|
||||
}
|
||||
|
||||
CorrelationData temp = value;
|
||||
while (temp != null)
|
||||
{
|
||||
if (temp == this)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot set the parent correlation as it will cause circular reference.");
|
||||
}
|
||||
|
||||
temp = temp.ParentCorrelation;
|
||||
}
|
||||
|
||||
m_parentCorrelation = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Allocates the next sequence number to be used for logging purposes
|
||||
/// </summary>
|
||||
/// <returns>Incremented sequence number unique within the correlation</returns>
|
||||
public long NextEventSequenceNumber()
|
||||
{
|
||||
return Interlocked.Increment(ref m_eventSequenceNumber);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Allocates the next sequence number to be used for logging the
|
||||
/// start of a new scope.
|
||||
/// </summary>
|
||||
/// <returns>Incremented sequence number unique within the correlation</returns>
|
||||
public long NextScopeStartSequenceNumber()
|
||||
{
|
||||
// Make the root scope of a correlation identifiable
|
||||
// by giving it a sequence of at least 'RootScopeBoundary'.
|
||||
// Note that this is only a heuristic, but avoids excessive code changes.
|
||||
if (m_eventSequenceNumber < RootScopeBoundary)
|
||||
{
|
||||
m_eventSequenceNumber = RootScopeBoundary;
|
||||
return m_eventSequenceNumber;
|
||||
}
|
||||
|
||||
return Interlocked.Increment(ref m_eventSequenceNumber);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Should log directly to the underlying log handler
|
||||
/// </summary>
|
||||
public bool ShouldLogDirectly
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_shouldLogDirectly || (ParentCorrelation != null && ParentCorrelation.ShouldLogDirectly);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
m_shouldLogDirectly = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines if ULS events should be replayed for the current correlation.
|
||||
/// Setting this to true will only affect events logged afterwards, see ReplayPreviouslyCachedUlsEvents below.
|
||||
/// One-way switch, cannot be set back to false.
|
||||
/// </summary>
|
||||
public bool ShouldReplayUls
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_ulsReplay;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
// this flag is a one way switch
|
||||
if (value == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_ulsReplay = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Have previously cached ULS events for this correlation already been replayed?
|
||||
/// </summary>
|
||||
public bool CachedUlsEventsReplayed
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_cachedUlsEventsReplayed == 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Replay previously cached ULS events for this correlation now.
|
||||
/// This should be called along with ShouldReplayUls = true to replay previous events in the correlation.
|
||||
/// Will only run once per correlation.
|
||||
/// </summary>
|
||||
public void ReplayPreviouslyCachedUlsEvents()
|
||||
{
|
||||
int originalValue = Interlocked.CompareExchange(ref m_cachedUlsEventsReplayed, 1, 0);
|
||||
|
||||
if (originalValue == 0)
|
||||
{
|
||||
m_logEventCache.ReplayCorrelation(VisibleId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the correlation data for a key
|
||||
/// </summary>
|
||||
/// <param name="key">key to get data for</param>
|
||||
/// <returns>string containing the data, null if it doesn't exist</returns>
|
||||
public string Data(string key)
|
||||
{
|
||||
if (m_additionalData == null || key == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (string)m_additionalData[key];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Add correlation data
|
||||
/// </summary>
|
||||
/// <param name="key">key to get data for</param>
|
||||
/// <param name="value">value of data</param>
|
||||
/// <remarks>Calling this method only modifies the in memory representation
|
||||
/// of the correlation data, to modify the actual correlation data, use
|
||||
/// Correlation.CorrelationAdd(string key, string value).</remarks>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> or <paramref name="value"/> is null</exception>
|
||||
public void AddData(string key, string value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException("key");
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("value");
|
||||
}
|
||||
|
||||
if (string.Equals(key, TransactionIdKey, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Caching the TransactionId specifically as it is likely to be queried for
|
||||
// repeatedly
|
||||
uint transactionId;
|
||||
if (uint.TryParse(value, out transactionId))
|
||||
{
|
||||
TransactionId = transactionId;
|
||||
}
|
||||
else
|
||||
{
|
||||
TransactionId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_additionalData == null)
|
||||
{
|
||||
m_additionalData = new ListDictionary(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
m_additionalData[key] = value;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="cachedUlsEventsReplayed">Have previously cached ULS events for this correlation already been replayed?</param>
|
||||
public CorrelationData(bool cachedUlsEventsReplayed = false)
|
||||
{
|
||||
m_cachedUlsEventsReplayed = cachedUlsEventsReplayed ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="logEventCache">Log event cache</param>
|
||||
/// <param name="cachedUlsEventsReplayed">Have previously cached ULS events for this correlation already been replayed?</param>
|
||||
public CorrelationData(ILogEventCache logEventCache, bool cachedUlsEventsReplayed = false)
|
||||
{
|
||||
Code.ExpectsArgument(logEventCache, nameof(logEventCache), TaggingUtilities.ReserveTag(0x2381771a /* tag_96x20 */));
|
||||
m_logEventCache = logEventCache;
|
||||
m_cachedUlsEventsReplayed = cachedUlsEventsReplayed ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Correlation data extension methods
|
||||
/// </summary>
|
||||
public static class CorrelationDataExtensionMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// Clone the correlation
|
||||
/// </summary>
|
||||
/// <param name="existingCorrelation">existing correlation</param>
|
||||
/// <returns>cloned correlation, null if existing correlation is null</returns>
|
||||
/// <remarks>Added as a extension method instead of a method on the
|
||||
/// object to allow for cloning of the data when it is null, i.e.
|
||||
/// CorrelationData data = null; data.Clone(); will not throw exception.</remarks>
|
||||
public static CorrelationData Clone(this CorrelationData existingCorrelation)
|
||||
{
|
||||
if (existingCorrelation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
CorrelationData newCorrelation = new CorrelationData(existingCorrelation.CachedUlsEventsReplayed);
|
||||
newCorrelation.ShouldLogDirectly = existingCorrelation.ShouldLogDirectly;
|
||||
newCorrelation.ShouldReplayUls = existingCorrelation.ShouldReplayUls;
|
||||
newCorrelation.VisibleId = existingCorrelation.VisibleId;
|
||||
newCorrelation.CallDepth = existingCorrelation.CallDepth;
|
||||
newCorrelation.UserHash = existingCorrelation.UserHash;
|
||||
newCorrelation.EventSequenceNumber = existingCorrelation.EventSequenceNumber;
|
||||
newCorrelation.IsFallbackCall = existingCorrelation.IsFallbackCall;
|
||||
newCorrelation.TransactionContextId = existingCorrelation.TransactionContextId;
|
||||
newCorrelation.TransactionId = existingCorrelation.TransactionId;
|
||||
newCorrelation.TransactionStep = existingCorrelation.TransactionStep;
|
||||
|
||||
if (existingCorrelation.HasData)
|
||||
{
|
||||
bool copiedSuccessfully = SpinWait.SpinUntil(() =>
|
||||
{
|
||||
bool success = false;
|
||||
try
|
||||
{
|
||||
foreach (object key in existingCorrelation.Keys)
|
||||
{
|
||||
string keystring = (string)key;
|
||||
newCorrelation.AddData(keystring, existingCorrelation.Data(keystring));
|
||||
}
|
||||
|
||||
success = true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
}
|
||||
|
||||
return success;
|
||||
}, 10);
|
||||
|
||||
if (!copiedSuccessfully)
|
||||
{
|
||||
// Add a marker to the correlation data indicating it is not complete
|
||||
newCorrelation.AddData("Error", "Failed to clone correlation data.");
|
||||
}
|
||||
}
|
||||
|
||||
return newCorrelation;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new TransactionData object from the current CorrelationData object.
|
||||
/// </summary>
|
||||
/// <param name="correlationData">The CorrelationData object to copy when creating the TransactionData object.</param>
|
||||
/// <returns>A new TransactionData copy of the supplied CorrelationData or null if the current CorrelationData is null.</returns>
|
||||
public static TransactionData ToTransactionData(this CorrelationData correlationData)
|
||||
{
|
||||
if (correlationData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TransactionData()
|
||||
{
|
||||
CallDepth = correlationData.CallDepth,
|
||||
CorrelationId = correlationData.VisibleId,
|
||||
EventSequenceNumber = correlationData.EventSequenceNumber,
|
||||
TransactionContextId = correlationData.TransactionContextId,
|
||||
TransactionId = correlationData.TransactionId,
|
||||
TransactionStep = correlationData.TransactionStep,
|
||||
UserHash = correlationData.UserHash,
|
||||
IsFallbackCall = correlationData.IsFallbackCall
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Clear the test type resolver
|
||||
/// </summary>
|
||||
/// <param name="correlationData">correlation data</param>
|
||||
/// <returns>test type resolver in use</returns>
|
||||
public static IStructuralTypeResolver ClearTestTypeResolver(this CorrelationData correlationData)
|
||||
{
|
||||
if (correlationData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string uniqueId = correlationData.Data(TypeResolverKey);
|
||||
if (!string.IsNullOrWhiteSpace(uniqueId))
|
||||
{
|
||||
IStructuralTypeResolver testResolver;
|
||||
if (s_testResolvers.TryRemove(uniqueId, out testResolver))
|
||||
{
|
||||
return testResolver;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the current test type resolver
|
||||
/// </summary>
|
||||
/// <param name="correlationData">correlation data</param>
|
||||
/// <returns>test type resolver in use</returns>
|
||||
public static IStructuralTypeResolver GetTestTypeResolver(this CorrelationData correlationData)
|
||||
{
|
||||
if (correlationData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string uniqueId = correlationData.Data(TypeResolverKey);
|
||||
if (!string.IsNullOrWhiteSpace(uniqueId))
|
||||
{
|
||||
IStructuralTypeResolver testResolver;
|
||||
if (s_testResolvers.TryGetValue(uniqueId, out testResolver))
|
||||
{
|
||||
return testResolver;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private const string TypeResolverKey = "Test.TypeResolver";
|
||||
|
||||
|
||||
private static readonly ConcurrentDictionary<string, IStructuralTypeResolver> s_testResolvers = new ConcurrentDictionary<string, IStructuralTypeResolver>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Event arguments used with correlation events
|
||||
/// </summary>
|
||||
public class CorrelationEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Correlation data
|
||||
/// </summary>
|
||||
public CorrelationData Correlation { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// If the correlation data has changed, the key of the data that changed
|
||||
/// </summary>
|
||||
public string ChangedKey { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// If the correlation data has changed, the old value of the data
|
||||
/// </summary>
|
||||
public string OldData { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="data">correlation data</param>
|
||||
/// <param name="key">changed key</param>
|
||||
/// <param name="oldData">the previous value of the data</param>
|
||||
public CorrelationEventArgs(CorrelationData data, string key, string oldData)
|
||||
{
|
||||
Correlation = data;
|
||||
ChangedKey = key;
|
||||
OldData = oldData;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="data">correlation data</param>
|
||||
public CorrelationEventArgs(CorrelationData data)
|
||||
{
|
||||
Correlation = data;
|
||||
ChangedKey = null;
|
||||
OldData = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Omex.System.Context;
|
||||
using Microsoft.Omex.System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for handling call context
|
||||
/// </summary>
|
||||
public interface ICallContextManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Current call context
|
||||
/// </summary>
|
||||
ICallContext CallContextHandler(IMachineInformation machineInformation);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An overridable call context for unit test purposes
|
||||
/// </summary>
|
||||
ICallContext CallContextOverride { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for handling correlation
|
||||
/// </summary>
|
||||
public interface ICorrelationStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// Start correlation
|
||||
/// </summary>
|
||||
/// <param name="data">existing correlation data</param>
|
||||
/// <returns>correlation data</returns>
|
||||
CorrelationData CorrelationStart(CorrelationData data);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Add correlation data
|
||||
/// </summary>
|
||||
/// <param name="key">key</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <param name="data">existing correlation data</param>
|
||||
void CorrelationAdd(string key, string value, CorrelationData data);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// End correlation
|
||||
/// </summary>
|
||||
/// <param name="data">correlation data</param>
|
||||
void CorrelationEnd(CorrelationData data);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for a log event cache with log replay logic.
|
||||
/// </summary>
|
||||
public interface ILogEventCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Next process-wide sequence number for event caching
|
||||
/// </summary>
|
||||
long NextSequenceNumber { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Add a log event to the cache and replay if required.
|
||||
/// </summary>
|
||||
/// <param name="logEventArgs">Log event</param>
|
||||
void AddLogEvent(LogEventArgs logEventArgs);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Query the events for a specified correlation
|
||||
/// </summary>
|
||||
/// <param name="correlation">Target correlation Id</param>
|
||||
/// <returns>list of trace events</returns>
|
||||
IReadOnlyList<LogEventArgs> GetEventsForCorrelation(Guid correlation);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Query the trace for a specified correlation
|
||||
/// </summary>
|
||||
/// <param name="correlation">Target correlation Id</param>
|
||||
/// <returns>list of trace events</returns>
|
||||
IReadOnlyList<LogEntry> GetTraceForCorrelation(Guid correlation);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Query the trace for entries starting at a specified sequence number
|
||||
/// </summary>
|
||||
/// <param name="sequenceNumber">Start number</param>
|
||||
/// <returns>enumeration of trace events</returns>
|
||||
IEnumerable<LogEntry> GetTraceFromSequenceNumber(long sequenceNumber);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Replay a specified correlation
|
||||
/// </summary>
|
||||
/// <param name="correlation">Target correlation Id</param>
|
||||
void ReplayCorrelation(Guid correlation);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface to resolve a type to an instance, with parent inheritance
|
||||
/// </summary>
|
||||
public interface IStructuralTypeResolver : ITypeResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Parent resolver
|
||||
/// </summary>
|
||||
IStructuralTypeResolver Parent { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resolve type 'TFrom' to an instance of type 'TFrom' in context of the current call
|
||||
/// </summary>
|
||||
/// <typeparam name="TFrom">type to resolve</typeparam>
|
||||
/// <returns>instance of type 'TFrom'</returns>
|
||||
TFrom ResolveInCallContext<TFrom>()
|
||||
where TFrom : class;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Is the type 'TFrom' known to the type resolver in context of the current call
|
||||
/// </summary>
|
||||
/// <typeparam name="TFrom">type to resolve</typeparam>
|
||||
/// <param name="instance">the stored value, if one exists</param>
|
||||
/// <returns>true if a type is known to the resover, false otherwise</returns>
|
||||
bool DoesTypeExistInCallContext<TFrom>(out TFrom instance)
|
||||
where TFrom : class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for logging timed scopes
|
||||
/// </summary>
|
||||
public interface ITimedScopeLogger
|
||||
{
|
||||
/// <summary>
|
||||
/// Logs the scope start
|
||||
/// </summary>
|
||||
/// <param name="scope">Scope to log</param>
|
||||
void LogScopeStart(TimedScope scope);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Logs the scope end
|
||||
/// </summary>
|
||||
/// <param name="scope">Scope to log</param>
|
||||
/// <param name="data">Correlation data</param>
|
||||
void LogScopeEnd(TimedScope scope, CorrelationData data);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright(c) Microsoft Corporation.All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface to resolve a type to an instance
|
||||
/// </summary>
|
||||
public interface ITypeResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolve type 'TFrom' to an instance of type 'TFrom'
|
||||
/// </summary>
|
||||
/// <typeparam name="TFrom">type to resolve</typeparam>
|
||||
/// <returns>instance of type 'TFrom'</returns>
|
||||
TFrom Resolve<TFrom>()
|
||||
where TFrom : class;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Is the type 'TFrom' known to the type resolver
|
||||
/// </summary>
|
||||
/// <typeparam name="TFrom">type to resolve</typeparam>
|
||||
/// <param name="instance">the stored value, if one exists</param>
|
||||
/// <returns>true if a type is known to the resover, false otherwise</returns>
|
||||
bool DoesTypeExist<TFrom>(out TFrom instance)
|
||||
where TFrom : class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Data Contract for a single ULS Log Entry
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[Serializable]
|
||||
public sealed class LogEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Logging category name
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string CategoryName { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// CorrelationId this entry is part of
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public Guid CorrelationId { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ULS Message
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string Message { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ULS TagId in numeric format
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public uint NumericTagId { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Unique Sequence Number for this event in the context of the host service type
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public long SequenceNumber { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Server Timestamp (Stopwatch.GetTimestamp) when event occurred
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public long ServerTimestamp { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Server UTC Time when event occurred
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public DateTime ServerTimeUtc { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ULS TagId in string format
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string TagId { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ULS trace level
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string TraceLevel { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// In-memory correlation handler
|
||||
/// </summary>
|
||||
public class MemoryCorrelationHandler : ICorrelationStorage
|
||||
{
|
||||
/// <summary>
|
||||
/// Start correlation
|
||||
/// </summary>
|
||||
/// <param name="data">existing correlation data</param>
|
||||
/// <returns>correlation data</returns>
|
||||
public CorrelationData CorrelationStart(CorrelationData data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
data = new CorrelationData();
|
||||
data.VisibleId = Guid.NewGuid();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Add correlation data
|
||||
/// </summary>
|
||||
/// <param name="key">key</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <param name="data">existing correlation data</param>
|
||||
public void CorrelationAdd(string key, string value, CorrelationData data)
|
||||
{
|
||||
if (data != null)
|
||||
{
|
||||
data.AddData(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// End correlation
|
||||
/// </summary>
|
||||
/// <param name="data">correlation data</param>
|
||||
public void CorrelationEnd(CorrelationData data)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,309 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a class that captures Performance Diagnostics.
|
||||
/// </summary>
|
||||
public class PerfDiagnostics
|
||||
{
|
||||
/// <summary>
|
||||
/// Status of the last timer run
|
||||
/// </summary>
|
||||
public bool LastStatus { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Number of cycles used (updated after Stop is called)
|
||||
/// </summary>
|
||||
public long CyclesUsed { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Approximate number of milliseconds spent in User Mode
|
||||
/// </summary>
|
||||
public decimal UserModeMilliseconds { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Approximate number of milliseconds spent in Kernel Mode
|
||||
/// </summary>
|
||||
public decimal KernelModeMilliseconds { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Backing field for number of http requests
|
||||
/// </summary>
|
||||
private int m_httpRequests;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Number of HttpRequests
|
||||
/// </summary>
|
||||
public int HttpRequestCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_httpRequests;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Backing field for number of service calls
|
||||
/// </summary>
|
||||
private int m_serviceCalls;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Number of service calls
|
||||
/// </summary>
|
||||
public int ServiceCallCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_serviceCalls;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Psuedo handle to the current thread
|
||||
/// </summary>
|
||||
private static readonly IntPtr s_currentThreadHandle = NativeMethods.GetCurrentThread();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Parent diagnostics if available
|
||||
/// </summary>
|
||||
private readonly PerfDiagnostics m_parent;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Id of the thread being timed
|
||||
/// </summary>
|
||||
private uint m_threadId;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Kernel mode ticks at start of timing
|
||||
/// </summary>
|
||||
private long m_startKernel;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// User mode ticks at start of timing
|
||||
/// </summary>
|
||||
private long m_startUser;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Cpu cycles at start of timing
|
||||
/// </summary>
|
||||
private ulong m_startCycles;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="parent">Parent diagnostics, or null if not available</param>
|
||||
public PerfDiagnostics(PerfDiagnostics parent = null)
|
||||
{
|
||||
m_parent = parent;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reset the state of the Timer
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
CyclesUsed = 0;
|
||||
UserModeMilliseconds = 0;
|
||||
KernelModeMilliseconds = 0;
|
||||
LastStatus = false;
|
||||
m_threadId = uint.MaxValue;
|
||||
m_httpRequests = 0;
|
||||
m_serviceCalls = 0;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Start the timer
|
||||
/// </summary>
|
||||
/// <returns>Success status</returns>
|
||||
public bool Start()
|
||||
{
|
||||
m_threadId = uint.MaxValue;
|
||||
|
||||
long creationTime, exitTime;
|
||||
|
||||
if (!NativeMethods.GetThreadTimes(s_currentThreadHandle, out creationTime, out exitTime, out m_startKernel, out m_startUser))
|
||||
{
|
||||
ULSLogging.LogTraceTag(0x23817716 /* tag_96x2w */, Categories.Infrastructure, Levels.Error, "GetThreadTimes system call failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!NativeMethods.QueryThreadCycleTime(s_currentThreadHandle, out m_startCycles))
|
||||
{
|
||||
ULSLogging.LogTraceTag(0x23817717 /* tag_96x2x */, Categories.Infrastructure, Levels.Error, "QueryThreadCycleTime system call failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_threadId = NativeMethods.GetCurrentThreadId();
|
||||
LastStatus = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get timing metrics since Start was called
|
||||
/// </summary>
|
||||
/// <returns>Success status</returns>
|
||||
public bool Stop()
|
||||
{
|
||||
LastStatus = false;
|
||||
|
||||
if (NativeMethods.GetCurrentThreadId() != m_threadId)
|
||||
{
|
||||
// Thread Id mismatch between Start and Stop.
|
||||
// This is expected in async methods so not logging anything, would be too much spam.
|
||||
m_threadId = uint.MaxValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_threadId = uint.MaxValue;
|
||||
|
||||
long creationTime, exitTime, kernelModeTime, userModeTime;
|
||||
|
||||
if (!NativeMethods.GetThreadTimes(s_currentThreadHandle, out creationTime, out exitTime, out kernelModeTime, out userModeTime))
|
||||
{
|
||||
ULSLogging.LogTraceTag(0x23817718 /* tag_96x2y */, Categories.Infrastructure, Levels.Error, "GetThreadTimes system call failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong cycles;
|
||||
|
||||
if (!NativeMethods.QueryThreadCycleTime(s_currentThreadHandle, out cycles))
|
||||
{
|
||||
ULSLogging.LogTraceTag(0x23817719 /* tag_96x2z */, Categories.Infrastructure, Levels.Error, "QueryThreadCycleTime system call failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
LastStatus = true;
|
||||
CyclesUsed += m_startCycles >= cycles ? 0 : (long)(cycles - m_startCycles);
|
||||
KernelModeMilliseconds += m_startKernel >= kernelModeTime ? 0 : (decimal)(kernelModeTime - m_startKernel) / 10000m;
|
||||
UserModeMilliseconds += m_startUser >= userModeTime ? 0 : (decimal)(userModeTime - m_startUser) / 10000m;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Suspend Cpu timer tree
|
||||
/// </summary>
|
||||
public void Suspend()
|
||||
{
|
||||
Stop();
|
||||
|
||||
if (m_parent != null)
|
||||
{
|
||||
m_parent.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resume Cpu timer tree
|
||||
/// </summary>
|
||||
public void Resume()
|
||||
{
|
||||
Start();
|
||||
|
||||
if (m_parent != null)
|
||||
{
|
||||
m_parent.Start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Increment the number of Service Calls
|
||||
/// </summary>
|
||||
public void IncrementServiceCallCount()
|
||||
{
|
||||
if (m_parent != null)
|
||||
{
|
||||
m_parent.IncrementServiceCallCount();
|
||||
}
|
||||
|
||||
Interlocked.Increment(ref m_serviceCalls);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Increment the number of Http Requests
|
||||
/// </summary>
|
||||
public void IncrementHttpRequestCount()
|
||||
{
|
||||
if (m_parent != null)
|
||||
{
|
||||
m_parent.IncrementHttpRequestCount();
|
||||
}
|
||||
|
||||
Interlocked.Increment(ref m_httpRequests);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Private Windows API definitions
|
||||
/// </summary>
|
||||
private static class NativeMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Id of the current native thread
|
||||
/// </summary>
|
||||
/// <returns>Thread Id</returns>
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern uint GetCurrentThreadId();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a psuedo handle to the current native thread
|
||||
/// </summary>
|
||||
/// <returns>Psuedo handle, doesn't need to be closed</returns>
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern IntPtr GetCurrentThread();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets Cpu times for the specified native thread, accurate to around 15ms
|
||||
/// </summary>
|
||||
/// <param name="thread">Target thread</param>
|
||||
/// <param name="creationTime">Creation time as 100-nanosecond intervals since January 1, 1601 (UTC)</param>
|
||||
/// <param name="exitTime">Exit time as 100-nanosecond intervals since January 1, 1601 (UTC)</param>
|
||||
/// <param name="kernelModeTime">Number of 100-nanosecond intervals thread has spent in Kernel mode</param>
|
||||
/// <param name="userModeTime">Number of 100-nanosecond intervals thread has spent in User mode</param>
|
||||
/// <returns>Success status</returns>
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern bool GetThreadTimes(IntPtr thread, out long creationTime, out long exitTime, out long kernelModeTime, out long userModeTime);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Query the current Cpu cycle count for a specified thread
|
||||
/// </summary>
|
||||
/// <param name="thread">Target thread</param>
|
||||
/// <param name="cycles">Number of cycles</param>
|
||||
/// <returns>Success status</returns>
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern bool QueryThreadCycleTime(IntPtr thread, out ulong cycles);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes.ReplayEventLogging
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures event replaying when a timed scope ends
|
||||
/// </summary>
|
||||
public interface IReplayEventConfigurator
|
||||
{
|
||||
/// <summary>
|
||||
/// Configure event replaying when a timed scope ends
|
||||
/// </summary>
|
||||
/// <param name="scope"></param>
|
||||
void ConfigureReplayEventsOnScopeEnd(TimedScope scope);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes.ReplayEventLogging
|
||||
{
|
||||
/// <summary>
|
||||
/// Decides if a timed scope is disabled for event replaying
|
||||
/// </summary>
|
||||
public interface IReplayEventDisabledTimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Is the timed scope disabled for event replaying
|
||||
/// </summary>
|
||||
/// <param name="scopeDefinition">Scope definition</param>
|
||||
/// <returns><c>true</c> if the scope is disabled, <c>false</c> otherwise.</returns>
|
||||
bool IsDisabled(TimedScopeDefinition scopeDefinition);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.Validation;
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes.ReplayEventLogging
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures event replaying when a timed scope ends
|
||||
/// </summary>
|
||||
public class ReplayEventConfigurator : IReplayEventConfigurator
|
||||
{
|
||||
private IReplayEventDisabledTimedScopes DisabledTimedScopes { get; }
|
||||
|
||||
|
||||
private Correlation Correlation { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="disabledTimedScopes">Disabled timed scopes</param>
|
||||
/// <param name="correlation">Correlation</param>
|
||||
public ReplayEventConfigurator(IReplayEventDisabledTimedScopes disabledTimedScopes, Correlation correlation)
|
||||
{
|
||||
Code.ExpectsArgument(disabledTimedScopes, nameof(disabledTimedScopes), TaggingUtilities.ReserveTag(0x23817714 /* tag_96x2u */));
|
||||
Code.ExpectsArgument(correlation, nameof(correlation), TaggingUtilities.ReserveTag(0x23817715 /* tag_96x2v */));
|
||||
|
||||
DisabledTimedScopes = disabledTimedScopes;
|
||||
Correlation = correlation;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Configure event replaying when a timed scope ends
|
||||
/// </summary>
|
||||
/// <param name="scope"></param>
|
||||
public void ConfigureReplayEventsOnScopeEnd(TimedScope scope)
|
||||
{
|
||||
CorrelationData currentCorrelation = Correlation.CurrentCorrelation;
|
||||
|
||||
if (scope.IsSuccessful ?? false)
|
||||
{
|
||||
// assumption is that if any lower level scopes fail that should bubble up to the parent scope; if replay is enabled a previous scope has failed so
|
||||
// log some telemetry to help us understand these mixed scenarios better / identify error handling bugs
|
||||
if (currentCorrelation != null && currentCorrelation.ShouldReplayUls)
|
||||
{
|
||||
// ASSERTTAG_IGNORE_START
|
||||
ULSLogging.LogTraceTag(0, Categories.TimingGeneral, Levels.Warning,
|
||||
"Scope '{0}' succeeded even though a previous scope on this correlation failed.", scope.Name);
|
||||
// ASSERTTAG_IGNORE_FINISH
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// flip the replay switch on Scope failure for scenarios where its useful to get a verbose ULS trace in production
|
||||
if (currentCorrelation != null &&
|
||||
scope.Result.ShouldReplayEvents() &&
|
||||
!scope.IsTransaction &&
|
||||
!scope.ScopeDefinition.OnDemand &&
|
||||
!scope.DisableVerboseUlsCapture &&
|
||||
!DisabledTimedScopes.IsDisabled(scope.ScopeDefinition))
|
||||
{
|
||||
currentCorrelation.ShouldReplayUls = true;
|
||||
currentCorrelation.ReplayPreviouslyCachedUlsEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,983 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using Microsoft.Omex.System.Context;
|
||||
using Microsoft.Omex.System.Diagnostics;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.TimedScopes.ReplayEventLogging;
|
||||
using Microsoft.Omex.System.Validation;
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for managing timed scopes, writing to performance
|
||||
/// monitor and loggign to ULS if test transactions are running
|
||||
/// </summary>
|
||||
public class TimedScope : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="scopeDefinition">Timed scope definition</param>
|
||||
/// <param name="scopeLogger">Scope metrics logger</param>
|
||||
/// <param name="replayEventConfigurator">Replay event configurator</param>
|
||||
/// <param name="machineInformation">Machine Information</param>
|
||||
/// <param name="correlationData">Correlation data</param>
|
||||
private TimedScope(TimedScopeDefinition scopeDefinition, CorrelationData correlationData, ITimedScopeLogger scopeLogger,
|
||||
IReplayEventConfigurator replayEventConfigurator, IMachineInformation machineInformation)
|
||||
{
|
||||
Code.ExpectsArgument(scopeDefinition, nameof(scopeDefinition), TaggingUtilities.ReserveTag(0x23817708 /* tag_96x2i */));
|
||||
Code.ExpectsArgument(scopeLogger, nameof(scopeLogger), TaggingUtilities.ReserveTag(0x23817709 /* tag_96x2j */));
|
||||
Code.ExpectsArgument(replayEventConfigurator, nameof(replayEventConfigurator), TaggingUtilities.ReserveTag(0x2381770a /* tag_96x2k */));
|
||||
|
||||
ScopeDefinition = scopeDefinition;
|
||||
ScopeLogger = scopeLogger;
|
||||
ReplayEventConfigurator = replayEventConfigurator;
|
||||
CorrelationData = correlationData;
|
||||
MachineInformation = machineInformation;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a timed scope
|
||||
/// </summary>
|
||||
/// <param name="scopeDefinition">Timed scope definition</param>
|
||||
/// <param name="initialResult">The default result for the scope</param>
|
||||
/// <param name="customLogger">Use a custom logger for the timed scope</param>
|
||||
/// <param name="replayEventConfigurator">Replay event configurator</param>
|
||||
/// <param name="correlationData">Correlation data</param>
|
||||
/// <param name="machineInformation">Machine Information</param>
|
||||
/// <returns>Newly created scope</returns>
|
||||
public static TimedScope Create(TimedScopeDefinition scopeDefinition, CorrelationData correlationData, IMachineInformation machineInformation,
|
||||
TimedScopeResult initialResult = default(TimedScopeResult), ITimedScopeLogger customLogger = null, IReplayEventConfigurator replayEventConfigurator = null)
|
||||
{
|
||||
return new TimedScope(scopeDefinition, correlationData, customLogger, replayEventConfigurator, machineInformation)
|
||||
{
|
||||
TimedScopeData = correlationData,
|
||||
RunningTransaction = TransactionMonitor.RunningTransaction(correlationData),
|
||||
Result = initialResult,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Deprecated - Start a timed scope
|
||||
/// </summary>
|
||||
/// <remarks>Please use TimedScopeDefinition for creating timed scopes</remarks>
|
||||
/// <param name="correlationData">Correlation data</param>
|
||||
/// <param name="machineInformation">Machine Information</param>
|
||||
/// <param name="scopeName">The name of the timed scope</param>
|
||||
/// <param name="initialResult">The default result for the scope</param>
|
||||
/// <param name="customLogger">Use a custom logger for the timed scope</param>
|
||||
/// <param name="replayEventConfigurator">Replay event configurator</param>
|
||||
/// <returns>Newly created scope</returns>
|
||||
public static TimedScope Start(CorrelationData correlationData, IMachineInformation machineInformation, string scopeName, TimedScopeResult initialResult = default(TimedScopeResult),
|
||||
ITimedScopeLogger customLogger = null, IReplayEventConfigurator replayEventConfigurator = null)
|
||||
=> new TimedScopeDefinition(scopeName).Start(correlationData, machineInformation, initialResult, customLogger, replayEventConfigurator);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Deprecated - Start a timed scope
|
||||
/// </summary>
|
||||
/// <remarks>Please use TimedScopeDefinition for creating timed scopes</remarks>
|
||||
/// <param name="correlationData">Correlation data</param>
|
||||
/// <param name="machineInformation">Machine Information</param>
|
||||
/// <param name="scopeName">The name of the timed scope</param>
|
||||
/// <param name="initialResult">The default result for the scope</param>
|
||||
/// <returns>Newly created scope</returns>
|
||||
public static TimedScope Start(CorrelationData correlationData, IMachineInformation machineInformation, string scopeName, bool? initialResult)
|
||||
=> new TimedScopeDefinition(scopeName).Start(correlationData, machineInformation, ConvertBoolResultToTimedScopeResult(initialResult));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Deprecated - Start a timed scope
|
||||
/// </summary>
|
||||
/// <remarks>Please use TimedScopeDefinition for creating timed scopes</remarks>
|
||||
/// <param name="correlationData">Correlation Data</param>
|
||||
/// <param name="machineInformation">Machine Information</param>
|
||||
/// <param name="scopeName">The name of the timed scope</param>
|
||||
/// <param name="description">The description of the timed scope</param>
|
||||
/// <param name="initialResult">The default result for the scope</param>
|
||||
/// <param name="customLogger">Use a custom logger for the timed scope</param>
|
||||
/// <param name="replayEventConfigurator">Replay event configurator</param>
|
||||
/// <returns>Newly created scope</returns>
|
||||
public static TimedScope Start(CorrelationData correlationData, IMachineInformation machineInformation, string scopeName, string description,
|
||||
TimedScopeResult initialResult = default(TimedScopeResult), ITimedScopeLogger customLogger = null, IReplayEventConfigurator replayEventConfigurator = null)
|
||||
=> new TimedScopeDefinition(scopeName, description).Start(correlationData, machineInformation, initialResult, customLogger, replayEventConfigurator);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Deprecated - Start a timed scope
|
||||
/// </summary>
|
||||
/// <remarks>Please use TimedScopeDefinition for creating timed scopes</remarks>
|
||||
/// <param name="correlationData">Correlation Data</param>
|
||||
/// <param name="machineInformation">Machine Information</param>
|
||||
/// <param name="scopeName">The name of the timed scope</param>
|
||||
/// <param name="description">The description of the timed scope</param>
|
||||
/// <param name="initialResult">The default result for the scope</param>
|
||||
/// <returns>Newly created scope</returns>
|
||||
public static TimedScope Start(CorrelationData correlationData, IMachineInformation machineInformation, string scopeName, string description, bool? initialResult)
|
||||
=> new TimedScopeDefinition(scopeName, description).Start(correlationData, machineInformation, ConvertBoolResultToTimedScopeResult(initialResult));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Deprecated - Create a timed scope
|
||||
/// </summary>
|
||||
/// <remarks>Please use TimedScopeDefinition for creating timed scopes</remarks>
|
||||
/// <param name="correlationData">Correlation data</param>
|
||||
/// <param name="machineInformation">Machine Information</param>
|
||||
/// <param name="scopeName">The name of the timed scope</param>
|
||||
/// <param name="description">The description of the timed scope</param>
|
||||
/// <param name="initialResult">The default result for the scope</param>
|
||||
/// <param name="customLogger">Use a custom logger for the timed scope</param>
|
||||
/// <param name="replayEventConfigurator">Replay event configurator</param>
|
||||
/// <returns>newly created scope</returns>
|
||||
public static TimedScope Create(CorrelationData correlationData, IMachineInformation machineInformation, string scopeName, string description,
|
||||
TimedScopeResult initialResult = default(TimedScopeResult), ITimedScopeLogger customLogger = null, IReplayEventConfigurator replayEventConfigurator = null)
|
||||
=> new TimedScopeDefinition(scopeName, description).Create(correlationData, machineInformation, initialResult, startScope: false, customLogger: customLogger,
|
||||
replayEventConfigurator: replayEventConfigurator);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Deprecated - Create a timed scope
|
||||
/// </summary>
|
||||
/// <remarks>Please use TimedScopeDefinition for creating timed scopes</remarks>
|
||||
/// <param name="correlationData">Correlation data</param>
|
||||
/// <param name="machineInformation">Machine Information</param>
|
||||
/// <param name="scopeName">The name of the timed scope</param>
|
||||
/// <param name="description">The description of the timed scope</param>
|
||||
/// <param name="initialResult">The default result for the scope</param>
|
||||
/// <returns>Newly created scope</returns>
|
||||
public static TimedScope Create(CorrelationData correlationData, IMachineInformation machineInformation, string scopeName, string description, bool? initialResult)
|
||||
=> new TimedScopeDefinition(scopeName, description).Create(correlationData, machineInformation, ConvertBoolResultToTimedScopeResult(initialResult), startScope: false);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current active timed scope, or null if there are none active
|
||||
/// </summary>
|
||||
public static TimedScope Current => Scopes?.Peek();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Parent Scope if available
|
||||
/// </summary>
|
||||
public TimedScope Parent { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Start the timed scope
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
ULSLogging.LogTraceTag(0x2381770b /* tag_96x2l */, Categories.TimingGeneral, Levels.Error,
|
||||
"Attempting to start scope '{0}' that has already been disposed.", Name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsScopeActive)
|
||||
{
|
||||
ULSLogging.LogTraceTag(0x2381770c /* tag_96x2m */, Categories.TimingGeneral, Levels.Error,
|
||||
"Attempting to start scope '{0}' that has already been started.", Name);
|
||||
return;
|
||||
}
|
||||
|
||||
string metaDataCopy = MetaData;
|
||||
string subTypeCopy = SubType;
|
||||
|
||||
CorrelationData currentCorrelation = CorrelationData;
|
||||
TimedScopeData = currentCorrelation.Clone() ?? new CorrelationData();
|
||||
RunningTransaction = TransactionMonitor.RunningTransaction(TimedScopeData);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(metaDataCopy) && string.IsNullOrWhiteSpace(MetaData))
|
||||
{
|
||||
MetaData = metaDataCopy;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(subTypeCopy) && string.IsNullOrWhiteSpace(SubType))
|
||||
{
|
||||
SubType = subTypeCopy;
|
||||
}
|
||||
|
||||
// differentiate scope name when running under a test transaction
|
||||
if (IsTransaction)
|
||||
{
|
||||
NameSuffix = string.Concat(NameSuffix, "::Trx", RunningTransaction.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
// differentiate special scopes
|
||||
if (TimedScopeData.IsFallbackCall)
|
||||
{
|
||||
NameSuffix = string.Concat(NameSuffix, "::Fallback");
|
||||
}
|
||||
|
||||
// differentiate scope name for inner (proxied) calls
|
||||
if (TimedScopeData.CallDepth > 0)
|
||||
{
|
||||
NameSuffix = string.Concat(NameSuffix, "::Depth", TimedScopeData.CallDepth.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
if (currentCorrelation != null)
|
||||
{
|
||||
// reset call depth so any inner scopes are reported as layer 0 again
|
||||
currentCorrelation.CallDepth = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Parent = TimedScope.Current;
|
||||
IsRoot = Parent == null && TimedScopeData.CallDepth == 0;
|
||||
StartTick = Stopwatch.GetTimestamp();
|
||||
IsScopeActive = true;
|
||||
ScopeLogger.LogScopeStart(this);
|
||||
|
||||
PerfDiagnostics = new PerfDiagnostics(Parent != null ? Parent.PerfDiagnostics : null);
|
||||
PerfDiagnostics.Start();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// End the timed scope
|
||||
/// </summary>
|
||||
public void End(IMachineInformation machineInformation)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
ULSLogging.LogTraceTag(0x2381770d /* tag_96x2n */, Categories.TimingGeneral, Levels.Error,
|
||||
"Attempting to end scope '{0}' that has already been disposed.", Name);
|
||||
return;
|
||||
}
|
||||
|
||||
EndScope(machineInformation);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Discard the timed scope, aborting timer and avoiding any data being logged
|
||||
/// </summary>
|
||||
public void Discard()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref m_isScopeEnded, 1, 0) == 0)
|
||||
{
|
||||
IsScopeActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Timed Scope Definition
|
||||
/// </summary>
|
||||
public TimedScopeDefinition ScopeDefinition { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Timed Scope Definition
|
||||
/// </summary>
|
||||
public IMachineInformation MachineInformation { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The name of the timed scope
|
||||
/// </summary>
|
||||
public string Name => string.Concat(ScopeDefinition.Name, NameSuffix);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Timed Scope Name suffix
|
||||
/// </summary>
|
||||
private string NameSuffix { get; set; } = string.Empty;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Subtype
|
||||
/// </summary>
|
||||
public string SubType
|
||||
{
|
||||
set { AddLoggingValue(TimedScopeDataKeys.SubType, value, overrideValue: true); }
|
||||
get { return TimedScopeData.Data(TimedScopeDataKeys.SubType); }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Metadata
|
||||
/// </summary>
|
||||
public string MetaData
|
||||
{
|
||||
set { AddLoggingValue(TimedScopeDataKeys.ObjectMetaData, value, overrideValue: true); }
|
||||
get { return TimedScopeData.Data(TimedScopeDataKeys.ObjectMetaData); }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Unique Id for this timed scope
|
||||
/// </summary>
|
||||
public Guid InstanceId { get; } = Guid.NewGuid();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Description of the timed scope
|
||||
/// </summary>
|
||||
public string Description => ScopeDefinition.Description;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Is this the outermost scope in a call stack
|
||||
/// </summary>
|
||||
public bool IsRoot { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The Performance diagnostics instance
|
||||
/// </summary>
|
||||
public PerfDiagnostics PerfDiagnostics { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Timed Scope metrics logger
|
||||
/// </summary>
|
||||
private ITimedScopeLogger ScopeLogger { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Replay event configurator
|
||||
/// </summary>
|
||||
private IReplayEventConfigurator ReplayEventConfigurator { get; }
|
||||
|
||||
|
||||
public CorrelationData CorrelationData { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Id of the counterset
|
||||
/// </summary>
|
||||
public uint Id
|
||||
{
|
||||
get
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (uint)Name.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Explicit duration property
|
||||
/// </summary>
|
||||
public TimeSpan? Duration { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Starting tick of the timed scope
|
||||
/// </summary>
|
||||
public long StartTick { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ending tick of the timed scope
|
||||
/// </summary>
|
||||
public long EndTick { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Scope duration in milliseconds
|
||||
/// </summary>
|
||||
public double DurationInMilliseconds
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Duration.HasValue)
|
||||
{
|
||||
return Duration.Value.TotalMilliseconds;
|
||||
}
|
||||
else if (EndTick != 0)
|
||||
{
|
||||
return Math.Round((EndTick - StartTick) * 1000d / Frequency, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Math.Round((Stopwatch.GetTimestamp() - StartTick) * 1000d / Frequency, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The frequency for the timed scope
|
||||
/// </summary>
|
||||
public static long Frequency
|
||||
{
|
||||
get
|
||||
{
|
||||
return Stopwatch.Frequency;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Failure description
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This field is only exposed in ULS logs and the MDS timed scope stream.
|
||||
/// </remarks>
|
||||
public Enum FailureDescription { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Result of the timed scope.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You should set this property instead of IsSuccessful property. You shouldn't set both of them as one rewrites the other.
|
||||
/// Setting this property is strongly prefered over authoring new heuristic rules.
|
||||
/// </remarks>
|
||||
public TimedScopeResult Result { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Is the timed scope successful
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Setting this property is obsoleted and shouldn't be used in new code. You should set Result property directly. This property internally
|
||||
/// sets the Result property anyway. We do not remove the property setter because of the backward compatibility.
|
||||
/// </remarks>
|
||||
public bool? IsSuccessful
|
||||
{
|
||||
get
|
||||
{
|
||||
return Result.IsSuccessful();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
Result = ConvertBoolResultToTimedScopeResult(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Is running transaction
|
||||
/// </summary>
|
||||
public bool IsTransaction => RunningTransaction != Transactions.None;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Scope ended flag - 0 not ended, 1 ended
|
||||
/// </summary>
|
||||
private int m_isScopeEnded;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Is the timed scope active
|
||||
/// </summary>
|
||||
private bool m_isScopeActive;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// UserHash override
|
||||
/// </summary>
|
||||
private string m_userHashOverride = null;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the user hash override
|
||||
/// </summary>
|
||||
/// <param name="userHash">User hash</param>
|
||||
public void OverrideUserHash(string userHash)
|
||||
{
|
||||
if (!Code.ValidateNotNullOrWhiteSpaceArgument(userHash, nameof(userHash), TaggingUtilities.ReserveTag(0x2381770e /* tag_96x2o */)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_userHashOverride = userHash;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Is the timed scope active
|
||||
/// </summary>
|
||||
public bool IsScopeActive
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_isScopeActive;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
if (value != m_isScopeActive)
|
||||
{
|
||||
ModifyActiveScopes(value);
|
||||
}
|
||||
|
||||
m_isScopeActive = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag indicating if Verbose ULS capture should be disabled if this scope fails
|
||||
/// </summary>
|
||||
public bool DisableVerboseUlsCapture { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Abort the timer
|
||||
/// </summary>
|
||||
/// <param name="success">true if action should be considered successful</param>
|
||||
public void AbortTimer(bool? success = null)
|
||||
{
|
||||
IsScopeActive = false;
|
||||
if (success.HasValue)
|
||||
{
|
||||
IsSuccessful = success;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given key and value to the context of the timed scope
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the item to be added.</param>
|
||||
/// <param name="value">Value of the item to be added.</param>
|
||||
/// <param name="overrideValue">Whether the value should be overriden</param>
|
||||
public void AddLoggingValue(string key, string value, bool overrideValue = false)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
ULSLogging.LogTraceTag(0x2381770f /* tag_96x2p */, Categories.TimingGeneral, Levels.Error,
|
||||
"Empty or null key detected when attempting to add an entry in the timed scope data dictionary. Key : '{0}'.",
|
||||
key ?? "<NULL>");
|
||||
return;
|
||||
}
|
||||
|
||||
string existingValue = TimedScopeData.Data(key);
|
||||
|
||||
if (existingValue != null && !overrideValue)
|
||||
{
|
||||
ULSLogging.LogTraceTag(0x23817710 /* tag_96x2q */, Categories.TimingGeneral, Levels.Warning,
|
||||
"Timed scope data dictionary already contains key '{0}' with value '{1}'.", key, existingValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
TimedScopeData.AddData(key, value ?? string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set success value from a passed in status code
|
||||
/// </summary>
|
||||
/// <param name="statusCode">status code to set success value from</param>
|
||||
/// <remarks>500 or above is considered failure,
|
||||
/// all other values considered success. However, if the success value is already set,
|
||||
/// calling this method does not override the success value.
|
||||
/// The status code is stored on the scope in any case.</remarks>
|
||||
public void SetSuccessFromStatusCode(HttpStatusCode statusCode)
|
||||
{
|
||||
if (!IsSuccessful.HasValue)
|
||||
{
|
||||
IsSuccessful = (uint)statusCode < (uint)HttpStatusCode.InternalServerError;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(TimedScopeData.Data(TimedScopeDataKeys.InternalOnly.StatusCode)))
|
||||
{
|
||||
AddLoggingValue(TimedScopeDataKeys.InternalOnly.StatusCode, statusCode.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Is the timed scope disposed
|
||||
/// </summary>
|
||||
private bool IsDisposed { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// End the timed scope explicitly
|
||||
/// </summary>
|
||||
private void EndScope(IMachineInformation machineInformation)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref m_isScopeEnded, 1, 0) == 0)
|
||||
{
|
||||
EndTick = Stopwatch.GetTimestamp();
|
||||
|
||||
PerfDiagnostics?.Stop();
|
||||
|
||||
LogEnd(machineInformation);
|
||||
|
||||
IsScopeActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Logs the scope end to ULS
|
||||
/// </summary>
|
||||
private void LogEnd(IMachineInformation machineInformation)
|
||||
{
|
||||
if (!IsSuccessful.HasValue)
|
||||
{
|
||||
ULSLogging.LogTraceTag(0x23817711 /* tag_96x2r */, Categories.TimingGeneral, Levels.Warning,
|
||||
"Result not set for scope {0}. Considered as SystemError", Name);
|
||||
|
||||
Result = TimedScopeResult.SystemError;
|
||||
FailureDescription = InternalFailureDescription.UnknownResultAsSystemError;
|
||||
}
|
||||
|
||||
CorrelationData scopeData = ConstructCorrelationDataEntries(machineInformation);
|
||||
ScopeLogger.LogScopeEnd(this, scopeData);
|
||||
|
||||
ReplayEventConfigurator.ConfigureReplayEventsOnScopeEnd(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of the timed scope
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true, MachineInformation);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of the timed scope
|
||||
/// </summary>
|
||||
/// <param name="disposing">Should dispose or not</param>
|
||||
/// <param name="machineInformation">Machine Information</param>
|
||||
protected virtual void Dispose(bool disposing, IMachineInformation machineInformation)
|
||||
{
|
||||
if (disposing && !IsDisposed)
|
||||
{
|
||||
IsDisposed = true;
|
||||
EndScope(machineInformation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Timed scope data, added to the log output
|
||||
/// </summary>
|
||||
private CorrelationData TimedScopeData { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the timed scope correlation data
|
||||
/// </summary>
|
||||
/// <returns>Correlation data</returns>
|
||||
private CorrelationData ConstructCorrelationDataEntries(IMachineInformation machineInformation)
|
||||
{
|
||||
|
||||
CorrelationData correlationData = TimedScopeData;
|
||||
|
||||
CorrelationData scopeData = TimedScopeData.Clone();
|
||||
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.ScopeName, Name);
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.InstanceId, InstanceId.ToString());
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.IsSuccessful, IsSuccessful.HasValue ? IsSuccessful.Value.ToString() : bool.FalseString);
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.IsRoot, IsRoot.ToString());
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.ScopeResult, Result.ToString());
|
||||
|
||||
bool isFailed = !IsSuccessful ?? false;
|
||||
if (isFailed && FailureDescription != null)
|
||||
{
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.FailureDescription, FailureDescription.ToString());
|
||||
}
|
||||
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.Duration, DurationInMilliseconds.ToString(CultureInfo.InvariantCulture));
|
||||
long sequenceNumber = correlationData == null ? 0 : correlationData.NextEventSequenceNumber();
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.SequenceNumber, sequenceNumber.ToString(CultureInfo.InvariantCulture));
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.CallDepth, correlationData == null ? "0" : correlationData.CallDepth.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
IMachineInformation machineInfo = machineInformation;
|
||||
|
||||
if (machineInfo != null)
|
||||
{
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.MachineId, machineInfo.MachineId);
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.MachineCluster, machineInfo.MachineCluster);
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.MachineRole, machineInfo.MachineRole);
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.AgentName, machineInfo.AgentName);
|
||||
}
|
||||
|
||||
// if the user hash has been set, add it to the scope data
|
||||
if (!string.IsNullOrWhiteSpace(m_userHashOverride))
|
||||
{
|
||||
ULSLogging.LogTraceTag(0x23817712 /* tag_96x2s */, Categories.TimingGeneral, Levels.Verbose,
|
||||
"Overriding user hash metadata in the Timed Scope '{0}' with value '{1}'", Name, m_userHashOverride);
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.UserHash, m_userHashOverride);
|
||||
}
|
||||
else if (correlationData != null && !string.IsNullOrWhiteSpace(correlationData.UserHash))
|
||||
{
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.UserHash, correlationData.UserHash);
|
||||
}
|
||||
|
||||
// capture performance metrics
|
||||
if (PerfDiagnostics != null && PerfDiagnostics.LastStatus)
|
||||
{
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.CpuCycles,
|
||||
PerfDiagnostics.CyclesUsed.ToString(CultureInfo.InvariantCulture));
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.UserModeDuration,
|
||||
PerfDiagnostics.UserModeMilliseconds.ToString(CultureInfo.InvariantCulture));
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.KernelModeDuration,
|
||||
PerfDiagnostics.KernelModeMilliseconds.ToString(CultureInfo.InvariantCulture));
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.HttpRequestCount,
|
||||
PerfDiagnostics.HttpRequestCount.ToString(CultureInfo.InvariantCulture));
|
||||
scopeData.AddData(TimedScopeDataKeys.InternalOnly.ServiceCallCount,
|
||||
PerfDiagnostics.ServiceCallCount.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
return scopeData;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve or create a guid which identifies set of all active timedscopes for this flow
|
||||
/// </summary>
|
||||
private static Guid? AllScopesGuid
|
||||
{
|
||||
get
|
||||
{
|
||||
Context.ICallContext callContext = new ThreadCallContext().ExistingCallContext();
|
||||
|
||||
if (callContext != null)
|
||||
{
|
||||
Guid scopesId;
|
||||
object scopesIdObject;
|
||||
if (callContext.SharedData.TryGetValue(AllActiveScopesDataKey, out scopesIdObject))
|
||||
{
|
||||
scopesId = (Guid)scopesIdObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
scopesId = Guid.NewGuid();
|
||||
callContext.SharedData[AllActiveScopesDataKey] = scopesId;
|
||||
}
|
||||
|
||||
return scopesId;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get a stack of active scopes, creating a new stack if one does not exist
|
||||
/// </summary>
|
||||
/// <returns>stack of scopes</returns>
|
||||
private static TimedScopeStack Scopes
|
||||
{
|
||||
get
|
||||
{
|
||||
Context.ICallContext callContext = new ThreadCallContext().ExistingCallContext();
|
||||
|
||||
TimedScopeStack stack = null;
|
||||
if (callContext != null)
|
||||
{
|
||||
object stackObject = null;
|
||||
if (callContext.Data.TryGetValue(ActiveScopesDataKey, out stackObject))
|
||||
{
|
||||
stack = stackObject as TimedScopeStack;
|
||||
}
|
||||
|
||||
if (stack == null)
|
||||
{
|
||||
stack = TimedScopeStack.Root;
|
||||
callContext.Data[ActiveScopesDataKey] = stack;
|
||||
}
|
||||
}
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
Context.ICallContext callContext = new ThreadCallContext().ExistingCallContext();
|
||||
|
||||
if (callContext != null)
|
||||
{
|
||||
callContext.Data[ActiveScopesDataKey] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Modify the set of active scopes
|
||||
/// </summary>
|
||||
/// <param name="addScope">should the scope be added or removed</param>
|
||||
private void ModifyActiveScopes(bool addScope)
|
||||
{
|
||||
TimedScopeStack scopes = Scopes;
|
||||
if (scopes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (addScope)
|
||||
{
|
||||
scopes = scopes.Push(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
Stack<TimedScope> temporaryScopes = new Stack<TimedScope>();
|
||||
while (!scopes.IsRoot)
|
||||
{
|
||||
TimedScope popScope;
|
||||
scopes = scopes.Pop(out popScope);
|
||||
if (ReferenceEquals(popScope, this))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
temporaryScopes.Push(popScope);
|
||||
}
|
||||
|
||||
while (temporaryScopes.Count > 0)
|
||||
{
|
||||
scopes = scopes.Push(temporaryScopes.Pop());
|
||||
}
|
||||
}
|
||||
|
||||
Scopes = scopes;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts bool? scope results (legacy type) to TimedScopeResult type
|
||||
/// </summary>
|
||||
/// <param name="scopeResult">Scope result</param>
|
||||
/// <returns>Scope result of type TimedScopeResult</returns>
|
||||
public static TimedScopeResult ConvertBoolResultToTimedScopeResult(bool? scopeResult)
|
||||
{
|
||||
if (!scopeResult.HasValue)
|
||||
{
|
||||
return default(TimedScopeResult);
|
||||
}
|
||||
else if (scopeResult.Value)
|
||||
{
|
||||
return TimedScopeResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TimedScopeResult.SystemError;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// LinkedStack
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
private class TimedScopeStack
|
||||
{
|
||||
/// <summary>
|
||||
/// Root item for all stacks
|
||||
/// </summary>
|
||||
public static TimedScopeStack Root { get; } = new TimedScopeStack();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Root node
|
||||
/// </summary>
|
||||
public bool IsRoot => ReferenceEquals(this, Parent);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new item to the stack
|
||||
/// </summary>
|
||||
/// <param name="item">Data item to store</param>
|
||||
/// <returns>Stack with the new item on it</returns>
|
||||
public TimedScopeStack Push(TimedScope item) => new TimedScopeStack(item, this);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Remove item from the stack and return the new stack
|
||||
/// </summary>
|
||||
/// <param name="scope">TimedScope stored at the top od the stack</param>
|
||||
/// <returns>New stack with the top item removed</returns>
|
||||
public TimedScopeStack Pop(out TimedScope scope)
|
||||
{
|
||||
scope = Item;
|
||||
return Parent;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve item from the top of the stack
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public TimedScope Peek() => Item;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="item">Item stored in the stack</param>
|
||||
/// <param name="parent">Parent of this stack</param>
|
||||
private TimedScopeStack(TimedScope item, TimedScopeStack parent)
|
||||
{
|
||||
Code.ExpectsArgument(parent, nameof(parent), TaggingUtilities.ReserveTag(0x23817713 /* tag_96x2t */));
|
||||
|
||||
Item = item;
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
private TimedScopeStack()
|
||||
{
|
||||
Parent = this;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Parent of this node
|
||||
/// </summary>
|
||||
private TimedScopeStack Parent { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Data item stored in this node
|
||||
/// </summary>
|
||||
private TimedScope Item { get; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Data key used to store the active time scopes on the call context
|
||||
/// </summary>
|
||||
private const string ActiveScopesDataKey = "TimedScope.ActiveScopes";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Data key used to store set of all active time scopes on the call context
|
||||
/// </summary>
|
||||
private const string AllActiveScopesDataKey = "TimedScope.AllActiveScopes";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Internal failure descriptions
|
||||
/// </summary>
|
||||
private enum InternalFailureDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// Failure description set when we change unknown error to system error
|
||||
/// </summary>
|
||||
UnknownResultAsSystemError,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Running transaction Id, or Transactions.None
|
||||
/// </summary>
|
||||
public uint RunningTransaction { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class to store the value of the keys used when writing to the timedscope ULS logs.
|
||||
/// </summary>
|
||||
public static class TimedScopeDataKeys
|
||||
{
|
||||
/// <summary>
|
||||
/// Keys used for internal telemetry framework purposes - should not be set/modified by feature code
|
||||
/// </summary>
|
||||
public static class InternalOnly
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the Host Agent
|
||||
/// </summary>
|
||||
public const string AgentName = "AgentName";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Depth of call within proxying infrastructure, Depth 0 is original caller
|
||||
/// </summary>
|
||||
public const string CallDepth = "CallDepth";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Number of Cpu cycles used
|
||||
/// </summary>
|
||||
public const string CpuCycles = "CpuCycles";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The key value used when writing the Duration value to the ULS log.
|
||||
/// </summary>
|
||||
public const string Duration = "Duration";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The key value used when writing http request count
|
||||
/// </summary>
|
||||
public const string HttpRequestCount = "HttpRequestCount";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The key value used when writing the unique instance Id for a Timed Scope
|
||||
/// </summary>
|
||||
public const string InstanceId = "InstanceId";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The key value used when writing the timescope IsSuccessful value to the ULS log.
|
||||
/// </summary>
|
||||
public const string IsSuccessful = "IsSuccessful";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Is this a root scope in a call stack
|
||||
/// </summary>
|
||||
public const string IsRoot = "IsRoot";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Number of milliseconds thread spent in kernel mode
|
||||
/// </summary>
|
||||
public const string KernelModeDuration = "KernelModeDuration";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The key value used when writing the timed scope result to the ULS log.
|
||||
/// </summary>
|
||||
public const string ScopeResult = "ScopeResult";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Number of milliseconds thread spent in user mode
|
||||
/// </summary>
|
||||
public const string UserModeDuration = "UserModeDuration";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The key value used when writing the service call count (WCF calls)
|
||||
/// </summary>
|
||||
public const string ServiceCallCount = "ServiceCallCount";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The key value used when writing the timescope failure description to the ULS log.
|
||||
/// </summary>
|
||||
public const string FailureDescription = "FailureDescription";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Identity of the host machine cluster
|
||||
/// </summary>
|
||||
public const string MachineCluster = "MachineCluster";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Identity of the host machine
|
||||
/// </summary>
|
||||
public const string MachineId = "MachineId";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Identity of the host machine role
|
||||
/// </summary>
|
||||
public const string MachineRole = "MachineRole";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ULS event level from Replay
|
||||
/// </summary>
|
||||
public const string ReplayLevel = "Level";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ULS message body from Replay
|
||||
/// </summary>
|
||||
public const string ReplayMessage = "Message";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ULS Tag Id from Replay
|
||||
/// </summary>
|
||||
public const string ReplayTagId = "Tag";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Result/Status code used explictly by Analytics for tracking scope outcome reason
|
||||
/// </summary>
|
||||
public const string ReasonCodeForAnalytics = "ReasonCodeForAnalytics";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The sequence number of the scope within its correlation
|
||||
/// </summary>
|
||||
public const string SequenceNumber = "SequenceNumber";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The key value used when writing the ScopeName value to the ULS log.
|
||||
/// </summary>
|
||||
public const string ScopeName = "ScopeName";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The key value used when writing the StatusCode value to the ULS log.
|
||||
/// </summary>
|
||||
public const string StatusCode = "StatusCode";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A Hash that uniquely identifies the user
|
||||
/// </summary>
|
||||
public const string UserHash = "UserHash";
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Key that can be used to store source category associated with an Object, e.g. office client type
|
||||
/// for a specified app query
|
||||
/// </summary>
|
||||
public const string Category = "Category";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Key that can be used to store additional meta data about the scope.
|
||||
/// This is the third dimension surfaced in MDM after scope name and subtype.
|
||||
/// Do not use this field for highly granular, user- or request-specific data!
|
||||
/// </summary>
|
||||
public const string ObjectMetaData = "ObjectMetaData";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Key that can be used to store a constant string representing a Scope being used in a specific
|
||||
/// scenario context - e.g. Purchase of a Trial vs PrePaid offer
|
||||
/// </summary>
|
||||
public const string SubType = "SubType";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Omex.System.Diagnostics;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.TimedScopes.ReplayEventLogging;
|
||||
using Microsoft.Omex.System.Validation;
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Store Timed Scope name and its description
|
||||
/// </summary>
|
||||
public class TimedScopeDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// Name
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Description
|
||||
/// </summary>
|
||||
public string Description { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Description
|
||||
/// </summary>
|
||||
/// <remarks>Could be null</remarks>
|
||||
public string LinkToOnCallEngineerHandbook { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Should the scope be logged only when explicitly demanded
|
||||
/// </summary>
|
||||
public bool OnDemand { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Does the scope capture user hashes that are suitable for unique user-based alerting?
|
||||
/// </summary>
|
||||
public bool CapturesUniqueUserHashes { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="name">Name</param>
|
||||
/// <param name="description">Description</param>
|
||||
/// <param name="linkToOnCallEngineerHandbook">Link to On Call Engineer Handbook</param>
|
||||
/// <param name="onDemand">Should the scope be logged only when explicitly demanded</param>
|
||||
/// <param name="capturesUniqueUserHashes">Does the scope capture user hashes that are suitable for unique user-based alerting?</param>
|
||||
public TimedScopeDefinition(string name, string description = null, string linkToOnCallEngineerHandbook = null, bool onDemand = false, bool capturesUniqueUserHashes = false)
|
||||
{
|
||||
Code.ExpectsNotNullOrWhiteSpaceArgument(name, nameof(name), TaggingUtilities.ReserveTag(0x23817707 /* tag_96x2h */));
|
||||
|
||||
Name = name;
|
||||
Description = description ?? string.Empty;
|
||||
LinkToOnCallEngineerHandbook = linkToOnCallEngineerHandbook;
|
||||
OnDemand = onDemand;
|
||||
CapturesUniqueUserHashes = capturesUniqueUserHashes;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Starts a scope
|
||||
/// </summary>
|
||||
/// <param name="correlationData">Correlation Data</param>
|
||||
/// <param name="machineInformation">Machine Information</param>
|
||||
/// <param name="initialResult">Initial result to use</param>
|
||||
/// <param name="customLogger">Optional custom timed scope logger</param>
|
||||
/// <param name="replayEventConfigurator">Optional replay event configurator</param>
|
||||
/// <returns>A timed scope</returns>
|
||||
public TimedScope Start(CorrelationData correlationData, IMachineInformation machineInformation, TimedScopeResult initialResult = default(TimedScopeResult),
|
||||
ITimedScopeLogger customLogger = null, IReplayEventConfigurator replayEventConfigurator = null)
|
||||
=> Create(correlationData, machineInformation, initialResult: initialResult, startScope: true, customLogger: customLogger,
|
||||
replayEventConfigurator: replayEventConfigurator);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scope (and starts by default)
|
||||
/// </summary>
|
||||
/// <param name="correlationData">Correlation Data</param>
|
||||
/// <param name="machineInformation">Machine Information</param>
|
||||
/// <param name="initialResult">Initial result to use</param>
|
||||
/// <param name="startScope">Should the scope be automatically started (for use in e.g. 'using' statement)</param>
|
||||
/// <param name="customLogger">Optional custom timed scope logger</param>
|
||||
/// <param name="replayEventConfigurator">Optional replay event configurator</param>
|
||||
/// <returns>A timed scope</returns>
|
||||
public TimedScope Create(CorrelationData correlationData, IMachineInformation machineInformation, TimedScopeResult initialResult = default(TimedScopeResult),
|
||||
bool startScope = true, ITimedScopeLogger customLogger = null, IReplayEventConfigurator replayEventConfigurator = null)
|
||||
{
|
||||
TimedScope scope = TimedScope.Create(this, correlationData, machineInformation, initialResult, customLogger, replayEventConfigurator);
|
||||
|
||||
if (startScope)
|
||||
{
|
||||
scope.Start();
|
||||
}
|
||||
|
||||
return scope;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Deprecated - Creates a scope
|
||||
/// </summary>
|
||||
/// <remarks>This overload is obsoleted. Use the overload with TimedScopeResult for new scopes instead.</remarks>
|
||||
/// <param name="correlationData">Correlation data</param>
|
||||
/// <param name="machineInformation">Machine Information</param>
|
||||
/// <param name="initialResult">Initial result to use</param>
|
||||
/// <param name="startScope">Should the scope be automatically started (for use in e.g. 'using' statement)</param>
|
||||
/// <param name="customLogger">Optional custom timed scope logger</param>
|
||||
/// <param name="replayEventConfigurator">Optional replay event configurator</param>
|
||||
/// <returns>A timed scope</returns>
|
||||
public TimedScope Create(CorrelationData correlationData, IMachineInformation machineInformation, bool? initialResult, bool startScope = true,
|
||||
ITimedScopeLogger customLogger = null, IReplayEventConfigurator replayEventConfigurator = null)
|
||||
=> Create(correlationData, machineInformation, TimedScope.ConvertBoolResultToTimedScopeResult(initialResult), startScope, customLogger, replayEventConfigurator);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,302 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.Validation;
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an immutable scope based instance name in a strongly typed fashion
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[Serializable]
|
||||
[Bond.Schema]
|
||||
public sealed class TimedScopeInstanceName : IEquatable<TimedScopeInstanceName>
|
||||
{
|
||||
/// <summary>
|
||||
/// Separator used for separating SubType and MetaData
|
||||
/// </summary>
|
||||
private const char FieldsSeparator = '/';
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Separator used for separating FailureClassification suffix
|
||||
/// </summary>
|
||||
private const char FailureClassificationSeparator = '.';
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Complete strongly typed scope name
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
[Bond.Id(0)]
|
||||
public TimedScopeName CompleteScopeName { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Failure Classification
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
[Bond.Id(1)]
|
||||
public TimedScopeResult Classification { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Scope name
|
||||
/// </summary>
|
||||
public string Scope
|
||||
{
|
||||
get
|
||||
{
|
||||
return CompleteScopeName.Scope;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// SubType
|
||||
/// </summary>
|
||||
/// <remarks>The value is null if no subtype is specified</remarks>
|
||||
public string SubType
|
||||
{
|
||||
get
|
||||
{
|
||||
return CompleteScopeName.SubType;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// MetaData
|
||||
/// </summary>
|
||||
/// <remarks>The value is null if no metadata is specified</remarks>
|
||||
public string MetaData
|
||||
{
|
||||
get
|
||||
{
|
||||
return CompleteScopeName.MetaData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="scopeName">Strongy typed cope name</param>
|
||||
/// <param name="classification">Failure classification</param>
|
||||
public TimedScopeInstanceName(TimedScopeName scopeName, TimedScopeResult classification)
|
||||
{
|
||||
Code.ExpectsArgument(scopeName, nameof(scopeName), TaggingUtilities.ReserveTag(0x23817705 /* tag_96x2f */));
|
||||
|
||||
CompleteScopeName = scopeName;
|
||||
Classification = classification;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="scope">Scope name</param>
|
||||
/// <param name="classification">Failure classification</param>
|
||||
/// <param name="subType">SubType</param>
|
||||
/// <param name="metaData">MetaData</param>
|
||||
public TimedScopeInstanceName(string scope, TimedScopeResult classification, string subType = null, string metaData = null)
|
||||
: this(new TimedScopeName(scope, subType, metaData), classification)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Serves as a hash function for a particular type.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for the current <see cref="T:System.Object"/>.
|
||||
/// </returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = 17;
|
||||
hash = 23 * hash + CompleteScopeName.GetHashCode();
|
||||
hash = 23 * hash + Classification.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if the specified object is equal to the current object; otherwise, false.
|
||||
/// </returns>
|
||||
/// <param name="obj">The object to compare with the current object. </param>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return this == obj as TimedScopeInstanceName;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified instance is equal to this object
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance to compare with this instance</param>
|
||||
/// <returns><c>true</c> if they are equal, <c>false</c> otherwise</returns>
|
||||
public bool Equals(TimedScopeInstanceName other)
|
||||
{
|
||||
return this == other;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Equality operator
|
||||
/// </summary>
|
||||
/// <param name="x">First object</param>
|
||||
/// <param name="y">Second object</param>
|
||||
/// <returns><c>true</c> if they are equal, <c>false</c> otherwise</returns>
|
||||
public static bool operator ==(TimedScopeInstanceName x, TimedScopeInstanceName y)
|
||||
{
|
||||
if (ReferenceEquals(x, y))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return x.Classification.Equals(y.Classification)
|
||||
&& x.CompleteScopeName.Equals(y.CompleteScopeName);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Inequality operator
|
||||
/// </summary>
|
||||
/// <param name="x">First object</param>
|
||||
/// <param name="y">Second object</param>
|
||||
/// <returns><c>true</c> if they are not equal, <c>false</c> otherwise</returns>
|
||||
public static bool operator !=(TimedScopeInstanceName x, TimedScopeInstanceName y)
|
||||
{
|
||||
return !(x == y);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current object.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A string that represents the current object.
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(Scope);
|
||||
builder.Append(FailureClassificationSeparator).Append(Classification);
|
||||
|
||||
if (SubType != null)
|
||||
{
|
||||
builder.Append(FieldsSeparator).Append(SubType);
|
||||
}
|
||||
|
||||
if (MetaData != null)
|
||||
{
|
||||
builder.Append(FieldsSeparator).Append(MetaData);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse a TimedScopeInstanceName instance from a string
|
||||
/// </summary>
|
||||
/// <param name="toParse">String to be parsed, cannot be null</param>
|
||||
/// <param name="parsed">This output parameter is set to a new TimedScopeInstanceName instance when succeeded, null otherwise</param>
|
||||
/// <param name="preferMetaData">If <c>true</c>, the second field is assumed to be MetaData value</param>
|
||||
/// <returns>Success status</returns>
|
||||
public static bool TryParse(string toParse, out TimedScopeInstanceName parsed, bool preferMetaData = false)
|
||||
{
|
||||
parsed = null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(toParse))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// The scope instance names have the following form: scope.classification[.description][/subtype][/metadata]
|
||||
// We are going to extract the classification substring, the remaining string with the classification and
|
||||
// description substring removed should form an ordinary timed scope name so we will parse it by TimedScopeName.TryParse.
|
||||
|
||||
// Split the string by the field separators ('/') first
|
||||
string[] parts = toParse.Split(new[] { FieldsSeparator }, StringSplitOptions.None);
|
||||
|
||||
// The first part is further divided by classification separator. It should have two or three fields, the first is
|
||||
// a scope, the second is a classification
|
||||
string[] firstFieldsubParts = parts[0].Split(new[] { FailureClassificationSeparator }, StringSplitOptions.None);
|
||||
if (firstFieldsubParts.Length < 2 || firstFieldsubParts.Length > 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string scope = firstFieldsubParts[0];
|
||||
string classification = firstFieldsubParts[1];
|
||||
|
||||
// Try to parse the classification substring
|
||||
TimedScopeResult parsedClassification;
|
||||
if (!Enum.TryParse(classification, out parsedClassification))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reconstruct the scope name string without classification and try to parse it
|
||||
parts[0] = scope;
|
||||
string scopeName = string.Join(FieldsSeparator.ToString(CultureInfo.InvariantCulture), parts);
|
||||
|
||||
TimedScopeName parsedScopeName;
|
||||
if (!TimedScopeName.TryParse(scopeName, out parsedScopeName, preferMetaData))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a new instance
|
||||
parsed = new TimedScopeInstanceName(parsedScopeName, parsedClassification);
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
// The parsing shouldn't throw but we catch exceptions to be safe
|
||||
ULSLogging.ReportExceptionTag(0x23817706 /* tag_96x2g */, Categories.Common, exception,
|
||||
"An unexpected exception occured during TimedScopeInstanceName.TryParse. Returning false.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse a TimedScopeInstanceName instance from a string.
|
||||
/// </summary>
|
||||
/// <param name="toParse">String to be parsed, cannot be null</param>
|
||||
/// <param name="preferMetaData">If <c>true</c>, the second field is assumed to be MetaData value</param>
|
||||
/// <returns>New TimedScopeInstanceName instance if succeeded, <c>null</c> otherwise</returns>
|
||||
public static TimedScopeInstanceName ParseOrReturnNull(string toParse, bool preferMetaData = false)
|
||||
{
|
||||
TimedScopeInstanceName name;
|
||||
if (TryParse(toParse, out name, preferMetaData))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using Bond.Tag;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.Validation;
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents Scope name, subtype and metada values in a strongly typed and immutable fashion
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[Serializable]
|
||||
[Bond.Schema]
|
||||
public sealed class TimedScopeName : IEquatable<TimedScopeName>
|
||||
{
|
||||
/// <summary>
|
||||
/// Separator used in the string representation
|
||||
/// </summary>
|
||||
private const char Separator = '/';
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Scope name
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
[Bond.Id(0)]
|
||||
public string Scope { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// SubType
|
||||
/// </summary>
|
||||
/// <remarks>The value is null if no subtype is specified. Empty value is allowed.</remarks>
|
||||
[DataMember]
|
||||
[Bond.Id(1), Bond.Type(typeof(nullable<wstring>))]
|
||||
public string SubType { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// MetaData
|
||||
/// </summary>
|
||||
/// <remarks>The value is null if no metadata is specified. Empty value is allowed.</remarks>
|
||||
[DataMember]
|
||||
[Bond.Id(2), Bond.Type(typeof(nullable<wstring>))]
|
||||
public string MetaData { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="scope">Scope name</param>
|
||||
/// <param name="subType">SubType</param>
|
||||
/// <param name="metaData">MetaData</param>
|
||||
public TimedScopeName(string scope, string subType = null, string metaData = null)
|
||||
{
|
||||
Code.ExpectsNotNullOrWhiteSpaceArgument(scope, nameof(scope), TaggingUtilities.ReserveTag(0x23817702 /* tag_96x2c */));
|
||||
|
||||
Scope = scope;
|
||||
SubType = subType;
|
||||
MetaData = metaData;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that copies all fields (except the classification) from a TimedScopeInstanceName.
|
||||
/// </summary>
|
||||
/// <param name="copyFrom">Object to copy values from</param>
|
||||
public TimedScopeName(TimedScopeInstanceName copyFrom)
|
||||
{
|
||||
Code.ExpectsArgument(copyFrom, nameof(copyFrom), TaggingUtilities.ReserveTag(0x23817703 /* tag_96x2d */));
|
||||
|
||||
Scope = copyFrom.Scope;
|
||||
SubType = copyFrom.SubType;
|
||||
MetaData = copyFrom.MetaData;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Parameterless constructor for Bond serialization
|
||||
/// </summary>
|
||||
public TimedScopeName()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Serves as a hash function for a particular type.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for the current <see cref="T:System.Object"/>.
|
||||
/// </returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = 17;
|
||||
hash = 23 * hash + Scope.GetHashCode();
|
||||
hash = 23 * hash + (SubType == null ? 0 : SubType.GetHashCode());
|
||||
hash = 23 * hash + (MetaData == null ? 0 : MetaData.GetHashCode());
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if the specified object is equal to the current object; otherwise, false.
|
||||
/// </returns>
|
||||
/// <param name="obj">The object to compare with the current object. </param>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return this == obj as TimedScopeName;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified TimedScopeName is equal to this object
|
||||
/// </summary>
|
||||
/// <param name="other">The other UserScopeEvent instance to compare with this instance</param>
|
||||
/// <returns><c>true</c> if they are equal, <c>false</c> otherwise</returns>
|
||||
public bool Equals(TimedScopeName other)
|
||||
{
|
||||
return this == other;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Equality operator
|
||||
/// </summary>
|
||||
/// <param name="x">First object</param>
|
||||
/// <param name="y">Second object</param>
|
||||
/// <returns><c>true</c> if they are equal, <c>false</c> otherwise</returns>
|
||||
public static bool operator ==(TimedScopeName x, TimedScopeName y)
|
||||
{
|
||||
if (ReferenceEquals(x, y))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return string.Equals(x.Scope, y.Scope, StringComparison.Ordinal)
|
||||
&& string.Equals(x.SubType, y.SubType, StringComparison.Ordinal)
|
||||
&& string.Equals(x.MetaData, y.MetaData, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Inequality operator
|
||||
/// </summary>
|
||||
/// <param name="x">First object</param>
|
||||
/// <param name="y">Second object</param>
|
||||
/// <returns><c>true</c> if they are not equal, <c>false</c> otherwise</returns>
|
||||
public static bool operator !=(TimedScopeName x, TimedScopeName y)
|
||||
{
|
||||
return !(x == y);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current object.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A string that represents the current object.
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(Scope);
|
||||
|
||||
if (SubType != null)
|
||||
{
|
||||
builder.Append(Separator).Append(SubType);
|
||||
}
|
||||
|
||||
if (MetaData != null)
|
||||
{
|
||||
builder.Append(Separator).Append(MetaData);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance name with provided scope result
|
||||
/// </summary>
|
||||
/// <param name="result">Scope result</param>
|
||||
/// <returns>Returns instance name</returns>
|
||||
public TimedScopeInstanceName CreateInstanceName(TimedScopeResult result)
|
||||
{
|
||||
return new TimedScopeInstanceName(this, result);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// All instances (all timed scope results) of given timed scope name
|
||||
/// </summary>
|
||||
public IEnumerable<TimedScopeInstanceName> AllInstances
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (TimedScopeResult result in Enum.GetValues(typeof(TimedScopeResult)))
|
||||
{
|
||||
yield return CreateInstanceName(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// True if it has a subtype
|
||||
/// </summary>
|
||||
public bool HasSubType => SubType != null;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// True if it has a metadata
|
||||
/// </summary>
|
||||
public bool HasMetaData => MetaData != null;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse a TimedScopeName instance from a string
|
||||
/// </summary>
|
||||
/// <param name="toParse">String to be parsed, cannot be null</param>
|
||||
/// <param name="parsed">This output parameter is set to a new TimedScopeName instance when succeeded, null otherwise</param>
|
||||
/// <param name="preferMetaData">If <c>true</c>, the second field is assumed to be MetaData value</param>
|
||||
/// <returns>Success status</returns>
|
||||
public static bool TryParse(string toParse, out TimedScopeName parsed, bool preferMetaData = false)
|
||||
{
|
||||
parsed = null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(toParse))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// The scope name should have the following form: scope[/subtype][/metadata]
|
||||
// We just split the string, check the number of fields and assign correct fields
|
||||
|
||||
string[] parts = toParse.Split(new[] { Separator }, StringSplitOptions.None);
|
||||
|
||||
string scope = parts[0];
|
||||
string subType = null;
|
||||
string metaData = null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(scope))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
if (preferMetaData)
|
||||
{
|
||||
metaData = parts[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
subType = parts[1];
|
||||
}
|
||||
}
|
||||
else if (parts.Length == 3)
|
||||
{
|
||||
subType = parts[1];
|
||||
metaData = parts[2];
|
||||
}
|
||||
else if (parts.Length > 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
parsed = new TimedScopeName(scope, subType, metaData);
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
// The parsing shouldn't throw but we catch exceptions to be safe
|
||||
ULSLogging.ReportExceptionTag(0x23817704 /* tag_96x2e */, Categories.Common, exception,
|
||||
"An unexpected exception occured during TimedScopeName.TryParse. Returning false.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the possible scope results
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This enum is essential to OMEX monitoring solutions and generally should not change.
|
||||
/// If values are added or removed, TimedScopeResultExtensions should be updated as well.
|
||||
/// </remarks>
|
||||
[DataContract]
|
||||
public enum TimedScopeResult : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Result is unknown (default)
|
||||
/// </summary>
|
||||
/// <remarks>Result should always be set to one of the other values explicitly. Unknown causes an error to be logged, and the scope is assumed failed.</remarks>
|
||||
[EnumMember]
|
||||
[Obsolete("Default value, not to be used explicitly", error: true)]
|
||||
Unknown = 0,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Success
|
||||
/// </summary>
|
||||
[EnumMember]
|
||||
Success = 1,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// System Error
|
||||
/// </summary>
|
||||
[EnumMember]
|
||||
SystemError = 2,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Expected Error (consolidating old UserError and PayloadError)
|
||||
/// </summary>
|
||||
[EnumMember]
|
||||
ExpectedError = 6
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Omex.System.Logging;
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for TimedScopeResult enum
|
||||
/// </summary>
|
||||
public static class TimedScopeResultExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Decides whether the result is success
|
||||
/// </summary>
|
||||
/// <param name="result">The result</param>
|
||||
/// <returns>Success flag (null if we don't know)</returns>
|
||||
public static bool? IsSuccessful(this TimedScopeResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case default(TimedScopeResult):
|
||||
return null;
|
||||
|
||||
case TimedScopeResult.Success:
|
||||
return true;
|
||||
|
||||
case TimedScopeResult.ExpectedError:
|
||||
case TimedScopeResult.SystemError:
|
||||
return false;
|
||||
|
||||
default:
|
||||
ULSLogging.LogTraceTag(0x23817701 /* tag_96x2b */, Categories.TimingGeneral, Levels.Error, "IsSuccessful status unknown for timed scope result {0}", result);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Decides whether we should replay events for scopes with given result
|
||||
/// </summary>
|
||||
/// <param name="result">The result</param>
|
||||
/// <returns>true if we should replay events for this result; false otherwise</returns>
|
||||
public static bool ShouldReplayEvents(this TimedScopeResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case TimedScopeResult.SystemError:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for handling transaction data
|
||||
/// </summary>
|
||||
public class TransactionData
|
||||
{
|
||||
/// <summary>
|
||||
/// Transaction id
|
||||
/// </summary>
|
||||
public uint TransactionId { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Transaction Context Id
|
||||
/// </summary>
|
||||
public uint TransactionContextId { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Transaction Step Number
|
||||
/// </summary>
|
||||
public uint TransactionStep { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Originator
|
||||
/// </summary>
|
||||
public string Originator { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Start tick
|
||||
/// </summary>
|
||||
public long StartTick { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Correlation Id
|
||||
/// </summary>
|
||||
public Guid CorrelationId { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tracks the depth of the call through proxying layers
|
||||
/// Original caller is Depth 0, subsequent hops are +1 each time
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// WARNING: this field is used internally by the telemetry infrastructure and should
|
||||
/// NOT be set to anything except 0 (i.e. leave as default!) by consumers making a service call
|
||||
/// </remarks>
|
||||
public uint CallDepth { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// logging sequence number used for guarenteed ordering of correlation events across machine boundaries
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// WARNING: this field is used internally by the telemetry infrastructure and should
|
||||
/// NOT be set to anything except 0 (i.e. leave as default!) by consumers making a service call
|
||||
/// </remarks>
|
||||
public long EventSequenceNumber { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Result
|
||||
/// </summary>
|
||||
public uint Result { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// User hash for the current correlation.
|
||||
/// Used to propagate the value between internal S2S calls.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="CorrelationData.UserHash"/> for details on how the field is computed.
|
||||
/// </remarks>
|
||||
public string UserHash { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The TestScenarioId for Response Injection
|
||||
/// </summary>
|
||||
public string TestScenarioId { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The TestScenarioRecordingState for Response Injection
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Values include 'play' and 'record'
|
||||
/// </remarks>
|
||||
public string TestScenarioRecordingState { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The TestSceanrio failure message, if any, from use of Response Injection
|
||||
/// </summary>
|
||||
public string TestScenarioFailureMessage { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Is this a fallback call
|
||||
/// </summary>
|
||||
public bool IsFallbackCall { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Construct an empty transaction data
|
||||
/// </summary>
|
||||
public TransactionData()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Monitors timing and test transactions
|
||||
/// </summary>
|
||||
class TransactionMonitor
|
||||
{
|
||||
/// <summary>
|
||||
/// The id of the running transaction
|
||||
/// </summary>
|
||||
/// <param name="correlation">current correlation</param>
|
||||
/// <returns>id of the running transaction</returns>
|
||||
public static uint RunningTransaction(CorrelationData correlation)
|
||||
{
|
||||
uint id = Transactions.None;
|
||||
if (correlation != null)
|
||||
{
|
||||
id = correlation.TransactionId;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Omex.System.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Set of known transactions
|
||||
/// </summary>
|
||||
public static class Transactions
|
||||
{
|
||||
/// <summary>
|
||||
/// There is no running transaction
|
||||
/// </summary>
|
||||
public const uint None = 0;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A transaction to exercise the pipeline is running
|
||||
/// </summary>
|
||||
/// <remarks>This runs through the pipeline for web services,
|
||||
/// without actually running each request processor
|
||||
/// </remarks>
|
||||
public const uint ExercisePipeline = 1;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A transaction to build dependency graphs
|
||||
/// </summary>
|
||||
/// <remarks>Builds up dependency graphs rather than processing the request</remarks>
|
||||
public const uint BuildDependencyGraph = 2;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Transaction Id specified by AppSure Validation Probes
|
||||
/// </summary>
|
||||
public const uint AppSureValidationProbe = 9997;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Transaction Id specified by when monitoring by watchdogs/observers/CVTs to
|
||||
/// mark that a test transaction is ongoing.
|
||||
/// </summary>
|
||||
/// <remarks>If a specific behaviour is required for the transaction, use one
|
||||
/// of the transaction id's above instead</remarks>
|
||||
public const uint MonitorEndpoint = 9998;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Transaction Id specified by OMEX Test Probes
|
||||
/// </summary>
|
||||
public const uint TestProbe = 9999;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using Directives
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -13,8 +11,6 @@ using Microsoft.Omex.System.UnitTests.Shared;
|
|||
using Microsoft.Omex.System.UnitTests.Shared.Configuration;
|
||||
using Xunit;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.Gating.UnitTests
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonVersionCore)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Moq" Version="$(MoqVersion)" />
|
||||
<PackageReference Include="Moq" Version="$(MoqVersionCore)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="DefaultGates.xml" Link="DefaultGates.xml">
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Omex.System.UnitTests.Shared;
|
||||
|
||||
namespace Microsoft.Omex.System.AspNetCore.UnitTests
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Asp Net Core Unit test base class
|
||||
/// </summary>
|
||||
public abstract class AspNetCoreUnitTestsBase : UnitTestBase
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Static constructor
|
||||
/// </summary>
|
||||
static AspNetCoreUnitTestsBase()
|
||||
{
|
||||
HttpContextWrapper.Configure(m_httpContextAccessor.Value);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Http context accessor
|
||||
/// </summary>
|
||||
protected static readonly Lazy<IHttpContextAccessor> m_httpContextAccessor = new Lazy<IHttpContextAccessor>(
|
||||
() => new HttpContextAccessor(),
|
||||
LazyThreadSafetyMode.PublicationOnly);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,379 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Omex.System.Context;
|
||||
using Microsoft.Omex.System.Diagnostics;
|
||||
using Microsoft.Omex.System.TimedScopes;
|
||||
using Microsoft.Omex.System.UnitTests.Shared.Diagnostics;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Assert = Xunit.Assert;
|
||||
|
||||
namespace Microsoft.Omex.System.AspNetCore.UnitTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Unit tests for Correlation
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class CorrelationUnitTests : AspNetCoreUnitTestsBase
|
||||
{
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
CallContextManagerInstance = CreateCallContextManager();
|
||||
CallContextManagerInstance.CallContextOverride = new HttpCallContext(useLogicalCallContext: true);
|
||||
|
||||
MachineInformation = new UnitTestMachineInformation();
|
||||
|
||||
HttpCallContext callContext = CallContextManagerInstance.CallContextHandler(MachineInformation) as HttpCallContext;
|
||||
callContext.ExistingCallContext();
|
||||
callContext.StartCallContext();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CurrentCorrelation_WithNoActiveCorrelation_ShouldReturnNull()
|
||||
{
|
||||
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
|
||||
Correlation.CorrelationStart(new CorrelationData());
|
||||
|
||||
while (CallContextManagerInstance.CallContextHandler(MachineInformation).ExistingCallContext() != null)
|
||||
{
|
||||
CallContextManagerInstance.CallContextHandler(MachineInformation).EndCallContext();
|
||||
}
|
||||
|
||||
Assert.Null(Correlation.CurrentCorrelation);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void CorrelationStart_WithNullData_ShouldStartNewCorrelation()
|
||||
{
|
||||
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
|
||||
Correlation.CorrelationStart(null);
|
||||
|
||||
Assert.NotNull(Correlation.CurrentCorrelation);
|
||||
|
||||
CallContextManagerInstance.CallContextOverride.EndCallContext();
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void CorrelationStart_MultipleCorrelations_ShouldCreateAHierarchy()
|
||||
{
|
||||
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
Correlation.CorrelationStart(null);
|
||||
CorrelationData currentCorrelation = Correlation.CurrentCorrelation;
|
||||
for (int j = i; j > 0; j--)
|
||||
{
|
||||
CorrelationData parentCorrelation = currentCorrelation.ParentCorrelation;
|
||||
|
||||
Assert.NotNull(parentCorrelation);
|
||||
Assert.NotEqual(currentCorrelation.VisibleId, parentCorrelation.VisibleId);
|
||||
|
||||
currentCorrelation = parentCorrelation;
|
||||
}
|
||||
Assert.Null(currentCorrelation.ParentCorrelation);
|
||||
}
|
||||
|
||||
CallContextManagerInstance.CallContextOverride.EndCallContext();
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void CorrelationEnd_MultipleCorrelations_ShouldUnwindHierarchy()
|
||||
{
|
||||
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
|
||||
|
||||
Guid[] correlations = new Guid[3];
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
Correlation.CorrelationStart(null);
|
||||
correlations[i] = Correlation.CurrentCorrelation.VisibleId;
|
||||
}
|
||||
|
||||
for (int i = 2; i >= 0; i--)
|
||||
{
|
||||
CorrelationData currentCorrelation = Correlation.CurrentCorrelation;
|
||||
Assert.Equal(correlations[i], currentCorrelation.VisibleId);
|
||||
|
||||
Correlation.CorrelationEnd();
|
||||
}
|
||||
|
||||
Assert.Null(Correlation.CurrentCorrelation);
|
||||
|
||||
CallContextManagerInstance.CallContextOverride.EndCallContext();
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void CorrelationData_SetParent_ShouldThrowIfAlreadySet()
|
||||
{
|
||||
CorrelationData data = new CorrelationData();
|
||||
data.ParentCorrelation = new CorrelationData();
|
||||
Assert.True(true, "Should not throw when the parent correlation is not set");
|
||||
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => data.ParentCorrelation = new CorrelationData());
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void CorrelationData_SetParentCausingCircularReference_ShouldThrow()
|
||||
{
|
||||
CorrelationData data = new CorrelationData();
|
||||
data.ParentCorrelation = new CorrelationData();
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => data.ParentCorrelation.ParentCorrelation = data);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void CorrelationData_CloneWithNullData_ShouldReturnNull()
|
||||
{
|
||||
CorrelationData data = null;
|
||||
Assert.Null(data.Clone());
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void CorrelationData_Clone_ShouldReturnCopyOfCorrelationData()
|
||||
{
|
||||
CorrelationData data = new CorrelationData();
|
||||
data.ParentCorrelation = new CorrelationData();
|
||||
data.AddData(CorrelationData.TransactionIdKey, "1");
|
||||
data.ShouldLogDirectly = true;
|
||||
|
||||
CorrelationData clone = data.Clone();
|
||||
Assert.NotSame(data, clone);
|
||||
Assert.Equal(data.VisibleId, clone.VisibleId);
|
||||
Assert.Equal(data.ShouldLogDirectly, clone.ShouldLogDirectly);
|
||||
Assert.Equal(data.HasData, clone.HasData);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void CorrelationData_Add_WithNullKey_ShouldThrowException()
|
||||
{
|
||||
try
|
||||
{
|
||||
FailOnErrors = false;
|
||||
|
||||
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
|
||||
Correlation.CorrelationStart(null);
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
{
|
||||
Correlation.CorrelationAdd(null, "value");
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndRequest();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void CorrelationData_Add_WithNullValue_ShouldThrowException()
|
||||
{
|
||||
try
|
||||
{
|
||||
FailOnErrors = false;
|
||||
|
||||
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
|
||||
Correlation.CorrelationStart(null);
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
{
|
||||
Correlation.CorrelationAdd("key", null);
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndRequest();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void CorrelationData_NullObjectToTransactionData_ShouldReturnNull()
|
||||
{
|
||||
CorrelationData data = null;
|
||||
Assert.Null(data.ToTransactionData());
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void CorrelationData_ToTransactionData_ShouldReturnSameData()
|
||||
{
|
||||
try
|
||||
{
|
||||
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
|
||||
Correlation.CorrelationStart(null);
|
||||
|
||||
CorrelationData data = Correlation.CurrentCorrelation;
|
||||
TransactionData transaction = data.ToTransactionData();
|
||||
|
||||
Assert.True(data.CallDepth == transaction.CallDepth, "Call depth properties should be equal.");
|
||||
Assert.True(data.EventSequenceNumber == transaction.EventSequenceNumber, "EventSequenceNumber properties should be equal.");
|
||||
Assert.True(data.TransactionId == transaction.TransactionId, "TransactionId properties should be equal.");
|
||||
Assert.True(data.UserHash == transaction.UserHash, "UserHash properties should be equal.");
|
||||
Assert.True(data.VisibleId == transaction.CorrelationId, "VisibleId and CorrelationId properties should be equal.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndRequest();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void CorrelationClear_ShouldClearAllCorrelations()
|
||||
{
|
||||
try
|
||||
{
|
||||
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
|
||||
Correlation.CorrelationStart(null);
|
||||
|
||||
CorrelationData previousCorrelation = Correlation.CurrentCorrelation;
|
||||
for (int i = 0; i < (new Random()).Next(3, 10); i++)
|
||||
{
|
||||
Correlation.CorrelationStart(null);
|
||||
CorrelationData parentCorrelation = Correlation.CurrentCorrelation.ParentCorrelation;
|
||||
Assert.Same(previousCorrelation, parentCorrelation);
|
||||
previousCorrelation = Correlation.CurrentCorrelation;
|
||||
}
|
||||
|
||||
Assert.NotNull(Correlation.CurrentCorrelation);
|
||||
|
||||
Correlation.CorrelationClear();
|
||||
|
||||
|
||||
Assert.Null(Correlation.CurrentCorrelation);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndRequest();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void SettingShouldLogDirectly_WithoutAnActiveCorrelation_ShouldSetResetShouldLogDirectly()
|
||||
{
|
||||
try
|
||||
{
|
||||
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
|
||||
Correlation.CorrelationStart(null);
|
||||
|
||||
Assert.False(Correlation.ShouldLogDirectly, "ShouldLogDirectly should be set to false by default.");
|
||||
|
||||
Correlation.ShouldLogDirectly = true;
|
||||
|
||||
Assert.True(Correlation.ShouldLogDirectly, "ShouldLogDirectly should be set to true.");
|
||||
|
||||
Correlation.ShouldLogDirectly = false;
|
||||
|
||||
Assert.False(Correlation.ShouldLogDirectly, "ShouldLogDirectly should be reset to false.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndRequest();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void SettingShouldLogDirectly_OnCurrentCorrelation_ShouldSetOnCorrelationOnly()
|
||||
{
|
||||
try
|
||||
{
|
||||
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
|
||||
|
||||
Assert.False(Correlation.ShouldLogDirectly, "ShouldLogDirectly should be set to false by default.");
|
||||
|
||||
Correlation.CorrelationStart(null);
|
||||
Correlation.ShouldLogDirectly = true;
|
||||
|
||||
Assert.True(Correlation.ShouldLogDirectly, "ShouldLogDirectly should be set to true.");
|
||||
|
||||
Correlation.CorrelationEnd();
|
||||
|
||||
Assert.False(Correlation.ShouldLogDirectly, "ShouldLogDirectly should be set to false after correlation ended.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndRequest();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void SettingShouldLogDirectly_OnBaseCorrelation_ShouldBeInheritedByCurrentCorrelation()
|
||||
{
|
||||
try
|
||||
{
|
||||
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
|
||||
|
||||
Assert.False(Correlation.ShouldLogDirectly, "ShouldLogDirectly should be set to false by default.");
|
||||
|
||||
Correlation.CorrelationStart(null);
|
||||
Correlation.ShouldLogDirectly = true;
|
||||
|
||||
Correlation.CorrelationStart(null);
|
||||
|
||||
Assert.True(Correlation.ShouldLogDirectly, "ShouldLogDirectly should be set to true.");
|
||||
Correlation.CorrelationClear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndRequest();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private ICallContextManager CreateCallContextManager()
|
||||
{
|
||||
return new CallContextManager();
|
||||
}
|
||||
|
||||
|
||||
private Correlation Correlation { get; set; }
|
||||
|
||||
|
||||
private ICallContextManager CallContextManagerInstance { get; set; }
|
||||
|
||||
|
||||
private IMachineInformation MachineInformation { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create an HttpContext and configure HttpContext.Current
|
||||
/// </summary>
|
||||
private static void SetHttpContextCurrent()
|
||||
{
|
||||
HttpContext httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Host = new HostString("localhost");
|
||||
|
||||
m_httpContextAccessor.Value.HttpContext = httpContext;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// End a request
|
||||
/// </summary>
|
||||
private void EndRequest()
|
||||
{
|
||||
ICallContext context = CallContextManagerInstance.CallContextOverride;
|
||||
|
||||
ICallContext existingContext = context.ExistingCallContext();
|
||||
Correlation.CorrelationEnd();
|
||||
context.EndCallContext();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net461;$(NetCoreVersion)</TargetFrameworks>
|
||||
<Description>Contains unit tests for Omex libraries in AspNetCore.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Moq" Version="$(MoqVersionCore)" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="$(MSTestAdapterVersion)" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="$(MSTestAdapterVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\System\Microsoft.Omex.System.csproj" />
|
||||
<ProjectReference Include="..\..\src\System.AspNetCore\Microsoft.Omex.System.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\src\System.UnitTests.Shared\Microsoft.Omex.System.UnitTests.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,165 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Omex.System.Context;
|
||||
using Microsoft.Omex.System.Diagnostics;
|
||||
using Microsoft.Omex.System.TimedScopes;
|
||||
using Microsoft.Omex.System.TimedScopes.ReplayEventLogging;
|
||||
using Microsoft.Omex.System.UnitTests.Shared.Diagnostics;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using Assert = Xunit.Assert;
|
||||
|
||||
namespace Microsoft.Omex.System.AspNetCore.UnitTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Unit tests for verifying functionality of ReplayEventConfigurator class
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class ReplayEventConfiguratorUnitTests : AspNetCoreUnitTestsBase
|
||||
{
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
SetHttpContextCurrent();
|
||||
|
||||
CallContextManagerInstance = CreateCallContextManager();
|
||||
CallContextManagerInstance.CallContextOverride = new HttpCallContext(useLogicalCallContext: true);
|
||||
|
||||
MachineInformation = new UnitTestMachineInformation();
|
||||
|
||||
HttpCallContext callContext = CallContextManagerInstance.CallContextHandler(MachineInformation) as HttpCallContext;
|
||||
callContext.ExistingCallContext();
|
||||
callContext.StartCallContext();
|
||||
}
|
||||
|
||||
|
||||
[TestCleanup]
|
||||
public void end()
|
||||
{
|
||||
ICallContext context = CallContextManagerInstance.CallContextOverride;
|
||||
|
||||
ICallContext existingContext = context.ExistingCallContext();
|
||||
Correlation.CorrelationEnd();
|
||||
context.EndCallContext();
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void SuccessTimedScope_DoesntReplayLogs()
|
||||
{
|
||||
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
|
||||
Correlation.CorrelationStart(new CorrelationData());
|
||||
|
||||
CorrelationData currentCorrelation = Correlation.CurrentCorrelation;
|
||||
|
||||
Assert.False(currentCorrelation.ShouldReplayUls);
|
||||
|
||||
using (TimedScope scope = TimedScope.Start(currentCorrelation, MachineInformation, "TestScope",
|
||||
customLogger: timedScopeLoggerMock.Object, replayEventConfigurator: replyEventConfiguratorMock.Object))
|
||||
{
|
||||
scope.Result = TimedScopeResult.Success;
|
||||
|
||||
Mock<IReplayEventDisabledTimedScopes> disabledScopes = new Mock<IReplayEventDisabledTimedScopes>();
|
||||
disabledScopes.Setup(x => x.IsDisabled(scope.ScopeDefinition)).Returns(false);
|
||||
|
||||
ReplayEventConfigurator configurator = new ReplayEventConfigurator(disabledScopes.Object, Correlation);
|
||||
configurator.ConfigureReplayEventsOnScopeEnd(scope);
|
||||
}
|
||||
|
||||
Assert.False(currentCorrelation.ShouldReplayUls);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void FailedTimedScope_ShouldReplayLogs()
|
||||
{
|
||||
|
||||
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
Mock<ILogEventCache> mockCache = new Mock<ILogEventCache>();
|
||||
|
||||
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
|
||||
Correlation.CorrelationStart(new CorrelationData(mockCache.Object));
|
||||
|
||||
CorrelationData currentCorrelation = Correlation.CurrentCorrelation;
|
||||
|
||||
Assert.False(currentCorrelation.ShouldReplayUls, "Logs shouldn't be replayed");
|
||||
|
||||
using (TimedScope scope = TimedScope.Start(currentCorrelation, MachineInformation, "TestScope",
|
||||
customLogger: timedScopeLoggerMock.Object, replayEventConfigurator: replyEventConfiguratorMock.Object))
|
||||
{
|
||||
scope.Result = TimedScopeResult.SystemError;
|
||||
|
||||
Mock<IReplayEventDisabledTimedScopes> disabledScopes = new Mock<IReplayEventDisabledTimedScopes>();
|
||||
disabledScopes.Setup(x => x.IsDisabled(scope.ScopeDefinition)).Returns(false);
|
||||
|
||||
ReplayEventConfigurator configurator = new ReplayEventConfigurator(disabledScopes.Object, Correlation);
|
||||
configurator.ConfigureReplayEventsOnScopeEnd(scope);
|
||||
}
|
||||
|
||||
Assert.True(currentCorrelation.ShouldReplayUls, "Logs should be replayed");
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void FailedDisabledTimedScope_DoesntReplayLogs()
|
||||
{
|
||||
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
|
||||
Correlation.CorrelationStart(new CorrelationData());
|
||||
|
||||
CorrelationData currentCorrelation = Correlation.CurrentCorrelation;
|
||||
|
||||
Assert.False(currentCorrelation.ShouldReplayUls, "Logs shouldn't be replayed");
|
||||
|
||||
using (TimedScope scope = TimedScope.Start(currentCorrelation, MachineInformation, "TestScope",
|
||||
customLogger: timedScopeLoggerMock.Object, replayEventConfigurator: replyEventConfiguratorMock.Object))
|
||||
{
|
||||
scope.Result = TimedScopeResult.SystemError;
|
||||
|
||||
Mock<IReplayEventDisabledTimedScopes> disabledScopes = new Mock<IReplayEventDisabledTimedScopes>();
|
||||
disabledScopes.Setup(x => x.IsDisabled(scope.ScopeDefinition)).Returns(true);
|
||||
|
||||
ReplayEventConfigurator configurator = new ReplayEventConfigurator(disabledScopes.Object, Correlation);
|
||||
configurator.ConfigureReplayEventsOnScopeEnd(scope);
|
||||
}
|
||||
|
||||
Assert.False(currentCorrelation.ShouldReplayUls, "Logs shouldn't be replayed");
|
||||
}
|
||||
|
||||
|
||||
private ICallContextManager CreateCallContextManager()
|
||||
{
|
||||
return new CallContextManager();
|
||||
}
|
||||
|
||||
|
||||
private Correlation Correlation { get; set; }
|
||||
|
||||
|
||||
private ICallContextManager CallContextManagerInstance { get; set; }
|
||||
|
||||
|
||||
private IMachineInformation MachineInformation { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create an HttpContext and configure HttpContext.Current
|
||||
/// </summary>
|
||||
private static void SetHttpContextCurrent()
|
||||
{
|
||||
HttpContext httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Host = new HostString("localhost");
|
||||
|
||||
m_httpContextAccessor.Value.HttpContext = httpContext;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Omex.System.Caching;
|
||||
using Xunit;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.Shared.Caching
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using Microsoft.Omex.System.Data;
|
||||
using Microsoft.Omex.System.Data.FileSystem;
|
||||
using Microsoft.Omex.System.UnitTests.Shared;
|
||||
using Microsoft.Omex.System.UnitTests.Shared.Data.FileSystem;
|
||||
using Xunit;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.Data
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -7,12 +7,11 @@
|
|||
<ItemGroup Condition="'$(TargetFramework)' != '$(NetCoreVersion)'">
|
||||
<Reference Include="System.Runtime" />
|
||||
<Reference Include="System.Threading.Tasks" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersion)" />
|
||||
<PackageReference Include="Moq" Version="$(MoqVersion)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == '$(NetCoreVersion)'">
|
||||
<PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersionCore)" />
|
||||
<PackageReference Include="Moq" Version="$(MoqVersionCore)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\System.UnitTests.Shared\Microsoft.Omex.System.UnitTests.Shared.csproj" />
|
||||
<ProjectReference Include="..\..\src\System\Microsoft.Omex.System.csproj" />
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using Microsoft.Omex.System.Monads;
|
||||
using Microsoft.Omex.System.UnitTests.Shared;
|
||||
using Xunit;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.Monads
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.Omex.System.Monads;
|
||||
using Microsoft.Omex.System.UnitTests.Shared;
|
||||
using Xunit;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.Monads
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.Omex.System.Monads;
|
||||
using Microsoft.Omex.System.UnitTests.Shared;
|
||||
using Xunit;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.Monads
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -0,0 +1,262 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Omex.System.TimedScopes;
|
||||
using Microsoft.Omex.System.UnitTests.Shared;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Unit tests for verifying functionality of TimedScopeInstanceName class
|
||||
/// </summary>
|
||||
public class TimedScopeInstanceNameUnitTests : UnitTestBase
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Test Scope
|
||||
/// </summary>
|
||||
private const string TestScope = "TestScope";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Test Subtype
|
||||
/// </summary>
|
||||
private const string TestSubtype = "TestSubType";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Test Metadata
|
||||
/// </summary>
|
||||
private const string TestMetadata = "TestMetadata";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Test Classification
|
||||
/// </summary>
|
||||
private const TimedScopeResult TestClassification = TimedScopeResult.SystemError;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Test Scope with classification suffix
|
||||
/// </summary>
|
||||
private const string TestScopeWithClassification = TestScope + ".SystemError";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Valid scope name
|
||||
/// </summary>
|
||||
private const string OnlyScope = TestScopeWithClassification;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Valid scope name
|
||||
/// </summary>
|
||||
private const string ScopeWithSubtype = TestScopeWithClassification + "/" + TestSubtype;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Valid scope name
|
||||
/// </summary>
|
||||
private const string ScopeWithMetadata = TestScopeWithClassification + "/" + TestMetadata;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Valid scope name
|
||||
/// </summary>
|
||||
private const string ScopeWithSubtypeAndMetadata = TestScopeWithClassification + "/" + TestSubtype + "/" + TestMetadata;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Valid scope name
|
||||
/// </summary>
|
||||
private const string ScopeWithEmptySubtype = TestScopeWithClassification + "/";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Valid scope name
|
||||
/// </summary>
|
||||
private const string ScopeWithEmptySubtypeAndMetadata = TestScopeWithClassification + "//" + TestMetadata;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Valid scope name
|
||||
/// </summary>
|
||||
private const string ScopeNameWithDescription = TestScopeWithClassification + ".Description/" + TestSubtype + "/" + TestMetadata;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="log">Log helper</param>
|
||||
public TimedScopeInstanceNameUnitTests(ITestOutputHelper log)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Verify_ValidScopeInstanceNames_Parsed()
|
||||
{
|
||||
TimedScopeInstanceName scopeName;
|
||||
|
||||
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(OnlyScope, out scopeName)))
|
||||
{
|
||||
Assert.Equal(scopeName.Scope, TestScope);
|
||||
Assert.Equal(null, scopeName.SubType);
|
||||
Assert.Equal(null, scopeName.MetaData);
|
||||
Assert.Equal(TestClassification, scopeName.Classification);
|
||||
}
|
||||
|
||||
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(ScopeWithSubtype, out scopeName)))
|
||||
{
|
||||
Assert.Equal(TestScope, scopeName.Scope);
|
||||
Assert.Equal(TestSubtype, scopeName.SubType);
|
||||
Assert.Equal(null, scopeName.MetaData);
|
||||
Assert.Equal(TestClassification, scopeName.Classification);
|
||||
}
|
||||
|
||||
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(ScopeWithMetadata, out scopeName, preferMetaData: true)))
|
||||
{
|
||||
Assert.Equal(TestScope, scopeName.Scope);
|
||||
Assert.Equal(null, scopeName.SubType);
|
||||
Assert.Equal(TestMetadata, scopeName.MetaData);
|
||||
Assert.Equal(TestClassification, scopeName.Classification);
|
||||
}
|
||||
|
||||
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(ScopeWithSubtypeAndMetadata, out scopeName)))
|
||||
{
|
||||
Assert.Equal(TestScope, scopeName.Scope);
|
||||
Assert.Equal(TestSubtype, scopeName.SubType);
|
||||
Assert.Equal(TestMetadata, scopeName.MetaData);
|
||||
Assert.Equal(TestClassification, scopeName.Classification);
|
||||
}
|
||||
|
||||
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(ScopeNameWithDescription, out scopeName)))
|
||||
{
|
||||
Assert.Equal(TestScope, scopeName.Scope);
|
||||
Assert.Equal(TestSubtype, scopeName.SubType);
|
||||
Assert.Equal(TestMetadata, scopeName.MetaData);
|
||||
Assert.Equal(TestClassification, scopeName.Classification);
|
||||
}
|
||||
|
||||
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(ScopeWithEmptySubtype, out scopeName)))
|
||||
{
|
||||
Assert.Equal(TestScope, scopeName.Scope);
|
||||
Assert.Equal(string.Empty, scopeName.SubType);
|
||||
Assert.Equal(null, scopeName.MetaData);
|
||||
Assert.Equal(TestClassification, scopeName.Classification);
|
||||
}
|
||||
|
||||
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(ScopeWithEmptySubtypeAndMetadata, out scopeName)))
|
||||
{
|
||||
Assert.Equal(TestScope, scopeName.Scope);
|
||||
Assert.Equal(string.Empty, scopeName.SubType);
|
||||
Assert.Equal(TestMetadata, scopeName.MetaData);
|
||||
Assert.Equal(TestClassification, scopeName.Classification);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Verify_MalformedScopeNames_NotParsed()
|
||||
{
|
||||
TimedScopeInstanceName scopeName;
|
||||
|
||||
Assert.False(TimedScopeInstanceName.TryParse(".SystemError", out scopeName));
|
||||
Assert.False(TimedScopeInstanceName.TryParse(null, out scopeName));
|
||||
Assert.False(TimedScopeInstanceName.TryParse("", out scopeName));
|
||||
Assert.False(TimedScopeInstanceName.TryParse("///", out scopeName));
|
||||
Assert.False(TimedScopeInstanceName.TryParse("...///", out scopeName));
|
||||
Assert.False(TimedScopeInstanceName.TryParse("Scope.MalformedClassification/x", out scopeName));
|
||||
Assert.False(TimedScopeInstanceName.TryParse("/x/y", out scopeName));
|
||||
Assert.False(TimedScopeInstanceName.TryParse("x/y/z/a", out scopeName));
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Verify_HashCodesAndEquality()
|
||||
{
|
||||
CheckSingleScope(OnlyScope);
|
||||
CheckSingleScope(ScopeWithSubtype);
|
||||
CheckSingleScope(ScopeWithMetadata);
|
||||
CheckSingleScope(ScopeWithSubtypeAndMetadata);
|
||||
CheckSingleScope(ScopeNameWithDescription);
|
||||
CheckSingleScope(ScopeWithEmptySubtype);
|
||||
CheckSingleScope(ScopeWithEmptySubtypeAndMetadata);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that parsing the same string gives equal TimedScopeInstanceName instances
|
||||
/// </summary>
|
||||
/// <param name="scopeName">The string to be parsed</param>
|
||||
private void CheckSingleScope(string scopeName)
|
||||
{
|
||||
TimedScopeInstanceName parsed1;
|
||||
TimedScopeInstanceName parsed2;
|
||||
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(scopeName, out parsed1)) &&
|
||||
VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(scopeName, out parsed2)))
|
||||
{
|
||||
CheckHashCodesAndEquality(parsed1, parsed2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Check equality of two timed scope instance names by all possible ways
|
||||
/// </summary>
|
||||
/// <param name="x">First instance</param>
|
||||
/// <param name="y">Second instance</param>
|
||||
private void CheckHashCodesAndEquality(TimedScopeInstanceName x, TimedScopeInstanceName y)
|
||||
{
|
||||
Assert.Equal(x.GetHashCode(), y.GetHashCode());
|
||||
Assert.True(x.Equals(y));
|
||||
Assert.True(x.Equals((object)y));
|
||||
Assert.True(x == y);
|
||||
Assert.False(x != y);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Verify_ParseAndToStringRoundTrip_EqualStringRepresentation()
|
||||
{
|
||||
CheckRoundTrip(OnlyScope);
|
||||
CheckRoundTrip(ScopeWithSubtype);
|
||||
CheckRoundTrip(ScopeWithMetadata);
|
||||
CheckRoundTrip(ScopeWithSubtypeAndMetadata);
|
||||
CheckRoundTrip(ScopeWithEmptySubtype);
|
||||
CheckRoundTrip(ScopeWithEmptySubtypeAndMetadata);
|
||||
|
||||
// We are not checking round trip for ScopeNameWithDescription as we do not store the description in the scope name
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks that parsing a string and then converting back to string gives the same string
|
||||
/// </summary>
|
||||
/// <param name="scopeName">String to be parsed and converted back</param>
|
||||
private void CheckRoundTrip(string scopeName)
|
||||
{
|
||||
TimedScopeInstanceName parsed;
|
||||
if (VerifyTrueAndReturn(TimedScopeInstanceName.TryParse(scopeName, out parsed)))
|
||||
{
|
||||
Assert.Equal(parsed.ToString(), scopeName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a condition is true and returns the value.
|
||||
/// </summary>
|
||||
/// <param name="condition">The condition to check.</param>
|
||||
/// <param name="message">The logged message.</param>
|
||||
/// <param name="args">Format string args.</param>
|
||||
/// <returns>The evaluation result.</returns>
|
||||
private bool VerifyTrueAndReturn(bool condition)
|
||||
{
|
||||
Assert.True(condition);
|
||||
return condition;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Omex.System.TimedScopes;
|
||||
using Microsoft.Omex.System.UnitTests.Shared;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Unit tests for verifying functionality of TimedScopeName class
|
||||
/// </summary>
|
||||
public class TimedScopeNameUnitTests : UnitTestBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Test Scope
|
||||
/// </summary>
|
||||
private const string TestScope = "TestScope";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Test Subtype
|
||||
/// </summary>
|
||||
private const string TestSubtype = "TestSubType";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Test Metadata
|
||||
/// </summary>
|
||||
private const string TestMetadata = "TestMetadata";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Valid scope name
|
||||
/// </summary>
|
||||
private const string OnlyScope = TestScope;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Valid scope name
|
||||
/// </summary>
|
||||
private const string ScopeWithSubtype = TestScope + "/" + TestSubtype;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Valid scope name
|
||||
/// </summary>
|
||||
private const string ScopeWithMetadata = TestScope + "/" + TestMetadata;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Valid scope name
|
||||
/// </summary>
|
||||
private const string ScopeWithSubtypeAndMetadata = TestScope + "/" + TestSubtype + "/" + TestMetadata;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Valid scope name
|
||||
/// </summary>
|
||||
private const string ScopeWithEmptySubtype = TestScope + "/";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Valid scope name
|
||||
/// </summary>
|
||||
private const string ScopeWithEmptySubtypeAndMetadata = TestScope + "//" + TestMetadata;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="log">Log helper</param>
|
||||
public TimedScopeNameUnitTests(ITestOutputHelper log)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Verify_ValidScopeNames_Parsed()
|
||||
{
|
||||
TimedScopeName scopeName;
|
||||
|
||||
if (VerifyTrueAndReturn(TimedScopeName.TryParse(OnlyScope, out scopeName)))
|
||||
{
|
||||
Assert.Equal(TestScope, scopeName.Scope);
|
||||
Assert.Equal(null, scopeName.SubType);
|
||||
Assert.Equal(null, scopeName.MetaData);
|
||||
}
|
||||
|
||||
if (VerifyTrueAndReturn(TimedScopeName.TryParse(ScopeWithSubtype, out scopeName)))
|
||||
{
|
||||
Assert.Equal(TestScope, scopeName.Scope);
|
||||
Assert.Equal(TestSubtype, scopeName.SubType);
|
||||
Assert.Equal(null, scopeName.MetaData);
|
||||
}
|
||||
|
||||
if (VerifyTrueAndReturn(TimedScopeName.TryParse(ScopeWithMetadata, out scopeName, preferMetaData: true)))
|
||||
{
|
||||
Assert.Equal(TestScope, scopeName.Scope);
|
||||
Assert.Equal(null, scopeName.SubType);
|
||||
Assert.Equal(TestMetadata, scopeName.MetaData);
|
||||
}
|
||||
|
||||
if (VerifyTrueAndReturn(TimedScopeName.TryParse(ScopeWithSubtypeAndMetadata, out scopeName)))
|
||||
{
|
||||
Assert.Equal(TestScope, scopeName.Scope);
|
||||
Assert.Equal(TestSubtype, scopeName.SubType);
|
||||
Assert.Equal(TestMetadata, scopeName.MetaData);
|
||||
}
|
||||
|
||||
if (VerifyTrueAndReturn(TimedScopeName.TryParse(ScopeWithEmptySubtype, out scopeName)))
|
||||
{
|
||||
Assert.Equal(TestScope, scopeName.Scope);
|
||||
Assert.Equal(string.Empty, scopeName.SubType);
|
||||
Assert.Equal(null, scopeName.MetaData);
|
||||
}
|
||||
|
||||
if (VerifyTrueAndReturn(TimedScopeName.TryParse(ScopeWithEmptySubtypeAndMetadata, out scopeName)))
|
||||
{
|
||||
Assert.Equal(TestScope, scopeName.Scope);
|
||||
Assert.Equal(string.Empty, scopeName.SubType);
|
||||
Assert.Equal(TestMetadata, scopeName.MetaData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Verify_MalformedScopeNames_NotParsed()
|
||||
{
|
||||
TimedScopeName scopeName;
|
||||
|
||||
Assert.False(TimedScopeName.TryParse(null, out scopeName), "Malformed scope name should not be parsed");
|
||||
Assert.False(TimedScopeName.TryParse("", out scopeName), "Malformed scope name should not be parsed");
|
||||
Assert.False(TimedScopeName.TryParse("/x", out scopeName), "Malformed scope name should not be parsed");
|
||||
Assert.False(TimedScopeName.TryParse("/x/y", out scopeName), "Malformed scope name should not be parsed");
|
||||
Assert.False(TimedScopeName.TryParse("x/y/z/a", out scopeName), "Malformed scope name should not be parsed");
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Verify_HashCodesAndEquality()
|
||||
{
|
||||
CheckSingleScope(OnlyScope);
|
||||
CheckSingleScope(ScopeWithSubtype);
|
||||
CheckSingleScope(ScopeWithMetadata);
|
||||
CheckSingleScope(ScopeWithSubtypeAndMetadata);
|
||||
CheckSingleScope(ScopeWithEmptySubtype);
|
||||
CheckSingleScope(ScopeWithEmptySubtypeAndMetadata);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that parsing the same string (even with changed letter casing) gives equal TimedScopeName instances
|
||||
/// </summary>
|
||||
/// <param name="scopeName">The string to be parsed</param>
|
||||
private void CheckSingleScope(string scopeName)
|
||||
{
|
||||
TimedScopeName parsed1;
|
||||
TimedScopeName parsed2;
|
||||
if (VerifyTrueAndReturn(TimedScopeName.TryParse(scopeName, out parsed1)) &&
|
||||
VerifyTrueAndReturn(TimedScopeName.TryParse(scopeName, out parsed2)))
|
||||
{
|
||||
CheckHashCodesAndEquality(parsed1, parsed2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Check equality of two timed scope name instances by all possible ways
|
||||
/// </summary>
|
||||
/// <param name="x">First instance</param>
|
||||
/// <param name="y">Second instance</param>
|
||||
private void CheckHashCodesAndEquality(TimedScopeName x, TimedScopeName y)
|
||||
{
|
||||
Assert.Equal(x.GetHashCode(), y.GetHashCode());
|
||||
Assert.True(x.Equals(y));
|
||||
Assert.True(x.Equals((object)y));
|
||||
Assert.True(x == y);
|
||||
Assert.False(x != y);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Verify_ParseAndToStringRoundTrip_EqualStringRepresentation()
|
||||
{
|
||||
CheckRoundTrip(OnlyScope);
|
||||
CheckRoundTrip(ScopeWithSubtype);
|
||||
CheckRoundTrip(ScopeWithMetadata);
|
||||
CheckRoundTrip(ScopeWithSubtypeAndMetadata);
|
||||
CheckRoundTrip(ScopeWithEmptySubtype);
|
||||
CheckRoundTrip(ScopeWithEmptySubtypeAndMetadata);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks that parsing a string and then converting back to string gives the same string
|
||||
/// </summary>
|
||||
/// <param name="scopeName">String to be parsed and converted back</param>
|
||||
private void CheckRoundTrip(string scopeName)
|
||||
{
|
||||
TimedScopeName parsed;
|
||||
if (VerifyTrueAndReturn(TimedScopeName.TryParse(scopeName, out parsed)))
|
||||
{
|
||||
Assert.Equal(parsed.ToString(), scopeName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a condition is true and returns the value.
|
||||
/// </summary>
|
||||
/// <param name="condition">The condition to check.</param>
|
||||
/// <param name="message">The logged message.</param>
|
||||
/// <param name="args">Format string args.</param>
|
||||
/// <returns>The evaluation result.</returns>
|
||||
private bool VerifyTrueAndReturn(bool condition)
|
||||
{
|
||||
Assert.True(condition);
|
||||
return condition;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,413 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.Omex.System.Logging;
|
||||
using Microsoft.Omex.System.TimedScopes;
|
||||
using Microsoft.Omex.System.TimedScopes.ReplayEventLogging;
|
||||
using Microsoft.Omex.System.UnitTests.Shared;
|
||||
using Microsoft.Omex.System.UnitTests.Shared.Diagnostics;
|
||||
using Microsoft.Omex.System.UnitTests.Shared.TimedScopes;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.TimedScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Unit tests for verifying functionality of TimedScope class
|
||||
/// </summary>
|
||||
public sealed class TimedScopeUnitTests : UnitTestBase
|
||||
{
|
||||
|
||||
[Fact]
|
||||
public void Scope_TimedScopeLogger_IsCalled()
|
||||
{
|
||||
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
TimedScope scope;
|
||||
CorrelationData data = new CorrelationData();
|
||||
|
||||
using (scope = new TimedScopeDefinition("TestScope")
|
||||
.Create(data, new UnitTestMachineInformation(), TimedScopeResult.SystemError, customLogger: timedScopeLoggerMock.Object,
|
||||
replayEventConfigurator: replyEventConfiguratorMock.Object))
|
||||
{
|
||||
timedScopeLoggerMock.Verify(x => x.LogScopeStart(scope), Times.Once);
|
||||
timedScopeLoggerMock.Verify(x => x.LogScopeEnd(scope, It.IsAny<CorrelationData>()), Times.Never);
|
||||
}
|
||||
|
||||
timedScopeLoggerMock.Verify(x => x.LogScopeEnd(scope, It.IsAny<CorrelationData>()), Times.Once);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Start_ShouldConstructTimedScope_WithTimerActive()
|
||||
{
|
||||
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
using (TimedScope scope = TestHooks.CreateTestCountersUnitTestTimedScope(false, true, timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object))
|
||||
{
|
||||
Assert.True(scope.IsScopeActive, "Starting a scope should start the timer.");
|
||||
|
||||
Assert.True(scope.IsSuccessful.HasValue, "Starting a scope should start the timer.");
|
||||
Assert.False(scope.IsSuccessful.Value, "IsSuccessful should be set to false.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Create_ShouldConstructTimedScope_WithTimerInactive()
|
||||
{
|
||||
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
using (TimedScope scope = TestHooks.CreateTestCountersUnitTestTimedScope(true, false, timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object))
|
||||
{
|
||||
Assert.False(scope.IsScopeActive, "Creating a scope should not start the timer.");
|
||||
|
||||
Assert.True(scope.IsSuccessful.HasValue, "IsSuccessful should be set.");
|
||||
Assert.True(scope.IsSuccessful.Value, "IsSuccessful should be set to true.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void AbortTimer_ShouldDisableTimerActive()
|
||||
{
|
||||
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
using (TimedScope scope = TestHooks.CreateDefaultTimedScope(timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object))
|
||||
{
|
||||
Assert.True(scope.IsScopeActive, "Default scope should have timer active.");
|
||||
|
||||
scope.AbortTimer();
|
||||
Assert.False(scope.IsScopeActive, "Aborting timer should stop timer.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void AbortTimer_ShouldDisableTimerActive_AndSetResultsToTrue()
|
||||
{
|
||||
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
using (TimedScope scope = TestHooks.CreateDefaultTimedScope(timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object))
|
||||
{
|
||||
Assert.True(scope.IsScopeActive, "Default scope should have timer active.");
|
||||
|
||||
scope.AbortTimer(true);
|
||||
Assert.False(scope.IsScopeActive, "Aborting timer should stop timer.");
|
||||
Assert.True(scope.IsSuccessful.HasValue, "IsSuccessful should be set.");
|
||||
Assert.True(scope.IsSuccessful.Value, "IsSuccesful should be set to true.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void AbortTimer_ShouldDisableTimerActive_AndSetResultsToFalse()
|
||||
{
|
||||
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
using (TimedScope scope = TestHooks.CreateDefaultTimedScope(scopeLogger: timedScopeLoggerMock.Object,
|
||||
replayEventConfigurator: replyEventConfiguratorMock.Object, startScope: false))
|
||||
{
|
||||
Assert.False(scope.IsScopeActive, "Default scope started without an active scope should have timer active.");
|
||||
|
||||
scope.Start();
|
||||
|
||||
Assert.True(scope.IsScopeActive, "Default scope should have timer active.");
|
||||
|
||||
scope.AbortTimer(false);
|
||||
Assert.False(scope.IsScopeActive, "Aborting timer should stop timer.");
|
||||
Assert.True(scope.IsSuccessful.HasValue, "IsSuccessful should be set.");
|
||||
Assert.False(scope.IsSuccessful.Value, "IsSuccesful should be set to false.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void AddLoggingValue_ShouldOutputValueInLogEvent()
|
||||
{
|
||||
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
using (TimedScope scope = TestHooks.CreateTestCountersUnitTestTimedScope(scopeLogger: timedScopeLoggerMock.Object,
|
||||
replayEventConfigurator: replyEventConfiguratorMock.Object))
|
||||
{
|
||||
scope.AddLoggingValue(TimedScopeDataKeys.Category, "MyCategory");
|
||||
scope.End(new UnitTestMachineInformation());
|
||||
|
||||
// There should be one 'Ending' transaction log call with formatted output
|
||||
foreach (LogEventArgs args in LoggedEvents)
|
||||
{
|
||||
if (args.Category == Categories.TimingGeneral)
|
||||
{
|
||||
if (args.FullMessage.Contains("Ending timed scope"))
|
||||
{
|
||||
Assert.Contains("Category:'MyCategory';", args.FullMessage, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void FailedScope_ResultAndFailureDescription_ShouldOutputValueInLogEvent()
|
||||
{
|
||||
FailOnErrors = false;
|
||||
|
||||
UnitTestTimedScopeLogger unitTestTimedScopeLogger = new UnitTestTimedScopeLogger();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
using (TimedScope scope = TestHooks.CreateTestCountersUnitTestTimedScope(scopeLogger: unitTestTimedScopeLogger,
|
||||
replayEventConfigurator: replyEventConfiguratorMock.Object))
|
||||
{
|
||||
scope.Result = TimedScopeResult.ExpectedError;
|
||||
scope.FailureDescription = UnitTestFailureDescription.ExampleDescription;
|
||||
}
|
||||
|
||||
TimedScopeLogEvent scopeEvent = unitTestTimedScopeLogger.Events.SingleOrDefault();
|
||||
|
||||
if (VerifyNotNullAndReturn(scopeEvent, "Scope end event should be logged"))
|
||||
{
|
||||
Assert.Equal(scopeEvent.Result, TimedScopeResult.ExpectedError);
|
||||
Assert.Equal(scopeEvent.FailureDescription, UnitTestFailureDescription.ExampleDescription.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void SucceededScope_Result_ShouldOutputValueInLogEvent()
|
||||
{
|
||||
|
||||
UnitTestTimedScopeLogger unitTestTimedScopeLogger = new UnitTestTimedScopeLogger();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
using (TimedScope scope = TestHooks.CreateTestCountersUnitTestTimedScope(scopeLogger: unitTestTimedScopeLogger,
|
||||
replayEventConfigurator: replyEventConfiguratorMock.Object))
|
||||
{
|
||||
scope.Result = TimedScopeResult.Success;
|
||||
}
|
||||
|
||||
TimedScopeLogEvent scopeEvent = unitTestTimedScopeLogger.Events.SingleOrDefault();
|
||||
|
||||
if (VerifyNotNullAndReturn(scopeEvent, "Timed scope should be logged"))
|
||||
{
|
||||
Assert.Equal(scopeEvent.Result, TimedScopeResult.Success);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void AddLoggingValue_WithNullKey_ShouldLogError()
|
||||
{
|
||||
FailOnErrors = false;
|
||||
|
||||
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
using (TimedScope scope = TestHooks.CreateDefaultTimedScope(timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object))
|
||||
{
|
||||
scope.AddLoggingValue(null, "My Application.");
|
||||
|
||||
Assert.Equal(TraceErrors.Count(), 1);
|
||||
LoggedEvents.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Start_DisposedTimedScope_ShoudLogError()
|
||||
{
|
||||
FailOnErrors = false;
|
||||
|
||||
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
TimedScope scope = TestHooks.CreateDefaultTimedScope(timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object);
|
||||
scope.Dispose();
|
||||
|
||||
scope.Start();
|
||||
|
||||
Assert.Equal(TraceErrors.Count(), 1);
|
||||
LoggedEvents.Clear();
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Start_WithActiveTimer_ShouldLogError()
|
||||
{
|
||||
FailOnErrors = false;
|
||||
|
||||
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
using (TimedScope scope = TestHooks.CreateDefaultTimedScope(timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object))
|
||||
{
|
||||
Assert.True(scope.IsScopeActive, "Timer should be active.");
|
||||
|
||||
scope.Start();
|
||||
|
||||
Assert.Equal(TraceErrors.Count(), 1);
|
||||
LoggedEvents.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void End_WithDisposedTimedScope_ShouldLogError()
|
||||
{
|
||||
FailOnErrors = false;
|
||||
|
||||
Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
using (TimedScope scope = TestHooks.CreateDefaultTimedScope(timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object))
|
||||
{
|
||||
Assert.True(scope.IsScopeActive, "Timer should be active.");
|
||||
|
||||
scope.Dispose();
|
||||
|
||||
Assert.False(scope.IsScopeActive, "Dispose should turn off timer.");
|
||||
|
||||
scope.End(new UnitTestMachineInformation());
|
||||
|
||||
Assert.Equal(TraceErrors.Count(), 1);
|
||||
LoggedEvents.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Frequency_ShouldMatchFrequencyOfStopwatch()
|
||||
{
|
||||
Assert.Equal(TimedScope.Frequency, Stopwatch.Frequency);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void NotSettingTimedScopeResult_ChangesToSystemError()
|
||||
{
|
||||
LoggedEvents.Clear();
|
||||
|
||||
UnitTestTimedScopeLogger unitTestTimedScopeLogger = new UnitTestTimedScopeLogger();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
using (TimedScope scope = TestHooks.CreateDefaultTimedScope(unitTestTimedScopeLogger, replyEventConfiguratorMock.Object))
|
||||
{
|
||||
}
|
||||
|
||||
TimedScopeLogEvent evt = unitTestTimedScopeLogger.SingleTimedScopeEvent(TestHooks.DefaultTimedScopeName);
|
||||
if (VerifyNotNullAndReturn(evt, "A scope event has been logged"))
|
||||
{
|
||||
Assert.Equal(evt.Result, TimedScopeResult.SystemError);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void DefaultTimedScopeResult_IsSeparateValue()
|
||||
{
|
||||
// Default value should never be used explicitly and is marked as Obsolete for that reason, so it can't be used.
|
||||
// But we need a separate default value to support the "not yet set" state. Verify the default is actually separate
|
||||
// and does not map to any of the real result values.
|
||||
TimedScopeResult result = default(TimedScopeResult);
|
||||
|
||||
Assert.NotEqual(TimedScopeResult.Success, result);
|
||||
Assert.NotEqual(TimedScopeResult.SystemError, result);
|
||||
Assert.NotEqual(TimedScopeResult.ExpectedError, result);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void DefaultTimedScopeResult_LogsAsSystemError()
|
||||
{
|
||||
LoggedEvents.Clear();
|
||||
|
||||
CorrelationData data = new CorrelationData();
|
||||
|
||||
UnitTestTimedScopeLogger unitTestTimedScopeLogger = new UnitTestTimedScopeLogger();
|
||||
Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
|
||||
|
||||
using (TimedScope.Create(data, new UnitTestMachineInformation(), TestHooks.DefaultTimedScopeName, "description", default(TimedScopeResult), unitTestTimedScopeLogger, replyEventConfiguratorMock.Object))
|
||||
{
|
||||
}
|
||||
|
||||
TimedScopeLogEvent evt = unitTestTimedScopeLogger.SingleTimedScopeEvent(TestHooks.DefaultTimedScopeName);
|
||||
if (VerifyNotNullAndReturn(evt, "A scope event has been logged"))
|
||||
{
|
||||
Assert.Equal(TimedScopeResult.SystemError, evt.Result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Unit test failure descriptions
|
||||
/// </summary>
|
||||
private enum UnitTestFailureDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// Example description
|
||||
/// </summary>
|
||||
ExampleDescription,
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a reference type is not null and returns the evaluation result.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The reference type.</typeparam>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="message">The logged message.</param>
|
||||
/// <param name="args">Format string args.</param>
|
||||
/// <returns>The evaluation result.</returns>
|
||||
private bool VerifyNotNullAndReturn<T>(T value, string message, params object[] args) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
Assert.NotNull(value);
|
||||
return value != null;
|
||||
}
|
||||
catch (NotNullException)
|
||||
{
|
||||
Console.WriteLine(message, args);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Trace errors
|
||||
/// </summary>
|
||||
private IEnumerable<LogEventArgs> TraceErrors
|
||||
{
|
||||
get
|
||||
{
|
||||
return LoggedEvents.Except(ReportExceptions).Where((args) => { return args.Level == Levels.Error; });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Exceptions reported
|
||||
/// </summary>
|
||||
private IEnumerable<ReportExceptionEventArgs> ReportExceptions
|
||||
{
|
||||
get
|
||||
{
|
||||
IEnumerable<LogEventArgs> events = LoggedEvents;
|
||||
|
||||
return LoggedEvents.OfType<ReportExceptionEventArgs>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -11,8 +9,6 @@ using Microsoft.Omex.System.UnitTests.Shared;
|
|||
using Microsoft.Omex.System.Validation;
|
||||
using Xunit;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Microsoft.Omex.System.UnitTests.Validation
|
||||
{
|
||||
/// <summary>
|
||||
|
|
Загрузка…
Ссылка в новой задаче