From 28a8abd3094624d108e87bbb4c7be21797f8bb25 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 6 Jul 2020 15:07:08 -0700 Subject: [PATCH 01/45] Removed obsolete diagnostic interface IContainerAwareComponent. --- .../Diagnostics/IContainerAwareComponent.cs | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 src/Autofac/Core/Diagnostics/IContainerAwareComponent.cs diff --git a/src/Autofac/Core/Diagnostics/IContainerAwareComponent.cs b/src/Autofac/Core/Diagnostics/IContainerAwareComponent.cs deleted file mode 100644 index 81273400f..000000000 --- a/src/Autofac/Core/Diagnostics/IContainerAwareComponent.cs +++ /dev/null @@ -1,42 +0,0 @@ -// This software is part of the Autofac IoC container -// Copyright © 2011 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -using System; - -namespace Autofac.Core.Diagnostics -{ - /// - /// Marks a module as container-aware (for the purposes of attaching to diagnostic events.) - /// - [Obsolete("Use the more general Autofac.IStartable interface instead. The IContainer parameter can be emulated when implementing IStartable by taking a dependency on IComponentContext or ILifetimeScope.", true)] - public interface IContainerAwareComponent - { - /// - /// Initialise the module with the container into which it is being registered. - /// - /// The container. - void SetContainer(IContainer container); - } -} From aac41f303013ed4de9d0a5ea67a1e0dc3956e198 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 6 Jul 2020 16:20:56 -0700 Subject: [PATCH 02/45] Adapted .editorconfig rules from corefx to help with naming suggestions and conflicting styles. --- .editorconfig | 149 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 136 insertions(+), 13 deletions(-) diff --git a/.editorconfig b/.editorconfig index 3a242268e..1e63cf796 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,22 +10,145 @@ indent_style = space trim_trailing_whitespace = true insert_final_newline = true -; .NET Code - match defaults for VS -[*.{cs,csx,vb,vbx}] +; .NET Code - almost, but not exactly, the same suggestions as corefx +; https://github.com/dotnet/corefx/blob/master/.editorconfig +[*.cs] indent_size = 4 charset = utf-8-bom -; Force VS to recommend underscore at the start of created private fields. -[*.{cs,vb}] -dotnet_naming_rule.private_members_with_underscore.symbols = private_fields -dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore -dotnet_naming_rule.private_members_with_underscore.severity = suggestion - -dotnet_naming_symbols.private_fields.applicable_kinds = field -dotnet_naming_symbols.private_fields.applicable_accessibilities = private - -dotnet_naming_style.prefix_underscore.capitalization = camel_case -dotnet_naming_style.prefix_underscore.required_prefix = _ +; New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +; Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current + +; Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +; Using this. makes navigation outside VS a lot clearer. +dotnet_style_qualification_for_field = true:suggestion +dotnet_style_qualification_for_property = true:suggestion +dotnet_style_qualification_for_method = true:suggestion +dotnet_style_qualification_for_event = true:suggestion + +; Types: use keywords instead of BCL types, using var is fine. +;csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = false:none +;csharp_style_var_elsewhere = false:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +; Name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +; Static fields should be PascalCase +dotnet_naming_rule.static_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.static_fields_should_be_pascal_case.symbols = static_fields +dotnet_naming_rule.static_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected + +; Internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +; Code style defaults +csharp_using_directive_placement = outside_namespace:suggestion +dotnet_sort_system_directives_first = true +csharp_prefer_braces = true:refactoring +csharp_preserve_single_line_blocks = true:none +csharp_preserve_single_line_statements = false:none +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_simple_using_statement = false:none +csharp_style_prefer_switch_expression = true:suggestion + +; Code quality +dotnet_style_readonly_field = true:suggestion +dotnet_code_quality_unused_parameters = non_public:suggestion + +; Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:refactoring +dotnet_style_prefer_conditional_expression_over_return = true:refactoring +csharp_prefer_simple_default_expression = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = true:refactoring +csharp_style_expression_bodied_constructors = true:refactoring +csharp_style_expression_bodied_operators = true:refactoring +csharp_style_expression_bodied_properties = true:refactoring +csharp_style_expression_bodied_indexers = true:refactoring +csharp_style_expression_bodied_accessors = true:refactoring +csharp_style_expression_bodied_lambdas = true:refactoring +csharp_style_expression_bodied_local_functions = true:refactoring + +# Pattern matching +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +# Null checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Other features +csharp_style_prefer_index_operator = false:none +csharp_style_prefer_range_operator = false:none +csharp_style_pattern_local_over_anonymous_function = false:none + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false ; .NET project files and MSBuild - match defaults for VS [*.{csproj,nuspec,proj,projitems,props,shproj,targets,vbproj,vcxproj,vcxproj.filters,vsixmanifest,vsct}] From c09f4ceefd0131081a7b1166b87041c3748027ba Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 6 Jul 2020 16:24:28 -0700 Subject: [PATCH 03/45] Add reference to DiagnosticSource package. --- src/Autofac/Autofac.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Autofac/Autofac.csproj b/src/Autofac/Autofac.csproj index ec63ae13b..805f27087 100644 --- a/src/Autofac/Autofac.csproj +++ b/src/Autofac/Autofac.csproj @@ -57,6 +57,7 @@ All + From 87f2148a3f108336bb641e5723e2d2fe63adc3c3 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 6 Jul 2020 16:24:45 -0700 Subject: [PATCH 04/45] Extensions for writing the diagnostic messages. --- .../Diagnostics/DiagnosticSourceExtensions.cs | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs diff --git a/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs b/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs new file mode 100644 index 000000000..0f1a135e2 --- /dev/null +++ b/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs @@ -0,0 +1,183 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Autofac.Core.Resolving; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Core.Diagnostics +{ + // TODO: Create a common DiagnosticSource instance (a DiagnosticListener) that will be used for handling all events. This should be what gets passed from parent to child instead of a tracer object. + // https://github.com/dotnet/aspnetcore/blob/f3f9a1cdbcd06b298035b523732b9f45b1408461/src/Hosting/Hosting/src/WebHostBuilder.cs + // TODO: Each child lifetime scope should write to the DiagnosticSource using the same event IDs. Maybe we need this to be an extension method class on DiagnosticSource? ASP.NET has a wrapper class instead. + // https://github.com/dotnet/aspnetcore/blob/16be9a264e48560e10a3ee9683ecaed342d4ca11/src/Hosting/Hosting/src/Internal/HostingApplication.cs#L29 + // TODO: Write messages to the DiagnosticSource if it's enabled for different events. + // https://github.com/dotnet/aspnetcore/blob/c97a0020d8bab6d895bf821f6e47ee8722aa17d5/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs + // https://github.com/dotnet/aspnetcore/blob/28157e62597bf0e043bc7e937e44c5ec81946b83/src/Middleware/MiddlewareAnalysis/src/AnalysisMiddleware.cs + // TODO: Update the default diagnostic tracer so it uses a DiagnosticListener subscription. + // https://andrewlock.net/logging-using-diagnosticsource-in-asp-net-core/ + + /// + /// Extension methods for writing diagnostic messages. + /// + internal static class DiagnosticSourceExtensions + { + private const string MiddlewareEntryKey = "Autofac.Middleware.Entry"; + private const string MiddlewareFailureKey = "Autofac.Middleware.Failure"; + private const string MiddlewareSuccessKey = "Autofac.Middleware.Success"; + private const string OperationFailureKey = "Autofac.Operation.Failure"; + private const string OperationStartKey = "Autofac.Operation.Start"; + private const string OperationSuccessKey = "Autofac.Operation.Success"; + private const string RequestFailureKey = "Autofac.Request.Failure"; + private const string RequestStartKey = "Autofac.Request.Start"; + private const string RequestSuccessKey = "Autofac.Request.Success"; + + /// + /// Determines if diagnostics for middleware events is enabled. + /// + /// The diagnostic source to check for diagnostic settings. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool MiddlewareDiagnosticsEnabled(this DiagnosticSource diagnosticSource) + { + return diagnosticSource.IsEnabled(MiddlewareEntryKey) || diagnosticSource.IsEnabled(MiddlewareSuccessKey) || diagnosticSource.IsEnabled(MiddlewareFailureKey); + } + + /// + /// Invoked when an individual middleware item is about to execute (just before the method executes). + /// + /// The diagnostic source to which events will be written. + /// The pipeline resolve operation that this request is running within. + /// The context for the resolve request that is running. + /// The middleware that is about to run. + public static void MiddlewareEntry(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + { + if (diagnosticSource.IsEnabled(MiddlewareEntryKey)) + { + diagnosticSource.Write(MiddlewareEntryKey, new { operation, requestContext, middleware }); + } + } + + /// + /// Invoked when an individual middleware item has finished executing (when the method returns). + /// + /// The diagnostic source to which events will be written. + /// The pipeline resolve operation that this request is running within. + /// The context for the resolve request that is running. + /// The middleware that just ran. + /// + /// Indicates whether the given middleware succeeded. The exception that + /// caused the middleware to fail is not available here, but will be + /// available in the next request failure event. + /// + public static void MiddlewareExit(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware, bool succeeded) + { + if (succeeded && diagnosticSource.IsEnabled(MiddlewareSuccessKey)) + { + diagnosticSource.Write(MiddlewareSuccessKey, new { operation, requestContext, middleware }); + } + else if (!succeeded && diagnosticSource.IsEnabled(MiddlewareFailureKey)) + { + diagnosticSource.Write(MiddlewareFailureKey, new { operation, requestContext, middleware }); + } + } + + /// + /// Determines if diagnostics for operation events is enabled. + /// + /// The diagnostic source to check for diagnostic settings. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool OperationDiagnosticsEnabled(this DiagnosticSource diagnosticSource) + { + return diagnosticSource.IsEnabled(OperationStartKey) || diagnosticSource.IsEnabled(OperationSuccessKey) || diagnosticSource.IsEnabled(OperationFailureKey); + } + + /// + /// Invoked at operation start. + /// + /// The diagnostic source to which events will be written. + /// The pipeline resolve operation that is about to run. + /// The request that is responsible for starting this operation. + /// + /// A single operation can in turn invoke other full operations (as opposed to requests). Check + /// to know if you're looking at the entry operation. + /// + public static void OperationStart(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequest initiatingRequest) + { + if (diagnosticSource.IsEnabled(OperationStartKey)) + { + diagnosticSource.Write(OperationStartKey, new { operation, initiatingRequest }); + } + } + + /// + /// Invoked when a resolve operation fails. + /// + /// The diagnostic source to which events will be written. + /// The resolve operation that failed. + /// The exception that caused the operation failure. + public static void OperationFailure(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, Exception operationException) + { + if (diagnosticSource.IsEnabled(OperationStartKey)) + { + diagnosticSource.Write(OperationStartKey, new { operation, operationException }); + } + } + + /// + /// Invoked when a resolve operation succeeds. You can check whether this operation was the top-level entry operation using + /// . + /// + /// The diagnostic source to which events will be written. + /// The resolve operation that succeeded. + /// The resolved instance providing the requested service. + public static void OperationSuccess(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, object resolvedInstance) + { + if (diagnosticSource.IsEnabled(OperationStartKey)) + { + diagnosticSource.Write(OperationStartKey, new { operation, resolvedInstance }); + } + } + + /// + /// Invoked at the start of a single resolve request initiated from within an operation. + /// + /// The diagnostic source to which events will be written. + /// The pipeline resolve operation that this request is running within. + /// The context for the resolve request that is about to start. + public static void RequestStart(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext) + { + if (diagnosticSource.IsEnabled(RequestStartKey)) + { + diagnosticSource.Write(RequestStartKey, new { operation, requestContext }); + } + } + + /// + /// Invoked when a resolve request fails. + /// + /// The diagnostic source to which events will be written. + /// The pipeline resolve operation that this request is running within. + /// The context for the resolve request that failed. + /// The exception that caused the failure. + public static void RequestFailure(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException) + { + if (diagnosticSource.IsEnabled(RequestFailureKey)) + { + diagnosticSource.Write(RequestFailureKey, new { operation, requestContext, requestException }); + } + } + + /// + /// Invoked when a resolve request succeeds. + /// + /// The diagnostic source to which events will be written. + /// The pipeline resolve operation that this request is running within. + /// The context for the resolve request that failed. + public static void RequestSuccess(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext) + { + if (diagnosticSource.IsEnabled(RequestSuccessKey)) + { + diagnosticSource.Write(RequestSuccessKey, new { operation, requestContext }); + } + } + } +} From 8f01e366d6fbe449d0a08befaaafd399ff588a87 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Tue, 7 Jul 2020 07:33:49 -0700 Subject: [PATCH 05/45] DiagosticSource is not CLSCompliant. --- src/Autofac/Properties/AssemblyInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Autofac/Properties/AssemblyInfo.cs b/src/Autofac/Properties/AssemblyInfo.cs index a16421572..640bd074b 100644 --- a/src/Autofac/Properties/AssemblyInfo.cs +++ b/src/Autofac/Properties/AssemblyInfo.cs @@ -8,8 +8,8 @@ [assembly: InternalsVisibleTo("Autofac.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001008728425885ef385e049261b18878327dfaaf0d666dea3bd2b0e4f18b33929ad4e5fbc9087e7eda3c1291d2de579206d9b4292456abffbe8be6c7060b36da0c33b883e3878eaf7c89fddf29e6e27d24588e81e86f3a22dd7b1a296b5f06fbfb500bbd7410faa7213ef4e2ce7622aefc03169b0324bcd30ccfe9ac8204e4960be6")] [assembly: InternalsVisibleTo("Autofac.Test.Uwp.DeviceRunner, PublicKey=00240000048000009400000006020000002400005253413100040000010001008728425885ef385e049261b18878327dfaaf0d666dea3bd2b0e4f18b33929ad4e5fbc9087e7eda3c1291d2de579206d9b4292456abffbe8be6c7060b36da0c33b883e3878eaf7c89fddf29e6e27d24588e81e86f3a22dd7b1a296b5f06fbfb500bbd7410faa7213ef4e2ce7622aefc03169b0324bcd30ccfe9ac8204e4960be6")] [assembly: InternalsVisibleTo("Autofac.Net46.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001008728425885ef385e049261b18878327dfaaf0d666dea3bd2b0e4f18b33929ad4e5fbc9087e7eda3c1291d2de579206d9b4292456abffbe8be6c7060b36da0c33b883e3878eaf7c89fddf29e6e27d24588e81e86f3a22dd7b1a296b5f06fbfb500bbd7410faa7213ef4e2ce7622aefc03169b0324bcd30ccfe9ac8204e4960be6")] -[assembly: CLSCompliant(true)] +[assembly: CLSCompliant(false)] [assembly: ComVisible(false)] [assembly: NeutralResourcesLanguage("en-US")] [assembly: AssemblyCopyright("Copyright © 2015 Autofac Contributors")] -[assembly: AssemblyDescription("Autofac Inversion of Control container for .NET applications.")] \ No newline at end of file +[assembly: AssemblyDescription("Autofac Inversion of Control container for .NET applications.")] From 98dbdb03563e439f5a83f3e23c55463c07b362c9 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Tue, 7 Jul 2020 07:34:13 -0700 Subject: [PATCH 06/45] Remove IResolvePipelineTracer to start tracking down places to insert DiagnosticSource. --- .../Diagnostics/DefaultDiagnosticTracer.cs | 22 ++--- .../Diagnostics/IResolvePipelineTracer.cs | 86 ------------------- 2 files changed, 11 insertions(+), 97 deletions(-) delete mode 100644 src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs diff --git a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs index 0312db835..e4c7d0c7e 100644 --- a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs @@ -12,13 +12,13 @@ namespace Autofac.Core.Diagnostics /// Provides a default resolve pipeline tracer that builds a multi-line string describing the end-to-end operation flow. /// Attach to the event to receive notifications when new trace content is available. /// - public class DefaultDiagnosticTracer : IResolvePipelineTracer + public class DefaultDiagnosticTracer { private const string RequestExceptionTraced = "__RequestException"; private readonly ConcurrentDictionary _operationBuilders = new ConcurrentDictionary(); - private static readonly string[] _newLineSplit = new[] { Environment.NewLine }; + private static readonly string[] NewLineSplit = new[] { Environment.NewLine }; /// /// Event raised when a resolve operation completes, and trace data is available. @@ -35,7 +35,7 @@ public class DefaultDiagnosticTracer : IResolvePipelineTracer public int OperationsInProgress => this._operationBuilders.Count; /// - void IResolvePipelineTracer.OperationStart(ResolveOperationBase operation, ResolveRequest initiatingRequest) + public void OperationStart(ResolveOperationBase operation, ResolveRequest initiatingRequest) { var builder = _operationBuilders.GetOrAdd(operation.TracingId, k => new IndentingStringBuilder()); @@ -45,7 +45,7 @@ void IResolvePipelineTracer.OperationStart(ResolveOperationBase operation, Resol } /// - void IResolvePipelineTracer.RequestStart(ResolveOperationBase operation, ResolveRequestContextBase requestContext) + public void RequestStart(ResolveOperationBase operation, ResolveRequestContextBase requestContext) { if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) { @@ -66,7 +66,7 @@ void IResolvePipelineTracer.RequestStart(ResolveOperationBase operation, Resolve } /// - void IResolvePipelineTracer.MiddlewareEntry(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + public void MiddlewareEntry(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) { if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) { @@ -76,7 +76,7 @@ void IResolvePipelineTracer.MiddlewareEntry(ResolveOperationBase operation, Reso } /// - void IResolvePipelineTracer.MiddlewareExit(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware, bool succeeded) + public void MiddlewareExit(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware, bool succeeded) { if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) { @@ -94,7 +94,7 @@ void IResolvePipelineTracer.MiddlewareExit(ResolveOperationBase operation, Resol } /// - void IResolvePipelineTracer.RequestFailure(ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException) + public void RequestFailure(ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException) { if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) { @@ -120,7 +120,7 @@ void IResolvePipelineTracer.RequestFailure(ResolveOperationBase operation, Resol } /// - void IResolvePipelineTracer.RequestSuccess(ResolveOperationBase operation, ResolveRequestContextBase requestContext) + public void RequestSuccess(ResolveOperationBase operation, ResolveRequestContextBase requestContext) { if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) { @@ -131,7 +131,7 @@ void IResolvePipelineTracer.RequestSuccess(ResolveOperationBase operation, Resol } /// - void IResolvePipelineTracer.OperationFailure(ResolveOperationBase operation, Exception operationException) + public void OperationFailure(ResolveOperationBase operation, Exception operationException) { if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) { @@ -155,7 +155,7 @@ void IResolvePipelineTracer.OperationFailure(ResolveOperationBase operation, Exc } /// - void IResolvePipelineTracer.OperationSuccess(ResolveOperationBase operation, object resolvedInstance) + public void OperationSuccess(ResolveOperationBase operation, object resolvedInstance) { if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) { @@ -220,7 +220,7 @@ public void AppendException(string message, Exception ex) AppendIndent(); _builder.AppendLine(message); - var exceptionBody = ex.ToString().Split(_newLineSplit, StringSplitOptions.None); + var exceptionBody = ex.ToString().Split(NewLineSplit, StringSplitOptions.None); Indent(); diff --git a/src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs b/src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs deleted file mode 100644 index eb1ec5b67..000000000 --- a/src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using Autofac.Core.Resolving; -using Autofac.Core.Resolving.Pipeline; - -namespace Autofac.Core.Diagnostics -{ - /// - /// Defines the interface for a tracer that is invoked by a resolve pipeline during execution. Implement this class if you want - /// to provide custom trace output or other diagnostic functionality. - /// - /// - /// You can get a 'tracing ID' object from that can be used as a dictionary tracking key - /// to associate related operations. - /// - /// - public interface IResolvePipelineTracer - { - /// - /// Invoked at operation start. - /// - /// The pipeline resolve operation that is about to run. - /// The request that is responsible for starting this operation. - /// - /// A single operation can in turn invoke other full operations (as opposed to requests). Check - /// to know if you're looking at the entry operation. - /// - void OperationStart(ResolveOperationBase operation, ResolveRequest initiatingRequest); - - /// - /// Invoked at the start of a single resolve request initiated from within an operation. - /// - /// The pipeline resolve operation that this request is running within. - /// The context for the resolve request that is about to start. - void RequestStart(ResolveOperationBase operation, ResolveRequestContextBase requestContext); - - /// - /// Invoked when an individual middleware item is about to execute (just before the method executes). - /// - /// The pipeline resolve operation that this request is running within. - /// The context for the resolve request that is running. - /// The middleware that is about to run. - void MiddlewareEntry(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware); - - /// - /// Invoked when an individual middleware item has finished executing (when the method returns). - /// - /// The pipeline resolve operation that this request is running within. - /// The context for the resolve request that is running. - /// The middleware that just ran. - /// - /// Indicates whether the given middleware succeeded. - /// The exception that caused the middleware to fail is not available here, but will be available in the next call. - /// - void MiddlewareExit(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware, bool succeeded); - - /// - /// Invoked when a resolve request fails. - /// - /// The pipeline resolve operation that this request is running within. - /// The context for the resolve request that failed. - /// The exception that caused the failure. - void RequestFailure(ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException); - - /// - /// Invoked when a resolve request succeeds. - /// - /// The pipeline resolve operation that this request is running within. - /// The context for the resolve request that failed. - void RequestSuccess(ResolveOperationBase operation, ResolveRequestContextBase requestContext); - - /// - /// Invoked when a resolve operation fails. - /// - /// The resolve operation that failed. - /// The exception that caused the operation failure. - void OperationFailure(ResolveOperationBase operation, Exception operationException); - - /// - /// Invoked when a resolve operation succeeds. You can check whether this operation was the top-level entry operation using - /// . - /// - /// The resolve operation that succeeded. - /// The resolved instance providing the requested service. - void OperationSuccess(ResolveOperationBase operation, object resolvedInstance); - } -} From 8ea2d94c2fe771afc0e325bfd3d184a6fd488a4a Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Tue, 7 Jul 2020 08:42:51 -0700 Subject: [PATCH 07/45] TODO cleanup so task-level TODO items are easier to track. --- src/Autofac/Builder/MetadataKeys.cs | 13 ++++++------- .../OpenGenericDecoratorRegistrationSource.cs | 2 +- .../Features/Scanning/ScanningActivatorData.cs | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Autofac/Builder/MetadataKeys.cs b/src/Autofac/Builder/MetadataKeys.cs index d256f7e51..529f11739 100644 --- a/src/Autofac/Builder/MetadataKeys.cs +++ b/src/Autofac/Builder/MetadataKeys.cs @@ -50,15 +50,14 @@ internal static class MetadataKeys /// internal const string ContainerBuildOptions = "__ContainerBuildOptions"; - // TODO: Change the registration builder event handler to not use these. - #pragma warning disable SA1600 // Elements should be documented + /// + /// Event handler for . + /// internal const string RegisteredPropertyKey = "__RegisteredKey"; + /// + /// Event handler for . + /// internal const string RegistrationSourceAddedPropertyKey = "__RegistrationSourceAddedKey"; - - internal const string InternalRegisteredPropertyKey = "__InternalRegisteredKey"; - - internal const string InternalRegistrationSourceAddedPropertyKey = "__InternalRegistrationSourceAddedKey"; - #pragma warning restore SA1600 // Elements should be documented } } diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorRegistrationSource.cs b/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorRegistrationSource.cs index 443df5658..7b4dd3d91 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorRegistrationSource.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorRegistrationSource.cs @@ -36,7 +36,7 @@ namespace Autofac.Features.OpenGenerics { /// - /// TODO: Replace this with a service pipeline source. + /// Registration source for handling open generic decorators. /// internal class OpenGenericDecoratorRegistrationSource : IRegistrationSource { diff --git a/src/Autofac/Features/Scanning/ScanningActivatorData.cs b/src/Autofac/Features/Scanning/ScanningActivatorData.cs index 4c84e50c0..0ccf1b1fa 100644 --- a/src/Autofac/Features/Scanning/ScanningActivatorData.cs +++ b/src/Autofac/Features/Scanning/ScanningActivatorData.cs @@ -39,7 +39,7 @@ public class ScanningActivatorData : ReflectionActivatorData /// Initializes a new instance of the class. /// public ScanningActivatorData() - : base(typeof(object)) // TODO - refactor common base class out of RAD + : base(typeof(object)) { } From aa6764fc7f0886eed17a464c365d82a6241edd18 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Tue, 7 Jul 2020 10:52:11 -0700 Subject: [PATCH 08/45] Replaced pipeline tracer with DiagnosticListener and subscriptions. --- src/Autofac/Core/Container.cs | 5 +- .../Diagnostics/DefaultDiagnosticTracer.cs | 83 ++++----- .../Core/Diagnostics/DiagnosticEventKeys.cs | 49 ++++++ .../Diagnostics/DiagnosticSourceExtensions.cs | 51 +++++- .../Core/Diagnostics/DiagnosticTracerBase.cs | 163 ++++++++++++++++++ .../Diagnostics/MiddlewareDiagnosticData.cs | 46 +++++ .../OperationFailureDiagnosticData.cs | 43 +++++ .../OperationStartDiagnosticData.cs | 42 +++++ .../OperationSuccessDiagnosticData.cs | 42 +++++ .../OperationTraceCompletedArgs.cs | 27 ++- .../Core/Diagnostics/RequestDiagnosticData.cs | 43 +++++ .../RequestFailureDiagnosticData.cs | 47 +++++ src/Autofac/Core/Lifetime/LifetimeScope.cs | 37 ++-- .../Pipeline/ResolvePipelineBuilder.cs | 9 +- .../Pipeline/ResolveRequestContext.cs | 8 +- .../Pipeline/ResolveRequestContextBase.cs | 17 +- .../Core/Resolving/ResolveOperation.cs | 16 +- .../Core/Resolving/ResolveOperationBase.cs | 71 +++++--- src/Autofac/ILifetimeScope.cs | 13 +- src/Autofac/LifetimeScopeExtensions.cs | 33 ++-- .../DefaultDiagnosticTracerTests.cs | 8 +- 21 files changed, 690 insertions(+), 163 deletions(-) create mode 100644 src/Autofac/Core/Diagnostics/DiagnosticEventKeys.cs create mode 100644 src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs create mode 100644 src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs create mode 100644 src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs create mode 100644 src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs create mode 100644 src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs create mode 100644 src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs create mode 100644 src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs diff --git a/src/Autofac/Core/Container.cs b/src/Autofac/Core/Container.cs index 9ceb1b9b4..df7fc0f08 100644 --- a/src/Autofac/Core/Container.cs +++ b/src/Autofac/Core/Container.cs @@ -100,10 +100,7 @@ public ILifetimeScope BeginLifetimeScope(object tag, Action co } /// - public void AttachTrace(IResolvePipelineTracer tracer) - { - _rootLifetimeScope.AttachTrace(tracer); - } + public DiagnosticListener DiagnosticSource => this._rootLifetimeScope.DiagnosticSource; /// /// Gets the disposer associated with this container. Instances can be associated diff --git a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs index e4c7d0c7e..4cfe2bfe3 100644 --- a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Globalization; using System.Text; using Autofac.Core.Resolving; -using Autofac.Core.Resolving.Pipeline; namespace Autofac.Core.Diagnostics { @@ -12,7 +10,7 @@ namespace Autofac.Core.Diagnostics /// Provides a default resolve pipeline tracer that builds a multi-line string describing the end-to-end operation flow. /// Attach to the event to receive notifications when new trace content is available. /// - public class DefaultDiagnosticTracer + public class DefaultDiagnosticTracer : DiagnosticTracerBase { private const string RequestExceptionTraced = "__RequestException"; @@ -35,9 +33,9 @@ public class DefaultDiagnosticTracer public int OperationsInProgress => this._operationBuilders.Count; /// - public void OperationStart(ResolveOperationBase operation, ResolveRequest initiatingRequest) + public override void OnOperationStart(OperationStartDiagnosticData data) { - var builder = _operationBuilders.GetOrAdd(operation.TracingId, k => new IndentingStringBuilder()); + var builder = _operationBuilders.GetOrAdd(data.Operation.TracingId, k => new IndentingStringBuilder()); builder.AppendFormattedLine(TracerMessages.ResolveOperationStarting); builder.AppendLine(TracerMessages.EntryBrace); @@ -45,19 +43,19 @@ public void OperationStart(ResolveOperationBase operation, ResolveRequest initia } /// - public void RequestStart(ResolveOperationBase operation, ResolveRequestContextBase requestContext) + public override void OnRequestStart(RequestDiagnosticData data) { - if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) { builder.AppendFormattedLine(TracerMessages.ResolveRequestStarting); builder.AppendLine(TracerMessages.EntryBrace); builder.Indent(); - builder.AppendFormattedLine(TracerMessages.ServiceDisplay, requestContext.Service); - builder.AppendFormattedLine(TracerMessages.ComponentDisplay, requestContext.Registration.Activator.DisplayName()); + builder.AppendFormattedLine(TracerMessages.ServiceDisplay, data.RequestContext.Service); + builder.AppendFormattedLine(TracerMessages.ComponentDisplay, data.RequestContext.Registration.Activator.DisplayName()); - if (requestContext.DecoratorTarget is object) + if (data.RequestContext.DecoratorTarget is object) { - builder.AppendFormattedLine(TracerMessages.TargetDisplay, requestContext.DecoratorTarget.Activator.DisplayName()); + builder.AppendFormattedLine(TracerMessages.TargetDisplay, data.RequestContext.DecoratorTarget.Activator.DisplayName()); } builder.AppendLine(); @@ -66,40 +64,43 @@ public void RequestStart(ResolveOperationBase operation, ResolveRequestContextBa } /// - public void MiddlewareEntry(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + public override void OnMiddlewareEntry(MiddlewareDiagnosticData data) { - if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) { - builder.AppendFormattedLine(TracerMessages.EnterMiddleware, middleware.ToString()); + builder.AppendFormattedLine(TracerMessages.EnterMiddleware, data.Middleware.ToString()); builder.Indent(); } } /// - public void MiddlewareExit(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware, bool succeeded) + public override void OnMiddlewareFailure(MiddlewareDiagnosticData data) { - if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) { builder.Outdent(); + builder.AppendFormattedLine(TracerMessages.ExitMiddlewareFailure, data.Middleware.ToString()); + } + } - if (succeeded) - { - builder.AppendFormattedLine(TracerMessages.ExitMiddlewareSuccess, middleware.ToString()); - } - else - { - builder.AppendFormattedLine(TracerMessages.ExitMiddlewareFailure, middleware.ToString()); - } + /// + public override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) + { + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + { + builder.Outdent(); + builder.AppendFormattedLine(TracerMessages.ExitMiddlewareSuccess, data.Middleware.ToString()); } } /// - public void RequestFailure(ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException) + public override void OnRequestFailure(RequestFailureDiagnosticData data) { - if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) { builder.Outdent(); builder.AppendLine(TracerMessages.ExitBrace); + var requestException = data.RequestException; if (requestException is DependencyResolutionException && requestException.InnerException is object) { @@ -120,60 +121,60 @@ public void RequestFailure(ResolveOperationBase operation, ResolveRequestContext } /// - public void RequestSuccess(ResolveOperationBase operation, ResolveRequestContextBase requestContext) + public override void OnRequestSuccess(RequestDiagnosticData data) { - if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) { builder.Outdent(); builder.AppendLine(TracerMessages.ExitBrace); - builder.AppendFormattedLine(TracerMessages.ResolveRequestSucceeded, requestContext.Instance); + builder.AppendFormattedLine(TracerMessages.ResolveRequestSucceeded, data.RequestContext.Instance); } } /// - public void OperationFailure(ResolveOperationBase operation, Exception operationException) + public override void OnOperationFailure(OperationFailureDiagnosticData data) { - if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) { try { builder.Outdent(); builder.AppendLine(TracerMessages.ExitBrace); - builder.AppendException(TracerMessages.OperationFailed, operationException); + builder.AppendException(TracerMessages.OperationFailed, data.OperationException); // If we're completing the root operation, raise the event. - if (operation.IsTopLevelOperation) + if (data.Operation.IsTopLevelOperation) { - OperationCompleted?.Invoke(this, new OperationTraceCompletedArgs(operation, builder.ToString())); + OperationCompleted?.Invoke(this, new OperationTraceCompletedArgs(data.Operation, builder.ToString())); } } finally { - _operationBuilders.TryRemove(operation.TracingId, out var _); + _operationBuilders.TryRemove(data.Operation.TracingId, out var _); } } } /// - public void OperationSuccess(ResolveOperationBase operation, object resolvedInstance) + public override void OnOperationSuccess(OperationSuccessDiagnosticData data) { - if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) { try { builder.Outdent(); builder.AppendLine(TracerMessages.ExitBrace); - builder.AppendFormattedLine(TracerMessages.OperationSucceeded, resolvedInstance); + builder.AppendFormattedLine(TracerMessages.OperationSucceeded, data.ResolvedInstance); // If we're completing the root operation, raise the event. - if (operation.IsTopLevelOperation) + if (data.Operation.IsTopLevelOperation) { - OperationCompleted?.Invoke(this, new OperationTraceCompletedArgs(operation, builder.ToString())); + OperationCompleted?.Invoke(this, new OperationTraceCompletedArgs(data.Operation, builder.ToString())); } } finally { - _operationBuilders.TryRemove(operation.TracingId, out var _); + _operationBuilders.TryRemove(data.Operation.TracingId, out var _); } } } diff --git a/src/Autofac/Core/Diagnostics/DiagnosticEventKeys.cs b/src/Autofac/Core/Diagnostics/DiagnosticEventKeys.cs new file mode 100644 index 000000000..d0fb09780 --- /dev/null +++ b/src/Autofac/Core/Diagnostics/DiagnosticEventKeys.cs @@ -0,0 +1,49 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Autofac.Core.Resolving; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Core.Diagnostics +{ + /// + /// Names of the events raised in diagnostics. + /// + internal static class DiagnosticEventKeys + { + public const string MiddlewareEntry = "Autofac.Middleware.Entry"; + public const string MiddlewareFailure = "Autofac.Middleware.Failure"; + public const string MiddlewareSuccess = "Autofac.Middleware.Success"; + public const string OperationFailure = "Autofac.Operation.Failure"; + public const string OperationStart = "Autofac.Operation.Start"; + public const string OperationSuccess = "Autofac.Operation.Success"; + public const string RequestFailure = "Autofac.Request.Failure"; + public const string RequestStart = "Autofac.Request.Start"; + public const string RequestSuccess = "Autofac.Request.Success"; + } +} diff --git a/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs b/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs index 0f1a135e2..6be7c5e44 100644 --- a/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs +++ b/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs @@ -1,3 +1,28 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + using System; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -72,11 +97,11 @@ public static void MiddlewareExit(this DiagnosticSource diagnosticSource, Resolv { if (succeeded && diagnosticSource.IsEnabled(MiddlewareSuccessKey)) { - diagnosticSource.Write(MiddlewareSuccessKey, new { operation, requestContext, middleware }); + diagnosticSource.Write(MiddlewareSuccessKey, new MiddlewareDiagnosticData(operation, requestContext, middleware)); } else if (!succeeded && diagnosticSource.IsEnabled(MiddlewareFailureKey)) { - diagnosticSource.Write(MiddlewareFailureKey, new { operation, requestContext, middleware }); + diagnosticSource.Write(MiddlewareFailureKey, new MiddlewareDiagnosticData(operation, requestContext, middleware)); } } @@ -104,7 +129,7 @@ public static void OperationStart(this DiagnosticSource diagnosticSource, Resolv { if (diagnosticSource.IsEnabled(OperationStartKey)) { - diagnosticSource.Write(OperationStartKey, new { operation, initiatingRequest }); + diagnosticSource.Write(OperationStartKey, new OperationStartDiagnosticData(operation, initiatingRequest)); } } @@ -118,7 +143,7 @@ public static void OperationFailure(this DiagnosticSource diagnosticSource, Reso { if (diagnosticSource.IsEnabled(OperationStartKey)) { - diagnosticSource.Write(OperationStartKey, new { operation, operationException }); + diagnosticSource.Write(OperationStartKey, new OperationFailureDiagnosticData(operation, operationException)); } } @@ -133,10 +158,20 @@ public static void OperationSuccess(this DiagnosticSource diagnosticSource, Reso { if (diagnosticSource.IsEnabled(OperationStartKey)) { - diagnosticSource.Write(OperationStartKey, new { operation, resolvedInstance }); + diagnosticSource.Write(OperationStartKey, new OperationSuccessDiagnosticData(operation, resolvedInstance)); } } + /// + /// Determines if diagnostics for resolve requests is enabled. + /// + /// The diagnostic source to check for diagnostic settings. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool RequestDiagnosticsEnabled(this DiagnosticSource diagnosticSource) + { + return diagnosticSource.IsEnabled(RequestStartKey) || diagnosticSource.IsEnabled(RequestSuccessKey) || diagnosticSource.IsEnabled(RequestFailureKey); + } + /// /// Invoked at the start of a single resolve request initiated from within an operation. /// @@ -147,7 +182,7 @@ public static void RequestStart(this DiagnosticSource diagnosticSource, ResolveO { if (diagnosticSource.IsEnabled(RequestStartKey)) { - diagnosticSource.Write(RequestStartKey, new { operation, requestContext }); + diagnosticSource.Write(RequestStartKey, new RequestDiagnosticData(operation, requestContext)); } } @@ -162,7 +197,7 @@ public static void RequestFailure(this DiagnosticSource diagnosticSource, Resolv { if (diagnosticSource.IsEnabled(RequestFailureKey)) { - diagnosticSource.Write(RequestFailureKey, new { operation, requestContext, requestException }); + diagnosticSource.Write(RequestFailureKey, new RequestFailureDiagnosticData(operation, requestContext, requestException)); } } @@ -176,7 +211,7 @@ public static void RequestSuccess(this DiagnosticSource diagnosticSource, Resolv { if (diagnosticSource.IsEnabled(RequestSuccessKey)) { - diagnosticSource.Write(RequestSuccessKey, new { operation, requestContext }); + diagnosticSource.Write(RequestSuccessKey, new RequestDiagnosticData(operation, requestContext)); } } } diff --git a/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs b/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs new file mode 100644 index 000000000..7d6461fe2 --- /dev/null +++ b/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs @@ -0,0 +1,163 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; + +namespace Autofac.Core.Diagnostics +{ + public abstract class DiagnosticTracerBase : IObserver> + { + private readonly List _subscriptions = new List(); + + public virtual void Enable(string diagnosticName) + { + if (!this._subscriptions.Contains(diagnosticName)) + { + this._subscriptions.Add(diagnosticName); + } + } + + public void EnableAll() + { + this._subscriptions.Add(DiagnosticEventKeys.MiddlewareEntry); + this._subscriptions.Add(DiagnosticEventKeys.MiddlewareFailure); + this._subscriptions.Add(DiagnosticEventKeys.MiddlewareSuccess); + this._subscriptions.Add(DiagnosticEventKeys.OperationFailure); + this._subscriptions.Add(DiagnosticEventKeys.OperationStart); + this._subscriptions.Add(DiagnosticEventKeys.OperationSuccess); + this._subscriptions.Add(DiagnosticEventKeys.RequestFailure); + this._subscriptions.Add(DiagnosticEventKeys.RequestStart); + this._subscriptions.Add(DiagnosticEventKeys.RequestSuccess); + } + + public virtual void Disable(string diagnosticName) + { + this._subscriptions.Remove(diagnosticName); + } + + public virtual bool IsEnabled(string diagnosticName) + { + if (this._subscriptions.Count == 0) + { + return false; + } + + return this._subscriptions.Contains(diagnosticName); + } + + void IObserver>.OnCompleted() + { + // Do nothing + } + + void IObserver>.OnError(Exception error) + { + // Do nothing + } + + void IObserver>.OnNext(KeyValuePair value) + { + this.Write(value.Key, value.Value); + } + + public virtual void OnMiddlewareEntry(MiddlewareDiagnosticData data) + { + } + + public virtual void OnMiddlewareFailure(MiddlewareDiagnosticData data) + { + } + + public virtual void OnMiddlewareSuccess(MiddlewareDiagnosticData data) + { + } + + public virtual void OnOperationFailure(OperationFailureDiagnosticData data) + { + } + + public virtual void OnOperationStart(OperationStartDiagnosticData data) + { + } + + public virtual void OnOperationSuccess(OperationSuccessDiagnosticData data) + { + } + + public virtual void OnRequestFailure(RequestFailureDiagnosticData data) + { + } + + public virtual void OnRequestStart(RequestDiagnosticData data) + { + } + + public virtual void OnRequestSuccess(RequestDiagnosticData data) + { + } + + public virtual void Write(string diagnosticName, object parameters) + { + if (parameters == null || !this.IsEnabled(diagnosticName)) + { + return; + } + + switch (diagnosticName) + { + case DiagnosticEventKeys.MiddlewareEntry: + this.OnMiddlewareEntry((MiddlewareDiagnosticData)parameters); + break; + case DiagnosticEventKeys.MiddlewareFailure: + this.OnMiddlewareFailure((MiddlewareDiagnosticData)parameters); + break; + case DiagnosticEventKeys.MiddlewareSuccess: + this.OnMiddlewareSuccess((MiddlewareDiagnosticData)parameters); + break; + case DiagnosticEventKeys.OperationFailure: + this.OnOperationFailure((OperationFailureDiagnosticData)parameters); + break; + case DiagnosticEventKeys.OperationStart: + this.OnOperationStart((OperationStartDiagnosticData)parameters); + break; + case DiagnosticEventKeys.OperationSuccess: + this.OnOperationSuccess((OperationSuccessDiagnosticData)parameters); + break; + case DiagnosticEventKeys.RequestFailure: + this.OnRequestFailure((RequestFailureDiagnosticData)parameters); + break; + case DiagnosticEventKeys.RequestStart: + this.OnRequestStart((RequestDiagnosticData)parameters); + break; + case DiagnosticEventKeys.RequestSuccess: + this.OnRequestSuccess((RequestDiagnosticData)parameters); + break; + default: + break; + } + } + } +} diff --git a/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs b/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs new file mode 100644 index 000000000..2fb8be151 --- /dev/null +++ b/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs @@ -0,0 +1,46 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using Autofac.Core.Resolving; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Core.Diagnostics +{ + public class MiddlewareDiagnosticData + { + public MiddlewareDiagnosticData(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + { + this.Operation = operation; + this.RequestContext = requestContext; + this.Middleware = middleware; + } + + public ResolveOperationBase Operation { get; private set; } + + public ResolveRequestContextBase RequestContext { get; private set; } + + public IResolveMiddleware Middleware { get; private set; } + } +} diff --git a/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs b/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs new file mode 100644 index 000000000..967cc075e --- /dev/null +++ b/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs @@ -0,0 +1,43 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; +using Autofac.Core.Resolving; + +namespace Autofac.Core.Diagnostics +{ + public class OperationFailureDiagnosticData + { + public OperationFailureDiagnosticData(ResolveOperationBase operation, Exception operationException) + { + this.Operation = operation; + this.OperationException = operationException; + } + + public ResolveOperationBase Operation { get; private set; } + + public Exception OperationException { get; private set; } + } +} diff --git a/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs b/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs new file mode 100644 index 000000000..f76cc07ff --- /dev/null +++ b/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs @@ -0,0 +1,42 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using Autofac.Core.Resolving; + +namespace Autofac.Core.Diagnostics +{ + public class OperationStartDiagnosticData + { + public OperationStartDiagnosticData(ResolveOperationBase operation, ResolveRequest initiatingRequest) + { + this.Operation = operation; + this.InitiatingRequest = initiatingRequest; + } + + public ResolveOperationBase Operation { get; private set; } + + public ResolveRequest InitiatingRequest { get; private set; } + } +} diff --git a/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs b/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs new file mode 100644 index 000000000..a9e53a336 --- /dev/null +++ b/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs @@ -0,0 +1,42 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using Autofac.Core.Resolving; + +namespace Autofac.Core.Diagnostics +{ + public class OperationSuccessDiagnosticData + { + public OperationSuccessDiagnosticData(ResolveOperationBase operation, object resolvedInstance) + { + this.Operation = operation; + this.ResolvedInstance = resolvedInstance; + } + + public ResolveOperationBase Operation { get; private set; } + + public object ResolvedInstance { get; private set; } + } +} diff --git a/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs b/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs index 3ca54f219..7818d6d86 100644 --- a/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs +++ b/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs @@ -1,4 +1,29 @@ -using Autofac.Core.Resolving; +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using Autofac.Core.Resolving; namespace Autofac.Core.Diagnostics { diff --git a/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs b/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs new file mode 100644 index 000000000..8ee50212b --- /dev/null +++ b/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs @@ -0,0 +1,43 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using Autofac.Core.Resolving; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Core.Diagnostics +{ + public class RequestDiagnosticData + { + public RequestDiagnosticData(ResolveOperationBase operation, ResolveRequestContextBase requestContext) + { + this.Operation = operation; + this.RequestContext = requestContext; + } + + public ResolveOperationBase Operation { get; private set; } + + public ResolveRequestContextBase RequestContext { get; private set; } + } +} diff --git a/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs b/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs new file mode 100644 index 000000000..4a525f5a6 --- /dev/null +++ b/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs @@ -0,0 +1,47 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; +using Autofac.Core.Resolving; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Core.Diagnostics +{ + public class RequestFailureDiagnosticData + { + public RequestFailureDiagnosticData(ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException) + { + this.Operation = operation; + this.RequestContext = requestContext; + this.RequestException = requestException; + } + + public ResolveOperationBase Operation { get; private set; } + + public ResolveRequestContextBase RequestContext { get; private set; } + + public Exception RequestException { get; private set; } + } +} diff --git a/src/Autofac/Core/Lifetime/LifetimeScope.cs b/src/Autofac/Core/Lifetime/LifetimeScope.cs index 2b8e3ba90..4a538dbf2 100644 --- a/src/Autofac/Core/Lifetime/LifetimeScope.cs +++ b/src/Autofac/Core/Lifetime/LifetimeScope.cs @@ -53,7 +53,6 @@ public class LifetimeScope : Disposable, ISharingLifetimeScope, IServiceProvider private readonly ConcurrentDictionary<(Guid, Guid), object> _sharedQualifiedInstances = new ConcurrentDictionary<(Guid, Guid), object>(); private object? _anonymousTag; private LifetimeScope? _parentScope; - private IResolvePipelineTracer? _tracer; /// /// Gets the id of the lifetime scope self-registration. @@ -75,23 +74,14 @@ public class LifetimeScope : Disposable, ISharingLifetimeScope, IServiceProvider /// Components used in the scope. /// Parent scope. protected LifetimeScope(IComponentRegistry componentRegistry, LifetimeScope parent, object tag) - : this(componentRegistry, parent, null, tag) { - } - - /// - /// Initializes a new instance of the class. - /// - /// The tag applied to the . - /// Components used in the scope. - /// Parent scope. - /// A tracer instance for this scope. - protected LifetimeScope(IComponentRegistry componentRegistry, LifetimeScope parent, IResolvePipelineTracer? tracer, object tag) - : this(componentRegistry, tag) - { - _tracer = tracer; + ComponentRegistry = componentRegistry ?? throw new ArgumentNullException(nameof(componentRegistry)); + Tag = tag ?? throw new ArgumentNullException(nameof(tag)); _parentScope = parent ?? throw new ArgumentNullException(nameof(parent)); + + _sharedInstances[SelfRegistrationId] = this; RootLifetimeScope = _parentScope.RootLifetimeScope; + this.DiagnosticSource = _parentScope.DiagnosticSource; } /// @@ -101,10 +91,12 @@ protected LifetimeScope(IComponentRegistry componentRegistry, LifetimeScope pare /// Components used in the scope. public LifetimeScope(IComponentRegistry componentRegistry, object tag) { - _sharedInstances[SelfRegistrationId] = this; ComponentRegistry = componentRegistry ?? throw new ArgumentNullException(nameof(componentRegistry)); Tag = tag ?? throw new ArgumentNullException(nameof(tag)); + + _sharedInstances[SelfRegistrationId] = this; RootLifetimeScope = this; + this.DiagnosticSource = new DiagnosticListener("Autofac"); } /// @@ -137,7 +129,7 @@ public ILifetimeScope BeginLifetimeScope(object tag) CheckNotDisposed(); CheckTagIsUnique(tag); - var scope = new LifetimeScope(ComponentRegistry, this, _tracer, tag); + var scope = new LifetimeScope(ComponentRegistry, this, tag); RaiseBeginning(scope); return scope; } @@ -168,11 +160,8 @@ private void RaiseBeginning(ILifetimeScope scope) handler?.Invoke(this, new LifetimeScopeBeginningEventArgs(scope)); } - /// - public void AttachTrace(IResolvePipelineTracer tracer) - { - _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer)); - } + /// + public DiagnosticListener DiagnosticSource { get; } /// /// Begin a new anonymous sub-scope, with additional components available to it. @@ -226,7 +215,7 @@ public ILifetimeScope BeginLifetimeScope(object tag, Action co CheckTagIsUnique(tag); var localsBuilder = CreateScopeRestrictedRegistry(tag, configurationAction); - var scope = new LifetimeScope(localsBuilder.Build(), this, _tracer, tag); + var scope = new LifetimeScope(localsBuilder.Build(), this, tag); scope.Disposer.AddInstanceForDisposal(localsBuilder); if (localsBuilder.Properties.TryGetValue(MetadataKeys.ContainerBuildOptions, out var options) @@ -303,7 +292,7 @@ public object ResolveComponent(ResolveRequest request) CheckNotDisposed(); - var operation = new ResolveOperation(this, _tracer); + var operation = new ResolveOperation(this); var handler = ResolveOperationBeginning; handler?.Invoke(this, new ResolveOperationBeginningEventArgs(operation)); return operation.Execute(request); diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs index 9daad7996..27cebff3d 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs @@ -29,6 +29,7 @@ using System.Globalization; using System.Linq; using System.Text; +using Autofac.Core.Diagnostics; using Autofac.Core.Pipeline; using Autofac.Core.Resolving.Middleware; @@ -271,10 +272,10 @@ Action Chain(Action next, return (ctxt) => { - // Optimise the path depending on whether a tracer is attached. - if (ctxt.TracingEnabled) + // Optimise the path depending on whether diagnostics are enabled. + if (ctxt.DiagnosticSource.MiddlewareDiagnosticsEnabled()) { - ctxt.Tracer!.MiddlewareEntry(ctxt.Operation, ctxt, stage); + ctxt.DiagnosticSource.MiddlewareEntry(ctxt.Operation, ctxt, stage); var succeeded = false; try { @@ -284,7 +285,7 @@ Action Chain(Action next, } finally { - ctxt.Tracer.MiddlewareExit(ctxt.Operation, ctxt, stage, succeeded); + ctxt.DiagnosticSource.MiddlewareExit(ctxt.Operation, ctxt, stage, succeeded); } } else diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs index 6fa899fe9..f0aeebbd1 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs @@ -23,8 +23,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. -using Autofac.Core.Diagnostics; - namespace Autofac.Core.Resolving.Pipeline { /// @@ -38,13 +36,11 @@ internal sealed class ResolveRequestContext : ResolveRequestContextBase /// The owning resolve operation. /// The initiating resolve request. /// The lifetime scope. - /// An optional tracer. internal ResolveRequestContext( ResolveOperationBase owningOperation, ResolveRequest request, - ISharingLifetimeScope scope, - IResolvePipelineTracer? tracer) - : base(owningOperation, request, scope, tracer) + ISharingLifetimeScope scope) + : base(owningOperation, request, scope) { } diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs index 05378ebe5..20b2916fb 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs @@ -25,6 +25,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Autofac.Core.Diagnostics; using Autofac.Core.Registration; @@ -47,19 +48,16 @@ public abstract class ResolveRequestContextBase : IComponentContext /// The owning resolve operation. /// The initiating resolve request. /// The lifetime scope. - /// An optional tracer. internal ResolveRequestContextBase( ResolveOperationBase owningOperation, ResolveRequest request, - ISharingLifetimeScope scope, - IResolvePipelineTracer? tracer) + ISharingLifetimeScope scope) { Operation = owningOperation; ActivationScope = scope; Parameters = request.Parameters; PhaseReached = PipelinePhase.ResolveRequestStart; - Tracer = tracer; - TracingEnabled = tracer is object; + this.DiagnosticSource = scope.DiagnosticSource; _resolveRequest = request; } @@ -109,14 +107,9 @@ public abstract class ResolveRequestContextBase : IComponentContext public bool NewInstanceActivated => Instance is object && PhaseReached == PipelinePhase.Activation; /// - /// Gets the active for the request. + /// Gets the for the request. /// - public IResolvePipelineTracer? Tracer { get; } - - /// - /// Gets a value indicating whether tracing is enabled. If this value is true, then will not be null. - /// - public bool TracingEnabled { get; } + public DiagnosticSource DiagnosticSource { get; } /// /// Gets the current resolve parameters. These can be changed using the method. diff --git a/src/Autofac/Core/Resolving/ResolveOperation.cs b/src/Autofac/Core/Resolving/ResolveOperation.cs index 0c2c443e5..518145a19 100644 --- a/src/Autofac/Core/Resolving/ResolveOperation.cs +++ b/src/Autofac/Core/Resolving/ResolveOperation.cs @@ -50,21 +50,9 @@ public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope) /// /// The most nested scope in which to begin the operation. The operation /// can move upward to less nested scopes as components with wider sharing scopes are activated. - /// A pipeline tracer for the operation. - public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, IResolvePipelineTracer? pipelineTracer) - : base(mostNestedLifetimeScope, pipelineTracer) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The most nested scope in which to begin the operation. The operation - /// can move upward to less nested scopes as components with wider sharing scopes are activated. - /// An optional pipeline tracer. /// A parent resolve operation, used to maintain tracing between related operations. - public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, IResolvePipelineTracer? pipelineTracer, ResolveOperationBase parentOperation) - : base(mostNestedLifetimeScope, pipelineTracer, parentOperation) + public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, ResolveOperationBase parentOperation) + : base(mostNestedLifetimeScope, parentOperation) { } diff --git a/src/Autofac/Core/Resolving/ResolveOperationBase.cs b/src/Autofac/Core/Resolving/ResolveOperationBase.cs index 8a0497526..f82738b6d 100644 --- a/src/Autofac/Core/Resolving/ResolveOperationBase.cs +++ b/src/Autofac/Core/Resolving/ResolveOperationBase.cs @@ -25,6 +25,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using Autofac.Core.Diagnostics; using Autofac.Core.Resolving.Middleware; using Autofac.Core.Resolving.Pipeline; @@ -39,33 +40,22 @@ public abstract class ResolveOperationBase : IResolveOperation, ITracingIdentife private const int SuccessListInitialCapacity = 32; private bool _ended; - private IResolvePipelineTracer? _pipelineTracer; private List _successfulRequests = new List(SuccessListInitialCapacity); private int _nextCompleteSuccessfulRequestStartPos = 0; - /// - /// Initializes a new instance of the class. - /// - /// The most nested scope in which to begin the operation. The operation - /// can move upward to less nested scopes as components with wider sharing scopes are activated. - protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope) - : this(mostNestedLifetimeScope, null) - { - } - /// /// Initializes a new instance of the class. /// /// The most nested scope in which to begin the operation. The operation /// can move upward to less nested scopes as components with wider sharing scopes are activated. /// A pipeline tracer for the operation. - protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, IResolvePipelineTracer? pipelineTracer) + protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope) { TracingId = this; IsTopLevelOperation = true; CurrentScope = mostNestedLifetimeScope; - _pipelineTracer = pipelineTracer; IsTopLevelOperation = true; + this.DiagnosticSource = mostNestedLifetimeScope.DiagnosticSource; } /// @@ -73,10 +63,9 @@ protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, IR /// /// The most nested scope in which to begin the operation. The operation /// can move upward to less nested scopes as components with wider sharing scopes are activated. - /// A pipeline tracer for the operation. /// A tracing ID for the operation. - protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, IResolvePipelineTracer? pipelineTracer, ITracingIdentifer tracingId) - : this(mostNestedLifetimeScope, pipelineTracer) + protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, ITracingIdentifer tracingId) + : this(mostNestedLifetimeScope) { TracingId = tracingId; IsTopLevelOperation = false; @@ -102,6 +91,11 @@ protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, IR /// public ITracingIdentifer TracingId { get; } + /// + /// Gets the for the operation. + /// + public DiagnosticSource DiagnosticSource { get; } + /// /// Gets or sets the current request depth. /// @@ -155,26 +149,40 @@ protected object ExecuteOperation(ResolveRequest request) { InitiatingRequest = request; - _pipelineTracer?.OperationStart(this, request); + if (this.DiagnosticSource.OperationDiagnosticsEnabled()) + { + this.DiagnosticSource.OperationStart(this, request); + } result = GetOrCreateInstance(CurrentScope, request); } catch (ObjectDisposedException disposeException) { - _pipelineTracer?.OperationFailure(this, disposeException); + if (this.DiagnosticSource.OperationDiagnosticsEnabled()) + { + this.DiagnosticSource.OperationFailure(this, disposeException); + } throw; } catch (DependencyResolutionException dependencyResolutionException) { - _pipelineTracer?.OperationFailure(this, dependencyResolutionException); + if (this.DiagnosticSource.OperationDiagnosticsEnabled()) + { + this.DiagnosticSource.OperationFailure(this, dependencyResolutionException); + } + End(dependencyResolutionException); throw; } catch (Exception exception) { End(exception); - _pipelineTracer?.OperationFailure(this, exception); + if (this.DiagnosticSource.OperationDiagnosticsEnabled()) + { + this.DiagnosticSource.OperationFailure(this, exception); + } + throw new DependencyResolutionException(ResolveOperationResources.ExceptionDuringResolve, exception); } finally @@ -184,7 +192,10 @@ protected object ExecuteOperation(ResolveRequest request) End(); - _pipelineTracer?.OperationSuccess(this, result); + if (this.DiagnosticSource.OperationDiagnosticsEnabled()) + { + this.DiagnosticSource.OperationSuccess(this, result); + } return result; } @@ -195,7 +206,7 @@ public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, R if (_ended) throw new ObjectDisposedException(ResolveOperationResources.TemporaryContextDisposed, innerException: null); // Create a new request context. - var requestContext = new ResolveRequestContext(this, request, currentOperationScope, _pipelineTracer); + var requestContext = new ResolveRequestContext(this, request, currentOperationScope); // Raise our request-beginning event. var handler = ResolveRequestBeginning; @@ -212,7 +223,10 @@ public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, R try { - _pipelineTracer?.RequestStart(this, requestContext); + if (this.DiagnosticSource.RequestDiagnosticsEnabled()) + { + this.DiagnosticSource.RequestStart(this, requestContext); + } // Invoke the resolve pipeline. request.ResolvePipeline.Invoke(requestContext); @@ -224,11 +238,18 @@ public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, R } _successfulRequests.Add(requestContext); - _pipelineTracer?.RequestSuccess(this, requestContext); + if (this.DiagnosticSource.RequestDiagnosticsEnabled()) + { + this.DiagnosticSource.RequestSuccess(this, requestContext); + } } catch (Exception ex) { - _pipelineTracer?.RequestFailure(this, requestContext, ex); + if (this.DiagnosticSource.RequestDiagnosticsEnabled()) + { + this.DiagnosticSource.RequestFailure(this, requestContext, ex); + } + throw; } finally diff --git a/src/Autofac/ILifetimeScope.cs b/src/Autofac/ILifetimeScope.cs index da9d1fe92..9181ffddc 100644 --- a/src/Autofac/ILifetimeScope.cs +++ b/src/Autofac/ILifetimeScope.cs @@ -24,9 +24,9 @@ // OTHER DEALINGS IN THE SOFTWARE. using System; +using System.Diagnostics; using Autofac.Builder; using Autofac.Core; -using Autofac.Core.Diagnostics; using Autofac.Core.Lifetime; using Autofac.Core.Resolving; @@ -119,15 +119,10 @@ public interface ILifetimeScope : IComponentContext, IDisposable, IAsyncDisposab ILifetimeScope BeginLifetimeScope(object tag, Action configurationAction); /// - /// Enable tracing (or replace existing tracing) on this scope, routing trace events to the specified tracer. - /// All lifetime scopes created from this one will inherit this tracer as well. + /// Gets the to which + /// trace events should be written. /// - /// The implementation. - /// - /// Only one tracer is supported at once, so calling this will replace any prior tracer on this scope. Nested scopes - /// will continue to retain the same trace behaviour. - /// - void AttachTrace(IResolvePipelineTracer tracer); + DiagnosticListener DiagnosticSource { get; } /// /// Gets the disposer associated with this . diff --git a/src/Autofac/LifetimeScopeExtensions.cs b/src/Autofac/LifetimeScopeExtensions.cs index b10ab1bae..52d964086 100644 --- a/src/Autofac/LifetimeScopeExtensions.cs +++ b/src/Autofac/LifetimeScopeExtensions.cs @@ -24,6 +24,7 @@ // OTHER DEALINGS IN THE SOFTWARE. using System; +using System.Diagnostics; using Autofac.Core.Diagnostics; namespace Autofac @@ -43,20 +44,30 @@ public static class LifetimeScopeExtensions /// Only one tracer is supported at once, so calling this will replace any prior tracer on this scope. Existing nested scopes /// will continue to retain their original trace behaviour. /// - public static void AttachTrace(this ILifetimeScope scope, Action newTraceCallback) + public static void SubscribeToDiagnostics(this ILifetimeScope scope, DiagnosticTracerBase tracer) { if (scope is null) throw new ArgumentNullException(nameof(scope)); - if (newTraceCallback is null) throw new ArgumentNullException(nameof(newTraceCallback)); - - // Create a new default tracer and attach the callback. - var tracer = new DefaultDiagnosticTracer(); - tracer.OperationCompleted += (sender, args) => - { - // The initiating request will always be non-null by the time an operation completes. - newTraceCallback(args.Operation.InitiatingRequest!, args.TraceContent); - }; + if (tracer is null) throw new ArgumentNullException(nameof(tracer)); + scope.DiagnosticSource.Subscribe(tracer, tracer.IsEnabled); + } - scope.AttachTrace(tracer); + /// + /// Enable tracing on this scope, routing trace events to the specified tracer. + /// All lifetime scopes created from this one will inherit this tracer as well. + /// + /// The lifetime scope. + /// A callback that will receive the trace output. + /// + /// Only one tracer is supported at once, so calling this will replace any prior tracer on this scope. Existing nested scopes + /// will continue to retain their original trace behaviour. + /// + public static T SubscribeToDiagnostics(this ILifetimeScope scope) + where T : DiagnosticTracerBase, new() + { + if (scope is null) throw new ArgumentNullException(nameof(scope)); + var tracer = new T(); + scope.SubscribeToDiagnostics(tracer); + return tracer; } } } diff --git a/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs b/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs index b11d137f8..52b70da0a 100644 --- a/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs +++ b/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs @@ -16,7 +16,7 @@ public void DiagnosticTracerRaisesEvents() var container = containerBuilder.Build(); - container.AttachTrace(tracer); + container.SubscribeToDiagnostics(tracer); string lastOpResult = null; @@ -41,7 +41,7 @@ public void DiagnosticTracerRaisesEventsOnError() var container = containerBuilder.Build(); - container.AttachTrace(tracer); + container.SubscribeToDiagnostics(tracer); string lastOpResult = null; @@ -73,7 +73,7 @@ public void DiagnosticTracerDoesNotRaiseAnEventOnNestedOperations() var container = containerBuilder.Build(); - container.AttachTrace(tracer); + container.SubscribeToDiagnostics(tracer); int traceCount = 0; string lastOpResult = null; @@ -103,7 +103,7 @@ public void DiagnosticTracerDoesNotLeakMemory() var container = containerBuilder.Build(); - container.AttachTrace(tracer); + container.SubscribeToDiagnostics(tracer); container.Resolve(); // The dictionary of tracked trace IDs and From aaf370c91441834cbfd6e1b2ea0ac173646152b3 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Tue, 7 Jul 2020 12:00:01 -0700 Subject: [PATCH 09/45] Existing tests pass with the new trace setup. --- .../Diagnostics/DefaultDiagnosticTracer.cs | 15 +++++++ .../Diagnostics/DiagnosticSourceExtensions.cs | 10 ++--- .../Features/CircularDependencyTests.cs | 10 +++-- .../Lifetime/InstancePerLifetimeScopeTests.cs | 2 +- .../ActivatorPipelineExtensions.cs | 3 +- .../Core/Pipeline/PipelineBuilderTests.cs | 11 ++--- .../Core/Resolving/ResolveOperationTests.cs | 12 +++--- .../Decorators/OpenGenericDecoratorTests.cs | 4 +- test/Autofac.Test/Mocks.cs | 40 +++++++++++-------- 9 files changed, 63 insertions(+), 44 deletions(-) diff --git a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs index 4cfe2bfe3..97f194683 100644 --- a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs @@ -18,6 +18,21 @@ public class DefaultDiagnosticTracer : DiagnosticTracerBase private static readonly string[] NewLineSplit = new[] { Environment.NewLine }; + public DefaultDiagnosticTracer() + { + this.EnableAll(); + } + + public override void Enable(string diagnosticName) + { + // Do nothing. Default is always enabled for everything. + } + + public override void Disable(string diagnosticName) + { + // Do nothing. Default is always enabled for everything. + } + /// /// Event raised when a resolve operation completes, and trace data is available. /// diff --git a/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs b/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs index 6be7c5e44..c233e91f3 100644 --- a/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs +++ b/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs @@ -77,7 +77,7 @@ public static void MiddlewareEntry(this DiagnosticSource diagnosticSource, Resol { if (diagnosticSource.IsEnabled(MiddlewareEntryKey)) { - diagnosticSource.Write(MiddlewareEntryKey, new { operation, requestContext, middleware }); + diagnosticSource.Write(MiddlewareEntryKey, new MiddlewareDiagnosticData(operation, requestContext, middleware)); } } @@ -141,9 +141,9 @@ public static void OperationStart(this DiagnosticSource diagnosticSource, Resolv /// The exception that caused the operation failure. public static void OperationFailure(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, Exception operationException) { - if (diagnosticSource.IsEnabled(OperationStartKey)) + if (diagnosticSource.IsEnabled(OperationFailureKey)) { - diagnosticSource.Write(OperationStartKey, new OperationFailureDiagnosticData(operation, operationException)); + diagnosticSource.Write(OperationFailureKey, new OperationFailureDiagnosticData(operation, operationException)); } } @@ -156,9 +156,9 @@ public static void OperationFailure(this DiagnosticSource diagnosticSource, Reso /// The resolved instance providing the requested service. public static void OperationSuccess(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, object resolvedInstance) { - if (diagnosticSource.IsEnabled(OperationStartKey)) + if (diagnosticSource.IsEnabled(OperationSuccessKey)) { - diagnosticSource.Write(OperationStartKey, new OperationSuccessDiagnosticData(operation, resolvedInstance)); + diagnosticSource.Write(OperationSuccessKey, new OperationSuccessDiagnosticData(operation, resolvedInstance)); } } diff --git a/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs b/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs index 5bfc8a216..b075e03b9 100644 --- a/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs +++ b/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs @@ -113,11 +113,13 @@ public void InstancePerDependencyDoesNotAllowCircularDependencies_PropertyOwnerR string capturedTrace = null; - c.AttachTrace((req, trace) => + var tracer = new DefaultDiagnosticTracer(); + tracer.OperationCompleted += (sender, args) => { - capturedTrace = trace; - _output.WriteLine(trace); - }); + capturedTrace = args.TraceContent; + _output.WriteLine(capturedTrace); + }; + c.SubscribeToDiagnostics(tracer); Assert.Throws(() => c.Resolve()); Assert.NotNull(capturedTrace); diff --git a/test/Autofac.Specification.Test/Lifetime/InstancePerLifetimeScopeTests.cs b/test/Autofac.Specification.Test/Lifetime/InstancePerLifetimeScopeTests.cs index 70a09f8ce..06c663f51 100644 --- a/test/Autofac.Specification.Test/Lifetime/InstancePerLifetimeScopeTests.cs +++ b/test/Autofac.Specification.Test/Lifetime/InstancePerLifetimeScopeTests.cs @@ -31,7 +31,7 @@ public void TypeAsInstancePerScope() _output.WriteLine(args.TraceContent); }; - lifetime.AttachTrace(tracer); + lifetime.SubscribeToDiagnostics(tracer); var ctxA = lifetime.Resolve(); var ctxA2 = lifetime.Resolve(); diff --git a/test/Autofac.Test/ActivatorPipelineExtensions.cs b/test/Autofac.Test/ActivatorPipelineExtensions.cs index 080c237c8..6c4570a18 100644 --- a/test/Autofac.Test/ActivatorPipelineExtensions.cs +++ b/test/Autofac.Test/ActivatorPipelineExtensions.cs @@ -46,8 +46,7 @@ public static class ActivatorPipelineExtensions var request = new ResolveRequestContext( new ResolveOperation(lifetimeScope), new ResolveRequest(new TypedService(typeof(T)), Mocks.GetResolvableImplementation(), parameters), - lifetimeScope, - null); + lifetimeScope); built.Invoke(request); diff --git a/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs b/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs index f54a72209..582f77992 100644 --- a/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs +++ b/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Autofac.Core; @@ -424,8 +425,7 @@ public MockPipelineRequestContext() : base( new ResolveOperation(new MockLifetimeScope()), new ResolveRequest(new TypedService(typeof(int)), Mocks.GetResolvableImplementation(), Enumerable.Empty()), - new MockLifetimeScope(), - null) + new MockLifetimeScope()) { } } @@ -442,6 +442,8 @@ private class MockLifetimeScope : ISharingLifetimeScope public IComponentRegistry ComponentRegistry => throw new NotImplementedException(); + public DiagnosticListener DiagnosticSource { get; } = new DiagnosticListener("Autofac"); + public event EventHandler ChildLifetimeScopeBeginning { add { } @@ -460,11 +462,6 @@ private class MockLifetimeScope : ISharingLifetimeScope remove { } } - public void AttachTrace(IResolvePipelineTracer tracer) - { - throw new NotImplementedException(); - } - public ILifetimeScope BeginLifetimeScope() { throw new NotImplementedException(); diff --git a/test/Autofac.Test/Core/Resolving/ResolveOperationTests.cs b/test/Autofac.Test/Core/Resolving/ResolveOperationTests.cs index b11d166ce..103906f5a 100644 --- a/test/Autofac.Test/Core/Resolving/ResolveOperationTests.cs +++ b/test/Autofac.Test/Core/Resolving/ResolveOperationTests.cs @@ -34,12 +34,12 @@ public void OperationRaisesSuccessTraceEvents() builder.RegisterInstance("Hello"); var container = builder.Build(); + var mockTracer = Mocks.GetTracer(); + container.SubscribeToDiagnostics(mockTracer); var scope = container.Resolve() as ISharingLifetimeScope; - var mockTracer = Mocks.GetTracer(); - - var resolveOp = new ResolveOperation(scope, mockTracer); + var resolveOp = new ResolveOperation(scope); var raisedEvents = new List(); @@ -84,12 +84,12 @@ public void OperationRaisesFailureTraceEvents() builder.Register(ctxt => throw new InvalidOperationException()); var container = builder.Build(); + var mockTracer = Mocks.GetTracer(); + container.SubscribeToDiagnostics(mockTracer); var scope = container.Resolve() as ISharingLifetimeScope; - var mockTracer = Mocks.GetTracer(); - - var resolveOp = new ResolveOperation(scope, mockTracer); + var resolveOp = new ResolveOperation(scope); var raisedEvents = new List(); diff --git a/test/Autofac.Test/Features/Decorators/OpenGenericDecoratorTests.cs b/test/Autofac.Test/Features/Decorators/OpenGenericDecoratorTests.cs index c5f66f03f..ec12e6fb6 100644 --- a/test/Autofac.Test/Features/Decorators/OpenGenericDecoratorTests.cs +++ b/test/Autofac.Test/Features/Decorators/OpenGenericDecoratorTests.cs @@ -217,7 +217,7 @@ public void DecoratedInstancePerDependencyRegistrationCanIncludeOtherServices() var tracer = new DefaultDiagnosticTracer(); - container.AttachTrace(tracer); + container.SubscribeToDiagnostics(tracer); tracer.OperationCompleted += (sender, args) => { @@ -253,7 +253,7 @@ public void DecoratedInstancePerLifetimeScopeRegistrationCanIncludeOtherServices var tracer = new DefaultDiagnosticTracer(); - container.AttachTrace(tracer); + container.SubscribeToDiagnostics(tracer); tracer.OperationCompleted += (sender, args) => { diff --git a/test/Autofac.Test/Mocks.cs b/test/Autofac.Test/Mocks.cs index 4cfc5583b..28b2e2864 100644 --- a/test/Autofac.Test/Mocks.cs +++ b/test/Autofac.Test/Mocks.cs @@ -104,10 +104,11 @@ public void BuildResolvePipeline(IComponentRegistryServices registryServices) } } - internal class MockTracer : IResolvePipelineTracer + internal class MockTracer : DiagnosticTracerBase { public MockTracer() { + this.EnableAll(); } public event Action OperationStarting; @@ -126,44 +127,49 @@ public MockTracer() public event Action OperationSucceeding; - public void OperationStart(ResolveOperationBase operation, ResolveRequest initiatingRequest) + public override void OnOperationStart(OperationStartDiagnosticData data) { - OperationStarting?.Invoke(operation, initiatingRequest); + OperationStarting?.Invoke(data.Operation, data.InitiatingRequest); } - public void RequestStart(ResolveOperationBase operation, ResolveRequestContextBase requestContext) + public override void OnRequestStart(RequestDiagnosticData data) { - RequestStarting?.Invoke(operation, requestContext); + RequestStarting?.Invoke(data.Operation, data.RequestContext); } - public void MiddlewareEntry(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + public override void OnMiddlewareEntry(MiddlewareDiagnosticData data) { - EnteringMiddleware?.Invoke(operation, requestContext, middleware); + EnteringMiddleware?.Invoke(data.Operation, data.RequestContext, data.Middleware); } - public void MiddlewareExit(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware, bool succeeded) + public override void OnMiddlewareFailure(MiddlewareDiagnosticData data) { - ExitingMiddleware?.Invoke(operation, requestContext, middleware, succeeded); + ExitingMiddleware?.Invoke(data.Operation, data.RequestContext, data.Middleware, false); } - public void RequestFailure(ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException) + public override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) { - RequestFailing?.Invoke(operation, requestContext, requestException); + ExitingMiddleware?.Invoke(data.Operation, data.RequestContext, data.Middleware, true); } - public void RequestSuccess(ResolveOperationBase operation, ResolveRequestContextBase requestContext) + public override void OnRequestFailure(RequestFailureDiagnosticData data) { - RequestSucceeding?.Invoke(operation, requestContext); + RequestFailing?.Invoke(data.Operation, data.RequestContext, data.RequestException); } - public void OperationFailure(ResolveOperationBase operation, Exception operationException) + public override void OnRequestSuccess(RequestDiagnosticData data) { - OperationFailing?.Invoke(operation, operationException); + RequestSucceeding?.Invoke(data.Operation, data.RequestContext); } - public void OperationSuccess(ResolveOperationBase operation, object resolvedInstance) + public override void OnOperationFailure(OperationFailureDiagnosticData data) { - OperationSucceeding?.Invoke(operation, resolvedInstance); + OperationFailing?.Invoke(data.Operation, data.OperationException); + } + + public override void OnOperationSuccess(OperationSuccessDiagnosticData data) + { + OperationSucceeding?.Invoke(data.Operation, data.ResolvedInstance); } } } From 40efaecf6fce7db8915af7d1be2325d315058cbf Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Tue, 7 Jul 2020 16:37:14 -0700 Subject: [PATCH 10/45] this. settings like corefx. I got overruled! --- .editorconfig | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.editorconfig b/.editorconfig index 1e63cf796..e08a1daf4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -36,16 +36,14 @@ csharp_indent_labels = one_less_than_current ; Modifier preferences csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion -; Using this. makes navigation outside VS a lot clearer. -dotnet_style_qualification_for_field = true:suggestion -dotnet_style_qualification_for_property = true:suggestion -dotnet_style_qualification_for_method = true:suggestion -dotnet_style_qualification_for_event = true:suggestion +; Avoid this. unless absolutely necessary +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion ; Types: use keywords instead of BCL types, using var is fine. -;csharp_style_var_for_built_in_types = false:suggestion csharp_style_var_when_type_is_apparent = false:none -;csharp_style_var_elsewhere = false:suggestion dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion dotnet_style_predefined_type_for_member_access = true:suggestion From 7010c9b440fb85d30e5bfb11e0332c0893b409d4 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Tue, 7 Jul 2020 16:43:45 -0700 Subject: [PATCH 11/45] Reference updates to match conventions. --- .../Diagnostics/DefaultDiagnosticTracer.cs | 4 +- .../Core/Diagnostics/DiagnosticTracerBase.cs | 50 +++++++++---------- .../Diagnostics/MiddlewareDiagnosticData.cs | 6 +-- .../OperationFailureDiagnosticData.cs | 4 +- .../OperationStartDiagnosticData.cs | 4 +- .../OperationSuccessDiagnosticData.cs | 4 +- .../Core/Diagnostics/RequestDiagnosticData.cs | 4 +- .../RequestFailureDiagnosticData.cs | 6 +-- .../Core/Resolving/ResolveOperationBase.cs | 34 ++++++------- .../DefaultDiagnosticTracerTests.cs | 2 +- 10 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs index 97f194683..6706ced35 100644 --- a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs @@ -20,7 +20,7 @@ public class DefaultDiagnosticTracer : DiagnosticTracerBase public DefaultDiagnosticTracer() { - this.EnableAll(); + EnableAll(); } public override void Enable(string diagnosticName) @@ -45,7 +45,7 @@ public override void Disable(string diagnosticName) /// An with the number of trace IDs associated /// with in-progress operations being traced by this tracer. /// - public int OperationsInProgress => this._operationBuilders.Count; + public int OperationsInProgress => _operationBuilders.Count; /// public override void OnOperationStart(OperationStartDiagnosticData data) diff --git a/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs b/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs index 7d6461fe2..1878e0818 100644 --- a/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs +++ b/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs @@ -34,38 +34,38 @@ public abstract class DiagnosticTracerBase : IObserver>.OnCompleted() @@ -80,7 +80,7 @@ public virtual bool IsEnabled(string diagnosticName) void IObserver>.OnNext(KeyValuePair value) { - this.Write(value.Key, value.Value); + Write(value.Key, value.Value); } public virtual void OnMiddlewareEntry(MiddlewareDiagnosticData data) @@ -121,7 +121,7 @@ public virtual void OnRequestSuccess(RequestDiagnosticData data) public virtual void Write(string diagnosticName, object parameters) { - if (parameters == null || !this.IsEnabled(diagnosticName)) + if (parameters == null || !IsEnabled(diagnosticName)) { return; } @@ -129,31 +129,31 @@ public virtual void Write(string diagnosticName, object parameters) switch (diagnosticName) { case DiagnosticEventKeys.MiddlewareEntry: - this.OnMiddlewareEntry((MiddlewareDiagnosticData)parameters); + OnMiddlewareEntry((MiddlewareDiagnosticData)parameters); break; case DiagnosticEventKeys.MiddlewareFailure: - this.OnMiddlewareFailure((MiddlewareDiagnosticData)parameters); + OnMiddlewareFailure((MiddlewareDiagnosticData)parameters); break; case DiagnosticEventKeys.MiddlewareSuccess: - this.OnMiddlewareSuccess((MiddlewareDiagnosticData)parameters); + OnMiddlewareSuccess((MiddlewareDiagnosticData)parameters); break; case DiagnosticEventKeys.OperationFailure: - this.OnOperationFailure((OperationFailureDiagnosticData)parameters); + OnOperationFailure((OperationFailureDiagnosticData)parameters); break; case DiagnosticEventKeys.OperationStart: - this.OnOperationStart((OperationStartDiagnosticData)parameters); + OnOperationStart((OperationStartDiagnosticData)parameters); break; case DiagnosticEventKeys.OperationSuccess: - this.OnOperationSuccess((OperationSuccessDiagnosticData)parameters); + OnOperationSuccess((OperationSuccessDiagnosticData)parameters); break; case DiagnosticEventKeys.RequestFailure: - this.OnRequestFailure((RequestFailureDiagnosticData)parameters); + OnRequestFailure((RequestFailureDiagnosticData)parameters); break; case DiagnosticEventKeys.RequestStart: - this.OnRequestStart((RequestDiagnosticData)parameters); + OnRequestStart((RequestDiagnosticData)parameters); break; case DiagnosticEventKeys.RequestSuccess: - this.OnRequestSuccess((RequestDiagnosticData)parameters); + OnRequestSuccess((RequestDiagnosticData)parameters); break; default: break; diff --git a/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs b/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs index 2fb8be151..de83b7b3b 100644 --- a/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs @@ -32,9 +32,9 @@ public class MiddlewareDiagnosticData { public MiddlewareDiagnosticData(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) { - this.Operation = operation; - this.RequestContext = requestContext; - this.Middleware = middleware; + Operation = operation; + RequestContext = requestContext; + Middleware = middleware; } public ResolveOperationBase Operation { get; private set; } diff --git a/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs b/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs index 967cc075e..3094e5d88 100644 --- a/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs @@ -32,8 +32,8 @@ public class OperationFailureDiagnosticData { public OperationFailureDiagnosticData(ResolveOperationBase operation, Exception operationException) { - this.Operation = operation; - this.OperationException = operationException; + Operation = operation; + OperationException = operationException; } public ResolveOperationBase Operation { get; private set; } diff --git a/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs b/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs index f76cc07ff..e06a7a660 100644 --- a/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs @@ -31,8 +31,8 @@ public class OperationStartDiagnosticData { public OperationStartDiagnosticData(ResolveOperationBase operation, ResolveRequest initiatingRequest) { - this.Operation = operation; - this.InitiatingRequest = initiatingRequest; + Operation = operation; + InitiatingRequest = initiatingRequest; } public ResolveOperationBase Operation { get; private set; } diff --git a/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs b/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs index a9e53a336..878ab5feb 100644 --- a/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs @@ -31,8 +31,8 @@ public class OperationSuccessDiagnosticData { public OperationSuccessDiagnosticData(ResolveOperationBase operation, object resolvedInstance) { - this.Operation = operation; - this.ResolvedInstance = resolvedInstance; + Operation = operation; + ResolvedInstance = resolvedInstance; } public ResolveOperationBase Operation { get; private set; } diff --git a/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs b/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs index 8ee50212b..b25c1f9dc 100644 --- a/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs @@ -32,8 +32,8 @@ public class RequestDiagnosticData { public RequestDiagnosticData(ResolveOperationBase operation, ResolveRequestContextBase requestContext) { - this.Operation = operation; - this.RequestContext = requestContext; + Operation = operation; + RequestContext = requestContext; } public ResolveOperationBase Operation { get; private set; } diff --git a/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs b/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs index 4a525f5a6..6eddd9073 100644 --- a/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs @@ -33,9 +33,9 @@ public class RequestFailureDiagnosticData { public RequestFailureDiagnosticData(ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException) { - this.Operation = operation; - this.RequestContext = requestContext; - this.RequestException = requestException; + Operation = operation; + RequestContext = requestContext; + RequestException = requestException; } public ResolveOperationBase Operation { get; private set; } diff --git a/src/Autofac/Core/Resolving/ResolveOperationBase.cs b/src/Autofac/Core/Resolving/ResolveOperationBase.cs index f82738b6d..811763f8c 100644 --- a/src/Autofac/Core/Resolving/ResolveOperationBase.cs +++ b/src/Autofac/Core/Resolving/ResolveOperationBase.cs @@ -55,7 +55,7 @@ protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope) IsTopLevelOperation = true; CurrentScope = mostNestedLifetimeScope; IsTopLevelOperation = true; - this.DiagnosticSource = mostNestedLifetimeScope.DiagnosticSource; + DiagnosticSource = mostNestedLifetimeScope.DiagnosticSource; } /// @@ -149,27 +149,27 @@ protected object ExecuteOperation(ResolveRequest request) { InitiatingRequest = request; - if (this.DiagnosticSource.OperationDiagnosticsEnabled()) + if (DiagnosticSource.OperationDiagnosticsEnabled()) { - this.DiagnosticSource.OperationStart(this, request); + DiagnosticSource.OperationStart(this, request); } result = GetOrCreateInstance(CurrentScope, request); } catch (ObjectDisposedException disposeException) { - if (this.DiagnosticSource.OperationDiagnosticsEnabled()) + if (DiagnosticSource.OperationDiagnosticsEnabled()) { - this.DiagnosticSource.OperationFailure(this, disposeException); + DiagnosticSource.OperationFailure(this, disposeException); } throw; } catch (DependencyResolutionException dependencyResolutionException) { - if (this.DiagnosticSource.OperationDiagnosticsEnabled()) + if (DiagnosticSource.OperationDiagnosticsEnabled()) { - this.DiagnosticSource.OperationFailure(this, dependencyResolutionException); + DiagnosticSource.OperationFailure(this, dependencyResolutionException); } End(dependencyResolutionException); @@ -178,9 +178,9 @@ protected object ExecuteOperation(ResolveRequest request) catch (Exception exception) { End(exception); - if (this.DiagnosticSource.OperationDiagnosticsEnabled()) + if (DiagnosticSource.OperationDiagnosticsEnabled()) { - this.DiagnosticSource.OperationFailure(this, exception); + DiagnosticSource.OperationFailure(this, exception); } throw new DependencyResolutionException(ResolveOperationResources.ExceptionDuringResolve, exception); @@ -192,9 +192,9 @@ protected object ExecuteOperation(ResolveRequest request) End(); - if (this.DiagnosticSource.OperationDiagnosticsEnabled()) + if (DiagnosticSource.OperationDiagnosticsEnabled()) { - this.DiagnosticSource.OperationSuccess(this, result); + DiagnosticSource.OperationSuccess(this, result); } return result; @@ -223,9 +223,9 @@ public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, R try { - if (this.DiagnosticSource.RequestDiagnosticsEnabled()) + if (DiagnosticSource.RequestDiagnosticsEnabled()) { - this.DiagnosticSource.RequestStart(this, requestContext); + DiagnosticSource.RequestStart(this, requestContext); } // Invoke the resolve pipeline. @@ -238,16 +238,16 @@ public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, R } _successfulRequests.Add(requestContext); - if (this.DiagnosticSource.RequestDiagnosticsEnabled()) + if (DiagnosticSource.RequestDiagnosticsEnabled()) { - this.DiagnosticSource.RequestSuccess(this, requestContext); + DiagnosticSource.RequestSuccess(this, requestContext); } } catch (Exception ex) { - if (this.DiagnosticSource.RequestDiagnosticsEnabled()) + if (DiagnosticSource.RequestDiagnosticsEnabled()) { - this.DiagnosticSource.RequestFailure(this, requestContext, ex); + DiagnosticSource.RequestFailure(this, requestContext, ex); } throw; diff --git a/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs b/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs index 52b70da0a..9fcc7629e 100644 --- a/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs +++ b/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs @@ -119,7 +119,7 @@ private class Decorator : IService { public Decorator(IService decorated) { - this.Decorated = decorated; + Decorated = decorated; } public IService Decorated { get; } From b09abc6f22379fa9e461cc42dd738120be5b80d7 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Wed, 8 Jul 2020 08:31:29 -0700 Subject: [PATCH 12/45] Docs and minor refactoring. --- src/Autofac/ContainerExtensions.cs | 87 ++++++ .../Diagnostics/DefaultDiagnosticTracer.cs | 67 ++++- .../Core/Diagnostics/DiagnosticEventKeys.cs | 37 ++- .../Diagnostics/DiagnosticSourceExtensions.cs | 109 ++++---- .../Core/Diagnostics/DiagnosticTracerBase.cs | 260 ++++++++++++++++-- .../Diagnostics/MiddlewareDiagnosticData.cs | 18 ++ .../OperationFailureDiagnosticData.cs | 14 + .../OperationStartDiagnosticData.cs | 14 + .../OperationSuccessDiagnosticData.cs | 14 + .../Core/Diagnostics/RequestDiagnosticData.cs | 14 + .../RequestFailureDiagnosticData.cs | 18 ++ .../Pipeline/ResolvePipelineBuilder.cs | 11 +- .../Core/Resolving/ResolveOperationBase.cs | 3 +- src/Autofac/LifetimeScopeExtensions.cs | 73 ----- .../Lifetime/InstancePerLifetimeScopeTests.cs | 2 +- test/Autofac.Test/Mocks.cs | 2 +- 16 files changed, 584 insertions(+), 159 deletions(-) create mode 100644 src/Autofac/ContainerExtensions.cs delete mode 100644 src/Autofac/LifetimeScopeExtensions.cs diff --git a/src/Autofac/ContainerExtensions.cs b/src/Autofac/ContainerExtensions.cs new file mode 100644 index 000000000..d210466ac --- /dev/null +++ b/src/Autofac/ContainerExtensions.cs @@ -0,0 +1,87 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Diagnostics; +using Autofac.Core.Diagnostics; + +namespace Autofac +{ + /// + /// Extensions to the container to provide convenience methods for tracing. + /// + public static class ContainerExtensions + { + /// + /// Subscribes a diagnostic tracer to Autofac events. + /// + /// The container with the diagnostics to which you want to subscribe. + /// A diagnostic tracer that will be subscribed to the lifetime scope's diagnostic source. + /// + /// + /// This is a convenience method that attaches the to the + /// associated with the . If you + /// have an event listener that isn't a you can + /// use standard semantics to subscribe to the events + /// with your custom listener. + /// + /// + public static void SubscribeToDiagnostics(this IContainer container, DiagnosticTracerBase tracer) + { + if (container is null) throw new ArgumentNullException(nameof(container)); + if (tracer is null) throw new ArgumentNullException(nameof(tracer)); + container.DiagnosticSource.Subscribe(tracer, tracer.IsEnabled); + } + + /// + /// Subscribes a diagnostic tracer to Autofac events. + /// + /// + /// The type of diagnostic tracer that will be subscribed to the lifetime scope's diagnostic source. + /// + /// The container with the diagnostics to which you want to subscribe. + /// + /// The diagnostic tracer that was created and attached to the diagnostic source. Use + /// this instance to enable or disable the messages that should be handled. + /// + /// + /// + /// This is a convenience method that attaches a tracer to the + /// associated with the . If you + /// have an event listener that isn't a you can + /// use standard semantics to subscribe to the events + /// with your custom listener. + /// + /// + public static T SubscribeToDiagnostics(this IContainer container) + where T : DiagnosticTracerBase, new() + { + if (container is null) throw new ArgumentNullException(nameof(container)); + var tracer = new T(); + container.SubscribeToDiagnostics(tracer); + return tracer; + } + } +} diff --git a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs index 6706ced35..0bed2f8aa 100644 --- a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs @@ -7,9 +7,18 @@ namespace Autofac.Core.Diagnostics { /// - /// Provides a default resolve pipeline tracer that builds a multi-line string describing the end-to-end operation flow. - /// Attach to the event to receive notifications when new trace content is available. + /// Provides a default resolve pipeline tracer that builds a multi-line + /// string describing the end-to-end operation flow. Attach to the + /// event to receive notifications when + /// new trace content is available. /// + /// + /// + /// The tracer subscribes to all Autofac diagnostic events and can't be + /// unsubscribed. This is required to ensure beginning and end of each + /// logical activity can be captured. + /// + /// public class DefaultDiagnosticTracer : DiagnosticTracerBase { private const string RequestExceptionTraced = "__RequestException"; @@ -18,23 +27,28 @@ public class DefaultDiagnosticTracer : DiagnosticTracerBase private static readonly string[] NewLineSplit = new[] { Environment.NewLine }; + /// + /// Initializes a new instance of the class. + /// public DefaultDiagnosticTracer() { EnableAll(); } + /// public override void Enable(string diagnosticName) { // Do nothing. Default is always enabled for everything. } + /// public override void Disable(string diagnosticName) { // Do nothing. Default is always enabled for everything. } /// - /// Event raised when a resolve operation completes, and trace data is available. + /// Event raised when a resolve operation completes and trace data is available. /// public event EventHandler? OperationCompleted; @@ -50,6 +64,11 @@ public override void Disable(string diagnosticName) /// public override void OnOperationStart(OperationStartDiagnosticData data) { + if (data is null) + { + return; + } + var builder = _operationBuilders.GetOrAdd(data.Operation.TracingId, k => new IndentingStringBuilder()); builder.AppendFormattedLine(TracerMessages.ResolveOperationStarting); @@ -60,6 +79,11 @@ public override void OnOperationStart(OperationStartDiagnosticData data) /// public override void OnRequestStart(RequestDiagnosticData data) { + if (data is null) + { + return; + } + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) { builder.AppendFormattedLine(TracerMessages.ResolveRequestStarting); @@ -79,8 +103,13 @@ public override void OnRequestStart(RequestDiagnosticData data) } /// - public override void OnMiddlewareEntry(MiddlewareDiagnosticData data) + public override void OnMiddlewareStart(MiddlewareDiagnosticData data) { + if (data is null) + { + return; + } + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) { builder.AppendFormattedLine(TracerMessages.EnterMiddleware, data.Middleware.ToString()); @@ -91,6 +120,11 @@ public override void OnMiddlewareEntry(MiddlewareDiagnosticData data) /// public override void OnMiddlewareFailure(MiddlewareDiagnosticData data) { + if (data is null) + { + return; + } + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) { builder.Outdent(); @@ -101,6 +135,11 @@ public override void OnMiddlewareFailure(MiddlewareDiagnosticData data) /// public override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) { + if (data is null) + { + return; + } + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) { builder.Outdent(); @@ -111,6 +150,11 @@ public override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) /// public override void OnRequestFailure(RequestFailureDiagnosticData data) { + if (data is null) + { + return; + } + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) { builder.Outdent(); @@ -138,6 +182,11 @@ public override void OnRequestFailure(RequestFailureDiagnosticData data) /// public override void OnRequestSuccess(RequestDiagnosticData data) { + if (data is null) + { + return; + } + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) { builder.Outdent(); @@ -149,6 +198,11 @@ public override void OnRequestSuccess(RequestDiagnosticData data) /// public override void OnOperationFailure(OperationFailureDiagnosticData data) { + if (data is null) + { + return; + } + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) { try @@ -173,6 +227,11 @@ public override void OnOperationFailure(OperationFailureDiagnosticData data) /// public override void OnOperationSuccess(OperationSuccessDiagnosticData data) { + if (data is null) + { + return; + } + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) { try diff --git a/src/Autofac/Core/Diagnostics/DiagnosticEventKeys.cs b/src/Autofac/Core/Diagnostics/DiagnosticEventKeys.cs index d0fb09780..86c9ec653 100644 --- a/src/Autofac/Core/Diagnostics/DiagnosticEventKeys.cs +++ b/src/Autofac/Core/Diagnostics/DiagnosticEventKeys.cs @@ -36,14 +36,49 @@ namespace Autofac.Core.Diagnostics /// internal static class DiagnosticEventKeys { - public const string MiddlewareEntry = "Autofac.Middleware.Entry"; + /// + /// ID for the event raised when middleware starts. + /// + public const string MiddlewareStart = "Autofac.Middleware.Start"; + + /// + /// ID for the event raised when middleware encounters an error. + /// public const string MiddlewareFailure = "Autofac.Middleware.Failure"; + + /// + /// ID for the event raised when middleware exits successfully. + /// public const string MiddlewareSuccess = "Autofac.Middleware.Success"; + + /// + /// ID for the event raised when a resolve operation encounters an error. + /// public const string OperationFailure = "Autofac.Operation.Failure"; + + /// + /// ID for the event raised when a resolve operation starts. + /// public const string OperationStart = "Autofac.Operation.Start"; + + /// + /// ID for the event raised when a resolve operation completes successfully. + /// public const string OperationSuccess = "Autofac.Operation.Success"; + + /// + /// ID for the event raised when a resolve request encounters an error. + /// public const string RequestFailure = "Autofac.Request.Failure"; + + /// + /// ID for the event raised when a resolve request starts. + /// public const string RequestStart = "Autofac.Request.Start"; + + /// + /// ID for the event raised when a resolve request completes successfully. + /// public const string RequestSuccess = "Autofac.Request.Success"; } } diff --git a/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs b/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs index c233e91f3..9674e7c3e 100644 --- a/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs +++ b/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs @@ -31,31 +31,11 @@ namespace Autofac.Core.Diagnostics { - // TODO: Create a common DiagnosticSource instance (a DiagnosticListener) that will be used for handling all events. This should be what gets passed from parent to child instead of a tracer object. - // https://github.com/dotnet/aspnetcore/blob/f3f9a1cdbcd06b298035b523732b9f45b1408461/src/Hosting/Hosting/src/WebHostBuilder.cs - // TODO: Each child lifetime scope should write to the DiagnosticSource using the same event IDs. Maybe we need this to be an extension method class on DiagnosticSource? ASP.NET has a wrapper class instead. - // https://github.com/dotnet/aspnetcore/blob/16be9a264e48560e10a3ee9683ecaed342d4ca11/src/Hosting/Hosting/src/Internal/HostingApplication.cs#L29 - // TODO: Write messages to the DiagnosticSource if it's enabled for different events. - // https://github.com/dotnet/aspnetcore/blob/c97a0020d8bab6d895bf821f6e47ee8722aa17d5/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs - // https://github.com/dotnet/aspnetcore/blob/28157e62597bf0e043bc7e937e44c5ec81946b83/src/Middleware/MiddlewareAnalysis/src/AnalysisMiddleware.cs - // TODO: Update the default diagnostic tracer so it uses a DiagnosticListener subscription. - // https://andrewlock.net/logging-using-diagnosticsource-in-asp-net-core/ - /// /// Extension methods for writing diagnostic messages. /// internal static class DiagnosticSourceExtensions { - private const string MiddlewareEntryKey = "Autofac.Middleware.Entry"; - private const string MiddlewareFailureKey = "Autofac.Middleware.Failure"; - private const string MiddlewareSuccessKey = "Autofac.Middleware.Success"; - private const string OperationFailureKey = "Autofac.Operation.Failure"; - private const string OperationStartKey = "Autofac.Operation.Start"; - private const string OperationSuccessKey = "Autofac.Operation.Success"; - private const string RequestFailureKey = "Autofac.Request.Failure"; - private const string RequestStartKey = "Autofac.Request.Start"; - private const string RequestSuccessKey = "Autofac.Request.Success"; - /// /// Determines if diagnostics for middleware events is enabled. /// @@ -63,45 +43,51 @@ internal static class DiagnosticSourceExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool MiddlewareDiagnosticsEnabled(this DiagnosticSource diagnosticSource) { - return diagnosticSource.IsEnabled(MiddlewareEntryKey) || diagnosticSource.IsEnabled(MiddlewareSuccessKey) || diagnosticSource.IsEnabled(MiddlewareFailureKey); + return diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareStart) || diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareSuccess) || diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareFailure); } /// - /// Invoked when an individual middleware item is about to execute (just before the method executes). + /// Writes a diagnostic event indicating an individual middleware item is about to execute (just before the method executes). /// /// The diagnostic source to which events will be written. /// The pipeline resolve operation that this request is running within. /// The context for the resolve request that is running. /// The middleware that is about to run. - public static void MiddlewareEntry(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + public static void MiddlewareStart(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) { - if (diagnosticSource.IsEnabled(MiddlewareEntryKey)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareStart)) { - diagnosticSource.Write(MiddlewareEntryKey, new MiddlewareDiagnosticData(operation, requestContext, middleware)); + diagnosticSource.Write(DiagnosticEventKeys.MiddlewareStart, new MiddlewareDiagnosticData(operation, requestContext, middleware)); } } /// - /// Invoked when an individual middleware item has finished executing (when the method returns). + /// Writes a diagnostic event indicating an individual middleware item has finished in an error state (when the method returns). /// /// The diagnostic source to which events will be written. /// The pipeline resolve operation that this request is running within. /// The context for the resolve request that is running. /// The middleware that just ran. - /// - /// Indicates whether the given middleware succeeded. The exception that - /// caused the middleware to fail is not available here, but will be - /// available in the next request failure event. - /// - public static void MiddlewareExit(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware, bool succeeded) + public static void MiddlewareFailure(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) { - if (succeeded && diagnosticSource.IsEnabled(MiddlewareSuccessKey)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareFailure)) { - diagnosticSource.Write(MiddlewareSuccessKey, new MiddlewareDiagnosticData(operation, requestContext, middleware)); + diagnosticSource.Write(DiagnosticEventKeys.MiddlewareFailure, new MiddlewareDiagnosticData(operation, requestContext, middleware)); } - else if (!succeeded && diagnosticSource.IsEnabled(MiddlewareFailureKey)) + } + + /// + /// Writes a diagnostic event indicating an individual middleware item has finished successfully (when the method returns). + /// + /// The diagnostic source to which events will be written. + /// The pipeline resolve operation that this request is running within. + /// The context for the resolve request that is running. + /// The middleware that just ran. + public static void MiddlewareSuccess(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + { + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareSuccess)) { - diagnosticSource.Write(MiddlewareFailureKey, new MiddlewareDiagnosticData(operation, requestContext, middleware)); + diagnosticSource.Write(DiagnosticEventKeys.MiddlewareSuccess, new MiddlewareDiagnosticData(operation, requestContext, middleware)); } } @@ -112,11 +98,11 @@ public static void MiddlewareExit(this DiagnosticSource diagnosticSource, Resolv [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool OperationDiagnosticsEnabled(this DiagnosticSource diagnosticSource) { - return diagnosticSource.IsEnabled(OperationStartKey) || diagnosticSource.IsEnabled(OperationSuccessKey) || diagnosticSource.IsEnabled(OperationFailureKey); + return diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationStart) || diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationSuccess) || diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationFailure); } /// - /// Invoked at operation start. + /// Writes a diagnostic event indicating a resolve operation has started. /// /// The diagnostic source to which events will be written. /// The pipeline resolve operation that is about to run. @@ -127,38 +113,45 @@ public static bool OperationDiagnosticsEnabled(this DiagnosticSource diagnosticS /// public static void OperationStart(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequest initiatingRequest) { - if (diagnosticSource.IsEnabled(OperationStartKey)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationStart)) { - diagnosticSource.Write(OperationStartKey, new OperationStartDiagnosticData(operation, initiatingRequest)); + diagnosticSource.Write(DiagnosticEventKeys.OperationStart, new OperationStartDiagnosticData(operation, initiatingRequest)); } } /// - /// Invoked when a resolve operation fails. + /// Writes a diagnostic event indicating a resolve operation has failed. /// /// The diagnostic source to which events will be written. /// The resolve operation that failed. /// The exception that caused the operation failure. + /// + /// A single operation can in turn invoke other full operations (as opposed to requests). Check + /// to know if you're looking at the entry operation. + /// public static void OperationFailure(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, Exception operationException) { - if (diagnosticSource.IsEnabled(OperationFailureKey)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationFailure)) { - diagnosticSource.Write(OperationFailureKey, new OperationFailureDiagnosticData(operation, operationException)); + diagnosticSource.Write(DiagnosticEventKeys.OperationFailure, new OperationFailureDiagnosticData(operation, operationException)); } } /// - /// Invoked when a resolve operation succeeds. You can check whether this operation was the top-level entry operation using - /// . + /// Writes a diagnostic event indicating a resolve operation has succeeded. /// /// The diagnostic source to which events will be written. /// The resolve operation that succeeded. /// The resolved instance providing the requested service. + /// + /// A single operation can in turn invoke other full operations (as opposed to requests). Check + /// to know if you're looking at the entry operation. + /// public static void OperationSuccess(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, object resolvedInstance) { - if (diagnosticSource.IsEnabled(OperationSuccessKey)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationSuccess)) { - diagnosticSource.Write(OperationSuccessKey, new OperationSuccessDiagnosticData(operation, resolvedInstance)); + diagnosticSource.Write(DiagnosticEventKeys.OperationSuccess, new OperationSuccessDiagnosticData(operation, resolvedInstance)); } } @@ -169,25 +162,25 @@ public static void OperationSuccess(this DiagnosticSource diagnosticSource, Reso [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool RequestDiagnosticsEnabled(this DiagnosticSource diagnosticSource) { - return diagnosticSource.IsEnabled(RequestStartKey) || diagnosticSource.IsEnabled(RequestSuccessKey) || diagnosticSource.IsEnabled(RequestFailureKey); + return diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestStart) || diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestSuccess) || diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestFailure); } /// - /// Invoked at the start of a single resolve request initiated from within an operation. + /// Writes a diagnostic event indicating a resolve request has started inside an operation. /// /// The diagnostic source to which events will be written. /// The pipeline resolve operation that this request is running within. - /// The context for the resolve request that is about to start. + /// The context for the resolve request that is about to start. public static void RequestStart(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext) { - if (diagnosticSource.IsEnabled(RequestStartKey)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestStart)) { - diagnosticSource.Write(RequestStartKey, new RequestDiagnosticData(operation, requestContext)); + diagnosticSource.Write(DiagnosticEventKeys.RequestStart, new RequestDiagnosticData(operation, requestContext)); } } /// - /// Invoked when a resolve request fails. + /// Writes a diagnostic event indicating a resolve request inside an operation has failed. /// /// The diagnostic source to which events will be written. /// The pipeline resolve operation that this request is running within. @@ -195,23 +188,23 @@ public static void RequestStart(this DiagnosticSource diagnosticSource, ResolveO /// The exception that caused the failure. public static void RequestFailure(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException) { - if (diagnosticSource.IsEnabled(RequestFailureKey)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestFailure)) { - diagnosticSource.Write(RequestFailureKey, new RequestFailureDiagnosticData(operation, requestContext, requestException)); + diagnosticSource.Write(DiagnosticEventKeys.RequestFailure, new RequestFailureDiagnosticData(operation, requestContext, requestException)); } } /// - /// Invoked when a resolve request succeeds. + /// Writes a diagnostic event indicating a resolve request inside an operation has succeeded. /// /// The diagnostic source to which events will be written. /// The pipeline resolve operation that this request is running within. /// The context for the resolve request that failed. public static void RequestSuccess(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext) { - if (diagnosticSource.IsEnabled(RequestSuccessKey)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestSuccess)) { - diagnosticSource.Write(RequestSuccessKey, new RequestDiagnosticData(operation, requestContext)); + diagnosticSource.Write(DiagnosticEventKeys.RequestSuccess, new RequestDiagnosticData(operation, requestContext)); } } } diff --git a/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs b/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs index 1878e0818..891a669f9 100644 --- a/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs +++ b/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs @@ -28,21 +28,80 @@ namespace Autofac.Core.Diagnostics { + /// + /// Base class for creating diagnostic tracers that follow Autofac diagnostic events. + /// + /// + /// + /// Following events from a + /// involves subscribing to that listener with an + /// where the observable is a of + /// event names and event data. + /// + /// + /// This class helps with that by providing strongly typed event handlers + /// pre-wired to convert the event data into the right object type, allowing you + /// to focus on the handling logic and not the event/data parsing. + /// + /// + /// While technically you could subscribe to non-Autofac events using this + /// observer, it's not a general purpose mechanism - it's very much tailored + /// to Autofac. + /// + /// + /// public abstract class DiagnosticTracerBase : IObserver> { + /// + /// The list of event names to which this observer is subscribed. + /// private readonly List _subscriptions = new List(); + /// + /// Subscribes the observer to a particular named diagnostic event. + /// + /// + /// The name of the event to which the observer should subscribe. Diagnostic + /// names are case-sensitive. + /// + /// + /// + /// By default the observer is not subscribed to any events. You must + /// opt-in to any events you wish to handle. You may use + /// to subscribe to all Autofac events at once. + /// + /// + /// + /// public virtual void Enable(string diagnosticName) { + if (diagnosticName == null) + { + throw new ArgumentNullException(nameof(diagnosticName)); + } + if (!_subscriptions.Contains(diagnosticName)) { _subscriptions.Add(diagnosticName); } } + /// + /// Subscribes the observer to all Autofac events. + /// + /// + /// + /// By default the observer is not subscribed to any events. You must + /// opt-in to any events you wish to handle. This method helps you + /// to quickly subscribe to all the Autofac events at once. You may use + /// to subscribe to individual events. + /// + /// + /// + /// public void EnableAll() { - _subscriptions.Add(DiagnosticEventKeys.MiddlewareEntry); + _subscriptions.Add(DiagnosticEventKeys.MiddlewareStart); _subscriptions.Add(DiagnosticEventKeys.MiddlewareFailure); _subscriptions.Add(DiagnosticEventKeys.MiddlewareSuccess); _subscriptions.Add(DiagnosticEventKeys.OperationFailure); @@ -53,11 +112,40 @@ public void EnableAll() _subscriptions.Add(DiagnosticEventKeys.RequestSuccess); } + /// + /// Unsubscribes the observer from a particular named diagnostic event. + /// + /// + /// The name of the event to which the observer should unsubscribe. Diagnostic + /// names are case-sensitive. + /// + /// + /// + /// By default the observer is not subscribed to any events. You must + /// opt-in to any events you wish to handle. You may use + /// to subscribe to all Autofac events at once, + /// or to subscribe to individual events. + /// + /// + /// + /// public virtual void Disable(string diagnosticName) { + if (diagnosticName == null) + { + throw new ArgumentNullException(nameof(diagnosticName)); + } + _subscriptions.Remove(diagnosticName); } + /// + /// Determines if this observer is enabled for listening to a specific + /// named event. + /// + /// + /// The name of the event to check. Diagnostic names are case-sensitive. + /// public virtual bool IsEnabled(string diagnosticName) { if (_subscriptions.Count == 0) @@ -68,92 +156,230 @@ public virtual bool IsEnabled(string diagnosticName) return _subscriptions.Contains(diagnosticName); } + /// + /// Notifies the observer that the provider has finished sending push-based notifications. + /// void IObserver>.OnCompleted() { - // Do nothing + // If there was something to dispose or clean up, here's where it would + // happen, but we don't have anything like that. } + /// + /// Notifies the observer that the provider has experienced an error condition. + /// + /// + /// An object that provides additional information about the error. + /// void IObserver>.OnError(Exception error) { - // Do nothing + // The internal diagnostic source isn't really going to experience an + // error condition (which is not the same as _reporting errors_) so + // there's nothing to do here. } + /// + /// Provides the observer with new data. + /// + /// + /// The current notification information. + /// void IObserver>.OnNext(KeyValuePair value) { + // This is what gets called when a new diagnostic event occurs. Write(value.Key, value.Value); } - public virtual void OnMiddlewareEntry(MiddlewareDiagnosticData data) + /// + /// Handles the event raised when middleware encounters an error. + /// + /// + /// Diagnostic data associated with the event. + /// + /// + /// + /// Derived classes can override this method and perform actions based + /// on the event. By default, the base class does nothing. + /// + /// + public virtual void OnMiddlewareFailure(MiddlewareDiagnosticData data) { } - public virtual void OnMiddlewareFailure(MiddlewareDiagnosticData data) + /// + /// Handles the event raised when middleware starts. + /// + /// + /// Diagnostic data associated with the event. + /// + /// + /// + /// Derived classes can override this method and perform actions based + /// on the event. By default, the base class does nothing. + /// + /// + public virtual void OnMiddlewareStart(MiddlewareDiagnosticData data) { } + /// + /// Handles the event raised when middleware exits successfully. + /// + /// + /// Diagnostic data associated with the event. + /// + /// + /// + /// Derived classes can override this method and perform actions based + /// on the event. By default, the base class does nothing. + /// + /// public virtual void OnMiddlewareSuccess(MiddlewareDiagnosticData data) { } + /// + /// Handles the event raised when a resolve operation encounters an error. + /// + /// + /// Diagnostic data associated with the event. + /// + /// + /// + /// Derived classes can override this method and perform actions based + /// on the event. By default, the base class does nothing. + /// + /// public virtual void OnOperationFailure(OperationFailureDiagnosticData data) { } + /// + /// Handles the event raised when a resolve operation starts. + /// + /// + /// Diagnostic data associated with the event. + /// + /// + /// + /// Derived classes can override this method and perform actions based + /// on the event. By default, the base class does nothing. + /// + /// public virtual void OnOperationStart(OperationStartDiagnosticData data) { } + /// + /// Handles the event raised when a resolve operation completes successfully. + /// + /// + /// Diagnostic data associated with the event. + /// + /// + /// + /// Derived classes can override this method and perform actions based + /// on the event. By default, the base class does nothing. + /// + /// public virtual void OnOperationSuccess(OperationSuccessDiagnosticData data) { } + /// + /// Handles the event raised when a resolve request encounters an error. + /// + /// + /// Diagnostic data associated with the event. + /// + /// + /// + /// Derived classes can override this method and perform actions based + /// on the event. By default, the base class does nothing. + /// + /// public virtual void OnRequestFailure(RequestFailureDiagnosticData data) { } + /// + /// Handles the event raised when a resolve request starts. + /// + /// + /// Diagnostic data associated with the event. + /// + /// + /// + /// Derived classes can override this method and perform actions based + /// on the event. By default, the base class does nothing. + /// + /// public virtual void OnRequestStart(RequestDiagnosticData data) { } + /// + /// Handles the event raised when a resolve request completes successfully. + /// + /// + /// Diagnostic data associated with the event. + /// + /// + /// + /// Derived classes can override this method and perform actions based + /// on the event. By default, the base class does nothing. + /// + /// public virtual void OnRequestSuccess(RequestDiagnosticData data) { } - public virtual void Write(string diagnosticName, object parameters) + /// + /// Handles inbound events and converts the diagnostic data to a + /// strongly-typed object that can be handled by the other methods + /// in this observer. + /// + /// + /// The name of the event that was raised. Diagnostic names are case-sensitive. + /// + /// + /// The diagnostic data associated with the event. + /// + public virtual void Write(string diagnosticName, object data) { - if (parameters == null || !IsEnabled(diagnosticName)) + if (data == null || !IsEnabled(diagnosticName)) { return; } switch (diagnosticName) { - case DiagnosticEventKeys.MiddlewareEntry: - OnMiddlewareEntry((MiddlewareDiagnosticData)parameters); + case DiagnosticEventKeys.MiddlewareStart: + OnMiddlewareStart((MiddlewareDiagnosticData)data); break; case DiagnosticEventKeys.MiddlewareFailure: - OnMiddlewareFailure((MiddlewareDiagnosticData)parameters); + OnMiddlewareFailure((MiddlewareDiagnosticData)data); break; case DiagnosticEventKeys.MiddlewareSuccess: - OnMiddlewareSuccess((MiddlewareDiagnosticData)parameters); + OnMiddlewareSuccess((MiddlewareDiagnosticData)data); break; case DiagnosticEventKeys.OperationFailure: - OnOperationFailure((OperationFailureDiagnosticData)parameters); + OnOperationFailure((OperationFailureDiagnosticData)data); break; case DiagnosticEventKeys.OperationStart: - OnOperationStart((OperationStartDiagnosticData)parameters); + OnOperationStart((OperationStartDiagnosticData)data); break; case DiagnosticEventKeys.OperationSuccess: - OnOperationSuccess((OperationSuccessDiagnosticData)parameters); + OnOperationSuccess((OperationSuccessDiagnosticData)data); break; case DiagnosticEventKeys.RequestFailure: - OnRequestFailure((RequestFailureDiagnosticData)parameters); + OnRequestFailure((RequestFailureDiagnosticData)data); break; case DiagnosticEventKeys.RequestStart: - OnRequestStart((RequestDiagnosticData)parameters); + OnRequestStart((RequestDiagnosticData)data); break; case DiagnosticEventKeys.RequestSuccess: - OnRequestSuccess((RequestDiagnosticData)parameters); + OnRequestSuccess((RequestDiagnosticData)data); break; default: break; diff --git a/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs b/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs index de83b7b3b..ec2aef6b9 100644 --- a/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs @@ -28,8 +28,17 @@ namespace Autofac.Core.Diagnostics { + /// + /// Diagnostic data associated with middleware events. + /// public class MiddlewareDiagnosticData { + /// + /// Initializes a new instance of the class. + /// + /// The pipeline resolve operation that this request is running within. + /// The context for the resolve request that is running. + /// The middleware that is running. public MiddlewareDiagnosticData(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) { Operation = operation; @@ -37,10 +46,19 @@ public MiddlewareDiagnosticData(ResolveOperationBase operation, ResolveRequestCo Middleware = middleware; } + /// + /// Gets the pipeline resolve operation that this request is running within. + /// public ResolveOperationBase Operation { get; private set; } + /// + /// Gets the context for the resolve request that is running. + /// public ResolveRequestContextBase RequestContext { get; private set; } + /// + /// Gets the middleware that is running. + /// public IResolveMiddleware Middleware { get; private set; } } } diff --git a/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs b/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs index 3094e5d88..ce1b3e739 100644 --- a/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs @@ -28,16 +28,30 @@ namespace Autofac.Core.Diagnostics { + /// + /// Diagnostic data associated with resolve operation failure events. + /// public class OperationFailureDiagnosticData { + /// + /// Initializes a new instance of the class. + /// + /// The resolve operation that failed. + /// The exception that caused the operation failure. public OperationFailureDiagnosticData(ResolveOperationBase operation, Exception operationException) { Operation = operation; OperationException = operationException; } + /// + /// Gets the resolve operation that failed. + /// public ResolveOperationBase Operation { get; private set; } + /// + /// Gets the exception that caused the operation failure. + /// public Exception OperationException { get; private set; } } } diff --git a/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs b/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs index e06a7a660..1090166c5 100644 --- a/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs @@ -27,16 +27,30 @@ namespace Autofac.Core.Diagnostics { + /// + /// Diagnostic data associated with resolve operation start events. + /// public class OperationStartDiagnosticData { + /// + /// Initializes a new instance of the class. + /// + /// The pipeline resolve operation that is about to run. + /// The request that is responsible for starting this operation. public OperationStartDiagnosticData(ResolveOperationBase operation, ResolveRequest initiatingRequest) { Operation = operation; InitiatingRequest = initiatingRequest; } + /// + /// Gets the pipeline resolve operation that is about to run. + /// public ResolveOperationBase Operation { get; private set; } + /// + /// Gets the request that is responsible for starting this operation. + /// public ResolveRequest InitiatingRequest { get; private set; } } } diff --git a/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs b/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs index 878ab5feb..1232ae525 100644 --- a/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs @@ -27,16 +27,30 @@ namespace Autofac.Core.Diagnostics { + /// + /// Diagnostic data associated with resolve operation success events. + /// public class OperationSuccessDiagnosticData { + /// + /// Initializes a new instance of the class. + /// + /// The resolve operation that succeeded. + /// The resolved instance providing the requested service. public OperationSuccessDiagnosticData(ResolveOperationBase operation, object resolvedInstance) { Operation = operation; ResolvedInstance = resolvedInstance; } + /// + /// Gets the resolve operation that succeeded. + /// public ResolveOperationBase Operation { get; private set; } + /// + /// Gets the resolved instance providing the requested service. + /// public object ResolvedInstance { get; private set; } } } diff --git a/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs b/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs index b25c1f9dc..dd20dc3d1 100644 --- a/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs @@ -28,16 +28,30 @@ namespace Autofac.Core.Diagnostics { + /// + /// Diagnostic data associated with resolve request events. + /// public class RequestDiagnosticData { + /// + /// Initializes a new instance of the class. + /// + /// The pipeline resolve operation that this request is running within. + /// The context for the resolve request that is running. public RequestDiagnosticData(ResolveOperationBase operation, ResolveRequestContextBase requestContext) { Operation = operation; RequestContext = requestContext; } + /// + /// Gets the pipeline resolve operation that this request is running within. + /// public ResolveOperationBase Operation { get; private set; } + /// + /// Gets the context for the resolve request that is running. + /// public ResolveRequestContextBase RequestContext { get; private set; } } } diff --git a/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs b/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs index 6eddd9073..aa1b055e0 100644 --- a/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs @@ -29,8 +29,17 @@ namespace Autofac.Core.Diagnostics { + /// + /// Diagnostic data associated with resolve request failure events. + /// public class RequestFailureDiagnosticData { + /// + /// Initializes a new instance of the class. + /// + /// The pipeline resolve operation that this request is running within. + /// The context for the resolve request that failed. + /// The exception that caused the failure. public RequestFailureDiagnosticData(ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException) { Operation = operation; @@ -38,10 +47,19 @@ public RequestFailureDiagnosticData(ResolveOperationBase operation, ResolveReque RequestException = requestException; } + /// + /// Gets the pipeline resolve operation that this request is running within. + /// public ResolveOperationBase Operation { get; private set; } + /// + /// Gets the context for the resolve request that failed. + /// public ResolveRequestContextBase RequestContext { get; private set; } + /// + /// Gets the exception that caused the failure. + /// public Exception RequestException { get; private set; } } } diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs index 27cebff3d..25a39a3ef 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs @@ -275,7 +275,7 @@ Action Chain(Action next, // Optimise the path depending on whether diagnostics are enabled. if (ctxt.DiagnosticSource.MiddlewareDiagnosticsEnabled()) { - ctxt.DiagnosticSource.MiddlewareEntry(ctxt.Operation, ctxt, stage); + ctxt.DiagnosticSource.MiddlewareStart(ctxt.Operation, ctxt, stage); var succeeded = false; try { @@ -285,7 +285,14 @@ Action Chain(Action next, } finally { - ctxt.DiagnosticSource.MiddlewareExit(ctxt.Operation, ctxt, stage, succeeded); + if (succeeded) + { + ctxt.DiagnosticSource.MiddlewareSuccess(ctxt.Operation, ctxt, stage); + } + else + { + ctxt.DiagnosticSource.MiddlewareFailure(ctxt.Operation, ctxt, stage); + } } } else diff --git a/src/Autofac/Core/Resolving/ResolveOperationBase.cs b/src/Autofac/Core/Resolving/ResolveOperationBase.cs index 811763f8c..f082377de 100644 --- a/src/Autofac/Core/Resolving/ResolveOperationBase.cs +++ b/src/Autofac/Core/Resolving/ResolveOperationBase.cs @@ -48,12 +48,11 @@ public abstract class ResolveOperationBase : IResolveOperation, ITracingIdentife /// /// The most nested scope in which to begin the operation. The operation /// can move upward to less nested scopes as components with wider sharing scopes are activated. - /// A pipeline tracer for the operation. protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope) { TracingId = this; IsTopLevelOperation = true; - CurrentScope = mostNestedLifetimeScope; + CurrentScope = mostNestedLifetimeScope ?? throw new ArgumentNullException(nameof(mostNestedLifetimeScope)); IsTopLevelOperation = true; DiagnosticSource = mostNestedLifetimeScope.DiagnosticSource; } diff --git a/src/Autofac/LifetimeScopeExtensions.cs b/src/Autofac/LifetimeScopeExtensions.cs deleted file mode 100644 index 52d964086..000000000 --- a/src/Autofac/LifetimeScopeExtensions.cs +++ /dev/null @@ -1,73 +0,0 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -using System; -using System.Diagnostics; -using Autofac.Core.Diagnostics; - -namespace Autofac -{ - /// - /// Extensions to the lifetime scope to provide convenience methods for tracing. - /// - public static class LifetimeScopeExtensions - { - /// - /// Enable tracing on this scope, routing trace events to the specified tracer. - /// All lifetime scopes created from this one will inherit this tracer as well. - /// - /// The lifetime scope. - /// A callback that will receive the trace output. - /// - /// Only one tracer is supported at once, so calling this will replace any prior tracer on this scope. Existing nested scopes - /// will continue to retain their original trace behaviour. - /// - public static void SubscribeToDiagnostics(this ILifetimeScope scope, DiagnosticTracerBase tracer) - { - if (scope is null) throw new ArgumentNullException(nameof(scope)); - if (tracer is null) throw new ArgumentNullException(nameof(tracer)); - scope.DiagnosticSource.Subscribe(tracer, tracer.IsEnabled); - } - - /// - /// Enable tracing on this scope, routing trace events to the specified tracer. - /// All lifetime scopes created from this one will inherit this tracer as well. - /// - /// The lifetime scope. - /// A callback that will receive the trace output. - /// - /// Only one tracer is supported at once, so calling this will replace any prior tracer on this scope. Existing nested scopes - /// will continue to retain their original trace behaviour. - /// - public static T SubscribeToDiagnostics(this ILifetimeScope scope) - where T : DiagnosticTracerBase, new() - { - if (scope is null) throw new ArgumentNullException(nameof(scope)); - var tracer = new T(); - scope.SubscribeToDiagnostics(tracer); - return tracer; - } - } -} diff --git a/test/Autofac.Specification.Test/Lifetime/InstancePerLifetimeScopeTests.cs b/test/Autofac.Specification.Test/Lifetime/InstancePerLifetimeScopeTests.cs index 06c663f51..a80ce0026 100644 --- a/test/Autofac.Specification.Test/Lifetime/InstancePerLifetimeScopeTests.cs +++ b/test/Autofac.Specification.Test/Lifetime/InstancePerLifetimeScopeTests.cs @@ -31,7 +31,7 @@ public void TypeAsInstancePerScope() _output.WriteLine(args.TraceContent); }; - lifetime.SubscribeToDiagnostics(tracer); + container.SubscribeToDiagnostics(tracer); var ctxA = lifetime.Resolve(); var ctxA2 = lifetime.Resolve(); diff --git a/test/Autofac.Test/Mocks.cs b/test/Autofac.Test/Mocks.cs index 28b2e2864..8e7ec6834 100644 --- a/test/Autofac.Test/Mocks.cs +++ b/test/Autofac.Test/Mocks.cs @@ -137,7 +137,7 @@ public override void OnRequestStart(RequestDiagnosticData data) RequestStarting?.Invoke(data.Operation, data.RequestContext); } - public override void OnMiddlewareEntry(MiddlewareDiagnosticData data) + public override void OnMiddlewareStart(MiddlewareDiagnosticData data) { EnteringMiddleware?.Invoke(data.Operation, data.RequestContext, data.Middleware); } From ac8a48d6229c9aa04fd2fff607365c41edb68171 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Thu, 9 Jul 2020 08:22:22 -0700 Subject: [PATCH 13/45] Tests for DiagnosticSourceExtensions. --- .../Diagnostics/DefaultDiagnosticTracer.cs | 6 +- .../Diagnostics/DiagnosticSourceExtensions.cs | 15 +- .../Diagnostics/MiddlewareDiagnosticData.cs | 10 +- .../Pipeline/ResolvePipelineBuilder.cs | 6 +- test/Autofac.Test/Autofac.Test.csproj | 1 + .../DiagnosticSourceExtensionsTests.cs | 299 ++++++++++++++++++ test/Autofac.Test/Mocks.cs | 10 +- 7 files changed, 318 insertions(+), 29 deletions(-) create mode 100644 test/Autofac.Test/Core/Diagnostics/DiagnosticSourceExtensionsTests.cs diff --git a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs index 0bed2f8aa..c72f444e4 100644 --- a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs @@ -110,7 +110,7 @@ public override void OnMiddlewareStart(MiddlewareDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.RequestContext.Operation.TracingId, out var builder)) { builder.AppendFormattedLine(TracerMessages.EnterMiddleware, data.Middleware.ToString()); builder.Indent(); @@ -125,7 +125,7 @@ public override void OnMiddlewareFailure(MiddlewareDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.RequestContext.Operation.TracingId, out var builder)) { builder.Outdent(); builder.AppendFormattedLine(TracerMessages.ExitMiddlewareFailure, data.Middleware.ToString()); @@ -140,7 +140,7 @@ public override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.RequestContext.Operation.TracingId, out var builder)) { builder.Outdent(); builder.AppendFormattedLine(TracerMessages.ExitMiddlewareSuccess, data.Middleware.ToString()); diff --git a/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs b/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs index 9674e7c3e..c9310b075 100644 --- a/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs +++ b/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs @@ -50,14 +50,13 @@ public static bool MiddlewareDiagnosticsEnabled(this DiagnosticSource diagnostic /// Writes a diagnostic event indicating an individual middleware item is about to execute (just before the method executes). /// /// The diagnostic source to which events will be written. - /// The pipeline resolve operation that this request is running within. /// The context for the resolve request that is running. /// The middleware that is about to run. - public static void MiddlewareStart(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + public static void MiddlewareStart(this DiagnosticSource diagnosticSource, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) { if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareStart)) { - diagnosticSource.Write(DiagnosticEventKeys.MiddlewareStart, new MiddlewareDiagnosticData(operation, requestContext, middleware)); + diagnosticSource.Write(DiagnosticEventKeys.MiddlewareStart, new MiddlewareDiagnosticData(requestContext, middleware)); } } @@ -65,14 +64,13 @@ public static void MiddlewareStart(this DiagnosticSource diagnosticSource, Resol /// Writes a diagnostic event indicating an individual middleware item has finished in an error state (when the method returns). /// /// The diagnostic source to which events will be written. - /// The pipeline resolve operation that this request is running within. /// The context for the resolve request that is running. /// The middleware that just ran. - public static void MiddlewareFailure(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + public static void MiddlewareFailure(this DiagnosticSource diagnosticSource, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) { if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareFailure)) { - diagnosticSource.Write(DiagnosticEventKeys.MiddlewareFailure, new MiddlewareDiagnosticData(operation, requestContext, middleware)); + diagnosticSource.Write(DiagnosticEventKeys.MiddlewareFailure, new MiddlewareDiagnosticData(requestContext, middleware)); } } @@ -80,14 +78,13 @@ public static void MiddlewareFailure(this DiagnosticSource diagnosticSource, Res /// Writes a diagnostic event indicating an individual middleware item has finished successfully (when the method returns). /// /// The diagnostic source to which events will be written. - /// The pipeline resolve operation that this request is running within. /// The context for the resolve request that is running. /// The middleware that just ran. - public static void MiddlewareSuccess(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + public static void MiddlewareSuccess(this DiagnosticSource diagnosticSource, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) { if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareSuccess)) { - diagnosticSource.Write(DiagnosticEventKeys.MiddlewareSuccess, new MiddlewareDiagnosticData(operation, requestContext, middleware)); + diagnosticSource.Write(DiagnosticEventKeys.MiddlewareSuccess, new MiddlewareDiagnosticData(requestContext, middleware)); } } diff --git a/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs b/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs index ec2aef6b9..f2380776a 100644 --- a/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs @@ -23,7 +23,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. -using Autofac.Core.Resolving; using Autofac.Core.Resolving.Pipeline; namespace Autofac.Core.Diagnostics @@ -36,21 +35,14 @@ public class MiddlewareDiagnosticData /// /// Initializes a new instance of the class. /// - /// The pipeline resolve operation that this request is running within. /// The context for the resolve request that is running. /// The middleware that is running. - public MiddlewareDiagnosticData(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + public MiddlewareDiagnosticData(ResolveRequestContextBase requestContext, IResolveMiddleware middleware) { - Operation = operation; RequestContext = requestContext; Middleware = middleware; } - /// - /// Gets the pipeline resolve operation that this request is running within. - /// - public ResolveOperationBase Operation { get; private set; } - /// /// Gets the context for the resolve request that is running. /// diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs index 25a39a3ef..10e1d721c 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs @@ -275,7 +275,7 @@ Action Chain(Action next, // Optimise the path depending on whether diagnostics are enabled. if (ctxt.DiagnosticSource.MiddlewareDiagnosticsEnabled()) { - ctxt.DiagnosticSource.MiddlewareStart(ctxt.Operation, ctxt, stage); + ctxt.DiagnosticSource.MiddlewareStart(ctxt, stage); var succeeded = false; try { @@ -287,11 +287,11 @@ Action Chain(Action next, { if (succeeded) { - ctxt.DiagnosticSource.MiddlewareSuccess(ctxt.Operation, ctxt, stage); + ctxt.DiagnosticSource.MiddlewareSuccess(ctxt, stage); } else { - ctxt.DiagnosticSource.MiddlewareFailure(ctxt.Operation, ctxt, stage); + ctxt.DiagnosticSource.MiddlewareFailure(ctxt, stage); } } } diff --git a/test/Autofac.Test/Autofac.Test.csproj b/test/Autofac.Test/Autofac.Test.csproj index d87264041..151684fe8 100644 --- a/test/Autofac.Test/Autofac.Test.csproj +++ b/test/Autofac.Test/Autofac.Test.csproj @@ -26,6 +26,7 @@ + all diff --git a/test/Autofac.Test/Core/Diagnostics/DiagnosticSourceExtensionsTests.cs b/test/Autofac.Test/Core/Diagnostics/DiagnosticSourceExtensionsTests.cs new file mode 100644 index 000000000..1ec8e507b --- /dev/null +++ b/test/Autofac.Test/Core/Diagnostics/DiagnosticSourceExtensionsTests.cs @@ -0,0 +1,299 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A1 PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Autofac.Core; +using Autofac.Core.Diagnostics; +using Autofac.Core.Resolving; +using Autofac.Core.Resolving.Pipeline; +using Moq; +using Xunit; + +namespace Autofac.Test.Core.Diagnostics +{ + public class DiagnosticSourceExtensionsTests + { + [InlineData(DiagnosticEventKeys.MiddlewareFailure)] + [InlineData(DiagnosticEventKeys.MiddlewareStart)] + [InlineData(DiagnosticEventKeys.MiddlewareSuccess)] + [Theory] + public void MiddlewareDiagnosticsEnabled_IsEnabled(string key) + { + var source = new DiagnosticListener("Autofac"); + var subscriber = new MockSubscriber(); + subscriber.Enable(key); + source.Subscribe(subscriber, subscriber.IsEnabled); + Assert.True(source.MiddlewareDiagnosticsEnabled()); + } + + [InlineData(DiagnosticEventKeys.OperationFailure)] + [InlineData(DiagnosticEventKeys.OperationStart)] + [InlineData(DiagnosticEventKeys.OperationSuccess)] + [InlineData(DiagnosticEventKeys.RequestFailure)] + [InlineData(DiagnosticEventKeys.RequestStart)] + [InlineData(DiagnosticEventKeys.RequestSuccess)] + [Theory] + public void MiddlewareDiagnosticsEnabled_IsNotEnabled(string key) + { + var source = new DiagnosticListener("Autofac"); + var subscriber = new MockSubscriber(); + subscriber.Enable(key); + source.Subscribe(subscriber, subscriber.IsEnabled); + Assert.False(source.MiddlewareDiagnosticsEnabled()); + } + + [InlineData(DiagnosticEventKeys.OperationFailure)] + [InlineData(DiagnosticEventKeys.OperationStart)] + [InlineData(DiagnosticEventKeys.OperationSuccess)] + [Theory] + public void OperationDiagnosticsEnabled_IsEnabled(string key) + { + var source = new DiagnosticListener("Autofac"); + var subscriber = new MockSubscriber(); + subscriber.Enable(key); + source.Subscribe(subscriber, subscriber.IsEnabled); + Assert.True(source.OperationDiagnosticsEnabled()); + } + + [InlineData(DiagnosticEventKeys.MiddlewareFailure)] + [InlineData(DiagnosticEventKeys.MiddlewareStart)] + [InlineData(DiagnosticEventKeys.MiddlewareSuccess)] + [InlineData(DiagnosticEventKeys.RequestFailure)] + [InlineData(DiagnosticEventKeys.RequestStart)] + [InlineData(DiagnosticEventKeys.RequestSuccess)] + [Theory] + public void OperationDiagnosticsEnabled_IsNotEnabled(string key) + { + var source = new DiagnosticListener("Autofac"); + var subscriber = new MockSubscriber(); + subscriber.Enable(key); + source.Subscribe(subscriber, subscriber.IsEnabled); + Assert.False(source.OperationDiagnosticsEnabled()); + } + + [InlineData(DiagnosticEventKeys.RequestFailure)] + [InlineData(DiagnosticEventKeys.RequestStart)] + [InlineData(DiagnosticEventKeys.RequestSuccess)] + [Theory] + public void RequestDiagnosticsEnabled_IsEnabled(string key) + { + var source = new DiagnosticListener("Autofac"); + var subscriber = new MockSubscriber(); + subscriber.Enable(key); + source.Subscribe(subscriber, subscriber.IsEnabled); + Assert.True(source.RequestDiagnosticsEnabled()); + } + + [InlineData(DiagnosticEventKeys.MiddlewareFailure)] + [InlineData(DiagnosticEventKeys.MiddlewareStart)] + [InlineData(DiagnosticEventKeys.MiddlewareSuccess)] + [InlineData(DiagnosticEventKeys.OperationFailure)] + [InlineData(DiagnosticEventKeys.OperationStart)] + [InlineData(DiagnosticEventKeys.OperationSuccess)] + [Theory] + public void RequestDiagnosticsEnabled_IsNotEnabled(string key) + { + var source = new DiagnosticListener("Autofac"); + var subscriber = new MockSubscriber(); + subscriber.Enable(key); + source.Subscribe(subscriber, subscriber.IsEnabled); + Assert.False(source.RequestDiagnosticsEnabled()); + } + + [Fact] + public void MiddlewareFailure_CorrectEventContent() + { + var source = new DiagnosticListener("Autofac"); + var subscriber = new MockSubscriber(); + subscriber.Enable(DiagnosticEventKeys.MiddlewareFailure); + source.Subscribe(subscriber, subscriber.IsEnabled); + + var context = MockResolveRequestContext(); + source.MiddlewareFailure(context, Mock.Of()); + var e = Assert.Single(subscriber.Events); + Assert.Equal(DiagnosticEventKeys.MiddlewareFailure, e.Key); + Assert.IsType(e.Value); + } + + [Fact] + public void MiddlewareStart_CorrectEventContent() + { + var source = new DiagnosticListener("Autofac"); + var subscriber = new MockSubscriber(); + subscriber.Enable(DiagnosticEventKeys.MiddlewareStart); + source.Subscribe(subscriber, subscriber.IsEnabled); + + var context = MockResolveRequestContext(); + source.MiddlewareStart(context, Mock.Of()); + var e = Assert.Single(subscriber.Events); + Assert.Equal(DiagnosticEventKeys.MiddlewareStart, e.Key); + Assert.IsType(e.Value); + } + + [Fact] + public void MiddlewareSuccess_CorrectEventContent() + { + var source = new DiagnosticListener("Autofac"); + var subscriber = new MockSubscriber(); + subscriber.Enable(DiagnosticEventKeys.MiddlewareSuccess); + source.Subscribe(subscriber, subscriber.IsEnabled); + + var context = MockResolveRequestContext(); + source.MiddlewareSuccess(context, Mock.Of()); + var e = Assert.Single(subscriber.Events); + Assert.Equal(DiagnosticEventKeys.MiddlewareSuccess, e.Key); + Assert.IsType(e.Value); + } + + [Fact] + public void OperationFailure_CorrectEventContent() + { + var source = new DiagnosticListener("Autofac"); + var subscriber = new MockSubscriber(); + subscriber.Enable(DiagnosticEventKeys.OperationFailure); + source.Subscribe(subscriber, subscriber.IsEnabled); + + var operation = MockResolveOperation(); + source.OperationFailure(operation, new DivideByZeroException()); + var e = Assert.Single(subscriber.Events); + Assert.Equal(DiagnosticEventKeys.OperationFailure, e.Key); + Assert.IsType(e.Value); + } + + [Fact] + public void OperationStart_CorrectEventContent() + { + var source = new DiagnosticListener("Autofac"); + var subscriber = new MockSubscriber(); + subscriber.Enable(DiagnosticEventKeys.OperationStart); + source.Subscribe(subscriber, subscriber.IsEnabled); + + var operation = MockResolveOperation(); + var request = MockResolveRequest(); + source.OperationStart(operation, request); + var e = Assert.Single(subscriber.Events); + Assert.Equal(DiagnosticEventKeys.OperationStart, e.Key); + Assert.IsType(e.Value); + } + + [Fact] + public void OperationSuccess_CorrectEventContent() + { + var source = new DiagnosticListener("Autofac"); + var subscriber = new MockSubscriber(); + subscriber.Enable(DiagnosticEventKeys.OperationSuccess); + source.Subscribe(subscriber, subscriber.IsEnabled); + + var operation = MockResolveOperation(); + source.OperationSuccess(operation, "instance"); + var e = Assert.Single(subscriber.Events); + Assert.Equal(DiagnosticEventKeys.OperationSuccess, e.Key); + Assert.IsType(e.Value); + } + + [Fact] + public void RequestFailure_CorrectEventContent() + { + var source = new DiagnosticListener("Autofac"); + var subscriber = new MockSubscriber(); + subscriber.Enable(DiagnosticEventKeys.RequestFailure); + source.Subscribe(subscriber, subscriber.IsEnabled); + + var operation = MockResolveOperation(); + var context = MockResolveRequestContext(); + source.RequestFailure(operation, context, new DivideByZeroException()); + var e = Assert.Single(subscriber.Events); + Assert.Equal(DiagnosticEventKeys.RequestFailure, e.Key); + Assert.IsType(e.Value); + } + + [Fact] + public void RequestStart_CorrectEventContent() + { + var source = new DiagnosticListener("Autofac"); + var subscriber = new MockSubscriber(); + subscriber.Enable(DiagnosticEventKeys.RequestStart); + source.Subscribe(subscriber, subscriber.IsEnabled); + + var operation = MockResolveOperation(); + var context = MockResolveRequestContext(); + source.RequestStart(operation, context); + var e = Assert.Single(subscriber.Events); + Assert.Equal(DiagnosticEventKeys.RequestStart, e.Key); + Assert.IsType(e.Value); + } + + [Fact] + public void RequestSuccess_CorrectEventContent() + { + var source = new DiagnosticListener("Autofac"); + var subscriber = new MockSubscriber(); + subscriber.Enable(DiagnosticEventKeys.RequestSuccess); + source.Subscribe(subscriber, subscriber.IsEnabled); + + var operation = MockResolveOperation(); + var context = MockResolveRequestContext(); + source.RequestSuccess(operation, context); + var e = Assert.Single(subscriber.Events); + Assert.Equal(DiagnosticEventKeys.RequestSuccess, e.Key); + Assert.IsType(e.Value); + } + + private static ResolveOperation MockResolveOperation() + { + var container = new ContainerBuilder().Build(); + var scope = container.BeginLifetimeScope() as ISharingLifetimeScope; + return new ResolveOperation(scope); + } + + private static ResolveRequest MockResolveRequest() + { + return new ResolveRequest( + new TypedService(typeof(string)), + new ServiceRegistration(Mock.Of(), Mock.Of()), + Enumerable.Empty()); + } + + private static ResolveRequestContext MockResolveRequestContext() + { + var operation = MockResolveOperation(); + var request = MockResolveRequest(); + return new ResolveRequestContext(operation, request, operation.CurrentScope); + } + + private class MockSubscriber : DiagnosticTracerBase + { + public List> Events { get; } = new List>(); + + public override void Write(string diagnosticName, object data) + { + Events.Add(new KeyValuePair(diagnosticName, data)); + } + } + } +} diff --git a/test/Autofac.Test/Mocks.cs b/test/Autofac.Test/Mocks.cs index 8e7ec6834..4c0b08baa 100644 --- a/test/Autofac.Test/Mocks.cs +++ b/test/Autofac.Test/Mocks.cs @@ -115,9 +115,9 @@ public MockTracer() public event Action RequestStarting; - public event Action EnteringMiddleware; + public event Action EnteringMiddleware; - public event Action ExitingMiddleware; + public event Action ExitingMiddleware; public event Action RequestFailing; @@ -139,17 +139,17 @@ public override void OnRequestStart(RequestDiagnosticData data) public override void OnMiddlewareStart(MiddlewareDiagnosticData data) { - EnteringMiddleware?.Invoke(data.Operation, data.RequestContext, data.Middleware); + EnteringMiddleware?.Invoke(data.RequestContext, data.Middleware); } public override void OnMiddlewareFailure(MiddlewareDiagnosticData data) { - ExitingMiddleware?.Invoke(data.Operation, data.RequestContext, data.Middleware, false); + ExitingMiddleware?.Invoke(data.RequestContext, data.Middleware, false); } public override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) { - ExitingMiddleware?.Invoke(data.Operation, data.RequestContext, data.Middleware, true); + ExitingMiddleware?.Invoke(data.RequestContext, data.Middleware, true); } public override void OnRequestFailure(RequestFailureDiagnosticData data) From 1b3a2d924613f3a442b5fb2ebf7f040a8b1df281 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Thu, 9 Jul 2020 08:31:49 -0700 Subject: [PATCH 14/45] Tests for DiagnosticTracerBase. --- .../Diagnostics/DiagnosticTracerBaseTests.cs | 48 +++++++++++++ .../Diagnostics/DiagnosticTracerBaseTests.cs | 70 +++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 test/Autofac.Specification.Test/Diagnostics/DiagnosticTracerBaseTests.cs create mode 100644 test/Autofac.Test/Core/Diagnostics/DiagnosticTracerBaseTests.cs diff --git a/test/Autofac.Specification.Test/Diagnostics/DiagnosticTracerBaseTests.cs b/test/Autofac.Specification.Test/Diagnostics/DiagnosticTracerBaseTests.cs new file mode 100644 index 000000000..c6bae13a8 --- /dev/null +++ b/test/Autofac.Specification.Test/Diagnostics/DiagnosticTracerBaseTests.cs @@ -0,0 +1,48 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A1 PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using Autofac.Core.Diagnostics; +using Xunit; + +namespace Autofac.Specification.Test.Diagnostics +{ + public class DiagnosticTracerBaseTests + { + [Fact] + public void Enable_Disable() + { + var tracer = new TestTracer(); + Assert.False(tracer.IsEnabled("TestEvent")); + tracer.Enable("TestEvent"); + Assert.True(tracer.IsEnabled("TestEvent")); + tracer.Disable("TestEvent"); + Assert.False(tracer.IsEnabled("TestEvent")); + } + + private class TestTracer : DiagnosticTracerBase + { + } + } +} diff --git a/test/Autofac.Test/Core/Diagnostics/DiagnosticTracerBaseTests.cs b/test/Autofac.Test/Core/Diagnostics/DiagnosticTracerBaseTests.cs new file mode 100644 index 000000000..5ba809754 --- /dev/null +++ b/test/Autofac.Test/Core/Diagnostics/DiagnosticTracerBaseTests.cs @@ -0,0 +1,70 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A1 PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using Autofac.Core.Diagnostics; +using Xunit; + +namespace Autofac.Test.Core.Diagnostics +{ + public class DiagnosticTracerBaseTests + { + [InlineData(DiagnosticEventKeys.MiddlewareStart)] + [InlineData(DiagnosticEventKeys.MiddlewareFailure)] + [InlineData(DiagnosticEventKeys.MiddlewareSuccess)] + [InlineData(DiagnosticEventKeys.OperationFailure)] + [InlineData(DiagnosticEventKeys.OperationStart)] + [InlineData(DiagnosticEventKeys.OperationSuccess)] + [InlineData(DiagnosticEventKeys.RequestFailure)] + [InlineData(DiagnosticEventKeys.RequestStart)] + [InlineData(DiagnosticEventKeys.RequestSuccess)] + [Theory] + public void EnableAll_SubscribesToAllEvents(string key) + { + var tracer = new TestTracer(); + tracer.EnableAll(); + Assert.True(tracer.IsEnabled(key)); + } + + [InlineData(DiagnosticEventKeys.MiddlewareStart)] + [InlineData(DiagnosticEventKeys.MiddlewareFailure)] + [InlineData(DiagnosticEventKeys.MiddlewareSuccess)] + [InlineData(DiagnosticEventKeys.OperationFailure)] + [InlineData(DiagnosticEventKeys.OperationStart)] + [InlineData(DiagnosticEventKeys.OperationSuccess)] + [InlineData(DiagnosticEventKeys.RequestFailure)] + [InlineData(DiagnosticEventKeys.RequestStart)] + [InlineData(DiagnosticEventKeys.RequestSuccess)] + [Theory] + public void IsEnabled_NotSubscribedByDefault(string key) + { + var tracer = new TestTracer(); + Assert.False(tracer.IsEnabled(key)); + } + + private class TestTracer : DiagnosticTracerBase + { + } + } +} From 6875d1204f08477ee0abf70ca98631960d530b5c Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Thu, 9 Jul 2020 08:32:49 -0700 Subject: [PATCH 15/45] PSScriptAnalyzer null check fix. --- build.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.ps1 b/build.ps1 index c46f3e1b1..00ae039c7 100644 --- a/build.ps1 +++ b/build.ps1 @@ -29,8 +29,8 @@ if ($isWindows) { & dotnet --info # Set version suffix -$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL]; -$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL]; +$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$NULL -ne $env:APPVEYOR_REPO_BRANCH]; +$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$NULL -ne $env:APPVEYOR_BUILD_NUMBER]; $versionSuffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "master" -and $revision -ne "local"] Write-Message "Package version suffix is '$versionSuffix'" From e0c320c465c0d5e83e20e70613a288f5b7eaf3d0 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 10 Jul 2020 10:06:13 -0700 Subject: [PATCH 16/45] "Full operation" tracing base class, common to default and DOT generator. --- .../Diagnostics/DefaultDiagnosticTracer.cs | 31 +- .../Core/Diagnostics/DotDiagnosticTracer.cs | 412 ++++++++++++++++++ .../FullOperationDiagnosticTracerBase.cs | 66 +++ .../OperationTraceCompletedArgs.cs | 2 +- .../DefaultDiagnosticTracerTests.cs | 27 +- .../Diagnostics/DotDiagnosticTracerTests.cs | 157 +++++++ .../Diagnostics/DotDiagnosticTracerTests.cs | 34 ++ .../FullOperationDiagnosticTracerBaseTests.cs | 59 +++ 8 files changed, 762 insertions(+), 26 deletions(-) create mode 100644 src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs create mode 100644 src/Autofac/Core/Diagnostics/FullOperationDiagnosticTracerBase.cs create mode 100644 test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs create mode 100644 test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs create mode 100644 test/Autofac.Test/Core/Diagnostics/FullOperationDiagnosticTracerBaseTests.cs diff --git a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs index c72f444e4..7ea070a16 100644 --- a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs @@ -9,8 +9,8 @@ namespace Autofac.Core.Diagnostics /// /// Provides a default resolve pipeline tracer that builds a multi-line /// string describing the end-to-end operation flow. Attach to the - /// event to receive notifications when - /// new trace content is available. + /// + /// event to receive notifications when new trace content is available. /// /// /// @@ -19,7 +19,7 @@ namespace Autofac.Core.Diagnostics /// logical activity can be captured. /// /// - public class DefaultDiagnosticTracer : DiagnosticTracerBase + public class DefaultDiagnosticTracer : FullOperationDiagnosticTracerBase { private const string RequestExceptionTraced = "__RequestException"; @@ -31,27 +31,10 @@ public class DefaultDiagnosticTracer : DiagnosticTracerBase /// Initializes a new instance of the class. /// public DefaultDiagnosticTracer() + : base() { - EnableAll(); } - /// - public override void Enable(string diagnosticName) - { - // Do nothing. Default is always enabled for everything. - } - - /// - public override void Disable(string diagnosticName) - { - // Do nothing. Default is always enabled for everything. - } - - /// - /// Event raised when a resolve operation completes and trace data is available. - /// - public event EventHandler? OperationCompleted; - /// /// Gets the number of operations in progress being traced. /// @@ -59,7 +42,7 @@ public override void Disable(string diagnosticName) /// An with the number of trace IDs associated /// with in-progress operations being traced by this tracer. /// - public int OperationsInProgress => _operationBuilders.Count; + public override int OperationsInProgress => _operationBuilders.Count; /// public override void OnOperationStart(OperationStartDiagnosticData data) @@ -214,7 +197,7 @@ public override void OnOperationFailure(OperationFailureDiagnosticData data) // If we're completing the root operation, raise the event. if (data.Operation.IsTopLevelOperation) { - OperationCompleted?.Invoke(this, new OperationTraceCompletedArgs(data.Operation, builder.ToString())); + OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); } } finally @@ -243,7 +226,7 @@ public override void OnOperationSuccess(OperationSuccessDiagnosticData data) // If we're completing the root operation, raise the event. if (data.Operation.IsTopLevelOperation) { - OperationCompleted?.Invoke(this, new OperationTraceCompletedArgs(data.Operation, builder.ToString())); + OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); } } finally diff --git a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs new file mode 100644 index 000000000..9d05c3d2f --- /dev/null +++ b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs @@ -0,0 +1,412 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using Autofac.Core.Resolving; + +namespace Autofac.Core.Diagnostics +{ + /// + /// Provides a resolve pipeline tracer that generates DOT graph output + /// traces for an end-to-end operation flow. Attach to the + /// + /// event to receive notifications when a new graph is available. + /// + /// + /// + /// The tracer subscribes to all Autofac diagnostic events and can't be + /// unsubscribed. This is required to ensure beginning and end of each + /// logical activity can be captured. + /// + /// + public class DotDiagnosticTracer : FullOperationDiagnosticTracerBase + { + private const string RequestExceptionTraced = "__RequestException"; + + private readonly ConcurrentDictionary _operationBuilders = new ConcurrentDictionary(); + + /// + /// Initializes a new instance of the class. + /// + public DotDiagnosticTracer() + : base() + { + } + + /// + /// Gets the number of operations in progress being traced. + /// + /// + /// An with the number of trace IDs associated + /// with in-progress operations being traced by this tracer. + /// + public override int OperationsInProgress => _operationBuilders.Count; + + /// + public override void OnOperationStart(OperationStartDiagnosticData data) + { + if (data is null) + { + return; + } + + var builder = _operationBuilders.GetOrAdd(data.Operation.TracingId, k => new DotGraphBuilder()); + } + + /// + public override void OnRequestStart(RequestDiagnosticData data) + { + if (data is null) + { + return; + } + + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + { + builder.OnRequestStart( + data.RequestContext.Service.ToString(), + data.RequestContext.Registration.Activator.DisplayName(), + data.RequestContext.DecoratorTarget?.Activator.DisplayName()); + } + } + + /// + public override void OnMiddlewareStart(MiddlewareDiagnosticData data) + { + if (data is null) + { + return; + } + + if (_operationBuilders.TryGetValue(data.RequestContext.Operation.TracingId, out var builder)) + { + builder.OnMiddlewareStart(data.Middleware.ToString()); + } + } + + /// + public override void OnMiddlewareFailure(MiddlewareDiagnosticData data) + { + if (data is null) + { + return; + } + + if (_operationBuilders.TryGetValue(data.RequestContext.Operation.TracingId, out var builder)) + { + builder.OnMiddlewareFailure(); + } + } + + /// + public override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) + { + if (data is null) + { + return; + } + + if (_operationBuilders.TryGetValue(data.RequestContext.Operation.TracingId, out var builder)) + { + builder.OnMiddlewareSuccess(); + } + } + + /// + public override void OnRequestFailure(RequestFailureDiagnosticData data) + { + if (data is null) + { + return; + } + + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + { + var requestException = data.RequestException; + if (requestException is DependencyResolutionException && requestException.InnerException is object) + { + requestException = requestException.InnerException; + } + + if (requestException.Data.Contains(RequestExceptionTraced)) + { + builder.OnRequestFailure(null); + } + else + { + builder.OnRequestFailure(requestException); + } + + requestException.Data[RequestExceptionTraced] = true; + } + } + + /// + public override void OnRequestSuccess(RequestDiagnosticData data) + { + if (data is null) + { + return; + } + + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + { + builder.OnRequestSuccess(data.RequestContext.Instance?.GetType().ToString()); + } + } + + /// + public override void OnOperationFailure(OperationFailureDiagnosticData data) + { + if (data is null) + { + return; + } + + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + { + try + { + builder.OnOperationFailure(data.OperationException); + + // If we're completing the root operation, raise the event. + if (data.Operation.IsTopLevelOperation) + { + OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); + } + } + finally + { + _operationBuilders.TryRemove(data.Operation.TracingId, out var _); + } + } + } + + /// + public override void OnOperationSuccess(OperationSuccessDiagnosticData data) + { + if (data is null) + { + return; + } + + if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + { + try + { + builder.OnOperationSuccess(data.ResolvedInstance?.GetType().ToString()); + + // If we're completing the root operation, raise the event. + if (data.Operation.IsTopLevelOperation) + { + OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); + } + } + finally + { + _operationBuilders.TryRemove(data.Operation.TracingId, out var _); + } + } + } + + private abstract class DotGraphNode + { + private const int IndentSize = 2; + + public string Id { get; } = "n" + Guid.NewGuid().ToString("N"); + + public List Children { get; } = new List(); + + public bool Success { get; set; } + + protected static void AppendIndent(int indent, StringBuilder stringBuilder) + { + stringBuilder.Append(' ', indent * IndentSize); + } + + public virtual void ToString(int indent, StringBuilder stringBuilder) + { + foreach (var child in Children) + { + child.ToString(indent + 1, stringBuilder); + } + } + } + + private class ResolveRequestNode : DotGraphNode + { + public ResolveRequestNode(string service, string component) + { + Service = service; + Component = component; + } + + public string Service { get; private set; } + + public string Component { get; private set; } + + public string? DecoratorTarget { get; set; } + + public Exception? Exception { get; set; } + + public string? InstanceType { get; set; } + + public override void ToString(int indent, StringBuilder stringBuilder) + { + AppendIndent(indent, stringBuilder); + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} [label=\"Resolve Request\"]", Id); + stringBuilder.AppendLine(); + foreach (var child in Children) + { + AppendIndent(indent, stringBuilder); + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} -> {1}", Id, child.Id); + stringBuilder.AppendLine(); + } + + base.ToString(indent, stringBuilder); + } + } + + private class OperationNode : DotGraphNode + { + public Exception? Exception { get; set; } + + public string? InstanceType { get; set; } + + public override void ToString(int indent, StringBuilder stringBuilder) + { + AppendIndent(indent, stringBuilder); + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} [label=\"Operation\"]", Id); + stringBuilder.AppendLine(); + foreach (var child in Children) + { + AppendIndent(indent, stringBuilder); + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} -> {1}", Id, child.Id); + stringBuilder.AppendLine(); + } + + base.ToString(indent, stringBuilder); + } + } + + private class MiddlewareNode : DotGraphNode + { + public MiddlewareNode(string name) + { + Name = name; + } + + public string Name { get; } + + public override void ToString(int indent, StringBuilder stringBuilder) + { + AppendIndent(indent, stringBuilder); + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} [label=\"Middleware\"]", Id); + stringBuilder.AppendLine(); + foreach (var child in Children) + { + AppendIndent(indent, stringBuilder); + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} -> {1}", Id, child.Id); + stringBuilder.AppendLine(); + } + + base.ToString(indent, stringBuilder); + } + } + + /// + /// Generator for DOT format graph traces. + /// + private class DotGraphBuilder + { + // https://www.graphviz.org/pdf/dotguide.pdf + // TODO: Wrong tree structure - middleware calls middleware, not request calls all middleware. + // TODO: Try to render the middleware as a stack that can spawn other requests. + // TODO: Bold on failure paths. + // TODO: Actually put real info into the graph (errors, names) + // TODO: Different shapes per thing - operation, request, MW. + public OperationNode Root { get; private set; } + + public Stack Operations { get; } = new Stack(); + + public Stack ResolveRequests { get; } = new Stack(); + + public Stack Middlewares { get; } = new Stack(); + + public DotGraphBuilder() + { + Root = new OperationNode(); + Operations.Push(Root); + } + + public void OnOperationFailure(Exception? operationException) + { + var operation = Operations.Pop(); + operation.Success = false; + operation.Exception = operationException; + } + + public void OnOperationSuccess(string? instanceType) + { + var operation = Operations.Pop(); + operation.Success = true; + operation.InstanceType = instanceType; + } + + public void OnRequestStart(string service, string component, string? decoratorTarget) + { + var request = new ResolveRequestNode(service, component); + if (decoratorTarget is object) + { + request.DecoratorTarget = decoratorTarget; + } + + Operations.Peek().Children.Add(request); + ResolveRequests.Push(request); + } + + public void OnRequestFailure(Exception? requestException) + { + var request = ResolveRequests.Pop(); + request.Success = false; + request.Exception = requestException; + } + + public void OnRequestSuccess(string? instanceType) + { + var request = ResolveRequests.Pop(); + request.Success = true; + request.InstanceType = instanceType; + } + + public void OnMiddlewareStart(string middleware) + { + var mw = new MiddlewareNode(middleware); + ResolveRequests.Peek().Children.Add(mw); + Middlewares.Push(mw); + } + + public void OnMiddlewareFailure() + { + var mw = Middlewares.Pop(); + mw.Success = false; + } + + public void OnMiddlewareSuccess() + { + var mw = Middlewares.Pop(); + mw.Success = true; + } + + public override string ToString() + { + var builder = new StringBuilder(); + builder.AppendLine("digraph G {"); + Root.ToString(1, builder); + builder.AppendLine("}"); + return builder.ToString(); + } + } + } +} diff --git a/src/Autofac/Core/Diagnostics/FullOperationDiagnosticTracerBase.cs b/src/Autofac/Core/Diagnostics/FullOperationDiagnosticTracerBase.cs new file mode 100644 index 000000000..0c8651930 --- /dev/null +++ b/src/Autofac/Core/Diagnostics/FullOperationDiagnosticTracerBase.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Concurrent; +using System.Globalization; +using System.Text; +using Autofac.Core.Resolving; + +namespace Autofac.Core.Diagnostics +{ + /// + /// Base class for tracers that require all operations for logical operation tracing. + /// + /// + /// + /// Derived classes will be subscribed to all Autofac diagnostic events + /// and will raise an + /// event when a logical operation has finished and trace data is available. + /// + /// + public abstract class FullOperationDiagnosticTracerBase : DiagnosticTracerBase + { + /// + /// Initializes a new instance of the class. + /// + protected FullOperationDiagnosticTracerBase() + { + EnableAll(); + } + + /// + public override void Enable(string diagnosticName) + { + // Do nothing. Default is always enabled for everything. + } + + /// + public override void Disable(string diagnosticName) + { + // Do nothing. Default is always enabled for everything. + } + + /// + /// Event raised when a resolve operation completes and trace data is available. + /// + public event EventHandler? OperationCompleted; + + /// + /// Gets the number of operations in progress being traced. + /// + /// + /// An with the number of trace IDs associated + /// with in-progress operations being traced by this tracer. + /// + public abstract int OperationsInProgress { get; } + + /// + /// Invokes the event. + /// + /// + /// The arguments to provide in the raised event. + /// + protected virtual void OnOperationCompleted(OperationTraceCompletedArgs args) + { + OperationCompleted?.Invoke(this, args); + } + } +} diff --git a/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs b/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs index 7818d6d86..04b871d6b 100644 --- a/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs +++ b/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs @@ -28,7 +28,7 @@ namespace Autofac.Core.Diagnostics { /// - /// Event data for the event. + /// Event data for the event. /// public sealed class OperationTraceCompletedArgs { diff --git a/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs b/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs index 9fcc7629e..f2ea452ae 100644 --- a/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs +++ b/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs @@ -1,4 +1,29 @@ -using System; +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A1 PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; using Autofac.Core.Diagnostics; using Xunit; diff --git a/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs b/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs new file mode 100644 index 000000000..1d58ca6bd --- /dev/null +++ b/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs @@ -0,0 +1,157 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A1 PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; +using Autofac.Core.Diagnostics; +using Xunit; + +namespace Autofac.Specification.Test.Diagnostics +{ + public class DotDiagnosticTracerTests + { + [Fact] + public void DiagnosticTracerRaisesEvents() + { + var tracer = new DotDiagnosticTracer(); + + var containerBuilder = new ContainerBuilder(); + containerBuilder.Register(ctxt => "Hello"); + + var container = containerBuilder.Build(); + + container.SubscribeToDiagnostics(tracer); + + string lastOpResult = null; + + tracer.OperationCompleted += (sender, args) => + { + Assert.Same(tracer, sender); + lastOpResult = args.TraceContent; + }; + + container.Resolve(); + + Assert.Contains("Hello", lastOpResult); + } + + [Fact] + public void DiagnosticTracerRaisesEventsOnError() + { + var tracer = new DotDiagnosticTracer(); + + var containerBuilder = new ContainerBuilder(); + containerBuilder.Register(ctxt => throw new InvalidOperationException()); + + var container = containerBuilder.Build(); + + container.SubscribeToDiagnostics(tracer); + + string lastOpResult = null; + + tracer.OperationCompleted += (sender, args) => + { + Assert.Same(tracer, sender); + lastOpResult = args.TraceContent; + }; + + try + { + container.Resolve(); + } + catch + { + } + + Assert.Contains(nameof(InvalidOperationException), lastOpResult); + } + + [Fact] + public void DiagnosticTracerDoesNotRaiseAnEventOnNestedOperations() + { + var tracer = new DotDiagnosticTracer(); + + var containerBuilder = new ContainerBuilder(); + containerBuilder.RegisterType().As(); + containerBuilder.RegisterDecorator(); + + var container = containerBuilder.Build(); + + container.SubscribeToDiagnostics(tracer); + + int traceCount = 0; + string lastOpResult = null; + + tracer.OperationCompleted += (sender, args) => + { + Assert.Same(tracer, sender); + lastOpResult = args.TraceContent; + traceCount++; + }; + + container.Resolve(); + + // Only a single trace (despite the nested operations). + Assert.Equal(1, traceCount); + Assert.Contains("Decorator", lastOpResult); + } + + [Fact] + public void DiagnosticTracerDoesNotLeakMemory() + { + var tracer = new DotDiagnosticTracer(); + + var containerBuilder = new ContainerBuilder(); + containerBuilder.RegisterType().As(); + containerBuilder.RegisterDecorator(); + + var container = containerBuilder.Build(); + + container.SubscribeToDiagnostics(tracer); + container.Resolve(); + + // The dictionary of tracked trace IDs and + // string builders should be empty. + Assert.Equal(0, tracer.OperationsInProgress); + } + + private interface IService + { + } + + private class Decorator : IService + { + public Decorator(IService decorated) + { + Decorated = decorated; + } + + public IService Decorated { get; } + } + + private class Implementor : IService + { + } + } +} diff --git a/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs b/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs new file mode 100644 index 000000000..79599e236 --- /dev/null +++ b/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs @@ -0,0 +1,34 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A1 PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using Autofac.Core.Diagnostics; +using Xunit; + +namespace Autofac.Test.Core.Diagnostics +{ + public class DotDiagnosticTracerTests + { + } +} diff --git a/test/Autofac.Test/Core/Diagnostics/FullOperationDiagnosticTracerBaseTests.cs b/test/Autofac.Test/Core/Diagnostics/FullOperationDiagnosticTracerBaseTests.cs new file mode 100644 index 000000000..adf15eac8 --- /dev/null +++ b/test/Autofac.Test/Core/Diagnostics/FullOperationDiagnosticTracerBaseTests.cs @@ -0,0 +1,59 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A1 PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using Autofac.Core.Diagnostics; +using Xunit; + +namespace Autofac.Test.Core.Diagnostics +{ + public class FullOperationDiagnosticTracerBaseTests + { + [InlineData(DiagnosticEventKeys.MiddlewareStart)] + [InlineData(DiagnosticEventKeys.MiddlewareFailure)] + [InlineData(DiagnosticEventKeys.MiddlewareSuccess)] + [InlineData(DiagnosticEventKeys.OperationFailure)] + [InlineData(DiagnosticEventKeys.OperationStart)] + [InlineData(DiagnosticEventKeys.OperationSuccess)] + [InlineData(DiagnosticEventKeys.RequestFailure)] + [InlineData(DiagnosticEventKeys.RequestStart)] + [InlineData(DiagnosticEventKeys.RequestSuccess)] + [Theory] + public void Ctor_SubscribesToAllEvents(string key) + { + var tracer = new TestTracer(); + Assert.True(tracer.IsEnabled(key)); + } + + private class TestTracer : FullOperationDiagnosticTracerBase + { + public TestTracer() + : base() + { + } + + public override int OperationsInProgress => throw new System.NotImplementedException(); + } + } +} From c3163441cd064dd715b9f46a5ea920278b04aca7 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Sat, 11 Jul 2020 07:34:53 +0100 Subject: [PATCH 17/45] Remove the concept of IsTopLevelOperation; it is no longer required. --- .../Diagnostics/DefaultDiagnosticTracer.cs | 36 +++++++---------- .../Diagnostics/DiagnosticSourceExtensions.cs | 14 +------ .../Core/Diagnostics/DotDiagnosticTracer.cs | 38 +++++++----------- .../Core/Diagnostics/ITracingIdentifer.cs | 40 ------------------- .../Core/Resolving/ResolveOperation.cs | 11 ----- .../Core/Resolving/ResolveOperationBase.cs | 28 +------------ 6 files changed, 31 insertions(+), 136 deletions(-) delete mode 100644 src/Autofac/Core/Diagnostics/ITracingIdentifer.cs diff --git a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs index 7ea070a16..e64f45897 100644 --- a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs @@ -23,7 +23,7 @@ public class DefaultDiagnosticTracer : FullOperationDiagnosticTracerBase { private const string RequestExceptionTraced = "__RequestException"; - private readonly ConcurrentDictionary _operationBuilders = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _operationBuilders = new ConcurrentDictionary(); private static readonly string[] NewLineSplit = new[] { Environment.NewLine }; @@ -52,7 +52,7 @@ public override void OnOperationStart(OperationStartDiagnosticData data) return; } - var builder = _operationBuilders.GetOrAdd(data.Operation.TracingId, k => new IndentingStringBuilder()); + var builder = _operationBuilders.GetOrAdd(data.Operation, k => new IndentingStringBuilder()); builder.AppendFormattedLine(TracerMessages.ResolveOperationStarting); builder.AppendLine(TracerMessages.EntryBrace); @@ -67,7 +67,7 @@ public override void OnRequestStart(RequestDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { builder.AppendFormattedLine(TracerMessages.ResolveRequestStarting); builder.AppendLine(TracerMessages.EntryBrace); @@ -93,7 +93,7 @@ public override void OnMiddlewareStart(MiddlewareDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.RequestContext.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.RequestContext.Operation, out var builder)) { builder.AppendFormattedLine(TracerMessages.EnterMiddleware, data.Middleware.ToString()); builder.Indent(); @@ -108,7 +108,7 @@ public override void OnMiddlewareFailure(MiddlewareDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.RequestContext.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.RequestContext.Operation, out var builder)) { builder.Outdent(); builder.AppendFormattedLine(TracerMessages.ExitMiddlewareFailure, data.Middleware.ToString()); @@ -123,7 +123,7 @@ public override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.RequestContext.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.RequestContext.Operation, out var builder)) { builder.Outdent(); builder.AppendFormattedLine(TracerMessages.ExitMiddlewareSuccess, data.Middleware.ToString()); @@ -138,7 +138,7 @@ public override void OnRequestFailure(RequestFailureDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { builder.Outdent(); builder.AppendLine(TracerMessages.ExitBrace); @@ -170,7 +170,7 @@ public override void OnRequestSuccess(RequestDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { builder.Outdent(); builder.AppendLine(TracerMessages.ExitBrace); @@ -186,7 +186,7 @@ public override void OnOperationFailure(OperationFailureDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { try { @@ -194,15 +194,11 @@ public override void OnOperationFailure(OperationFailureDiagnosticData data) builder.AppendLine(TracerMessages.ExitBrace); builder.AppendException(TracerMessages.OperationFailed, data.OperationException); - // If we're completing the root operation, raise the event. - if (data.Operation.IsTopLevelOperation) - { - OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); - } + OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); } finally { - _operationBuilders.TryRemove(data.Operation.TracingId, out var _); + _operationBuilders.TryRemove(data.Operation, out var _); } } } @@ -215,7 +211,7 @@ public override void OnOperationSuccess(OperationSuccessDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { try { @@ -223,15 +219,11 @@ public override void OnOperationSuccess(OperationSuccessDiagnosticData data) builder.AppendLine(TracerMessages.ExitBrace); builder.AppendFormattedLine(TracerMessages.OperationSucceeded, data.ResolvedInstance); - // If we're completing the root operation, raise the event. - if (data.Operation.IsTopLevelOperation) - { - OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); - } + OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); } finally { - _operationBuilders.TryRemove(data.Operation.TracingId, out var _); + _operationBuilders.TryRemove(data.Operation, out var _); } } } diff --git a/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs b/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs index c9310b075..bd38c2d49 100644 --- a/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs +++ b/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs @@ -1,4 +1,4 @@ -// This software is part of the Autofac IoC container +// This software is part of the Autofac IoC container // Copyright © 2020 Autofac Contributors // https://autofac.org // @@ -104,10 +104,6 @@ public static bool OperationDiagnosticsEnabled(this DiagnosticSource diagnosticS /// The diagnostic source to which events will be written. /// The pipeline resolve operation that is about to run. /// The request that is responsible for starting this operation. - /// - /// A single operation can in turn invoke other full operations (as opposed to requests). Check - /// to know if you're looking at the entry operation. - /// public static void OperationStart(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequest initiatingRequest) { if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationStart)) @@ -122,10 +118,6 @@ public static void OperationStart(this DiagnosticSource diagnosticSource, Resolv /// The diagnostic source to which events will be written. /// The resolve operation that failed. /// The exception that caused the operation failure. - /// - /// A single operation can in turn invoke other full operations (as opposed to requests). Check - /// to know if you're looking at the entry operation. - /// public static void OperationFailure(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, Exception operationException) { if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationFailure)) @@ -140,10 +132,6 @@ public static void OperationFailure(this DiagnosticSource diagnosticSource, Reso /// The diagnostic source to which events will be written. /// The resolve operation that succeeded. /// The resolved instance providing the requested service. - /// - /// A single operation can in turn invoke other full operations (as opposed to requests). Check - /// to know if you're looking at the entry operation. - /// public static void OperationSuccess(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, object resolvedInstance) { if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationSuccess)) diff --git a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs index 9d05c3d2f..dea4d4ec3 100644 --- a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; @@ -24,7 +24,7 @@ public class DotDiagnosticTracer : FullOperationDiagnosticTracerBase { private const string RequestExceptionTraced = "__RequestException"; - private readonly ConcurrentDictionary _operationBuilders = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _operationBuilders = new ConcurrentDictionary(); /// /// Initializes a new instance of the class. @@ -51,7 +51,7 @@ public override void OnOperationStart(OperationStartDiagnosticData data) return; } - var builder = _operationBuilders.GetOrAdd(data.Operation.TracingId, k => new DotGraphBuilder()); + var builder = _operationBuilders.GetOrAdd(data.Operation, k => new DotGraphBuilder()); } /// @@ -62,7 +62,7 @@ public override void OnRequestStart(RequestDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { builder.OnRequestStart( data.RequestContext.Service.ToString(), @@ -79,7 +79,7 @@ public override void OnMiddlewareStart(MiddlewareDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.RequestContext.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.RequestContext.Operation, out var builder)) { builder.OnMiddlewareStart(data.Middleware.ToString()); } @@ -93,7 +93,7 @@ public override void OnMiddlewareFailure(MiddlewareDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.RequestContext.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.RequestContext.Operation, out var builder)) { builder.OnMiddlewareFailure(); } @@ -107,7 +107,7 @@ public override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.RequestContext.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.RequestContext.Operation, out var builder)) { builder.OnMiddlewareSuccess(); } @@ -121,7 +121,7 @@ public override void OnRequestFailure(RequestFailureDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { var requestException = data.RequestException; if (requestException is DependencyResolutionException && requestException.InnerException is object) @@ -150,7 +150,7 @@ public override void OnRequestSuccess(RequestDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { builder.OnRequestSuccess(data.RequestContext.Instance?.GetType().ToString()); } @@ -164,21 +164,17 @@ public override void OnOperationFailure(OperationFailureDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { try { builder.OnOperationFailure(data.OperationException); - // If we're completing the root operation, raise the event. - if (data.Operation.IsTopLevelOperation) - { - OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); - } + OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); } finally { - _operationBuilders.TryRemove(data.Operation.TracingId, out var _); + _operationBuilders.TryRemove(data.Operation, out var _); } } } @@ -191,21 +187,17 @@ public override void OnOperationSuccess(OperationSuccessDiagnosticData data) return; } - if (_operationBuilders.TryGetValue(data.Operation.TracingId, out var builder)) + if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { try { builder.OnOperationSuccess(data.ResolvedInstance?.GetType().ToString()); - // If we're completing the root operation, raise the event. - if (data.Operation.IsTopLevelOperation) - { - OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); - } + OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); } finally { - _operationBuilders.TryRemove(data.Operation.TracingId, out var _); + _operationBuilders.TryRemove(data.Operation, out var _); } } } diff --git a/src/Autofac/Core/Diagnostics/ITracingIdentifer.cs b/src/Autofac/Core/Diagnostics/ITracingIdentifer.cs deleted file mode 100644 index e189ccde3..000000000 --- a/src/Autofac/Core/Diagnostics/ITracingIdentifer.cs +++ /dev/null @@ -1,40 +0,0 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -using System.Diagnostics.CodeAnalysis; - -namespace Autofac.Core.Diagnostics -{ - /// - /// Marker interface indicating objects that can function as a resolve operation tracing ID. - /// - [SuppressMessage( - "Design", - "CA1040:Avoid empty interfaces", - Justification = "Holding interface assigned to objects that can be used as a key for tracing dictionaries.")] - public interface ITracingIdentifer - { - } -} diff --git a/src/Autofac/Core/Resolving/ResolveOperation.cs b/src/Autofac/Core/Resolving/ResolveOperation.cs index 518145a19..5bdde89c7 100644 --- a/src/Autofac/Core/Resolving/ResolveOperation.cs +++ b/src/Autofac/Core/Resolving/ResolveOperation.cs @@ -45,17 +45,6 @@ public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope) { } - /// - /// Initializes a new instance of the class. - /// - /// The most nested scope in which to begin the operation. The operation - /// can move upward to less nested scopes as components with wider sharing scopes are activated. - /// A parent resolve operation, used to maintain tracing between related operations. - public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, ResolveOperationBase parentOperation) - : base(mostNestedLifetimeScope, parentOperation) - { - } - /// /// Execute the complete resolve operation. /// diff --git a/src/Autofac/Core/Resolving/ResolveOperationBase.cs b/src/Autofac/Core/Resolving/ResolveOperationBase.cs index f082377de..93e066d46 100644 --- a/src/Autofac/Core/Resolving/ResolveOperationBase.cs +++ b/src/Autofac/Core/Resolving/ResolveOperationBase.cs @@ -35,7 +35,7 @@ namespace Autofac.Core.Resolving /// /// Defines the base properties and behaviour of a resolve operation. /// - public abstract class ResolveOperationBase : IResolveOperation, ITracingIdentifer + public abstract class ResolveOperationBase : IResolveOperation { private const int SuccessListInitialCapacity = 32; @@ -50,26 +50,10 @@ public abstract class ResolveOperationBase : IResolveOperation, ITracingIdentife /// can move upward to less nested scopes as components with wider sharing scopes are activated. protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope) { - TracingId = this; - IsTopLevelOperation = true; CurrentScope = mostNestedLifetimeScope ?? throw new ArgumentNullException(nameof(mostNestedLifetimeScope)); - IsTopLevelOperation = true; DiagnosticSource = mostNestedLifetimeScope.DiagnosticSource; } - /// - /// Initializes a new instance of the class. - /// - /// The most nested scope in which to begin the operation. The operation - /// can move upward to less nested scopes as components with wider sharing scopes are activated. - /// A tracing ID for the operation. - protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, ITracingIdentifer tracingId) - : this(mostNestedLifetimeScope) - { - TracingId = tracingId; - IsTopLevelOperation = false; - } - /// /// Gets the active resolve request. /// @@ -85,11 +69,6 @@ protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, IT /// public IEnumerable InProgressRequests => RequestStack; - /// - /// Gets the tracing identifier for the operation. - /// - public ITracingIdentifer TracingId { get; } - /// /// Gets the for the operation. /// @@ -100,11 +79,6 @@ protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, IT /// public int RequestDepth { get; protected set; } - /// - /// Gets a value indicating whether this operation is a top-level operation (as opposed to one initiated from inside an existing operation). - /// - public bool IsTopLevelOperation { get; } - /// /// Gets or sets the that initiated the operation. Other nested requests may have been issued as a result of this one. /// From 9ad6a0dea3dead15636ff27b302e9a840b0f40a2 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 13 Jul 2020 07:55:58 -0700 Subject: [PATCH 18/45] Enable TracerMessages generation based on https://github.com/microsoft/msbuild/issues/4751 --- src/Autofac/Autofac.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Autofac/Autofac.csproj b/src/Autofac/Autofac.csproj index 805f27087..c8546de74 100644 --- a/src/Autofac/Autofac.csproj +++ b/src/Autofac/Autofac.csproj @@ -354,6 +354,10 @@ ResXFileCodeGenerator TracerMessages.Designer.cs + CSharp + Core\Diagnostics\TracerMessages.Designer.cs + TracerMessages + Autofac.Core.Diagnostics ResXFileCodeGenerator From c650a519087ac57829c2f3fccef9a4e1bb6e794e Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 13 Jul 2020 08:44:16 -0700 Subject: [PATCH 19/45] Figure out resx generation later. CS2002 occurs with full info specified. --- src/Autofac/Autofac.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Autofac/Autofac.csproj b/src/Autofac/Autofac.csproj index c8546de74..805f27087 100644 --- a/src/Autofac/Autofac.csproj +++ b/src/Autofac/Autofac.csproj @@ -354,10 +354,6 @@ ResXFileCodeGenerator TracerMessages.Designer.cs - CSharp - Core\Diagnostics\TracerMessages.Designer.cs - TracerMessages - Autofac.Core.Diagnostics ResXFileCodeGenerator From 8d73ee6654bd848cdc5bc1721b25e277bcefb762 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 13 Jul 2020 08:46:24 -0700 Subject: [PATCH 20/45] Additional labels for DOT graph display. --- .../Diagnostics/TracerMessages.Designer.cs | 68 ++++++++++++------- .../Core/Diagnostics/TracerMessages.resx | 62 +++++++++-------- 2 files changed, 77 insertions(+), 53 deletions(-) diff --git a/src/Autofac/Core/Diagnostics/TracerMessages.Designer.cs b/src/Autofac/Core/Diagnostics/TracerMessages.Designer.cs index efb6bdb72..039115563 100644 --- a/src/Autofac/Core/Diagnostics/TracerMessages.Designer.cs +++ b/src/Autofac/Core/Diagnostics/TracerMessages.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -10,8 +10,8 @@ namespace Autofac.Core.Diagnostics { using System; - - + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -23,15 +23,15 @@ namespace Autofac.Core.Diagnostics { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class TracerMessages { - + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal TracerMessages() { } - + /// /// Returns the cached ResourceManager instance used by this class. /// @@ -45,7 +45,7 @@ internal class TracerMessages { return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. @@ -59,7 +59,7 @@ internal class TracerMessages { resourceCulture = value; } } - + /// /// Looks up a localized string similar to Component: {0}. /// @@ -68,7 +68,7 @@ internal class TracerMessages { return ResourceManager.GetString("ComponentDisplay", resourceCulture); } } - + /// /// Looks up a localized string similar to -> {0}. /// @@ -77,7 +77,7 @@ internal class TracerMessages { return ResourceManager.GetString("EnterMiddleware", resourceCulture); } } - + /// /// Looks up a localized string similar to {. /// @@ -86,7 +86,16 @@ internal class TracerMessages { return ResourceManager.GetString("EntryBrace", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Exception: {0}. + /// + internal static string ExceptionDisplay { + get { + return ResourceManager.GetString("ExceptionDisplay", resourceCulture); + } + } + /// /// Looks up a localized string similar to }. /// @@ -95,7 +104,7 @@ internal class TracerMessages { return ResourceManager.GetString("ExitBrace", resourceCulture); } } - + /// /// Looks up a localized string similar to X- {0}. /// @@ -104,7 +113,7 @@ internal class TracerMessages { return ResourceManager.GetString("ExitMiddlewareFailure", resourceCulture); } } - + /// /// Looks up a localized string similar to <- {0}. /// @@ -113,7 +122,16 @@ internal class TracerMessages { return ResourceManager.GetString("ExitMiddlewareSuccess", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Instance: {0}. + /// + internal static string InstanceDisplay { + get { + return ResourceManager.GetString("InstanceDisplay", resourceCulture); + } + } + /// /// Looks up a localized string similar to Operation FAILED. /// @@ -122,7 +140,7 @@ internal class TracerMessages { return ResourceManager.GetString("OperationFailed", resourceCulture); } } - + /// /// Looks up a localized string similar to Operation Succeeded; result instance was {0}. /// @@ -131,7 +149,7 @@ internal class TracerMessages { return ResourceManager.GetString("OperationSucceeded", resourceCulture); } } - + /// /// Looks up a localized string similar to Cannot outdent; no indentation specified.. /// @@ -140,7 +158,7 @@ internal class TracerMessages { return ResourceManager.GetString("OutdentFailure", resourceCulture); } } - + /// /// Looks up a localized string similar to Pipeline:. /// @@ -149,7 +167,7 @@ internal class TracerMessages { return ResourceManager.GetString("Pipeline", resourceCulture); } } - + /// /// Looks up a localized string similar to Resolve Operation Starting. /// @@ -158,7 +176,7 @@ internal class TracerMessages { return ResourceManager.GetString("ResolveOperationStarting", resourceCulture); } } - + /// /// Looks up a localized string similar to Resolve Request FAILED. /// @@ -167,7 +185,7 @@ internal class TracerMessages { return ResourceManager.GetString("ResolveRequestFailed", resourceCulture); } } - + /// /// Looks up a localized string similar to Resolve Request FAILED: Nested Resolve Failed. /// @@ -176,7 +194,7 @@ internal class TracerMessages { return ResourceManager.GetString("ResolveRequestFailedNested", resourceCulture); } } - + /// /// Looks up a localized string similar to Resolve Request Starting. /// @@ -185,7 +203,7 @@ internal class TracerMessages { return ResourceManager.GetString("ResolveRequestStarting", resourceCulture); } } - + /// /// Looks up a localized string similar to Resolve Request Succeeded; result instance was {0}. /// @@ -194,7 +212,7 @@ internal class TracerMessages { return ResourceManager.GetString("ResolveRequestSucceeded", resourceCulture); } } - + /// /// Looks up a localized string similar to Service: {0}. /// @@ -203,7 +221,7 @@ internal class TracerMessages { return ResourceManager.GetString("ServiceDisplay", resourceCulture); } } - + /// /// Looks up a localized string similar to Target: {0}. /// diff --git a/src/Autofac/Core/Diagnostics/TracerMessages.resx b/src/Autofac/Core/Diagnostics/TracerMessages.resx index 9dba2349a..212aa963a 100644 --- a/src/Autofac/Core/Diagnostics/TracerMessages.resx +++ b/src/Autofac/Core/Diagnostics/TracerMessages.resx @@ -1,17 +1,17 @@  - @@ -126,6 +126,9 @@ { + + Exception: {0} + } @@ -135,6 +138,9 @@ <- {0} + + Instance: {0} + Operation FAILED @@ -169,4 +175,4 @@ Target: {0} - \ No newline at end of file + From 2d0fcae3984847cc1bfa06ab53ed2edc9fb5de89 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 13 Jul 2020 10:06:09 -0700 Subject: [PATCH 21/45] Naming updates to reflect no more nested ops. --- .../Diagnostics/DefaultDiagnosticTracerTests.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs b/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs index f2ea452ae..da3f48b8e 100644 --- a/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs +++ b/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs @@ -88,7 +88,7 @@ public void DiagnosticTracerRaisesEventsOnError() } [Fact] - public void DiagnosticTracerDoesNotRaiseAnEventOnNestedOperations() + public void DiagnosticTracerHandlesDecorators() { var tracer = new DefaultDiagnosticTracer(); @@ -100,20 +100,16 @@ public void DiagnosticTracerDoesNotRaiseAnEventOnNestedOperations() container.SubscribeToDiagnostics(tracer); - int traceCount = 0; string lastOpResult = null; tracer.OperationCompleted += (sender, args) => { Assert.Same(tracer, sender); lastOpResult = args.TraceContent; - traceCount++; }; container.Resolve(); - // Only a single trace (despite the nested operations). - Assert.Equal(1, traceCount); Assert.Contains("Decorator", lastOpResult); } @@ -131,7 +127,7 @@ public void DiagnosticTracerDoesNotLeakMemory() container.SubscribeToDiagnostics(tracer); container.Resolve(); - // The dictionary of tracked trace IDs and + // The dictionary of tracked operations and // string builders should be empty. Assert.Equal(0, tracer.OperationsInProgress); } From eee56ad6182d6509b7593a134536f66128e8363c Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 13 Jul 2020 10:06:18 -0700 Subject: [PATCH 22/45] Graphs work! --- .../Core/Diagnostics/DotDiagnosticTracer.cs | 217 +++++++++++++----- .../Diagnostics/DotDiagnosticTracerTests.cs | 12 +- 2 files changed, 163 insertions(+), 66 deletions(-) diff --git a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs index dea4d4ec3..c7299a7ec 100644 --- a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs @@ -1,8 +1,35 @@ -using System; +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Text; +using System.Web; using Autofac.Core.Resolving; namespace Autofac.Core.Diagnostics @@ -204,25 +231,20 @@ public override void OnOperationSuccess(OperationSuccessDiagnosticData data) private abstract class DotGraphNode { - private const int IndentSize = 2; - public string Id { get; } = "n" + Guid.NewGuid().ToString("N"); - public List Children { get; } = new List(); - public bool Success { get; set; } - protected static void AppendIndent(int indent, StringBuilder stringBuilder) + protected static string NewlineReplace(string input, string newlineReplacement) { - stringBuilder.Append(' ', indent * IndentSize); - } - - public virtual void ToString(int indent, StringBuilder stringBuilder) - { - foreach (var child in Children) - { - child.ToString(indent + 1, stringBuilder); - } + // Pretty stoked the StringComparison overload is only in one of our + // target frameworks. :( +#if NETSTANDARD2_0 + return input.Replace(Environment.NewLine, newlineReplacement); +#endif +#if !NETSTANDARD2_0 + return input.Replace(Environment.NewLine, newlineReplacement, StringComparison.Ordinal); +#endif } } @@ -232,6 +254,7 @@ public ResolveRequestNode(string service, string component) { Service = service; Component = component; + Middleware = new List(); } public string Service { get; private set; } @@ -244,19 +267,79 @@ public ResolveRequestNode(string service, string component) public string? InstanceType { get; set; } - public override void ToString(int indent, StringBuilder stringBuilder) + public List Middleware { get; } + + private const string NodeStartFormat = @"{0} [ +shape=plaintext,label=< + +"; + + private const string MiddlewareStartFormat = @"{0} [ +shape=plaintext,label=< +
+"; + + private const string NodeEndFormat = "
>];"; + + private static void AppendRowFormat(StringBuilder stringBuilder, string format, params object[] args) + { + stringBuilder.Append(""); + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, format, args); + stringBuilder.Append(""); + } + + public void ToString(StringBuilder stringBuilder) { - AppendIndent(indent, stringBuilder); - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} [label=\"Resolve Request\"]", Id); + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, NodeStartFormat, Id); + AppendRowFormat(stringBuilder, TracerMessages.ServiceDisplay, HttpUtility.HtmlEncode(Service)); + AppendRowFormat(stringBuilder, TracerMessages.ComponentDisplay, HttpUtility.HtmlEncode(Component)); + + if (DecoratorTarget is object) + { + AppendRowFormat(stringBuilder, TracerMessages.TargetDisplay, HttpUtility.HtmlEncode(DecoratorTarget)); + } + + if (InstanceType is object) + { + AppendRowFormat(stringBuilder, TracerMessages.InstanceDisplay, HttpUtility.HtmlEncode(InstanceType)); + } + + if (Exception is object) + { + AppendRowFormat(stringBuilder, TracerMessages.ExceptionDisplay, NewlineReplace(HttpUtility.HtmlEncode(Exception.ToString()), "
")); + } + + stringBuilder.Append(NodeEndFormat); stringBuilder.AppendLine(); - foreach (var child in Children) + + if (Middleware.Count != 0) { - AppendIndent(indent, stringBuilder); - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} -> {1}", Id, child.Id); + var middlewareSubgraphId = "mw" + Id; + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} -> {1}", Id, middlewareSubgraphId); + if (Middleware.Any(mw => !mw.Success)) + { + stringBuilder.Append(" [penwidth=3]"); + } + + stringBuilder.AppendLine(); + + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, MiddlewareStartFormat, middlewareSubgraphId); + foreach (var mw in Middleware) + { + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "", mw.Id); + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0}{1}{2}", mw.Success ? "" : "", mw.Name, mw.Success ? "" : ""); + stringBuilder.Append(""); + stringBuilder.AppendLine(); + } + + stringBuilder.Append(NodeEndFormat); stringBuilder.AppendLine(); - } - base.ToString(indent, stringBuilder); + foreach (var mw in Middleware) + { + mw.ToString(stringBuilder, middlewareSubgraphId); + } + } } } @@ -266,19 +349,37 @@ private class OperationNode : DotGraphNode public string? InstanceType { get; set; } - public override void ToString(int indent, StringBuilder stringBuilder) + public List ResolveRequests { get; } = new List(); + + public void ToString(StringBuilder stringBuilder) { - AppendIndent(indent, stringBuilder); - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} [label=\"Operation\"]", Id); + stringBuilder.Append(Id); + stringBuilder.Append(" [label=\""); + + if (InstanceType is object) + { + stringBuilder.Append(InstanceType); + } + else if (Exception is object) + { + stringBuilder.Append(NewlineReplace(HttpUtility.HtmlEncode(Exception.ToString()), "\\l")); + stringBuilder.Append("\\l"); + } + + stringBuilder.Append("\"]"); stringBuilder.AppendLine(); - foreach (var child in Children) + foreach (var request in ResolveRequests) { - AppendIndent(indent, stringBuilder); - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} -> {1}", Id, child.Id); + request.ToString(stringBuilder); + + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} -> {1}", Id, request.Id); + if (!request.Success) + { + stringBuilder.Append(" [penwidth=3]"); + } + stringBuilder.AppendLine(); } - - base.ToString(indent, stringBuilder); } } @@ -287,23 +388,26 @@ private class MiddlewareNode : DotGraphNode public MiddlewareNode(string name) { Name = name; + ResolveRequests = new List(); } public string Name { get; } - public override void ToString(int indent, StringBuilder stringBuilder) + public List ResolveRequests { get; } + + public void ToString(StringBuilder stringBuilder, string middlewareSubgraphId) { - AppendIndent(indent, stringBuilder); - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} [label=\"Middleware\"]", Id); - stringBuilder.AppendLine(); - foreach (var child in Children) + foreach (var request in ResolveRequests) { - AppendIndent(indent, stringBuilder); - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} -> {1}", Id, child.Id); + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0}:{1} -> {2}", middlewareSubgraphId, Id, request.Id); + if (!request.Success) + { + stringBuilder.Append(" [penwidth=3]"); + } + stringBuilder.AppendLine(); + request.ToString(stringBuilder); } - - base.ToString(indent, stringBuilder); } } @@ -312,16 +416,8 @@ public override void ToString(int indent, StringBuilder stringBuilder) ///
private class DotGraphBuilder { - // https://www.graphviz.org/pdf/dotguide.pdf - // TODO: Wrong tree structure - middleware calls middleware, not request calls all middleware. - // TODO: Try to render the middleware as a stack that can spawn other requests. - // TODO: Bold on failure paths. - // TODO: Actually put real info into the graph (errors, names) - // TODO: Different shapes per thing - operation, request, MW. public OperationNode Root { get; private set; } - public Stack Operations { get; } = new Stack(); - public Stack ResolveRequests { get; } = new Stack(); public Stack Middlewares { get; } = new Stack(); @@ -329,21 +425,18 @@ private class DotGraphBuilder public DotGraphBuilder() { Root = new OperationNode(); - Operations.Push(Root); } public void OnOperationFailure(Exception? operationException) { - var operation = Operations.Pop(); - operation.Success = false; - operation.Exception = operationException; + Root.Success = false; + Root.Exception = operationException; } public void OnOperationSuccess(string? instanceType) { - var operation = Operations.Pop(); - operation.Success = true; - operation.InstanceType = instanceType; + Root.Success = true; + Root.InstanceType = instanceType; } public void OnRequestStart(string service, string component, string? decoratorTarget) @@ -354,7 +447,15 @@ public void OnRequestStart(string service, string component, string? decoratorTa request.DecoratorTarget = decoratorTarget; } - Operations.Peek().Children.Add(request); + if (Middlewares.Count != 0) + { + Middlewares.Peek().ResolveRequests.Add(request); + } + else + { + Root.ResolveRequests.Add(request); + } + ResolveRequests.Push(request); } @@ -375,7 +476,7 @@ public void OnRequestSuccess(string? instanceType) public void OnMiddlewareStart(string middleware) { var mw = new MiddlewareNode(middleware); - ResolveRequests.Peek().Children.Add(mw); + ResolveRequests.Peek().Middleware.Add(mw); Middlewares.Push(mw); } @@ -395,7 +496,7 @@ public override string ToString() { var builder = new StringBuilder(); builder.AppendLine("digraph G {"); - Root.ToString(1, builder); + Root.ToString(builder); builder.AppendLine("}"); return builder.ToString(); } diff --git a/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs b/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs index 1d58ca6bd..1321d60bd 100644 --- a/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs +++ b/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs @@ -53,7 +53,7 @@ public void DiagnosticTracerRaisesEvents() container.Resolve(); - Assert.Contains("Hello", lastOpResult); + Assert.Contains("λ:System.String", lastOpResult); } [Fact] @@ -88,7 +88,7 @@ public void DiagnosticTracerRaisesEventsOnError() } [Fact] - public void DiagnosticTracerDoesNotRaiseAnEventOnNestedOperations() + public void DiagnosticTracerHandlesDecorators() { var tracer = new DotDiagnosticTracer(); @@ -100,20 +100,16 @@ public void DiagnosticTracerDoesNotRaiseAnEventOnNestedOperations() container.SubscribeToDiagnostics(tracer); - int traceCount = 0; string lastOpResult = null; tracer.OperationCompleted += (sender, args) => { Assert.Same(tracer, sender); lastOpResult = args.TraceContent; - traceCount++; }; container.Resolve(); - // Only a single trace (despite the nested operations). - Assert.Equal(1, traceCount); Assert.Contains("Decorator", lastOpResult); } @@ -131,8 +127,8 @@ public void DiagnosticTracerDoesNotLeakMemory() container.SubscribeToDiagnostics(tracer); container.Resolve(); - // The dictionary of tracked trace IDs and - // string builders should be empty. + // The dictionary of tracked operations and + // graphs should be empty. Assert.Equal(0, tracer.OperationsInProgress); } From ca4815b77fc2161ca52724c30fc8356a9ee096ff Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 13 Jul 2020 15:24:47 -0700 Subject: [PATCH 23/45] Clean up string manipulation code. --- .../Core/Diagnostics/DotDiagnosticTracer.cs | 86 ++----- .../Core/Diagnostics/DotStringExtensions.cs | 219 ++++++++++++++++++ 2 files changed, 235 insertions(+), 70 deletions(-) create mode 100644 src/Autofac/Core/Diagnostics/DotStringExtensions.cs diff --git a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs index c7299a7ec..59a80c2f4 100644 --- a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs @@ -234,18 +234,6 @@ private abstract class DotGraphNode public string Id { get; } = "n" + Guid.NewGuid().ToString("N"); public bool Success { get; set; } - - protected static string NewlineReplace(string input, string newlineReplacement) - { - // Pretty stoked the StringComparison overload is only in one of our - // target frameworks. :( -#if NETSTANDARD2_0 - return input.Replace(Environment.NewLine, newlineReplacement); -#endif -#if !NETSTANDARD2_0 - return input.Replace(Environment.NewLine, newlineReplacement, StringComparison.Ordinal); -#endif - } } private class ResolveRequestNode : DotGraphNode @@ -269,72 +257,42 @@ public ResolveRequestNode(string service, string component) public List Middleware { get; } - private const string NodeStartFormat = @"{0} [ -shape=plaintext,label=< - -"; - - private const string MiddlewareStartFormat = @"{0} [ -shape=plaintext,label=< -
-"; - - private const string NodeEndFormat = "
>];"; - - private static void AppendRowFormat(StringBuilder stringBuilder, string format, params object[] args) - { - stringBuilder.Append(""); - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, format, args); - stringBuilder.Append(""); - } - public void ToString(StringBuilder stringBuilder) { - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, NodeStartFormat, Id); - AppendRowFormat(stringBuilder, TracerMessages.ServiceDisplay, HttpUtility.HtmlEncode(Service)); - AppendRowFormat(stringBuilder, TracerMessages.ComponentDisplay, HttpUtility.HtmlEncode(Component)); + stringBuilder.StartTableNode(Id, border: 1) + .AppendTableRow(TracerMessages.ServiceDisplay, Service) + .AppendTableRow(TracerMessages.ComponentDisplay, Component); if (DecoratorTarget is object) { - AppendRowFormat(stringBuilder, TracerMessages.TargetDisplay, HttpUtility.HtmlEncode(DecoratorTarget)); + stringBuilder.AppendTableRow(TracerMessages.TargetDisplay, DecoratorTarget); } if (InstanceType is object) { - AppendRowFormat(stringBuilder, TracerMessages.InstanceDisplay, HttpUtility.HtmlEncode(InstanceType)); + stringBuilder.AppendTableRow(TracerMessages.InstanceDisplay, InstanceType); } if (Exception is object) { - AppendRowFormat(stringBuilder, TracerMessages.ExceptionDisplay, NewlineReplace(HttpUtility.HtmlEncode(Exception.ToString()), "
")); + stringBuilder.AppendTableRow(TracerMessages.ExceptionDisplay, Exception.ToString()); } - stringBuilder.Append(NodeEndFormat); - stringBuilder.AppendLine(); + stringBuilder.EndTableNode(); if (Middleware.Count != 0) { var middlewareSubgraphId = "mw" + Id; - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} -> {1}", Id, middlewareSubgraphId); - if (Middleware.Any(mw => !mw.Success)) - { - stringBuilder.Append(" [penwidth=3]"); - } - - stringBuilder.AppendLine(); + stringBuilder + .ConnectNodes(Id, middlewareSubgraphId, Middleware.Any(mw => !mw.Success)) + .StartTableNode(middlewareSubgraphId, cellBorder: 1); - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, MiddlewareStartFormat, middlewareSubgraphId); foreach (var mw in Middleware) { - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "", mw.Id); - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0}{1}{2}", mw.Success ? "" : "", mw.Name, mw.Success ? "" : ""); - stringBuilder.Append(""); - stringBuilder.AppendLine(); + stringBuilder.AppendTableRow(!mw.Success, mw.Id, "{0}", mw.Name); } - stringBuilder.Append(NodeEndFormat); - stringBuilder.AppendLine(); - + stringBuilder.EndTableNode(); foreach (var mw in Middleware) { mw.ToString(stringBuilder, middlewareSubgraphId); @@ -362,7 +320,7 @@ public void ToString(StringBuilder stringBuilder) } else if (Exception is object) { - stringBuilder.Append(NewlineReplace(HttpUtility.HtmlEncode(Exception.ToString()), "\\l")); + stringBuilder.Append(HttpUtility.HtmlEncode(Exception.ToString()).NewlineReplace("\\l")); stringBuilder.Append("\\l"); } @@ -371,14 +329,7 @@ public void ToString(StringBuilder stringBuilder) foreach (var request in ResolveRequests) { request.ToString(stringBuilder); - - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} -> {1}", Id, request.Id); - if (!request.Success) - { - stringBuilder.Append(" [penwidth=3]"); - } - - stringBuilder.AppendLine(); + stringBuilder.ConnectNodes(Id, request.Id, !request.Success); } } } @@ -397,15 +348,10 @@ public MiddlewareNode(string name) public void ToString(StringBuilder stringBuilder, string middlewareSubgraphId) { + var fullId = string.Format(CultureInfo.CurrentCulture, "{0}:{1}", middlewareSubgraphId, Id); foreach (var request in ResolveRequests) { - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0}:{1} -> {2}", middlewareSubgraphId, Id, request.Id); - if (!request.Success) - { - stringBuilder.Append(" [penwidth=3]"); - } - - stringBuilder.AppendLine(); + stringBuilder.ConnectNodes(fullId, request.Id, !request.Success); request.ToString(stringBuilder); } } diff --git a/src/Autofac/Core/Diagnostics/DotStringExtensions.cs b/src/Autofac/Core/Diagnostics/DotStringExtensions.cs new file mode 100644 index 000000000..c53a26df9 --- /dev/null +++ b/src/Autofac/Core/Diagnostics/DotStringExtensions.cs @@ -0,0 +1,219 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Globalization; +using System.Text; +using System.Web; + +namespace Autofac.Core.Diagnostics +{ + /// + /// Extension methods for building DOT graph format strings. + /// + internal static class DotStringExtensions + { + /// + /// Starts a DOT graph node where the label is an HTML table. + /// + /// + /// The to which the node should be written. + /// + /// + /// The node ID. You can connect to this node by this ID. + /// + /// + /// Size of the table border. + /// + /// + /// Size of the border around individual cells. + /// + /// + /// Space between the text in the cell and the cell wall. + /// + /// + /// Space between the cells. + /// + /// + /// The for continued writing. + /// + public static StringBuilder StartTableNode(this StringBuilder stringBuilder, string id, int border = 0, int cellBorder = 0, int cellPadding = 5, int cellSpacing = 0) + { + stringBuilder.AppendFormat( + CultureInfo.CurrentCulture, + "{0} [shape=plaintext,label=<", + id, + border, + cellBorder, + cellPadding, + cellSpacing); + stringBuilder.AppendLine(); + return stringBuilder; + } + + /// + /// Ends a DOT graph node where the label is an HTML table. + /// + /// + /// The with the node to close. + /// + /// + /// The for continued writing. + /// + public static StringBuilder EndTableNode(this StringBuilder stringBuilder) + { + stringBuilder.Append("
>];"); + stringBuilder.AppendLine(); + return stringBuilder; + } + + /// + /// Writes a table row to an HTML table node in a DOT graph. + /// + /// + /// The to which the node should be written. + /// + /// + /// A string into which the arguments will be formatted. + /// + /// + /// The arguments to HTML encode and put into the format string. + /// + /// + /// The for continued writing. + /// + public static StringBuilder AppendTableRow(this StringBuilder stringBuilder, string format, params string[] args) + { + stringBuilder.Append(""); + for (var i = 0; i < args.Length; i++) + { + args[i] = HttpUtility.HtmlEncode(args[i]).NewlineReplace("
"); + } + + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, format, args); + stringBuilder.Append(""); + return stringBuilder; + } + + /// + /// Writes a table row to an HTML table node in a DOT graph. + /// + /// + /// The to which the node should be written. + /// + /// + /// if the text in the cell should be bold; if not. + /// + /// + /// The cell/row ID. You can connect to this cell by this ID. + /// + /// + /// A string into which the arguments will be formatted. + /// + /// + /// The arguments to HTML encode and put into the format string. + /// + /// + /// The for continued writing. + /// + public static StringBuilder AppendTableRow(this StringBuilder stringBuilder, bool bold, string id, string format, params string[] args) + { + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "", id); + if (bold) + { + stringBuilder.Append(""); + } + + for (var i = 0; i < args.Length; i++) + { + args[i] = HttpUtility.HtmlEncode(args[i]).NewlineReplace("
"); + } + + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, format, args); + if (bold) + { + stringBuilder.Append("
"); + } + + stringBuilder.Append(""); + stringBuilder.AppendLine(); + return stringBuilder; + } + + /// + /// Writes a DOT graph connection between two named nodes. + /// + /// + /// The to which the node should be written. + /// + /// + /// The ID of the node where the connection originates. + /// + /// + /// The ID of the node where the connection ends. + /// + /// + /// if the text in the cell should be bold; if not. + /// + /// + /// The for continued writing. + /// + public static StringBuilder ConnectNodes(this StringBuilder stringBuilder, string fromId, string toId, bool bold) + { + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} -> {1}", fromId, toId); + if (bold) + { + stringBuilder.Append(" [penwidth=3]"); + } + + stringBuilder.AppendLine(); + return stringBuilder; + } + + /// + /// Replaces the environment newline character with something else. + /// + /// + /// The string with characters to replace. + /// + /// + /// The content that should replace newlines. + /// + /// + /// The with newlines replaced with the specified content. + /// + public static string NewlineReplace(this string input, string newlineReplacement) + { + // Pretty stoked the StringComparison overload is only in one of our + // target frameworks. :( +#if NETSTANDARD2_0 + return input.Replace(Environment.NewLine, newlineReplacement); +#endif +#if !NETSTANDARD2_0 + return input.Replace(Environment.NewLine, newlineReplacement, StringComparison.Ordinal); +#endif + } + } +} From b7fd58e782dfd68b72601816adbd34bebfc5411e Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 13 Jul 2020 16:06:54 -0700 Subject: [PATCH 24/45] Easier testing setup - activator may be null in a mock. --- src/Autofac/Core/Resolving/ActivatorExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Autofac/Core/Resolving/ActivatorExtensions.cs b/src/Autofac/Core/Resolving/ActivatorExtensions.cs index 857d14b8e..673cde567 100644 --- a/src/Autofac/Core/Resolving/ActivatorExtensions.cs +++ b/src/Autofac/Core/Resolving/ActivatorExtensions.cs @@ -41,7 +41,7 @@ internal static class ActivatorExtensions /// A display name. public static string DisplayName(this IInstanceActivator activator) { - var fullName = activator.LimitType.FullName ?? ""; + var fullName = activator?.LimitType.FullName ?? ""; return activator is DelegateActivator ? $"λ:{fullName}" : fullName; From b4fcb5b0a6764334988ec897cef3aafc44eb9dc8 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 13 Jul 2020 16:07:12 -0700 Subject: [PATCH 25/45] Rounded out DOT test suite. --- .../Core/Diagnostics/DotDiagnosticTracer.cs | 33 +++ .../Diagnostics/DotDiagnosticTracerTests.cs | 6 +- .../Diagnostics/DotDiagnosticTracerTests.cs | 221 ++++++++++++++++++ 3 files changed, 259 insertions(+), 1 deletion(-) diff --git a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs index 59a80c2f4..4ce6d0ce7 100644 --- a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs @@ -49,6 +49,9 @@ namespace Autofac.Core.Diagnostics /// public class DotDiagnosticTracer : FullOperationDiagnosticTracerBase { + /// + /// Metadata flag to help deduplicate the number of places where the exception is traced. + /// private const string RequestExceptionTraced = "__RequestException"; private readonly ConcurrentDictionary _operationBuilders = new ConcurrentDictionary(); @@ -407,6 +410,12 @@ public void OnRequestStart(string service, string component, string? decoratorTa public void OnRequestFailure(Exception? requestException) { + if (ResolveRequests.Count == 0) + { + // OnRequestFailure happened without a corresponding OnRequestStart. + return; + } + var request = ResolveRequests.Pop(); request.Success = false; request.Exception = requestException; @@ -414,6 +423,12 @@ public void OnRequestFailure(Exception? requestException) public void OnRequestSuccess(string? instanceType) { + if (ResolveRequests.Count == 0) + { + // OnRequestSuccess happened without a corresponding OnRequestStart. + return; + } + var request = ResolveRequests.Pop(); request.Success = true; request.InstanceType = instanceType; @@ -421,6 +436,12 @@ public void OnRequestSuccess(string? instanceType) public void OnMiddlewareStart(string middleware) { + if (ResolveRequests.Count == 0) + { + // Middleware only happens in context of a request. + return; + } + var mw = new MiddlewareNode(middleware); ResolveRequests.Peek().Middleware.Add(mw); Middlewares.Push(mw); @@ -428,12 +449,24 @@ public void OnMiddlewareStart(string middleware) public void OnMiddlewareFailure() { + if (Middlewares.Count == 0) + { + // We somehow missed the start event. + return; + } + var mw = Middlewares.Pop(); mw.Success = false; } public void OnMiddlewareSuccess() { + if (Middlewares.Count == 0) + { + // We somehow missed the start event. + return; + } + var mw = Middlewares.Pop(); mw.Success = true; } diff --git a/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs b/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs index 1321d60bd..96f25d3e9 100644 --- a/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs +++ b/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs @@ -32,7 +32,7 @@ namespace Autofac.Specification.Test.Diagnostics public class DotDiagnosticTracerTests { [Fact] - public void DiagnosticTracerRaisesEvents() + public void DiagnosticTracerRaisesEventsOnSuccess() { var tracer = new DotDiagnosticTracer(); @@ -54,6 +54,8 @@ public void DiagnosticTracerRaisesEvents() container.Resolve(); Assert.Contains("λ:System.String", lastOpResult); + Assert.StartsWith("digraph G {", lastOpResult); + Assert.EndsWith("}", lastOpResult.Trim()); } [Fact] @@ -85,6 +87,8 @@ public void DiagnosticTracerRaisesEventsOnError() } Assert.Contains(nameof(InvalidOperationException), lastOpResult); + Assert.StartsWith("digraph G {", lastOpResult); + Assert.EndsWith("}", lastOpResult.Trim()); } [Fact] diff --git a/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs b/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs index 79599e236..ac1c55457 100644 --- a/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs +++ b/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs @@ -23,12 +23,233 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. +using System; +using System.Linq; +using Autofac.Core; using Autofac.Core.Diagnostics; +using Autofac.Core.Resolving; +using Autofac.Core.Resolving.Pipeline; +using Moq; using Xunit; namespace Autofac.Test.Core.Diagnostics { public class DotDiagnosticTracerTests { + [Fact] + public void OnOperationStart_AddsOperation() + { + var tracer = new DotDiagnosticTracer(); + tracer.OnOperationStart(new OperationStartDiagnosticData(MockResolveOperation(), MockResolveRequest())); + Assert.Equal(1, tracer.OperationsInProgress); + } + + [Fact] + public void OnOperationStart_NoDataSkipsOperation() + { + var tracer = new DotDiagnosticTracer(); + tracer.OnOperationStart(null); + Assert.Equal(0, tracer.OperationsInProgress); + } + + [Fact] + public void OnOperationSuccess_CompletesOperation() + { + var tracer = new DotDiagnosticTracer(); + var called = false; + tracer.OperationCompleted += (sender, args) => + { + called = true; + }; + var op = MockResolveOperation(); + tracer.OnOperationStart(new OperationStartDiagnosticData(op, MockResolveRequest())); + tracer.OnOperationSuccess(new OperationSuccessDiagnosticData(op, "instance")); + Assert.Equal(0, tracer.OperationsInProgress); + Assert.True(called); + } + + [Fact] + public void OnOperationSuccess_NoDataSkipsOperation() + { + var tracer = new DotDiagnosticTracer(); + var op = MockResolveOperation(); + var called = false; + tracer.OperationCompleted += (sender, args) => + { + called = true; + }; + + tracer.OnOperationStart(new OperationStartDiagnosticData(op, MockResolveRequest())); + tracer.OnOperationSuccess(null); + Assert.Equal(1, tracer.OperationsInProgress); + Assert.False(called); + } + + [Fact] + public void OnOperationFailure_CompletesOperation() + { + var tracer = new DotDiagnosticTracer(); + var called = false; + tracer.OperationCompleted += (sender, args) => + { + called = true; + }; + var op = MockResolveOperation(); + tracer.OnOperationStart(new OperationStartDiagnosticData(op, MockResolveRequest())); + tracer.OnOperationFailure(new OperationFailureDiagnosticData(op, new DivideByZeroException())); + Assert.Equal(0, tracer.OperationsInProgress); + Assert.True(called); + } + + [Fact] + public void OnOperationFailure_NoDataSkipsOperation() + { + var tracer = new DotDiagnosticTracer(); + var op = MockResolveOperation(); + var called = false; + tracer.OperationCompleted += (sender, args) => + { + called = true; + }; + + tracer.OnOperationStart(new OperationStartDiagnosticData(op, MockResolveRequest())); + tracer.OnOperationFailure(null); + Assert.Equal(1, tracer.OperationsInProgress); + Assert.False(called); + } + + [Fact] + public void OnRequestStart_NoOperation() + { + var tracer = new DotDiagnosticTracer(); + tracer.OnRequestStart(new RequestDiagnosticData(MockResolveOperation(), MockResolveRequestContext())); + Assert.Equal(0, tracer.OperationsInProgress); + } + + [Fact] + public void OnRequestSuccess_NoStartOperation() + { + var tracer = new DotDiagnosticTracer(); + var op = MockResolveOperation(); + tracer.OnOperationStart(new OperationStartDiagnosticData(op, MockResolveRequest())); + + // Should have a request start before ending, but make sure we don't + // explode if something weird happens. + tracer.OnRequestSuccess(new RequestDiagnosticData(op, MockResolveRequestContext())); + } + + [Fact] + public void OnRequestFailure_NoStartOperation() + { + var tracer = new DotDiagnosticTracer(); + var op = MockResolveOperation(); + tracer.OnOperationStart(new OperationStartDiagnosticData(op, MockResolveRequest())); + + // Should have a request start before ending, but make sure we don't + // explode if something weird happens. + tracer.OnRequestFailure(new RequestFailureDiagnosticData(op, MockResolveRequestContext(), new DivideByZeroException())); + } + + [Fact] + public void OnMiddlewareStart_NoOperation() + { + var tracer = new DotDiagnosticTracer(); + tracer.OnMiddlewareStart(new MiddlewareDiagnosticData(MockResolveRequestContext(), Mock.Of())); + Assert.Equal(0, tracer.OperationsInProgress); + } + + [Fact] + public void OnMiddlewareStart_NoRequest() + { + var tracer = new DotDiagnosticTracer(); + tracer.OnOperationStart(new OperationStartDiagnosticData(MockResolveOperation(), MockResolveRequest())); + + // Middleware only happens during a request, but in the event we missed the start... + tracer.OnMiddlewareStart(new MiddlewareDiagnosticData(MockResolveRequestContext(), Mock.Of())); + } + + [Fact] + public void OnMiddlewareSuccess_NoOperation() + { + var tracer = new DotDiagnosticTracer(); + tracer.OnMiddlewareSuccess(new MiddlewareDiagnosticData(MockResolveRequestContext(), Mock.Of())); + Assert.Equal(0, tracer.OperationsInProgress); + } + + [Fact] + public void OnMiddlewareSuccess_NoRequest() + { + var tracer = new DotDiagnosticTracer(); + tracer.OnOperationStart(new OperationStartDiagnosticData(MockResolveOperation(), MockResolveRequest())); + + // Middleware only happens during a request, but in the event we missed the start... + tracer.OnMiddlewareSuccess(new MiddlewareDiagnosticData(MockResolveRequestContext(), Mock.Of())); + } + + [Fact] + public void OnMiddlewareSuccess_NoMiddleware() + { + var tracer = new DotDiagnosticTracer(); + var reqCtx = MockResolveRequestContext(); + var op = reqCtx.Operation; + tracer.OnOperationStart(new OperationStartDiagnosticData(op, MockResolveRequest())); + tracer.OnRequestStart(new RequestDiagnosticData(op, reqCtx)); + + // Middleware should have a start before success... + tracer.OnMiddlewareSuccess(new MiddlewareDiagnosticData(reqCtx, Mock.Of())); + } + + [Fact] + public void OnMiddlewareFailure_NoOperation() + { + var tracer = new DotDiagnosticTracer(); + tracer.OnMiddlewareFailure(new MiddlewareDiagnosticData(MockResolveRequestContext(), Mock.Of())); + Assert.Equal(0, tracer.OperationsInProgress); + } + + [Fact] + public void OnMiddlewareFailure_NoRequest() + { + var tracer = new DotDiagnosticTracer(); + tracer.OnOperationStart(new OperationStartDiagnosticData(MockResolveOperation(), MockResolveRequest())); + + // Middleware only happens during a request, but in the event we missed the start... + tracer.OnMiddlewareFailure(new MiddlewareDiagnosticData(MockResolveRequestContext(), Mock.Of())); + } + + [Fact] + public void OnMiddlewareFailure_NoMiddleware() + { + var tracer = new DotDiagnosticTracer(); + var reqCtx = MockResolveRequestContext(); + var op = reqCtx.Operation; + tracer.OnOperationStart(new OperationStartDiagnosticData(op, MockResolveRequest())); + tracer.OnRequestStart(new RequestDiagnosticData(op, reqCtx)); + + // Middleware should have a start before success... + tracer.OnMiddlewareFailure(new MiddlewareDiagnosticData(reqCtx, Mock.Of())); + } + + private static ResolveOperation MockResolveOperation() + { + var container = new ContainerBuilder().Build(); + var scope = container.BeginLifetimeScope() as ISharingLifetimeScope; + return new ResolveOperation(scope); + } + + private static ResolveRequest MockResolveRequest() + { + return new ResolveRequest( + new TypedService(typeof(string)), + new ServiceRegistration(Mock.Of(), Mock.Of()), + Enumerable.Empty()); + } + + private static ResolveRequestContext MockResolveRequestContext() + { + var operation = MockResolveOperation(); + var request = MockResolveRequest(); + return new ResolveRequestContext(operation, request, operation.CurrentScope); + } } } From ad1b0520f4fe55bf406be690bcf2ca610cc0dba8 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Tue, 14 Jul 2020 14:54:35 -0700 Subject: [PATCH 26/45] Removed middleware operations from the DOT tracer. --- .../Diagnostics/DefaultDiagnosticTracer.cs | 4 +- .../Core/Diagnostics/DiagnosticTracerBase.cs | 6 +- .../Core/Diagnostics/DotDiagnosticTracer.cs | 172 ++++-------------- .../FullOperationDiagnosticTracerBase.cs | 66 ------- .../OperationDiagnosticTracerBase.cs | 101 ++++++++++ .../OperationTraceCompletedArgs.cs | 2 +- ... => OperationDiagnosticTracerBaseTests.cs} | 24 ++- 7 files changed, 159 insertions(+), 216 deletions(-) delete mode 100644 src/Autofac/Core/Diagnostics/FullOperationDiagnosticTracerBase.cs create mode 100644 src/Autofac/Core/Diagnostics/OperationDiagnosticTracerBase.cs rename test/Autofac.Test/Core/Diagnostics/{FullOperationDiagnosticTracerBaseTests.cs => OperationDiagnosticTracerBaseTests.cs} (70%) diff --git a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs index e64f45897..67bbc999a 100644 --- a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs @@ -9,7 +9,7 @@ namespace Autofac.Core.Diagnostics /// /// Provides a default resolve pipeline tracer that builds a multi-line /// string describing the end-to-end operation flow. Attach to the - /// + /// /// event to receive notifications when new trace content is available. /// /// @@ -19,7 +19,7 @@ namespace Autofac.Core.Diagnostics /// logical activity can be captured. /// /// - public class DefaultDiagnosticTracer : FullOperationDiagnosticTracerBase + public class DefaultDiagnosticTracer : OperationDiagnosticTracerBase { private const string RequestExceptionTraced = "__RequestException"; diff --git a/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs b/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs index 891a669f9..bddeb434a 100644 --- a/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs +++ b/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs @@ -73,7 +73,7 @@ public abstract class DiagnosticTracerBase : IObserver /// /// - public virtual void Enable(string diagnosticName) + public void Enable(string diagnosticName) { if (diagnosticName == null) { @@ -129,7 +129,7 @@ public void EnableAll() /// /// /// - public virtual void Disable(string diagnosticName) + public void Disable(string diagnosticName) { if (diagnosticName == null) { @@ -146,7 +146,7 @@ public virtual void Disable(string diagnosticName) /// /// The name of the event to check. Diagnostic names are case-sensitive. /// - public virtual bool IsEnabled(string diagnosticName) + public bool IsEnabled(string diagnosticName) { if (_subscriptions.Count == 0) { diff --git a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs index 4ce6d0ce7..bd2856843 100644 --- a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs @@ -37,7 +37,7 @@ namespace Autofac.Core.Diagnostics /// /// Provides a resolve pipeline tracer that generates DOT graph output /// traces for an end-to-end operation flow. Attach to the - /// + /// /// event to receive notifications when a new graph is available. /// /// @@ -47,7 +47,7 @@ namespace Autofac.Core.Diagnostics /// logical activity can be captured. /// /// - public class DotDiagnosticTracer : FullOperationDiagnosticTracerBase + public class DotDiagnosticTracer : OperationDiagnosticTracerBase { /// /// Metadata flag to help deduplicate the number of places where the exception is traced. @@ -56,11 +56,21 @@ public class DotDiagnosticTracer : FullOperationDiagnosticTracerBase private readonly ConcurrentDictionary _operationBuilders = new ConcurrentDictionary(); + private static readonly string[] DotEvents = new string[] + { + DiagnosticEventKeys.OperationStart, + DiagnosticEventKeys.OperationFailure, + DiagnosticEventKeys.OperationSuccess, + DiagnosticEventKeys.RequestStart, + DiagnosticEventKeys.RequestFailure, + DiagnosticEventKeys.RequestSuccess, + }; + /// /// Initializes a new instance of the class. /// public DotDiagnosticTracer() - : base() + : base(DotEvents) { } @@ -101,48 +111,6 @@ public override void OnRequestStart(RequestDiagnosticData data) } } - /// - public override void OnMiddlewareStart(MiddlewareDiagnosticData data) - { - if (data is null) - { - return; - } - - if (_operationBuilders.TryGetValue(data.RequestContext.Operation, out var builder)) - { - builder.OnMiddlewareStart(data.Middleware.ToString()); - } - } - - /// - public override void OnMiddlewareFailure(MiddlewareDiagnosticData data) - { - if (data is null) - { - return; - } - - if (_operationBuilders.TryGetValue(data.RequestContext.Operation, out var builder)) - { - builder.OnMiddlewareFailure(); - } - } - - /// - public override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) - { - if (data is null) - { - return; - } - - if (_operationBuilders.TryGetValue(data.RequestContext.Operation, out var builder)) - { - builder.OnMiddlewareSuccess(); - } - } - /// public override void OnRequestFailure(RequestFailureDiagnosticData data) { @@ -237,6 +205,17 @@ private abstract class DotGraphNode public string Id { get; } = "n" + Guid.NewGuid().ToString("N"); public bool Success { get; set; } + + public List Children { get; } = new List(); + + public virtual void ToString(StringBuilder stringBuilder) + { + foreach (var child in Children) + { + child.ToString(stringBuilder); + stringBuilder.ConnectNodes(Id, child.Id, !child.Success); + } + } } private class ResolveRequestNode : DotGraphNode @@ -245,7 +224,6 @@ public ResolveRequestNode(string service, string component) { Service = service; Component = component; - Middleware = new List(); } public string Service { get; private set; } @@ -258,9 +236,7 @@ public ResolveRequestNode(string service, string component) public string? InstanceType { get; set; } - public List Middleware { get; } - - public void ToString(StringBuilder stringBuilder) + public override void ToString(StringBuilder stringBuilder) { stringBuilder.StartTableNode(Id, border: 1) .AppendTableRow(TracerMessages.ServiceDisplay, Service) @@ -282,25 +258,7 @@ public void ToString(StringBuilder stringBuilder) } stringBuilder.EndTableNode(); - - if (Middleware.Count != 0) - { - var middlewareSubgraphId = "mw" + Id; - stringBuilder - .ConnectNodes(Id, middlewareSubgraphId, Middleware.Any(mw => !mw.Success)) - .StartTableNode(middlewareSubgraphId, cellBorder: 1); - - foreach (var mw in Middleware) - { - stringBuilder.AppendTableRow(!mw.Success, mw.Id, "{0}", mw.Name); - } - - stringBuilder.EndTableNode(); - foreach (var mw in Middleware) - { - mw.ToString(stringBuilder, middlewareSubgraphId); - } - } + base.ToString(stringBuilder); } } @@ -310,9 +268,7 @@ private class OperationNode : DotGraphNode public string? InstanceType { get; set; } - public List ResolveRequests { get; } = new List(); - - public void ToString(StringBuilder stringBuilder) + public override void ToString(StringBuilder stringBuilder) { stringBuilder.Append(Id); stringBuilder.Append(" [label=\""); @@ -329,34 +285,7 @@ public void ToString(StringBuilder stringBuilder) stringBuilder.Append("\"]"); stringBuilder.AppendLine(); - foreach (var request in ResolveRequests) - { - request.ToString(stringBuilder); - stringBuilder.ConnectNodes(Id, request.Id, !request.Success); - } - } - } - - private class MiddlewareNode : DotGraphNode - { - public MiddlewareNode(string name) - { - Name = name; - ResolveRequests = new List(); - } - - public string Name { get; } - - public List ResolveRequests { get; } - - public void ToString(StringBuilder stringBuilder, string middlewareSubgraphId) - { - var fullId = string.Format(CultureInfo.CurrentCulture, "{0}:{1}", middlewareSubgraphId, Id); - foreach (var request in ResolveRequests) - { - stringBuilder.ConnectNodes(fullId, request.Id, !request.Success); - request.ToString(stringBuilder); - } + base.ToString(stringBuilder); } } @@ -369,8 +298,6 @@ private class DotGraphBuilder public Stack ResolveRequests { get; } = new Stack(); - public Stack Middlewares { get; } = new Stack(); - public DotGraphBuilder() { Root = new OperationNode(); @@ -396,13 +323,13 @@ public void OnRequestStart(string service, string component, string? decoratorTa request.DecoratorTarget = decoratorTarget; } - if (Middlewares.Count != 0) + if (ResolveRequests.Count != 0) { - Middlewares.Peek().ResolveRequests.Add(request); + ResolveRequests.Peek().Children.Add(request); } else { - Root.ResolveRequests.Add(request); + Root.Children.Add(request); } ResolveRequests.Push(request); @@ -434,43 +361,6 @@ public void OnRequestSuccess(string? instanceType) request.InstanceType = instanceType; } - public void OnMiddlewareStart(string middleware) - { - if (ResolveRequests.Count == 0) - { - // Middleware only happens in context of a request. - return; - } - - var mw = new MiddlewareNode(middleware); - ResolveRequests.Peek().Middleware.Add(mw); - Middlewares.Push(mw); - } - - public void OnMiddlewareFailure() - { - if (Middlewares.Count == 0) - { - // We somehow missed the start event. - return; - } - - var mw = Middlewares.Pop(); - mw.Success = false; - } - - public void OnMiddlewareSuccess() - { - if (Middlewares.Count == 0) - { - // We somehow missed the start event. - return; - } - - var mw = Middlewares.Pop(); - mw.Success = true; - } - public override string ToString() { var builder = new StringBuilder(); diff --git a/src/Autofac/Core/Diagnostics/FullOperationDiagnosticTracerBase.cs b/src/Autofac/Core/Diagnostics/FullOperationDiagnosticTracerBase.cs deleted file mode 100644 index 0c8651930..000000000 --- a/src/Autofac/Core/Diagnostics/FullOperationDiagnosticTracerBase.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Globalization; -using System.Text; -using Autofac.Core.Resolving; - -namespace Autofac.Core.Diagnostics -{ - /// - /// Base class for tracers that require all operations for logical operation tracing. - /// - /// - /// - /// Derived classes will be subscribed to all Autofac diagnostic events - /// and will raise an - /// event when a logical operation has finished and trace data is available. - /// - /// - public abstract class FullOperationDiagnosticTracerBase : DiagnosticTracerBase - { - /// - /// Initializes a new instance of the class. - /// - protected FullOperationDiagnosticTracerBase() - { - EnableAll(); - } - - /// - public override void Enable(string diagnosticName) - { - // Do nothing. Default is always enabled for everything. - } - - /// - public override void Disable(string diagnosticName) - { - // Do nothing. Default is always enabled for everything. - } - - /// - /// Event raised when a resolve operation completes and trace data is available. - /// - public event EventHandler? OperationCompleted; - - /// - /// Gets the number of operations in progress being traced. - /// - /// - /// An with the number of trace IDs associated - /// with in-progress operations being traced by this tracer. - /// - public abstract int OperationsInProgress { get; } - - /// - /// Invokes the event. - /// - /// - /// The arguments to provide in the raised event. - /// - protected virtual void OnOperationCompleted(OperationTraceCompletedArgs args) - { - OperationCompleted?.Invoke(this, args); - } - } -} diff --git a/src/Autofac/Core/Diagnostics/OperationDiagnosticTracerBase.cs b/src/Autofac/Core/Diagnostics/OperationDiagnosticTracerBase.cs new file mode 100644 index 000000000..ed4756b1d --- /dev/null +++ b/src/Autofac/Core/Diagnostics/OperationDiagnosticTracerBase.cs @@ -0,0 +1,101 @@ +// This software is part of the Autofac IoC container +// Copyright © 2020 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using Autofac.Core.Resolving; + +namespace Autofac.Core.Diagnostics +{ + /// + /// Base class for tracers that require all operations for logical operation tracing. + /// + /// + /// + /// Derived classes will be subscribed to all Autofac diagnostic events + /// and will raise an + /// event when a logical operation has finished and trace data is available. + /// + /// + public abstract class OperationDiagnosticTracerBase : DiagnosticTracerBase + { + /// + /// Initializes a new instance of the class + /// and enables all subscriptions. + /// + protected OperationDiagnosticTracerBase() + { + EnableAll(); + } + + /// + /// Initializes a new instance of the class + /// and enables a specified set of subscriptions. + /// + /// + /// The set of subscriptions that should be enabled by default. + /// + protected OperationDiagnosticTracerBase(IEnumerable subscriptions) + { + if (subscriptions == null) + { + throw new ArgumentNullException(nameof(subscriptions)); + } + + foreach (var subscription in subscriptions) + { + Enable(subscription); + } + } + + /// + /// Event raised when a resolve operation completes and trace data is available. + /// + public event EventHandler? OperationCompleted; + + /// + /// Gets the number of operations in progress being traced. + /// + /// + /// An with the number of trace IDs associated + /// with in-progress operations being traced by this tracer. + /// + public abstract int OperationsInProgress { get; } + + /// + /// Invokes the event. + /// + /// + /// The arguments to provide in the raised event. + /// + protected virtual void OnOperationCompleted(OperationTraceCompletedArgs args) + { + OperationCompleted?.Invoke(this, args); + } + } +} diff --git a/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs b/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs index 04b871d6b..aef9ff962 100644 --- a/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs +++ b/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs @@ -28,7 +28,7 @@ namespace Autofac.Core.Diagnostics { /// - /// Event data for the event. + /// Event data for the event. /// public sealed class OperationTraceCompletedArgs { diff --git a/test/Autofac.Test/Core/Diagnostics/FullOperationDiagnosticTracerBaseTests.cs b/test/Autofac.Test/Core/Diagnostics/OperationDiagnosticTracerBaseTests.cs similarity index 70% rename from test/Autofac.Test/Core/Diagnostics/FullOperationDiagnosticTracerBaseTests.cs rename to test/Autofac.Test/Core/Diagnostics/OperationDiagnosticTracerBaseTests.cs index adf15eac8..86923f36f 100644 --- a/test/Autofac.Test/Core/Diagnostics/FullOperationDiagnosticTracerBaseTests.cs +++ b/test/Autofac.Test/Core/Diagnostics/OperationDiagnosticTracerBaseTests.cs @@ -23,12 +23,13 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. +using System.Collections.Generic; using Autofac.Core.Diagnostics; using Xunit; namespace Autofac.Test.Core.Diagnostics { - public class FullOperationDiagnosticTracerBaseTests + public class OperationDiagnosticTracerBaseTests { [InlineData(DiagnosticEventKeys.MiddlewareStart)] [InlineData(DiagnosticEventKeys.MiddlewareFailure)] @@ -40,19 +41,36 @@ public class FullOperationDiagnosticTracerBaseTests [InlineData(DiagnosticEventKeys.RequestStart)] [InlineData(DiagnosticEventKeys.RequestSuccess)] [Theory] - public void Ctor_SubscribesToAllEvents(string key) + public void Ctor_DefaultSubscribesToAllEvents(string key) { var tracer = new TestTracer(); Assert.True(tracer.IsEnabled(key)); } - private class TestTracer : FullOperationDiagnosticTracerBase + [Fact] + public void Ctor_SpecifySubscriptions() + { + var tracer = new TestTracer(new string[] { DiagnosticEventKeys.RequestStart, DiagnosticEventKeys.RequestSuccess }); + Assert.True(tracer.IsEnabled(DiagnosticEventKeys.RequestStart)); + Assert.True(tracer.IsEnabled(DiagnosticEventKeys.RequestSuccess)); + + // Others shouldn't be enabled. + Assert.False(tracer.IsEnabled(DiagnosticEventKeys.RequestFailure)); + Assert.False(tracer.IsEnabled(DiagnosticEventKeys.MiddlewareFailure)); + } + + private class TestTracer : OperationDiagnosticTracerBase { public TestTracer() : base() { } + public TestTracer(IEnumerable subscriptions) + : base(subscriptions) + { + } + public override int OperationsInProgress => throw new System.NotImplementedException(); } } From c48b53db1aef2acb966a813ed0a63331829e90a2 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Wed, 15 Jul 2020 11:10:03 -0700 Subject: [PATCH 27/45] Graph overhaul and file header update. --- .../Diagnostics/DefaultDiagnosticTracer.cs | 5 +- .../Core/Diagnostics/DiagnosticEventKeys.cs | 26 +-- .../Diagnostics/DiagnosticSourceExtensions.cs | 26 +-- .../Core/Diagnostics/DiagnosticTracerBase.cs | 26 +-- .../Core/Diagnostics/DotDiagnosticTracer.cs | 166 ++++++++--------- .../Core/Diagnostics/DotStringExtensions.cs | 176 +++++++++--------- .../Diagnostics/MiddlewareDiagnosticData.cs | 26 +-- .../OperationDiagnosticTracerBase.cs | 30 +-- .../OperationFailureDiagnosticData.cs | 26 +-- .../OperationStartDiagnosticData.cs | 26 +-- .../OperationSuccessDiagnosticData.cs | 26 +-- .../OperationTraceCompletedArgs.cs | 26 +-- .../Core/Diagnostics/RequestDiagnosticData.cs | 26 +-- .../RequestFailureDiagnosticData.cs | 26 +-- .../DefaultDiagnosticTracerTests.cs | 26 +-- .../Diagnostics/DiagnosticTracerBaseTests.cs | 26 +-- .../Diagnostics/DotDiagnosticTracerTests.cs | 26 +-- .../DiagnosticSourceExtensionsTests.cs | 26 +-- .../Diagnostics/DiagnosticTracerBaseTests.cs | 26 +-- .../Diagnostics/DotDiagnosticTracerTests.cs | 26 +-- .../Diagnostics/DotStringExtensionsTests.cs | 38 ++++ .../OperationDiagnosticTracerBaseTests.cs | 26 +-- 22 files changed, 245 insertions(+), 612 deletions(-) create mode 100644 test/Autofac.Test/Core/Diagnostics/DotStringExtensionsTests.cs diff --git a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs index 67bbc999a..cdeeaaddd 100644 --- a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; using System.Collections.Concurrent; using System.Globalization; using System.Text; diff --git a/src/Autofac/Core/Diagnostics/DiagnosticEventKeys.cs b/src/Autofac/Core/Diagnostics/DiagnosticEventKeys.cs index 86c9ec653..6b042ae38 100644 --- a/src/Autofac/Core/Diagnostics/DiagnosticEventKeys.cs +++ b/src/Autofac/Core/Diagnostics/DiagnosticEventKeys.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using System; using System.Diagnostics; diff --git a/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs b/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs index bd38c2d49..fbe376e26 100644 --- a/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs +++ b/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using System; using System.Diagnostics; diff --git a/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs b/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs index bddeb434a..c44243fc3 100644 --- a/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs +++ b/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using System; using System.Collections.Generic; diff --git a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs index bd2856843..bad26fd6c 100644 --- a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using System; using System.Collections.Concurrent; @@ -92,10 +70,11 @@ public override void OnOperationStart(OperationStartDiagnosticData data) } var builder = _operationBuilders.GetOrAdd(data.Operation, k => new DotGraphBuilder()); + builder.OnOperationStart(data.Operation.InitiatingRequest?.Service.Description); } /// - public override void OnRequestStart(RequestDiagnosticData data) + public override void OnOperationSuccess(OperationSuccessDiagnosticData data) { if (data is null) { @@ -104,15 +83,20 @@ public override void OnRequestStart(RequestDiagnosticData data) if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { - builder.OnRequestStart( - data.RequestContext.Service.ToString(), - data.RequestContext.Registration.Activator.DisplayName(), - data.RequestContext.DecoratorTarget?.Activator.DisplayName()); + try + { + builder.OnOperationSuccess(); + OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); + } + finally + { + _operationBuilders.TryRemove(data.Operation, out var _); + } } } /// - public override void OnRequestFailure(RequestFailureDiagnosticData data) + public override void OnOperationFailure(OperationFailureDiagnosticData data) { if (data is null) { @@ -121,27 +105,20 @@ public override void OnRequestFailure(RequestFailureDiagnosticData data) if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { - var requestException = data.RequestException; - if (requestException is DependencyResolutionException && requestException.InnerException is object) - { - requestException = requestException.InnerException; - } - - if (requestException.Data.Contains(RequestExceptionTraced)) + try { - builder.OnRequestFailure(null); + builder.OnOperationFailure(); + OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); } - else + finally { - builder.OnRequestFailure(requestException); + _operationBuilders.TryRemove(data.Operation, out var _); } - - requestException.Data[RequestExceptionTraced] = true; } } /// - public override void OnRequestSuccess(RequestDiagnosticData data) + public override void OnRequestStart(RequestDiagnosticData data) { if (data is null) { @@ -150,12 +127,15 @@ public override void OnRequestSuccess(RequestDiagnosticData data) if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { - builder.OnRequestSuccess(data.RequestContext.Instance?.GetType().ToString()); + builder.OnRequestStart( + data.RequestContext.Service.ToString(), + data.RequestContext.Registration.Activator.DisplayName(), + data.RequestContext.DecoratorTarget?.Activator.DisplayName()); } } /// - public override void OnOperationFailure(OperationFailureDiagnosticData data) + public override void OnRequestSuccess(RequestDiagnosticData data) { if (data is null) { @@ -164,21 +144,12 @@ public override void OnOperationFailure(OperationFailureDiagnosticData data) if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { - try - { - builder.OnOperationFailure(data.OperationException); - - OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); - } - finally - { - _operationBuilders.TryRemove(data.Operation, out var _); - } + builder.OnRequestSuccess(data.RequestContext.Instance?.GetType().ToString()); } } /// - public override void OnOperationSuccess(OperationSuccessDiagnosticData data) + public override void OnRequestFailure(RequestFailureDiagnosticData data) { if (data is null) { @@ -187,16 +158,22 @@ public override void OnOperationSuccess(OperationSuccessDiagnosticData data) if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { - try + var requestException = data.RequestException; + if (requestException is DependencyResolutionException && requestException.InnerException is object) { - builder.OnOperationSuccess(data.ResolvedInstance?.GetType().ToString()); + requestException = requestException.InnerException; + } - OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); + if (requestException.Data.Contains(RequestExceptionTraced)) + { + builder.OnRequestFailure(null); } - finally + else { - _operationBuilders.TryRemove(data.Operation, out var _); + builder.OnRequestFailure(requestException); } + + requestException.Data[RequestExceptionTraced] = true; } } @@ -208,14 +185,7 @@ private abstract class DotGraphNode public List Children { get; } = new List(); - public virtual void ToString(StringBuilder stringBuilder) - { - foreach (var child in Children) - { - child.ToString(stringBuilder); - stringBuilder.ConnectNodes(Id, child.Id, !child.Success); - } - } + public abstract void ToString(StringBuilder stringBuilder); } private class ResolveRequestNode : DotGraphNode @@ -238,8 +208,9 @@ public ResolveRequestNode(string service, string component) public override void ToString(StringBuilder stringBuilder) { - stringBuilder.StartTableNode(Id, border: 1) - .AppendTableRow(TracerMessages.ServiceDisplay, Service) + var shape = DecoratorTarget == null ? "component" : "box3d"; + stringBuilder.StartNode(Id, shape, Success) + .AppendTableHeader(Service) .AppendTableRow(TracerMessages.ComponentDisplay, Component); if (DecoratorTarget is object) @@ -254,38 +225,46 @@ public override void ToString(StringBuilder stringBuilder) if (Exception is object) { - stringBuilder.AppendTableRow(TracerMessages.ExceptionDisplay, Exception.ToString()); + stringBuilder.AppendTableErrorRow(Exception.GetType().FullName, Exception.Message); } - stringBuilder.EndTableNode(); - base.ToString(stringBuilder); + stringBuilder.EndNode(); + foreach (var child in Children) + { + child.ToString(stringBuilder); + stringBuilder.ConnectNodes(Id, child.Id, !child.Success); + } } } private class OperationNode : DotGraphNode { - public Exception? Exception { get; set; } - - public string? InstanceType { get; set; } + public string? Service { get; set; } public override void ToString(StringBuilder stringBuilder) { - stringBuilder.Append(Id); - stringBuilder.Append(" [label=\""); - - if (InstanceType is object) + // Graph header + stringBuilder.Append("label=<"); + if (!Success) { - stringBuilder.Append(InstanceType); + stringBuilder.Append(""); } - else if (Exception is object) + + stringBuilder.Append(HttpUtility.HtmlEncode(Service)); + if (!Success) { - stringBuilder.Append(HttpUtility.HtmlEncode(Exception.ToString()).NewlineReplace("\\l")); - stringBuilder.Append("\\l"); + stringBuilder.Append(""); } - stringBuilder.Append("\"]"); + stringBuilder.Append(">;"); stringBuilder.AppendLine(); - base.ToString(stringBuilder); + stringBuilder.AppendLine("labelloc=t"); + foreach (var child in Children) + { + // We _should_ only have one child here - the + // initiating request. + child.ToString(stringBuilder); + } } } @@ -303,16 +282,19 @@ public DotGraphBuilder() Root = new OperationNode(); } - public void OnOperationFailure(Exception? operationException) + public void OnOperationStart(string? service) + { + Root.Service = service; + } + + public void OnOperationFailure() { Root.Success = false; - Root.Exception = operationException; } - public void OnOperationSuccess(string? instanceType) + public void OnOperationSuccess() { Root.Success = true; - Root.InstanceType = instanceType; } public void OnRequestStart(string service, string component, string? decoratorTarget) @@ -329,6 +311,8 @@ public void OnRequestStart(string service, string component, string? decoratorTa } else { + // The initiating request will be the first request + // we see, and should be the last one off the stack. Root.Children.Add(request); } diff --git a/src/Autofac/Core/Diagnostics/DotStringExtensions.cs b/src/Autofac/Core/Diagnostics/DotStringExtensions.cs index c53a26df9..6ebb9339c 100644 --- a/src/Autofac/Core/Diagnostics/DotStringExtensions.cs +++ b/src/Autofac/Core/Diagnostics/DotStringExtensions.cs @@ -1,29 +1,8 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using System; +using System.Collections.Generic; using System.Globalization; using System.Text; using System.Web; @@ -35,6 +14,11 @@ namespace Autofac.Core.Diagnostics /// internal static class DotStringExtensions { + /// + /// The set of characters on which a line is allowed to wrap. + /// + private static readonly char[] WrapCharacters = new[] { ' ', ',', '.', '?', '!', ':', ';', '-', '\n', '\r', '\t' }; + /// /// Starts a DOT graph node where the label is an HTML table. /// @@ -44,32 +28,26 @@ internal static class DotStringExtensions /// /// The node ID. You can connect to this node by this ID. /// - /// - /// Size of the table border. - /// - /// - /// Size of the border around individual cells. + /// + /// The shape the node should take, like component, box3d, or plaintext. /// - /// - /// Space between the text in the cell and the cell wall. - /// - /// - /// Space between the cells. + /// + /// to indicate the operation was successful; to + /// highlight the operation as a failure path. /// /// /// The for continued writing. /// - public static StringBuilder StartTableNode(this StringBuilder stringBuilder, string id, int border = 0, int cellBorder = 0, int cellPadding = 5, int cellSpacing = 0) + public static StringBuilder StartNode(this StringBuilder stringBuilder, string id, string shape, bool success) { stringBuilder.AppendFormat( CultureInfo.CurrentCulture, - "{0} [shape=plaintext,label=<", + "{0} [shape={1},{2}label=<", id, - border, - cellBorder, - cellPadding, - cellSpacing); + shape, + success ? null : "penwidth=3,color=red,"); stringBuilder.AppendLine(); + stringBuilder.AppendLine("
"); return stringBuilder; } @@ -82,9 +60,30 @@ public static StringBuilder StartTableNode(this StringBuilder stringBuilder, str /// /// The for continued writing. /// - public static StringBuilder EndTableNode(this StringBuilder stringBuilder) + public static StringBuilder EndNode(this StringBuilder stringBuilder) { - stringBuilder.Append("
>];"); + stringBuilder.AppendLine(""); + stringBuilder.AppendLine(">];"); + return stringBuilder; + } + + /// + /// Writes a table header to an HTML table node in a DOT graph. + /// + /// + /// The to which the node should be written. + /// + /// + /// A string that will be displayed as a header in the table. + /// + /// + /// The for continued writing. + /// + public static StringBuilder AppendTableHeader(this StringBuilder stringBuilder, string header) + { + stringBuilder.Append(""); + stringBuilder.Append(header.Encode()); + stringBuilder.Append(""); stringBuilder.AppendLine(); return stringBuilder; } @@ -106,14 +105,15 @@ public static StringBuilder EndTableNode(this StringBuilder stringBuilder) /// public static StringBuilder AppendTableRow(this StringBuilder stringBuilder, string format, params string[] args) { - stringBuilder.Append(""); + stringBuilder.Append(""); for (var i = 0; i < args.Length; i++) { - args[i] = HttpUtility.HtmlEncode(args[i]).NewlineReplace("
"); + args[i] = args[i].Encode(); } stringBuilder.AppendFormat(CultureInfo.CurrentCulture, format, args); - stringBuilder.Append(""); + stringBuilder.Append("
"); + stringBuilder.AppendLine(); return stringBuilder; } @@ -123,41 +123,22 @@ public static StringBuilder AppendTableRow(this StringBuilder stringBuilder, str /// /// The to which the node should be written. /// - /// - /// if the text in the cell should be bold; if not. - /// - /// - /// The cell/row ID. You can connect to this cell by this ID. + /// + /// The full exception type name to display. /// - /// - /// A string into which the arguments will be formatted. - /// - /// - /// The arguments to HTML encode and put into the format string. + /// + /// The message from the exception. /// /// /// The for continued writing. /// - public static StringBuilder AppendTableRow(this StringBuilder stringBuilder, bool bold, string id, string format, params string[] args) + public static StringBuilder AppendTableErrorRow(this StringBuilder stringBuilder, string exceptionType, string exceptionMessage) { - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "", id); - if (bold) - { - stringBuilder.Append(""); - } - - for (var i = 0; i < args.Length; i++) - { - args[i] = HttpUtility.HtmlEncode(args[i]).NewlineReplace("
"); - } - - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, format, args); - if (bold) - { - stringBuilder.Append("
"); - } - - stringBuilder.Append(""); + stringBuilder.Append(""); + stringBuilder.Append(exceptionType.Encode()); + stringBuilder.Append(":
\n"); + stringBuilder.Append(exceptionMessage.Wrap().Encode()); + stringBuilder.Append("
"); stringBuilder.AppendLine(); return stringBuilder; } @@ -185,7 +166,7 @@ public static StringBuilder ConnectNodes(this StringBuilder stringBuilder, strin stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} -> {1}", fromId, toId); if (bold) { - stringBuilder.Append(" [penwidth=3]"); + stringBuilder.Append(" [penwidth=3,color=red]"); } stringBuilder.AppendLine(); @@ -193,27 +174,54 @@ public static StringBuilder ConnectNodes(this StringBuilder stringBuilder, strin } /// - /// Replaces the environment newline character with something else. + /// HTML-encodes and converts newlines to line break tags in an input string. /// /// - /// The string with characters to replace. - /// - /// - /// The content that should replace newlines. + /// The to encode. /// /// - /// The with newlines replaced with the specified content. + /// The encoded version of . /// - public static string NewlineReplace(this string input, string newlineReplacement) + public static string Encode(this string input) { // Pretty stoked the StringComparison overload is only in one of our // target frameworks. :( #if NETSTANDARD2_0 - return input.Replace(Environment.NewLine, newlineReplacement); + return HttpUtility.HtmlEncode(input).Replace(Environment.NewLine, "
"); #endif #if !NETSTANDARD2_0 - return input.Replace(Environment.NewLine, newlineReplacement, StringComparison.Ordinal); + return HttpUtility.HtmlEncode(input).Replace(Environment.NewLine, "
", StringComparison.Ordinal); #endif } + + /// + /// Line-wraps a long string for display in a graph. + /// + /// + /// The string to line wrap. + /// + /// + /// A line-wrapped version of the input. + /// + public static string Wrap(this string input) + { + const int maxLineLength = 40; + var list = new List(); + var lastWrap = 0; + int currentIndex; + do + { + currentIndex = lastWrap + maxLineLength > input.Length ? input.Length : (input.LastIndexOfAny(WrapCharacters, Math.Min(input.Length - 1, lastWrap + maxLineLength)) + 1); + if (currentIndex <= lastWrap) + { + currentIndex = Math.Min(lastWrap + maxLineLength, input.Length); + } + + list.Add(input.Substring(lastWrap, currentIndex - lastWrap).Trim()); + lastWrap = currentIndex; + } + while (currentIndex < input.Length); + return string.Join(Environment.NewLine, list); + } } } diff --git a/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs b/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs index f2380776a..bc18ff86c 100644 --- a/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using Autofac.Core.Resolving.Pipeline; diff --git a/src/Autofac/Core/Diagnostics/OperationDiagnosticTracerBase.cs b/src/Autofac/Core/Diagnostics/OperationDiagnosticTracerBase.cs index ed4756b1d..34cb93b6c 100644 --- a/src/Autofac/Core/Diagnostics/OperationDiagnosticTracerBase.cs +++ b/src/Autofac/Core/Diagnostics/OperationDiagnosticTracerBase.cs @@ -1,34 +1,8 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Globalization; -using System.Text; -using Autofac.Core.Resolving; namespace Autofac.Core.Diagnostics { diff --git a/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs b/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs index ce1b3e739..22377f0c3 100644 --- a/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using System; using Autofac.Core.Resolving; diff --git a/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs b/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs index 1090166c5..763abf843 100644 --- a/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using Autofac.Core.Resolving; diff --git a/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs b/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs index 1232ae525..f2bd2b14d 100644 --- a/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using Autofac.Core.Resolving; diff --git a/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs b/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs index aef9ff962..19ae91a8c 100644 --- a/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs +++ b/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using Autofac.Core.Resolving; diff --git a/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs b/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs index dd20dc3d1..a9af48170 100644 --- a/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using Autofac.Core.Resolving; using Autofac.Core.Resolving.Pipeline; diff --git a/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs b/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs index aa1b055e0..0d87c2876 100644 --- a/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs +++ b/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using System; using Autofac.Core.Resolving; diff --git a/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs b/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs index da3f48b8e..a484d265a 100644 --- a/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs +++ b/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A1 PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using System; using Autofac.Core.Diagnostics; diff --git a/test/Autofac.Specification.Test/Diagnostics/DiagnosticTracerBaseTests.cs b/test/Autofac.Specification.Test/Diagnostics/DiagnosticTracerBaseTests.cs index c6bae13a8..ba40b1731 100644 --- a/test/Autofac.Specification.Test/Diagnostics/DiagnosticTracerBaseTests.cs +++ b/test/Autofac.Specification.Test/Diagnostics/DiagnosticTracerBaseTests.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A1 PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using Autofac.Core.Diagnostics; using Xunit; diff --git a/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs b/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs index 96f25d3e9..d1e32ffc0 100644 --- a/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs +++ b/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A1 PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using System; using Autofac.Core.Diagnostics; diff --git a/test/Autofac.Test/Core/Diagnostics/DiagnosticSourceExtensionsTests.cs b/test/Autofac.Test/Core/Diagnostics/DiagnosticSourceExtensionsTests.cs index 1ec8e507b..1c9055dec 100644 --- a/test/Autofac.Test/Core/Diagnostics/DiagnosticSourceExtensionsTests.cs +++ b/test/Autofac.Test/Core/Diagnostics/DiagnosticSourceExtensionsTests.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A1 PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using System; using System.Collections.Generic; diff --git a/test/Autofac.Test/Core/Diagnostics/DiagnosticTracerBaseTests.cs b/test/Autofac.Test/Core/Diagnostics/DiagnosticTracerBaseTests.cs index 5ba809754..4485821f0 100644 --- a/test/Autofac.Test/Core/Diagnostics/DiagnosticTracerBaseTests.cs +++ b/test/Autofac.Test/Core/Diagnostics/DiagnosticTracerBaseTests.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A1 PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using Autofac.Core.Diagnostics; using Xunit; diff --git a/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs b/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs index ac1c55457..b64a054b0 100644 --- a/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs +++ b/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A1 PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using System; using System.Linq; diff --git a/test/Autofac.Test/Core/Diagnostics/DotStringExtensionsTests.cs b/test/Autofac.Test/Core/Diagnostics/DotStringExtensionsTests.cs new file mode 100644 index 000000000..a35e1ecdf --- /dev/null +++ b/test/Autofac.Test/Core/Diagnostics/DotStringExtensionsTests.cs @@ -0,0 +1,38 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using Autofac.Core.Diagnostics; +using Xunit; + +namespace Autofac.Test.Core.Diagnostics +{ + public class DotStringExtensionsTests + { + [Fact] + public void Wrap_ShortString() + { + var input = "This is short."; + var actual = DotStringExtensions.Wrap(input); + Assert.Equal(input, actual); + } + + [Fact] + public void Wrap_LongString() + { + var input = "This is a very long string. It should line wrap so it isn't this long, but that's why this test is here - to see if it line wraps or not. If it doesn't line wrap, then the function isn't working."; + var expected = "This is a very long string. It should\nline wrap so it isn't this long, but\nthat's why this test is here - to see if\nit line wraps or not. If it doesn't line\nwrap, then the function isn't working.".Replace("\n", Environment.NewLine); + var actual = DotStringExtensions.Wrap(input); + Assert.Equal(expected, actual); + } + + [Fact] + public void Wrap_LongWord() + { + var input = "Thisisunrealisticbutislongerthantheallowedlinelengthwithnowheretotbreaksowehavetoforceit."; + var expected = "Thisisunrealisticbutislongerthantheallow\nedlinelengthwithnowheretotbreaksowehavet\noforceit.".Replace("\n", Environment.NewLine); + var actual = DotStringExtensions.Wrap(input); + Assert.Equal(expected, actual); + } + } +} diff --git a/test/Autofac.Test/Core/Diagnostics/OperationDiagnosticTracerBaseTests.cs b/test/Autofac.Test/Core/Diagnostics/OperationDiagnosticTracerBaseTests.cs index 86923f36f..2ac886b77 100644 --- a/test/Autofac.Test/Core/Diagnostics/OperationDiagnosticTracerBaseTests.cs +++ b/test/Autofac.Test/Core/Diagnostics/OperationDiagnosticTracerBaseTests.cs @@ -1,27 +1,5 @@ -// This software is part of the Autofac IoC container -// Copyright © 2020 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A1 PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. using System.Collections.Generic; using Autofac.Core.Diagnostics; From 5dbebeaf9cfe8b7711cae8e6c85d0dbb77a0c2a2 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Thu, 16 Jul 2020 11:49:38 -0700 Subject: [PATCH 28/45] Graph now normalized based on dependency chain instances. --- .../Core/Diagnostics/DotDiagnosticTracer.cs | 295 ++++++++++++++---- .../Core/Diagnostics/DotStringExtensions.cs | 41 ++- 2 files changed, 267 insertions(+), 69 deletions(-) diff --git a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs index bad26fd6c..957a795a8 100644 --- a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs @@ -4,8 +4,9 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Globalization; +using System.Collections.ObjectModel; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Web; using Autofac.Core.Resolving; @@ -128,7 +129,7 @@ public override void OnRequestStart(RequestDiagnosticData data) if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { builder.OnRequestStart( - data.RequestContext.Service.ToString(), + data.RequestContext.Service, data.RequestContext.Registration.Activator.DisplayName(), data.RequestContext.DecoratorTarget?.Activator.DisplayName()); } @@ -144,7 +145,7 @@ public override void OnRequestSuccess(RequestDiagnosticData data) if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { - builder.OnRequestSuccess(data.RequestContext.Instance?.GetType().ToString()); + builder.OnRequestSuccess(data.RequestContext.Instance); } } @@ -177,50 +178,57 @@ public override void OnRequestFailure(RequestFailureDiagnosticData data) } } - private abstract class DotGraphNode - { - public string Id { get; } = "n" + Guid.NewGuid().ToString("N"); - - public bool Success { get; set; } - - public List Children { get; } = new List(); - - public abstract void ToString(StringBuilder stringBuilder); - } - - private class ResolveRequestNode : DotGraphNode + /// + /// One node in the graph. The resulting instance (for successful requests) + /// is what uniquely identifies the node when normalizing the data. This + /// converts the notion of a "resolve request" into a dependency graph + /// based on completed resolutions. + /// + private class ResolveRequestNode { - public ResolveRequestNode(string service, string component) + public ResolveRequestNode(string component) { - Service = service; + Services = new Dictionary(); Component = component; + Id = Guid.NewGuid(); + Edges = new HashSet(); } - public string Service { get; private set; } + public Guid Id { get; } + + public Dictionary Services { get; private set; } public string Component { get; private set; } public string? DecoratorTarget { get; set; } + public bool Success { get; set; } + public Exception? Exception { get; set; } - public string? InstanceType { get; set; } + public object? Instance { get; set; } + + public HashSet Edges { get; } - public override void ToString(StringBuilder stringBuilder) + public void ToString(StringBuilder stringBuilder, RequestDictionary allRequests) { var shape = DecoratorTarget == null ? "component" : "box3d"; - stringBuilder.StartNode(Id, shape, Success) - .AppendTableHeader(Service) - .AppendTableRow(TracerMessages.ComponentDisplay, Component); + stringBuilder.StartNode(Id, shape, Success); + foreach (var service in Services.Keys) + { + stringBuilder.AppendServiceRow(service.Description, Services[service]); + } + + stringBuilder.AppendTableRow(TracerMessages.ComponentDisplay, Component); if (DecoratorTarget is object) { stringBuilder.AppendTableRow(TracerMessages.TargetDisplay, DecoratorTarget); } - if (InstanceType is object) + if (Instance is object) { - stringBuilder.AppendTableRow(TracerMessages.InstanceDisplay, InstanceType); + stringBuilder.AppendTableRow(TracerMessages.InstanceDisplay, Instance.GetType().FullName); } if (Exception is object) @@ -229,19 +237,27 @@ public override void ToString(StringBuilder stringBuilder) } stringBuilder.EndNode(); - foreach (var child in Children) + foreach (var edge in Edges) { - child.ToString(stringBuilder); - stringBuilder.ConnectNodes(Id, child.Id, !child.Success); + // Connect into a table with the ID format "parent:tablerow" + var destination = allRequests[edge.Request]; + var edgeId = destination.Id.NodeId() + ":" + destination.Services[edge.Service].NodeId(); + stringBuilder.ConnectNodes(Id.NodeId(), edgeId, edge.Service.Description, !destination.Success); } } } - private class OperationNode : DotGraphNode + /// + /// Metadata about the operation being graphed. Used to + /// generate the graph header. + /// + private class OperationNode { public string? Service { get; set; } - public override void ToString(StringBuilder stringBuilder) + public bool Success { get; set; } + + public void ToString(StringBuilder stringBuilder) { // Graph header stringBuilder.Append("label=<"); @@ -259,97 +275,260 @@ public override void ToString(StringBuilder stringBuilder) stringBuilder.Append(">;"); stringBuilder.AppendLine(); stringBuilder.AppendLine("labelloc=t"); - foreach (var child in Children) - { - // We _should_ only have one child here - the - // initiating request. - child.ToString(stringBuilder); - } } } + /// + /// Convenience collection for accessing a request by ID + /// out of the list of all requests. + /// + private class RequestDictionary : KeyedCollection + { + protected override Guid GetKeyForItem(ResolveRequestNode item) + { + return item.Id; + } + } + + /// + /// An edge that connects two nodes (two resolve requests) in a graph. + /// The source of an edge is the request that's resolving child items; + /// the target is a specific service on a child request. + /// + private class GraphEdge : IEquatable + { + public GraphEdge(Guid request, Service service) + { + Request = request; + Service = service ?? throw new ArgumentNullException(nameof(service)); + } + + public Guid Request { get; private set; } + + public Service Service { get; private set; } + + public bool Equals(GraphEdge? other) + { + return + other is object && + other.Request == Request && + ((other.Service is object && Service is object && other.Service.Equals(Service)) || + (other.Service is null && Service is null)); + } + + public override bool Equals(object? obj) + { + return Equals(obj as GraphEdge); + } + + public override int GetHashCode() + { + // This doesn't have to be great; we don't really use it + // but analyzers complain since we do need equality. + return Request.GetHashCode() ^ Service.GetHashCode(); + } + } + + /// + /// Equality comparer that determines if two resolve requests are effectively the + /// same based on the returned instance. Used to find "duplicates" in the graph + /// during normalization. + /// + private class InstanceEqualityComparer : IEqualityComparer + { + public static InstanceEqualityComparer Default { get; } = new InstanceEqualityComparer(); + + public bool Equals(ResolveRequestNode x, ResolveRequestNode y) => ReferenceEquals(x.Instance, y.Instance); + + public int GetHashCode(ResolveRequestNode obj) => RuntimeHelpers.GetHashCode(obj.Instance); + } + /// /// Generator for DOT format graph traces. /// private class DotGraphBuilder { - public OperationNode Root { get; private set; } - - public Stack ResolveRequests { get; } = new Stack(); + /// + /// Gets the node that has operation-level data for the graph. + /// + public OperationNode Operation { get; private set; } + + /// + /// Gets the set of all requests made during the operation. + /// + public RequestDictionary Requests { get; private set; } + + /// + /// Gets the originating request ID. This will also be the first request in the + /// stack of ongoing requests. Tracked to ensure we retain the originating + /// request during the normalization of the graph. + /// + public Guid OriginatingRequest { get; private set; } + + /// + /// Gets the stack of ongoing requests. The first request in the stack is the originating + /// request where the graph should start. + /// + public Stack CurrentRequest { get; private set; } public DotGraphBuilder() { - Root = new OperationNode(); + Operation = new OperationNode(); + Requests = new RequestDictionary(); + CurrentRequest = new Stack(); } public void OnOperationStart(string? service) { - Root.Service = service; + Operation.Service = service; } public void OnOperationFailure() { - Root.Success = false; + Operation.Success = false; + NormalizeGraph(); } public void OnOperationSuccess() { - Root.Success = true; + Operation.Success = true; + NormalizeGraph(); } - public void OnRequestStart(string service, string component, string? decoratorTarget) + public void OnRequestStart(Service service, string component, string? decoratorTarget) { - var request = new ResolveRequestNode(service, component); + var request = new ResolveRequestNode(component); + request.Services.Add(service, Guid.NewGuid()); + Requests.Add(request); if (decoratorTarget is object) { request.DecoratorTarget = decoratorTarget; } - if (ResolveRequests.Count != 0) + if (CurrentRequest.Count != 0) { - ResolveRequests.Peek().Children.Add(request); + // We're already in a request, so add an edge from + // the parent to this new request/service. + var parent = Requests[CurrentRequest.Peek()]; + parent.Edges.Add(new GraphEdge(request.Id, service)); } else { - // The initiating request will be the first request - // we see, and should be the last one off the stack. - Root.Children.Add(request); + // The initiating request will be the first request we see. + OriginatingRequest = request.Id; } - ResolveRequests.Push(request); + // The inbound request is the new current. + CurrentRequest.Push(request.Id); } public void OnRequestFailure(Exception? requestException) { - if (ResolveRequests.Count == 0) + if (CurrentRequest.Count == 0) { // OnRequestFailure happened without a corresponding OnRequestStart. return; } - var request = ResolveRequests.Pop(); + var request = Requests[CurrentRequest.Pop()]; request.Success = false; request.Exception = requestException; } - public void OnRequestSuccess(string? instanceType) + public void OnRequestSuccess(object? instance) { - if (ResolveRequests.Count == 0) + if (CurrentRequest.Count == 0) { // OnRequestSuccess happened without a corresponding OnRequestStart. return; } - var request = ResolveRequests.Pop(); + var request = Requests[CurrentRequest.Pop()]; request.Success = true; - request.InstanceType = instanceType; + request.Instance = instance; + } + + private void NormalizeGraph() + { + // Remove any duplicates of the root node. We need to make sure that node in particular stays. + RemoveDuplicates(OriginatingRequest); + + // Other than the originating request, find the rest of the distinct values + // so we can de-dupe. + var unique = Requests + .Where(r => r.Success && r.Instance is object && r.Id != OriginatingRequest) + .Distinct(InstanceEqualityComparer.Default) + .Select(r => r.Id) + .ToArray(); + + foreach (var id in unique) + { + RemoveDuplicates(id); + } + } + + private void RemoveDuplicates(Guid sourceId) + { + var source = Requests[sourceId]; + if (!source.Success || source.Instance is null) + { + // We can only de-duplicate successful operations because + // failed operations don't have instances to compare. + return; + } + + var duplicates = Requests.Where(dup => + + // Successful requests where IDs are different + dup.Id != sourceId && dup.Success && + + // Instance is exactly the same + dup.Instance is object && object.ReferenceEquals(dup.Instance, source.Instance) && + + // Decorator target must also be the same (otherwise we lose the instance/decorator relationship) + dup.DecoratorTarget == source.DecoratorTarget).ToArray(); + if (duplicates.Length == 0) + { + // No duplicates. + return; + } + + foreach (var duplicate in duplicates) + { + Requests.Remove(duplicate.Id); + foreach (var request in Requests) + { + var duplicateEdges = request.Edges.Where(e => e.Request == duplicate.Id).ToArray(); + foreach (var duplicateEdge in duplicateEdges) + { + // Replace edges pointing to the duplicate so they + // point at the new source. HashSet will only keep + // unique edges, so if there was already a link to + // the source, there won't be duplicate edges. + // Also, duplicateEdge will never be null but the + // analyzer thinks it could be in that GraphEdge.ctor + // call. + request.Edges.Remove(duplicateEdge); + request.Edges.Add(new GraphEdge(sourceId, duplicateEdge!.Service)); + if (!source.Services.ContainsKey(duplicateEdge.Service)) + { + source.Services.Add(duplicateEdge.Service, Guid.NewGuid()); + } + } + } + } } public override string ToString() { var builder = new StringBuilder(); builder.AppendLine("digraph G {"); - Root.ToString(builder); + Operation.ToString(builder); + foreach (var request in Requests) + { + request.ToString(builder, Requests); + } + builder.AppendLine("}"); return builder.ToString(); } diff --git a/src/Autofac/Core/Diagnostics/DotStringExtensions.cs b/src/Autofac/Core/Diagnostics/DotStringExtensions.cs index 6ebb9339c..a34c7f664 100644 --- a/src/Autofac/Core/Diagnostics/DotStringExtensions.cs +++ b/src/Autofac/Core/Diagnostics/DotStringExtensions.cs @@ -38,12 +38,12 @@ internal static class DotStringExtensions /// /// The for continued writing. /// - public static StringBuilder StartNode(this StringBuilder stringBuilder, string id, string shape, bool success) + public static StringBuilder StartNode(this StringBuilder stringBuilder, Guid id, string shape, bool success) { stringBuilder.AppendFormat( CultureInfo.CurrentCulture, "{0} [shape={1},{2}label=<", - id, + id.NodeId(), shape, success ? null : "penwidth=3,color=red,"); stringBuilder.AppendLine(); @@ -73,16 +73,21 @@ public static StringBuilder EndNode(this StringBuilder stringBuilder) /// /// The to which the node should be written. /// - /// - /// A string that will be displayed as a header in the table. + /// + /// A string that will be displayed as a header in the table, should be the name + /// of the service being resolved. + /// + /// + /// A that uniquely identifies the service instance being + /// resolved. Used for linking from one request to a specific service. /// /// /// The for continued writing. /// - public static StringBuilder AppendTableHeader(this StringBuilder stringBuilder, string header) + public static StringBuilder AppendServiceRow(this StringBuilder stringBuilder, string service, Guid portId) { - stringBuilder.Append(""); - stringBuilder.Append(header.Encode()); + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "", portId.NodeId()); + stringBuilder.Append(service.Encode()); stringBuilder.Append(""); stringBuilder.AppendLine(); return stringBuilder; @@ -155,21 +160,24 @@ public static StringBuilder AppendTableErrorRow(this StringBuilder stringBuilder /// /// The ID of the node where the connection ends. /// + /// + /// The label on the connecting line. + /// /// /// if the text in the cell should be bold; if not. /// /// /// The for continued writing. /// - public static StringBuilder ConnectNodes(this StringBuilder stringBuilder, string fromId, string toId, bool bold) + public static StringBuilder ConnectNodes(this StringBuilder stringBuilder, string fromId, string toId, string label, bool bold) { - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} -> {1}", fromId, toId); + stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} -> {1} [label=<{2}>", fromId, toId, label.Encode()); if (bold) { - stringBuilder.Append(" [penwidth=3,color=red]"); + stringBuilder.Append(",penwidth=3,color=red"); } - stringBuilder.AppendLine(); + stringBuilder.AppendLine("]"); return stringBuilder; } @@ -223,5 +231,16 @@ public static string Wrap(this string input) while (currentIndex < input.Length); return string.Join(Environment.NewLine, list); } + + /// + /// Creates a unique string ID from a + /// that can be used to identify a node in a DOT graph. + /// + /// The ID to serialize. + /// A string version of the ID. + public static string NodeId(this Guid guid) + { + return "n" + guid.ToString("N"); + } } } From bfef17a5c2c26660bf4240402fec3d0fdc27b592 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Thu, 16 Jul 2020 12:17:22 -0700 Subject: [PATCH 29/45] Testing may not always be able to mock right; fix code to be more testable. --- .../Core/Diagnostics/DotDiagnosticTracer.cs | 9 ++- .../Diagnostics/DotDiagnosticTracerTests.cs | 80 ------------------- 2 files changed, 8 insertions(+), 81 deletions(-) diff --git a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs index 957a795a8..49e3cb4ea 100644 --- a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs @@ -469,7 +469,14 @@ private void NormalizeGraph() private void RemoveDuplicates(Guid sourceId) { - var source = Requests[sourceId]; + if (!Requests.TryGetValue(sourceId, out var source)) + { + // Should always find this value, but in testing + // sometimes the mock value uses an empty GUID or + // something that might not be here. + return; + } + if (!source.Success || source.Instance is null) { // We can only de-duplicate successful operations because diff --git a/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs b/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs index b64a054b0..b046ec629 100644 --- a/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs +++ b/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs @@ -128,86 +128,6 @@ public void OnRequestFailure_NoStartOperation() tracer.OnRequestFailure(new RequestFailureDiagnosticData(op, MockResolveRequestContext(), new DivideByZeroException())); } - [Fact] - public void OnMiddlewareStart_NoOperation() - { - var tracer = new DotDiagnosticTracer(); - tracer.OnMiddlewareStart(new MiddlewareDiagnosticData(MockResolveRequestContext(), Mock.Of())); - Assert.Equal(0, tracer.OperationsInProgress); - } - - [Fact] - public void OnMiddlewareStart_NoRequest() - { - var tracer = new DotDiagnosticTracer(); - tracer.OnOperationStart(new OperationStartDiagnosticData(MockResolveOperation(), MockResolveRequest())); - - // Middleware only happens during a request, but in the event we missed the start... - tracer.OnMiddlewareStart(new MiddlewareDiagnosticData(MockResolveRequestContext(), Mock.Of())); - } - - [Fact] - public void OnMiddlewareSuccess_NoOperation() - { - var tracer = new DotDiagnosticTracer(); - tracer.OnMiddlewareSuccess(new MiddlewareDiagnosticData(MockResolveRequestContext(), Mock.Of())); - Assert.Equal(0, tracer.OperationsInProgress); - } - - [Fact] - public void OnMiddlewareSuccess_NoRequest() - { - var tracer = new DotDiagnosticTracer(); - tracer.OnOperationStart(new OperationStartDiagnosticData(MockResolveOperation(), MockResolveRequest())); - - // Middleware only happens during a request, but in the event we missed the start... - tracer.OnMiddlewareSuccess(new MiddlewareDiagnosticData(MockResolveRequestContext(), Mock.Of())); - } - - [Fact] - public void OnMiddlewareSuccess_NoMiddleware() - { - var tracer = new DotDiagnosticTracer(); - var reqCtx = MockResolveRequestContext(); - var op = reqCtx.Operation; - tracer.OnOperationStart(new OperationStartDiagnosticData(op, MockResolveRequest())); - tracer.OnRequestStart(new RequestDiagnosticData(op, reqCtx)); - - // Middleware should have a start before success... - tracer.OnMiddlewareSuccess(new MiddlewareDiagnosticData(reqCtx, Mock.Of())); - } - - [Fact] - public void OnMiddlewareFailure_NoOperation() - { - var tracer = new DotDiagnosticTracer(); - tracer.OnMiddlewareFailure(new MiddlewareDiagnosticData(MockResolveRequestContext(), Mock.Of())); - Assert.Equal(0, tracer.OperationsInProgress); - } - - [Fact] - public void OnMiddlewareFailure_NoRequest() - { - var tracer = new DotDiagnosticTracer(); - tracer.OnOperationStart(new OperationStartDiagnosticData(MockResolveOperation(), MockResolveRequest())); - - // Middleware only happens during a request, but in the event we missed the start... - tracer.OnMiddlewareFailure(new MiddlewareDiagnosticData(MockResolveRequestContext(), Mock.Of())); - } - - [Fact] - public void OnMiddlewareFailure_NoMiddleware() - { - var tracer = new DotDiagnosticTracer(); - var reqCtx = MockResolveRequestContext(); - var op = reqCtx.Operation; - tracer.OnOperationStart(new OperationStartDiagnosticData(op, MockResolveRequest())); - tracer.OnRequestStart(new RequestDiagnosticData(op, reqCtx)); - - // Middleware should have a start before success... - tracer.OnMiddlewareFailure(new MiddlewareDiagnosticData(reqCtx, Mock.Of())); - } - private static ResolveOperation MockResolveOperation() { var container = new ContainerBuilder().Build(); From ad0b26f9c2433427f720957056470a454f6d78e9 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Thu, 16 Jul 2020 12:24:48 -0700 Subject: [PATCH 30/45] netstandard2.0 doesn't have KeyedCollection.TryGetValue. --- src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs index 49e3cb4ea..f33a08949 100644 --- a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs @@ -469,14 +469,18 @@ private void NormalizeGraph() private void RemoveDuplicates(Guid sourceId) { - if (!Requests.TryGetValue(sourceId, out var source)) + if (!Requests.Contains(sourceId)) { // Should always find this value, but in testing // sometimes the mock value uses an empty GUID or - // something that might not be here. + // something that might not be here. Also, it appears + // TryGetValue on KeyedCollection wasn't added + // until netstandard2.1, so it causes compiler problems + // to multitarget netstandard2.0. return; } + var source = Requests[sourceId]; if (!source.Success || source.Instance is null) { // We can only de-duplicate successful operations because From 3dfc8364b8329e54140cfe58710576e0dd81bf38 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Thu, 16 Jul 2020 15:29:54 -0700 Subject: [PATCH 31/45] Test cleanup. --- .../Diagnostics/DotDiagnosticTracerTests.cs | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs b/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs index d1e32ffc0..68a2ee2e4 100644 --- a/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs +++ b/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs @@ -12,17 +12,13 @@ public class DotDiagnosticTracerTests [Fact] public void DiagnosticTracerRaisesEventsOnSuccess() { - var tracer = new DotDiagnosticTracer(); - var containerBuilder = new ContainerBuilder(); containerBuilder.Register(ctxt => "Hello"); - var container = containerBuilder.Build(); + var tracer = new DotDiagnosticTracer(); container.SubscribeToDiagnostics(tracer); - string lastOpResult = null; - tracer.OperationCompleted += (sender, args) => { Assert.Same(tracer, sender); @@ -39,17 +35,13 @@ public void DiagnosticTracerRaisesEventsOnSuccess() [Fact] public void DiagnosticTracerRaisesEventsOnError() { - var tracer = new DotDiagnosticTracer(); - var containerBuilder = new ContainerBuilder(); containerBuilder.Register(ctxt => throw new InvalidOperationException()); - var container = containerBuilder.Build(); + var tracer = new DotDiagnosticTracer(); container.SubscribeToDiagnostics(tracer); - string lastOpResult = null; - tracer.OperationCompleted += (sender, args) => { Assert.Same(tracer, sender); @@ -72,18 +64,14 @@ public void DiagnosticTracerRaisesEventsOnError() [Fact] public void DiagnosticTracerHandlesDecorators() { - var tracer = new DotDiagnosticTracer(); - var containerBuilder = new ContainerBuilder(); containerBuilder.RegisterType().As(); containerBuilder.RegisterDecorator(); - var container = containerBuilder.Build(); + var tracer = new DotDiagnosticTracer(); container.SubscribeToDiagnostics(tracer); - string lastOpResult = null; - tracer.OperationCompleted += (sender, args) => { Assert.Same(tracer, sender); @@ -98,14 +86,12 @@ public void DiagnosticTracerHandlesDecorators() [Fact] public void DiagnosticTracerDoesNotLeakMemory() { - var tracer = new DotDiagnosticTracer(); - var containerBuilder = new ContainerBuilder(); containerBuilder.RegisterType().As(); containerBuilder.RegisterDecorator(); - var container = containerBuilder.Build(); + var tracer = new DotDiagnosticTracer(); container.SubscribeToDiagnostics(tracer); container.Resolve(); From 54550a79dfc409cfda44fa804a94571a33e9dbf9 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Thu, 16 Jul 2020 15:30:11 -0700 Subject: [PATCH 32/45] Reduce redundant info in the graph. --- src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs index f33a08949..9471df3a4 100644 --- a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs @@ -226,7 +226,13 @@ public void ToString(StringBuilder stringBuilder, RequestDictionary allRequests) stringBuilder.AppendTableRow(TracerMessages.TargetDisplay, DecoratorTarget); } - if (Instance is object) + // Only write the instance info IF + // - There IS an instance AND + // - There's more than one service exposed (which means there's at least one service) + // not matching the instance type OR + // - There's only one service exposed and that one doesn't match the instance type. + if (Instance is object && + (Services.Count != 1 || !(Services.First().Key is IServiceWithType swt) || swt.ServiceType != Instance.GetType())) { stringBuilder.AppendTableRow(TracerMessages.InstanceDisplay, Instance.GetType().FullName); } @@ -242,7 +248,10 @@ public void ToString(StringBuilder stringBuilder, RequestDictionary allRequests) // Connect into a table with the ID format "parent:tablerow" var destination = allRequests[edge.Request]; var edgeId = destination.Id.NodeId() + ":" + destination.Services[edge.Service].NodeId(); - stringBuilder.ConnectNodes(Id.NodeId(), edgeId, edge.Service.Description, !destination.Success); + + // Shorter type name for line descriptions where possible. + var description = edge.Service is IServiceWithType edgeSwt ? edgeSwt.ServiceType.Name : edge.Service.Description; + stringBuilder.ConnectNodes(Id.NodeId(), edgeId, description, !destination.Success); } } } From c5efb26246745dadb591638432d4e1324f0e9f46 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 17 Jul 2020 10:13:03 -0700 Subject: [PATCH 33/45] Removed DOT tracer in prep for new package. --- .../Core/Diagnostics/DotDiagnosticTracer.cs | 557 ------------------ .../Core/Diagnostics/DotStringExtensions.cs | 246 -------- .../Diagnostics/DotDiagnosticTracerTests.cs | 121 ---- .../Diagnostics/DotDiagnosticTracerTests.cs | 153 ----- .../Diagnostics/DotStringExtensionsTests.cs | 38 -- 5 files changed, 1115 deletions(-) delete mode 100644 src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs delete mode 100644 src/Autofac/Core/Diagnostics/DotStringExtensions.cs delete mode 100644 test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs delete mode 100644 test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs delete mode 100644 test/Autofac.Test/Core/Diagnostics/DotStringExtensionsTests.cs diff --git a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs deleted file mode 100644 index 9471df3a4..000000000 --- a/src/Autofac/Core/Diagnostics/DotDiagnosticTracer.cs +++ /dev/null @@ -1,557 +0,0 @@ -// Copyright (c) Autofac Project. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Web; -using Autofac.Core.Resolving; - -namespace Autofac.Core.Diagnostics -{ - /// - /// Provides a resolve pipeline tracer that generates DOT graph output - /// traces for an end-to-end operation flow. Attach to the - /// - /// event to receive notifications when a new graph is available. - /// - /// - /// - /// The tracer subscribes to all Autofac diagnostic events and can't be - /// unsubscribed. This is required to ensure beginning and end of each - /// logical activity can be captured. - /// - /// - public class DotDiagnosticTracer : OperationDiagnosticTracerBase - { - /// - /// Metadata flag to help deduplicate the number of places where the exception is traced. - /// - private const string RequestExceptionTraced = "__RequestException"; - - private readonly ConcurrentDictionary _operationBuilders = new ConcurrentDictionary(); - - private static readonly string[] DotEvents = new string[] - { - DiagnosticEventKeys.OperationStart, - DiagnosticEventKeys.OperationFailure, - DiagnosticEventKeys.OperationSuccess, - DiagnosticEventKeys.RequestStart, - DiagnosticEventKeys.RequestFailure, - DiagnosticEventKeys.RequestSuccess, - }; - - /// - /// Initializes a new instance of the class. - /// - public DotDiagnosticTracer() - : base(DotEvents) - { - } - - /// - /// Gets the number of operations in progress being traced. - /// - /// - /// An with the number of trace IDs associated - /// with in-progress operations being traced by this tracer. - /// - public override int OperationsInProgress => _operationBuilders.Count; - - /// - public override void OnOperationStart(OperationStartDiagnosticData data) - { - if (data is null) - { - return; - } - - var builder = _operationBuilders.GetOrAdd(data.Operation, k => new DotGraphBuilder()); - builder.OnOperationStart(data.Operation.InitiatingRequest?.Service.Description); - } - - /// - public override void OnOperationSuccess(OperationSuccessDiagnosticData data) - { - if (data is null) - { - return; - } - - if (_operationBuilders.TryGetValue(data.Operation, out var builder)) - { - try - { - builder.OnOperationSuccess(); - OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); - } - finally - { - _operationBuilders.TryRemove(data.Operation, out var _); - } - } - } - - /// - public override void OnOperationFailure(OperationFailureDiagnosticData data) - { - if (data is null) - { - return; - } - - if (_operationBuilders.TryGetValue(data.Operation, out var builder)) - { - try - { - builder.OnOperationFailure(); - OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); - } - finally - { - _operationBuilders.TryRemove(data.Operation, out var _); - } - } - } - - /// - public override void OnRequestStart(RequestDiagnosticData data) - { - if (data is null) - { - return; - } - - if (_operationBuilders.TryGetValue(data.Operation, out var builder)) - { - builder.OnRequestStart( - data.RequestContext.Service, - data.RequestContext.Registration.Activator.DisplayName(), - data.RequestContext.DecoratorTarget?.Activator.DisplayName()); - } - } - - /// - public override void OnRequestSuccess(RequestDiagnosticData data) - { - if (data is null) - { - return; - } - - if (_operationBuilders.TryGetValue(data.Operation, out var builder)) - { - builder.OnRequestSuccess(data.RequestContext.Instance); - } - } - - /// - public override void OnRequestFailure(RequestFailureDiagnosticData data) - { - if (data is null) - { - return; - } - - if (_operationBuilders.TryGetValue(data.Operation, out var builder)) - { - var requestException = data.RequestException; - if (requestException is DependencyResolutionException && requestException.InnerException is object) - { - requestException = requestException.InnerException; - } - - if (requestException.Data.Contains(RequestExceptionTraced)) - { - builder.OnRequestFailure(null); - } - else - { - builder.OnRequestFailure(requestException); - } - - requestException.Data[RequestExceptionTraced] = true; - } - } - - /// - /// One node in the graph. The resulting instance (for successful requests) - /// is what uniquely identifies the node when normalizing the data. This - /// converts the notion of a "resolve request" into a dependency graph - /// based on completed resolutions. - /// - private class ResolveRequestNode - { - public ResolveRequestNode(string component) - { - Services = new Dictionary(); - Component = component; - Id = Guid.NewGuid(); - Edges = new HashSet(); - } - - public Guid Id { get; } - - public Dictionary Services { get; private set; } - - public string Component { get; private set; } - - public string? DecoratorTarget { get; set; } - - public bool Success { get; set; } - - public Exception? Exception { get; set; } - - public object? Instance { get; set; } - - public HashSet Edges { get; } - - public void ToString(StringBuilder stringBuilder, RequestDictionary allRequests) - { - var shape = DecoratorTarget == null ? "component" : "box3d"; - stringBuilder.StartNode(Id, shape, Success); - foreach (var service in Services.Keys) - { - stringBuilder.AppendServiceRow(service.Description, Services[service]); - } - - stringBuilder.AppendTableRow(TracerMessages.ComponentDisplay, Component); - - if (DecoratorTarget is object) - { - stringBuilder.AppendTableRow(TracerMessages.TargetDisplay, DecoratorTarget); - } - - // Only write the instance info IF - // - There IS an instance AND - // - There's more than one service exposed (which means there's at least one service) - // not matching the instance type OR - // - There's only one service exposed and that one doesn't match the instance type. - if (Instance is object && - (Services.Count != 1 || !(Services.First().Key is IServiceWithType swt) || swt.ServiceType != Instance.GetType())) - { - stringBuilder.AppendTableRow(TracerMessages.InstanceDisplay, Instance.GetType().FullName); - } - - if (Exception is object) - { - stringBuilder.AppendTableErrorRow(Exception.GetType().FullName, Exception.Message); - } - - stringBuilder.EndNode(); - foreach (var edge in Edges) - { - // Connect into a table with the ID format "parent:tablerow" - var destination = allRequests[edge.Request]; - var edgeId = destination.Id.NodeId() + ":" + destination.Services[edge.Service].NodeId(); - - // Shorter type name for line descriptions where possible. - var description = edge.Service is IServiceWithType edgeSwt ? edgeSwt.ServiceType.Name : edge.Service.Description; - stringBuilder.ConnectNodes(Id.NodeId(), edgeId, description, !destination.Success); - } - } - } - - /// - /// Metadata about the operation being graphed. Used to - /// generate the graph header. - /// - private class OperationNode - { - public string? Service { get; set; } - - public bool Success { get; set; } - - public void ToString(StringBuilder stringBuilder) - { - // Graph header - stringBuilder.Append("label=<"); - if (!Success) - { - stringBuilder.Append(""); - } - - stringBuilder.Append(HttpUtility.HtmlEncode(Service)); - if (!Success) - { - stringBuilder.Append(""); - } - - stringBuilder.Append(">;"); - stringBuilder.AppendLine(); - stringBuilder.AppendLine("labelloc=t"); - } - } - - /// - /// Convenience collection for accessing a request by ID - /// out of the list of all requests. - /// - private class RequestDictionary : KeyedCollection - { - protected override Guid GetKeyForItem(ResolveRequestNode item) - { - return item.Id; - } - } - - /// - /// An edge that connects two nodes (two resolve requests) in a graph. - /// The source of an edge is the request that's resolving child items; - /// the target is a specific service on a child request. - /// - private class GraphEdge : IEquatable - { - public GraphEdge(Guid request, Service service) - { - Request = request; - Service = service ?? throw new ArgumentNullException(nameof(service)); - } - - public Guid Request { get; private set; } - - public Service Service { get; private set; } - - public bool Equals(GraphEdge? other) - { - return - other is object && - other.Request == Request && - ((other.Service is object && Service is object && other.Service.Equals(Service)) || - (other.Service is null && Service is null)); - } - - public override bool Equals(object? obj) - { - return Equals(obj as GraphEdge); - } - - public override int GetHashCode() - { - // This doesn't have to be great; we don't really use it - // but analyzers complain since we do need equality. - return Request.GetHashCode() ^ Service.GetHashCode(); - } - } - - /// - /// Equality comparer that determines if two resolve requests are effectively the - /// same based on the returned instance. Used to find "duplicates" in the graph - /// during normalization. - /// - private class InstanceEqualityComparer : IEqualityComparer - { - public static InstanceEqualityComparer Default { get; } = new InstanceEqualityComparer(); - - public bool Equals(ResolveRequestNode x, ResolveRequestNode y) => ReferenceEquals(x.Instance, y.Instance); - - public int GetHashCode(ResolveRequestNode obj) => RuntimeHelpers.GetHashCode(obj.Instance); - } - - /// - /// Generator for DOT format graph traces. - /// - private class DotGraphBuilder - { - /// - /// Gets the node that has operation-level data for the graph. - /// - public OperationNode Operation { get; private set; } - - /// - /// Gets the set of all requests made during the operation. - /// - public RequestDictionary Requests { get; private set; } - - /// - /// Gets the originating request ID. This will also be the first request in the - /// stack of ongoing requests. Tracked to ensure we retain the originating - /// request during the normalization of the graph. - /// - public Guid OriginatingRequest { get; private set; } - - /// - /// Gets the stack of ongoing requests. The first request in the stack is the originating - /// request where the graph should start. - /// - public Stack CurrentRequest { get; private set; } - - public DotGraphBuilder() - { - Operation = new OperationNode(); - Requests = new RequestDictionary(); - CurrentRequest = new Stack(); - } - - public void OnOperationStart(string? service) - { - Operation.Service = service; - } - - public void OnOperationFailure() - { - Operation.Success = false; - NormalizeGraph(); - } - - public void OnOperationSuccess() - { - Operation.Success = true; - NormalizeGraph(); - } - - public void OnRequestStart(Service service, string component, string? decoratorTarget) - { - var request = new ResolveRequestNode(component); - request.Services.Add(service, Guid.NewGuid()); - Requests.Add(request); - if (decoratorTarget is object) - { - request.DecoratorTarget = decoratorTarget; - } - - if (CurrentRequest.Count != 0) - { - // We're already in a request, so add an edge from - // the parent to this new request/service. - var parent = Requests[CurrentRequest.Peek()]; - parent.Edges.Add(new GraphEdge(request.Id, service)); - } - else - { - // The initiating request will be the first request we see. - OriginatingRequest = request.Id; - } - - // The inbound request is the new current. - CurrentRequest.Push(request.Id); - } - - public void OnRequestFailure(Exception? requestException) - { - if (CurrentRequest.Count == 0) - { - // OnRequestFailure happened without a corresponding OnRequestStart. - return; - } - - var request = Requests[CurrentRequest.Pop()]; - request.Success = false; - request.Exception = requestException; - } - - public void OnRequestSuccess(object? instance) - { - if (CurrentRequest.Count == 0) - { - // OnRequestSuccess happened without a corresponding OnRequestStart. - return; - } - - var request = Requests[CurrentRequest.Pop()]; - request.Success = true; - request.Instance = instance; - } - - private void NormalizeGraph() - { - // Remove any duplicates of the root node. We need to make sure that node in particular stays. - RemoveDuplicates(OriginatingRequest); - - // Other than the originating request, find the rest of the distinct values - // so we can de-dupe. - var unique = Requests - .Where(r => r.Success && r.Instance is object && r.Id != OriginatingRequest) - .Distinct(InstanceEqualityComparer.Default) - .Select(r => r.Id) - .ToArray(); - - foreach (var id in unique) - { - RemoveDuplicates(id); - } - } - - private void RemoveDuplicates(Guid sourceId) - { - if (!Requests.Contains(sourceId)) - { - // Should always find this value, but in testing - // sometimes the mock value uses an empty GUID or - // something that might not be here. Also, it appears - // TryGetValue on KeyedCollection wasn't added - // until netstandard2.1, so it causes compiler problems - // to multitarget netstandard2.0. - return; - } - - var source = Requests[sourceId]; - if (!source.Success || source.Instance is null) - { - // We can only de-duplicate successful operations because - // failed operations don't have instances to compare. - return; - } - - var duplicates = Requests.Where(dup => - - // Successful requests where IDs are different - dup.Id != sourceId && dup.Success && - - // Instance is exactly the same - dup.Instance is object && object.ReferenceEquals(dup.Instance, source.Instance) && - - // Decorator target must also be the same (otherwise we lose the instance/decorator relationship) - dup.DecoratorTarget == source.DecoratorTarget).ToArray(); - if (duplicates.Length == 0) - { - // No duplicates. - return; - } - - foreach (var duplicate in duplicates) - { - Requests.Remove(duplicate.Id); - foreach (var request in Requests) - { - var duplicateEdges = request.Edges.Where(e => e.Request == duplicate.Id).ToArray(); - foreach (var duplicateEdge in duplicateEdges) - { - // Replace edges pointing to the duplicate so they - // point at the new source. HashSet will only keep - // unique edges, so if there was already a link to - // the source, there won't be duplicate edges. - // Also, duplicateEdge will never be null but the - // analyzer thinks it could be in that GraphEdge.ctor - // call. - request.Edges.Remove(duplicateEdge); - request.Edges.Add(new GraphEdge(sourceId, duplicateEdge!.Service)); - if (!source.Services.ContainsKey(duplicateEdge.Service)) - { - source.Services.Add(duplicateEdge.Service, Guid.NewGuid()); - } - } - } - } - } - - public override string ToString() - { - var builder = new StringBuilder(); - builder.AppendLine("digraph G {"); - Operation.ToString(builder); - foreach (var request in Requests) - { - request.ToString(builder, Requests); - } - - builder.AppendLine("}"); - return builder.ToString(); - } - } - } -} diff --git a/src/Autofac/Core/Diagnostics/DotStringExtensions.cs b/src/Autofac/Core/Diagnostics/DotStringExtensions.cs deleted file mode 100644 index a34c7f664..000000000 --- a/src/Autofac/Core/Diagnostics/DotStringExtensions.cs +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright (c) Autofac Project. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Text; -using System.Web; - -namespace Autofac.Core.Diagnostics -{ - /// - /// Extension methods for building DOT graph format strings. - /// - internal static class DotStringExtensions - { - /// - /// The set of characters on which a line is allowed to wrap. - /// - private static readonly char[] WrapCharacters = new[] { ' ', ',', '.', '?', '!', ':', ';', '-', '\n', '\r', '\t' }; - - /// - /// Starts a DOT graph node where the label is an HTML table. - /// - /// - /// The to which the node should be written. - /// - /// - /// The node ID. You can connect to this node by this ID. - /// - /// - /// The shape the node should take, like component, box3d, or plaintext. - /// - /// - /// to indicate the operation was successful; to - /// highlight the operation as a failure path. - /// - /// - /// The for continued writing. - /// - public static StringBuilder StartNode(this StringBuilder stringBuilder, Guid id, string shape, bool success) - { - stringBuilder.AppendFormat( - CultureInfo.CurrentCulture, - "{0} [shape={1},{2}label=<", - id.NodeId(), - shape, - success ? null : "penwidth=3,color=red,"); - stringBuilder.AppendLine(); - stringBuilder.AppendLine(""); - return stringBuilder; - } - - /// - /// Ends a DOT graph node where the label is an HTML table. - /// - /// - /// The with the node to close. - /// - /// - /// The for continued writing. - /// - public static StringBuilder EndNode(this StringBuilder stringBuilder) - { - stringBuilder.AppendLine("
"); - stringBuilder.AppendLine(">];"); - return stringBuilder; - } - - /// - /// Writes a table header to an HTML table node in a DOT graph. - /// - /// - /// The to which the node should be written. - /// - /// - /// A string that will be displayed as a header in the table, should be the name - /// of the service being resolved. - /// - /// - /// A that uniquely identifies the service instance being - /// resolved. Used for linking from one request to a specific service. - /// - /// - /// The for continued writing. - /// - public static StringBuilder AppendServiceRow(this StringBuilder stringBuilder, string service, Guid portId) - { - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "", portId.NodeId()); - stringBuilder.Append(service.Encode()); - stringBuilder.Append(""); - stringBuilder.AppendLine(); - return stringBuilder; - } - - /// - /// Writes a table row to an HTML table node in a DOT graph. - /// - /// - /// The to which the node should be written. - /// - /// - /// A string into which the arguments will be formatted. - /// - /// - /// The arguments to HTML encode and put into the format string. - /// - /// - /// The for continued writing. - /// - public static StringBuilder AppendTableRow(this StringBuilder stringBuilder, string format, params string[] args) - { - stringBuilder.Append(""); - for (var i = 0; i < args.Length; i++) - { - args[i] = args[i].Encode(); - } - - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, format, args); - stringBuilder.Append(""); - stringBuilder.AppendLine(); - return stringBuilder; - } - - /// - /// Writes a table row to an HTML table node in a DOT graph. - /// - /// - /// The to which the node should be written. - /// - /// - /// The full exception type name to display. - /// - /// - /// The message from the exception. - /// - /// - /// The for continued writing. - /// - public static StringBuilder AppendTableErrorRow(this StringBuilder stringBuilder, string exceptionType, string exceptionMessage) - { - stringBuilder.Append(""); - stringBuilder.Append(exceptionType.Encode()); - stringBuilder.Append(":
\n"); - stringBuilder.Append(exceptionMessage.Wrap().Encode()); - stringBuilder.Append("
"); - stringBuilder.AppendLine(); - return stringBuilder; - } - - /// - /// Writes a DOT graph connection between two named nodes. - /// - /// - /// The to which the node should be written. - /// - /// - /// The ID of the node where the connection originates. - /// - /// - /// The ID of the node where the connection ends. - /// - /// - /// The label on the connecting line. - /// - /// - /// if the text in the cell should be bold; if not. - /// - /// - /// The for continued writing. - /// - public static StringBuilder ConnectNodes(this StringBuilder stringBuilder, string fromId, string toId, string label, bool bold) - { - stringBuilder.AppendFormat(CultureInfo.CurrentCulture, "{0} -> {1} [label=<{2}>", fromId, toId, label.Encode()); - if (bold) - { - stringBuilder.Append(",penwidth=3,color=red"); - } - - stringBuilder.AppendLine("]"); - return stringBuilder; - } - - /// - /// HTML-encodes and converts newlines to line break tags in an input string. - /// - /// - /// The to encode. - /// - /// - /// The encoded version of . - /// - public static string Encode(this string input) - { - // Pretty stoked the StringComparison overload is only in one of our - // target frameworks. :( -#if NETSTANDARD2_0 - return HttpUtility.HtmlEncode(input).Replace(Environment.NewLine, "
"); -#endif -#if !NETSTANDARD2_0 - return HttpUtility.HtmlEncode(input).Replace(Environment.NewLine, "
", StringComparison.Ordinal); -#endif - } - - /// - /// Line-wraps a long string for display in a graph. - /// - /// - /// The string to line wrap. - /// - /// - /// A line-wrapped version of the input. - /// - public static string Wrap(this string input) - { - const int maxLineLength = 40; - var list = new List(); - var lastWrap = 0; - int currentIndex; - do - { - currentIndex = lastWrap + maxLineLength > input.Length ? input.Length : (input.LastIndexOfAny(WrapCharacters, Math.Min(input.Length - 1, lastWrap + maxLineLength)) + 1); - if (currentIndex <= lastWrap) - { - currentIndex = Math.Min(lastWrap + maxLineLength, input.Length); - } - - list.Add(input.Substring(lastWrap, currentIndex - lastWrap).Trim()); - lastWrap = currentIndex; - } - while (currentIndex < input.Length); - return string.Join(Environment.NewLine, list); - } - - /// - /// Creates a unique string ID from a - /// that can be used to identify a node in a DOT graph. - /// - /// The ID to serialize. - /// A string version of the ID. - public static string NodeId(this Guid guid) - { - return "n" + guid.ToString("N"); - } - } -} diff --git a/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs b/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs deleted file mode 100644 index 68a2ee2e4..000000000 --- a/test/Autofac.Specification.Test/Diagnostics/DotDiagnosticTracerTests.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Autofac Project. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -using System; -using Autofac.Core.Diagnostics; -using Xunit; - -namespace Autofac.Specification.Test.Diagnostics -{ - public class DotDiagnosticTracerTests - { - [Fact] - public void DiagnosticTracerRaisesEventsOnSuccess() - { - var containerBuilder = new ContainerBuilder(); - containerBuilder.Register(ctxt => "Hello"); - var container = containerBuilder.Build(); - - var tracer = new DotDiagnosticTracer(); - container.SubscribeToDiagnostics(tracer); - string lastOpResult = null; - tracer.OperationCompleted += (sender, args) => - { - Assert.Same(tracer, sender); - lastOpResult = args.TraceContent; - }; - - container.Resolve(); - - Assert.Contains("λ:System.String", lastOpResult); - Assert.StartsWith("digraph G {", lastOpResult); - Assert.EndsWith("}", lastOpResult.Trim()); - } - - [Fact] - public void DiagnosticTracerRaisesEventsOnError() - { - var containerBuilder = new ContainerBuilder(); - containerBuilder.Register(ctxt => throw new InvalidOperationException()); - var container = containerBuilder.Build(); - - var tracer = new DotDiagnosticTracer(); - container.SubscribeToDiagnostics(tracer); - string lastOpResult = null; - tracer.OperationCompleted += (sender, args) => - { - Assert.Same(tracer, sender); - lastOpResult = args.TraceContent; - }; - - try - { - container.Resolve(); - } - catch - { - } - - Assert.Contains(nameof(InvalidOperationException), lastOpResult); - Assert.StartsWith("digraph G {", lastOpResult); - Assert.EndsWith("}", lastOpResult.Trim()); - } - - [Fact] - public void DiagnosticTracerHandlesDecorators() - { - var containerBuilder = new ContainerBuilder(); - containerBuilder.RegisterType().As(); - containerBuilder.RegisterDecorator(); - var container = containerBuilder.Build(); - - var tracer = new DotDiagnosticTracer(); - container.SubscribeToDiagnostics(tracer); - string lastOpResult = null; - tracer.OperationCompleted += (sender, args) => - { - Assert.Same(tracer, sender); - lastOpResult = args.TraceContent; - }; - - container.Resolve(); - - Assert.Contains("Decorator", lastOpResult); - } - - [Fact] - public void DiagnosticTracerDoesNotLeakMemory() - { - var containerBuilder = new ContainerBuilder(); - containerBuilder.RegisterType().As(); - containerBuilder.RegisterDecorator(); - var container = containerBuilder.Build(); - - var tracer = new DotDiagnosticTracer(); - container.SubscribeToDiagnostics(tracer); - container.Resolve(); - - // The dictionary of tracked operations and - // graphs should be empty. - Assert.Equal(0, tracer.OperationsInProgress); - } - - private interface IService - { - } - - private class Decorator : IService - { - public Decorator(IService decorated) - { - Decorated = decorated; - } - - public IService Decorated { get; } - } - - private class Implementor : IService - { - } - } -} diff --git a/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs b/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs deleted file mode 100644 index b046ec629..000000000 --- a/test/Autofac.Test/Core/Diagnostics/DotDiagnosticTracerTests.cs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Autofac Project. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -using System; -using System.Linq; -using Autofac.Core; -using Autofac.Core.Diagnostics; -using Autofac.Core.Resolving; -using Autofac.Core.Resolving.Pipeline; -using Moq; -using Xunit; - -namespace Autofac.Test.Core.Diagnostics -{ - public class DotDiagnosticTracerTests - { - [Fact] - public void OnOperationStart_AddsOperation() - { - var tracer = new DotDiagnosticTracer(); - tracer.OnOperationStart(new OperationStartDiagnosticData(MockResolveOperation(), MockResolveRequest())); - Assert.Equal(1, tracer.OperationsInProgress); - } - - [Fact] - public void OnOperationStart_NoDataSkipsOperation() - { - var tracer = new DotDiagnosticTracer(); - tracer.OnOperationStart(null); - Assert.Equal(0, tracer.OperationsInProgress); - } - - [Fact] - public void OnOperationSuccess_CompletesOperation() - { - var tracer = new DotDiagnosticTracer(); - var called = false; - tracer.OperationCompleted += (sender, args) => - { - called = true; - }; - var op = MockResolveOperation(); - tracer.OnOperationStart(new OperationStartDiagnosticData(op, MockResolveRequest())); - tracer.OnOperationSuccess(new OperationSuccessDiagnosticData(op, "instance")); - Assert.Equal(0, tracer.OperationsInProgress); - Assert.True(called); - } - - [Fact] - public void OnOperationSuccess_NoDataSkipsOperation() - { - var tracer = new DotDiagnosticTracer(); - var op = MockResolveOperation(); - var called = false; - tracer.OperationCompleted += (sender, args) => - { - called = true; - }; - - tracer.OnOperationStart(new OperationStartDiagnosticData(op, MockResolveRequest())); - tracer.OnOperationSuccess(null); - Assert.Equal(1, tracer.OperationsInProgress); - Assert.False(called); - } - - [Fact] - public void OnOperationFailure_CompletesOperation() - { - var tracer = new DotDiagnosticTracer(); - var called = false; - tracer.OperationCompleted += (sender, args) => - { - called = true; - }; - var op = MockResolveOperation(); - tracer.OnOperationStart(new OperationStartDiagnosticData(op, MockResolveRequest())); - tracer.OnOperationFailure(new OperationFailureDiagnosticData(op, new DivideByZeroException())); - Assert.Equal(0, tracer.OperationsInProgress); - Assert.True(called); - } - - [Fact] - public void OnOperationFailure_NoDataSkipsOperation() - { - var tracer = new DotDiagnosticTracer(); - var op = MockResolveOperation(); - var called = false; - tracer.OperationCompleted += (sender, args) => - { - called = true; - }; - - tracer.OnOperationStart(new OperationStartDiagnosticData(op, MockResolveRequest())); - tracer.OnOperationFailure(null); - Assert.Equal(1, tracer.OperationsInProgress); - Assert.False(called); - } - - [Fact] - public void OnRequestStart_NoOperation() - { - var tracer = new DotDiagnosticTracer(); - tracer.OnRequestStart(new RequestDiagnosticData(MockResolveOperation(), MockResolveRequestContext())); - Assert.Equal(0, tracer.OperationsInProgress); - } - - [Fact] - public void OnRequestSuccess_NoStartOperation() - { - var tracer = new DotDiagnosticTracer(); - var op = MockResolveOperation(); - tracer.OnOperationStart(new OperationStartDiagnosticData(op, MockResolveRequest())); - - // Should have a request start before ending, but make sure we don't - // explode if something weird happens. - tracer.OnRequestSuccess(new RequestDiagnosticData(op, MockResolveRequestContext())); - } - - [Fact] - public void OnRequestFailure_NoStartOperation() - { - var tracer = new DotDiagnosticTracer(); - var op = MockResolveOperation(); - tracer.OnOperationStart(new OperationStartDiagnosticData(op, MockResolveRequest())); - - // Should have a request start before ending, but make sure we don't - // explode if something weird happens. - tracer.OnRequestFailure(new RequestFailureDiagnosticData(op, MockResolveRequestContext(), new DivideByZeroException())); - } - - private static ResolveOperation MockResolveOperation() - { - var container = new ContainerBuilder().Build(); - var scope = container.BeginLifetimeScope() as ISharingLifetimeScope; - return new ResolveOperation(scope); - } - - private static ResolveRequest MockResolveRequest() - { - return new ResolveRequest( - new TypedService(typeof(string)), - new ServiceRegistration(Mock.Of(), Mock.Of()), - Enumerable.Empty()); - } - - private static ResolveRequestContext MockResolveRequestContext() - { - var operation = MockResolveOperation(); - var request = MockResolveRequest(); - return new ResolveRequestContext(operation, request, operation.CurrentScope); - } - } -} diff --git a/test/Autofac.Test/Core/Diagnostics/DotStringExtensionsTests.cs b/test/Autofac.Test/Core/Diagnostics/DotStringExtensionsTests.cs deleted file mode 100644 index a35e1ecdf..000000000 --- a/test/Autofac.Test/Core/Diagnostics/DotStringExtensionsTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Autofac Project. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -using System; -using Autofac.Core.Diagnostics; -using Xunit; - -namespace Autofac.Test.Core.Diagnostics -{ - public class DotStringExtensionsTests - { - [Fact] - public void Wrap_ShortString() - { - var input = "This is short."; - var actual = DotStringExtensions.Wrap(input); - Assert.Equal(input, actual); - } - - [Fact] - public void Wrap_LongString() - { - var input = "This is a very long string. It should line wrap so it isn't this long, but that's why this test is here - to see if it line wraps or not. If it doesn't line wrap, then the function isn't working."; - var expected = "This is a very long string. It should\nline wrap so it isn't this long, but\nthat's why this test is here - to see if\nit line wraps or not. If it doesn't line\nwrap, then the function isn't working.".Replace("\n", Environment.NewLine); - var actual = DotStringExtensions.Wrap(input); - Assert.Equal(expected, actual); - } - - [Fact] - public void Wrap_LongWord() - { - var input = "Thisisunrealisticbutislongerthantheallowedlinelengthwithnowheretotbreaksowehavetoforceit."; - var expected = "Thisisunrealisticbutislongerthantheallow\nedlinelengthwithnowheretotbreaksowehavet\noforceit.".Replace("\n", Environment.NewLine); - var actual = DotStringExtensions.Wrap(input); - Assert.Equal(expected, actual); - } - } -} From 75128b99a34e0d860cc153e31e23605e5a955383 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 17 Jul 2020 10:14:07 -0700 Subject: [PATCH 34/45] Community files moved to .github repo. --- .github/ISSUE_TEMPLATE/bug_report.md | 55 ---------- .github/ISSUE_TEMPLATE/config.yml | 5 - .github/ISSUE_TEMPLATE/feature_request.md | 34 ------ CODE_OF_CONDUCT.md | 45 -------- CONTRIBUTING.md | 120 ---------------------- 5 files changed, 259 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 CODE_OF_CONDUCT.md delete mode 100644 CONTRIBUTING.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 987d60874..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -name: Bug Report -about: Create a report to help us fix a problem in core Autofac. -title: '' -labels: '' -assignees: '' ---- - - - -## Describe the Bug - - - -## Steps to Reproduce - - - -```c# -public class ReproTest -{ - [Fact] - public void Repro() - { - var builder = new ContainerBuilder(); - var container = builder.Build(); - Assert.NotNull(container); - } -} -``` - -## Expected Behavior - - - -## Exception with Stack Trace - - - -```text -Put the exception with stack trace here. -``` - -## Dependency Versions - -Autofac: - - -## Additional Info - - diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 5255a50aa..000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Usage Questions - url: https://stackoverflow.com/questions/tagged/autofac - about: Want to know how to do something? We monitor questions tagged `autofac` on Stack Overflow! Ask there! diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 1b78de6e8..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Feature Request -about: Suggest an idea to make Autofac better. -title: '' -labels: '' -assignees: '' - ---- - -## Problem Statement - - - -## Desired Solution - - - -## Alternatives You've Considered - - - -## Additional Context - - diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 5143b1a48..000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,45 +0,0 @@ -# CODE OF CONDUCT - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -- Using welcoming and inclusive language -- Being respectful of differing viewpoints and experiences -- Gracefully accepting constructive criticism -- Focusing on what is best for the community -- Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -- The use of sexualized language or imagery and unwelcome sexual attention or advances -- Trolling, insulting/derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others’ private information, such as a physical or electronic address, without explicit permission -- Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at `autofac-internal@googlegroups.com`. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership. - -## Attribution - -This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html). - -For answers to common questions about this code of conduct, see [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 931cfb2f6..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,120 +0,0 @@ -# Autofac Contributor Guide - -Contributions to Autofac, whether new features or bug fixes, are deeply appreciated and benefit the whole user community. - -The following guidelines help ensure the smooth running of the project, and keep a consistent standard across the codebase. They are guidelines only - should you feel a need to deviate from them it is probably for a good reason - but please adhere to them as closely as possible. - -If you'd like to contribute code or documentation to Autofac, we welcome pull requests. [Questions and suggestions are welcome on the newsgroup.](https://groups.google.com/forum/#!forum/autofac). - -**Your contributions must be your own work and licensed under the same terms as Autofac.** - -## Code of Conduct - -The Autofac Code of Conduct [is posted on GitHub](CODE_OF_CONDUCT.md). It is expected that all contributors follow the code of conduct. - -## Process - -**When working through contributions, please file issues and submit pull requests in the repository containing the code in question.** For example, if the issue is with the Autofac MVC integration, file it in that repo rather than the core Autofac repo. - -- **File an issue.** Either suggest a feature or note a defect. If it's a feature, explain the challenge you're facing and how you think the feature should work. If it's a defect, include a description and reproduction (ideally one or more failing unit tests). -- **Design discussion.** For new features, some discussion on the issue will take place to determine if it's something that should be included with Autofac or be a user-supplied extension. For defects, discussion may happen around whether the issue is truly a defect or if the behavior is correct. -- **Pull request.** Create [a pull request](https://help.github.com/articles/using-pull-requests/) on the `develop` branch of the repository to submit changes to the code based on the information in the issue. Pull requests need to pass the CI build and follow coding standards. See below for more on coding standards in use with Autofac. Note all pull requests should include accompanying unit tests to verify the work. -- **Code review.** Some iteration may take place requiring updates to the pull request (e.g., to fix a typo or add error handling). -- **Pull request acceptance.** The pull request will be accepted into the `develop` branch and pushed to `master` with the next release. - -## License - -By contributing to Autofac, you assert that: - -1. The contribution is your own original work. -2. You have the right to assign the *copyright* for the work (it is not owned by your employer, or you have been given copyright assignment in writing). -3. You license it under the terms applied to the rest of the Autofac project. - -## Coding - -### Workflow - -Autofac and the associated integration libraries follow the [Gitflow workflow process](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow/) for handling releases. This means active development is done on the `develop` branch and we push to `master` when it's release time. **If you're creating a pull request or contribution, please do it on the `develop` branch.** We can then build, push to MyGet for testing, and release to NuGet when everything's verified. - -We use [semantic versioning](https://semver.org/) for our package versions. - -### Developer Environment - -**Windows**: - -- Visual Studio 2019 or VS Code -- .NET Core SDK (each repo has a `global.json` with the version required) -- PowerShell 5+ / PowerShell Core - -**Mac**: - -- VS Code -- .NET Core SDK (each repo has a `global.json` with the version required) -- PowerShell 5+ / PowerShell Core -- Mono - install the latest "Visual Studio channel" version; the standalone version or the one from Homebrew won't work. - -### Build / Test - -Project codelines with scripted builds generally have a `build.ps1` script. This Powershell script will build, package, and execute tests. - -Some project codelines rely on convention-based builds so do not have a specific script. In these cases you will not see a `.ps1` or `.proj` file to execute. In these cases... - -- The build is executed by running it in Visual Studio or by executing `dotnet build Autofac.sln` on the solution in the codeline root. -- Unit tests can be run from the Visual Studio test explorer or by manually executing the command-line unit test runner from the `packages` folder against the built unit test assembly. - -Unit tests are written in XUnit and Moq. **Code contributions should include tests that exercise/demonstrate the contribution.** - -**Everything should build and test with zero errors and zero warnings.** - -### Coding Standards - -Normal .NET coding guidelines apply. See the [Framework Design Guidelines](https://msdn.microsoft.com/en-us/library/ms229042.aspx) for suggestions. We have Roslyn analyzers running on most of the code. These analyzers are actually correct a majority of the time. Please try to fix warnings rather than suppressing the message. If you do need to suppress a false positive, use the `[SuppressMessage]` attribute. - -Autofac source code uses four spaces for indents. We use [EditorConfig](https://editorconfig.org/) to ensure consistent formatting in code docs. Visual Studio has this built in since VS 2017. VS Code requires the EditorConfig extension. Many other editors also support EditorConfig. - -### Public API and Breaking Changes - -Part of the responsibility of working on a widely used package is that you must strive to avoid breaking changes in the public API. A breaking change can be a lot of things: - -- Change a type's namespace. -- Remove or rename a method on a class or interface. -- Move an extension method from one static class to a different static class. -- Add an optional parameter to an existing method. -- Add a new abstract method to an existing abstract class. -- Add a new member on an interface. - -You have to be careful if you change the public API. Adding a new method to a class is OK... unless it's an abstract class and someone consuming it is now required to implement it. - -You'll notice a lot of Autofac is internal and the unit test fixtures have internals visible. This allows for more opportunity to refactor the inner workings of Autofac and its integrations without incurring breaking changes on consumers. - -**Adding to the public API is something to seriously consider.** If you're contributing something that expands on the public API, you need to consider that once it's out there, we can't pull it out without running it through a lifecycle - marking it obsolete, making a major version release, providing support for folks who had taken it and still need that feature. Even if it's just one more overload for an existing method, consider if it's really necessary or if the task at hand can be accomplished by something that's already publicly exposed. - -### Dependencies and Upgrades - -**All Autofac packages should strive to be as long-term compatible as possible with things and not require downstream consumers to take upgrades.** - -Core Autofac attempts to be as compatible as possible to allow as many clients to use it as it can. No third-party assemblies outside the base .NET/.NET Core framework are allowed. No upgrades to base requirements are allowed unless there's a technical reason. Taking an upgrade to Autofac core should generally not require you to take any other upgrades in your application. - -Integration packages should do their best to balance the need for upgrades with the need for functionality. Generally speaking: - -- Unless there's a technical need to take an upgrade to an integration package dependency, **don't**. That includes Autofac - don't require an upgrade to an integration package to force an upgrade to the core Autofac version. -- Integration packages really only need to be compatible with the latest version of the framework with which they integration. For example, the Autofac ASP.NET MVC integration _may_ require use of the latest ASP.NET MVC bits. There is no requirement to maintain backwards compatibility with every old version of ASP.NET MVC and no requirement to fork and maintain multiple branches of the integration in order to support all the ASP.NET MVC versions. - -Again, the goal is to be as compatible with as many things for as long as possible. - -If you need to take an update to a dependency for a technical or security reason, do it. It's not a bad thing. Just be aware that if you take an upgrade, anyone taking the latest version of the package you're working on will also be forced to take that upgrade and they may not be ready. - -Additional considerations: - -- Projects should be able to be built straight out of Git (no additional installation needs to take place on the developer's machine). This means NuGet package references, not installation of required components. -- Any third-party libraries consumed by Autofac integration must have licenses compatible with Autofac's (the GPL and licenses like it are incompatible - please ask on the discussion forum if you're unsure). - -### Code Documentation and Examples - -It is *strongly* encouraged that you update the Autofac documentation when making changes. If your changes impact existing features, the documentation may be updated regardless of whether a binary distribution has been made that includes the changes. [This can also be done through pull request.](https://github.com/autofac/Documentation) - -You should also include XML API comments in the code. These are used to generate API documentation as well as for IntelliSense. - -**The Golden Rule of Documentation: Write the documentation you'd want to read.** Every developer has seen self explanatory docs and wondered why there wasn't more information. (Parameter: "index." Documentation: "The index.") Please write the documentation you'd want to read if you were a developer first trying to understand how to make use of a feature. - -For new integrations or changes to existing integrations, you may need to add or update [the examples repo](https://github.com/autofac/Examples) to show how the integration works. From 2770326146178faef4d8772bd1e0fc887638ba69 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 17 Jul 2020 10:38:11 -0700 Subject: [PATCH 35/45] Moved Diagnostics to Autofac.Diagnostics namespace. --- src/Autofac/Autofac.csproj | 4 ++-- src/Autofac/ContainerExtensions.cs | 2 +- src/Autofac/Core/Container.cs | 2 +- src/Autofac/Core/Lifetime/LifetimeScope.cs | 2 +- src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs | 4 +--- .../Core/Resolving/Pipeline/ResolveRequestContextBase.cs | 2 +- src/Autofac/Core/Resolving/ResolveOperation.cs | 2 +- src/Autofac/Core/Resolving/ResolveOperationBase.cs | 2 +- src/Autofac/{Core => }/Diagnostics/DefaultDiagnosticTracer.cs | 3 ++- src/Autofac/{Core => }/Diagnostics/DiagnosticEventKeys.cs | 2 +- .../{Core => }/Diagnostics/DiagnosticSourceExtensions.cs | 2 +- src/Autofac/{Core => }/Diagnostics/DiagnosticTracerBase.cs | 2 +- .../{Core => }/Diagnostics/MiddlewareDiagnosticData.cs | 2 +- .../{Core => }/Diagnostics/OperationDiagnosticTracerBase.cs | 2 +- .../{Core => }/Diagnostics/OperationFailureDiagnosticData.cs | 2 +- .../{Core => }/Diagnostics/OperationStartDiagnosticData.cs | 2 +- .../{Core => }/Diagnostics/OperationSuccessDiagnosticData.cs | 2 +- .../{Core => }/Diagnostics/OperationTraceCompletedArgs.cs | 2 +- src/Autofac/{Core => }/Diagnostics/RequestDiagnosticData.cs | 2 +- .../{Core => }/Diagnostics/RequestFailureDiagnosticData.cs | 2 +- src/Autofac/{Core => }/Diagnostics/TracerMessages.Designer.cs | 2 +- src/Autofac/{Core => }/Diagnostics/TracerMessages.resx | 0 .../Diagnostics/DefaultDiagnosticTracerTests.cs | 2 +- .../Diagnostics/DiagnosticTracerBaseTests.cs | 2 +- .../Features/CircularDependencyTests.cs | 2 +- .../Lifetime/InstancePerLifetimeScopeTests.cs | 2 +- test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs | 2 +- .../{Core => }/Diagnostics/DiagnosticSourceExtensionsTests.cs | 4 ++-- .../{Core => }/Diagnostics/DiagnosticTracerBaseTests.cs | 4 ++-- .../Diagnostics/OperationDiagnosticTracerBaseTests.cs | 4 ++-- test/Autofac.Test/Features/Decorators/DecoratorTests.cs | 2 +- .../Features/Decorators/OpenGenericDecoratorTests.cs | 2 +- test/Autofac.Test/Mocks.cs | 3 +-- 33 files changed, 37 insertions(+), 39 deletions(-) rename src/Autofac/{Core => }/Diagnostics/DefaultDiagnosticTracer.cs (99%) rename src/Autofac/{Core => }/Diagnostics/DiagnosticEventKeys.cs (98%) rename src/Autofac/{Core => }/Diagnostics/DiagnosticSourceExtensions.cs (99%) rename src/Autofac/{Core => }/Diagnostics/DiagnosticTracerBase.cs (99%) rename src/Autofac/{Core => }/Diagnostics/MiddlewareDiagnosticData.cs (97%) rename src/Autofac/{Core => }/Diagnostics/OperationDiagnosticTracerBase.cs (98%) rename src/Autofac/{Core => }/Diagnostics/OperationFailureDiagnosticData.cs (97%) rename src/Autofac/{Core => }/Diagnostics/OperationStartDiagnosticData.cs (97%) rename src/Autofac/{Core => }/Diagnostics/OperationSuccessDiagnosticData.cs (97%) rename src/Autofac/{Core => }/Diagnostics/OperationTraceCompletedArgs.cs (97%) rename src/Autofac/{Core => }/Diagnostics/RequestDiagnosticData.cs (97%) rename src/Autofac/{Core => }/Diagnostics/RequestFailureDiagnosticData.cs (97%) rename src/Autofac/{Core => }/Diagnostics/TracerMessages.Designer.cs (99%) rename src/Autofac/{Core => }/Diagnostics/TracerMessages.resx (100%) rename test/Autofac.Test/{Core => }/Diagnostics/DiagnosticSourceExtensionsTests.cs (99%) rename test/Autofac.Test/{Core => }/Diagnostics/DiagnosticTracerBaseTests.cs (96%) rename test/Autofac.Test/{Core => }/Diagnostics/OperationDiagnosticTracerBaseTests.cs (96%) diff --git a/src/Autofac/Autofac.csproj b/src/Autofac/Autofac.csproj index 805f27087..560d23bec 100644 --- a/src/Autofac/Autofac.csproj +++ b/src/Autofac/Autofac.csproj @@ -125,7 +125,7 @@ True ContainerResources.resx - + True True TracerMessages.resx @@ -351,7 +351,7 @@ ResXFileCodeGenerator ContainerResources.Designer.cs
- + ResXFileCodeGenerator TracerMessages.Designer.cs diff --git a/src/Autofac/ContainerExtensions.cs b/src/Autofac/ContainerExtensions.cs index d210466ac..4f64155e3 100644 --- a/src/Autofac/ContainerExtensions.cs +++ b/src/Autofac/ContainerExtensions.cs @@ -25,7 +25,7 @@ using System; using System.Diagnostics; -using Autofac.Core.Diagnostics; +using Autofac.Diagnostics; namespace Autofac { diff --git a/src/Autofac/Core/Container.cs b/src/Autofac/Core/Container.cs index df7fc0f08..779c5003c 100644 --- a/src/Autofac/Core/Container.cs +++ b/src/Autofac/Core/Container.cs @@ -26,9 +26,9 @@ using System; using System.Diagnostics; using System.Threading.Tasks; -using Autofac.Core.Diagnostics; using Autofac.Core.Lifetime; using Autofac.Core.Resolving; +using Autofac.Diagnostics; using Autofac.Util; namespace Autofac.Core diff --git a/src/Autofac/Core/Lifetime/LifetimeScope.cs b/src/Autofac/Core/Lifetime/LifetimeScope.cs index 4a538dbf2..3a683f041 100644 --- a/src/Autofac/Core/Lifetime/LifetimeScope.cs +++ b/src/Autofac/Core/Lifetime/LifetimeScope.cs @@ -31,9 +31,9 @@ using System.Runtime.CompilerServices; using System.Threading.Tasks; using Autofac.Builder; -using Autofac.Core.Diagnostics; using Autofac.Core.Registration; using Autofac.Core.Resolving; +using Autofac.Diagnostics; using Autofac.Util; namespace Autofac.Core.Lifetime diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs index 10e1d721c..d858330be 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs @@ -28,10 +28,8 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Text; -using Autofac.Core.Diagnostics; using Autofac.Core.Pipeline; -using Autofac.Core.Resolving.Middleware; +using Autofac.Diagnostics; namespace Autofac.Core.Resolving.Pipeline { diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs index 20b2916fb..41163879d 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs @@ -27,8 +27,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using Autofac.Core.Diagnostics; using Autofac.Core.Registration; +using Autofac.Diagnostics; using Autofac.Features.Decorators; namespace Autofac.Core.Resolving.Pipeline diff --git a/src/Autofac/Core/Resolving/ResolveOperation.cs b/src/Autofac/Core/Resolving/ResolveOperation.cs index 5bdde89c7..359039763 100644 --- a/src/Autofac/Core/Resolving/ResolveOperation.cs +++ b/src/Autofac/Core/Resolving/ResolveOperation.cs @@ -24,8 +24,8 @@ // OTHER DEALINGS IN THE SOFTWARE. using System.Diagnostics.CodeAnalysis; -using Autofac.Core.Diagnostics; using Autofac.Core.Resolving.Pipeline; +using Autofac.Diagnostics; namespace Autofac.Core.Resolving { diff --git a/src/Autofac/Core/Resolving/ResolveOperationBase.cs b/src/Autofac/Core/Resolving/ResolveOperationBase.cs index 93e066d46..0bdc37309 100644 --- a/src/Autofac/Core/Resolving/ResolveOperationBase.cs +++ b/src/Autofac/Core/Resolving/ResolveOperationBase.cs @@ -26,9 +26,9 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using Autofac.Core.Diagnostics; using Autofac.Core.Resolving.Middleware; using Autofac.Core.Resolving.Pipeline; +using Autofac.Diagnostics; namespace Autofac.Core.Resolving { diff --git a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs b/src/Autofac/Diagnostics/DefaultDiagnosticTracer.cs similarity index 99% rename from src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs rename to src/Autofac/Diagnostics/DefaultDiagnosticTracer.cs index cdeeaaddd..e3249c851 100644 --- a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs +++ b/src/Autofac/Diagnostics/DefaultDiagnosticTracer.cs @@ -5,9 +5,10 @@ using System.Collections.Concurrent; using System.Globalization; using System.Text; +using Autofac.Core; using Autofac.Core.Resolving; -namespace Autofac.Core.Diagnostics +namespace Autofac.Diagnostics { /// /// Provides a default resolve pipeline tracer that builds a multi-line diff --git a/src/Autofac/Core/Diagnostics/DiagnosticEventKeys.cs b/src/Autofac/Diagnostics/DiagnosticEventKeys.cs similarity index 98% rename from src/Autofac/Core/Diagnostics/DiagnosticEventKeys.cs rename to src/Autofac/Diagnostics/DiagnosticEventKeys.cs index 6b042ae38..646d061ff 100644 --- a/src/Autofac/Core/Diagnostics/DiagnosticEventKeys.cs +++ b/src/Autofac/Diagnostics/DiagnosticEventKeys.cs @@ -7,7 +7,7 @@ using Autofac.Core.Resolving; using Autofac.Core.Resolving.Pipeline; -namespace Autofac.Core.Diagnostics +namespace Autofac.Diagnostics { /// /// Names of the events raised in diagnostics. diff --git a/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs b/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs similarity index 99% rename from src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs rename to src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs index fbe376e26..ea1c48f7c 100644 --- a/src/Autofac/Core/Diagnostics/DiagnosticSourceExtensions.cs +++ b/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs @@ -7,7 +7,7 @@ using Autofac.Core.Resolving; using Autofac.Core.Resolving.Pipeline; -namespace Autofac.Core.Diagnostics +namespace Autofac.Diagnostics { /// /// Extension methods for writing diagnostic messages. diff --git a/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs b/src/Autofac/Diagnostics/DiagnosticTracerBase.cs similarity index 99% rename from src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs rename to src/Autofac/Diagnostics/DiagnosticTracerBase.cs index c44243fc3..0525ccc88 100644 --- a/src/Autofac/Core/Diagnostics/DiagnosticTracerBase.cs +++ b/src/Autofac/Diagnostics/DiagnosticTracerBase.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Autofac.Core.Diagnostics +namespace Autofac.Diagnostics { /// /// Base class for creating diagnostic tracers that follow Autofac diagnostic events. diff --git a/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs b/src/Autofac/Diagnostics/MiddlewareDiagnosticData.cs similarity index 97% rename from src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs rename to src/Autofac/Diagnostics/MiddlewareDiagnosticData.cs index bc18ff86c..2dd88f811 100644 --- a/src/Autofac/Core/Diagnostics/MiddlewareDiagnosticData.cs +++ b/src/Autofac/Diagnostics/MiddlewareDiagnosticData.cs @@ -3,7 +3,7 @@ using Autofac.Core.Resolving.Pipeline; -namespace Autofac.Core.Diagnostics +namespace Autofac.Diagnostics { /// /// Diagnostic data associated with middleware events. diff --git a/src/Autofac/Core/Diagnostics/OperationDiagnosticTracerBase.cs b/src/Autofac/Diagnostics/OperationDiagnosticTracerBase.cs similarity index 98% rename from src/Autofac/Core/Diagnostics/OperationDiagnosticTracerBase.cs rename to src/Autofac/Diagnostics/OperationDiagnosticTracerBase.cs index 34cb93b6c..14963ecf2 100644 --- a/src/Autofac/Core/Diagnostics/OperationDiagnosticTracerBase.cs +++ b/src/Autofac/Diagnostics/OperationDiagnosticTracerBase.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Autofac.Core.Diagnostics +namespace Autofac.Diagnostics { /// /// Base class for tracers that require all operations for logical operation tracing. diff --git a/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs b/src/Autofac/Diagnostics/OperationFailureDiagnosticData.cs similarity index 97% rename from src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs rename to src/Autofac/Diagnostics/OperationFailureDiagnosticData.cs index 22377f0c3..4cc32ceb0 100644 --- a/src/Autofac/Core/Diagnostics/OperationFailureDiagnosticData.cs +++ b/src/Autofac/Diagnostics/OperationFailureDiagnosticData.cs @@ -4,7 +4,7 @@ using System; using Autofac.Core.Resolving; -namespace Autofac.Core.Diagnostics +namespace Autofac.Diagnostics { /// /// Diagnostic data associated with resolve operation failure events. diff --git a/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs b/src/Autofac/Diagnostics/OperationStartDiagnosticData.cs similarity index 97% rename from src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs rename to src/Autofac/Diagnostics/OperationStartDiagnosticData.cs index 763abf843..c69532c9a 100644 --- a/src/Autofac/Core/Diagnostics/OperationStartDiagnosticData.cs +++ b/src/Autofac/Diagnostics/OperationStartDiagnosticData.cs @@ -3,7 +3,7 @@ using Autofac.Core.Resolving; -namespace Autofac.Core.Diagnostics +namespace Autofac.Diagnostics { /// /// Diagnostic data associated with resolve operation start events. diff --git a/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs b/src/Autofac/Diagnostics/OperationSuccessDiagnosticData.cs similarity index 97% rename from src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs rename to src/Autofac/Diagnostics/OperationSuccessDiagnosticData.cs index f2bd2b14d..52f51dcd4 100644 --- a/src/Autofac/Core/Diagnostics/OperationSuccessDiagnosticData.cs +++ b/src/Autofac/Diagnostics/OperationSuccessDiagnosticData.cs @@ -3,7 +3,7 @@ using Autofac.Core.Resolving; -namespace Autofac.Core.Diagnostics +namespace Autofac.Diagnostics { /// /// Diagnostic data associated with resolve operation success events. diff --git a/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs b/src/Autofac/Diagnostics/OperationTraceCompletedArgs.cs similarity index 97% rename from src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs rename to src/Autofac/Diagnostics/OperationTraceCompletedArgs.cs index 19ae91a8c..6f7530a85 100644 --- a/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs +++ b/src/Autofac/Diagnostics/OperationTraceCompletedArgs.cs @@ -3,7 +3,7 @@ using Autofac.Core.Resolving; -namespace Autofac.Core.Diagnostics +namespace Autofac.Diagnostics { /// /// Event data for the event. diff --git a/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs b/src/Autofac/Diagnostics/RequestDiagnosticData.cs similarity index 97% rename from src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs rename to src/Autofac/Diagnostics/RequestDiagnosticData.cs index a9af48170..a2e4e6a6a 100644 --- a/src/Autofac/Core/Diagnostics/RequestDiagnosticData.cs +++ b/src/Autofac/Diagnostics/RequestDiagnosticData.cs @@ -4,7 +4,7 @@ using Autofac.Core.Resolving; using Autofac.Core.Resolving.Pipeline; -namespace Autofac.Core.Diagnostics +namespace Autofac.Diagnostics { /// /// Diagnostic data associated with resolve request events. diff --git a/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs b/src/Autofac/Diagnostics/RequestFailureDiagnosticData.cs similarity index 97% rename from src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs rename to src/Autofac/Diagnostics/RequestFailureDiagnosticData.cs index 0d87c2876..e485c2b8e 100644 --- a/src/Autofac/Core/Diagnostics/RequestFailureDiagnosticData.cs +++ b/src/Autofac/Diagnostics/RequestFailureDiagnosticData.cs @@ -5,7 +5,7 @@ using Autofac.Core.Resolving; using Autofac.Core.Resolving.Pipeline; -namespace Autofac.Core.Diagnostics +namespace Autofac.Diagnostics { /// /// Diagnostic data associated with resolve request failure events. diff --git a/src/Autofac/Core/Diagnostics/TracerMessages.Designer.cs b/src/Autofac/Diagnostics/TracerMessages.Designer.cs similarity index 99% rename from src/Autofac/Core/Diagnostics/TracerMessages.Designer.cs rename to src/Autofac/Diagnostics/TracerMessages.Designer.cs index 039115563..e8bb3e961 100644 --- a/src/Autofac/Core/Diagnostics/TracerMessages.Designer.cs +++ b/src/Autofac/Diagnostics/TracerMessages.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace Autofac.Core.Diagnostics { +namespace Autofac.Diagnostics { using System; diff --git a/src/Autofac/Core/Diagnostics/TracerMessages.resx b/src/Autofac/Diagnostics/TracerMessages.resx similarity index 100% rename from src/Autofac/Core/Diagnostics/TracerMessages.resx rename to src/Autofac/Diagnostics/TracerMessages.resx diff --git a/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs b/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs index a484d265a..3e78ae32e 100644 --- a/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs +++ b/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using System; -using Autofac.Core.Diagnostics; +using Autofac.Diagnostics; using Xunit; namespace Autofac.Specification.Test.Diagnostics diff --git a/test/Autofac.Specification.Test/Diagnostics/DiagnosticTracerBaseTests.cs b/test/Autofac.Specification.Test/Diagnostics/DiagnosticTracerBaseTests.cs index ba40b1731..013df4a0d 100644 --- a/test/Autofac.Specification.Test/Diagnostics/DiagnosticTracerBaseTests.cs +++ b/test/Autofac.Specification.Test/Diagnostics/DiagnosticTracerBaseTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -using Autofac.Core.Diagnostics; +using Autofac.Diagnostics; using Xunit; namespace Autofac.Specification.Test.Diagnostics diff --git a/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs b/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs index b075e03b9..7ca670f62 100644 --- a/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs +++ b/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using Autofac.Core; -using Autofac.Core.Diagnostics; +using Autofac.Diagnostics; using Autofac.Specification.Test.Features.CircularDependency; using Xunit; using Xunit.Abstractions; diff --git a/test/Autofac.Specification.Test/Lifetime/InstancePerLifetimeScopeTests.cs b/test/Autofac.Specification.Test/Lifetime/InstancePerLifetimeScopeTests.cs index a80ce0026..1582f5b14 100644 --- a/test/Autofac.Specification.Test/Lifetime/InstancePerLifetimeScopeTests.cs +++ b/test/Autofac.Specification.Test/Lifetime/InstancePerLifetimeScopeTests.cs @@ -1,5 +1,5 @@ using System; -using Autofac.Core.Diagnostics; +using Autofac.Diagnostics; using Autofac.Specification.Test.Util; using Xunit; using Xunit.Abstractions; diff --git a/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs b/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs index 582f77992..9dd5052ee 100644 --- a/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs +++ b/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs @@ -4,11 +4,11 @@ using System.Linq; using System.Threading.Tasks; using Autofac.Core; -using Autofac.Core.Diagnostics; using Autofac.Core.Lifetime; using Autofac.Core.Resolving; using Autofac.Core.Resolving.Middleware; using Autofac.Core.Resolving.Pipeline; +using Autofac.Diagnostics; using Xunit; namespace Autofac.Test.Core.Pipeline diff --git a/test/Autofac.Test/Core/Diagnostics/DiagnosticSourceExtensionsTests.cs b/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs similarity index 99% rename from test/Autofac.Test/Core/Diagnostics/DiagnosticSourceExtensionsTests.cs rename to test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs index 1c9055dec..900044d2a 100644 --- a/test/Autofac.Test/Core/Diagnostics/DiagnosticSourceExtensionsTests.cs +++ b/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs @@ -6,13 +6,13 @@ using System.Diagnostics; using System.Linq; using Autofac.Core; -using Autofac.Core.Diagnostics; using Autofac.Core.Resolving; using Autofac.Core.Resolving.Pipeline; +using Autofac.Diagnostics; using Moq; using Xunit; -namespace Autofac.Test.Core.Diagnostics +namespace Autofac.Test.Diagnostics { public class DiagnosticSourceExtensionsTests { diff --git a/test/Autofac.Test/Core/Diagnostics/DiagnosticTracerBaseTests.cs b/test/Autofac.Test/Diagnostics/DiagnosticTracerBaseTests.cs similarity index 96% rename from test/Autofac.Test/Core/Diagnostics/DiagnosticTracerBaseTests.cs rename to test/Autofac.Test/Diagnostics/DiagnosticTracerBaseTests.cs index 4485821f0..e6bf29902 100644 --- a/test/Autofac.Test/Core/Diagnostics/DiagnosticTracerBaseTests.cs +++ b/test/Autofac.Test/Diagnostics/DiagnosticTracerBaseTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -using Autofac.Core.Diagnostics; +using Autofac.Diagnostics; using Xunit; -namespace Autofac.Test.Core.Diagnostics +namespace Autofac.Test.Diagnostics { public class DiagnosticTracerBaseTests { diff --git a/test/Autofac.Test/Core/Diagnostics/OperationDiagnosticTracerBaseTests.cs b/test/Autofac.Test/Diagnostics/OperationDiagnosticTracerBaseTests.cs similarity index 96% rename from test/Autofac.Test/Core/Diagnostics/OperationDiagnosticTracerBaseTests.cs rename to test/Autofac.Test/Diagnostics/OperationDiagnosticTracerBaseTests.cs index 2ac886b77..d597e288d 100644 --- a/test/Autofac.Test/Core/Diagnostics/OperationDiagnosticTracerBaseTests.cs +++ b/test/Autofac.Test/Diagnostics/OperationDiagnosticTracerBaseTests.cs @@ -2,10 +2,10 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using System.Collections.Generic; -using Autofac.Core.Diagnostics; +using Autofac.Diagnostics; using Xunit; -namespace Autofac.Test.Core.Diagnostics +namespace Autofac.Test.Diagnostics { public class OperationDiagnosticTracerBaseTests { diff --git a/test/Autofac.Test/Features/Decorators/DecoratorTests.cs b/test/Autofac.Test/Features/Decorators/DecoratorTests.cs index 56d90588c..dc87343bf 100644 --- a/test/Autofac.Test/Features/Decorators/DecoratorTests.cs +++ b/test/Autofac.Test/Features/Decorators/DecoratorTests.cs @@ -1,7 +1,7 @@ using System; using System.Linq; using Autofac.Core; -using Autofac.Core.Diagnostics; +using Autofac.Diagnostics; using Xunit; namespace Autofac.Test.Features.Decorators diff --git a/test/Autofac.Test/Features/Decorators/OpenGenericDecoratorTests.cs b/test/Autofac.Test/Features/Decorators/OpenGenericDecoratorTests.cs index ec12e6fb6..16bf98d2f 100644 --- a/test/Autofac.Test/Features/Decorators/OpenGenericDecoratorTests.cs +++ b/test/Autofac.Test/Features/Decorators/OpenGenericDecoratorTests.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using Autofac.Core; -using Autofac.Core.Diagnostics; +using Autofac.Diagnostics; using Autofac.Features.Decorators; using Xunit; using Xunit.Abstractions; diff --git a/test/Autofac.Test/Mocks.cs b/test/Autofac.Test/Mocks.cs index 4c0b08baa..c6ceb2096 100644 --- a/test/Autofac.Test/Mocks.cs +++ b/test/Autofac.Test/Mocks.cs @@ -3,11 +3,10 @@ using System.Reflection; using Autofac.Core; using Autofac.Core.Activators.Reflection; -using Autofac.Core.Diagnostics; using Autofac.Core.Registration; using Autofac.Core.Resolving; -using Autofac.Core.Resolving.Middleware; using Autofac.Core.Resolving.Pipeline; +using Autofac.Diagnostics; namespace Autofac.Test { From 9777ad6762449778434527d6f751e23a0e089c87 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 17 Jul 2020 10:42:26 -0700 Subject: [PATCH 36/45] Namespace move, forgot the resources location. --- src/Autofac/Diagnostics/TracerMessages.Designer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Autofac/Diagnostics/TracerMessages.Designer.cs b/src/Autofac/Diagnostics/TracerMessages.Designer.cs index e8bb3e961..c559fdcf9 100644 --- a/src/Autofac/Diagnostics/TracerMessages.Designer.cs +++ b/src/Autofac/Diagnostics/TracerMessages.Designer.cs @@ -39,7 +39,7 @@ internal class TracerMessages { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Autofac.Core.Diagnostics.TracerMessages", typeof(TracerMessages).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Autofac.Diagnostics.TracerMessages", typeof(TracerMessages).Assembly); resourceMan = temp; } return resourceMan; From 9bad3ebd9f056a21555dc4514ffa3feadabe382b Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 20 Jul 2020 10:52:45 -0700 Subject: [PATCH 37/45] Removed all this qualifiers from product source. --- src/Autofac/Builder/DeferredCallback.cs | 8 +-- src/Autofac/Builder/SimpleActivatorData.cs | 4 +- .../ProvidedInstanceActivator.cs | 2 +- .../Activators/Reflection/BoundConstructor.cs | 2 +- .../NoConstructorsFoundException.cs | 4 +- .../Reflection/ReflectionActivator.cs | 2 +- src/Autofac/Core/Container.cs | 2 +- src/Autofac/Core/Lifetime/LifetimeScope.cs | 4 +- .../Core/Lifetime/MatchingScopeLifetime.cs | 13 ++--- .../Pipeline/ResolveRequestContextBase.cs | 2 +- src/Autofac/Util/FallbackDictionary.cs | 50 ++++++++----------- src/Autofac/Util/ReleaseAction.cs | 2 +- 12 files changed, 41 insertions(+), 54 deletions(-) diff --git a/src/Autofac/Builder/DeferredCallback.cs b/src/Autofac/Builder/DeferredCallback.cs index 6d953d31f..58f25be4b 100644 --- a/src/Autofac/Builder/DeferredCallback.cs +++ b/src/Autofac/Builder/DeferredCallback.cs @@ -28,8 +28,8 @@ public DeferredCallback(Action callback) throw new ArgumentNullException(nameof(callback)); } - this.Id = Guid.NewGuid(); - this.Callback = callback; + Id = Guid.NewGuid(); + Callback = callback; } /// @@ -46,7 +46,7 @@ public Action Callback { get { - return this._callback; + return _callback; } set @@ -56,7 +56,7 @@ public Action Callback throw new ArgumentNullException(nameof(value)); } - this._callback = value; + _callback = value; } } diff --git a/src/Autofac/Builder/SimpleActivatorData.cs b/src/Autofac/Builder/SimpleActivatorData.cs index 17ecaf99d..626e085d4 100644 --- a/src/Autofac/Builder/SimpleActivatorData.cs +++ b/src/Autofac/Builder/SimpleActivatorData.cs @@ -39,9 +39,7 @@ public class SimpleActivatorData : IConcreteActivatorData /// The activator to return. public SimpleActivatorData(IInstanceActivator activator) { - if (activator == null) throw new ArgumentNullException(nameof(activator)); - - Activator = activator; + Activator = activator ?? throw new ArgumentNullException(nameof(activator)); } /// diff --git a/src/Autofac/Core/Activators/ProvidedInstance/ProvidedInstanceActivator.cs b/src/Autofac/Core/Activators/ProvidedInstance/ProvidedInstanceActivator.cs index 4de5b1fff..6d6f935b1 100644 --- a/src/Autofac/Core/Activators/ProvidedInstance/ProvidedInstanceActivator.cs +++ b/src/Autofac/Core/Activators/ProvidedInstance/ProvidedInstanceActivator.cs @@ -67,7 +67,7 @@ private object GetInstance() CheckNotDisposed(); if (_activated) - throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, ProvidedInstanceActivatorResources.InstanceAlreadyActivated, this._instance.GetType())); + throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, ProvidedInstanceActivatorResources.InstanceAlreadyActivated, _instance.GetType())); _activated = true; diff --git a/src/Autofac/Core/Activators/Reflection/BoundConstructor.cs b/src/Autofac/Core/Activators/Reflection/BoundConstructor.cs index bec6283d1..3d20bf3c2 100644 --- a/src/Autofac/Core/Activators/Reflection/BoundConstructor.cs +++ b/src/Autofac/Core/Activators/Reflection/BoundConstructor.cs @@ -119,7 +119,7 @@ public object Instantiate() { if (!CanInstantiate) { - throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, BoundConstructorResources.CannotInstantitate, this.Description)); + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, BoundConstructorResources.CannotInstantitate, Description)); } var values = new object?[_valueRetrievers!.Length]; diff --git a/src/Autofac/Core/Activators/Reflection/NoConstructorsFoundException.cs b/src/Autofac/Core/Activators/Reflection/NoConstructorsFoundException.cs index b4ae0dcf6..c5d1c2e70 100644 --- a/src/Autofac/Core/Activators/Reflection/NoConstructorsFoundException.cs +++ b/src/Autofac/Core/Activators/Reflection/NoConstructorsFoundException.cs @@ -51,7 +51,7 @@ public NoConstructorsFoundException(Type offendingType, string message) : base(message) { if (offendingType == null) throw new ArgumentNullException(nameof(offendingType)); - this.OffendingType = offendingType; + OffendingType = offendingType; } /// @@ -74,7 +74,7 @@ public NoConstructorsFoundException(Type offendingType, string message, Exceptio : base(message, innerException) { if (offendingType == null) throw new ArgumentNullException(nameof(offendingType)); - this.OffendingType = offendingType; + OffendingType = offendingType; } /// diff --git a/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs b/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs index ad72ef9f7..779a9644b 100644 --- a/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs +++ b/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs @@ -109,7 +109,7 @@ public void ConfigurePipeline(IComponentRegistryServices componentRegistryServic _constructorBinders = binders; - pipelineBuilder.Use(this.ToString(), PipelinePhase.Activation, MiddlewareInsertionMode.EndOfPhase, (ctxt, next) => + pipelineBuilder.Use(ToString(), PipelinePhase.Activation, MiddlewareInsertionMode.EndOfPhase, (ctxt, next) => { ctxt.Instance = ActivateInstance(ctxt, ctxt.Parameters); diff --git a/src/Autofac/Core/Container.cs b/src/Autofac/Core/Container.cs index 779c5003c..54002a339 100644 --- a/src/Autofac/Core/Container.cs +++ b/src/Autofac/Core/Container.cs @@ -100,7 +100,7 @@ public ILifetimeScope BeginLifetimeScope(object tag, Action co } /// - public DiagnosticListener DiagnosticSource => this._rootLifetimeScope.DiagnosticSource; + public DiagnosticListener DiagnosticSource => _rootLifetimeScope.DiagnosticSource; /// /// Gets the disposer associated with this container. Instances can be associated diff --git a/src/Autofac/Core/Lifetime/LifetimeScope.cs b/src/Autofac/Core/Lifetime/LifetimeScope.cs index 3a683f041..9071b51ae 100644 --- a/src/Autofac/Core/Lifetime/LifetimeScope.cs +++ b/src/Autofac/Core/Lifetime/LifetimeScope.cs @@ -81,7 +81,7 @@ protected LifetimeScope(IComponentRegistry componentRegistry, LifetimeScope pare _sharedInstances[SelfRegistrationId] = this; RootLifetimeScope = _parentScope.RootLifetimeScope; - this.DiagnosticSource = _parentScope.DiagnosticSource; + DiagnosticSource = _parentScope.DiagnosticSource; } /// @@ -96,7 +96,7 @@ public LifetimeScope(IComponentRegistry componentRegistry, object tag) _sharedInstances[SelfRegistrationId] = this; RootLifetimeScope = this; - this.DiagnosticSource = new DiagnosticListener("Autofac"); + DiagnosticSource = new DiagnosticListener("Autofac"); } /// diff --git a/src/Autofac/Core/Lifetime/MatchingScopeLifetime.cs b/src/Autofac/Core/Lifetime/MatchingScopeLifetime.cs index d89675abc..f1e60567c 100644 --- a/src/Autofac/Core/Lifetime/MatchingScopeLifetime.cs +++ b/src/Autofac/Core/Lifetime/MatchingScopeLifetime.cs @@ -43,12 +43,7 @@ public class MatchingScopeLifetime : IComponentLifetime /// The tags applied to matching scopes. public MatchingScopeLifetime(params object[] lifetimeScopeTagsToMatch) { - if (lifetimeScopeTagsToMatch == null) - { - throw new ArgumentNullException(nameof(lifetimeScopeTagsToMatch)); - } - - this._tagsToMatch = lifetimeScopeTagsToMatch; + _tagsToMatch = lifetimeScopeTagsToMatch ?? throw new ArgumentNullException(nameof(lifetimeScopeTagsToMatch)); } /// @@ -62,7 +57,7 @@ public IEnumerable TagsToMatch { get { - return this._tagsToMatch; + return _tagsToMatch; } } @@ -82,7 +77,7 @@ public ISharingLifetimeScope FindScope(ISharingLifetimeScope mostNestedVisibleSc ISharingLifetimeScope? next = mostNestedVisibleScope; while (next != null) { - if (this._tagsToMatch.Contains(next.Tag)) + if (_tagsToMatch.Contains(next.Tag)) { return next; } @@ -91,7 +86,7 @@ public ISharingLifetimeScope FindScope(ISharingLifetimeScope mostNestedVisibleSc } throw new DependencyResolutionException(string.Format( - CultureInfo.CurrentCulture, MatchingScopeLifetimeResources.MatchingScopeNotFound, string.Join(", ", this._tagsToMatch))); + CultureInfo.CurrentCulture, MatchingScopeLifetimeResources.MatchingScopeNotFound, string.Join(", ", _tagsToMatch))); } } } diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs index 41163879d..6ebe84cc6 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs @@ -57,7 +57,7 @@ public abstract class ResolveRequestContextBase : IComponentContext ActivationScope = scope; Parameters = request.Parameters; PhaseReached = PipelinePhase.ResolveRequestStart; - this.DiagnosticSource = scope.DiagnosticSource; + DiagnosticSource = scope.DiagnosticSource; _resolveRequest = request; } diff --git a/src/Autofac/Util/FallbackDictionary.cs b/src/Autofac/Util/FallbackDictionary.cs index b4961bb26..cffc1bde7 100644 --- a/src/Autofac/Util/FallbackDictionary.cs +++ b/src/Autofac/Util/FallbackDictionary.cs @@ -39,12 +39,7 @@ public FallbackDictionary() /// public FallbackDictionary(IDictionary parent) { - if (parent == null) - { - throw new ArgumentNullException(nameof(parent)); - } - - this._parent = parent; + _parent = parent ?? throw new ArgumentNullException(nameof(parent)); } /// @@ -57,7 +52,7 @@ public int Count { get { - return this.Keys.Count; + return Keys.Count; } } @@ -90,7 +85,7 @@ public ICollection Keys { get { - return this.OrderedKeys().ToArray(); + return OrderedKeys().ToArray(); } } @@ -109,7 +104,7 @@ public ICollection Values { get { - var keys = this.Keys.ToArray(); + var keys = Keys.ToArray(); var values = new TValue[keys.Length]; for (var i = 0; i < keys.Length; i++) { @@ -136,18 +131,17 @@ public ICollection Values { get { - TValue value; - if (this._localValues.TryGetValue(key, out value)) + if (_localValues.TryGetValue(key, out TValue value)) { return value; } - return this._parent[key]; + return _parent[key]; } set { - this._localValues[key] = value; + _localValues[key] = value; } } @@ -162,7 +156,7 @@ public ICollection Values /// public void Add(KeyValuePair item) { - this.Add(item.Key, item.Value); + Add(item.Key, item.Value); } /// @@ -188,12 +182,12 @@ public void Add(TKey key, TValue value) throw new ArgumentNullException(nameof(key)); } - if (this._parent.ContainsKey(key)) + if (_parent.ContainsKey(key)) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, FallbackDictionaryResources.DuplicateItem, key)); } - this._localValues.Add(key, value); + _localValues.Add(key, value); } /// @@ -201,7 +195,7 @@ public void Add(TKey key, TValue value) /// public void Clear() { - this._localValues.Clear(); + _localValues.Clear(); } /// @@ -213,12 +207,12 @@ public void Clear() /// public bool Contains(KeyValuePair item) { - if (this._localValues.ContainsKey(item.Key)) + if (_localValues.ContainsKey(item.Key)) { - return this._localValues.Contains(item); + return _localValues.Contains(item); } - return this._parent.Contains(item); + return _parent.Contains(item); } /// @@ -230,7 +224,7 @@ public bool Contains(KeyValuePair item) /// public bool ContainsKey(TKey key) { - return this.Keys.Contains(key); + return Keys.Contains(key); } /// @@ -257,7 +251,7 @@ public void CopyTo(KeyValuePair[] array, int arrayIndex) /// public IEnumerator> GetEnumerator() { - foreach (var key in this.OrderedKeys()) + foreach (var key in OrderedKeys()) { yield return new KeyValuePair(key, this[key]); } @@ -278,7 +272,7 @@ public void CopyTo(KeyValuePair[] array, int arrayIndex) /// public bool Remove(KeyValuePair item) { - return this._localValues.Remove(item); + return _localValues.Remove(item); } /// @@ -296,7 +290,7 @@ public bool Remove(KeyValuePair item) /// public bool Remove(TKey key) { - return this._localValues.Remove(key); + return _localValues.Remove(key); } /// @@ -309,12 +303,12 @@ public bool Remove(TKey key) /// public bool TryGetValue(TKey key, out TValue value) { - if (this._localValues.TryGetValue(key, out value)) + if (_localValues.TryGetValue(key, out value)) { return true; } - return this._parent.TryGetValue(key, out value); + return _parent.TryGetValue(key, out value); } /// @@ -325,7 +319,7 @@ public bool TryGetValue(TKey key, out TValue value) /// IEnumerator IEnumerable.GetEnumerator() { - return this.GetEnumerator(); + return GetEnumerator(); } /// @@ -336,7 +330,7 @@ IEnumerator IEnumerable.GetEnumerator() /// private IEnumerable OrderedKeys() { - return this._localValues.Keys.Union(this._parent.Keys).Distinct().OrderBy(k => k); + return _localValues.Keys.Union(_parent.Keys).Distinct().OrderBy(k => k); } } } diff --git a/src/Autofac/Util/ReleaseAction.cs b/src/Autofac/Util/ReleaseAction.cs index 54124421c..f8a3079fb 100644 --- a/src/Autofac/Util/ReleaseAction.cs +++ b/src/Autofac/Util/ReleaseAction.cs @@ -61,7 +61,7 @@ protected override void Dispose(bool disposing) // disposal runs to ensure any calls to, say, .ReplaceInstance() // during .OnActivating() will be accounted for. if (disposing) - _action(this._factory()); + _action(_factory()); base.Dispose(disposing); } From 09e08023d1b5db7d89ee05e39ea3a8d47d05f2f7 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 20 Jul 2020 11:20:46 -0700 Subject: [PATCH 38/45] Disabled subscriptions on default diagnostic tracer to ensure traces work. --- .../Diagnostics/DiagnosticTracerBase.cs | 70 +++++++++++++++---- .../OperationDiagnosticTracerBase.cs | 14 +++- .../Diagnostics/TracerMessages.Designer.cs | 9 +++ src/Autofac/Diagnostics/TracerMessages.resx | 3 + .../DiagnosticSourceExtensionsTests.cs | 2 +- 5 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/Autofac/Diagnostics/DiagnosticTracerBase.cs b/src/Autofac/Diagnostics/DiagnosticTracerBase.cs index 0525ccc88..7e04e603e 100644 --- a/src/Autofac/Diagnostics/DiagnosticTracerBase.cs +++ b/src/Autofac/Diagnostics/DiagnosticTracerBase.cs @@ -51,7 +51,28 @@ public abstract class DiagnosticTracerBase : IObserver /// /// - public void Enable(string diagnosticName) + public virtual void Enable(string diagnosticName) + { + EnableBase(diagnosticName); + } + + /// + /// Subscribes the observer to a particular named diagnostic event. + /// + /// + /// The name of the event to which the observer should subscribe. Diagnostic + /// names are case-sensitive. + /// + /// + /// + /// Derived classes may override the public method to + /// handle consumer calls to enabling events; constructors needing to call + /// non-virtual methods may directly call this to avoid executing against + /// partially constructed derived classes. + /// + /// + /// + protected void EnableBase(string diagnosticName) { if (diagnosticName == null) { @@ -79,15 +100,19 @@ public void Enable(string diagnosticName) /// public void EnableAll() { - _subscriptions.Add(DiagnosticEventKeys.MiddlewareStart); - _subscriptions.Add(DiagnosticEventKeys.MiddlewareFailure); - _subscriptions.Add(DiagnosticEventKeys.MiddlewareSuccess); - _subscriptions.Add(DiagnosticEventKeys.OperationFailure); - _subscriptions.Add(DiagnosticEventKeys.OperationStart); - _subscriptions.Add(DiagnosticEventKeys.OperationSuccess); - _subscriptions.Add(DiagnosticEventKeys.RequestFailure); - _subscriptions.Add(DiagnosticEventKeys.RequestStart); - _subscriptions.Add(DiagnosticEventKeys.RequestSuccess); + // EnableAll is intentionally not virtual so it can be called + // from the OperationDiagnosticTracerBase constructor. + // + // Subscriptions ordered intentionally from most to least common events. + EnableBase(DiagnosticEventKeys.MiddlewareStart); + EnableBase(DiagnosticEventKeys.MiddlewareSuccess); + EnableBase(DiagnosticEventKeys.RequestStart); + EnableBase(DiagnosticEventKeys.RequestSuccess); + EnableBase(DiagnosticEventKeys.OperationStart); + EnableBase(DiagnosticEventKeys.OperationSuccess); + EnableBase(DiagnosticEventKeys.MiddlewareFailure); + EnableBase(DiagnosticEventKeys.RequestFailure); + EnableBase(DiagnosticEventKeys.OperationFailure); } /// @@ -107,7 +132,28 @@ public void EnableAll() /// /// /// - public void Disable(string diagnosticName) + public virtual void Disable(string diagnosticName) + { + DisableBase(diagnosticName); + } + + /// + /// Unsubscribes the observer from a particular named diagnostic event. + /// + /// + /// The name of the event to which the observer should unsubscribe. Diagnostic + /// names are case-sensitive. + /// + /// + /// + /// Derived classes may override the public method to + /// handle consumer calls to disabling events; constructors needing to call + /// non-virtual methods may directly call this to avoid executing against + /// partially constructed derived classes. + /// + /// + /// + protected void DisableBase(string diagnosticName) { if (diagnosticName == null) { @@ -323,7 +369,7 @@ public virtual void OnRequestSuccess(RequestDiagnosticData data) /// /// The diagnostic data associated with the event. /// - public virtual void Write(string diagnosticName, object data) + protected virtual void Write(string diagnosticName, object data) { if (data == null || !IsEnabled(diagnosticName)) { diff --git a/src/Autofac/Diagnostics/OperationDiagnosticTracerBase.cs b/src/Autofac/Diagnostics/OperationDiagnosticTracerBase.cs index 14963ecf2..8b41045b6 100644 --- a/src/Autofac/Diagnostics/OperationDiagnosticTracerBase.cs +++ b/src/Autofac/Diagnostics/OperationDiagnosticTracerBase.cs @@ -43,10 +43,22 @@ protected OperationDiagnosticTracerBase(IEnumerable subscriptions) foreach (var subscription in subscriptions) { - Enable(subscription); + EnableBase(subscription); } } + /// + public override void Enable(string diagnosticName) + { + throw new NotSupportedException(TracerMessages.SubscriptionsDisabled); + } + + /// + public override void Disable(string diagnosticName) + { + throw new NotSupportedException(TracerMessages.SubscriptionsDisabled); + } + /// /// Event raised when a resolve operation completes and trace data is available. /// diff --git a/src/Autofac/Diagnostics/TracerMessages.Designer.cs b/src/Autofac/Diagnostics/TracerMessages.Designer.cs index c559fdcf9..c2406382a 100644 --- a/src/Autofac/Diagnostics/TracerMessages.Designer.cs +++ b/src/Autofac/Diagnostics/TracerMessages.Designer.cs @@ -222,6 +222,15 @@ internal class TracerMessages { } } + /// + /// Looks up a localized string similar to You may not add or remove subscriptions on this tracer. This ensures all required events are properly captured and traces can be correctly handled.. + /// + internal static string SubscriptionsDisabled { + get { + return ResourceManager.GetString("SubscriptionsDisabled", resourceCulture); + } + } + /// /// Looks up a localized string similar to Target: {0}. /// diff --git a/src/Autofac/Diagnostics/TracerMessages.resx b/src/Autofac/Diagnostics/TracerMessages.resx index 212aa963a..d3f6959ba 100644 --- a/src/Autofac/Diagnostics/TracerMessages.resx +++ b/src/Autofac/Diagnostics/TracerMessages.resx @@ -172,6 +172,9 @@ Service: {0} + + You may not add or remove subscriptions on this tracer. This ensures all required events are properly captured and traces can be correctly handled. + Target: {0} diff --git a/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs b/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs index 900044d2a..906665f71 100644 --- a/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs +++ b/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs @@ -268,7 +268,7 @@ private class MockSubscriber : DiagnosticTracerBase { public List> Events { get; } = new List>(); - public override void Write(string diagnosticName, object data) + protected override void Write(string diagnosticName, object data) { Events.Add(new KeyValuePair(diagnosticName, data)); } From 879d4963f8d0bfa96e334c13291d18ba3d321df3 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 20 Jul 2020 11:26:43 -0700 Subject: [PATCH 39/45] Trace event handling methods now protected instead of public. --- .../Diagnostics/DefaultDiagnosticTracer.cs | 18 +++++++++--------- .../Diagnostics/DiagnosticTracerBase.cs | 18 +++++++++--------- test/Autofac.Test/Mocks.cs | 18 +++++++++--------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Autofac/Diagnostics/DefaultDiagnosticTracer.cs b/src/Autofac/Diagnostics/DefaultDiagnosticTracer.cs index e3249c851..f68f78f6c 100644 --- a/src/Autofac/Diagnostics/DefaultDiagnosticTracer.cs +++ b/src/Autofac/Diagnostics/DefaultDiagnosticTracer.cs @@ -49,7 +49,7 @@ public DefaultDiagnosticTracer() public override int OperationsInProgress => _operationBuilders.Count; /// - public override void OnOperationStart(OperationStartDiagnosticData data) + protected override void OnOperationStart(OperationStartDiagnosticData data) { if (data is null) { @@ -64,7 +64,7 @@ public override void OnOperationStart(OperationStartDiagnosticData data) } /// - public override void OnRequestStart(RequestDiagnosticData data) + protected override void OnRequestStart(RequestDiagnosticData data) { if (data is null) { @@ -90,7 +90,7 @@ public override void OnRequestStart(RequestDiagnosticData data) } /// - public override void OnMiddlewareStart(MiddlewareDiagnosticData data) + protected override void OnMiddlewareStart(MiddlewareDiagnosticData data) { if (data is null) { @@ -105,7 +105,7 @@ public override void OnMiddlewareStart(MiddlewareDiagnosticData data) } /// - public override void OnMiddlewareFailure(MiddlewareDiagnosticData data) + protected override void OnMiddlewareFailure(MiddlewareDiagnosticData data) { if (data is null) { @@ -120,7 +120,7 @@ public override void OnMiddlewareFailure(MiddlewareDiagnosticData data) } /// - public override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) + protected override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) { if (data is null) { @@ -135,7 +135,7 @@ public override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) } /// - public override void OnRequestFailure(RequestFailureDiagnosticData data) + protected override void OnRequestFailure(RequestFailureDiagnosticData data) { if (data is null) { @@ -167,7 +167,7 @@ public override void OnRequestFailure(RequestFailureDiagnosticData data) } /// - public override void OnRequestSuccess(RequestDiagnosticData data) + protected override void OnRequestSuccess(RequestDiagnosticData data) { if (data is null) { @@ -183,7 +183,7 @@ public override void OnRequestSuccess(RequestDiagnosticData data) } /// - public override void OnOperationFailure(OperationFailureDiagnosticData data) + protected override void OnOperationFailure(OperationFailureDiagnosticData data) { if (data is null) { @@ -208,7 +208,7 @@ public override void OnOperationFailure(OperationFailureDiagnosticData data) } /// - public override void OnOperationSuccess(OperationSuccessDiagnosticData data) + protected override void OnOperationSuccess(OperationSuccessDiagnosticData data) { if (data is null) { diff --git a/src/Autofac/Diagnostics/DiagnosticTracerBase.cs b/src/Autofac/Diagnostics/DiagnosticTracerBase.cs index 7e04e603e..810428b6b 100644 --- a/src/Autofac/Diagnostics/DiagnosticTracerBase.cs +++ b/src/Autofac/Diagnostics/DiagnosticTracerBase.cs @@ -226,7 +226,7 @@ public bool IsEnabled(string diagnosticName) /// on the event. By default, the base class does nothing. /// /// - public virtual void OnMiddlewareFailure(MiddlewareDiagnosticData data) + protected virtual void OnMiddlewareFailure(MiddlewareDiagnosticData data) { } @@ -242,7 +242,7 @@ public virtual void OnMiddlewareFailure(MiddlewareDiagnosticData data) /// on the event. By default, the base class does nothing. /// /// - public virtual void OnMiddlewareStart(MiddlewareDiagnosticData data) + protected virtual void OnMiddlewareStart(MiddlewareDiagnosticData data) { } @@ -258,7 +258,7 @@ public virtual void OnMiddlewareStart(MiddlewareDiagnosticData data) /// on the event. By default, the base class does nothing. /// /// - public virtual void OnMiddlewareSuccess(MiddlewareDiagnosticData data) + protected virtual void OnMiddlewareSuccess(MiddlewareDiagnosticData data) { } @@ -274,7 +274,7 @@ public virtual void OnMiddlewareSuccess(MiddlewareDiagnosticData data) /// on the event. By default, the base class does nothing. /// /// - public virtual void OnOperationFailure(OperationFailureDiagnosticData data) + protected virtual void OnOperationFailure(OperationFailureDiagnosticData data) { } @@ -290,7 +290,7 @@ public virtual void OnOperationFailure(OperationFailureDiagnosticData data) /// on the event. By default, the base class does nothing. /// /// - public virtual void OnOperationStart(OperationStartDiagnosticData data) + protected virtual void OnOperationStart(OperationStartDiagnosticData data) { } @@ -306,7 +306,7 @@ public virtual void OnOperationStart(OperationStartDiagnosticData data) /// on the event. By default, the base class does nothing. /// /// - public virtual void OnOperationSuccess(OperationSuccessDiagnosticData data) + protected virtual void OnOperationSuccess(OperationSuccessDiagnosticData data) { } @@ -322,7 +322,7 @@ public virtual void OnOperationSuccess(OperationSuccessDiagnosticData data) /// on the event. By default, the base class does nothing. /// /// - public virtual void OnRequestFailure(RequestFailureDiagnosticData data) + protected virtual void OnRequestFailure(RequestFailureDiagnosticData data) { } @@ -338,7 +338,7 @@ public virtual void OnRequestFailure(RequestFailureDiagnosticData data) /// on the event. By default, the base class does nothing. /// /// - public virtual void OnRequestStart(RequestDiagnosticData data) + protected virtual void OnRequestStart(RequestDiagnosticData data) { } @@ -354,7 +354,7 @@ public virtual void OnRequestStart(RequestDiagnosticData data) /// on the event. By default, the base class does nothing. /// /// - public virtual void OnRequestSuccess(RequestDiagnosticData data) + protected virtual void OnRequestSuccess(RequestDiagnosticData data) { } diff --git a/test/Autofac.Test/Mocks.cs b/test/Autofac.Test/Mocks.cs index c6ceb2096..04a90a52e 100644 --- a/test/Autofac.Test/Mocks.cs +++ b/test/Autofac.Test/Mocks.cs @@ -126,47 +126,47 @@ public MockTracer() public event Action OperationSucceeding; - public override void OnOperationStart(OperationStartDiagnosticData data) + protected override void OnOperationStart(OperationStartDiagnosticData data) { OperationStarting?.Invoke(data.Operation, data.InitiatingRequest); } - public override void OnRequestStart(RequestDiagnosticData data) + protected override void OnRequestStart(RequestDiagnosticData data) { RequestStarting?.Invoke(data.Operation, data.RequestContext); } - public override void OnMiddlewareStart(MiddlewareDiagnosticData data) + protected override void OnMiddlewareStart(MiddlewareDiagnosticData data) { EnteringMiddleware?.Invoke(data.RequestContext, data.Middleware); } - public override void OnMiddlewareFailure(MiddlewareDiagnosticData data) + protected override void OnMiddlewareFailure(MiddlewareDiagnosticData data) { ExitingMiddleware?.Invoke(data.RequestContext, data.Middleware, false); } - public override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) + protected override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) { ExitingMiddleware?.Invoke(data.RequestContext, data.Middleware, true); } - public override void OnRequestFailure(RequestFailureDiagnosticData data) + protected override void OnRequestFailure(RequestFailureDiagnosticData data) { RequestFailing?.Invoke(data.Operation, data.RequestContext, data.RequestException); } - public override void OnRequestSuccess(RequestDiagnosticData data) + protected override void OnRequestSuccess(RequestDiagnosticData data) { RequestSucceeding?.Invoke(data.Operation, data.RequestContext); } - public override void OnOperationFailure(OperationFailureDiagnosticData data) + protected override void OnOperationFailure(OperationFailureDiagnosticData data) { OperationFailing?.Invoke(data.Operation, data.OperationException); } - public override void OnOperationSuccess(OperationSuccessDiagnosticData data) + protected override void OnOperationSuccess(OperationSuccessDiagnosticData data) { OperationSucceeding?.Invoke(data.Operation, data.ResolvedInstance); } From 01cfe4e265befe410eed83dfea173d5cf2e4cea5 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 20 Jul 2020 11:57:04 -0700 Subject: [PATCH 40/45] Only IContainer has a public DiagnosticSource to avoid incorrect subscription handling mid-operation or on specific lifetime scopes. --- src/Autofac/Core/Container.cs | 2 +- src/Autofac/Core/Lifetime/LifetimeScope.cs | 10 +++++++--- .../Core/Resolving/Pipeline/ResolveRequestContext.cs | 10 ++++++++-- .../Resolving/Pipeline/ResolveRequestContextBase.cs | 10 +++++++--- src/Autofac/Core/Resolving/ResolveOperation.cs | 10 ++++++---- src/Autofac/Core/Resolving/ResolveOperationBase.cs | 9 ++++++--- src/Autofac/IContainer.cs | 7 +++++++ src/Autofac/ILifetimeScope.cs | 7 ------- test/Autofac.Test/ActivatorPipelineExtensions.cs | 8 +++++--- .../Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs | 7 +++---- .../Core/Resolving/ResolveOperationTests.cs | 5 +++-- .../Diagnostics/DiagnosticSourceExtensionsTests.cs | 4 ++-- 12 files changed, 55 insertions(+), 34 deletions(-) diff --git a/src/Autofac/Core/Container.cs b/src/Autofac/Core/Container.cs index 54002a339..4b91496be 100644 --- a/src/Autofac/Core/Container.cs +++ b/src/Autofac/Core/Container.cs @@ -39,7 +39,7 @@ namespace Autofac.Core [DebuggerDisplay("Tag = {Tag}, IsDisposed = {IsDisposed}")] public class Container : Disposable, IContainer, IServiceProvider { - private readonly ILifetimeScope _rootLifetimeScope; + private readonly LifetimeScope _rootLifetimeScope; /// /// Initializes a new instance of the class. diff --git a/src/Autofac/Core/Lifetime/LifetimeScope.cs b/src/Autofac/Core/Lifetime/LifetimeScope.cs index 9071b51ae..8fc482724 100644 --- a/src/Autofac/Core/Lifetime/LifetimeScope.cs +++ b/src/Autofac/Core/Lifetime/LifetimeScope.cs @@ -97,6 +97,7 @@ public LifetimeScope(IComponentRegistry componentRegistry, object tag) _sharedInstances[SelfRegistrationId] = this; RootLifetimeScope = this; DiagnosticSource = new DiagnosticListener("Autofac"); + Disposer.AddInstanceForDisposal(DiagnosticSource); } /// @@ -160,8 +161,11 @@ private void RaiseBeginning(ILifetimeScope scope) handler?.Invoke(this, new LifetimeScopeBeginningEventArgs(scope)); } - /// - public DiagnosticListener DiagnosticSource { get; } + /// + /// Gets the to which + /// trace events should be written. + /// + internal DiagnosticListener DiagnosticSource { get; } /// /// Begin a new anonymous sub-scope, with additional components available to it. @@ -292,7 +296,7 @@ public object ResolveComponent(ResolveRequest request) CheckNotDisposed(); - var operation = new ResolveOperation(this); + var operation = new ResolveOperation(this, DiagnosticSource); var handler = ResolveOperationBeginning; handler?.Invoke(this, new ResolveOperationBeginningEventArgs(operation)); return operation.Execute(request); diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs index f0aeebbd1..ea503dcfd 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs @@ -23,6 +23,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. +using System.Diagnostics; + namespace Autofac.Core.Resolving.Pipeline { /// @@ -36,11 +38,15 @@ internal sealed class ResolveRequestContext : ResolveRequestContextBase /// The owning resolve operation. /// The initiating resolve request. /// The lifetime scope. + /// + /// The to which trace events should be written. + /// internal ResolveRequestContext( ResolveOperationBase owningOperation, ResolveRequest request, - ISharingLifetimeScope scope) - : base(owningOperation, request, scope) + ISharingLifetimeScope scope, + DiagnosticSource diagnosticSource) + : base(owningOperation, request, scope, diagnosticSource) { } diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs index 6ebe84cc6..3baa4ca27 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs @@ -48,16 +48,20 @@ public abstract class ResolveRequestContextBase : IComponentContext /// The owning resolve operation. /// The initiating resolve request. /// The lifetime scope. + /// + /// The to which trace events should be written. + /// internal ResolveRequestContextBase( ResolveOperationBase owningOperation, ResolveRequest request, - ISharingLifetimeScope scope) + ISharingLifetimeScope scope, + DiagnosticSource diagnosticSource) { Operation = owningOperation; ActivationScope = scope; Parameters = request.Parameters; PhaseReached = PipelinePhase.ResolveRequestStart; - DiagnosticSource = scope.DiagnosticSource; + DiagnosticSource = diagnosticSource; _resolveRequest = request; } @@ -107,7 +111,7 @@ public abstract class ResolveRequestContextBase : IComponentContext public bool NewInstanceActivated => Instance is object && PhaseReached == PipelinePhase.Activation; /// - /// Gets the for the request. + /// Gets the to which trace events should be written. /// public DiagnosticSource DiagnosticSource { get; } diff --git a/src/Autofac/Core/Resolving/ResolveOperation.cs b/src/Autofac/Core/Resolving/ResolveOperation.cs index 359039763..60688fa6b 100644 --- a/src/Autofac/Core/Resolving/ResolveOperation.cs +++ b/src/Autofac/Core/Resolving/ResolveOperation.cs @@ -23,9 +23,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using Autofac.Core.Resolving.Pipeline; -using Autofac.Diagnostics; namespace Autofac.Core.Resolving { @@ -40,8 +39,11 @@ internal sealed class ResolveOperation : ResolveOperationBase /// /// The most nested scope in which to begin the operation. The operation /// can move upward to less nested scopes as components with wider sharing scopes are activated. - public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope) - : base(mostNestedLifetimeScope) + /// + /// The to which trace events should be written. + /// + public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, DiagnosticSource diagnosticSource) + : base(mostNestedLifetimeScope, diagnosticSource) { } diff --git a/src/Autofac/Core/Resolving/ResolveOperationBase.cs b/src/Autofac/Core/Resolving/ResolveOperationBase.cs index 0bdc37309..5350b88a9 100644 --- a/src/Autofac/Core/Resolving/ResolveOperationBase.cs +++ b/src/Autofac/Core/Resolving/ResolveOperationBase.cs @@ -48,10 +48,13 @@ public abstract class ResolveOperationBase : IResolveOperation /// /// The most nested scope in which to begin the operation. The operation /// can move upward to less nested scopes as components with wider sharing scopes are activated. - protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope) + /// + /// The to which trace events should be written. + /// + protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, DiagnosticSource diagnosticSource) { CurrentScope = mostNestedLifetimeScope ?? throw new ArgumentNullException(nameof(mostNestedLifetimeScope)); - DiagnosticSource = mostNestedLifetimeScope.DiagnosticSource; + DiagnosticSource = diagnosticSource ?? throw new ArgumentNullException(nameof(diagnosticSource)); } /// @@ -179,7 +182,7 @@ public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, R if (_ended) throw new ObjectDisposedException(ResolveOperationResources.TemporaryContextDisposed, innerException: null); // Create a new request context. - var requestContext = new ResolveRequestContext(this, request, currentOperationScope); + var requestContext = new ResolveRequestContext(this, request, currentOperationScope, DiagnosticSource); // Raise our request-beginning event. var handler = ResolveRequestBeginning; diff --git a/src/Autofac/IContainer.cs b/src/Autofac/IContainer.cs index 7b56a0ab9..31b0342dc 100644 --- a/src/Autofac/IContainer.cs +++ b/src/Autofac/IContainer.cs @@ -23,6 +23,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. +using System.Diagnostics; + namespace Autofac { /// @@ -50,5 +52,10 @@ namespace Autofac /// public interface IContainer : ILifetimeScope { + /// + /// Gets the to which + /// trace events should be written. + /// + DiagnosticListener DiagnosticSource { get; } } } diff --git a/src/Autofac/ILifetimeScope.cs b/src/Autofac/ILifetimeScope.cs index 9181ffddc..a1d5d6607 100644 --- a/src/Autofac/ILifetimeScope.cs +++ b/src/Autofac/ILifetimeScope.cs @@ -24,7 +24,6 @@ // OTHER DEALINGS IN THE SOFTWARE. using System; -using System.Diagnostics; using Autofac.Builder; using Autofac.Core; using Autofac.Core.Lifetime; @@ -118,12 +117,6 @@ public interface ILifetimeScope : IComponentContext, IDisposable, IAsyncDisposab /// A new lifetime scope. ILifetimeScope BeginLifetimeScope(object tag, Action configurationAction); - /// - /// Gets the to which - /// trace events should be written. - /// - DiagnosticListener DiagnosticSource { get; } - /// /// Gets the disposer associated with this . /// Component instances can be associated with it manually if required. diff --git a/test/Autofac.Test/ActivatorPipelineExtensions.cs b/test/Autofac.Test/ActivatorPipelineExtensions.cs index 6c4570a18..9187ad7dd 100644 --- a/test/Autofac.Test/ActivatorPipelineExtensions.cs +++ b/test/Autofac.Test/ActivatorPipelineExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Autofac.Core; +using Autofac.Core.Lifetime; using Autofac.Core.Resolving; using Autofac.Core.Resolving.Pipeline; @@ -41,12 +42,13 @@ public static class ActivatorPipelineExtensions return (scope, parameters) => { // To get the sharing scope from what might be a container, we're going to resolve the lifetime scope. - var lifetimeScope = scope.Resolve() as ISharingLifetimeScope; + var lifetimeScope = scope.Resolve() as LifetimeScope; var request = new ResolveRequestContext( - new ResolveOperation(lifetimeScope), + new ResolveOperation(lifetimeScope, lifetimeScope.DiagnosticSource), new ResolveRequest(new TypedService(typeof(T)), Mocks.GetResolvableImplementation(), parameters), - lifetimeScope); + lifetimeScope, + lifetimeScope.DiagnosticSource); built.Invoke(request); diff --git a/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs b/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs index 9dd5052ee..fa589603e 100644 --- a/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs +++ b/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs @@ -423,9 +423,10 @@ private class MockPipelineRequestContext : ResolveRequestContextBase { public MockPipelineRequestContext() : base( - new ResolveOperation(new MockLifetimeScope()), + new ResolveOperation(new MockLifetimeScope(), new DiagnosticListener("Autofac")), new ResolveRequest(new TypedService(typeof(int)), Mocks.GetResolvableImplementation(), Enumerable.Empty()), - new MockLifetimeScope()) + new MockLifetimeScope(), + new DiagnosticListener("Autofac")) { } } @@ -442,8 +443,6 @@ private class MockLifetimeScope : ISharingLifetimeScope public IComponentRegistry ComponentRegistry => throw new NotImplementedException(); - public DiagnosticListener DiagnosticSource { get; } = new DiagnosticListener("Autofac"); - public event EventHandler ChildLifetimeScopeBeginning { add { } diff --git a/test/Autofac.Test/Core/Resolving/ResolveOperationTests.cs b/test/Autofac.Test/Core/Resolving/ResolveOperationTests.cs index 103906f5a..43760b69b 100644 --- a/test/Autofac.Test/Core/Resolving/ResolveOperationTests.cs +++ b/test/Autofac.Test/Core/Resolving/ResolveOperationTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Autofac.Core; +using Autofac.Core.Lifetime; using Autofac.Core.Resolving; using Xunit; @@ -39,7 +40,7 @@ public void OperationRaisesSuccessTraceEvents() var scope = container.Resolve() as ISharingLifetimeScope; - var resolveOp = new ResolveOperation(scope); + var resolveOp = new ResolveOperation(scope, container.DiagnosticSource); var raisedEvents = new List(); @@ -89,7 +90,7 @@ public void OperationRaisesFailureTraceEvents() var scope = container.Resolve() as ISharingLifetimeScope; - var resolveOp = new ResolveOperation(scope); + var resolveOp = new ResolveOperation(scope, container.DiagnosticSource); var raisedEvents = new List(); diff --git a/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs b/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs index 906665f71..3754a1323 100644 --- a/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs +++ b/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs @@ -246,7 +246,7 @@ private static ResolveOperation MockResolveOperation() { var container = new ContainerBuilder().Build(); var scope = container.BeginLifetimeScope() as ISharingLifetimeScope; - return new ResolveOperation(scope); + return new ResolveOperation(scope, container.DiagnosticSource); } private static ResolveRequest MockResolveRequest() @@ -261,7 +261,7 @@ private static ResolveRequestContext MockResolveRequestContext() { var operation = MockResolveOperation(); var request = MockResolveRequest(); - return new ResolveRequestContext(operation, request, operation.CurrentScope); + return new ResolveRequestContext(operation, request, operation.CurrentScope, operation.DiagnosticSource); } private class MockSubscriber : DiagnosticTracerBase From 10d030682a86ce03b399662329411f0e83758a44 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 20 Jul 2020 12:11:38 -0700 Subject: [PATCH 41/45] Docs about custom event handling. --- .../Diagnostics/DiagnosticTracerBase.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Autofac/Diagnostics/DiagnosticTracerBase.cs b/src/Autofac/Diagnostics/DiagnosticTracerBase.cs index 810428b6b..0456218e6 100644 --- a/src/Autofac/Diagnostics/DiagnosticTracerBase.cs +++ b/src/Autofac/Diagnostics/DiagnosticTracerBase.cs @@ -26,6 +26,30 @@ namespace Autofac.Diagnostics /// observer, it's not a general purpose mechanism - it's very much tailored /// to Autofac. /// + /// + /// Should you want to start emitting and subscribing to your own events in the + /// Autofac pipeline (e.g., a custom middleware that emits your own custom events) + /// you have some options: + /// + /// + /// First, you could implement your own that listens + /// for your custom events. Since you can subscribe any number of + /// to a given , having one + /// that derives from this class and a separate one of your own making is acceptable. + /// + /// + /// Second, you could create your own that does not + /// derive from this class but still listens for Autofac events as well as any + /// custom events you emit. Any diagnostics observer can listen for these events as + /// long as it's subscribed to the ; + /// it doesn't have to be one of this specific class type. + /// + /// + /// Finally, if you want to use this as a base but to listen for your custom events, + /// you can derive from this and override the + /// method. Handle your custom events, or if it's not one of your custom events, call + /// the base to handle the Autofac events. + /// /// /// public abstract class DiagnosticTracerBase : IObserver> @@ -369,6 +393,13 @@ protected virtual void OnRequestSuccess(RequestDiagnosticData data) /// /// The diagnostic data associated with the event. /// + /// + /// + /// If you are interested in handling custom events, check out the top-level + /// documentation which discusses some + /// options. + /// + /// protected virtual void Write(string diagnosticName, object data) { if (data == null || !IsEnabled(diagnosticName)) From aaeb9a0b4d85dc5fb9abfd9ccccb2a0c75259837 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 20 Jul 2020 12:41:55 -0700 Subject: [PATCH 42/45] Diagnostic operation complete event args generic to handle different tracer types. --- .../Diagnostics/DefaultDiagnosticTracer.cs | 8 ++++---- .../Diagnostics/OperationDiagnosticTracerBase.cs | 15 +++++++++------ .../Diagnostics/OperationTraceCompletedArgs.cs | 13 ++++++++----- .../OperationDiagnosticTracerBaseTests.cs | 2 +- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/Autofac/Diagnostics/DefaultDiagnosticTracer.cs b/src/Autofac/Diagnostics/DefaultDiagnosticTracer.cs index f68f78f6c..d4b884a92 100644 --- a/src/Autofac/Diagnostics/DefaultDiagnosticTracer.cs +++ b/src/Autofac/Diagnostics/DefaultDiagnosticTracer.cs @@ -13,7 +13,7 @@ namespace Autofac.Diagnostics /// /// Provides a default resolve pipeline tracer that builds a multi-line /// string describing the end-to-end operation flow. Attach to the - /// + /// /// event to receive notifications when new trace content is available. /// /// @@ -23,7 +23,7 @@ namespace Autofac.Diagnostics /// logical activity can be captured. /// /// - public class DefaultDiagnosticTracer : OperationDiagnosticTracerBase + public class DefaultDiagnosticTracer : OperationDiagnosticTracerBase { private const string RequestExceptionTraced = "__RequestException"; @@ -198,7 +198,7 @@ protected override void OnOperationFailure(OperationFailureDiagnosticData data) builder.AppendLine(TracerMessages.ExitBrace); builder.AppendException(TracerMessages.OperationFailed, data.OperationException); - OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); + OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); } finally { @@ -223,7 +223,7 @@ protected override void OnOperationSuccess(OperationSuccessDiagnosticData data) builder.AppendLine(TracerMessages.ExitBrace); builder.AppendFormattedLine(TracerMessages.OperationSucceeded, data.ResolvedInstance); - OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); + OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); } finally { diff --git a/src/Autofac/Diagnostics/OperationDiagnosticTracerBase.cs b/src/Autofac/Diagnostics/OperationDiagnosticTracerBase.cs index 8b41045b6..587fa8d79 100644 --- a/src/Autofac/Diagnostics/OperationDiagnosticTracerBase.cs +++ b/src/Autofac/Diagnostics/OperationDiagnosticTracerBase.cs @@ -9,17 +9,20 @@ namespace Autofac.Diagnostics /// /// Base class for tracers that require all operations for logical operation tracing. /// + /// + /// The type of content generated by the trace at the end of the operation. + /// /// /// /// Derived classes will be subscribed to all Autofac diagnostic events - /// and will raise an + /// and will raise an /// event when a logical operation has finished and trace data is available. /// /// - public abstract class OperationDiagnosticTracerBase : DiagnosticTracerBase + public abstract class OperationDiagnosticTracerBase : DiagnosticTracerBase { /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// and enables all subscriptions. /// protected OperationDiagnosticTracerBase() @@ -28,7 +31,7 @@ protected OperationDiagnosticTracerBase() } /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// and enables a specified set of subscriptions. /// /// @@ -62,7 +65,7 @@ public override void Disable(string diagnosticName) /// /// Event raised when a resolve operation completes and trace data is available. /// - public event EventHandler? OperationCompleted; + public event EventHandler>? OperationCompleted; /// /// Gets the number of operations in progress being traced. @@ -79,7 +82,7 @@ public override void Disable(string diagnosticName) /// /// The arguments to provide in the raised event. /// - protected virtual void OnOperationCompleted(OperationTraceCompletedArgs args) + protected virtual void OnOperationCompleted(OperationTraceCompletedArgs args) { OperationCompleted?.Invoke(this, args); } diff --git a/src/Autofac/Diagnostics/OperationTraceCompletedArgs.cs b/src/Autofac/Diagnostics/OperationTraceCompletedArgs.cs index 6f7530a85..05ed423ec 100644 --- a/src/Autofac/Diagnostics/OperationTraceCompletedArgs.cs +++ b/src/Autofac/Diagnostics/OperationTraceCompletedArgs.cs @@ -6,16 +6,19 @@ namespace Autofac.Diagnostics { /// - /// Event data for the event. + /// Event data for the event. /// - public sealed class OperationTraceCompletedArgs + /// + /// The type of content generated by the trace at the end of the operation. + /// + public sealed class OperationTraceCompletedArgs { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The operation for which a trace has completed. /// The content of the trace. - public OperationTraceCompletedArgs(ResolveOperationBase operation, string traceContent) + public OperationTraceCompletedArgs(ResolveOperationBase operation, TContent traceContent) { Operation = operation; TraceContent = traceContent; @@ -29,6 +32,6 @@ public OperationTraceCompletedArgs(ResolveOperationBase operation, string traceC /// /// Gets the content of the trace. /// - public string TraceContent { get; } + public TContent TraceContent { get; } } } diff --git a/test/Autofac.Test/Diagnostics/OperationDiagnosticTracerBaseTests.cs b/test/Autofac.Test/Diagnostics/OperationDiagnosticTracerBaseTests.cs index d597e288d..f55f65ce3 100644 --- a/test/Autofac.Test/Diagnostics/OperationDiagnosticTracerBaseTests.cs +++ b/test/Autofac.Test/Diagnostics/OperationDiagnosticTracerBaseTests.cs @@ -37,7 +37,7 @@ public void Ctor_SpecifySubscriptions() Assert.False(tracer.IsEnabled(DiagnosticEventKeys.MiddlewareFailure)); } - private class TestTracer : OperationDiagnosticTracerBase + private class TestTracer : OperationDiagnosticTracerBase { public TestTracer() : base() From 876f157b5f29ca9d873782cd59c5d259d90d582a Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 20 Jul 2020 12:51:12 -0700 Subject: [PATCH 43/45] Removed redundant checks for diagnostics being enabled. --- .../Pipeline/ResolvePipelineBuilder.cs | 35 +++----- .../Core/Resolving/ResolveOperationBase.cs | 46 ++-------- .../Diagnostics/DiagnosticSourceExtensions.cs | 30 ------- .../DiagnosticSourceExtensionsTests.cs | 87 ------------------- 4 files changed, 21 insertions(+), 177 deletions(-) diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs index d858330be..946ea9d0a 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs @@ -270,34 +270,25 @@ Action Chain(Action next, return (ctxt) => { - // Optimise the path depending on whether diagnostics are enabled. - if (ctxt.DiagnosticSource.MiddlewareDiagnosticsEnabled()) + ctxt.DiagnosticSource.MiddlewareStart(ctxt, stage); + var succeeded = false; + try { - ctxt.DiagnosticSource.MiddlewareStart(ctxt, stage); - var succeeded = false; - try + ctxt.PhaseReached = stagePhase; + stage.Execute(ctxt, next); + succeeded = true; + } + finally + { + if (succeeded) { - ctxt.PhaseReached = stagePhase; - stage.Execute(ctxt, next); - succeeded = true; + ctxt.DiagnosticSource.MiddlewareSuccess(ctxt, stage); } - finally + else { - if (succeeded) - { - ctxt.DiagnosticSource.MiddlewareSuccess(ctxt, stage); - } - else - { - ctxt.DiagnosticSource.MiddlewareFailure(ctxt, stage); - } + ctxt.DiagnosticSource.MiddlewareFailure(ctxt, stage); } } - else - { - ctxt.PhaseReached = stagePhase; - stage.Execute(ctxt, next); - } }; } diff --git a/src/Autofac/Core/Resolving/ResolveOperationBase.cs b/src/Autofac/Core/Resolving/ResolveOperationBase.cs index 5350b88a9..ecd626579 100644 --- a/src/Autofac/Core/Resolving/ResolveOperationBase.cs +++ b/src/Autofac/Core/Resolving/ResolveOperationBase.cs @@ -124,40 +124,24 @@ protected object ExecuteOperation(ResolveRequest request) try { InitiatingRequest = request; - - if (DiagnosticSource.OperationDiagnosticsEnabled()) - { - DiagnosticSource.OperationStart(this, request); - } - + DiagnosticSource.OperationStart(this, request); result = GetOrCreateInstance(CurrentScope, request); } catch (ObjectDisposedException disposeException) { - if (DiagnosticSource.OperationDiagnosticsEnabled()) - { - DiagnosticSource.OperationFailure(this, disposeException); - } - + DiagnosticSource.OperationFailure(this, disposeException); throw; } catch (DependencyResolutionException dependencyResolutionException) { - if (DiagnosticSource.OperationDiagnosticsEnabled()) - { - DiagnosticSource.OperationFailure(this, dependencyResolutionException); - } - + DiagnosticSource.OperationFailure(this, dependencyResolutionException); End(dependencyResolutionException); throw; } catch (Exception exception) { End(exception); - if (DiagnosticSource.OperationDiagnosticsEnabled()) - { - DiagnosticSource.OperationFailure(this, exception); - } + DiagnosticSource.OperationFailure(this, exception); throw new DependencyResolutionException(ResolveOperationResources.ExceptionDuringResolve, exception); } @@ -167,11 +151,7 @@ protected object ExecuteOperation(ResolveRequest request) } End(); - - if (DiagnosticSource.OperationDiagnosticsEnabled()) - { - DiagnosticSource.OperationSuccess(this, result); - } + DiagnosticSource.OperationSuccess(this, result); return result; } @@ -199,10 +179,7 @@ public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, R try { - if (DiagnosticSource.RequestDiagnosticsEnabled()) - { - DiagnosticSource.RequestStart(this, requestContext); - } + DiagnosticSource.RequestStart(this, requestContext); // Invoke the resolve pipeline. request.ResolvePipeline.Invoke(requestContext); @@ -214,18 +191,11 @@ public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, R } _successfulRequests.Add(requestContext); - if (DiagnosticSource.RequestDiagnosticsEnabled()) - { - DiagnosticSource.RequestSuccess(this, requestContext); - } + DiagnosticSource.RequestSuccess(this, requestContext); } catch (Exception ex) { - if (DiagnosticSource.RequestDiagnosticsEnabled()) - { - DiagnosticSource.RequestFailure(this, requestContext, ex); - } - + DiagnosticSource.RequestFailure(this, requestContext, ex); throw; } finally diff --git a/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs b/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs index ea1c48f7c..ebef8b6e3 100644 --- a/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs +++ b/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs @@ -14,16 +14,6 @@ namespace Autofac.Diagnostics /// internal static class DiagnosticSourceExtensions { - /// - /// Determines if diagnostics for middleware events is enabled. - /// - /// The diagnostic source to check for diagnostic settings. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool MiddlewareDiagnosticsEnabled(this DiagnosticSource diagnosticSource) - { - return diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareStart) || diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareSuccess) || diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareFailure); - } - /// /// Writes a diagnostic event indicating an individual middleware item is about to execute (just before the method executes). /// @@ -66,16 +56,6 @@ public static void MiddlewareSuccess(this DiagnosticSource diagnosticSource, Res } } - /// - /// Determines if diagnostics for operation events is enabled. - /// - /// The diagnostic source to check for diagnostic settings. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool OperationDiagnosticsEnabled(this DiagnosticSource diagnosticSource) - { - return diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationStart) || diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationSuccess) || diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationFailure); - } - /// /// Writes a diagnostic event indicating a resolve operation has started. /// @@ -118,16 +98,6 @@ public static void OperationSuccess(this DiagnosticSource diagnosticSource, Reso } } - /// - /// Determines if diagnostics for resolve requests is enabled. - /// - /// The diagnostic source to check for diagnostic settings. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool RequestDiagnosticsEnabled(this DiagnosticSource diagnosticSource) - { - return diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestStart) || diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestSuccess) || diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestFailure); - } - /// /// Writes a diagnostic event indicating a resolve request has started inside an operation. /// diff --git a/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs b/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs index 3754a1323..7f0cc543f 100644 --- a/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs +++ b/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs @@ -16,93 +16,6 @@ namespace Autofac.Test.Diagnostics { public class DiagnosticSourceExtensionsTests { - [InlineData(DiagnosticEventKeys.MiddlewareFailure)] - [InlineData(DiagnosticEventKeys.MiddlewareStart)] - [InlineData(DiagnosticEventKeys.MiddlewareSuccess)] - [Theory] - public void MiddlewareDiagnosticsEnabled_IsEnabled(string key) - { - var source = new DiagnosticListener("Autofac"); - var subscriber = new MockSubscriber(); - subscriber.Enable(key); - source.Subscribe(subscriber, subscriber.IsEnabled); - Assert.True(source.MiddlewareDiagnosticsEnabled()); - } - - [InlineData(DiagnosticEventKeys.OperationFailure)] - [InlineData(DiagnosticEventKeys.OperationStart)] - [InlineData(DiagnosticEventKeys.OperationSuccess)] - [InlineData(DiagnosticEventKeys.RequestFailure)] - [InlineData(DiagnosticEventKeys.RequestStart)] - [InlineData(DiagnosticEventKeys.RequestSuccess)] - [Theory] - public void MiddlewareDiagnosticsEnabled_IsNotEnabled(string key) - { - var source = new DiagnosticListener("Autofac"); - var subscriber = new MockSubscriber(); - subscriber.Enable(key); - source.Subscribe(subscriber, subscriber.IsEnabled); - Assert.False(source.MiddlewareDiagnosticsEnabled()); - } - - [InlineData(DiagnosticEventKeys.OperationFailure)] - [InlineData(DiagnosticEventKeys.OperationStart)] - [InlineData(DiagnosticEventKeys.OperationSuccess)] - [Theory] - public void OperationDiagnosticsEnabled_IsEnabled(string key) - { - var source = new DiagnosticListener("Autofac"); - var subscriber = new MockSubscriber(); - subscriber.Enable(key); - source.Subscribe(subscriber, subscriber.IsEnabled); - Assert.True(source.OperationDiagnosticsEnabled()); - } - - [InlineData(DiagnosticEventKeys.MiddlewareFailure)] - [InlineData(DiagnosticEventKeys.MiddlewareStart)] - [InlineData(DiagnosticEventKeys.MiddlewareSuccess)] - [InlineData(DiagnosticEventKeys.RequestFailure)] - [InlineData(DiagnosticEventKeys.RequestStart)] - [InlineData(DiagnosticEventKeys.RequestSuccess)] - [Theory] - public void OperationDiagnosticsEnabled_IsNotEnabled(string key) - { - var source = new DiagnosticListener("Autofac"); - var subscriber = new MockSubscriber(); - subscriber.Enable(key); - source.Subscribe(subscriber, subscriber.IsEnabled); - Assert.False(source.OperationDiagnosticsEnabled()); - } - - [InlineData(DiagnosticEventKeys.RequestFailure)] - [InlineData(DiagnosticEventKeys.RequestStart)] - [InlineData(DiagnosticEventKeys.RequestSuccess)] - [Theory] - public void RequestDiagnosticsEnabled_IsEnabled(string key) - { - var source = new DiagnosticListener("Autofac"); - var subscriber = new MockSubscriber(); - subscriber.Enable(key); - source.Subscribe(subscriber, subscriber.IsEnabled); - Assert.True(source.RequestDiagnosticsEnabled()); - } - - [InlineData(DiagnosticEventKeys.MiddlewareFailure)] - [InlineData(DiagnosticEventKeys.MiddlewareStart)] - [InlineData(DiagnosticEventKeys.MiddlewareSuccess)] - [InlineData(DiagnosticEventKeys.OperationFailure)] - [InlineData(DiagnosticEventKeys.OperationStart)] - [InlineData(DiagnosticEventKeys.OperationSuccess)] - [Theory] - public void RequestDiagnosticsEnabled_IsNotEnabled(string key) - { - var source = new DiagnosticListener("Autofac"); - var subscriber = new MockSubscriber(); - subscriber.Enable(key); - source.Subscribe(subscriber, subscriber.IsEnabled); - Assert.False(source.RequestDiagnosticsEnabled()); - } - [Fact] public void MiddlewareFailure_CorrectEventContent() { From 6795a917b0ad1eb235ef6beae593a1b5ce4594de Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Mon, 20 Jul 2020 13:15:37 -0700 Subject: [PATCH 44/45] Checking basic IsEnabled() on DiagnosticSource before checking on the specific subscription. --- .../Pipeline/ResolveRequestContext.cs | 4 +- .../Pipeline/ResolveRequestContextBase.cs | 8 ++-- .../Core/Resolving/ResolveOperation.cs | 6 +-- .../Core/Resolving/ResolveOperationBase.cs | 8 ++-- .../Diagnostics/DiagnosticSourceExtensions.cs | 37 +++++++++---------- 5 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs index ea503dcfd..a9863d1f4 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs @@ -39,13 +39,13 @@ internal sealed class ResolveRequestContext : ResolveRequestContextBase /// The initiating resolve request. /// The lifetime scope. /// - /// The to which trace events should be written. + /// The to which trace events should be written. /// internal ResolveRequestContext( ResolveOperationBase owningOperation, ResolveRequest request, ISharingLifetimeScope scope, - DiagnosticSource diagnosticSource) + DiagnosticListener diagnosticSource) : base(owningOperation, request, scope, diagnosticSource) { } diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs index 3baa4ca27..e230633f3 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs @@ -49,13 +49,13 @@ public abstract class ResolveRequestContextBase : IComponentContext /// The initiating resolve request. /// The lifetime scope. /// - /// The to which trace events should be written. + /// The to which trace events should be written. /// internal ResolveRequestContextBase( ResolveOperationBase owningOperation, ResolveRequest request, ISharingLifetimeScope scope, - DiagnosticSource diagnosticSource) + DiagnosticListener diagnosticSource) { Operation = owningOperation; ActivationScope = scope; @@ -111,9 +111,9 @@ public abstract class ResolveRequestContextBase : IComponentContext public bool NewInstanceActivated => Instance is object && PhaseReached == PipelinePhase.Activation; /// - /// Gets the to which trace events should be written. + /// Gets the to which trace events should be written. /// - public DiagnosticSource DiagnosticSource { get; } + public DiagnosticListener DiagnosticSource { get; } /// /// Gets the current resolve parameters. These can be changed using the method. diff --git a/src/Autofac/Core/Resolving/ResolveOperation.cs b/src/Autofac/Core/Resolving/ResolveOperation.cs index 60688fa6b..ad8263f25 100644 --- a/src/Autofac/Core/Resolving/ResolveOperation.cs +++ b/src/Autofac/Core/Resolving/ResolveOperation.cs @@ -24,7 +24,6 @@ // OTHER DEALINGS IN THE SOFTWARE. using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; namespace Autofac.Core.Resolving { @@ -40,9 +39,9 @@ internal sealed class ResolveOperation : ResolveOperationBase /// The most nested scope in which to begin the operation. The operation /// can move upward to less nested scopes as components with wider sharing scopes are activated. /// - /// The to which trace events should be written. + /// The to which trace events should be written. /// - public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, DiagnosticSource diagnosticSource) + public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, DiagnosticListener diagnosticSource) : base(mostNestedLifetimeScope, diagnosticSource) { } @@ -51,7 +50,6 @@ public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, Diagnosti /// Execute the complete resolve operation. /// /// The resolution context. - [SuppressMessage("CA1031", "CA1031", Justification = "General exception gets rethrown in a DependencyResolutionException.")] public object Execute(ResolveRequest request) { return ExecuteOperation(request); diff --git a/src/Autofac/Core/Resolving/ResolveOperationBase.cs b/src/Autofac/Core/Resolving/ResolveOperationBase.cs index ecd626579..754b97978 100644 --- a/src/Autofac/Core/Resolving/ResolveOperationBase.cs +++ b/src/Autofac/Core/Resolving/ResolveOperationBase.cs @@ -49,9 +49,9 @@ public abstract class ResolveOperationBase : IResolveOperation /// The most nested scope in which to begin the operation. The operation /// can move upward to less nested scopes as components with wider sharing scopes are activated. /// - /// The to which trace events should be written. + /// The to which trace events should be written. /// - protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, DiagnosticSource diagnosticSource) + protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, DiagnosticListener diagnosticSource) { CurrentScope = mostNestedLifetimeScope ?? throw new ArgumentNullException(nameof(mostNestedLifetimeScope)); DiagnosticSource = diagnosticSource ?? throw new ArgumentNullException(nameof(diagnosticSource)); @@ -73,9 +73,9 @@ protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, Di public IEnumerable InProgressRequests => RequestStack; /// - /// Gets the for the operation. + /// Gets the for the operation. /// - public DiagnosticSource DiagnosticSource { get; } + public DiagnosticListener DiagnosticSource { get; } /// /// Gets or sets the current request depth. diff --git a/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs b/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs index ebef8b6e3..68ea1e40a 100644 --- a/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs +++ b/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics; -using System.Runtime.CompilerServices; using Autofac.Core.Resolving; using Autofac.Core.Resolving.Pipeline; @@ -20,9 +19,9 @@ internal static class DiagnosticSourceExtensions /// The diagnostic source to which events will be written. /// The context for the resolve request that is running. /// The middleware that is about to run. - public static void MiddlewareStart(this DiagnosticSource diagnosticSource, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + public static void MiddlewareStart(this DiagnosticListener diagnosticSource, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) { - if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareStart)) + if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareStart)) { diagnosticSource.Write(DiagnosticEventKeys.MiddlewareStart, new MiddlewareDiagnosticData(requestContext, middleware)); } @@ -34,9 +33,9 @@ public static void MiddlewareStart(this DiagnosticSource diagnosticSource, Resol /// The diagnostic source to which events will be written. /// The context for the resolve request that is running. /// The middleware that just ran. - public static void MiddlewareFailure(this DiagnosticSource diagnosticSource, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + public static void MiddlewareFailure(this DiagnosticListener diagnosticSource, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) { - if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareFailure)) + if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareFailure)) { diagnosticSource.Write(DiagnosticEventKeys.MiddlewareFailure, new MiddlewareDiagnosticData(requestContext, middleware)); } @@ -48,9 +47,9 @@ public static void MiddlewareFailure(this DiagnosticSource diagnosticSource, Res /// The diagnostic source to which events will be written. /// The context for the resolve request that is running. /// The middleware that just ran. - public static void MiddlewareSuccess(this DiagnosticSource diagnosticSource, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + public static void MiddlewareSuccess(this DiagnosticListener diagnosticSource, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) { - if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareSuccess)) + if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareSuccess)) { diagnosticSource.Write(DiagnosticEventKeys.MiddlewareSuccess, new MiddlewareDiagnosticData(requestContext, middleware)); } @@ -62,9 +61,9 @@ public static void MiddlewareSuccess(this DiagnosticSource diagnosticSource, Res /// The diagnostic source to which events will be written. /// The pipeline resolve operation that is about to run. /// The request that is responsible for starting this operation. - public static void OperationStart(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequest initiatingRequest) + public static void OperationStart(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, ResolveRequest initiatingRequest) { - if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationStart)) + if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationStart)) { diagnosticSource.Write(DiagnosticEventKeys.OperationStart, new OperationStartDiagnosticData(operation, initiatingRequest)); } @@ -76,9 +75,9 @@ public static void OperationStart(this DiagnosticSource diagnosticSource, Resolv /// The diagnostic source to which events will be written. /// The resolve operation that failed. /// The exception that caused the operation failure. - public static void OperationFailure(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, Exception operationException) + public static void OperationFailure(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, Exception operationException) { - if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationFailure)) + if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationFailure)) { diagnosticSource.Write(DiagnosticEventKeys.OperationFailure, new OperationFailureDiagnosticData(operation, operationException)); } @@ -90,9 +89,9 @@ public static void OperationFailure(this DiagnosticSource diagnosticSource, Reso /// The diagnostic source to which events will be written. /// The resolve operation that succeeded. /// The resolved instance providing the requested service. - public static void OperationSuccess(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, object resolvedInstance) + public static void OperationSuccess(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, object resolvedInstance) { - if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationSuccess)) + if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationSuccess)) { diagnosticSource.Write(DiagnosticEventKeys.OperationSuccess, new OperationSuccessDiagnosticData(operation, resolvedInstance)); } @@ -104,9 +103,9 @@ public static void OperationSuccess(this DiagnosticSource diagnosticSource, Reso /// The diagnostic source to which events will be written. /// The pipeline resolve operation that this request is running within. /// The context for the resolve request that is about to start. - public static void RequestStart(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext) + public static void RequestStart(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext) { - if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestStart)) + if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestStart)) { diagnosticSource.Write(DiagnosticEventKeys.RequestStart, new RequestDiagnosticData(operation, requestContext)); } @@ -119,9 +118,9 @@ public static void RequestStart(this DiagnosticSource diagnosticSource, ResolveO /// The pipeline resolve operation that this request is running within. /// The context for the resolve request that failed. /// The exception that caused the failure. - public static void RequestFailure(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException) + public static void RequestFailure(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException) { - if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestFailure)) + if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestFailure)) { diagnosticSource.Write(DiagnosticEventKeys.RequestFailure, new RequestFailureDiagnosticData(operation, requestContext, requestException)); } @@ -133,9 +132,9 @@ public static void RequestFailure(this DiagnosticSource diagnosticSource, Resolv /// The diagnostic source to which events will be written. /// The pipeline resolve operation that this request is running within. /// The context for the resolve request that failed. - public static void RequestSuccess(this DiagnosticSource diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext) + public static void RequestSuccess(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext) { - if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestSuccess)) + if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestSuccess)) { diagnosticSource.Write(DiagnosticEventKeys.RequestSuccess, new RequestDiagnosticData(operation, requestContext)); } From 5a6b977ecbc6bd84c99ec693fb03758fc0bc562c Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Tue, 21 Jul 2020 07:59:41 -0700 Subject: [PATCH 45/45] Optimizing hot path checks for diagnostics being enabled. --- .../Pipeline/ResolvePipelineBuilder.cs | 37 +++++---- .../Core/Resolving/ResolveOperationBase.cs | 78 ++++++++++++++----- .../Diagnostics/DiagnosticSourceExtensions.cs | 18 ++--- 3 files changed, 93 insertions(+), 40 deletions(-) diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs index 946ea9d0a..b52e5e623 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs @@ -270,25 +270,36 @@ Action Chain(Action next, return (ctxt) => { - ctxt.DiagnosticSource.MiddlewareStart(ctxt, stage); - var succeeded = false; - try + // Same basic flow in if/else, but doing a one-time check for diagnostics + // and choosing the "diagnostics enabled" version vs. the more common + // "no diagnostics enabled" path: hot-path optimization. + if (ctxt.DiagnosticSource.IsEnabled()) { - ctxt.PhaseReached = stagePhase; - stage.Execute(ctxt, next); - succeeded = true; - } - finally - { - if (succeeded) + ctxt.DiagnosticSource.MiddlewareStart(ctxt, stage); + var succeeded = false; + try { - ctxt.DiagnosticSource.MiddlewareSuccess(ctxt, stage); + ctxt.PhaseReached = stagePhase; + stage.Execute(ctxt, next); + succeeded = true; } - else + finally { - ctxt.DiagnosticSource.MiddlewareFailure(ctxt, stage); + if (succeeded) + { + ctxt.DiagnosticSource.MiddlewareSuccess(ctxt, stage); + } + else + { + ctxt.DiagnosticSource.MiddlewareFailure(ctxt, stage); + } } } + else + { + ctxt.PhaseReached = stagePhase; + stage.Execute(ctxt, next); + } }; } diff --git a/src/Autofac/Core/Resolving/ResolveOperationBase.cs b/src/Autofac/Core/Resolving/ResolveOperationBase.cs index 754b97978..11bb946f7 100644 --- a/src/Autofac/Core/Resolving/ResolveOperationBase.cs +++ b/src/Autofac/Core/Resolving/ResolveOperationBase.cs @@ -26,6 +26,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.CompilerServices; using Autofac.Core.Resolving.Middleware; using Autofac.Core.Resolving.Pipeline; using Autofac.Diagnostics; @@ -124,24 +125,39 @@ protected object ExecuteOperation(ResolveRequest request) try { InitiatingRequest = request; - DiagnosticSource.OperationStart(this, request); + if (DiagnosticSource.IsEnabled()) + { + DiagnosticSource.OperationStart(this, request); + } + result = GetOrCreateInstance(CurrentScope, request); } catch (ObjectDisposedException disposeException) { - DiagnosticSource.OperationFailure(this, disposeException); + if (DiagnosticSource.IsEnabled()) + { + DiagnosticSource.OperationFailure(this, disposeException); + } + throw; } catch (DependencyResolutionException dependencyResolutionException) { - DiagnosticSource.OperationFailure(this, dependencyResolutionException); + if (DiagnosticSource.IsEnabled()) + { + DiagnosticSource.OperationFailure(this, dependencyResolutionException); + } + End(dependencyResolutionException); throw; } catch (Exception exception) { End(exception); - DiagnosticSource.OperationFailure(this, exception); + if (DiagnosticSource.IsEnabled()) + { + DiagnosticSource.OperationFailure(this, exception); + } throw new DependencyResolutionException(ResolveOperationResources.ExceptionDuringResolve, exception); } @@ -151,7 +167,11 @@ protected object ExecuteOperation(ResolveRequest request) } End(); - DiagnosticSource.OperationSuccess(this, result); + + if (DiagnosticSource.IsEnabled()) + { + DiagnosticSource.OperationSuccess(this, result); + } return result; } @@ -179,23 +199,27 @@ public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, R try { - DiagnosticSource.RequestStart(this, requestContext); - - // Invoke the resolve pipeline. - request.ResolvePipeline.Invoke(requestContext); - - if (requestContext.Instance == null) + // Same basic flow in if/else, but doing a one-time check for diagnostics + // and choosing the "diagnostics enabled" version vs. the more common + // "no diagnostics enabled" path: hot-path optimization. + if (DiagnosticSource.IsEnabled()) { - // No exception, but was null; this shouldn't happen. - throw new DependencyResolutionException(ResolveOperationResources.PipelineCompletedWithNoInstance); + DiagnosticSource.RequestStart(this, requestContext); + InvokePipeline(request, requestContext); + DiagnosticSource.RequestSuccess(this, requestContext); + } + else + { + InvokePipeline(request, requestContext); } - - _successfulRequests.Add(requestContext); - DiagnosticSource.RequestSuccess(this, requestContext); } catch (Exception ex) { - DiagnosticSource.RequestFailure(this, requestContext, ex); + if (DiagnosticSource.IsEnabled()) + { + DiagnosticSource.RequestFailure(this, requestContext, ex); + } + throw; } finally @@ -212,7 +236,25 @@ public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, R RequestDepth--; } - return requestContext.Instance; + // InvokePipeline throws if the instance is null but + // analyzers don't pick that up and get mad. + return requestContext.Instance!; + } + + /// + /// Basic pipeline invocation steps used when retrieving an instance. Isolated + /// to enable it to be optionally surrounded with diagnostics. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void InvokePipeline(ResolveRequest request, ResolveRequestContext requestContext) + { + request.ResolvePipeline.Invoke(requestContext); + if (requestContext.Instance == null) + { + throw new DependencyResolutionException(ResolveOperationResources.PipelineCompletedWithNoInstance); + } + + _successfulRequests.Add(requestContext); } private void CompleteRequests() diff --git a/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs b/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs index 68ea1e40a..bb54e51ca 100644 --- a/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs +++ b/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs @@ -21,7 +21,7 @@ internal static class DiagnosticSourceExtensions /// The middleware that is about to run. public static void MiddlewareStart(this DiagnosticListener diagnosticSource, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) { - if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareStart)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareStart)) { diagnosticSource.Write(DiagnosticEventKeys.MiddlewareStart, new MiddlewareDiagnosticData(requestContext, middleware)); } @@ -35,7 +35,7 @@ public static void MiddlewareStart(this DiagnosticListener diagnosticSource, Res /// The middleware that just ran. public static void MiddlewareFailure(this DiagnosticListener diagnosticSource, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) { - if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareFailure)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareFailure)) { diagnosticSource.Write(DiagnosticEventKeys.MiddlewareFailure, new MiddlewareDiagnosticData(requestContext, middleware)); } @@ -49,7 +49,7 @@ public static void MiddlewareFailure(this DiagnosticListener diagnosticSource, R /// The middleware that just ran. public static void MiddlewareSuccess(this DiagnosticListener diagnosticSource, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) { - if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareSuccess)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareSuccess)) { diagnosticSource.Write(DiagnosticEventKeys.MiddlewareSuccess, new MiddlewareDiagnosticData(requestContext, middleware)); } @@ -63,7 +63,7 @@ public static void MiddlewareSuccess(this DiagnosticListener diagnosticSource, R /// The request that is responsible for starting this operation. public static void OperationStart(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, ResolveRequest initiatingRequest) { - if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationStart)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationStart)) { diagnosticSource.Write(DiagnosticEventKeys.OperationStart, new OperationStartDiagnosticData(operation, initiatingRequest)); } @@ -77,7 +77,7 @@ public static void OperationStart(this DiagnosticListener diagnosticSource, Reso /// The exception that caused the operation failure. public static void OperationFailure(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, Exception operationException) { - if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationFailure)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationFailure)) { diagnosticSource.Write(DiagnosticEventKeys.OperationFailure, new OperationFailureDiagnosticData(operation, operationException)); } @@ -91,7 +91,7 @@ public static void OperationFailure(this DiagnosticListener diagnosticSource, Re /// The resolved instance providing the requested service. public static void OperationSuccess(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, object resolvedInstance) { - if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationSuccess)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationSuccess)) { diagnosticSource.Write(DiagnosticEventKeys.OperationSuccess, new OperationSuccessDiagnosticData(operation, resolvedInstance)); } @@ -105,7 +105,7 @@ public static void OperationSuccess(this DiagnosticListener diagnosticSource, Re /// The context for the resolve request that is about to start. public static void RequestStart(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext) { - if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestStart)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestStart)) { diagnosticSource.Write(DiagnosticEventKeys.RequestStart, new RequestDiagnosticData(operation, requestContext)); } @@ -120,7 +120,7 @@ public static void RequestStart(this DiagnosticListener diagnosticSource, Resolv /// The exception that caused the failure. public static void RequestFailure(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException) { - if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestFailure)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestFailure)) { diagnosticSource.Write(DiagnosticEventKeys.RequestFailure, new RequestFailureDiagnosticData(operation, requestContext, requestException)); } @@ -134,7 +134,7 @@ public static void RequestFailure(this DiagnosticListener diagnosticSource, Reso /// The context for the resolve request that failed. public static void RequestSuccess(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext) { - if (diagnosticSource.IsEnabled() && diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestSuccess)) + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestSuccess)) { diagnosticSource.Write(DiagnosticEventKeys.RequestSuccess, new RequestDiagnosticData(operation, requestContext)); }