diff --git a/Dependencies.props b/Dependencies.props index cd375491..d4b7ecac 100644 --- a/Dependencies.props +++ b/Dependencies.props @@ -36,6 +36,7 @@ 3.1.2 3.1.2 3.1.2 + 4.5.3 4.7.0 2.1.0 2.1.0 diff --git a/src/Extensions/Abstractions/Activities/ActivityExtensions.cs b/src/Extensions/Abstractions/Activities/ActivityExtensions.cs new file mode 100644 index 00000000..32f4514b --- /dev/null +++ b/src/Extensions/Abstractions/Activities/ActivityExtensions.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Diagnostics; +using System.Globalization; + +namespace Microsoft.Omex.Extensions.Abstractions.Activities +{ + /// + /// Extensions for Activity + /// + public static class ActivityExtensions + { + /// + /// Get user hash from activity + /// + public static string GetUserHash(this Activity activity) => + activity.GetBaggageItem(UserHashKey) ?? string.Empty; + + + /// + /// Returns true if activity is transaction + /// + public static bool IsTransaction(this Activity activity) => + string.Equals( + activity.GetBaggageItem(TransactionMarkerKey), + TransactionMarkerValue, + StringComparison.OrdinalIgnoreCase); + + + /// + /// Set user hash for the activity + /// + /// This property would be transfered to child activity and via web requests + public static Activity SetUserHash(this Activity activity, string userHash) => + activity.AddBaggage(UserHashKey, userHash); + + + /// + /// Mark activity as transaction + /// + /// This property would be transfered to child activity and via web requests + public static Activity MarkAsTransaction(this Activity activity) => + activity.AddBaggage(TransactionMarkerKey, TransactionMarkerValue); + + + /// + /// Get correlation guid that is used by old Omex services + /// + [Obsolete(CorrelationIdObsoleteMessage, false)] + public static Guid? GetObsoleteCorrelationId(this Activity activity) => + Guid.TryParse(activity.GetBaggageItem(ObsoleteCorrelationId), out Guid correlation) + ? correlation + : (Guid?)null; + + + /// + /// Set correlation guid that is used by old Omex services + /// + /// This property would be transfered to child activity and via web requests + [Obsolete(CorrelationIdObsoleteMessage, false)] + public static Activity SetObsoleteCorrelationId(this Activity activity, Guid correlation) => + activity.AddBaggage(ObsoleteCorrelationId, correlation.ToString()); + + + /// + /// Get transaction id that is used by old Omex services + /// + [Obsolete(TransactionIdObsoleteMessage, false)] + public static uint? GetObsolteteTransactionId(this Activity activity) => + uint.TryParse(activity.GetBaggageItem(ObsoleteTransactionId), out uint transactionId) + ? transactionId + : (uint?)null; + + + /// + /// Set transaction id that is used by old Omex services + /// + /// This property would be transfered to child activity and via web requests + [Obsolete(TransactionIdObsoleteMessage, false)] + public static Activity SetObsoleteTransactionId(this Activity activity, uint transactionId) => + activity.AddBaggage(ObsoleteTransactionId, transactionId.ToString(CultureInfo.InvariantCulture)); + + + private const string UserHashKey = "UserHash"; + private const string TransactionMarkerKey = "TransactionMarkerKey"; + private const string TransactionMarkerValue = "true"; + private const string ObsoleteCorrelationId = "ObsoleteCorrelationId"; + private const string ObsoleteTransactionId = "ObsoleteTransactionId"; + private const string CorrelationIdObsoleteMessage = "Please use Activity.Id for new services instead"; + private const string TransactionIdObsoleteMessage = "Please use Activity.TraceId for new services instead"; + } +} diff --git a/src/Extensions/TimedScopes/ITimedScopeProvider.cs b/src/Extensions/Abstractions/Activities/ITimedScopeProvider.cs similarity index 76% rename from src/Extensions/TimedScopes/ITimedScopeProvider.cs rename to src/Extensions/Abstractions/Activities/ITimedScopeProvider.cs index 51000d50..44ec8ba1 100644 --- a/src/Extensions/TimedScopes/ITimedScopeProvider.cs +++ b/src/Extensions/Abstractions/Activities/ITimedScopeProvider.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using Microsoft.Omex.Extensions.Abstractions; - -namespace Microsoft.Omex.Extensions.TimedScopes +namespace Microsoft.Omex.Extensions.Abstractions.Activities { /// /// Interface to create TimedScope @@ -13,12 +11,12 @@ public interface ITimedScopeProvider /// /// Creates and start TimedScope /// - TimedScope CreateAndStart(TimedScopeDefinition name, TimedScopeResult result); + TimedScope CreateAndStart(TimedScopeDefinition name, TimedScopeResult result = TimedScopeResult.SystemError); /// /// Creates TimedScope /// - TimedScope Create(TimedScopeDefinition name, TimedScopeResult result); + TimedScope Create(TimedScopeDefinition name, TimedScopeResult result = TimedScopeResult.SystemError); } } diff --git a/src/Extensions/Abstractions/Activities/Processing/ActivityResultStrings.cs b/src/Extensions/Abstractions/Activities/Processing/ActivityResultStrings.cs new file mode 100644 index 00000000..821c8fdf --- /dev/null +++ b/src/Extensions/Abstractions/Activities/Processing/ActivityResultStrings.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace Microsoft.Omex.Extensions.Abstractions.Activities.Processing +{ + /// + /// Class with activity result enum string values to avoid alocations + /// + public static class ActivityResultStrings + { + /// + /// Activity SystemError result string + /// + public static string SystemError { get; } = "SystemError"; + + + /// + /// Activity ExpectedError result string + /// + public static string ExpectedError { get; } = "ExpectedError"; + + + /// + /// Activity Success result string + /// + public static string Success { get; } = "Success"; + + + /// + /// Activity Unknown result string + /// + public static string Unknown { get; } = "Unknown"; + + + /// + /// Returns corresponding to enum string value with creating new string + /// + public static string ResultToString(TimedScopeResult result) => + result switch + { + TimedScopeResult.SystemError => SystemError, + TimedScopeResult.ExpectedError => ExpectedError, + TimedScopeResult.Success => Success, + _ => throw new ArgumentException(FormattableString.Invariant($"Unsupported enum value '{result}'")) + }; + } +} diff --git a/src/Extensions/Abstractions/Activities/Processing/ActivityTagKeys.cs b/src/Extensions/Abstractions/Activities/Processing/ActivityTagKeys.cs new file mode 100644 index 00000000..e68cdfe6 --- /dev/null +++ b/src/Extensions/Abstractions/Activities/Processing/ActivityTagKeys.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.Omex.Extensions.Abstractions.Activities.Processing +{ + /// + /// Activity does not have methods to extract tags efficiently, so we need to expose keys to provide ability of extracting tags + /// + public static class ActivityTagKeys + { + /// + /// Activity result tag key + /// + public static string Result { get; } = "Result"; + + + /// + /// Activity sub type tag key + /// + public static string SubType { get; } = "SubType"; + + + /// + /// Activity metadata tag key + /// + public static string Metadata { get; } = "Metadata"; + } +} diff --git a/src/Extensions/Abstractions/ReplayableLogs/IActivityProvider.cs b/src/Extensions/Abstractions/Activities/Processing/IActivityProvider.cs similarity index 67% rename from src/Extensions/Abstractions/ReplayableLogs/IActivityProvider.cs rename to src/Extensions/Abstractions/Activities/Processing/IActivityProvider.cs index 7dd01095..8d04b807 100644 --- a/src/Extensions/Abstractions/ReplayableLogs/IActivityProvider.cs +++ b/src/Extensions/Abstractions/Activities/Processing/IActivityProvider.cs @@ -3,11 +3,12 @@ using System.Diagnostics; -namespace Microsoft.Omex.Extensions.Abstractions.ReplayableLogs +namespace Microsoft.Omex.Extensions.Abstractions.Activities.Processing { /// /// Provides activities /// + /// This interface would be deleted after move to net 5.0, since inheritance won't be a supported extension model for Activity public interface IActivityProvider { /// diff --git a/src/Extensions/Abstractions/Activities/Processing/IActivityStartObserver.cs b/src/Extensions/Abstractions/Activities/Processing/IActivityStartObserver.cs new file mode 100644 index 00000000..bebca40b --- /dev/null +++ b/src/Extensions/Abstractions/Activities/Processing/IActivityStartObserver.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Diagnostics; + +namespace Microsoft.Omex.Extensions.Abstractions.Activities.Processing +{ + /// + /// Interface to monitor activity start + /// + public interface IActivityStartObserver + { + /// + /// Method will be called after activity start + /// + void OnStart(Activity activity, object? payload); + } +} diff --git a/src/Extensions/Abstractions/Activities/Processing/IActivityStopObserver.cs b/src/Extensions/Abstractions/Activities/Processing/IActivityStopObserver.cs new file mode 100644 index 00000000..b67641cf --- /dev/null +++ b/src/Extensions/Abstractions/Activities/Processing/IActivityStopObserver.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Diagnostics; + +namespace Microsoft.Omex.Extensions.Abstractions.Activities.Processing +{ + /// + /// Interface to monitor activity stop + /// + public interface IActivityStopObserver + { + /// + /// Method will be called after activity stop + /// + void OnStop(Activity activity, object? payload); + } +} diff --git a/src/Extensions/TimedScopes/TaskExtensions.cs b/src/Extensions/Abstractions/Activities/TaskExtensions.cs similarity index 89% rename from src/Extensions/TimedScopes/TaskExtensions.cs rename to src/Extensions/Abstractions/Activities/TaskExtensions.cs index 1a3b4317..226d5e16 100644 --- a/src/Extensions/TimedScopes/TaskExtensions.cs +++ b/src/Extensions/Abstractions/Activities/TaskExtensions.cs @@ -2,9 +2,8 @@ // Licensed under the MIT license. using System.Threading.Tasks; -using Microsoft.Omex.Extensions.Abstractions; -namespace Microsoft.Omex.Extensions.TimedScopes +namespace Microsoft.Omex.Extensions.Abstractions.Activities { /// /// Extensions for Task to wrap them in TimedScope @@ -18,7 +17,7 @@ public static async ValueTask WithTimedScope(this ValueTask + /// Logs duration of activity + /// + /// This class will be replaced with Activity after move to net 5, since it will be trackable and disposable + public class TimedScope : IDisposable + { + /// + /// Creates TimedScope instance + /// + /// activity connected to this timedscope + /// TimedScope initial result + public TimedScope(Activity activity, TimedScopeResult result) + { + Activity = activity; + this.SetResult(result); + } + + + /// + /// Starts TimedScope activity + /// + public TimedScope Start() + { + s_listener.StartActivity(Activity, this); + return this; + } + + + /// + /// Stop TimedScope and log informations about it + /// + public void Stop() => s_listener.StopActivity(Activity, this); + + + internal Activity Activity { get; } + + + void IDisposable.Dispose() => Stop(); + + + private static readonly DiagnosticListener s_listener = new DiagnosticListener("TimedScopesListener"); + } +} diff --git a/src/Extensions/Abstractions/TimedScopeDefinition.cs b/src/Extensions/Abstractions/Activities/TimedScopeDefinition.cs similarity index 97% rename from src/Extensions/Abstractions/TimedScopeDefinition.cs rename to src/Extensions/Abstractions/Activities/TimedScopeDefinition.cs index 9976e4e0..00e05f9c 100644 --- a/src/Extensions/Abstractions/TimedScopeDefinition.cs +++ b/src/Extensions/Abstractions/Activities/TimedScopeDefinition.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Omex.Extensions.Abstractions +namespace Microsoft.Omex.Extensions.Abstractions.Activities { /// /// Store Timed Scope name diff --git a/src/Extensions/TimedScopes/TimedScopeResult.cs b/src/Extensions/Abstractions/Activities/TimedScopeResult.cs similarity index 89% rename from src/Extensions/TimedScopes/TimedScopeResult.cs rename to src/Extensions/Abstractions/Activities/TimedScopeResult.cs index 624adcba..94292a3b 100644 --- a/src/Extensions/TimedScopes/TimedScopeResult.cs +++ b/src/Extensions/Abstractions/Activities/TimedScopeResult.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.Serialization; -namespace Microsoft.Omex.Extensions.TimedScopes +namespace Microsoft.Omex.Extensions.Abstractions.Activities { /// /// Defines the possible scope results @@ -21,7 +21,7 @@ public enum TimedScopeResult : int /// /// 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. [EnumMember] - [Obsolete("Default value, not to be used explicitly", error: true)] + [Obsolete("Default value, not to be used explicitly", error: false)] Unknown = 0, diff --git a/src/Extensions/Abstractions/Activities/TimedScopesExtensions.cs b/src/Extensions/Abstractions/Activities/TimedScopesExtensions.cs new file mode 100644 index 00000000..a6b43695 --- /dev/null +++ b/src/Extensions/Abstractions/Activities/TimedScopesExtensions.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.Omex.Extensions.Abstractions.Activities.Processing; + +namespace Microsoft.Omex.Extensions.Abstractions.Activities +{ + /// + /// Extensions to TimedScopes class to set additional information + /// + public static class TimedScopesExtensions + { + /// + /// Set result + /// + /// This property won't be transfered to child activity or via web requests + public static TimedScope SetResult(this TimedScope timedScope, TimedScopeResult result) + { + timedScope.Activity.AddTag(ActivityTagKeys.Result, ActivityResultStrings.ResultToString(result)); + return timedScope; + } + + + /// + /// Set sub type + /// + /// This property won't be transfered to child activity or via web requests + public static TimedScope SetSubType(this TimedScope timedScope, string subType) + { + timedScope.Activity.AddTag(ActivityTagKeys.SubType, subType); + return timedScope; + } + + + /// + /// Set metadata + /// + /// This property won't be transfered to child activity or via web requests + public static TimedScope SetMetadata(this TimedScope timedScope, string metadata) + { + timedScope.Activity.AddTag(ActivityTagKeys.Metadata, metadata); + return timedScope; + } + } +} diff --git a/src/Extensions/Abstractions/Microsoft.Omex.Extensions.Abstractions.csproj b/src/Extensions/Abstractions/Microsoft.Omex.Extensions.Abstractions.csproj index 70c67353..5cace99f 100644 --- a/src/Extensions/Abstractions/Microsoft.Omex.Extensions.Abstractions.csproj +++ b/src/Extensions/Abstractions/Microsoft.Omex.Extensions.Abstractions.csproj @@ -8,6 +8,9 @@ + + + diff --git a/src/Extensions/Compatibility/OmexCompatibilityIntializer.cs b/src/Extensions/Compatibility/OmexCompatibilityIntializer.cs index 46d33b72..6b496e9c 100644 --- a/src/Extensions/Compatibility/OmexCompatibilityIntializer.cs +++ b/src/Extensions/Compatibility/OmexCompatibilityIntializer.cs @@ -5,10 +5,10 @@ using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Omex.Extensions.Abstractions.Activities; using Microsoft.Omex.Extensions.Compatibility.Logger; using Microsoft.Omex.Extensions.Compatibility.TimedScopes; using Microsoft.Omex.Extensions.Compatibility.Validation; -using Microsoft.Omex.Extensions.TimedScopes; namespace Microsoft.Omex.Extensions.Compatibility { diff --git a/src/Extensions/Compatibility/ServiceCollectionExtensions.cs b/src/Extensions/Compatibility/ServiceCollectionExtensions.cs index f460d60b..e425e323 100644 --- a/src/Extensions/Compatibility/ServiceCollectionExtensions.cs +++ b/src/Extensions/Compatibility/ServiceCollectionExtensions.cs @@ -17,7 +17,7 @@ public static class ServiceCollectionExtensions public static IHostBuilder AddOmexCompatibilityServices(this IHostBuilder builder) => builder.ConfigureServices((context, collection) => { - collection.AddTransient(); + collection.AddHostedService(); }); } } diff --git a/src/Extensions/Compatibility/TimedScopes/TimedScopeDefinitionExtensions.cs b/src/Extensions/Compatibility/TimedScopes/TimedScopeDefinitionExtensions.cs index 6e372d90..fed02696 100644 --- a/src/Extensions/Compatibility/TimedScopes/TimedScopeDefinitionExtensions.cs +++ b/src/Extensions/Compatibility/TimedScopes/TimedScopeDefinitionExtensions.cs @@ -2,8 +2,7 @@ // Licensed under the MIT license. using System; -using Microsoft.Omex.Extensions.Abstractions; -using Microsoft.Omex.Extensions.TimedScopes; +using Microsoft.Omex.Extensions.Abstractions.Activities; namespace Microsoft.Omex.Extensions.Compatibility.TimedScopes { diff --git a/src/Extensions/Hosting.Services/HostBuilderExtensions.cs b/src/Extensions/Hosting.Services/HostBuilderExtensions.cs index 17ef7468..f15c2bdb 100644 --- a/src/Extensions/Hosting.Services/HostBuilderExtensions.cs +++ b/src/Extensions/Hosting.Services/HostBuilderExtensions.cs @@ -126,8 +126,8 @@ private static IHost BuildServiceFabricService( { collection .AddOmexServiceFabricDependencies() - .AddTransient() - .AddSingleton(); + .AddSingleton() + .AddHostedService(); }) .UseDefaultServiceProvider(options => { diff --git a/src/Extensions/Hosting.Services/Internal/OmexHostedService.cs b/src/Extensions/Hosting.Services/Internal/OmexHostedService.cs index e8589048..402356f7 100644 --- a/src/Extensions/Hosting.Services/Internal/OmexHostedService.cs +++ b/src/Extensions/Hosting.Services/Internal/OmexHostedService.cs @@ -14,58 +14,24 @@ namespace Microsoft.Omex.Extensions.Hosting.Services /// /// Wraps service run into HostedService /// - internal sealed class OmexHostedService : IHostedService + internal sealed class OmexHostedService : BackgroundService { - private readonly IHostApplicationLifetime m_lifetime; private readonly IOmexServiceRunner m_runner; private readonly ILogger m_logger; - private readonly CancellationTokenSource m_tokenSource; - public OmexHostedService(IOmexServiceRunner runner, IHostApplicationLifetime lifetime, ILogger logger) + public OmexHostedService(IOmexServiceRunner runner, ILogger logger) { m_runner = runner; - m_lifetime = lifetime; m_logger = logger; - m_tokenSource = new CancellationTokenSource(); } - public Task StartAsync(CancellationToken cancellationToken) - { - m_lifetime.ApplicationStarted.Register(OnServiceStarted); - m_lifetime.ApplicationStopping.Register(OnServiceStopping); - return Task.CompletedTask; - } - - - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - - private void OnServiceStarted() - { - RunServiceAsync() - .ContinueWith(OnRunnerCompleted) - .ConfigureAwait(false); - } - - - private void OnServiceStopping() - { - if (m_tokenSource.IsCancellationRequested) - { - return; - } - - m_tokenSource.Cancel(); - } - - - private async Task RunServiceAsync() + protected override async Task ExecuteAsync(CancellationToken token) { try { - ConfiguredTaskAwaitable task = m_runner.RunServiceAsync(m_tokenSource.Token).ConfigureAwait(false); + ConfiguredTaskAwaitable task = m_runner.RunServiceAsync(token).ConfigureAwait(false); m_logger.LogInformation(Tag.Create(), "ServiceFabricHost initialized"); @@ -77,25 +43,5 @@ private async Task RunServiceAsync() throw; } } - - - private Task OnRunnerCompleted(Task task) - { - if (task.IsFaulted) - { - m_logger.LogCritical(Tag.Create(), task.Exception, "Service stoped due to exeption"); - } - else if (task.IsCanceled) - { - m_logger.LogInformation(Tag.Create(), "Service canceled"); - } - else - { - m_logger.LogInformation(Tag.Create(), "Service stopped"); - } - - m_lifetime.StopApplication(); - return Task.CompletedTask; - } } } diff --git a/src/Extensions/Logging/ILogEventSender.cs b/src/Extensions/Logging/ILogEventSender.cs index 4307296b..0faeb234 100644 --- a/src/Extensions/Logging/ILogEventSender.cs +++ b/src/Extensions/Logging/ILogEventSender.cs @@ -32,6 +32,6 @@ public interface ILogEventSender /// event Id /// Id of the thread /// Log message - void LogMessage(Activity activity, string category, LogLevel level, EventId eventId, int threadId, string message); + void LogMessage(Activity? activity, string category, LogLevel level, EventId eventId, int threadId, string message); } } diff --git a/src/Extensions/Logging/Internal/EventSource/ActivityEnhancementObserver.cs b/src/Extensions/Logging/Internal/EventSource/ActivityEnhancementObserver.cs new file mode 100644 index 00000000..ff3e25f7 --- /dev/null +++ b/src/Extensions/Logging/Internal/EventSource/ActivityEnhancementObserver.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Diagnostics; +using Microsoft.Extensions.Options; +using Microsoft.Omex.Extensions.Abstractions.Activities; +using Microsoft.Omex.Extensions.Abstractions.Activities.Processing; + +namespace Microsoft.Omex.Extensions.Logging +{ + internal class ActivityEnhancementObserver : IActivityStartObserver + { + private readonly IOptions m_options; + + public ActivityEnhancementObserver(IOptions options) + { + m_options = options; + } + + public void OnStart(Activity activity, object? payload) + { + if (m_options.Value.AddObsoleteCorrelationToActivity) + { +#pragma warning disable CS0618 // if we are supporting old correlation we need to add it for new activities + if (activity.GetObsoleteCorrelationId() == null) + { + activity.SetObsoleteCorrelationId(Guid.NewGuid()); + } +#pragma warning disable CS0618 + } + } + } +} diff --git a/src/Extensions/Logging/Internal/EventSource/OmexLogEventSender.cs b/src/Extensions/Logging/Internal/EventSource/OmexLogEventSender.cs index 6c097da5..2eb8e961 100644 --- a/src/Extensions/Logging/Internal/EventSource/OmexLogEventSender.cs +++ b/src/Extensions/Logging/Internal/EventSource/OmexLogEventSender.cs @@ -4,9 +4,9 @@ using System; using System.Diagnostics; using System.Globalization; -using System.Text; using Microsoft.Extensions.Logging; -using Microsoft.Omex.Extensions.Abstractions.ReplayableLogs; +using Microsoft.Extensions.Options; +using Microsoft.Omex.Extensions.Abstractions.Activities; using Microsoft.Omex.Extensions.Logging.Replayable; namespace Microsoft.Omex.Extensions.Logging @@ -20,65 +20,88 @@ static OmexLogEventSender() } - public OmexLogEventSender(OmexLogEventSource eventSource, IExecutionContext machineInformation, IServiceContext context) + public OmexLogEventSender(OmexLogEventSource eventSource, IExecutionContext executionContext, IServiceContext context, IOptions options) { m_eventSource = eventSource; - m_machineInformation = machineInformation; + m_executionContext = executionContext; m_serviceContext = context; + m_options = options; } - public void LogMessage(Activity activity, string category, LogLevel level, EventId eventId, int threadId, string message) + public void LogMessage(Activity? activity, string category, LogLevel level, EventId eventId, int threadId, string message) { if (!IsEnabled(level)) { return; } - string activityId = activity?.Id ?? string.Empty; - ActivityTraceId traceId = activity?.TraceId ?? default; Guid partitionId = m_serviceContext.PartitionId; long replicaId = m_serviceContext.ReplicaOrInstanceId; - string applicationName = m_machineInformation.ApplicationName; - string serviceName = m_machineInformation.ServiceName; - string buildVersion = m_machineInformation.BuildVersion; - string machineId = m_machineInformation.MachineId; + string applicationName = m_executionContext.ApplicationName; + string serviceName = m_executionContext.ServiceName; + string buildVersion = m_executionContext.BuildVersion; + string machineId = m_executionContext.MachineId; string tagName = eventId.Name; // In case if tag created using Tag.Create (line number and file in description) it's better to display decimal number string tagId = string.IsNullOrWhiteSpace(eventId.Name) -#pragma warning disable 618 // need to be used for to process reserved tags from GitTagger +#pragma warning disable CS0618 // Need to be used for to process reserved tags from GitTagger ? TagsExtensions.TagIdAsString(eventId.Id) -#pragma warning restore 618 +#pragma warning restore CS0618 : eventId.Id.ToString(CultureInfo.InvariantCulture); - string traceIdAsString = traceId.ToHexString(); + string activityId = string.Empty; + ActivityTraceId activityTraceId = default; + Guid obsoleteCorrelationId = Guid.Empty; + uint obsoleteTransactionId = 0u; + if (activity != null) + { + activityId = activity.Id ?? string.Empty; + activityTraceId = activity.TraceId; + + if (m_options.Value.AddObsoleteCorrelationToActivity) + { +#pragma warning disable CS0618 // We are using obsolete correlation to support logging correlation from old Omex services + obsoleteCorrelationId = activity.GetObsoleteCorrelationId() ?? Guid.Empty; + obsoleteTransactionId = activity.GetObsolteteTransactionId() ?? 0u; +#pragma warning restore CS0618 + } + } + + string traceIdAsString = activityTraceId.ToHexString(); //Event methods should have all information as parameters so we are passing them each time // Posible Breaking changes: - // 1. CorrelationId type changed from Guid ?? Guid.Empty - // 2. TransactionId type Changed from uint ?? 0u - // 3. ThreadId type Changed from string - // 4. TagName to events so it should be also send + // 1. ThreadId type Changed from string to avoid useless string creation + // 2. New fileds added: + // a. tagName to events since it will have more useful information + // b. activityId required for tracking net core activity + // c. activityTraceId required for tracking net core activity switch (level) { case LogLevel.None: break; case LogLevel.Trace: - m_eventSource.LogSpamServiceMessage(applicationName, serviceName, machineId, buildVersion, s_processName, partitionId, replicaId, activityId, traceIdAsString, "Spam", category, tagId, tagName, threadId, message); + m_eventSource.LogSpamServiceMessage(applicationName, serviceName, machineId, buildVersion, s_processName, partitionId, replicaId, + activityId, traceIdAsString, obsoleteCorrelationId, obsoleteTransactionId, "Spam", category, tagId, tagName, threadId, message); break; case LogLevel.Debug: - m_eventSource.LogVerboseServiceMessage(applicationName, serviceName, machineId, buildVersion, s_processName, partitionId, replicaId, activityId, traceIdAsString, "Verbose", category, tagId, tagName, threadId, message); + m_eventSource.LogVerboseServiceMessage(applicationName, serviceName, machineId, buildVersion, s_processName, partitionId, replicaId, + activityId, traceIdAsString, obsoleteCorrelationId, obsoleteTransactionId, "Verbose", category, tagId, tagName, threadId, message); break; case LogLevel.Information: - m_eventSource.LogInfoServiceMessage(applicationName, serviceName, machineId, buildVersion, s_processName, partitionId, replicaId, activityId, traceIdAsString, "Info", category, tagId, tagName, threadId, message); + m_eventSource.LogInfoServiceMessage(applicationName, serviceName, machineId, buildVersion, s_processName, partitionId, replicaId, + activityId, traceIdAsString, obsoleteCorrelationId, obsoleteTransactionId, "Info", category, tagId, tagName, threadId, message); break; case LogLevel.Warning: - m_eventSource.LogWarningServiceMessage(applicationName, serviceName, machineId, buildVersion, s_processName, partitionId, replicaId, activityId, traceIdAsString, "Warning", category, tagId, tagName, threadId, message); + m_eventSource.LogWarningServiceMessage(applicationName, serviceName, machineId, buildVersion, s_processName, partitionId, replicaId, + activityId, traceIdAsString, obsoleteCorrelationId, obsoleteTransactionId, "Warning", category, tagId, tagName, threadId, message); break; case LogLevel.Error: case LogLevel.Critical: - m_eventSource.LogErrorServiceMessage(applicationName, serviceName, machineId, buildVersion, s_processName, partitionId, replicaId, activityId, traceIdAsString, "Error", category, tagId, eventId.Name, threadId, message); + m_eventSource.LogErrorServiceMessage(applicationName, serviceName, machineId, buildVersion, s_processName, partitionId, replicaId, + activityId, traceIdAsString, obsoleteCorrelationId, obsoleteTransactionId, "Error", category, tagId, eventId.Name, threadId, message); break; default: throw new ArgumentException(FormattableString.Invariant($"Unknown EventLevel: {level}")); @@ -123,7 +146,8 @@ public void ReplayLogs(Activity activity) private readonly OmexLogEventSource m_eventSource; private readonly IServiceContext m_serviceContext; - private readonly IExecutionContext m_machineInformation; + private readonly IOptions m_options; + private readonly IExecutionContext m_executionContext; private static readonly string s_processName; } } diff --git a/src/Extensions/Logging/Internal/EventSource/OmexLogEventSource.cs b/src/Extensions/Logging/Internal/EventSource/OmexLogEventSource.cs index c9f4db52..d5816746 100644 --- a/src/Extensions/Logging/Internal/EventSource/OmexLogEventSource.cs +++ b/src/Extensions/Logging/Internal/EventSource/OmexLogEventSource.cs @@ -19,15 +19,18 @@ public void LogErrorServiceMessage( string processName, Guid partitionId, long replicaId, - string correlationId, - string transactionId, + string activityId, + string activityTraceId, + Guid correlationId, + uint transactionId, string level, string category, string tagId, string tagName, int threadId, string message) => - WriteEvent((int)EventSourcesEventIds.LogError, applicationName, serviceName, agentName, buildVersion, processName, partitionId, replicaId, correlationId, transactionId, level, category, tagId, tagName, threadId, message); + WriteEvent((int)EventSourcesEventIds.LogError, applicationName, serviceName, agentName, buildVersion, processName, partitionId, replicaId, + activityId, activityTraceId, correlationId, transactionId, level, category, tagId, tagName, threadId, message); [Event((int)EventSourcesEventIds.LogWarning, Level = EventLevel.Warning, Message = "{13}", Version = 6)] @@ -39,15 +42,18 @@ public void LogWarningServiceMessage( string processName, Guid partitionId, long replicaId, - string correlationId, - string transactionId, + string activityId, + string activityTraceId, + Guid correlationId, + uint transactionId, string level, string category, string tagId, string tagName, int threadId, string message) => - WriteEvent((int)EventSourcesEventIds.LogWarning, applicationName, serviceName, agentName, buildVersion, processName, partitionId, replicaId, correlationId, transactionId, level, category, tagId, tagName, threadId, message); + WriteEvent((int)EventSourcesEventIds.LogWarning, applicationName, serviceName, agentName, buildVersion, processName, partitionId, replicaId, + activityId, activityTraceId, correlationId, transactionId, level, category, tagId, tagName, threadId, message); [Event((int)EventSourcesEventIds.LogInfo, Level = EventLevel.Informational, Message = "{13}", Version = 6)] @@ -59,15 +65,18 @@ public void LogInfoServiceMessage( string processName, Guid partitionId, long replicaId, - string correlationId, - string transactionId, + string activityId, + string activityTraceId, + Guid correlationId, + uint transactionId, string level, string category, string tagId, string tagName, int threadId, string message) => - WriteEvent((int)EventSourcesEventIds.LogInfo, applicationName, serviceName, agentName, buildVersion, processName, partitionId, replicaId, correlationId, transactionId, level, category, tagId, tagName, threadId, message); + WriteEvent((int)EventSourcesEventIds.LogInfo, applicationName, serviceName, agentName, buildVersion, processName, partitionId, replicaId, + activityId, activityTraceId, correlationId, transactionId, level, category, tagId, tagName, threadId, message); [Event((int)EventSourcesEventIds.LogVerbose, Level = EventLevel.Verbose, Message = "{13}", Version = 6)] @@ -79,15 +88,18 @@ public void LogVerboseServiceMessage( string processName, Guid partitionId, long replicaId, - string correlationId, - string transactionId, + string activityId, + string activityTraceId, + Guid correlationId, + uint transactionId, string level, string category, string tagId, string tagName, int threadId, string message) => - WriteEvent((int)EventSourcesEventIds.LogVerbose, applicationName, serviceName, agentName, buildVersion, processName, partitionId, replicaId, correlationId, transactionId, level, category, tagId, tagName, threadId, message); + WriteEvent((int)EventSourcesEventIds.LogVerbose, applicationName, serviceName, agentName, buildVersion, processName, partitionId, replicaId, + activityId, activityTraceId, correlationId, transactionId, level, category, tagId, tagName, threadId, message); [Event((int)EventSourcesEventIds.LogSpam, Level = EventLevel.Verbose, Message = "{13}", Version = 6)] @@ -99,15 +111,18 @@ public void LogSpamServiceMessage( string processName, Guid partitionId, long replicaId, - string correlationId, - string transactionId, + string activityId, + string activityTraceId, + Guid correlationId, + uint transactionId, string level, string category, string tagId, string tagName, int threadId, string message) => - WriteEvent((int)EventSourcesEventIds.LogSpam, applicationName, serviceName, agentName, buildVersion, processName, partitionId, replicaId, correlationId, transactionId, level, category, tagId, tagName, threadId, message); + WriteEvent((int)EventSourcesEventIds.LogSpam, applicationName, serviceName, agentName, buildVersion, processName, partitionId, replicaId, + activityId, activityTraceId, correlationId, transactionId, level, category, tagId, tagName, threadId, message); public static OmexLogEventSource Instance { get; } = new OmexLogEventSource(); diff --git a/src/Extensions/Logging/Internal/OmexLogger.cs b/src/Extensions/Logging/Internal/OmexLogger.cs index 0cd13a34..6fe70fce 100644 --- a/src/Extensions/Logging/Internal/OmexLogger.cs +++ b/src/Extensions/Logging/Internal/OmexLogger.cs @@ -34,7 +34,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except string message = formatter(state, exception); int threadId = Thread.CurrentThread.ManagedThreadId; - Activity activity = Activity.Current; + Activity? activity = Activity.Current; m_logsEventSender.LogMessage(activity, m_categoryName, logLevel, eventId, threadId, message); diff --git a/src/Extensions/Abstractions/ReplayableLogs/ILogEventReplayer.cs b/src/Extensions/Logging/Internal/Replayable/ILogEventReplayer.cs similarity index 80% rename from src/Extensions/Abstractions/ReplayableLogs/ILogEventReplayer.cs rename to src/Extensions/Logging/Internal/Replayable/ILogEventReplayer.cs index 67bf7b70..0421acd7 100644 --- a/src/Extensions/Abstractions/ReplayableLogs/ILogEventReplayer.cs +++ b/src/Extensions/Logging/Internal/Replayable/ILogEventReplayer.cs @@ -3,12 +3,12 @@ using System.Diagnostics; -namespace Microsoft.Omex.Extensions.Abstractions.ReplayableLogs +namespace Microsoft.Omex.Extensions.Logging.Replayable { /// /// Replays activity logs if activity collects them /// - public interface ILogEventReplayer + internal interface ILogEventReplayer { /// /// Replays activity logs if activity supports it diff --git a/src/Extensions/Logging/Internal/Replayable/ReplayableActivityProvider.cs b/src/Extensions/Logging/Internal/Replayable/ReplayableActivityProvider.cs index ffc8eae4..8e785124 100644 --- a/src/Extensions/Logging/Internal/Replayable/ReplayableActivityProvider.cs +++ b/src/Extensions/Logging/Internal/Replayable/ReplayableActivityProvider.cs @@ -3,8 +3,8 @@ using System.Diagnostics; using Microsoft.Extensions.Options; -using Microsoft.Omex.Extensions.Abstractions; -using Microsoft.Omex.Extensions.Abstractions.ReplayableLogs; +using Microsoft.Omex.Extensions.Abstractions.Activities; +using Microsoft.Omex.Extensions.Abstractions.Activities.Processing; namespace Microsoft.Omex.Extensions.Logging.Replayable { diff --git a/src/Extensions/Logging/Internal/Replayable/ReplayableActivityStopObserver.cs b/src/Extensions/Logging/Internal/Replayable/ReplayableActivityStopObserver.cs new file mode 100644 index 00000000..5b26393b --- /dev/null +++ b/src/Extensions/Logging/Internal/Replayable/ReplayableActivityStopObserver.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Diagnostics; +using System.Linq; +using Microsoft.Extensions.Options; +using Microsoft.Omex.Extensions.Abstractions.Activities.Processing; + +namespace Microsoft.Omex.Extensions.Logging.Replayable +{ + internal class ReplayableActivityStopObserver : IActivityStopObserver + { + private readonly ILogEventReplayer m_logReplayer; + private readonly IOptions m_options; + + public ReplayableActivityStopObserver(ILogEventReplayer logReplayer, IOptions options) + { + m_logReplayer = logReplayer; + m_options = options; + } + + public void OnStop(Activity activity, object? payload) + { + if (m_options.Value.ReplayLogsInCaseOfError && ShouldReplayEvents(activity)) + { + m_logReplayer.ReplayLogs(activity); + } + } + + private bool ShouldReplayEvents(Activity activity) + { + string resultTagValue = activity.Tags.FirstOrDefault(p => string.Equals(p.Key, ActivityTagKeys.Result, StringComparison.Ordinal)).Value; + return string.Equals(ActivityResultStrings.SystemError, resultTagValue, StringComparison.Ordinal); + } + } +} diff --git a/src/Extensions/Logging/OmexLoggingOptions.cs b/src/Extensions/Logging/OmexLoggingOptions.cs index 2d8c363f..16edc5c2 100644 --- a/src/Extensions/Logging/OmexLoggingOptions.cs +++ b/src/Extensions/Logging/OmexLoggingOptions.cs @@ -15,6 +15,12 @@ public class OmexLoggingOptions public bool ReplayLogsInCaseOfError { get; set; } = false; + /// + /// Enabling this option will add CorrelationId guid to activity that will increase its size + /// + public bool AddObsoleteCorrelationToActivity { get; set; } = false; + + /// /// Maximum number of events that activity can store for replay /// diff --git a/src/Extensions/Logging/ServiceCollectionExtensions.cs b/src/Extensions/Logging/ServiceCollectionExtensions.cs index 7ee349a2..4f11a3a6 100644 --- a/src/Extensions/Logging/ServiceCollectionExtensions.cs +++ b/src/Extensions/Logging/ServiceCollectionExtensions.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; -using Microsoft.Omex.Extensions.Abstractions.ReplayableLogs; +using Microsoft.Omex.Extensions.Abstractions.Activities.Processing; using Microsoft.Omex.Extensions.Logging.Replayable; namespace Microsoft.Omex.Extensions.Logging @@ -48,13 +48,14 @@ public static IServiceCollection AddOmexLogging(this IServiceCollection serviceC serviceCollection.TryAddTransient(); serviceCollection.TryAddTransient(); serviceCollection.TryAddTransient(); - serviceCollection.TryAddTransient(); serviceCollection.TryAddSingleton(p => OmexLogEventSource.Instance); serviceCollection.TryAddTransient(); serviceCollection.TryAddTransient(); + serviceCollection.TryAddEnumerable(ServiceDescriptor.Transient()); + serviceCollection.TryAddEnumerable(ServiceDescriptor.Transient()); serviceCollection.TryAddEnumerable(ServiceDescriptor.Singleton()); return serviceCollection; } diff --git a/src/Extensions/TimedScopes/ActivityExtensions.cs b/src/Extensions/TimedScopes/ActivityExtensions.cs deleted file mode 100644 index c58b2ca4..00000000 --- a/src/Extensions/TimedScopes/ActivityExtensions.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; -using System.Diagnostics; - -namespace Microsoft.Omex.Extensions.TimedScopes -{ - /// - /// Extensions for Activity - /// - public static class ActivityExtensions - { - /// - /// Get user hash from activity - /// - public static string GetUserHash(this Activity activity) => - activity.GetBaggageItem(UserHashKey) ?? string.Empty; - - - /// - /// Returns true if activity is transaction - /// - public static bool IsTransaction(this Activity activity) => - string.Equals( - activity.GetBaggageItem(TransactionMarkerKey), - TransactionMarkerValue, - StringComparison.OrdinalIgnoreCase); - - - /// - /// Set user hash for the activity - /// - public static void SetUserHash(this Activity activity, string userHash) => - activity.AddBaggage(UserHashKey, userHash); - - - /// - /// Mark activity as transaction - /// - public static void MarkAsTransaction(this Activity activity) => - activity.AddBaggage(TransactionMarkerKey, TransactionMarkerValue); - - - private const string UserHashKey = "UserHash"; - private const string TransactionMarkerKey = "TransactionMarkerKey"; - private const string TransactionMarkerValue = "true"; - } -} diff --git a/src/Extensions/TimedScopes/ITimedScopeEventSource.cs b/src/Extensions/TimedScopes/ITimedScopeEventSender.cs similarity index 67% rename from src/Extensions/TimedScopes/ITimedScopeEventSource.cs rename to src/Extensions/TimedScopes/ITimedScopeEventSender.cs index 86eee93b..cd912f35 100644 --- a/src/Extensions/TimedScopes/ITimedScopeEventSource.cs +++ b/src/Extensions/TimedScopes/ITimedScopeEventSender.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Diagnostics; + namespace Microsoft.Omex.Extensions.TimedScopes { /// @@ -9,9 +11,8 @@ namespace Microsoft.Omex.Extensions.TimedScopes public interface ITimedScopeEventSender { /// - /// Log timed scope end information + /// Log information about activity stop /// - /// Ended timed scope - void LogTimedScopeEndEvent(TimedScope scope); + void LogActivityStop(Activity activity); } } diff --git a/src/Extensions/TimedScopes/Internal/ActivityObserversIntializer.cs b/src/Extensions/TimedScopes/Internal/ActivityObserversIntializer.cs new file mode 100644 index 00000000..61910e2b --- /dev/null +++ b/src/Extensions/TimedScopes/Internal/ActivityObserversIntializer.cs @@ -0,0 +1,94 @@ +// 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 System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Omex.Extensions.Abstractions.Activities.Processing; + +namespace Microsoft.Omex.Extensions.TimedScopes +{ + internal sealed class ActivityObserversIntializer : IHostedService, IObserver, IObserver> + { + private readonly IActivityStartObserver[] m_activityStartObservers; + private readonly IActivityStopObserver[] m_activityStopObservers; + private readonly LinkedList m_disposables; + private IDisposable? m_observerLifetime; + + public ActivityObserversIntializer( + IEnumerable activityStartObservers, + IEnumerable activityStopObservers) + { + m_activityStartObservers = activityStartObservers.ToArray(); + m_activityStopObservers = activityStopObservers.ToArray(); + m_disposables = new LinkedList(); + } + + public Task StartAsync(CancellationToken cancellationToken) + { + m_observerLifetime = DiagnosticListener.AllListeners.Subscribe(this); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + foreach (IDisposable disposable in m_disposables) + { + disposable.Dispose(); + } + + m_observerLifetime?.Dispose(); + return Task.CompletedTask; + } + + private bool IsActivityStart(string eventName) => m_activityStartObservers.Length > 0 && eventName.EndsWith(".Start", StringComparison.Ordinal); + + private bool IsActivityStop(string eventName) => m_activityStopObservers.Length > 0 && eventName.EndsWith(".Stop", StringComparison.Ordinal); + + private bool IsEnabled(string eventName) => IsActivityStart(eventName) || IsActivityStop(eventName); + + void IObserver.OnCompleted() { } + + void IObserver.OnError(Exception error) { } + + void IObserver.OnNext(DiagnosticListener value) => m_disposables.AddLast(value.Subscribe(this, IsEnabled)); + + void IObserver>.OnCompleted() { } + + void IObserver>.OnError(Exception error) { } + + void IObserver>.OnNext(KeyValuePair value) + { + Activity activity = Activity.Current; + + if (IsActivityStart(value.Key)) + { + OnActivityStarted(activity, value.Value); + } + else if (IsActivityStop(value.Key)) + { + OnActivityStoped(activity, value.Value); + } + } + + private void OnActivityStarted(Activity activity, object payload) + { + foreach (IActivityStartObserver startHandler in m_activityStartObservers) + { + startHandler.OnStart(activity, payload); + } + } + + private void OnActivityStoped(Activity activity, object payload) + { + foreach (IActivityStopObserver stopHandler in m_activityStopObservers) + { + stopHandler.OnStop(activity, payload); + } + } + } +} diff --git a/src/Extensions/TimedScopes/Internal/ActivityStopObserver.cs b/src/Extensions/TimedScopes/Internal/ActivityStopObserver.cs new file mode 100644 index 00000000..40a019d0 --- /dev/null +++ b/src/Extensions/TimedScopes/Internal/ActivityStopObserver.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Diagnostics; +using Microsoft.Omex.Extensions.Abstractions.Activities.Processing; + +namespace Microsoft.Omex.Extensions.TimedScopes +{ + internal sealed class ActivityStopObserver : IActivityStopObserver + { + private readonly ITimedScopeEventSender m_eventSender; + + public ActivityStopObserver(ITimedScopeEventSender timedScopeEventSender) => + m_eventSender = timedScopeEventSender; + + public void OnStop(Activity activity, object? payload) => + m_eventSender.LogActivityStop(activity); + } +} diff --git a/src/Extensions/TimedScopes/Internal/SimpleActivityProvider.cs b/src/Extensions/TimedScopes/Internal/SimpleActivityProvider.cs index 199373b9..ca3dc4c5 100644 --- a/src/Extensions/TimedScopes/Internal/SimpleActivityProvider.cs +++ b/src/Extensions/TimedScopes/Internal/SimpleActivityProvider.cs @@ -2,8 +2,8 @@ // Licensed under the MIT license. using System.Diagnostics; -using Microsoft.Omex.Extensions.Abstractions; -using Microsoft.Omex.Extensions.Abstractions.ReplayableLogs; +using Microsoft.Omex.Extensions.Abstractions.Activities; +using Microsoft.Omex.Extensions.Abstractions.Activities.Processing; namespace Microsoft.Omex.Extensions.TimedScopes { diff --git a/src/Extensions/TimedScopes/Internal/TimedScopeEventSender.cs b/src/Extensions/TimedScopes/Internal/TimedScopeEventSender.cs index 415867eb..8f9d2489 100644 --- a/src/Extensions/TimedScopes/Internal/TimedScopeEventSender.cs +++ b/src/Extensions/TimedScopes/Internal/TimedScopeEventSender.cs @@ -2,11 +2,14 @@ // Licensed under the MIT license. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Omex.Extensions.Abstractions; +using Microsoft.Omex.Extensions.Abstractions.Activities; +using Microsoft.Omex.Extensions.Abstractions.Activities.Processing; namespace Microsoft.Omex.Extensions.TimedScopes { @@ -20,7 +23,7 @@ public TimedScopeEventSender(TimedScopeEventSource eventSource, IHostEnvironment } - public void LogTimedScopeEndEvent(TimedScope scope) + public void LogActivityStop(Activity activity) { if (!m_eventSource.IsEnabled()) { @@ -28,23 +31,37 @@ public void LogTimedScopeEndEvent(TimedScope scope) } string serviceName = m_serviceName; - string subtype = scope.SubType; - string metadata = scope.Metadata; - TimedScopeResult result = scope.Result; - Activity activity = scope.Activity; string name = activity.OperationName; string correlationId = activity.Id; double durationMs = activity.Duration.TotalMilliseconds; string userHash = activity.GetUserHash(); //TODO: We need add middleware that will set userhash in compliant way and IsTransaction GitHub Issue #166 bool isTransaction = activity.IsTransaction(); + string subtype = NullPlaceholder; + string metadata = NullPlaceholder; + string resultAsString = NullPlaceholder; + foreach (KeyValuePair pair in activity.Tags) + { + if (string.Equals(ActivityTagKeys.Result, pair.Key, StringComparison.Ordinal)) + { + resultAsString = pair.Value; + } + else if (string.Equals(ActivityTagKeys.SubType, pair.Key, StringComparison.Ordinal)) + { + subtype = pair.Value; + } + else if (string.Equals(ActivityTagKeys.Metadata, pair.Key, StringComparison.Ordinal)) + { + metadata = pair.Value; + } + } + string nameAsString = SanitizeString(name, nameof(name), name); string subTypeAsString = SanitizeString(subtype, nameof(subtype), name); string metaDataAsString = SanitizeString(metadata, nameof(metadata), name); string userHashAsString = SanitizeString(userHash, nameof(userHash), name); string serviceNameAsString = SanitizeString(serviceName, nameof(serviceName), name); string correlationIdAsString = SanitizeString(correlationId, nameof(correlationId), name); - string resultAsString = result.ToString(); long durationMsAsLong = Convert.ToInt64(durationMs, CultureInfo.InvariantCulture); if (isTransaction) @@ -93,5 +110,6 @@ private string SanitizeString(string value, string name, string activityName) private readonly TimedScopeEventSource m_eventSource; private readonly string m_serviceName; private readonly ILogger m_logger; + private const string NullPlaceholder = "null"; } } diff --git a/src/Extensions/TimedScopes/Internal/TimedScopeProvider.cs b/src/Extensions/TimedScopes/Internal/TimedScopeProvider.cs index 33845094..82fd7ae4 100644 --- a/src/Extensions/TimedScopes/Internal/TimedScopeProvider.cs +++ b/src/Extensions/TimedScopes/Internal/TimedScopeProvider.cs @@ -1,22 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using Microsoft.Omex.Extensions.Abstractions; -using Microsoft.Omex.Extensions.Abstractions.ReplayableLogs; +using Microsoft.Omex.Extensions.Abstractions.Activities; +using Microsoft.Omex.Extensions.Abstractions.Activities.Processing; namespace Microsoft.Omex.Extensions.TimedScopes { internal sealed class TimedScopeProvider : ITimedScopeProvider { - public TimedScopeProvider( - ITimedScopeEventSender eventSource, - IActivityProvider activityProvider, - ILogEventReplayer? logReplayer = null) - { + public TimedScopeProvider(IActivityProvider activityProvider) => m_activityProvider = activityProvider; - m_eventSender = eventSource; - m_logReplayer = logReplayer; - } public TimedScope CreateAndStart(TimedScopeDefinition name, TimedScopeResult result) => @@ -24,11 +17,9 @@ public TimedScope CreateAndStart(TimedScopeDefinition name, TimedScopeResult res public TimedScope Create(TimedScopeDefinition name, TimedScopeResult result) => - new TimedScope(m_eventSender, m_activityProvider.Create(name), result, m_logReplayer); + new TimedScope(m_activityProvider.Create(name), result); - private readonly ITimedScopeEventSender m_eventSender; private readonly IActivityProvider m_activityProvider; - private readonly ILogEventReplayer? m_logReplayer; } } diff --git a/src/Extensions/TimedScopes/ServiceCollectionExtensions.cs b/src/Extensions/TimedScopes/ServiceCollectionExtensions.cs index 724b62ef..83172f55 100644 --- a/src/Extensions/TimedScopes/ServiceCollectionExtensions.cs +++ b/src/Extensions/TimedScopes/ServiceCollectionExtensions.cs @@ -4,7 +4,8 @@ using System.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Omex.Extensions.Abstractions.ReplayableLogs; +using Microsoft.Omex.Extensions.Abstractions.Activities; +using Microsoft.Omex.Extensions.Abstractions.Activities.Processing; namespace Microsoft.Omex.Extensions.TimedScopes { @@ -19,6 +20,9 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddTimedScopes(this IServiceCollection serviceCollection) { Activity.DefaultIdFormat = ActivityIdFormat.W3C; + serviceCollection.AddHostedService(); + serviceCollection.TryAddEnumerable(ServiceDescriptor.Transient()); + serviceCollection.TryAddTransient(); serviceCollection.TryAddTransient(); serviceCollection.TryAddTransient(); diff --git a/src/Extensions/TimedScopes/TimedScope.cs b/src/Extensions/TimedScopes/TimedScope.cs deleted file mode 100644 index dfd10625..00000000 --- a/src/Extensions/TimedScopes/TimedScope.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; -using System.Diagnostics; -using Microsoft.Omex.Extensions.Abstractions.ReplayableLogs; - -namespace Microsoft.Omex.Extensions.TimedScopes -{ - /// - /// Logs duration of activity - /// - public class TimedScope : IDisposable - { - /// - /// TimedScope result - /// - public TimedScopeResult Result { get; set; } - - - /// - /// TimedScope sub type - /// - public string SubType { get; set; } - - - /// - /// TimedScope meta data - /// - public string Metadata { get; set; } - - - /// - /// Activity connected with this TimedScope - /// - public Activity Activity { get; } - - - /// - /// Indicates if activty was finished - /// - public bool IsFinished { get; private set; } - - - /// - /// Indicates if activty was started - /// - public bool IsStarted { get; private set; } - - - /// - /// Creates TimedScope instance - /// - /// event sender to write timedscope end information - /// activity connected to this timedscope - /// TimedScope initial result - /// Log replayer that might be used to replay logs in case of error - protected internal TimedScope(ITimedScopeEventSender eventSender, Activity activity, TimedScopeResult result, ILogEventReplayer? logReplayer = null) - { - m_eventSender = eventSender; - Activity = activity; - Result = result; - m_logReplayer = logReplayer; - SubType = NullPlaceholder; - Metadata = NullPlaceholder; - IsStarted = false; - IsFinished = false; - } - - - /// - /// Starts TimedScope activity - /// - public TimedScope Start() - { - if (IsStarted) - { - throw new InvalidOperationException("Activity already started"); - } - - Activity.Start(); - IsStarted = true; - return this; - } - - - /// - /// Stop TimedScope and log informations about it - /// - public void Stop() - { - if (IsFinished) - { - return; - } - - IsFinished = true; - - Activity.Stop(); - - m_eventSender.LogTimedScopeEndEvent(this); - - if (m_logReplayer != null && ShouldReplayEvents) - { - m_logReplayer.ReplayLogs(Activity); - } - } - - - void IDisposable.Dispose() => Stop(); - - - private bool ShouldReplayEvents => - Result switch - { - TimedScopeResult.SystemError => true, - _ => false, - }; - - - private readonly ITimedScopeEventSender m_eventSender; - private const string NullPlaceholder = "null"; - private readonly ILogEventReplayer? m_logReplayer; - } -} diff --git a/tests/Extensions/TimedScopes.UnitTests/ActivityExtensionsTests.cs b/tests/Extensions/Abstractions.UnitTests/Activities/ActivityExtensionsTests.cs similarity index 50% rename from tests/Extensions/TimedScopes.UnitTests/ActivityExtensionsTests.cs rename to tests/Extensions/Abstractions.UnitTests/Activities/ActivityExtensionsTests.cs index 109df82a..67baedd3 100644 --- a/tests/Extensions/TimedScopes.UnitTests/ActivityExtensionsTests.cs +++ b/tests/Extensions/Abstractions.UnitTests/Activities/ActivityExtensionsTests.cs @@ -1,12 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Diagnostics; +using Microsoft.Omex.Extensions.Abstractions.Activities; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.Omex.Extensions.TimedScopes.UnitTests { [TestClass] + [TestCategory(nameof(Activity))] public class ActivityExtensionsTests { [TestMethod] @@ -23,7 +26,6 @@ public void SetUserHash_SetsHash() Assert.AreNotEqual(userHash, activity2.GetUserHash()); } - [TestMethod] public void MarkAsTransaction_AddsMarker() { @@ -35,5 +37,33 @@ public void MarkAsTransaction_AddsMarker() Assert.IsTrue(activity1.IsTransaction()); Assert.IsFalse(activity2.IsTransaction()); } + + [TestMethod] + [Obsolete] + public void SetObsoleteCorrelationId_SetsValue() + { + Activity activity1 = new Activity("CorrelationTest1"); + Activity activity2 = new Activity("CorrelationTest2"); + Guid correlation = Guid.NewGuid(); + + activity1.SetObsoleteCorrelationId(correlation); + + Assert.AreEqual(correlation, activity1.GetObsoleteCorrelationId()); + Assert.AreNotEqual(correlation, activity2.GetObsoleteCorrelationId()); + } + + [TestMethod] + [Obsolete] + public void SetObsoleteTransactionId_SetsValue() + { + Activity activity1 = new Activity("TransactionIdTest1"); + Activity activity2 = new Activity("TransactionIdTest2"); + uint id = 117u; + + activity1.SetObsoleteTransactionId(id); + + Assert.AreEqual(id, activity1.GetObsolteteTransactionId()); + Assert.AreNotEqual(id, activity2.GetObsolteteTransactionId()); + } } } diff --git a/tests/Extensions/Abstractions.UnitTests/Activities/HelperExtensions.cs b/tests/Extensions/Abstractions.UnitTests/Activities/HelperExtensions.cs new file mode 100644 index 00000000..ba7e0b57 --- /dev/null +++ b/tests/Extensions/Abstractions.UnitTests/Activities/HelperExtensions.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Linq; +using Microsoft.Omex.Extensions.Abstractions.Activities; +using Microsoft.Omex.Extensions.Abstractions.Activities.Processing; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Omex.Extensions.TimedScopes.UnitTests +{ + internal static class HelperExtensions + { + public static void AssertResult(this TimedScope scope, TimedScopeResult expectedResult) => + Assert.AreEqual(ActivityResultStrings.ResultToString(expectedResult), scope.GetTag(ActivityTagKeys.Result)); + + + public static void AssertTag(this TimedScope scope, string tag, string expectedValue) => + Assert.AreEqual(expectedValue, scope.GetTag(tag)); + + + public static string GetTag(this TimedScope scope, string tag) => + scope.Activity.Tags.FirstOrDefault(p => string.Equals(p.Key, tag, StringComparison.Ordinal)).Value; + } +} diff --git a/tests/Extensions/TimedScopes.UnitTests/TaskExtensionsTests.cs b/tests/Extensions/Abstractions.UnitTests/Activities/TaskExtensionsTests.cs similarity index 90% rename from tests/Extensions/TimedScopes.UnitTests/TaskExtensionsTests.cs rename to tests/Extensions/Abstractions.UnitTests/Activities/TaskExtensionsTests.cs index 46094482..9aad0f2f 100644 --- a/tests/Extensions/TimedScopes.UnitTests/TaskExtensionsTests.cs +++ b/tests/Extensions/Abstractions.UnitTests/Activities/TaskExtensionsTests.cs @@ -5,13 +5,14 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading.Tasks; -using Microsoft.Omex.Extensions.Abstractions; +using Microsoft.Omex.Extensions.Abstractions.Activities; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; namespace Microsoft.Omex.Extensions.TimedScopes.UnitTests { [TestClass] + [TestCategory(nameof(Activity))] public class TaskExtensionsTests { [TestMethod] @@ -61,11 +62,10 @@ private void TestExecution( TimedScopeResult expectedResult) { Mock providerMock = new Mock(); - Mock mockSender = new Mock(); TimedScopeDefinition timedScopeDefinition = new TimedScopeDefinition(scopeName); Activity activity = new Activity(scopeName); - TimedScope scope = new TimedScope(mockSender.Object, activity, TimedScopeResult.SystemError, null); + TimedScope scope = new TimedScope(activity, TimedScopeResult.SystemError); providerMock.Setup(p => p.CreateAndStart(timedScopeDefinition, TimedScopeResult.SystemError)).Returns(scope); TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); @@ -73,12 +73,11 @@ private void TestExecution( createTask(taskCompletionSource.Task, providerMock.Object, timedScopeDefinition); providerMock.Verify(p => p.CreateAndStart(timedScopeDefinition, TimedScopeResult.SystemError), Times.Once); - Assert.AreEqual(scope.Result, TimedScopeResult.SystemError); + scope.AssertResult(TimedScopeResult.SystemError); finishTask(taskCompletionSource); - Assert.IsTrue(scope.IsFinished); - Assert.AreEqual(scope.Result, expectedResult); + scope.AssertResult(expectedResult); } diff --git a/tests/Extensions/TimedScopes.UnitTests/TimedScopeDefinitionsTests.cs b/tests/Extensions/Abstractions.UnitTests/Activities/TimedScopeDefinitionsTests.cs similarity index 96% rename from tests/Extensions/TimedScopes.UnitTests/TimedScopeDefinitionsTests.cs rename to tests/Extensions/Abstractions.UnitTests/Activities/TimedScopeDefinitionsTests.cs index c2af3f39..aa93b088 100644 --- a/tests/Extensions/TimedScopes.UnitTests/TimedScopeDefinitionsTests.cs +++ b/tests/Extensions/Abstractions.UnitTests/Activities/TimedScopeDefinitionsTests.cs @@ -2,12 +2,14 @@ // Licensed under the MIT license. using System; -using Microsoft.Omex.Extensions.Abstractions; +using System.Diagnostics; +using Microsoft.Omex.Extensions.Abstractions.Activities; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.Omex.Extensions.TimedScopes.UnitTests { [TestClass] + [TestCategory(nameof(Activity))] public class TimedScopeDefinitionsTests { [DataTestMethod] diff --git a/tests/Extensions/Abstractions.UnitTests/Activities/TimedScopeExtensionsTests.cs b/tests/Extensions/Abstractions.UnitTests/Activities/TimedScopeExtensionsTests.cs new file mode 100644 index 00000000..b16b21bb --- /dev/null +++ b/tests/Extensions/Abstractions.UnitTests/Activities/TimedScopeExtensionsTests.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Diagnostics; +using Microsoft.Omex.Extensions.Abstractions.Activities; +using Microsoft.Omex.Extensions.Abstractions.Activities.Processing; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Omex.Extensions.TimedScopes.UnitTests +{ + [TestClass] + [TestCategory(nameof(Activity))] + public class TimedScopeExtensionsTests + { + [DataTestMethod] + [DataRow(TimedScopeResult.SystemError)] + [DataRow(TimedScopeResult.ExpectedError)] + [DataRow(TimedScopeResult.Success)] + public void SetResult_SetsValue(TimedScopeResult result) + { + TimedScope scope1 = CreateScope("SetResultTest1"); + TimedScope scope2 = CreateScope("SetResultTest2"); + + scope1.SetResult(result); + + scope1.AssertResult(result); + scope2.AssertResult(TimedScopeResult.SystemError); + } + + + [TestMethod] + public void SetSubType_SetsValue() + { + TimedScope scope1 = CreateScope("SetSubTypeTest1"); + TimedScope scope2 = CreateScope("SetSubTypeTest2"); + string value = "Some sub type"; + + scope1.SetSubType(value); + + Assert.AreEqual(value, scope1.GetTag(ActivityTagKeys.SubType)); + Assert.IsNull(scope2.GetTag(ActivityTagKeys.SubType)); + } + + + [TestMethod] + public void SetMetadata_SetsValue() + { + TimedScope scope1 = CreateScope("SetMetadataTest1"); + TimedScope scope2 = CreateScope("SetMetadataTest2"); + string value = "Some metadata"; + + scope1.SetMetadata(value); + + Assert.AreEqual(value, scope1.GetTag(ActivityTagKeys.Metadata)); + Assert.IsNull(scope2.GetTag(ActivityTagKeys.Metadata)); + } + + + private TimedScope CreateScope(string name) => + new TimedScope(new Activity(name), TimedScopeResult.SystemError); + } +} diff --git a/tests/Extensions/Abstractions.UnitTests/Activities/TimedScopeTests.cs b/tests/Extensions/Abstractions.UnitTests/Activities/TimedScopeTests.cs new file mode 100644 index 00000000..11c87c0b --- /dev/null +++ b/tests/Extensions/Abstractions.UnitTests/Activities/TimedScopeTests.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Diagnostics; +using Microsoft.Omex.Extensions.Abstractions.Activities; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace Microsoft.Omex.Extensions.TimedScopes.UnitTests +{ + [TestClass] + [TestCategory(nameof(Activity))] + public class TimedScopeTests + { + [TestMethod] + public void Constructor_WorksProperly() + { + CreateTimedScope(); + } + + + [TestMethod] + public void Start_StartsActivity() + { + TimedScope scope = CreateTimedScope(); + + Assert.IsNull(scope.Activity.Id); + + scope.Start(); + + Assert.IsNotNull(scope.Activity.Id); + } + + + [TestMethod] + public void Stop_WhenCalledMultipleTimes_IgnoresSuperfluousCalls() + { + TimedScope scope = CreateTimedScope(); + scope.Start(); + scope.Stop(); + scope.Stop(); + } + + + [TestMethod] + public void Dispose_WhenCalledMultipleTimes_IgnoresSuperfluousCalls() + { + TimedScope scope = CreateTimedScope(); + scope.Start(); + + IDisposable disposable = scope; + disposable.Dispose(); + disposable.Dispose(); + } + + + private TimedScope CreateTimedScope() + { + Activity activity = new Activity("TestName"); + TimedScopeResult result = TimedScopeResult.Success; + + TimedScope scope = new TimedScope(activity, result); + + Assert.ReferenceEquals(activity, scope.Activity); + scope.AssertResult(result); + + return scope; + } + } +} diff --git a/tests/Extensions/Compatibility.UnitTests/ServiceCollectionExtensionsTests.cs b/tests/Extensions/Compatibility.UnitTests/ServiceCollectionExtensionsTests.cs index d893fadf..2950e02c 100644 --- a/tests/Extensions/Compatibility.UnitTests/ServiceCollectionExtensionsTests.cs +++ b/tests/Extensions/Compatibility.UnitTests/ServiceCollectionExtensionsTests.cs @@ -2,10 +2,13 @@ // Licensed under the MIT license. using System; +using System.Diagnostics; +using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Omex.Extensions.Abstractions; +using Microsoft.Omex.Extensions.Abstractions.Activities; +using Microsoft.Omex.Extensions.Abstractions.Activities.Processing; using Microsoft.Omex.Extensions.Compatibility.Logger; using Microsoft.Omex.Extensions.Compatibility.TimedScopes; using Microsoft.Omex.Extensions.Compatibility.Validation; @@ -54,13 +57,23 @@ public void AddOmexCompatibilityServices_RegisterTypes() Code.Validate(false, logMessage, eventId); Assert.AreEqual(1, mockLogger.Invocations.Count, "Code.Validate not calling ILogger"); - TimedScope startedTimedScope = new TimedScopeDefinition("TestStartedTimedScope").Create(TimedScopeResult.SystemError); - Assert.IsTrue(startedTimedScope.IsStarted); - Assert.AreEqual(TimedScopeResult.SystemError, startedTimedScope.Result); + using (TimedScope startedTimedScope = new TimedScopeDefinition("TestStartedTimedScope").Create(TimedScopeResult.SystemError)) + { + AssertResult(ActivityResultStrings.SystemError); + } - TimedScope notStartedTimedScope = new TimedScopeDefinition("TestNotStartedTimedScope").Create(TimedScopeResult.ExpectedError, false); - Assert.IsFalse(notStartedTimedScope.IsStarted); - Assert.AreEqual(TimedScopeResult.ExpectedError, notStartedTimedScope.Result); + using (TimedScope notStartedTimedScope = new TimedScopeDefinition("TestNotStartedTimedScope").Create(TimedScopeResult.ExpectedError, false)) + { + notStartedTimedScope.Start(); + AssertResult(ActivityResultStrings.ExpectedError); + } + } + + + private static void AssertResult(string expectedResult) + { + string value = Activity.Current.Tags.FirstOrDefault(p => string.Equals(p.Key, ActivityTagKeys.Result, StringComparison.Ordinal)).Value; + Assert.AreEqual(expectedResult, value); } } } diff --git a/tests/Extensions/Hosting.Services.UnitTests/HostBuilderExtensionsTests.cs b/tests/Extensions/Hosting.Services.UnitTests/HostBuilderExtensionsTests.cs index 10ddd897..24de04fd 100644 --- a/tests/Extensions/Hosting.Services.UnitTests/HostBuilderExtensionsTests.cs +++ b/tests/Extensions/Hosting.Services.UnitTests/HostBuilderExtensionsTests.cs @@ -11,10 +11,10 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Omex.Extensions.Abstractions.Activities; using Microsoft.Omex.Extensions.Abstractions.EventSources; using Microsoft.Omex.Extensions.Hosting.Services; using Microsoft.Omex.Extensions.Logging; -using Microsoft.Omex.Extensions.TimedScopes; using Microsoft.ServiceFabric.Services.Communication.Runtime; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; diff --git a/tests/Extensions/Hosting.Services.UnitTests/OmexHostedServiceTests.cs b/tests/Extensions/Hosting.Services.UnitTests/OmexHostedServiceTests.cs index 587c1185..d602d271 100644 --- a/tests/Extensions/Hosting.Services.UnitTests/OmexHostedServiceTests.cs +++ b/tests/Extensions/Hosting.Services.UnitTests/OmexHostedServiceTests.cs @@ -4,7 +4,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Omex.Extensions.Hosting.Services; @@ -19,23 +18,19 @@ public class OmexHostedServiceTests public async Task StartAsync_ProperlyCanceled() { MockRunner runnerMock = new MockRunner(t => Task.Delay(int.MaxValue, t)); - MockLifetime lifetimeMock = new MockLifetime(); ILogger loggerMock = new NullLogger(); - OmexHostedService hostedService = new OmexHostedService(runnerMock, lifetimeMock, loggerMock); + OmexHostedService hostedService = new OmexHostedService(runnerMock, loggerMock); Assert.IsFalse(runnerMock.IsStarted, "RunServiceAsync should not be called after constructor"); - await hostedService.StartAsync(CancellationToken.None); - Assert.IsFalse(runnerMock.IsStarted, "RunServiceAsync should not be called after StartAsync"); - - lifetimeMock.ApplicationStartedSource.Cancel(); - Assert.IsTrue(runnerMock.IsStarted, "RunServiceAsync should not be called after StartAsync"); + await hostedService.StartAsync(CancellationToken.None).ConfigureAwait(false); + Assert.IsTrue(runnerMock.IsStarted, "RunServiceAsync should be called after StartAsync"); Task task = runnerMock.Task!; Assert.IsFalse(task.IsCanceled, "Task should not be canceled"); Assert.IsFalse(runnerMock.Token.IsCancellationRequested, "CancelationToken should not be canceled"); - lifetimeMock.ApplicationStoppingSource.Cancel(); + await hostedService.StopAsync(CancellationToken.None).ConfigureAwait(false); Assert.IsTrue(runnerMock.Token.IsCancellationRequested, "Task should be canceled"); Assert.IsTrue(task.IsCanceled, "CancelationToken should be canceled"); } @@ -44,24 +39,22 @@ public async Task StartAsync_ProperlyCanceled() [TestMethod] public async Task StartAsync_HandlesExceptions() { - MockRunner runnerMock = new MockRunner(t => Task.Run(() => throw new Exception("Totaly valid exeption"))); - MockLifetime lifetimeMock = new MockLifetime(); + MockRunner runnerMock = new MockRunner(t => Task.Run(async () => + { + await Task.Delay(5).ConfigureAwait(false); + throw new ArithmeticException("Totaly valid exeption"); + })); ILogger loggerMock = new NullLogger(); - OmexHostedService hostedService = new OmexHostedService(runnerMock, lifetimeMock, loggerMock); + OmexHostedService hostedService = new OmexHostedService(runnerMock, loggerMock); Assert.IsFalse(runnerMock.IsStarted, "RunServiceAsync should not be called after constructor"); - await hostedService.StartAsync(CancellationToken.None); - Assert.IsFalse(runnerMock.IsStarted, "RunServiceAsync should not be called after StartAsync"); - - lifetimeMock.ApplicationStartedSource.Cancel(); + await hostedService.StartAsync(CancellationToken.None).ConfigureAwait(false); Assert.IsTrue(runnerMock.IsStarted, "RunServiceAsync should be called after StartAsync"); Assert.IsFalse(runnerMock.Token.IsCancellationRequested, "CancelationToken should not be canceled"); - await lifetimeMock.CompletionTask; + await hostedService.StopAsync(CancellationToken.None).ConfigureAwait(false); Assert.IsTrue(runnerMock.Task!.IsFaulted, "Task should be faulted"); - - lifetimeMock.ApplicationStoppingSource.Cancel(); Assert.IsTrue(runnerMock.Token.IsCancellationRequested, "Task should be canceled"); } @@ -90,44 +83,5 @@ public Task RunServiceAsync(CancellationToken cancellationToken) private readonly Func m_function; } - - - private class MockLifetime : IHostApplicationLifetime - { - public MockLifetime() - { - ApplicationStartedSource = new CancellationTokenSource(); - ApplicationStoppingSource = new CancellationTokenSource(); - ApplicationStoppedSource = new CancellationTokenSource(); - m_completionSource = new TaskCompletionSource(); - } - - - public CancellationTokenSource ApplicationStartedSource { get; } - - - public CancellationTokenSource ApplicationStoppingSource { get; } - - - public CancellationTokenSource ApplicationStoppedSource { get; } - - - public Task CompletionTask => m_completionSource.Task; - - - public CancellationToken ApplicationStarted => ApplicationStartedSource.Token; - - - public CancellationToken ApplicationStopping => ApplicationStoppingSource.Token; - - - public CancellationToken ApplicationStopped => ApplicationStoppedSource.Token; - - - public void StopApplication() => m_completionSource.SetResult(true); - - - private readonly TaskCompletionSource m_completionSource; - } } } diff --git a/tests/Extensions/Hosting.Services.Web.UnitTests/ListenerValidator.cs b/tests/Extensions/Hosting.Services.Web.UnitTests/ListenerValidator.cs index 04f421b0..fdd3f593 100644 --- a/tests/Extensions/Hosting.Services.Web.UnitTests/ListenerValidator.cs +++ b/tests/Extensions/Hosting.Services.Web.UnitTests/ListenerValidator.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Omex.Extensions.Abstractions.Activities; using Microsoft.Omex.Extensions.Hosting.Services; using Microsoft.Omex.Extensions.Hosting.Services.Web; using Microsoft.Omex.Extensions.Logging; diff --git a/tests/Extensions/Hosting.UnitTests/HostBuilderExtensionsTests.cs b/tests/Extensions/Hosting.UnitTests/HostBuilderExtensionsTests.cs index 231ecf94..20e0a3da 100644 --- a/tests/Extensions/Hosting.UnitTests/HostBuilderExtensionsTests.cs +++ b/tests/Extensions/Hosting.UnitTests/HostBuilderExtensionsTests.cs @@ -6,7 +6,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting.Internal; using Microsoft.Extensions.Logging; -using Microsoft.Omex.Extensions.TimedScopes; +using Microsoft.Omex.Extensions.Abstractions.Activities; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.Omex.Extensions.Hosting.UnitTests diff --git a/tests/Extensions/Logging.UnitTests/ActivityEnhancementObserverTests.cs b/tests/Extensions/Logging.UnitTests/ActivityEnhancementObserverTests.cs new file mode 100644 index 00000000..4cfbc393 --- /dev/null +++ b/tests/Extensions/Logging.UnitTests/ActivityEnhancementObserverTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Diagnostics; +using Microsoft.Extensions.Options; +using Microsoft.Omex.Extensions.Abstractions.Activities; +using Microsoft.Omex.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Hosting.Services.UnitTests +{ + [TestClass] + public class ActivityEnhancementObserverTests + { + [TestMethod] + [Obsolete] + public void OnStart_WhenSettingEnabled_ObsolterCorrelationIdSet() + { + Guid? correlation = StartAndGetCorrelation(true); + Assert.IsNotNull(correlation); + Assert.AreNotEqual(Guid.Empty, correlation); + } + + [TestMethod] + [Obsolete] + public void OnStart_WhenSettingDisabled_ObsolterCorrelationIdNotSet() + { + Guid? correlation = StartAndGetCorrelation(false); + Assert.IsNull(correlation); + } + + [Obsolete] + private Guid? StartAndGetCorrelation(bool addCorrelation) + { + IOptions options = Options.Create(new OmexLoggingOptions { AddObsoleteCorrelationToActivity = addCorrelation }); + ActivityEnhancementObserver observer = new ActivityEnhancementObserver(options); + string name = FormattableString.Invariant($"{nameof(StartAndGetCorrelation)}|{nameof(addCorrelation)}:{addCorrelation}"); + Activity activity = new Activity(name); + observer.OnStart(activity, null); + return activity.GetObsoleteCorrelationId(); + } + } +} diff --git a/tests/Extensions/Logging.UnitTests/OmexLogEventSenderTests.cs b/tests/Extensions/Logging.UnitTests/OmexLogEventSenderTests.cs index ffba04a5..13581bad 100644 --- a/tests/Extensions/Logging.UnitTests/OmexLogEventSenderTests.cs +++ b/tests/Extensions/Logging.UnitTests/OmexLogEventSenderTests.cs @@ -7,6 +7,7 @@ using System.Diagnostics.Tracing; using System.Linq; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Microsoft.Omex.Extensions.Abstractions.EventSources; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -36,7 +37,8 @@ public void LogMessage_CreatesProperEvents(EventLevel eventLevel, LogLevel logLe OmexLogEventSender logsSender = new OmexLogEventSender( OmexLogEventSource.Instance, new BasicMachineInformation(), - new EmptyServiceContext()); + new EmptyServiceContext(), + Options.Create(new OmexLoggingOptions())); logsSender.LogMessage(activity, category, logLevel, tagId, 0, message); @@ -44,7 +46,7 @@ public void LogMessage_CreatesProperEvents(EventLevel eventLevel, LogLevel logLe AssertPayload(eventInfo, "message", message); AssertPayload(eventInfo, "category", category); - AssertPayload(eventInfo, "correlationId", activity.Id); + AssertPayload(eventInfo, "activityId", activity.Id); AssertPayload(eventInfo, "tagId", "fff9"); } diff --git a/tests/Extensions/Logging.UnitTests/ReplayableActivityProviderTests.cs b/tests/Extensions/Logging.UnitTests/ReplayableActivityProviderTests.cs index af3e1261..411e19f3 100644 --- a/tests/Extensions/Logging.UnitTests/ReplayableActivityProviderTests.cs +++ b/tests/Extensions/Logging.UnitTests/ReplayableActivityProviderTests.cs @@ -3,7 +3,7 @@ using System.Diagnostics; using Microsoft.Extensions.Options; -using Microsoft.Omex.Extensions.Abstractions; +using Microsoft.Omex.Extensions.Abstractions.Activities; using Microsoft.Omex.Extensions.Logging.Replayable; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/tests/Extensions/Logging.UnitTests/ReplayableActivityStopObserverTests.cs b/tests/Extensions/Logging.UnitTests/ReplayableActivityStopObserverTests.cs new file mode 100644 index 00000000..3a1ca4a2 --- /dev/null +++ b/tests/Extensions/Logging.UnitTests/ReplayableActivityStopObserverTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Diagnostics; +using Microsoft.Extensions.Options; +using Microsoft.Omex.Extensions.Abstractions.Activities; +using Microsoft.Omex.Extensions.Logging; +using Microsoft.Omex.Extensions.Logging.Replayable; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Hosting.Services.UnitTests +{ + [TestClass] + public class ReplayableActivityStopObserverTests + { + [DataTestMethod] + [DataRow(TimedScopeResult.SystemError, true, true)] + [DataRow(TimedScopeResult.SystemError, false, false)] + [DataRow(TimedScopeResult.ExpectedError, true, false)] + public void OnStop_UsingReplaySettingsAndResult_CallesLogReplayIfNeeded(TimedScopeResult result, bool replayLog, bool shouldBeCalled) + { + Activity activity = new Activity(nameof(OnStop_UsingReplaySettingsAndResult_CallesLogReplayIfNeeded)); + TimedScope timedScope = new TimedScope(activity, result); + IOptions options = Options.Create(new OmexLoggingOptions { ReplayLogsInCaseOfError = replayLog }); + LogEventReplayerMock replayerMock = new LogEventReplayerMock(); + ReplayableActivityStopObserver observer = new ReplayableActivityStopObserver(replayerMock, options); + observer.OnStop(activity, null); + + if (shouldBeCalled) + { + Assert.AreEqual(activity, replayerMock.Activity); + } + else + { + Assert.IsNull(replayerMock.Activity); + } + } + + private class LogEventReplayerMock : ILogEventReplayer + { + public Activity? Activity { get; private set; } + + public void ReplayLogs(Activity activity) => Activity = activity; + } + } +} diff --git a/tests/Extensions/TimedScopes.UnitTests/ActivityObserversIntializerTests.cs b/tests/Extensions/TimedScopes.UnitTests/ActivityObserversIntializerTests.cs new file mode 100644 index 00000000..c2f7b7f1 --- /dev/null +++ b/tests/Extensions/TimedScopes.UnitTests/ActivityObserversIntializerTests.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Omex.Extensions.Abstractions.Activities.Processing; +using Microsoft.Omex.Extensions.TimedScopes; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace Hosting.Services.UnitTests +{ + [TestClass] + public class ActivityObserversIntializerTests + { + [TestMethod] + public async Task ActivityObserversInvokedProperly() + { + string name = nameof(ActivityObserversInvokedProperly); + Mock startObserver = new Mock(); + Mock stopObserver = new Mock(); + ActivityObserversIntializer initializer = new ActivityObserversIntializer( + new [] { startObserver.Object }, + new [] { stopObserver.Object }); + + try + { + await initializer.StartAsync(CancellationToken.None).ConfigureAwait(false); + + using DiagnosticListener listener = new DiagnosticListener(name); + + Assert.IsTrue(listener.IsEnabled(MakeStartName(name)), "Should be enabled for Activity.Start"); + Assert.IsFalse(listener.IsEnabled(name, "Should be disabled for other events")); + Assert.IsTrue(listener.IsEnabled(MakeStopName(name)), "Should be enabled for Activity.Stop"); + + Activity activity = new Activity(name); + object obj = new object(); + + listener.StartActivity(activity, obj); + startObserver.Verify(obs => obs.OnStart(activity, obj), Times.Once); + + listener.StopActivity(activity, obj); + stopObserver.Verify(obs => obs.OnStop(activity, obj), Times.Once); + } + catch + { + await initializer.StopAsync(CancellationToken.None).ConfigureAwait(false); + throw; + } + } + + private string MakeStartName(string name) => name + ".Start"; + + private string MakeStopName(string name) => name + ".Stop"; + } +} diff --git a/tests/Extensions/TimedScopes.UnitTests/ActivityStopObserverTests.cs b/tests/Extensions/TimedScopes.UnitTests/ActivityStopObserverTests.cs new file mode 100644 index 00000000..93706214 --- /dev/null +++ b/tests/Extensions/TimedScopes.UnitTests/ActivityStopObserverTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Diagnostics; +using Microsoft.Omex.Extensions.TimedScopes; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace Hosting.Services.UnitTests +{ + [TestClass] + public class ActivityStopObserverTests + { + [TestMethod] + public void OnStop_CallsLogActivityStop() + { + Activity activity = new Activity(nameof(OnStop_CallsLogActivityStop)); + Mock senderMock = new Mock(); + ActivityStopObserver observer = new ActivityStopObserver(senderMock.Object); + observer.OnStop(activity, null); + senderMock.Verify(s => s.LogActivityStop(activity), Times.Once); + } + } +} diff --git a/tests/Extensions/TimedScopes.UnitTests/ServiceCollectionTests.cs b/tests/Extensions/TimedScopes.UnitTests/ServiceCollectionTests.cs index 7c5e97ff..72e5e51e 100644 --- a/tests/Extensions/TimedScopes.UnitTests/ServiceCollectionTests.cs +++ b/tests/Extensions/TimedScopes.UnitTests/ServiceCollectionTests.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Omex.Extensions.Abstractions.Activities; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.Omex.Extensions.TimedScopes.UnitTests diff --git a/tests/Extensions/TimedScopes.UnitTests/SimpleActivityProviderTests.cs b/tests/Extensions/TimedScopes.UnitTests/SimpleActivityProviderTests.cs index d058f0e5..aead7f27 100644 --- a/tests/Extensions/TimedScopes.UnitTests/SimpleActivityProviderTests.cs +++ b/tests/Extensions/TimedScopes.UnitTests/SimpleActivityProviderTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. using System.Diagnostics; -using Microsoft.Omex.Extensions.Abstractions; +using Microsoft.Omex.Extensions.Abstractions.Activities; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.Omex.Extensions.TimedScopes.UnitTests diff --git a/tests/Extensions/TimedScopes.UnitTests/TimedScopeEventSourceTests.cs b/tests/Extensions/TimedScopes.UnitTests/TimedScopeEventSourceTests.cs index 439d96c2..aaba61c4 100644 --- a/tests/Extensions/TimedScopes.UnitTests/TimedScopeEventSourceTests.cs +++ b/tests/Extensions/TimedScopes.UnitTests/TimedScopeEventSourceTests.cs @@ -7,6 +7,7 @@ using System.Linq; using Microsoft.Extensions.Hosting.Internal; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Omex.Extensions.Abstractions.Activities; using Microsoft.Omex.Extensions.Abstractions.EventSources; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -27,11 +28,16 @@ public void LogTimedScopeEndEvent_CreatesEvent(EventSourcesEventIds eventId, boo string subType = "TestSubType"; string metaData = "TestmetaData"; + TimedScopeEventSender logEventSource = new TimedScopeEventSender( + TimedScopeEventSource.Instance, + new HostingEnvironment { ApplicationName = "TestApp" }, + new NullLogger()); + Activity activity = new Activity(name); - using (TimedScope scope = new TimedScope(s_logEventSource, activity, TimedScopeResult.Success, null).Start()) + using (TimedScope scope = new TimedScope(activity, TimedScopeResult.Success).Start()) { - scope.SubType = subType; - scope.Metadata = metaData; + scope.SetSubType(subType); + scope.SetMetadata(metaData); activity.SetUserHash("TestUserHash"); if (isTransaction) { @@ -39,6 +45,8 @@ public void LogTimedScopeEndEvent_CreatesEvent(EventSourcesEventIds eventId, boo } } + logEventSource.LogActivityStop(activity); + EventWrittenEventArgs eventInfo = listener.EventsInformation.Single(e => e.EventId == (int)eventId); AssertPayload(eventInfo, "name", name); @@ -47,13 +55,6 @@ public void LogTimedScopeEndEvent_CreatesEvent(EventSourcesEventIds eventId, boo } - private static readonly TimedScopeEventSender s_logEventSource = - new TimedScopeEventSender( - TimedScopeEventSource.Instance, - new HostingEnvironment { ApplicationName = "TestApp" }, - new NullLogger()); - - private class CustomEventListener : EventListener { public List EventsInformation { get; } = new List(); diff --git a/tests/Extensions/TimedScopes.UnitTests/TimedScopeProviderTests.cs b/tests/Extensions/TimedScopes.UnitTests/TimedScopeProviderTests.cs index 62e359a1..f2c909e6 100644 --- a/tests/Extensions/TimedScopes.UnitTests/TimedScopeProviderTests.cs +++ b/tests/Extensions/TimedScopes.UnitTests/TimedScopeProviderTests.cs @@ -3,8 +3,8 @@ using System; using System.Diagnostics; -using Microsoft.Omex.Extensions.Abstractions; -using Microsoft.Omex.Extensions.Abstractions.ReplayableLogs; +using Microsoft.Omex.Extensions.Abstractions.Activities; +using Microsoft.Omex.Extensions.Abstractions.Activities.Processing; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -14,39 +14,27 @@ namespace Microsoft.Omex.Extensions.TimedScopes.UnitTests public class TimedScopeProviderTests { [TestMethod] - public void CreateAndStart_ActivityCreatedWithReplay() + public void CreateAndStart_ActivityCreated() { - CreateAndValidateActivity("testNameWithReplay", new Mock().Object); + CreateAndValidateActivity("testName"); } - [TestMethod] - public void CreateAndStart_ActivityCreatedWithoutReplay() - { - CreateAndValidateActivity("testNameWithoutReplay", null); - } - - - private void CreateAndValidateActivity(string activityName, ILogEventReplayer? replayer) + private void CreateAndValidateActivity(string activityName) { TimedScopeResult result = TimedScopeResult.ExpectedError; - Mock eventSourceMock = new Mock(); Mock activityProviderMock = new Mock(); Mock activityMock = new Mock(activityName); TimedScopeDefinition definition = new TimedScopeDefinition(activityName); activityProviderMock.Setup(p => p.Create(definition)).Returns(activityMock.Object); - TimedScopeProvider provider = new TimedScopeProvider( - eventSourceMock.Object, - activityProviderMock.Object, - replayer); + TimedScopeProvider provider = new TimedScopeProvider(activityProviderMock.Object); TimedScope scope = provider.CreateAndStart(definition, result); - Assert.AreEqual(result, scope.Result); - Assert.ReferenceEquals(activityMock.Object, scope.Activity); + Assert.IsNotNull(scope); } } } diff --git a/tests/Extensions/TimedScopes.UnitTests/TimedScopeTests.cs b/tests/Extensions/TimedScopes.UnitTests/TimedScopeTests.cs deleted file mode 100644 index a32c77ac..00000000 --- a/tests/Extensions/TimedScopes.UnitTests/TimedScopeTests.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Microsoft.Omex.Extensions.Abstractions.ReplayableLogs; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; - -namespace Microsoft.Omex.Extensions.TimedScopes.UnitTests -{ - [TestClass] - public class TimedScopeTests - { - [TestMethod] - public void ConstructorWithoutReplayer_WorksProperly() - { - CreateTimedScope(null); - } - - - [TestMethod] - public void ConstructorWithReplayer_WorksProperly() - { - CreateTimedScope(new Mock().Object); - } - - - [TestMethod] - public void Start_StartsActivity() - { - (TimedScope scope, _) = CreateTimedScope(null); - - Assert.IsNull(scope.Activity.Id); - - scope.Start(); - - Assert.IsNotNull(scope.Activity.Id); - } - - - [TestMethod] - public void Stop_MultipleCallsIgnored() - { - (TimedScope scope, _) = CreateTimedScope(null); - scope.Start(); - scope.Stop(); - - Assert.IsTrue(scope.IsFinished); - - scope.Stop(); - } - - - [TestMethod] - public void Dispose_MultipleCallsIgnored() - { - (TimedScope scope, _) = CreateTimedScope(null); - scope.Start(); - - IDisposable disposable = scope; - disposable.Dispose(); - - Assert.IsTrue(scope.IsFinished); - - disposable.Dispose(); - } - - - [TestMethod] - public void Stop_CallsEventSource() - { - (TimedScope scope, Mock source) = CreateTimedScope(null); - - scope.Start(); - - scope.Stop(); - source.Verify(s => s.LogTimedScopeEndEvent(scope), Times.Once); - source.Invocations.Clear(); - - scope.Stop(); - source.Verify(s => s.LogTimedScopeEndEvent(It.IsAny()), Times.Never); - } - - - [DataTestMethod] - [DynamicData(nameof(AllResults), DynamicDataSourceType.Method)] - public void Stop_NotCallsLogReplayerIfItNotExist(TimedScopeResult result) - { - Mock replayer = new Mock(); - (TimedScope scope, Mock source) = CreateTimedScope(null); - scope.Result = result; - - scope.Start(); - - scope.Stop(); - } - - - public static IEnumerable AllResults() => - Enum.GetValues(typeof(TimedScopeResult)).Cast().Select(e => new object[] { e }); - - - [DataTestMethod] - [DataRow(TimedScopeResult.SystemError)] - public void Stop_CallsLogReplayerInCaseOfError(TimedScopeResult result) - { - Mock replayer = new Mock(); - (TimedScope scope, Mock source) = CreateTimedScope(replayer.Object); - scope.Result = result; - - scope.Start(); - - scope.Stop(); - replayer.Verify(r => r.ReplayLogs(scope.Activity), Times.Once); - replayer.Invocations.Clear(); - - scope.Stop(); - replayer.Verify(s => s.ReplayLogs(It.IsAny()), Times.Never); - } - - - [DataTestMethod] - [DataRow(TimedScopeResult.ExpectedError)] - [DataRow(TimedScopeResult.Success)] - public void Stop_NotCallsLogReplayerInCaseOfSucces(TimedScopeResult result) - { - Mock replayer = new Mock(); - (TimedScope scope, Mock source) = CreateTimedScope(replayer.Object); - scope.Result = result; - - scope.Start(); - - scope.Stop(); - replayer.Verify(s => s.ReplayLogs(It.IsAny()), Times.Never); - } - - - private (TimedScope, Mock) CreateTimedScope(ILogEventReplayer? replayer) - { - Activity activity = new Activity("TestName"); - TimedScopeResult result = TimedScopeResult.Success; - - Mock eventSource = new Mock(); - - TimedScope scope = new TimedScope(eventSource.Object, activity, result, replayer); - - Assert.ReferenceEquals(activity, scope.Activity); - Assert.AreEqual(result, scope.Result); - Assert.IsNotNull(scope.SubType); - Assert.IsNotNull(scope.Metadata); - Assert.IsFalse(scope.IsFinished); - - return (scope, eventSource); - } - } -}