diff --git a/.editorconfig b/.editorconfig index 3a242268e..e08a1daf4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,22 +10,143 @@ 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 + +; 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_when_type_is_apparent = false:none +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}] 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. 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'" diff --git a/src/Autofac/Autofac.csproj b/src/Autofac/Autofac.csproj index ec63ae13b..560d23bec 100644 --- a/src/Autofac/Autofac.csproj +++ b/src/Autofac/Autofac.csproj @@ -57,6 +57,7 @@ All + @@ -124,7 +125,7 @@ True ContainerResources.resx - + True True TracerMessages.resx @@ -350,7 +351,7 @@ ResXFileCodeGenerator ContainerResources.Designer.cs - + ResXFileCodeGenerator TracerMessages.Designer.cs 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/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/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/ContainerExtensions.cs b/src/Autofac/ContainerExtensions.cs new file mode 100644 index 000000000..4f64155e3 --- /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.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/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 9ceb1b9b4..4b91496be 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 @@ -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. @@ -100,10 +100,7 @@ public ILifetimeScope BeginLifetimeScope(object tag, Action co } /// - public void AttachTrace(IResolvePipelineTracer tracer) - { - _rootLifetimeScope.AttachTrace(tracer); - } + public DiagnosticListener DiagnosticSource => _rootLifetimeScope.DiagnosticSource; /// /// Gets the disposer associated with this container. Instances can be associated 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); - } -} 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); - } -} 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/Lifetime/LifetimeScope.cs b/src/Autofac/Core/Lifetime/LifetimeScope.cs index 2b8e3ba90..8fc482724 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 @@ -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; + DiagnosticSource = _parentScope.DiagnosticSource; } /// @@ -101,10 +91,13 @@ 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; + DiagnosticSource = new DiagnosticListener("Autofac"); + Disposer.AddInstanceForDisposal(DiagnosticSource); } /// @@ -137,7 +130,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 +161,11 @@ private void RaiseBeginning(ILifetimeScope scope) handler?.Invoke(this, new LifetimeScopeBeginningEventArgs(scope)); } - /// - public void AttachTrace(IResolvePipelineTracer tracer) - { - _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer)); - } + /// + /// 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. @@ -226,7 +219,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 +296,7 @@ public object ResolveComponent(ResolveRequest request) CheckNotDisposed(); - var operation = new ResolveOperation(this, _tracer); + 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/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/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; diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs index 9daad7996..b52e5e623 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs @@ -28,9 +28,8 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Text; using Autofac.Core.Pipeline; -using Autofac.Core.Resolving.Middleware; +using Autofac.Diagnostics; namespace Autofac.Core.Resolving.Pipeline { @@ -271,10 +270,12 @@ Action Chain(Action next, return (ctxt) => { - // Optimise the path depending on whether a tracer is attached. - if (ctxt.TracingEnabled) + // 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.Tracer!.MiddlewareEntry(ctxt.Operation, ctxt, stage); + ctxt.DiagnosticSource.MiddlewareStart(ctxt, stage); var succeeded = false; try { @@ -284,7 +285,14 @@ Action Chain(Action next, } finally { - ctxt.Tracer.MiddlewareExit(ctxt.Operation, ctxt, stage, succeeded); + if (succeeded) + { + ctxt.DiagnosticSource.MiddlewareSuccess(ctxt, stage); + } + else + { + ctxt.DiagnosticSource.MiddlewareFailure(ctxt, stage); + } } } else diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs index 6fa899fe9..a9863d1f4 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs @@ -23,7 +23,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. -using Autofac.Core.Diagnostics; +using System.Diagnostics; namespace Autofac.Core.Resolving.Pipeline { @@ -38,13 +38,15 @@ internal sealed class ResolveRequestContext : ResolveRequestContextBase /// The owning resolve operation. /// The initiating resolve request. /// The lifetime scope. - /// An optional tracer. + /// + /// The to which trace events should be written. + /// internal ResolveRequestContext( ResolveOperationBase owningOperation, ResolveRequest request, ISharingLifetimeScope scope, - IResolvePipelineTracer? tracer) - : base(owningOperation, request, scope, tracer) + 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 05378ebe5..e230633f3 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs @@ -25,9 +25,10 @@ using System; 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 @@ -47,19 +48,20 @@ public abstract class ResolveRequestContextBase : IComponentContext /// The owning resolve operation. /// The initiating resolve request. /// The lifetime scope. - /// An optional tracer. + /// + /// The to which trace events should be written. + /// internal ResolveRequestContextBase( ResolveOperationBase owningOperation, ResolveRequest request, ISharingLifetimeScope scope, - IResolvePipelineTracer? tracer) + DiagnosticListener diagnosticSource) { Operation = owningOperation; ActivationScope = scope; Parameters = request.Parameters; PhaseReached = PipelinePhase.ResolveRequestStart; - Tracer = tracer; - TracingEnabled = tracer is object; + DiagnosticSource = diagnosticSource; _resolveRequest = request; } @@ -109,14 +111,9 @@ public abstract class ResolveRequestContextBase : IComponentContext public bool NewInstanceActivated => Instance is object && PhaseReached == PipelinePhase.Activation; /// - /// Gets the active for the request. + /// Gets the to which trace events should be written. /// - 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 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 0c2c443e5..ad8263f25 100644 --- a/src/Autofac/Core/Resolving/ResolveOperation.cs +++ b/src/Autofac/Core/Resolving/ResolveOperation.cs @@ -23,9 +23,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. -using System.Diagnostics.CodeAnalysis; -using Autofac.Core.Diagnostics; -using Autofac.Core.Resolving.Pipeline; +using System.Diagnostics; namespace Autofac.Core.Resolving { @@ -40,31 +38,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) - { - } - - /// - /// 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. - 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) + /// + /// The to which trace events should be written. + /// + public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, DiagnosticListener diagnosticSource) + : base(mostNestedLifetimeScope, diagnosticSource) { } @@ -72,7 +50,6 @@ public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, IResolveP /// 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 8a0497526..11bb946f7 100644 --- a/src/Autofac/Core/Resolving/ResolveOperationBase.cs +++ b/src/Autofac/Core/Resolving/ResolveOperationBase.cs @@ -25,21 +25,22 @@ using System; using System.Collections.Generic; -using Autofac.Core.Diagnostics; +using System.Diagnostics; +using System.Runtime.CompilerServices; using Autofac.Core.Resolving.Middleware; using Autofac.Core.Resolving.Pipeline; +using Autofac.Diagnostics; 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; private bool _ended; - private IResolvePipelineTracer? _pipelineTracer; private List _successfulRequests = new List(SuccessListInitialCapacity); private int _nextCompleteSuccessfulRequestStartPos = 0; @@ -48,38 +49,13 @@ 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. - protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope) - : this(mostNestedLifetimeScope, null) + /// + /// The to which trace events should be written. + /// + protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, DiagnosticListener 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 pipeline tracer for the operation. - protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, IResolvePipelineTracer? pipelineTracer) - { - TracingId = this; - IsTopLevelOperation = true; - CurrentScope = mostNestedLifetimeScope; - _pipelineTracer = pipelineTracer; - IsTopLevelOperation = true; - } - - /// - /// 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. - /// A tracing ID for the operation. - protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, IResolvePipelineTracer? pipelineTracer, ITracingIdentifer tracingId) - : this(mostNestedLifetimeScope, pipelineTracer) - { - TracingId = tracingId; - IsTopLevelOperation = false; + CurrentScope = mostNestedLifetimeScope ?? throw new ArgumentNullException(nameof(mostNestedLifetimeScope)); + DiagnosticSource = diagnosticSource ?? throw new ArgumentNullException(nameof(diagnosticSource)); } /// @@ -98,20 +74,15 @@ protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, IR public IEnumerable InProgressRequests => RequestStack; /// - /// Gets the tracing identifier for the operation. + /// Gets the for the operation. /// - public ITracingIdentifer TracingId { get; } + public DiagnosticListener DiagnosticSource { get; } /// /// Gets or sets the current request depth. /// 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. /// @@ -154,27 +125,40 @@ protected object ExecuteOperation(ResolveRequest request) try { InitiatingRequest = request; - - _pipelineTracer?.OperationStart(this, request); + if (DiagnosticSource.IsEnabled()) + { + DiagnosticSource.OperationStart(this, request); + } result = GetOrCreateInstance(CurrentScope, request); } catch (ObjectDisposedException disposeException) { - _pipelineTracer?.OperationFailure(this, disposeException); + if (DiagnosticSource.IsEnabled()) + { + DiagnosticSource.OperationFailure(this, disposeException); + } throw; } catch (DependencyResolutionException dependencyResolutionException) { - _pipelineTracer?.OperationFailure(this, dependencyResolutionException); + if (DiagnosticSource.IsEnabled()) + { + DiagnosticSource.OperationFailure(this, dependencyResolutionException); + } + End(dependencyResolutionException); throw; } catch (Exception exception) { End(exception); - _pipelineTracer?.OperationFailure(this, exception); + if (DiagnosticSource.IsEnabled()) + { + DiagnosticSource.OperationFailure(this, exception); + } + throw new DependencyResolutionException(ResolveOperationResources.ExceptionDuringResolve, exception); } finally @@ -184,7 +168,10 @@ protected object ExecuteOperation(ResolveRequest request) End(); - _pipelineTracer?.OperationSuccess(this, result); + if (DiagnosticSource.IsEnabled()) + { + DiagnosticSource.OperationSuccess(this, result); + } return result; } @@ -195,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, _pipelineTracer); + var requestContext = new ResolveRequestContext(this, request, currentOperationScope, DiagnosticSource); // Raise our request-beginning event. var handler = ResolveRequestBeginning; @@ -212,23 +199,27 @@ public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, R try { - _pipelineTracer?.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); - _pipelineTracer?.RequestSuccess(this, requestContext); } catch (Exception ex) { - _pipelineTracer?.RequestFailure(this, requestContext, ex); + if (DiagnosticSource.IsEnabled()) + { + DiagnosticSource.RequestFailure(this, requestContext, ex); + } + throw; } finally @@ -245,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/Core/Diagnostics/DefaultDiagnosticTracer.cs b/src/Autofac/Diagnostics/DefaultDiagnosticTracer.cs similarity index 52% rename from src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs rename to src/Autofac/Diagnostics/DefaultDiagnosticTracer.cs index 0312db835..d4b884a92 100644 --- a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs +++ b/src/Autofac/Diagnostics/DefaultDiagnosticTracer.cs @@ -1,29 +1,43 @@ -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.Collections.Generic; using System.Globalization; using System.Text; +using Autofac.Core; using Autofac.Core.Resolving; -using Autofac.Core.Resolving.Pipeline; -namespace Autofac.Core.Diagnostics +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. + /// 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 + /// + /// + /// 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 : OperationDiagnosticTracerBase { 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 }; + private static readonly string[] NewLineSplit = new[] { Environment.NewLine }; /// - /// Event raised when a resolve operation completes, and trace data is available. + /// Initializes a new instance of the class. /// - public event EventHandler? OperationCompleted; + public DefaultDiagnosticTracer() + : base() + { + } /// /// Gets the number of operations in progress being traced. @@ -32,12 +46,17 @@ public class DefaultDiagnosticTracer : IResolvePipelineTracer /// An with the number of trace IDs associated /// with in-progress operations being traced by this tracer. /// - public int OperationsInProgress => this._operationBuilders.Count; + public override int OperationsInProgress => _operationBuilders.Count; /// - void IResolvePipelineTracer.OperationStart(ResolveOperationBase operation, ResolveRequest initiatingRequest) + protected override void OnOperationStart(OperationStartDiagnosticData data) { - var builder = _operationBuilders.GetOrAdd(operation.TracingId, k => new IndentingStringBuilder()); + if (data is null) + { + return; + } + + var builder = _operationBuilders.GetOrAdd(data.Operation, k => new IndentingStringBuilder()); builder.AppendFormattedLine(TracerMessages.ResolveOperationStarting); builder.AppendLine(TracerMessages.EntryBrace); @@ -45,19 +64,24 @@ void IResolvePipelineTracer.OperationStart(ResolveOperationBase operation, Resol } /// - void IResolvePipelineTracer.RequestStart(ResolveOperationBase operation, ResolveRequestContextBase requestContext) + protected override void OnRequestStart(RequestDiagnosticData data) { - if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) + if (data is null) + { + return; + } + + if (_operationBuilders.TryGetValue(data.Operation, 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 +90,63 @@ void IResolvePipelineTracer.RequestStart(ResolveOperationBase operation, Resolve } /// - void IResolvePipelineTracer.MiddlewareEntry(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + protected override void OnMiddlewareStart(MiddlewareDiagnosticData data) { - if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) + if (data is null) { - builder.AppendFormattedLine(TracerMessages.EnterMiddleware, middleware.ToString()); + return; + } + + if (_operationBuilders.TryGetValue(data.RequestContext.Operation, out var builder)) + { + builder.AppendFormattedLine(TracerMessages.EnterMiddleware, data.Middleware.ToString()); builder.Indent(); } } /// - void IResolvePipelineTracer.MiddlewareExit(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware, bool succeeded) + protected override void OnMiddlewareFailure(MiddlewareDiagnosticData data) { - if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) + if (data is null) + { + return; + } + + if (_operationBuilders.TryGetValue(data.RequestContext.Operation, 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()); - } + /// + protected override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) + { + if (data is null) + { + return; + } + + if (_operationBuilders.TryGetValue(data.RequestContext.Operation, out var builder)) + { + builder.Outdent(); + builder.AppendFormattedLine(TracerMessages.ExitMiddlewareSuccess, data.Middleware.ToString()); } } /// - void IResolvePipelineTracer.RequestFailure(ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException) + protected override void OnRequestFailure(RequestFailureDiagnosticData data) { - if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) + if (data is null) + { + return; + } + + if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { builder.Outdent(); builder.AppendLine(TracerMessages.ExitBrace); + var requestException = data.RequestException; if (requestException is DependencyResolutionException && requestException.InnerException is object) { @@ -120,60 +167,67 @@ void IResolvePipelineTracer.RequestFailure(ResolveOperationBase operation, Resol } /// - void IResolvePipelineTracer.RequestSuccess(ResolveOperationBase operation, ResolveRequestContextBase requestContext) + protected override void OnRequestSuccess(RequestDiagnosticData data) { - if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) + if (data is null) + { + return; + } + + if (_operationBuilders.TryGetValue(data.Operation, out var builder)) { builder.Outdent(); builder.AppendLine(TracerMessages.ExitBrace); - builder.AppendFormattedLine(TracerMessages.ResolveRequestSucceeded, requestContext.Instance); + builder.AppendFormattedLine(TracerMessages.ResolveRequestSucceeded, data.RequestContext.Instance); } } /// - void IResolvePipelineTracer.OperationFailure(ResolveOperationBase operation, Exception operationException) + protected override void OnOperationFailure(OperationFailureDiagnosticData data) { - if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) + if (data is null) + { + return; + } + + if (_operationBuilders.TryGetValue(data.Operation, 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) - { - OperationCompleted?.Invoke(this, new OperationTraceCompletedArgs(operation, builder.ToString())); - } + OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); } finally { - _operationBuilders.TryRemove(operation.TracingId, out var _); + _operationBuilders.TryRemove(data.Operation, out var _); } } } /// - void IResolvePipelineTracer.OperationSuccess(ResolveOperationBase operation, object resolvedInstance) + protected override void OnOperationSuccess(OperationSuccessDiagnosticData data) { - if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) + if (data is null) + { + return; + } + + if (_operationBuilders.TryGetValue(data.Operation, 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) - { - OperationCompleted?.Invoke(this, new OperationTraceCompletedArgs(operation, builder.ToString())); - } + OnOperationCompleted(new OperationTraceCompletedArgs(data.Operation, builder.ToString())); } finally { - _operationBuilders.TryRemove(operation.TracingId, out var _); + _operationBuilders.TryRemove(data.Operation, out var _); } } } @@ -220,7 +274,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/Diagnostics/DiagnosticEventKeys.cs b/src/Autofac/Diagnostics/DiagnosticEventKeys.cs new file mode 100644 index 000000000..646d061ff --- /dev/null +++ b/src/Autofac/Diagnostics/DiagnosticEventKeys.cs @@ -0,0 +1,62 @@ +// 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; +using System.Runtime.CompilerServices; +using Autofac.Core.Resolving; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Diagnostics +{ + /// + /// Names of the events raised in diagnostics. + /// + internal static class DiagnosticEventKeys + { + /// + /// 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/Diagnostics/DiagnosticSourceExtensions.cs b/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs new file mode 100644 index 000000000..bb54e51ca --- /dev/null +++ b/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs @@ -0,0 +1,143 @@ +// 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; +using Autofac.Core.Resolving; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Diagnostics +{ + /// + /// Extension methods for writing diagnostic messages. + /// + internal static class DiagnosticSourceExtensions + { + /// + /// 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 context for the resolve request that is running. + /// The middleware that is about to run. + public static void MiddlewareStart(this DiagnosticListener diagnosticSource, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + { + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareStart)) + { + diagnosticSource.Write(DiagnosticEventKeys.MiddlewareStart, new MiddlewareDiagnosticData(requestContext, middleware)); + } + } + + /// + /// 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 context for the resolve request that is running. + /// The middleware that just ran. + public static void MiddlewareFailure(this DiagnosticListener diagnosticSource, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + { + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareFailure)) + { + diagnosticSource.Write(DiagnosticEventKeys.MiddlewareFailure, new MiddlewareDiagnosticData(requestContext, middleware)); + } + } + + /// + /// 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 context for the resolve request that is running. + /// The middleware that just ran. + public static void MiddlewareSuccess(this DiagnosticListener diagnosticSource, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + { + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareSuccess)) + { + diagnosticSource.Write(DiagnosticEventKeys.MiddlewareSuccess, new MiddlewareDiagnosticData(requestContext, middleware)); + } + } + + /// + /// 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. + /// The request that is responsible for starting this operation. + public static void OperationStart(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, ResolveRequest initiatingRequest) + { + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationStart)) + { + diagnosticSource.Write(DiagnosticEventKeys.OperationStart, new OperationStartDiagnosticData(operation, initiatingRequest)); + } + } + + /// + /// 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. + public static void OperationFailure(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, Exception operationException) + { + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationFailure)) + { + diagnosticSource.Write(DiagnosticEventKeys.OperationFailure, new OperationFailureDiagnosticData(operation, operationException)); + } + } + + /// + /// 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. + public static void OperationSuccess(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, object resolvedInstance) + { + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationSuccess)) + { + diagnosticSource.Write(DiagnosticEventKeys.OperationSuccess, new OperationSuccessDiagnosticData(operation, resolvedInstance)); + } + } + + /// + /// 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. + public static void RequestStart(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext) + { + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestStart)) + { + diagnosticSource.Write(DiagnosticEventKeys.RequestStart, new RequestDiagnosticData(operation, requestContext)); + } + } + + /// + /// 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. + /// The context for the resolve request that failed. + /// The exception that caused the failure. + public static void RequestFailure(this DiagnosticListener diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException) + { + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestFailure)) + { + diagnosticSource.Write(DiagnosticEventKeys.RequestFailure, new RequestFailureDiagnosticData(operation, requestContext, requestException)); + } + } + + /// + /// 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 DiagnosticListener diagnosticSource, ResolveOperationBase operation, ResolveRequestContextBase requestContext) + { + if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestSuccess)) + { + diagnosticSource.Write(DiagnosticEventKeys.RequestSuccess, new RequestDiagnosticData(operation, requestContext)); + } + } + } +} diff --git a/src/Autofac/Diagnostics/DiagnosticTracerBase.cs b/src/Autofac/Diagnostics/DiagnosticTracerBase.cs new file mode 100644 index 000000000..0456218e6 --- /dev/null +++ b/src/Autofac/Diagnostics/DiagnosticTracerBase.cs @@ -0,0 +1,444 @@ +// 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; + +namespace Autofac.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. + /// + /// + /// 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> + { + /// + /// 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) + { + 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) + { + 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() + { + // 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); + } + + /// + /// 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) + { + 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) + { + 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 bool IsEnabled(string diagnosticName) + { + if (_subscriptions.Count == 0) + { + return false; + } + + return _subscriptions.Contains(diagnosticName); + } + + /// + /// Notifies the observer that the provider has finished sending push-based notifications. + /// + void IObserver>.OnCompleted() + { + // 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) + { + // 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); + } + + /// + /// 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. + /// + /// + protected 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. + /// + /// + protected 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. + /// + /// + protected 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. + /// + /// + protected 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. + /// + /// + protected 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. + /// + /// + protected 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. + /// + /// + protected 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. + /// + /// + protected 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. + /// + /// + protected virtual void OnRequestSuccess(RequestDiagnosticData data) + { + } + + /// + /// 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. + /// + /// + /// + /// 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)) + { + return; + } + + switch (diagnosticName) + { + case DiagnosticEventKeys.MiddlewareStart: + OnMiddlewareStart((MiddlewareDiagnosticData)data); + break; + case DiagnosticEventKeys.MiddlewareFailure: + OnMiddlewareFailure((MiddlewareDiagnosticData)data); + break; + case DiagnosticEventKeys.MiddlewareSuccess: + OnMiddlewareSuccess((MiddlewareDiagnosticData)data); + break; + case DiagnosticEventKeys.OperationFailure: + OnOperationFailure((OperationFailureDiagnosticData)data); + break; + case DiagnosticEventKeys.OperationStart: + OnOperationStart((OperationStartDiagnosticData)data); + break; + case DiagnosticEventKeys.OperationSuccess: + OnOperationSuccess((OperationSuccessDiagnosticData)data); + break; + case DiagnosticEventKeys.RequestFailure: + OnRequestFailure((RequestFailureDiagnosticData)data); + break; + case DiagnosticEventKeys.RequestStart: + OnRequestStart((RequestDiagnosticData)data); + break; + case DiagnosticEventKeys.RequestSuccess: + OnRequestSuccess((RequestDiagnosticData)data); + break; + default: + break; + } + } + } +} diff --git a/src/Autofac/Diagnostics/MiddlewareDiagnosticData.cs b/src/Autofac/Diagnostics/MiddlewareDiagnosticData.cs new file mode 100644 index 000000000..2dd88f811 --- /dev/null +++ b/src/Autofac/Diagnostics/MiddlewareDiagnosticData.cs @@ -0,0 +1,34 @@ +// 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; + +namespace Autofac.Diagnostics +{ + /// + /// Diagnostic data associated with middleware events. + /// + public class MiddlewareDiagnosticData + { + /// + /// Initializes a new instance of the class. + /// + /// The context for the resolve request that is running. + /// The middleware that is running. + public MiddlewareDiagnosticData(ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + { + RequestContext = requestContext; + Middleware = middleware; + } + + /// + /// 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/Diagnostics/OperationDiagnosticTracerBase.cs b/src/Autofac/Diagnostics/OperationDiagnosticTracerBase.cs new file mode 100644 index 000000000..587fa8d79 --- /dev/null +++ b/src/Autofac/Diagnostics/OperationDiagnosticTracerBase.cs @@ -0,0 +1,90 @@ +// 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; + +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 + /// 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) + { + 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. + /// + 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/Diagnostics/OperationFailureDiagnosticData.cs b/src/Autofac/Diagnostics/OperationFailureDiagnosticData.cs new file mode 100644 index 000000000..4cc32ceb0 --- /dev/null +++ b/src/Autofac/Diagnostics/OperationFailureDiagnosticData.cs @@ -0,0 +1,35 @@ +// 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; + +namespace Autofac.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/Diagnostics/OperationStartDiagnosticData.cs b/src/Autofac/Diagnostics/OperationStartDiagnosticData.cs new file mode 100644 index 000000000..c69532c9a --- /dev/null +++ b/src/Autofac/Diagnostics/OperationStartDiagnosticData.cs @@ -0,0 +1,34 @@ +// 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; + +namespace Autofac.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/Diagnostics/OperationSuccessDiagnosticData.cs b/src/Autofac/Diagnostics/OperationSuccessDiagnosticData.cs new file mode 100644 index 000000000..52f51dcd4 --- /dev/null +++ b/src/Autofac/Diagnostics/OperationSuccessDiagnosticData.cs @@ -0,0 +1,34 @@ +// 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; + +namespace Autofac.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/OperationTraceCompletedArgs.cs b/src/Autofac/Diagnostics/OperationTraceCompletedArgs.cs similarity index 53% rename from src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs rename to src/Autofac/Diagnostics/OperationTraceCompletedArgs.cs index 3ca54f219..05ed423ec 100644 --- a/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs +++ b/src/Autofac/Diagnostics/OperationTraceCompletedArgs.cs @@ -1,18 +1,24 @@ -using Autofac.Core.Resolving; +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. -namespace Autofac.Core.Diagnostics +using Autofac.Core.Resolving; + +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; @@ -26,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/src/Autofac/Diagnostics/RequestDiagnosticData.cs b/src/Autofac/Diagnostics/RequestDiagnosticData.cs new file mode 100644 index 000000000..a2e4e6a6a --- /dev/null +++ b/src/Autofac/Diagnostics/RequestDiagnosticData.cs @@ -0,0 +1,35 @@ +// 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; + +namespace Autofac.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/Diagnostics/RequestFailureDiagnosticData.cs b/src/Autofac/Diagnostics/RequestFailureDiagnosticData.cs new file mode 100644 index 000000000..e485c2b8e --- /dev/null +++ b/src/Autofac/Diagnostics/RequestFailureDiagnosticData.cs @@ -0,0 +1,43 @@ +// 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; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.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; + RequestContext = requestContext; + 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/Diagnostics/TracerMessages.Designer.cs b/src/Autofac/Diagnostics/TracerMessages.Designer.cs similarity index 85% rename from src/Autofac/Core/Diagnostics/TracerMessages.Designer.cs rename to src/Autofac/Diagnostics/TracerMessages.Designer.cs index efb6bdb72..c2406382a 100644 --- a/src/Autofac/Core/Diagnostics/TracerMessages.Designer.cs +++ b/src/Autofac/Diagnostics/TracerMessages.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -8,10 +8,10 @@ // //------------------------------------------------------------------------------ -namespace Autofac.Core.Diagnostics { +namespace Autofac.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. /// @@ -39,13 +39,13 @@ 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; } } - + /// /// 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,16 @@ internal class TracerMessages { return ResourceManager.GetString("ServiceDisplay", resourceCulture); } } - + + /// + /// 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/Core/Diagnostics/TracerMessages.resx b/src/Autofac/Diagnostics/TracerMessages.resx similarity index 90% rename from src/Autofac/Core/Diagnostics/TracerMessages.resx rename to src/Autofac/Diagnostics/TracerMessages.resx index 9dba2349a..d3f6959ba 100644 --- a/src/Autofac/Core/Diagnostics/TracerMessages.resx +++ b/src/Autofac/Diagnostics/TracerMessages.resx @@ -1,17 +1,17 @@  - @@ -126,6 +126,9 @@ { + + Exception: {0} + } @@ -135,6 +138,9 @@ <- {0} + + Instance: {0} + Operation FAILED @@ -166,7 +172,10 @@ 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} - \ No newline at end of file + 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)) { } 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 da9d1fe92..a1d5d6607 100644 --- a/src/Autofac/ILifetimeScope.cs +++ b/src/Autofac/ILifetimeScope.cs @@ -26,7 +26,6 @@ using System; using Autofac.Builder; using Autofac.Core; -using Autofac.Core.Diagnostics; using Autofac.Core.Lifetime; using Autofac.Core.Resolving; @@ -118,17 +117,6 @@ public interface ILifetimeScope : IComponentContext, IDisposable, IAsyncDisposab /// A new lifetime scope. 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. - /// - /// 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); - /// /// Gets the disposer associated with this . /// Component instances can be associated with it manually if required. diff --git a/src/Autofac/LifetimeScopeExtensions.cs b/src/Autofac/LifetimeScopeExtensions.cs deleted file mode 100644 index b10ab1bae..000000000 --- a/src/Autofac/LifetimeScopeExtensions.cs +++ /dev/null @@ -1,62 +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 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 AttachTrace(this ILifetimeScope scope, Action newTraceCallback) - { - 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); - }; - - scope.AttachTrace(tracer); - } - } -} 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.")] 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); } diff --git a/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs b/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs index b11d137f8..3e78ae32e 100644 --- a/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs +++ b/test/Autofac.Specification.Test/Diagnostics/DefaultDiagnosticTracerTests.cs @@ -1,5 +1,8 @@ -using System; -using Autofac.Core.Diagnostics; +// 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.Diagnostics; using Xunit; namespace Autofac.Specification.Test.Diagnostics @@ -16,7 +19,7 @@ public void DiagnosticTracerRaisesEvents() var container = containerBuilder.Build(); - container.AttachTrace(tracer); + container.SubscribeToDiagnostics(tracer); string lastOpResult = null; @@ -41,7 +44,7 @@ public void DiagnosticTracerRaisesEventsOnError() var container = containerBuilder.Build(); - container.AttachTrace(tracer); + container.SubscribeToDiagnostics(tracer); string lastOpResult = null; @@ -63,7 +66,7 @@ public void DiagnosticTracerRaisesEventsOnError() } [Fact] - public void DiagnosticTracerDoesNotRaiseAnEventOnNestedOperations() + public void DiagnosticTracerHandlesDecorators() { var tracer = new DefaultDiagnosticTracer(); @@ -73,22 +76,18 @@ public void DiagnosticTracerDoesNotRaiseAnEventOnNestedOperations() var container = containerBuilder.Build(); - container.AttachTrace(tracer); + 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); } @@ -103,10 +102,10 @@ public void DiagnosticTracerDoesNotLeakMemory() var container = containerBuilder.Build(); - container.AttachTrace(tracer); + 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); } @@ -119,7 +118,7 @@ private class Decorator : IService { public Decorator(IService decorated) { - this.Decorated = decorated; + Decorated = decorated; } public IService Decorated { get; } diff --git a/test/Autofac.Specification.Test/Diagnostics/DiagnosticTracerBaseTests.cs b/test/Autofac.Specification.Test/Diagnostics/DiagnosticTracerBaseTests.cs new file mode 100644 index 000000000..013df4a0d --- /dev/null +++ b/test/Autofac.Specification.Test/Diagnostics/DiagnosticTracerBaseTests.cs @@ -0,0 +1,26 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Autofac.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.Specification.Test/Features/CircularDependencyTests.cs b/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs index 5bfc8a216..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; @@ -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..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; @@ -31,7 +31,7 @@ public void TypeAsInstancePerScope() _output.WriteLine(args.TraceContent); }; - lifetime.AttachTrace(tracer); + container.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..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,13 +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, - null); + lifetimeScope.DiagnosticSource); built.Invoke(request); 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/Pipeline/PipelineBuilderTests.cs b/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs index f54a72209..fa589603e 100644 --- a/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs +++ b/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs @@ -1,13 +1,14 @@ using System; using System.Collections.Generic; +using System.Diagnostics; 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 @@ -422,10 +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(), - null) + new DiagnosticListener("Autofac")) { } } @@ -460,11 +461,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..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; @@ -34,12 +35,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, container.DiagnosticSource); var raisedEvents = new List(); @@ -84,12 +85,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, container.DiagnosticSource); var raisedEvents = new List(); diff --git a/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs b/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs new file mode 100644 index 000000000..7f0cc543f --- /dev/null +++ b/test/Autofac.Test/Diagnostics/DiagnosticSourceExtensionsTests.cs @@ -0,0 +1,190 @@ +// 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.Diagnostics; +using System.Linq; +using Autofac.Core; +using Autofac.Core.Resolving; +using Autofac.Core.Resolving.Pipeline; +using Autofac.Diagnostics; +using Moq; +using Xunit; + +namespace Autofac.Test.Diagnostics +{ + public class DiagnosticSourceExtensionsTests + { + [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, container.DiagnosticSource); + } + + 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, operation.DiagnosticSource); + } + + private class MockSubscriber : DiagnosticTracerBase + { + public List> Events { get; } = new List>(); + + protected override void Write(string diagnosticName, object data) + { + Events.Add(new KeyValuePair(diagnosticName, data)); + } + } + } +} diff --git a/test/Autofac.Test/Diagnostics/DiagnosticTracerBaseTests.cs b/test/Autofac.Test/Diagnostics/DiagnosticTracerBaseTests.cs new file mode 100644 index 000000000..e6bf29902 --- /dev/null +++ b/test/Autofac.Test/Diagnostics/DiagnosticTracerBaseTests.cs @@ -0,0 +1,48 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Autofac.Diagnostics; +using Xunit; + +namespace Autofac.Test.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 + { + } + } +} diff --git a/test/Autofac.Test/Diagnostics/OperationDiagnosticTracerBaseTests.cs b/test/Autofac.Test/Diagnostics/OperationDiagnosticTracerBaseTests.cs new file mode 100644 index 000000000..f55f65ce3 --- /dev/null +++ b/test/Autofac.Test/Diagnostics/OperationDiagnosticTracerBaseTests.cs @@ -0,0 +1,55 @@ +// 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.Diagnostics; +using Xunit; + +namespace Autofac.Test.Diagnostics +{ + public class OperationDiagnosticTracerBaseTests + { + [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_DefaultSubscribesToAllEvents(string key) + { + var tracer = new TestTracer(); + Assert.True(tracer.IsEnabled(key)); + } + + [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(); + } + } +} 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 c5f66f03f..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; @@ -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..04a90a52e 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 { @@ -104,19 +103,20 @@ public void BuildResolvePipeline(IComponentRegistryServices registryServices) } } - internal class MockTracer : IResolvePipelineTracer + internal class MockTracer : DiagnosticTracerBase { public MockTracer() { + this.EnableAll(); } public event Action OperationStarting; public event Action RequestStarting; - public event Action EnteringMiddleware; + public event Action EnteringMiddleware; - public event Action ExitingMiddleware; + public event Action ExitingMiddleware; public event Action RequestFailing; @@ -126,44 +126,49 @@ public MockTracer() public event Action OperationSucceeding; - public void OperationStart(ResolveOperationBase operation, ResolveRequest initiatingRequest) + protected override void OnOperationStart(OperationStartDiagnosticData data) { - OperationStarting?.Invoke(operation, initiatingRequest); + OperationStarting?.Invoke(data.Operation, data.InitiatingRequest); } - public void RequestStart(ResolveOperationBase operation, ResolveRequestContextBase requestContext) + protected override void OnRequestStart(RequestDiagnosticData data) { - RequestStarting?.Invoke(operation, requestContext); + RequestStarting?.Invoke(data.Operation, data.RequestContext); } - public void MiddlewareEntry(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) + protected override void OnMiddlewareStart(MiddlewareDiagnosticData data) { - EnteringMiddleware?.Invoke(operation, requestContext, middleware); + EnteringMiddleware?.Invoke(data.RequestContext, data.Middleware); } - public void MiddlewareExit(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware, bool succeeded) + protected override void OnMiddlewareFailure(MiddlewareDiagnosticData data) { - ExitingMiddleware?.Invoke(operation, requestContext, middleware, succeeded); + ExitingMiddleware?.Invoke(data.RequestContext, data.Middleware, false); } - public void RequestFailure(ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException) + protected override void OnMiddlewareSuccess(MiddlewareDiagnosticData data) { - RequestFailing?.Invoke(operation, requestContext, requestException); + ExitingMiddleware?.Invoke(data.RequestContext, data.Middleware, true); } - public void RequestSuccess(ResolveOperationBase operation, ResolveRequestContextBase requestContext) + protected override void OnRequestFailure(RequestFailureDiagnosticData data) { - RequestSucceeding?.Invoke(operation, requestContext); + RequestFailing?.Invoke(data.Operation, data.RequestContext, data.RequestException); } - public void OperationFailure(ResolveOperationBase operation, Exception operationException) + protected override void OnRequestSuccess(RequestDiagnosticData data) { - OperationFailing?.Invoke(operation, operationException); + RequestSucceeding?.Invoke(data.Operation, data.RequestContext); } - public void OperationSuccess(ResolveOperationBase operation, object resolvedInstance) + protected override void OnOperationFailure(OperationFailureDiagnosticData data) { - OperationSucceeding?.Invoke(operation, resolvedInstance); + OperationFailing?.Invoke(data.Operation, data.OperationException); + } + + protected override void OnOperationSuccess(OperationSuccessDiagnosticData data) + { + OperationSucceeding?.Invoke(data.Operation, data.ResolvedInstance); } } }