diff --git a/Directory.Build.targets b/Directory.Build.targets index 3adec0a4c9b..3dcffa62576 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -26,7 +26,7 @@ $(NoWarn);AD0001 - $(NoWarn);EXTEXP0001;EXTEXP0002;EXTEXP0003;EXTEXP0004;EXTEXP0005;EXTEXP0006;EXTEXP0007;EXTEXP0008;EXTEXP0009;EXTEXP0010;EXTEXP0011;EXTEXP0012;EXTEXP0013 + $(NoWarn);EXTEXP0001;EXTEXP0002;EXTEXP0003;EXTEXP0004;EXTEXP0005;EXTEXP0006;EXTEXP0007;EXTEXP0008;EXTEXP0009;EXTEXP0010;EXTEXP0011;EXTEXP0012;EXTEXP0013;EXTEXP0014;EXTEXP0015;EXTEXP0016;EXTEXP0017 $(NoWarn);NU5104 @@ -53,7 +53,7 @@ - <_Parameter1>TBD + <_Parameter1>$(StageDevDiagnosticId) <_Parameter2>UrlFormat = "https://aka.ms/dotnet-extensions-warnings/{0}" <_Parameter2_IsLiteral>true diff --git a/NuGet.config b/NuGet.config index 2f02faaae97..77ef6280040 100644 --- a/NuGet.config +++ b/NuGet.config @@ -2,6 +2,11 @@ + + + + + @@ -14,6 +19,9 @@ + + + diff --git a/README.md b/README.md index 133a82b9c5d..247bdf69ec3 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,14 @@ This repository contains a suite of libraries that provide facilities commonly needed when creating production-ready applications. Initially developed to support high-scale and high-availability services within Microsoft, such as Microsoft Teams, these libraries deliver functionality that can help make applications more efficient, more robust, and more manageable. -The code in this repo is preliminary and will be released in stable form with .NET 8 - The major functional areas this repo addresses are: -- Compliance: Mechanisms to help manage application data according to privacy regulations and policies, which includes a data annotation framework, supporting analyzers, audit report generation, and telemetry redaction. +- Compliance: Mechanisms to help manage application data according to privacy regulations and policies, which includes a data annotation framework, audit report generation, and telemetry redaction. - Diagnostics: Provides a set of APIs that can be used to gather and report diagnostic information about the health of a service. - Contextual Options: Extends the .NET Options model to enable experimentations in production. -- Resilience: Builds on top of the popular Polly library to provide sophisticated resilience pipelines along with support for chaos engineering to make applications robust to transient errors. +- Resilience: Builds on top of the popular Polly library to provide sophisticated resilience pipelines to make applications robust to transient errors. - Telemetry: Sophisticated telemetry facilities provide enhanced logging, metering, tracing, and latency measuring functionality. - AspNetCore extensions: Provides different middlewares and extensions that can be used to build high-performance and high-availability ASP.NET Core services. -- Cloud Abstractions: A growing set of abstractions representing common cloud-native service types, making it possible to write applications that can work across multiple cloud providers with relative ease. -- Static Analysis: Provides a set of Roslyn analyzers that can be used to enforce best practices and coding standards. +- Static Analysis: Curated static analysis settings to help improve your code. - Testing: Dramatically simplifies testing around common .NET abstractions such as ILogger and the TimeProvider. [![Build Status](https://dev.azure.com/dnceng/internal/_apis/build/status/r9/dotnet-r9?branchName=main)](https://dev.azure.com/dnceng/internal/_build/latest?definitionId=1223&branchName=main) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0fab90d71d3..7f5a245bb87 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -56,12 +56,11 @@ variables: - name: runAsPublic value: ${{ eq(variables['System.TeamProject'], 'public') }} + - name: _BuildConfig value: Release - name: isOfficialBuild value: ${{ and(ne(variables['runAsPublic'], 'true'), notin(variables['Build.Reason'], 'PullRequest')) }} - - name: IsDeltaBuild - value: ${{ eq(variables['Build.Reason'], 'PullRequest') }} - name: Build.Arcade.ArtifactsPath value: $(Build.SourcesDirectory)/artifacts/ - name: Build.Arcade.LogsPath @@ -69,14 +68,6 @@ variables: - name: Build.Arcade.TestResultsPath value: $(Build.Arcade.ArtifactsPath)TestResults/$(_BuildConfig)/ - # For full build we can do a shallow build, for a delta build we need the full history. - - ${{ if eq(variables['IsDeltaBuild'], 'true') }}: - - name: _FetchDepth - value: 0 - - ${{ else }}: - - name: _FetchDepth - value: 1 - - ${{ if or(startswith(variables['Build.SourceBranch'], 'refs/heads/release/'), startswith(variables['Build.SourceBranch'], 'refs/heads/internal/release/'), eq(variables['Build.Reason'], 'Manual')) }}: - name: PostBuildSign value: false @@ -163,7 +154,7 @@ stages: - checkout: self clean: true persistCredentials: true - fetchDepth: $(_FetchDepth) + fetchDepth: 1 steps: - template: /eng/pipelines/templates/BuildAndTest.yml @@ -173,7 +164,6 @@ stages: repoLogPath: $(Build.Arcade.LogsPath) repoTestResultsPath: $(Build.Arcade.TestResultsPath) skipQualityGates: ${{ eq(variables['SkipQualityGates'], 'true') }} - isDeltaBuild: $(IsDeltaBuild) isWindows: true warnAsError: 0 @@ -200,7 +190,7 @@ stages: - checkout: self clean: true persistCredentials: true - fetchDepth: $(_FetchDepth) + fetchDepth: 1 steps: - template: /eng/pipelines/templates/BuildAndTest.yml @@ -210,7 +200,6 @@ stages: repoLogPath: $(Build.Arcade.LogsPath) repoTestResultsPath: $(Build.Arcade.TestResultsPath) skipQualityGates: ${{ eq(variables['SkipQualityGates'], 'true') }} - isDeltaBuild: $(IsDeltaBuild) isWindows: false warnAsError: 0 @@ -262,8 +251,6 @@ stages: displayName: Init toolset - template: /eng/pipelines/templates/VerifyCoverageReport.yml - parameters: - isDeltaBuild: $(IsDeltaBuild) # ---------------------------------------------------------------- @@ -304,7 +291,7 @@ stages: - checkout: self clean: true persistCredentials: true - fetchDepth: $(_FetchDepth) + fetchDepth: 1 steps: - template: \eng\pipelines\templates\BuildAndTest.yml @@ -315,7 +302,6 @@ stages: repoTestResultsPath: $(Build.Arcade.TestResultsPath) skipTests: true skipQualityGates: true - isDeltaBuild: $(IsDeltaBuild) isWindows: false diff --git a/bench/.editorconfig b/bench/.editorconfig index 0d495f075f7..b2c317651b7 100644 --- a/bench/.editorconfig +++ b/bench/.editorconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:53Z +# Generated : 2023-10-22 00:37:47Z # Max Tier : 2147483647 # Attributes: general, performance # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CodeStyle, Microsoft.CodeAnalysis.CSharp.CodeStyle, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp, StyleCop.Analyzers @@ -868,7 +868,7 @@ dotnet_diagnostic.CA1860.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = warning @@ -903,6 +903,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = warning + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = warning + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1720,62 +1730,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = warning # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = warning # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = warning # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = warning # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = warning # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = warning # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = warning # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = warning # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Set MSBuild property 'GenerateDocumentationFile' to 'true' @@ -2408,6 +2433,26 @@ dotnet_diagnostic.IDE0300.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0301 dotnet_diagnostic.IDE0301.severity = suggestion +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0302 +dotnet_diagnostic.IDE0302.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0303 +dotnet_diagnostic.IDE0303.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0304 +dotnet_diagnostic.IDE0304.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0305 +dotnet_diagnostic.IDE0305.severity = none + # Title : Delegate invocation can be simplified. # Category : Style # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1005 diff --git a/bench/Generators/Microsoft.Gen.EnumStrings.PerformanceTests/EnumStrings.cs b/bench/Generators/Microsoft.Gen.EnumStrings.PerformanceTests/EnumStrings.cs deleted file mode 100644 index 33d985958d6..00000000000 --- a/bench/Generators/Microsoft.Gen.EnumStrings.PerformanceTests/EnumStrings.cs +++ /dev/null @@ -1,163 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using BenchmarkDotNet.Attributes; -using Microsoft.Extensions.EnumStrings; - -namespace Microsoft.Gen.EnumStrings.Bench; - -#pragma warning disable CA1822 // Mark members as static -#pragma warning disable EA0006 // Replace uses of 'Enum.GetName' and 'Enum.ToString' with the '[EnumStrings]' code generator for improved performance - -[MemoryDiagnoser] -public class EnumStrings -{ - private static readonly int[] _randomValues = new int[1000]; - - [EnumStrings] - internal enum Color - { - Red, - Green, - Blue, - } - - [Flags] - [EnumStrings] - internal enum SmallOptions - { - Options1 = 1, - Options2 = 2, - Options4 = 4, - Options8 = 8, - Options16 = 16, - } - - [Flags] - [EnumStrings] - internal enum LargeOptions - { - Options1 = 1, - Options2 = 2, - Options4 = 4, - Options8 = 8, - Options16 = 16, - Options32 = 32, - Options64 = 64, - Options128 = 128, - } - - static EnumStrings() - { - var r = new Random(); - for (int i = 0; i < _randomValues.Length; i++) - { - _randomValues[i] = r.Next(); - } - } - - [Benchmark] - public void ToStringColor() - { - _ = Color.Red.ToString(); - _ = Color.Green.ToString(); - _ = Color.Blue.ToString(); - } - - [Benchmark] - public void GetNameColor() - { - _ = Enum.GetName(Color.Red); - _ = Enum.GetName(Color.Green); - _ = Enum.GetName(Color.Blue); - } - - [Benchmark] - public void ToInvariantStringColor() - { - _ = Color.Red.ToInvariantString(); - _ = Color.Green.ToInvariantString(); - _ = Color.Blue.ToInvariantString(); - } - - [Benchmark] - public void ToStringSmallOptions() - { - _ = SmallOptions.Options1.ToString(); - _ = SmallOptions.Options2.ToString(); - _ = SmallOptions.Options4.ToString(); - _ = SmallOptions.Options8.ToString(); - _ = SmallOptions.Options16.ToString(); - - _ = (SmallOptions.Options1 | SmallOptions.Options16).ToString(); - _ = (SmallOptions.Options2 | SmallOptions.Options4).ToString(); - } - - [Benchmark] - public void ToInvariantStringSmallOptions() - { - _ = SmallOptions.Options1.ToInvariantString(); - _ = SmallOptions.Options2.ToInvariantString(); - _ = SmallOptions.Options4.ToInvariantString(); - _ = SmallOptions.Options8.ToInvariantString(); - _ = SmallOptions.Options16.ToInvariantString(); - - _ = (SmallOptions.Options1 | SmallOptions.Options16).ToInvariantString(); - _ = (SmallOptions.Options2 | SmallOptions.Options4).ToInvariantString(); - } - - [Benchmark] - public void ToStringLargeOptions() - { - _ = LargeOptions.Options1.ToString(); - _ = LargeOptions.Options2.ToString(); - _ = LargeOptions.Options4.ToString(); - _ = LargeOptions.Options8.ToString(); - _ = LargeOptions.Options16.ToString(); - _ = LargeOptions.Options32.ToString(); - _ = LargeOptions.Options64.ToString(); - _ = LargeOptions.Options128.ToString(); - - _ = (LargeOptions.Options1 | LargeOptions.Options16).ToString(); - _ = (LargeOptions.Options2 | LargeOptions.Options4).ToString(); - } - - [Benchmark] - public void ToInvariantStringLargeOptions() - { - _ = LargeOptions.Options1.ToInvariantString(); - _ = LargeOptions.Options2.ToInvariantString(); - _ = LargeOptions.Options4.ToInvariantString(); - _ = LargeOptions.Options8.ToInvariantString(); - _ = LargeOptions.Options16.ToInvariantString(); - _ = LargeOptions.Options32.ToInvariantString(); - _ = LargeOptions.Options64.ToInvariantString(); - _ = LargeOptions.Options128.ToInvariantString(); - - _ = (LargeOptions.Options1 | LargeOptions.Options16).ToInvariantString(); - _ = (LargeOptions.Options2 | LargeOptions.Options4).ToInvariantString(); - } - - // the next two benchmarks aren't representative of expected real-world use cases, but let's see the impact the code gen has relative to naked Enum.ToString - - [Benchmark] - public void ToStringRandom() - { - for (int i = 0; i < _randomValues.Length; i++) - { - var o = (LargeOptions)_randomValues[i]; - _ = o.ToString(); - } - } - - [Benchmark] - public void ToInvariantStringRandom() - { - for (int i = 0; i < _randomValues.Length; i++) - { - var o = (LargeOptions)_randomValues[i]; - _ = o.ToInvariantString(); - } - } -} diff --git a/bench/Generators/Microsoft.Gen.EnumStrings.PerformanceTests/Microsoft.Gen.EnumStrings.PerformanceTests.csproj b/bench/Generators/Microsoft.Gen.EnumStrings.PerformanceTests/Microsoft.Gen.EnumStrings.PerformanceTests.csproj deleted file mode 100644 index af53a3cc942..00000000000 --- a/bench/Generators/Microsoft.Gen.EnumStrings.PerformanceTests/Microsoft.Gen.EnumStrings.PerformanceTests.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - Microsoft.Gen.EnumStrings.PerformanceTests - Benchmarks for Gen.EnumStrings. - true - true - - - - - - - - - - diff --git a/bench/Generators/Microsoft.Gen.EnumStrings.PerformanceTests/Program.cs b/bench/Generators/Microsoft.Gen.EnumStrings.PerformanceTests/Program.cs deleted file mode 100644 index 9f45ee6d5ea..00000000000 --- a/bench/Generators/Microsoft.Gen.EnumStrings.PerformanceTests/Program.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Running; -using BenchmarkDotNet.Toolchains.InProcess.Emit; - -namespace Microsoft.Gen.EnumStrings.Bench; - -internal static class Program -{ - public static void Main(string[] args) - { - var dontRequireSlnToRunBenchmarks = ManualConfig - .Create(DefaultConfig.Instance) - .AddJob(Job.MediumRun.WithToolchain(InProcessEmitToolchain.Instance)); - - BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, dontRequireSlnToRunBenchmarks); - } -} diff --git a/bench/Generators/Microsoft.Gen.EnumStrings.PerformanceTests/README.md b/bench/Generators/Microsoft.Gen.EnumStrings.PerformanceTests/README.md deleted file mode 100644 index 6c7f983f12b..00000000000 --- a/bench/Generators/Microsoft.Gen.EnumStrings.PerformanceTests/README.md +++ /dev/null @@ -1,21 +0,0 @@ -``` -BenchmarkDotNet=v0.13.2, OS=Windows 11 (10.0.22621.963) -Intel Core i7-9700K CPU 3.60GHz (Coffee Lake), 1 CPU, 8 logical and 8 physical cores -.NET SDK=7.0.100 - [Host] : .NET 7.0.1 (7.0.122.56804), X64 RyuJIT AVX2 - -Job=MediumRun Toolchain=InProcessEmitToolchain IterationCount=15 -LaunchCount=2 WarmupCount=10 - -| Method | Mean | Error | StdDev | Gen0 | Allocated | -|------------------------------ |--------------:|--------------:|--------------:|--------:|----------:| -| ToStringColor | 40.991 ns | 0.5249 ns | 0.7358 ns | 0.0114 | 72 B | -| GetNameColor | 33.143 ns | 0.4681 ns | 0.7007 ns | - | - | -| ToInvariantStringColor | 3.834 ns | 0.0178 ns | 0.0267 ns | - | - | -| ToStringSmallOptions | 135.437 ns | 1.7399 ns | 2.5503 ns | 0.0470 | 296 B | -| ToInvariantStringSmallOptions | 12.207 ns | 0.0620 ns | 0.0889 ns | - | - | -| ToStringLargeOptions | 193.580 ns | 2.6614 ns | 3.8169 ns | 0.0587 | 368 B | -| ToInvariantStringLargeOptions | 16.424 ns | 0.2146 ns | 0.3145 ns | - | - | -| ToStringRandom | 72,026.761 ns | 1,205.1409 ns | 1,728.3770 ns | 10.8643 | 68232 B | -| ToInvariantStringRandom | 64,554.832 ns | 581.9061 ns | 852.9504 ns | 8.0566 | 50758 B | -``` diff --git a/bench/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware.PerformanceTests/RedactionBenchmark.cs b/bench/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware.PerformanceTests/RedactionBenchmark.cs index d8aae25fead..30e87512578 100644 --- a/bench/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware.PerformanceTests/RedactionBenchmark.cs +++ b/bench/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware.PerformanceTests/RedactionBenchmark.cs @@ -29,8 +29,8 @@ public class RedactionBenchmark public RedactionBenchmark() { - _routeParameterDataClasses.Add("userId", FakeClassifications.PrivateData); - _routeParameterDataClasses.Add("chatId", FakeClassifications.PrivateData); + _routeParameterDataClasses.Add("userId", FakeTaxonomy.PrivateData); + _routeParameterDataClasses.Add("chatId", FakeTaxonomy.PrivateData); _httpPath = "/users/{userId}/chats/{chatId}/test1/test2/{userId}"; _stringBuilderPool = PoolFactory.CreateStringBuilderPool(); diff --git a/bench/Libraries/Microsoft.Extensions.Http.Diagnostics.PerformanceTests/HttpClientFactory.cs b/bench/Libraries/Microsoft.Extensions.Http.Diagnostics.PerformanceTests/HttpClientFactory.cs index 8f8d3366fcb..8cef086ae76 100644 --- a/bench/Libraries/Microsoft.Extensions.Http.Diagnostics.PerformanceTests/HttpClientFactory.cs +++ b/bench/Libraries/Microsoft.Extensions.Http.Diagnostics.PerformanceTests/HttpClientFactory.cs @@ -26,7 +26,7 @@ public static System.Net.Http.HttpClient CreateWithLoggingLogRequest(string file { options.BodySizeLimit = readLimit; options.RequestBodyContentTypes.Add(new("application/json")); - options.RequestHeadersDataClasses.Add("Content-Type", FakeClassifications.PrivateData); + options.RequestHeadersDataClasses.Add("Content-Type", FakeTaxonomy.PrivateData); }) .AddHttpMessageHandler() .Services @@ -49,7 +49,7 @@ public static System.Net.Http.HttpClient CreateWithLoggingLogResponse(string fil { options.BodySizeLimit = readLimit; options.ResponseBodyContentTypes.Add(new("application/json")); - options.ResponseHeadersDataClasses.Add("Content-Type", FakeClassifications.PrivateData); + options.ResponseHeadersDataClasses.Add("Content-Type", FakeTaxonomy.PrivateData); }) .AddHttpMessageHandler() .Services @@ -73,10 +73,10 @@ public static System.Net.Http.HttpClient CreateWithLoggingLogAll(string fileName options.BodySizeLimit = readLimit; options.RequestBodyContentTypes.Add(new("application/json")); - options.RequestHeadersDataClasses.Add("Content-Type", FakeClassifications.PrivateData); + options.RequestHeadersDataClasses.Add("Content-Type", FakeTaxonomy.PrivateData); options.ResponseBodyContentTypes.Add(new("application/json")); - options.ResponseHeadersDataClasses.Add("Content-Type", FakeClassifications.PrivateData); + options.ResponseHeadersDataClasses.Add("Content-Type", FakeTaxonomy.PrivateData); }) .AddHttpMessageHandler() .Services @@ -99,7 +99,7 @@ public static System.Net.Http.HttpClient CreateWithLoggingLogRequest_ChunkedEnco { options.BodySizeLimit = readLimit; options.RequestBodyContentTypes.Add("application/json"); - options.RequestHeadersDataClasses.Add("Content-Type", FakeClassifications.PrivateData); + options.RequestHeadersDataClasses.Add("Content-Type", FakeTaxonomy.PrivateData); }) .AddHttpMessageHandler() .Services @@ -122,7 +122,7 @@ public static System.Net.Http.HttpClient CreateWithLoggingLogResponse_ChunkedEnc { options.BodySizeLimit = readLimit; options.ResponseBodyContentTypes.Add("application/json"); - options.ResponseHeadersDataClasses.Add("Content-Type", FakeClassifications.PrivateData); + options.ResponseHeadersDataClasses.Add("Content-Type", FakeTaxonomy.PrivateData); }) .AddHttpMessageHandler() .Services @@ -146,10 +146,10 @@ public static System.Net.Http.HttpClient CreateWithLoggingLogAll_ChunkedEncoding options.BodySizeLimit = readLimit; options.RequestBodyContentTypes.Add("application/json"); - options.RequestHeadersDataClasses.Add("Content-Type", FakeClassifications.PrivateData); + options.RequestHeadersDataClasses.Add("Content-Type", FakeTaxonomy.PrivateData); options.ResponseBodyContentTypes.Add("application/json"); - options.ResponseHeadersDataClasses.Add("Content-Type", FakeClassifications.PrivateData); + options.ResponseHeadersDataClasses.Add("Content-Type", FakeTaxonomy.PrivateData); }) .AddHttpMessageHandler() .Services diff --git a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/EmptyHandler.cs b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/EmptyHandler.cs index 08a53910c2b..21139d6e600 100644 --- a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/EmptyHandler.cs +++ b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/EmptyHandler.cs @@ -5,7 +5,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Extensions.Http.Resilience.Bench; +namespace Microsoft.Extensions.Http.Resilience.PerformanceTests; internal sealed class EmptyHandler : DelegatingHandler { diff --git a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HedgingBenchmark.cs b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HedgingBenchmark.cs index 830e915440f..87444bd4d32 100644 --- a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HedgingBenchmark.cs +++ b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HedgingBenchmark.cs @@ -8,7 +8,7 @@ using BenchmarkDotNet.Attributes; using Microsoft.Extensions.DependencyInjection; -namespace Microsoft.Extensions.Http.Resilience.Bench; +namespace Microsoft.Extensions.Http.Resilience.PerformanceTests; public class HedgingBenchmark { @@ -22,9 +22,7 @@ public void GlobalSetup() { var serviceProvider = HttpClientFactory.InitializeServiceProvider(Type); var factory = serviceProvider.GetRequiredService(); -#pragma warning disable EA0006 // Replace uses of 'Enum.GetName' and 'Enum.ToString' with the '[EnumStrings]' code generator for improved performance _client = factory.CreateClient(Type.ToString()); -#pragma warning restore EA0006 // Replace uses of 'Enum.GetName' and 'Enum.ToString' with the '[EnumStrings]' code generator for improved performance } [Params( diff --git a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HttpClientFactory.cs b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HttpClientFactory.cs index 787fde93907..93b601ed743 100644 --- a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HttpClientFactory.cs +++ b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HttpClientFactory.cs @@ -11,9 +11,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -#pragma warning disable EA0006 // Replace uses of 'Enum.GetName' and 'Enum.ToString' with the '[EnumStrings]' code generator for improved performance - -namespace Microsoft.Extensions.Http.Resilience.Bench; +namespace Microsoft.Extensions.Http.Resilience.PerformanceTests; [Flags] [SuppressMessage("Performance", "EA0004:Make types declared in an executable internal", Justification = "Needs to be public for BenchmarkDotNet consumption")] diff --git a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HttpResilienceBenchmark.cs b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HttpResilienceBenchmark.cs index 0cba391425a..be33374a3d2 100644 --- a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HttpResilienceBenchmark.cs +++ b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HttpResilienceBenchmark.cs @@ -8,7 +8,7 @@ using BenchmarkDotNet.Attributes; using Microsoft.Extensions.DependencyInjection; -namespace Microsoft.Extensions.Http.Resilience.Bench; +namespace Microsoft.Extensions.Http.Resilience.PerformanceTests; public class HttpResilienceBenchmark { diff --git a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/Microsoft.Extensions.Http.Resilience.PerformanceTests.csproj b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/Microsoft.Extensions.Http.Resilience.PerformanceTests.csproj index da3c2b88b09..fc10dbcce77 100644 --- a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/Microsoft.Extensions.Http.Resilience.PerformanceTests.csproj +++ b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/Microsoft.Extensions.Http.Resilience.PerformanceTests.csproj @@ -1,13 +1,19 @@  - Microsoft.Extensions.Http.Resilience.FaultInjection.PerformanceTests + Microsoft.Extensions.Http.Resilience.PerformanceTests Benchmarks for Microsoft.Extensions.Http.Resilience. - + + + + + + + diff --git a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/NoRemoteCallHandler.cs b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/NoRemoteCallHandler.cs index a2d9a00f34e..a834695bf2b 100644 --- a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/NoRemoteCallHandler.cs +++ b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/NoRemoteCallHandler.cs @@ -5,7 +5,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Extensions.Http.Resilience.Bench; +namespace Microsoft.Extensions.Http.Resilience.PerformanceTests; internal sealed class NoRemoteCallHandler : DelegatingHandler { diff --git a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/Program.cs b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/Program.cs index 3f96d60b9c0..1b0dd707dd6 100644 --- a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/Program.cs +++ b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/Program.cs @@ -7,7 +7,7 @@ using BenchmarkDotNet.Running; using BenchmarkDotNet.Toolchains.InProcess.Emit; -namespace Microsoft.Extensions.Http.Resilience.FaultInjection.Benchmark +namespace Microsoft.Extensions.Http.Resilience.PerformanceTests { internal static class Program { diff --git a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/RetryBenchmark.cs b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/RetryBenchmark.cs new file mode 100644 index 00000000000..16da92acd4a --- /dev/null +++ b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/RetryBenchmark.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Polly; +using Polly.Timeout; + +namespace Microsoft.Extensions.Http.Resilience.PerformanceTests; + +public class RetryBenchmark +{ + private static readonly Uri _uri = new(HttpClientFactory.PrimaryEndpoint); + + private HttpClient _v7 = null!; + private HttpClient _v8 = null!; + private CancellationToken _cancellationToken; + + private static HttpRequestMessage Request => new(HttpMethod.Post, _uri); + + [GlobalSetup] + public void Prepare() + { + _cancellationToken = new CancellationTokenSource().Token; + + var services = new ServiceCollection(); + + // The ResiliencePipelineBuilder added by Polly includes telemetry, which affects the results. + // Since Polly v7 does not include telemetry either, let's disable the telemetry for v8 for fair results. + services.TryAddTransient(); + + services + .AddHttpClient("v8") + .ConfigurePrimaryHttpMessageHandler(() => new NoRemoteCallHandler()) + .AddResilienceHandler("my-retries", builder => builder.AddRetry(new HttpRetryStrategyOptions + { + BackoffType = DelayBackoffType.Constant, + Delay = TimeSpan.FromSeconds(1), + MaxRetryAttempts = 3 + })); + + var builder = Policy.Handle().Or().OrResult(r => + { + var statusCode = (int)r.StatusCode; + + return statusCode >= 500 || + r.StatusCode == HttpStatusCode.RequestTimeout || + statusCode == 429; + }); + + services + .AddHttpClient("v7") + .ConfigurePrimaryHttpMessageHandler(() => new NoRemoteCallHandler()) + .AddPolicyHandler(builder.WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1))); + + var factory = services.BuildServiceProvider().GetRequiredService(); + + _v7 = factory.CreateClient("v7"); + _v8 = factory.CreateClient("v8"); + } + + [Benchmark(Baseline = true)] + public Task Retry_Polly_V7() + { + return _v7!.SendAsync(Request, _cancellationToken); + } + + [Benchmark] + public Task Retry_Polly_V8() + { + return _v8!.SendAsync(Request, _cancellationToken); + } +} diff --git a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/StandardResilienceBenchmark.cs b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/StandardResilienceBenchmark.cs new file mode 100644 index 00000000000..d7cc50f090e --- /dev/null +++ b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/StandardResilienceBenchmark.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Polly; +using Polly.Timeout; + +namespace Microsoft.Extensions.Http.Resilience.PerformanceTests; + +public class StandardResilienceBenchmark +{ + private static readonly Uri _uri = new(HttpClientFactory.PrimaryEndpoint); + + private HttpClient _v7 = null!; + private HttpClient _v8 = null!; + private CancellationToken _cancellationToken; + + private static HttpRequestMessage Request => new(HttpMethod.Post, _uri); + + [GlobalSetup] + public void Prepare() + { + _cancellationToken = new CancellationTokenSource().Token; + + var services = new ServiceCollection(); + + // The ResiliencePipelineBuilder added by Polly includes telemetry, which affects the results. + // Since Polly v7 does not include telemetry either, let's disable the telemetry for v8 for fair results. + services.TryAddTransient(); + + services + .AddHttpClient("v8") + .ConfigurePrimaryHttpMessageHandler(() => new NoRemoteCallHandler()) + .AddStandardResilienceHandler(); + + var builder = Policy.Handle().Or().OrResult(r => + { + var statusCode = (int)r.StatusCode; + + return statusCode >= 500 || + r.StatusCode == HttpStatusCode.RequestTimeout || + statusCode == 429; + }); + + var policy = Policy.WrapAsync( + Policy.TimeoutAsync(3), + builder.AdvancedCircuitBreakerAsync(0.1, TimeSpan.FromSeconds(30), 100, TimeSpan.FromSeconds(5)), + builder.WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1)), + Policy.BulkheadAsync(1000, 100), + Policy.TimeoutAsync(30)); + + services + .AddHttpClient("v7") + .ConfigurePrimaryHttpMessageHandler(() => new NoRemoteCallHandler()) + .AddPolicyHandler(_ => policy); + + var factory = services.BuildServiceProvider().GetRequiredService(); + + _v7 = factory.CreateClient("v7"); + _v8 = factory.CreateClient("v8"); + } + + [Benchmark(Baseline = true)] + public Task StandardPipeline_Polly_V7() + { + return _v7!.SendAsync(Request, _cancellationToken); + } + + [Benchmark] + public Task StandardPipeline_Polly_V8() + { + return _v8!.SendAsync(Request, _cancellationToken); + } +} diff --git a/bench/Libraries/Microsoft.Extensions.Resilience.PerformanceTests/Microsoft.Extensions.Resilience.PerformanceTests.csproj b/bench/Libraries/Microsoft.Extensions.Resilience.PerformanceTests/Microsoft.Extensions.Resilience.PerformanceTests.csproj index f755df01656..a6f614b1cb8 100644 --- a/bench/Libraries/Microsoft.Extensions.Resilience.PerformanceTests/Microsoft.Extensions.Resilience.PerformanceTests.csproj +++ b/bench/Libraries/Microsoft.Extensions.Resilience.PerformanceTests/Microsoft.Extensions.Resilience.PerformanceTests.csproj @@ -6,6 +6,6 @@ - + diff --git a/bench/Libraries/Microsoft.Extensions.Resilience.PerformanceTests/ResilienceEnrichmentBenchmark.cs b/bench/Libraries/Microsoft.Extensions.Resilience.PerformanceTests/ResilienceEnrichmentBenchmark.cs index b4b34751790..228a42531f9 100644 --- a/bench/Libraries/Microsoft.Extensions.Resilience.PerformanceTests/ResilienceEnrichmentBenchmark.cs +++ b/bench/Libraries/Microsoft.Extensions.Resilience.PerformanceTests/ResilienceEnrichmentBenchmark.cs @@ -23,11 +23,7 @@ public void GlobalSetup() { _listener = MetricsUtil.ListenPollyMetrics(); _pipeline = CreateResiliencePipeline(_ => { }); - _pipelineEnriched = CreateResiliencePipeline(services => - { - services.AddResilienceEnricher(); - services.ConfigureFailureResultContext(res => FailureResultContext.Create("dummy", "dummy", "dummy")); - }); + _pipelineEnriched = CreateResiliencePipeline(services => services.AddResilienceEnricher()); } [GlobalCleanup] diff --git a/bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/BenchLogger.cs b/bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/BenchLogger.cs similarity index 100% rename from bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/BenchLogger.cs rename to bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/BenchLogger.cs diff --git a/bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/BenchLoggerProvider.cs b/bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/BenchLoggerProvider.cs similarity index 100% rename from bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/BenchLoggerProvider.cs rename to bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/BenchLoggerProvider.cs diff --git a/bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/ClassicCodeGen.cs b/bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/ClassicCodeGen.cs similarity index 100% rename from bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/ClassicCodeGen.cs rename to bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/ClassicCodeGen.cs diff --git a/bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/ExtendedLoggerBench.cs b/bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/ExtendedLoggerBench.cs similarity index 100% rename from bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/ExtendedLoggerBench.cs rename to bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/ExtendedLoggerBench.cs diff --git a/bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests.csproj b/bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/Microsoft.Extensions.Telemetry.PerformanceTests.csproj similarity index 68% rename from bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests.csproj rename to bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/Microsoft.Extensions.Telemetry.PerformanceTests.csproj index 6396bb13fe9..a39e8be6927 100644 --- a/bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests.csproj +++ b/bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/Microsoft.Extensions.Telemetry.PerformanceTests.csproj @@ -1,13 +1,13 @@  - Microsoft.Extensions.Diagnostics.Extra.Bench - Benchmarks for Microsoft.Extensions.Diagnostics.Extra. + Microsoft.Extensions.Telemetry.Bench + Benchmarks for Microsoft.Extensions.Telemetry. true true - + diff --git a/bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/ModernCodeGen.cs b/bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/ModernCodeGen.cs similarity index 100% rename from bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/ModernCodeGen.cs rename to bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/ModernCodeGen.cs diff --git a/bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/Program.cs b/bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/Program.cs similarity index 100% rename from bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/Program.cs rename to bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/Program.cs diff --git a/bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/README.md b/bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/README.md similarity index 100% rename from bench/Libraries/Microsoft.Extensions.Diagnostics.Extra.PerformanceTests/README.md rename to bench/Libraries/Microsoft.Extensions.Telemetry.PerformanceTests/README.md diff --git a/docs/list-of-diagnostics.md b/docs/list-of-diagnostics.md index 5033c0be651..db04bdd4529 100644 --- a/docs/list-of-diagnostics.md +++ b/docs/list-of-diagnostics.md @@ -1,3 +1,63 @@ + +# ContextualOptions + +| Diagnostic ID | Description | +| :---------------- | :---------- | +| `CTXOPTGEN000` | Options context classes can't be static | +| `CTXOPTGEN001` | Options context types must be partial | +| `CTXOPTGEN002` | The options context type does not have usable properties | +| `CTXOPTGEN003` | The options context cannot be a ref-like type | + + +# Design + +| Diagnostic ID | Description | +| :---------------- | :---------- | +| `AUTOCLIENTGEN001` | API client interfaces must not be nested types | +| `AUTOCLIENTGEN002` | REST API client does not have methods defined | +| `AUTOCLIENTGEN003` | An API method must not contain more than one REST method attribute | +| `AUTOCLIENTGEN004` | Invalid API method return type | +| `AUTOCLIENTGEN005` | API methods can't be generic | +| `AUTOCLIENTGEN006` | The current HTTP method does not support the body tag | +| `AUTOCLIENTGEN007` | API methods must not be static | +| `AUTOCLIENTGEN008` | HTTP method missing | +| `AUTOCLIENTGEN009` | The API interface cannot be generic | +| `AUTOCLIENTGEN010` | Invalid API interface name | +| `AUTOCLIENTGEN011` | Duplicate body attribute | +| `AUTOCLIENTGEN012` | URL parameter missing from path | +| `AUTOCLIENTGEN013` | REST API method has more than one cancellation token | +| `AUTOCLIENTGEN014` | Missing CancellationToken from REST API method | +| `AUTOCLIENTGEN015` | API method path should not contain query | +| `AUTOCLIENTGEN016` | A REST API method's request name must be unique | +| `AUTOCLIENTGEN017` | Invalid HttpClient name | +| `AUTOCLIENTGEN018` | Invalid dependency name | +| `AUTOCLIENTGEN019` | Invalid header name | +| `AUTOCLIENTGEN020` | Invalid header value | +| `AUTOCLIENTGEN021` | Invalid REST method path | +| `AUTOCLIENTGEN022` | Invalid request name | + + +# ExtraAnalyzers + +| Diagnostic ID | Category | Description | +| :---------------- | :---------- | :---------- | +| `EA0000` | Performance | Use source generated logging methods for improved performance | +| `EA0001` | Performance | Perform message formatting in the body of the logging method | +| `EA0002` | Reliability | Use 'System.TimeProvider' to make the code easier to test | +| `EA0003` | Performance | Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' | +| `EA0004` | Performance | Make types declared in an executable internal | +| `EA0005` | Performance | Consider using an array instead of a collection | +| `EA0006` | Performance | Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance | +| `EA0007` | Performance | Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance | +| `EA0008` | Performance | Use generic collections instead of legacy collections for improved performance | +| `EA0009` | Performance | Use 'System.MemoryExtensions.Split' for improved performance | +| `EA0010` | Correctness | Fire-and-forget async call inside a 'using' block | +| `EA0011` | Performance | Consider removing unnecessary conditional access operator (?) | +| `EA0012` | Performance | Consider removing unnecessary null coalescing assignment (??=) | +| `EA0013` | Performance | Consider removing unnecessary null coalescing operator (??) | +| `EA0014` | Resilience | The async method doesn't support cancellation | + + # Experiments As new functionality is introduced to this repo, new in-development APIs are marked as being experimental. Experimental APIs offer no @@ -28,3 +88,75 @@ if desired. | `EXTEXP0011` | Document database experiments | | `EXTEXP0012` | Auto-activation experiments | | `EXTEXP0013` | HttpLogging middleware experiments | +| `EXTEXP0014` | ASP.NET Core integration testing experiments | +| `EXTEXP0015` | Environmental probes experiments | +| `EXTEXP0016` | Hosting integration testing experiments | +| `EXTEXP0017` | Contextual options experiments | + + +# LoggerMessage + +| Diagnostic ID | Description | +| :---------------- | :---------- | +| `LOGGEN000` | Don't include log level parameters as templates | +| `LOGGEN001` | Couldn't find a required type definition | +| `LOGGEN002` | Each logging method should use a unique event id | +| `LOGGEN003` | Logging methods must return void | +| `LOGGEN004` | A static logging method must have a parameter that implements the "Microsoft.Extensions.Logging.ILogger" interface | +| `LOGGEN005` | Logging methods must be static | +| `LOGGEN006` | Logging methods must be partial | +| `LOGGEN007` | Logging methods can't be generic | +| `LOGGEN008` | Redundant qualifier in the logging message | +| `LOGGEN009` | Don't include exception parameters as templates in the logging message | +| `LOGGEN010` | The logging template has no corresponding method parameter | +| `LOGGEN011` | A parameter isn't referenced from the logging message | +| `LOGGEN012` | Logging methods can't have a body | +| `LOGGEN013` | A "LogLevel" value must be supplied | +| `LOGGEN014` | Don't include logger parameters as templates | +| `LOGGEN015` | Couldn't find a field of type "Microsoft.Extensions.Logging.ILogger" | +| `LOGGEN016` | Multiple fields of type "Microsoft.Extensions.Logging.ILogger" were found | +| `LOGGEN017` | Can't combine the [LogProperties] or [TagProvider] attributes with data classification attributes | +| `LOGGEN018` | Can't log properties of a parameter or property | +| `LOGGEN019` | Method parameter can't be used to log properties | +| `LOGGEN020` | Logging method parameter type has no public properties to log | +| `LOGGEN021` | Logging method parameter type has cycles in its type hierarchy | +| `LOGGEN022` | Tag provider method not found | +| `LOGGEN023` | Tag provider method is inaccessible | +| `LOGGEN024` | Property provider method has an invalid signature | +| `LOGGEN025` | Logging method parameters can't have "ref" or "out" modifiers | +| `LOGGEN026` | Parameters with a custom tag provider are not subject to redaciton | +| `LOGGEN027` | Multiple logging methods shouldn't use the same event name | +| `LOGGEN028` | Logging method parameter's type has a hidden property | +| `LOGGEN029` | A logging method parameter causes name conflicts | +| `LOGGEN030` | Logging method doesn't log anything | +| `LOGGEN031` | A logging message template starts with "@" | +| `LOGGEN032` | Can only have one of [LogProperties], [TagProvider], and [LogPropertyIgnore] | +| `LOGGEN033` | Method parameter can't be used with a tag provider | +| `LOGGEN034` | Attribute can't be used in this context | +| `LOGGEN035` | The logging method parameter leaks sensitive data | + + +# Metrics + +| Diagnostic ID | Description | +| :---------------- | :---------- | +| `METGEN000` | Metric method names can't start with an underscore | +| `METGEN001` | Metric method parameter names can't start with an underscore | +| `METGEN002` | Metric names must start with an uppercase alphabetic character | +| `METGEN003` | Multiple metric methods can't use the same metric name | +| `METGEN004` | Metric methods mustn't use any existing type as the return type | +| `METGEN005` | The first parameter should be of type `System.Diagnostics.Metrics.Meter` | +| `METGEN006` | Metric methods must be partial | +| `METGEN007` | Metric methods can't be generic | +| `METGEN008` | Metric methods can't have a body | +| `METGEN009` | Tag names should contain alphanumeric characters and only allowed symbols | +| `METGEN010` | Metric methods must be static | +| `METGEN011` | A strong type object contains duplicate tag names | +| `METGEN012` | A metric class contains an invalid tag name type | +| `METGEN013` | A metric class contains too many tag names | +| `METGEN014` | A metering attribute type argument is invalid | +| `METGEN015` | Metric methods mustn't use any external type as the return type | +| `METGEN016` | Metric methods mustn't use any generic type as the return type | +| `METGEN017` | Gauge is not supported yet | +| `METGEN018` | Xml comment was not parsed correctly | +| `METGEN019` | A metric class has cycles in its type hierarchy | diff --git a/eng/Diags/ILLink.RoslynAnalyzer.yml b/eng/Diags/ILLink.RoslynAnalyzer.yml index b79406b7c38..a86cabf00a2 100644 --- a/eng/Diags/ILLink.RoslynAnalyzer.yml +++ b/eng/Diags/ILLink.RoslynAnalyzer.yml @@ -1,12 +1,12 @@ Origin: AssemblyName: ILLink.RoslynAnalyzer - Version: 8.0.8.35901 + Version: 8.0.9.408 Diagnostics: IL2026: Metadata: Category: Trimming Title: Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2026 DefaultSeverity: Warning Tier: 1 Attributes: @@ -16,7 +16,7 @@ Diagnostics: Metadata: Category: Trimming Title: "'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides." - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2046 DefaultSeverity: Warning Tier: 1 Attributes: @@ -36,7 +36,7 @@ Diagnostics: Metadata: Category: Trimming Title: Either the type on which the MakeGenericType is called can't be statically determined, or the type parameters to be used for generic arguments can't be statically determined. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2055 DefaultSeverity: Warning Tier: 1 Attributes: @@ -46,7 +46,7 @@ Diagnostics: Metadata: Category: Trimming Title: Call to 'System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed. It's not possible to guarantee the availability of requirements of the generic method. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2060 DefaultSeverity: Warning Tier: 1 Attributes: @@ -56,7 +56,7 @@ Diagnostics: Metadata: Category: Trimming Title: Types that derive from a base class with 'RequiresUnreferencedCodeAttribute' need to explicitly use the 'RequiresUnreferencedCodeAttribute' or suppress this warning - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2109 DefaultSeverity: Warning Tier: 1 Attributes: @@ -96,7 +96,7 @@ Diagnostics: Metadata: Category: SingleFile Title: "'RequiresAssemblyFilesAttribute' annotations must match across all interface implementations or overrides." - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/single-file/warnings/il3003 DefaultSeverity: Warning Tier: 1 Attributes: @@ -106,7 +106,7 @@ Diagnostics: Metadata: Category: Trimming Title: The 'DynamicallyAccessedMembersAttribute' is not allowed on methods. It is allowed on method return value or method parameters. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2041 DefaultSeverity: Warning Tier: 2 Attributes: @@ -116,7 +116,7 @@ Diagnostics: Metadata: Category: Trimming Title: The parameter of method has a DynamicallyAccessedMembersAttribute, but the value passed to it can not be statically analyzed. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2062 DefaultSeverity: Warning Tier: 1 Attributes: @@ -126,7 +126,7 @@ Diagnostics: Metadata: Category: Trimming Title: The return value of method has a DynamicallyAccessedMembersAttribute, but the value returned from the method can not be statically analyzed. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2063 DefaultSeverity: Warning Tier: 1 Attributes: @@ -136,7 +136,7 @@ Diagnostics: Metadata: Category: Trimming Title: The field has a DynamicallyAccessedMembersAttribute, but the value assigned to it can not be statically analyzed. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2064 DefaultSeverity: Warning Tier: 1 Attributes: @@ -146,7 +146,7 @@ Diagnostics: Metadata: Category: Trimming Title: The method has a DynamicallyAccessedMembersAttribute (which applies to the implicit 'this' parameter), but the value used for the 'this' parameter can not be statically analyzed. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2065 DefaultSeverity: Warning Tier: 1 Attributes: @@ -156,7 +156,7 @@ Diagnostics: Metadata: Category: Trimming Title: The generic parameter of type or method has a DynamicallyAccessedMembersAttribute, but the value used for it can not be statically analyzed. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2066 DefaultSeverity: Warning Tier: 1 Attributes: @@ -166,7 +166,7 @@ Diagnostics: Metadata: Category: Trimming Title: Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The parameter of method does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2067 DefaultSeverity: Warning Tier: 2 Attributes: @@ -176,7 +176,7 @@ Diagnostics: Metadata: Category: Trimming Title: Target method return value does not satisfy 'DynamicallyAccessedMembersAttribute' requirements. The parameter of method does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2068 DefaultSeverity: Warning Tier: 2 Attributes: @@ -186,7 +186,7 @@ Diagnostics: Metadata: Category: Trimming Title: Value stored in field does not satisfy 'DynamicallyAccessedMembersAttribute' requirements. The parameter of method does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2069 DefaultSeverity: Warning Tier: 2 Attributes: @@ -196,7 +196,7 @@ Diagnostics: Metadata: Category: Trimming Title: "'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The parameter of method does not have matching annotations." - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2070 DefaultSeverity: Warning Tier: 1 Attributes: @@ -206,7 +206,7 @@ Diagnostics: Metadata: Category: Trimming Title: Generic argument does not satisfy 'DynamicallyAccessedMembersAttribute' in target method or type. The parameter of method does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2071 DefaultSeverity: Warning Tier: 1 Attributes: @@ -216,7 +216,7 @@ Diagnostics: Metadata: Category: Trimming Title: Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2072 DefaultSeverity: Warning Tier: 1 Attributes: @@ -226,7 +226,7 @@ Diagnostics: Metadata: Category: Trimming Title: Target method return value does not satisfy 'DynamicallyAccessedMembersAttribute' requirements. The return value of the source method does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2073 DefaultSeverity: Warning Tier: 1 Attributes: @@ -236,7 +236,7 @@ Diagnostics: Metadata: Category: Trimming Title: Value stored in field does not satisfy 'DynamicallyAccessedMembersAttribute' requirements. The return value of the source method does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2074 DefaultSeverity: Warning Tier: 1 Attributes: @@ -246,7 +246,7 @@ Diagnostics: Metadata: Category: Trimming Title: "'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations." - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2075 DefaultSeverity: Warning Tier: 1 Attributes: @@ -256,7 +256,7 @@ Diagnostics: Metadata: Category: Trimming Title: Target generic argument does not satisfy 'DynamicallyAccessedMembersAttribute' in target method or type. The return value of the source method does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2076 DefaultSeverity: Warning Tier: 1 Attributes: @@ -266,7 +266,7 @@ Diagnostics: Metadata: Category: Trimming Title: Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The source field does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2077 DefaultSeverity: Warning Tier: 1 Attributes: @@ -276,7 +276,7 @@ Diagnostics: Metadata: Category: Trimming Title: Target method return value does not satisfy 'DynamicallyAccessedMembersAttribute' requirements. The source field does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2078 DefaultSeverity: Warning Tier: 1 Attributes: @@ -286,7 +286,7 @@ Diagnostics: Metadata: Category: Trimming Title: Value stored in target field does not satisfy 'DynamicallyAccessedMembersAttribute' requirements. The source field does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2079 DefaultSeverity: Warning Tier: 1 Attributes: @@ -296,7 +296,7 @@ Diagnostics: Metadata: Category: Trimming Title: "'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The source field does not have matching annotations." - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2080 DefaultSeverity: Warning Tier: 1 Attributes: @@ -306,7 +306,7 @@ Diagnostics: Metadata: Category: Trimming Title: Target generic argument does not satisfy 'DynamicallyAccessedMembersAttribute' in target method or type. The source field does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2081 DefaultSeverity: Warning Tier: 1 Attributes: @@ -316,7 +316,7 @@ Diagnostics: Metadata: Category: Trimming Title: Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The implicit 'this' argument of source method does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2082 DefaultSeverity: Warning Tier: 1 Attributes: @@ -326,7 +326,7 @@ Diagnostics: Metadata: Category: Trimming Title: Target method return value does not satisfy 'DynamicallyAccessedMembersAttribute' requirements. The implicit 'this' argument of source method does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2083 DefaultSeverity: Warning Tier: 1 Attributes: @@ -336,7 +336,7 @@ Diagnostics: Metadata: Category: Trimming Title: Value stored in target field does not satisfy 'DynamicallyAccessedMembersAttribute' requirements. The implicit 'this' argument of source method does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2084 DefaultSeverity: Warning Tier: 1 Attributes: @@ -346,7 +346,7 @@ Diagnostics: Metadata: Category: Trimming Title: "'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The implicit 'this' argument of source method does not have matching annotations." - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2085 DefaultSeverity: Warning Tier: 1 Attributes: @@ -356,7 +356,7 @@ Diagnostics: Metadata: Category: Trimming Title: Target generic argument does not satisfy 'DynamicallyAccessedMembersAttribute' in target method or type. The implicit 'this' argument of source method does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2086 DefaultSeverity: Warning Tier: 1 Attributes: @@ -366,7 +366,7 @@ Diagnostics: Metadata: Category: Trimming Title: Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The generic parameter of the source method or type does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2087 DefaultSeverity: Warning Tier: 1 Attributes: @@ -376,7 +376,7 @@ Diagnostics: Metadata: Category: Trimming Title: Target method return value does not satisfy 'DynamicallyAccessedMembersAttribute' requirements. The generic parameter of the source method or type does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2088 DefaultSeverity: Warning Tier: 1 Attributes: @@ -386,7 +386,7 @@ Diagnostics: Metadata: Category: Trimming Title: Value stored in target field does not satisfy 'DynamicallyAccessedMembersAttribute' requirements. The generic parameter of the source method or type does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2089 DefaultSeverity: Warning Tier: 1 Attributes: @@ -396,7 +396,7 @@ Diagnostics: Metadata: Category: Trimming Title: "'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The generic parameter of the source method or type does not have matching annotations." - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2090 DefaultSeverity: Warning Tier: 1 Attributes: @@ -406,7 +406,7 @@ Diagnostics: Metadata: Category: Trimming Title: Target generic argument does not satisfy 'DynamicallyAccessedMembersAttribute' in target method or type. The generic parameter of the source method or type does not have matching annotations. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2091 DefaultSeverity: Warning Tier: 1 Attributes: @@ -416,7 +416,7 @@ Diagnostics: Metadata: Category: Trimming Title: Field has 'DynamicallyAccessedMembersAttribute', but that attribute can only be applied to fields of type 'System.Type' or 'System.String'. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2097 DefaultSeverity: Warning Tier: 1 Attributes: @@ -426,7 +426,7 @@ Diagnostics: Metadata: Category: Trimming Title: Parameter of method has 'DynamicallyAccessedMembersAttribute', but that attribute can only be applied to parameters of type 'System.Type' or 'System.String'. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2098 DefaultSeverity: Warning Tier: 1 Attributes: @@ -436,7 +436,7 @@ Diagnostics: Metadata: Category: Trimming Title: Property has 'DynamicallyAccessedMembersAttribute', but that attribute can only be applied to properties of type 'System.Type' or 'System.String'. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2099 DefaultSeverity: Warning Tier: 1 Attributes: @@ -446,7 +446,7 @@ Diagnostics: Metadata: Category: Trimming Title: Return type of method has 'DynamicallyAccessedMembersAttribute', but that attribute can only be applied to properties of type 'System.Type' or 'System.String'. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2106 DefaultSeverity: Warning Tier: 1 Attributes: @@ -456,7 +456,7 @@ Diagnostics: Metadata: Category: Trimming Title: Field with 'DynamicallyAccessedMembersAttribute' is accessed via reflection. Trimmer can't guarantee availability of the requirements of the field. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2110 DefaultSeverity: Warning Tier: 1 Attributes: @@ -466,7 +466,7 @@ Diagnostics: Metadata: Category: Trimming Title: Method with parameters or return value with `DynamicallyAccessedMembersAttribute` is accessed via reflection. Trimmer can't guarantee availability of the requirements of the method. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2111 DefaultSeverity: Warning Tier: 1 Attributes: @@ -476,7 +476,7 @@ Diagnostics: Metadata: Category: Trimming Title: The type passed to the RunClassConstructor is not statically known, Trimmer can't make sure that its static constructor is available. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2059 DefaultSeverity: Warning Tier: 2 Attributes: @@ -486,7 +486,7 @@ Diagnostics: Metadata: Category: Trimming Title: "'DynamicallyAccessedMemberTypes' on the return value of method don't match overridden return value of method. All overridden members must have the same 'DynamicallyAccessedMembersAttribute' usage." - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2093 DefaultSeverity: Warning Tier: 1 Attributes: @@ -496,7 +496,7 @@ Diagnostics: Metadata: Category: Trimming Title: "'DynamicallyAccessedMemberTypes' on the parameter of method don't match overridden parameter of method. All overridden members must have the same 'DynamicallyAccessedMembersAttribute' usage." - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2092 DefaultSeverity: Warning Tier: 1 Attributes: @@ -506,7 +506,7 @@ Diagnostics: Metadata: Category: Trimming Title: "'DynamicallyAccessedMemberTypes' on the generic parameter of method or type don't match overridden generic parameter method or type. All overridden members must have the same 'DynamicallyAccessedMembersAttribute' usage." - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2095 DefaultSeverity: Warning Tier: 1 Attributes: @@ -516,7 +516,7 @@ Diagnostics: Metadata: Category: Trimming Title: "'DynamicallyAccessedMemberTypes' on the implicit 'this' parameter of method don't match overridden implicit 'this' parameter of method. All overridden members must have the same 'DynamicallyAccessedMembersAttribute' usage." - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2094 DefaultSeverity: Warning Tier: 1 Attributes: @@ -526,7 +526,7 @@ Diagnostics: Metadata: Category: Trimming Title: "'DynamicallyAccessedMembersAttribute' on property conflicts with the same attribute on its accessor." - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2043 DefaultSeverity: Warning Tier: 2 Attributes: @@ -536,7 +536,7 @@ Diagnostics: Metadata: Category: Trimming Title: Value passed to the parameter of method cannot be statically determined as a property accessor. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2103 DefaultSeverity: Warning Tier: 2 Attributes: @@ -546,7 +546,7 @@ Diagnostics: Metadata: Category: Trimming Title: Call to 'Type.GetType' method can perform case insensitive lookup of the type, currently trimming can not guarantee presence of all the matching types. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2096 DefaultSeverity: Warning Tier: 3 Attributes: @@ -556,7 +556,7 @@ Diagnostics: Metadata: Category: Trimming Title: Unrecognized value passed to the parameter of method. It's not possible to guarantee the availability of the target type. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2057 DefaultSeverity: Warning Tier: 2 Attributes: @@ -566,7 +566,7 @@ Diagnostics: Metadata: Category: Trimming Title: The value passed as the assembly name or type name to the CreateInstance method can't be statically analyzed. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2032 DefaultSeverity: Warning Tier: 2 Attributes: @@ -576,7 +576,7 @@ Diagnostics: Metadata: Category: Trimming Title: Parameters passed to method cannot be analyzed. Consider using methods 'System.Type.GetType' and `System.Activator.CreateInstance` instead. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2058 DefaultSeverity: Warning Tier: 2 Attributes: @@ -586,7 +586,7 @@ Diagnostics: Metadata: Category: SingleFile Title: The use of 'RequiresAssemblyFilesAttribute' on static constructors is disallowed since is a method not callable by the user, is only called by the runtime. Placing the attribute directly on the static constructor will have no effect, instead use 'RequiresUnreferencedCodeAttribute' on the type which will handle warning and silencing from the static constructor. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/single-file/warnings/il3004 DefaultSeverity: Warning Tier: 3 Attributes: @@ -596,7 +596,7 @@ Diagnostics: Metadata: Category: AOT Title: Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/native-aot/warnings/il3050 DefaultSeverity: Warning Tier: 1 Attributes: @@ -606,7 +606,7 @@ Diagnostics: Metadata: Category: AOT Title: "'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides." - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/native-aot/warnings/il3051 DefaultSeverity: Warning Tier: 1 Attributes: @@ -616,7 +616,7 @@ Diagnostics: Metadata: Category: AOT Title: The use of 'RequiresDynamicCodeAttribute' on static constructors is disallowed since is a method not callable by the user, is only called by the runtime. Placing the attribute directly on the static constructor will have no effect, instead use 'RequiresUnreferencedCodeAttribute' on the type which will handle warning and silencing from the static constructor. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/native-aot/warnings/il3056 DefaultSeverity: Warning Tier: 1 Attributes: @@ -626,7 +626,7 @@ Diagnostics: Metadata: Category: Trimming Title: The use of 'RequiresUnreferencedCodeAttribute' on static constructors is disallowed since is a method not callable by the user, is only called by the runtime. Placing the attribute directly on the static constructor will have no effect, instead use 'RequiresUnreferencedCodeAttribute' on the type which will handle warning and silencing from the static constructor. - Description: '' + Description: https://learn.microsoft.com/dotnet/core/deploying/trimming/trim-warnings/il2116 DefaultSeverity: Warning Tier: 1 Attributes: diff --git a/eng/Diags/Microsoft.Analyzers.Extra.yml b/eng/Diags/Microsoft.Analyzers.Extra.yml index 42a21498cde..ec415a8bd1b 100644 --- a/eng/Diags/Microsoft.Analyzers.Extra.yml +++ b/eng/Diags/Microsoft.Analyzers.Extra.yml @@ -7,6 +7,7 @@ Diagnostics: Category: Performance Title: Consider removing unnecessary null coalescing assignment (??=) Description: Using the null coalescing assignment operator (??=) with values which are statically known not to be null causes superfluous null checks to be performed at runtime + HelpLinkUri: https://aka.ms/dotnet-extensions-warnings/EA0012 DefaultSeverity: Warning Tier: 1 Attributes: @@ -17,6 +18,7 @@ Diagnostics: Category: Performance Title: Consider removing unnecessary null coalescing operator (??) Description: Using the null coalescing operator (??) with values which are statically known to be null causes superfluous null checks to be performed at runtime + HelpLinkUri: https://aka.ms/dotnet-extensions-warnings/EA0013 DefaultSeverity: Warning Tier: 1 Attributes: @@ -27,6 +29,7 @@ Diagnostics: Category: Performance Title: Make types declared in an executable internal Description: Making an executable's types internal enables dead code analysis along with other potential optimizations + HelpLinkUri: https://aka.ms/dotnet-extensions-warnings/EA0004 DefaultSeverity: Warning Tier: 1 Attributes: @@ -39,6 +42,7 @@ Diagnostics: Category: Performance Title: Perform message formatting in the body of the logging method Description: Identifies calls to the 'ToString' method as arguments to a logging method + HelpLinkUri: https://aka.ms/dotnet-extensions-warnings/EA0001 DefaultSeverity: Warning Tier: 1 Attributes: @@ -51,6 +55,7 @@ Diagnostics: Category: Performance Title: Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' Description: When checking for a single character, prefer the character overloads of 'String.StartsWith' and 'String.EndsWith' for improved performance + HelpLinkUri: https://aka.ms/dotnet-extensions-warnings/EA0003 DefaultSeverity: Warning Tier: 1 Attributes: @@ -63,6 +68,7 @@ Diagnostics: Category: Reliability Title: Use 'System.TimeProvider' to make the code easier to test Description: Identifies uses of time dependent APIs that can lead to flaky tests + HelpLinkUri: https://aka.ms/dotnet-extensions-warnings/EA0002 DefaultSeverity: Warning Tier: 1 Attributes: @@ -75,6 +81,7 @@ Diagnostics: Category: Performance Title: Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance Description: Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance + HelpLinkUri: https://aka.ms/dotnet-extensions-warnings/EA0006 DefaultSeverity: Warning Tier: 1 Attributes: @@ -87,6 +94,7 @@ Diagnostics: Category: Performance Title: Use source generated logging methods for improved performance Description: Identifies calls to legacy logging methods + HelpLinkUri: https://aka.ms/dotnet-extensions-warnings/EA0000 DefaultSeverity: Warning Tier: 1 Attributes: @@ -99,6 +107,7 @@ Diagnostics: Category: Performance Title: Use 'System.MemoryExtensions.Split' for improved performance Description: Use 'System.MemoryExtensions.Split' for improved performance + HelpLinkUri: https://aka.ms/dotnet-extensions-warnings/EA0009 DefaultSeverity: Warning Tier: 1 Attributes: @@ -111,6 +120,7 @@ Diagnostics: Category: Performance Title: Use generic collections instead of legacy collections for improved performance Description: Using generic collections can avoid boxing overhead and provides strong typing + HelpLinkUri: https://aka.ms/dotnet-extensions-warnings/EA0008 DefaultSeverity: Warning Tier: 1 Attributes: @@ -123,6 +133,7 @@ Diagnostics: Category: Performance Title: Consider using an array instead of a collection Description: Dictionaries and sets which use enums and bytes as keys can often be replaced with simple arrays for improved performance + HelpLinkUri: https://aka.ms/dotnet-extensions-warnings/EA0005 DefaultSeverity: Warning Tier: 1 Attributes: @@ -135,6 +146,7 @@ Diagnostics: Category: Performance Title: Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance Description: Using 'System.ValueTuple' avoids allocations and is generally more efficient than 'System.Tuple' + HelpLinkUri: https://aka.ms/dotnet-extensions-warnings/EA0007 DefaultSeverity: Warning Tier: 1 Attributes: @@ -147,6 +159,7 @@ Diagnostics: Category: Correctness Title: Fire-and-forget async call inside a 'using' block Description: When skipping the await keyword for asynchronous operations inside a using block, then a disposable object could be disposed before the asynchronous invocation finishes. This might result in incorrect behavior and very often ends with a runtime exception notifying that the code is trying to operate on a disposed object. + HelpLinkUri: https://aka.ms/dotnet-extensions-warnings/EA0010 DefaultSeverity: Warning Tier: 1 Attributes: @@ -157,6 +170,7 @@ Diagnostics: Category: Resilience Title: The async method doesn't support cancellation Description: Accepting a CancellationToken as a parameter allows caller to express a loss of interest in the result enabling the method to save cycles by finishing early + HelpLinkUri: https://aka.ms/dotnet-extensions-warnings/EA0014 DefaultSeverity: Warning Tier: 1 Attributes: @@ -169,6 +183,7 @@ Diagnostics: Category: Performance Title: Consider removing unnecessary conditional access operator (?) Description: Using the conditional access operator (?) to access values which are statically known not to be null causes superfluous null checks to be performed at runtime + HelpLinkUri: https://aka.ms/dotnet-extensions-warnings/EA0011 DefaultSeverity: Warning Tier: 1 Attributes: diff --git a/eng/Diags/Microsoft.AspNetCore.Components.Analyzers.yml b/eng/Diags/Microsoft.AspNetCore.Components.Analyzers.yml index a7da7ee84a5..17fa4e09ab7 100644 --- a/eng/Diags/Microsoft.AspNetCore.Components.Analyzers.yml +++ b/eng/Diags/Microsoft.AspNetCore.Components.Analyzers.yml @@ -1,6 +1,6 @@ Origin: AssemblyName: Microsoft.AspNetCore.Components.Analyzers - Version: 8.0.23.35902 + Version: 8.0.23.50222 Diagnostics: BL0001: Metadata: diff --git a/eng/Diags/Microsoft.CodeAnalysis.CSharp.CodeStyle.yml b/eng/Diags/Microsoft.CodeAnalysis.CSharp.CodeStyle.yml index e0307e9eea4..2554594006d 100644 --- a/eng/Diags/Microsoft.CodeAnalysis.CSharp.CodeStyle.yml +++ b/eng/Diags/Microsoft.CodeAnalysis.CSharp.CodeStyle.yml @@ -1,6 +1,6 @@ Origin: AssemblyName: Microsoft.CodeAnalysis.CSharp.CodeStyle - Version: 4.8.8.35803 + Version: 4.8.9.404 Diagnostics: IDE0001: Metadata: @@ -1785,3 +1785,59 @@ Diagnostics: Attributes: general: Severity: None + IDE0304: + Metadata: + Category: Style + Title: Simplify collection initialization + Description: '' + HelpLinkUri: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0304 + CustomTags: + - Telemetry + - EnforceOnBuild_Recommended + DefaultSeverity: None + Tier: 1 + Attributes: + general: + Severity: None + IDE0305: + Metadata: + Category: Style + Title: Simplify collection initialization + Description: '' + HelpLinkUri: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0305 + CustomTags: + - Telemetry + - EnforceOnBuild_Recommended + DefaultSeverity: None + Tier: 1 + Attributes: + general: + Severity: None + IDE0303: + Metadata: + Category: Style + Title: Simplify collection initialization + Description: '' + HelpLinkUri: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0303 + CustomTags: + - Telemetry + - EnforceOnBuild_Recommended + DefaultSeverity: None + Tier: 1 + Attributes: + general: + Severity: None + IDE0302: + Metadata: + Category: Style + Title: Simplify collection initialization + Description: '' + HelpLinkUri: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0302 + CustomTags: + - Telemetry + - EnforceOnBuild_Recommended + DefaultSeverity: None + Tier: 1 + Attributes: + general: + Severity: None diff --git a/eng/Diags/Microsoft.CodeAnalysis.CSharp.NetAnalyzers.yml b/eng/Diags/Microsoft.CodeAnalysis.CSharp.NetAnalyzers.yml index 43490bdc8f0..422d307d8f7 100644 --- a/eng/Diags/Microsoft.CodeAnalysis.CSharp.NetAnalyzers.yml +++ b/eng/Diags/Microsoft.CodeAnalysis.CSharp.NetAnalyzers.yml @@ -1,6 +1,6 @@ Origin: AssemblyName: Microsoft.CodeAnalysis.CSharp.NetAnalyzers - Version: 8.0.8.35701 + Version: 8.0.8.47201 Diagnostics: CA1001: Metadata: @@ -597,3 +597,19 @@ Diagnostics: Attributes: general: Severity: Warning + CA1870: + Metadata: + Category: Performance + Title: Use a cached 'SearchValues' instance + Description: Using a cached 'SearchValues' instance is more efficient than passing values to 'IndexOfAny'/'ContainsAny' directly. + HelpLinkUri: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 + CustomTags: + - Telemetry + - EnabledRuleInAggressiveMode + DefaultSeverity: Suggestion + Tier: 1 + Attributes: + general: + Severity: None + performance: + Severity: Warning diff --git a/eng/Diags/Microsoft.CodeAnalysis.CodeStyle.yml b/eng/Diags/Microsoft.CodeAnalysis.CodeStyle.yml index 656ea970d10..5a7aafdcf79 100644 --- a/eng/Diags/Microsoft.CodeAnalysis.CodeStyle.yml +++ b/eng/Diags/Microsoft.CodeAnalysis.CodeStyle.yml @@ -1,6 +1,6 @@ Origin: AssemblyName: Microsoft.CodeAnalysis.CodeStyle - Version: 4.8.8.35803 + Version: 4.8.9.404 Diagnostics: IDE0033: Metadata: diff --git a/eng/Diags/Microsoft.CodeAnalysis.NetAnalyzers.yml b/eng/Diags/Microsoft.CodeAnalysis.NetAnalyzers.yml index 59edace3dd4..8a8c6e81b2b 100644 --- a/eng/Diags/Microsoft.CodeAnalysis.NetAnalyzers.yml +++ b/eng/Diags/Microsoft.CodeAnalysis.NetAnalyzers.yml @@ -1,6 +1,6 @@ Origin: AssemblyName: Microsoft.CodeAnalysis.NetAnalyzers - Version: 8.0.8.35701 + Version: 8.0.8.47201 Diagnostics: CA1000: Metadata: @@ -4514,8 +4514,8 @@ Diagnostics: CA1862: Metadata: Category: Performance - Title: Prefer using 'StringComparer' to perform case-insensitive string comparisons - Description: Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons when using 'CompareTo', because they lead to an allocation. Instead, use 'StringComparer' to perform case-insensitive comparisons. + Title: Use the 'StringComparison' method overloads to perform case-insensitive string comparisons + Description: Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. Switching to using an overload that takes a 'StringComparison' might cause subtle changes in behavior, so it's important to conduct thorough testing after applying the suggestion. Additionally, if a culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase'. HelpLinkUri: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 CustomTags: - Telemetry @@ -4558,3 +4558,19 @@ Diagnostics: Attributes: general: Severity: Warning + CA1869: + Metadata: + Category: Performance + Title: Cache and reuse 'JsonSerializerOptions' instances + Description: Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. + HelpLinkUri: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 + CustomTags: + - Telemetry + - EnabledRuleInAggressiveMode + DefaultSeverity: Suggestion + Tier: 1 + Attributes: + general: + Severity: None + performance: + Severity: Warning diff --git a/eng/MSBuild/Generators.props b/eng/MSBuild/Generators.props index 433353e8a1b..2a99ec02f95 100644 --- a/eng/MSBuild/Generators.props +++ b/eng/MSBuild/Generators.props @@ -3,10 +3,6 @@ - - - - diff --git a/eng/MSBuild/ProjectStaging.targets b/eng/MSBuild/ProjectStaging.targets index af5baa16a8e..32f3920db29 100644 --- a/eng/MSBuild/ProjectStaging.targets +++ b/eng/MSBuild/ProjectStaging.targets @@ -1,12 +1,13 @@ - + + @@ -17,8 +18,8 @@ - DEVELOPMENT BUILD - DO NOT USE IN PRODUCTION - $(Description) - OBSOLETE PACKAGE - $(Description) + Experimental package. $(Description) + Obsolete Package. $(Description) diff --git a/eng/Tools/.editorconfig b/eng/Tools/.editorconfig index d4e6af326f8..cf14cfec647 100644 --- a/eng/Tools/.editorconfig +++ b/eng/Tools/.editorconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:54Z +# Generated : 2023-10-22 00:37:48Z # Max Tier : 2147483647 # Attributes: general # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CodeStyle, Microsoft.CodeAnalysis.CSharp.CodeStyle, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp, StyleCop.Analyzers @@ -866,7 +866,7 @@ dotnet_diagnostic.CA1860.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = none @@ -901,6 +901,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = suggestion +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = none + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = none + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1718,62 +1728,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = none # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = none # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = none # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = none # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = none # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = none # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = none # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = none # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Set MSBuild property 'GenerateDocumentationFile' to 'true' @@ -2406,6 +2431,26 @@ dotnet_diagnostic.IDE0300.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0301 dotnet_diagnostic.IDE0301.severity = suggestion +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0302 +dotnet_diagnostic.IDE0302.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0303 +dotnet_diagnostic.IDE0303.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0304 +dotnet_diagnostic.IDE0304.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0305 +dotnet_diagnostic.IDE0305.severity = none + # Title : Delegate invocation can be simplified. # Category : Style # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1005 diff --git a/eng/Tools/ApiChief/Format/FormattingExtensions.cs b/eng/Tools/ApiChief/Format/FormattingExtensions.cs index 6a84afa9846..c7f973dd038 100644 --- a/eng/Tools/ApiChief/Format/FormattingExtensions.cs +++ b/eng/Tools/ApiChief/Format/FormattingExtensions.cs @@ -9,9 +9,9 @@ namespace ApiChief.Format; internal static class FormattingExtensions { - private static readonly HashSet _numberLiterals = new() { 'l', 'L', 'u', 'U', 'f', 'F', 'd', 'D', 'm', 'M' }; - private static readonly HashSet _secondCharInLiterals = new() { 'l', 'L', 'u', 'U' }; - private static readonly HashSet _possibleSpecialCharactersInANumber = new() { '.', 'x', 'X', 'b', 'B' }; + private static readonly HashSet _numberLiterals = ['l', 'L', 'u', 'U', 'f', 'F', 'd', 'D', 'm', 'M']; + private static readonly HashSet _secondCharInLiterals = ['l', 'L', 'u', 'U']; + private static readonly HashSet _possibleSpecialCharactersInANumber = ['.', 'x', 'X', 'b', 'B']; /// /// Ensures a single space between parameters. diff --git a/eng/Tools/DiagPublisher/DiagPublisher.csproj b/eng/Tools/DiagPublisher/DiagPublisher.csproj new file mode 100644 index 00000000000..0679bc045d5 --- /dev/null +++ b/eng/Tools/DiagPublisher/DiagPublisher.csproj @@ -0,0 +1,23 @@ + + + + Exe + $(LatestTargetFramework) + enable + enable + true + + + + + + + + + + + + + + + diff --git a/eng/Tools/DiagPublisher/Program.cs b/eng/Tools/DiagPublisher/Program.cs new file mode 100644 index 00000000000..f0be301b63e --- /dev/null +++ b/eng/Tools/DiagPublisher/Program.cs @@ -0,0 +1,156 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.Gen.AutoClient; +using Microsoft.Gen.ContextualOptions; +using Microsoft.Gen.Logging; +using Microsoft.Gen.Metrics; +using Microsoft.Shared.DiagnosticIds; + +namespace DiagPublisher; + +public class Program +{ + private static void Main(string[] args) + { + // Pre-read this type name to avoid + // error CS0433: The type 'DiagDescriptorsBase' exists in both 'Microsoft.Gen.AutoClient, ...' and 'Microsoft.Gen.ContextualOptions, ...' + string diagDescriptorsBaseName = typeof(Microsoft.Gen.Shared.DiagDescriptorsBase).FullName!; + + string extraAnalyzersDiagDescriptorsName = typeof(Microsoft.Extensions.ExtraAnalyzers.DiagDescriptors).FullName!; + + _ = typeof(AutoClientGenerator).FullName; + _ = typeof(ContextualOptionsGenerator).FullName; + _ = typeof(LoggingGenerator).FullName; + _ = typeof(MetricsGenerator).FullName; + _ = typeof(Microsoft.Extensions.ExtraAnalyzers.DiagDescriptors).FullName; + + Dictionary diagnosticIds = []; + + List candidateTypes = []; + foreach (Type type in GetCandidateTypes()) + { + if (type.BaseType is null) + { + continue; + } + + if (type.BaseType.FullName == diagDescriptorsBaseName) + { + // handle DiagDescriptorsBase implementations + foreach (DiagnosticDescriptor diagnosticDescriptors in GetDiagnosticDescriptors(type)) + { + if (!diagnosticIds.ContainsKey(diagnosticDescriptors.Category)) + { + StringBuilder sb = new(); + sb.AppendLine($"# {diagnosticDescriptors.Category}"); + sb.AppendLine(); + sb.AppendLine("| Diagnostic ID | Description |"); + sb.AppendLine("| :---------------- | :---------- |"); + + diagnosticIds[diagnosticDescriptors.Category] = sb; + } + + diagnosticIds[diagnosticDescriptors.Category].AppendLine($"| `{diagnosticDescriptors.Id}` | {diagnosticDescriptors.Title} |"); + } + + continue; + } + + if (type.FullName == extraAnalyzersDiagDescriptorsName) + { + // handle ExtraAnalyzers.DiagDescriptors + foreach (DiagnosticDescriptor diagnosticDescriptors in GetDiagnosticDescriptors(type)) + { + const string Category = "ExtraAnalyzers"; + + if (!diagnosticIds.ContainsKey(Category)) + { + StringBuilder sb = new(); + sb.AppendLine($"# {Category}"); + sb.AppendLine(); + sb.AppendLine("| Diagnostic ID | Category | Description |"); + sb.AppendLine("| :---------------- | :---------- | :---------- |"); + + diagnosticIds[Category] = sb; + } + + diagnosticIds[Category].AppendLine($"| `{diagnosticDescriptors.Id}` | {diagnosticDescriptors.Category} | {diagnosticDescriptors.Title} |"); + } + + continue; + } + } + + + foreach (string category in diagnosticIds.Keys.OrderBy(k => k)) + { + Console.WriteLine(diagnosticIds[category].ToString()); + Console.WriteLine(); + Console.WriteLine(); + } + + return; + + static IEnumerable GetCandidateTypes() + { + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies().Where(p => !p.IsDynamic)) + { + foreach (Type type in GetLoadableTypes(assembly)) + { + yield return type; + } + } + } + + static IEnumerable GetDiagnosticDescriptors(Type candidateType) + { + foreach (var member in candidateType.GetMembers()) + { + if (member is PropertyInfo propertyInfo && + propertyInfo.PropertyType.IsAssignableFrom(typeof(DiagnosticDescriptor))) + { + if (propertyInfo.GetGetMethod() is MethodInfo getMethodInfo && + getMethodInfo.IsStatic) + { + var descriptor = (DiagnosticDescriptor)propertyInfo.GetValue(null)!; + + if (descriptor.HelpLinkUri != string.Format(DiagnosticIds.UrlFormat, descriptor.Id)) + { + ReportError($"{candidateType.FullName}.{member.Name} {descriptor.Id}: {nameof(DiagnosticDescriptor.HelpLinkUri)} must start with {DiagnosticIds.UrlFormat}"); + } + + yield return descriptor; + } + else + { + ReportError($"{candidateType.FullName}.{member.Name} can't be queried"); + } + } + } + } + + static IEnumerable GetLoadableTypes(Assembly assembly) + { + try + { + return assembly.GetTypes(); + } + catch (ReflectionTypeLoadException e) + { + return e.Types.Where(t => t is not null)!; + } + } + + static void ReportError(string message) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Write("[ERROR]: "); + Console.ResetColor(); + Console.WriteLine(message); + } + } +} diff --git a/eng/Tools/DiagPublisher/README.md b/eng/Tools/DiagPublisher/README.md new file mode 100644 index 00000000000..caf971e7f02 --- /dev/null +++ b/eng/Tools/DiagPublisher/README.md @@ -0,0 +1 @@ +This tool is used to generate or update diagnostic IDs described in /docs/list-of-diagnostics.md. \ No newline at end of file diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index c6459339e73..f68eebe5b6d 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,182 +1,182 @@ - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/runtime - b20f704cc00f390e5560a137deb8f0e64e863e1d + 488a8a3521610422e8fbe22d5cc66127f3dce3dc - + https://github.com/dotnet/aspnetcore - 7ffeb436ad029d1e1012372b7bb345ad22770f09 + 815eb281ecad13eb69cf50516ac7f534b947f2b0 - + https://github.com/dotnet/aspnetcore - 7ffeb436ad029d1e1012372b7bb345ad22770f09 + 815eb281ecad13eb69cf50516ac7f534b947f2b0 - + https://github.com/dotnet/aspnetcore - 7ffeb436ad029d1e1012372b7bb345ad22770f09 + 815eb281ecad13eb69cf50516ac7f534b947f2b0 - + https://github.com/dotnet/aspnetcore - 7ffeb436ad029d1e1012372b7bb345ad22770f09 + 815eb281ecad13eb69cf50516ac7f534b947f2b0 - + https://github.com/dotnet/aspnetcore - 7ffeb436ad029d1e1012372b7bb345ad22770f09 + 815eb281ecad13eb69cf50516ac7f534b947f2b0 - + https://github.com/dotnet/aspnetcore - 7ffeb436ad029d1e1012372b7bb345ad22770f09 + 815eb281ecad13eb69cf50516ac7f534b947f2b0 - + https://github.com/dotnet/aspnetcore - 7ffeb436ad029d1e1012372b7bb345ad22770f09 + 815eb281ecad13eb69cf50516ac7f534b947f2b0 - + https://github.com/dotnet/aspnetcore - 7ffeb436ad029d1e1012372b7bb345ad22770f09 + 815eb281ecad13eb69cf50516ac7f534b947f2b0 - + https://github.com/dotnet/aspnetcore - 7ffeb436ad029d1e1012372b7bb345ad22770f09 + 815eb281ecad13eb69cf50516ac7f534b947f2b0 - + https://github.com/dotnet/arcade - e6be64c3e27aeb29f93f6aa751fad972e4ef2d52 + a57022b44f3ff23de925530ea1d27da9701aed57 - + https://github.com/dotnet/arcade - e6be64c3e27aeb29f93f6aa751fad972e4ef2d52 + a57022b44f3ff23de925530ea1d27da9701aed57 diff --git a/eng/Versions.props b/eng/Versions.props index 5267458fc3f..e4c916b058b 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -10,9 +10,9 @@ true $(MajorVersion).$(MinorVersion).0.0 - false + release true @@ -28,49 +28,49 @@ --> - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 - 8.0.0-rtm.23478.17 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 + 8.0.0 - 8.0.0-rtm.23510.7 - 8.0.0-rtm.23510.7 - 8.0.0-rtm.23510.7 - 8.0.0-rtm.23510.7 - 8.0.0-rtm.23510.7 - 8.0.0-rtm.23510.7 - 8.0.0-rtm.23510.7 - 8.0.0-rtm.23510.7 - 8.0.0-rtm.23510.7 + 8.0.0-rtm.23524.15 + 8.0.0-rtm.23524.15 + 8.0.0-rtm.23524.15 + 8.0.0-rtm.23524.15 + 8.0.0-rtm.23524.15 + 8.0.0-rtm.23524.15 + 8.0.0-rtm.23524.15 + 8.0.0-rtm.23524.15 + 8.0.0-rtm.23524.15 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - An API method must not contain more than one REST method attribute - - - An API method must not contain more than one REST method attribute - - - API client interfaces must not be nested types - - - API client interfaces must not be nested types - - - A Body is already defined for this method - - - Duplicate body attribute - - - A REST API method must not receive more than one cancellation token - - - REST API method has more than one cancellation token - - - The request name '{0}' is already in use within this REST API client. - - - A REST API method's request name must be unique - - - The API interface cannot be generic - - - The API interface cannot be generic - - - Invalid API interface name. It should start with an 'I' - - - Invalid API interface name - - - An unsupported character was used in the dependency name - - - Invalid dependency name - - - An unsupported character was used in the header name - - - Invalid header name - - - An unsupported character was used in the header value - - - Invalid header value - - - An unsupported character was used in the HttpClient name - - - Invalid HttpClient name - - - An unsupported character was used in the REST method path - - - Invalid REST method path - - - An unsupported character was used in the request name - - - Invalid request name - - - An API method return type must be of type Task<T>. T must not be nullable. - - - Invalid API method return type - - - API methods cannot be generic - - - API methods can't be generic - - - A REST API method must receive a CancellationToken parameter - - - Missing CancellationToken from REST API method - - - An HTTP method is missing for this API method - - - HTTP method missing - - - The parameter '{0}' is missing in the URL - - - URL parameter missing from path - - - An API method path must not contain '?'. Queries should be defined using the [Query] attribute. - - - API method path should not contain query - - - API methods must not be static - - - API methods must not be static - - - The '{0}' HTTP method does not support the body tag - - - The current HTTP method does not support the body tag - - - REST API client does not have methods defined. This will render the client class useless. - - - REST API client does not have methods defined - - \ No newline at end of file diff --git a/src/Generators/Microsoft.Gen.AutoClient/SymbolHolder.cs b/src/Generators/Microsoft.Gen.AutoClient/SymbolHolder.cs deleted file mode 100644 index 5018c12ce85..00000000000 --- a/src/Generators/Microsoft.Gen.AutoClient/SymbolHolder.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.CodeAnalysis; -using Microsoft.CodeAnalysis; - -namespace Microsoft.Gen.AutoClient; - -[ExcludeFromCodeCoverage] -internal sealed record class SymbolHolder( - INamedTypeSymbol RestApiAttribute, - INamedTypeSymbol? RestGetAttribute, - INamedTypeSymbol? RestPostAttribute, - INamedTypeSymbol? RestPutAttribute, - INamedTypeSymbol? RestDeleteAttribute, - INamedTypeSymbol? RestPatchAttribute, - INamedTypeSymbol? RestOptionsAttribute, - INamedTypeSymbol? RestHeadAttribute, - INamedTypeSymbol? RestStaticHeaderAttribute, - INamedTypeSymbol? RestHeaderAttribute, - INamedTypeSymbol? RestQueryAttribute, - INamedTypeSymbol? RestBodyAttribute); diff --git a/src/Generators/Microsoft.Gen.AutoClient/SymbolLoader.cs b/src/Generators/Microsoft.Gen.AutoClient/SymbolLoader.cs deleted file mode 100644 index 54ef7ab201e..00000000000 --- a/src/Generators/Microsoft.Gen.AutoClient/SymbolLoader.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.CodeAnalysis; - -namespace Microsoft.Gen.AutoClient; - -internal static class SymbolLoader -{ - internal const string RestApiAttribute = "Microsoft.Extensions.Http.AutoClient.AutoClientAttribute"; - - internal const string RestGetAttribute = "Microsoft.Extensions.Http.AutoClient.GetAttribute"; - internal const string RestPostAttribute = "Microsoft.Extensions.Http.AutoClient.PostAttribute"; - internal const string RestPutAttribute = "Microsoft.Extensions.Http.AutoClient.PutAttribute"; - internal const string RestDeleteAttribute = "Microsoft.Extensions.Http.AutoClient.DeleteAttribute"; - internal const string RestPatchAttribute = "Microsoft.Extensions.Http.AutoClient.PatchAttribute"; - internal const string RestOptionsAttribute = "Microsoft.Extensions.Http.AutoClient.OptionsAttribute"; - internal const string RestHeadAttribute = "Microsoft.Extensions.Http.AutoClient.HeadAttribute"; - - internal const string RestStaticHeaderAttribute = "Microsoft.Extensions.Http.AutoClient.StaticHeaderAttribute"; - internal const string RestHeaderAttribute = "Microsoft.Extensions.Http.AutoClient.HeaderAttribute"; - internal const string RestQueryAttribute = "Microsoft.Extensions.Http.AutoClient.QueryAttribute"; - internal const string RestBodyAttribute = "Microsoft.Extensions.Http.AutoClient.BodyAttribute"; - - internal static SymbolHolder? LoadSymbols(Compilation compilation) - { - var restApiAttribute = compilation.GetTypeByMetadataName(RestApiAttribute); - - var restGetAttribute = compilation.GetTypeByMetadataName(RestGetAttribute); - var restPostAttribute = compilation.GetTypeByMetadataName(RestPostAttribute); - var restPutAttribute = compilation.GetTypeByMetadataName(RestPutAttribute); - var restDeleteAttribute = compilation.GetTypeByMetadataName(RestDeleteAttribute); - var restPatchAttribute = compilation.GetTypeByMetadataName(RestPatchAttribute); - var restOptionsAttribute = compilation.GetTypeByMetadataName(RestOptionsAttribute); - var restHeadAttribute = compilation.GetTypeByMetadataName(RestHeadAttribute); - - var restStaticHeaderAttribute = compilation.GetTypeByMetadataName(RestStaticHeaderAttribute); - var restHeaderAttribute = compilation.GetTypeByMetadataName(RestHeaderAttribute); - var restQueryAttribute = compilation.GetTypeByMetadataName(RestQueryAttribute); - var restBodyAttribute = compilation.GetTypeByMetadataName(RestBodyAttribute); - - if (restApiAttribute == null) - { - // nothing to do if these types aren't available - return null; - } - - return new( - restApiAttribute, - restGetAttribute, - restPostAttribute, - restPutAttribute, - restDeleteAttribute, - restPatchAttribute, - restOptionsAttribute, - restHeadAttribute, - restStaticHeaderAttribute, - restHeaderAttribute, - restQueryAttribute, - restBodyAttribute); - } -} diff --git a/src/Generators/Microsoft.Gen.ContextualOptions/DiagDescriptors.cs b/src/Generators/Microsoft.Gen.ContextualOptions/DiagDescriptors.cs index d4ebf8d2095..841b3b4a3f4 100644 --- a/src/Generators/Microsoft.Gen.ContextualOptions/DiagDescriptors.cs +++ b/src/Generators/Microsoft.Gen.ContextualOptions/DiagDescriptors.cs @@ -3,34 +3,35 @@ using Microsoft.CodeAnalysis; using Microsoft.Gen.Shared; +using Microsoft.Shared.DiagnosticIds; namespace Microsoft.Gen.ContextualOptions; internal sealed class DiagDescriptors : DiagDescriptorsBase { - private const string Category = "ContextualOptions"; + private const string Category = nameof(DiagnosticIds.ContextualOptions); public static DiagnosticDescriptor ContextCannotBeStatic { get; } = Make( - id: "CTXOPTGEN000", + id: DiagnosticIds.ContextualOptions.CTXOPTGEN000, title: Resources.ContextCannotBeStaticTitle, messageFormat: Resources.ContextCannotBeStaticMessage, category: Category); public static DiagnosticDescriptor ContextMustBePartial { get; } = Make( - id: "CTXOPTGEN001", + id: DiagnosticIds.ContextualOptions.CTXOPTGEN001, title: Resources.ContextMustBePartialTitle, messageFormat: Resources.ContextMustBePartialMessage, category: Category); public static DiagnosticDescriptor ContextDoesNotHaveValidProperties { get; } = Make( - id: "CTXOPTGEN002", + id: DiagnosticIds.ContextualOptions.CTXOPTGEN002, title: Resources.ContextDoesNotHaveValidPropertiesTitle, messageFormat: Resources.ContextDoesNotHaveValidPropertiesMessage, category: Category, defaultSeverity: DiagnosticSeverity.Warning); public static DiagnosticDescriptor ContextCannotBeRefLike { get; } = Make( - id: "CTXOPTGEN003", + id: DiagnosticIds.ContextualOptions.CTXOPTGEN003, title: Resources.ContextCannotBeRefLikeTitle, messageFormat: Resources.ContextCannotBeRefLikeMessage, category: Category); diff --git a/src/Generators/Microsoft.Gen.EnumStrings/DiagDescriptors.cs b/src/Generators/Microsoft.Gen.EnumStrings/DiagDescriptors.cs deleted file mode 100644 index 9d4c5d7db9d..00000000000 --- a/src/Generators/Microsoft.Gen.EnumStrings/DiagDescriptors.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.CodeAnalysis; -using Microsoft.Gen.Shared; - -namespace Microsoft.Gen.EnumStrings; - -internal sealed class DiagDescriptors : DiagDescriptorsBase -{ - private const string Category = "EnumStrings"; - - public static DiagnosticDescriptor InvalidExtensionNamespace { get; } = Make( - id: "ENUMSTRGEN000", - title: Resources.InvalidExtensionNamespaceTitle, - messageFormat: Resources.InvalidExtensionNamespaceMessage, - category: Category); - - public static DiagnosticDescriptor IncorrectOverload { get; } = Make( - id: "ENUMSTRGEN001", - title: Resources.IncorrectOverloadTitle, - messageFormat: Resources.IncorrectOverloadMessage, - category: Category); - - public static DiagnosticDescriptor InvalidExtensionClassName { get; } = Make( - id: "ENUMSTRGEN002", - title: Resources.InvalidExtensionClassNameTitle, - messageFormat: Resources.InvalidExtensionClassNameMessage, - category: Category); - - public static DiagnosticDescriptor InvalidExtensionMethodName { get; } = Make( - id: "ENUMSTRGEN003", - title: Resources.InvalidExtensionMethodNameTitle, - messageFormat: Resources.InvalidExtensionMethodNameMessage, - category: Category); - - public static DiagnosticDescriptor InvalidEnumType { get; } = Make( - id: "ENUMSTRGEN004", - title: Resources.InvalidEnumTypeTitle, - messageFormat: Resources.InvalidEnumTypeMessage, - category: Category); -} diff --git a/src/Generators/Microsoft.Gen.EnumStrings/Emitter.cs b/src/Generators/Microsoft.Gen.EnumStrings/Emitter.cs deleted file mode 100644 index 34b51de7676..00000000000 --- a/src/Generators/Microsoft.Gen.EnumStrings/Emitter.cs +++ /dev/null @@ -1,474 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using Microsoft.Gen.EnumStrings.Model; -using Microsoft.Gen.Shared; - -namespace Microsoft.Gen.EnumStrings; - -#pragma warning disable LA0002 // Use 'Microsoft.Extensions.Text.NumericExtensions.ToInvariantString' for improved performance -#pragma warning disable S109 // Magic numbers should not be used - -// Stryker disable all - -/// -/// Emits fast ToInvariantString extension methods for enums. -/// -/// -/// The generated code uses different strategies depending on the shape of the enum, and depending on what -/// symbols are available at compile time. -/// -/// * If an enum has 1 or 2 entries, the lookup is done with explicit "if" statements. -/// -/// * If an enum has mostly contiguous values (a common case), then the lookup is done via an array -/// -/// * If an enum has a set of discontiguous values, then the lookup is done via a dictionary. This will be a frozen dictionary if the -/// frozen collections are available at compile time, otherwise a classic dictionary. -/// -/// In all cases, if the initial lookup fails, then we lookup in a static concurrent dictionary as a cache of values from -/// the original Enum.ToString(). -/// -internal sealed class Emitter : EmitterBase -{ - // max # entries we keep in the concurrent dictionary - private const int MaxCacheEntries = 256; - - // flags and sparse arrays can grow to this size no questions asked - private const int ArrayLookupThreshold = 1024; - - // sparse arrays can get bigger than the threshold only if they don't exceed this percentage of sparseness. - private const int MaxSparsePercent = 25; - - public string Emit( - IEnumerable toStringMethods, - bool frozenDictionaryAvailable, - CancellationToken cancellationToken) - { - foreach (var tsm in toStringMethods.OrderBy(static t => t.ExtensionNamespace + "." + t.ExtensionClass)) - { - cancellationToken.ThrowIfCancellationRequested(); - GenExtension(tsm, frozenDictionaryAvailable); - } - - return Capture(); - } - - // This code was stolen from .NET 6's implementation of Enum.ToString, and adapted for the circumstances - private static string FlagsName(List names, List enumMemberValues, ulong valueToFormat) - { - ulong originalValueToFormat = valueToFormat; - - // Values are sorted, so if the incoming value is 0, we can check to see whether - // the first entry matches it, in which case we can return its name; otherwise, - // we can just return "0". - if (valueToFormat == 0) - { - return enumMemberValues.Count > 0 && enumMemberValues[0] == 0 ? names[0] : "0"; - } - - // With a ulong result value, regardless of the enum's base type, the maximum - // possible number of consistent name/values we could have is 64, since every - // value is made up of one or more bits, and when we see values and incorporate - // their names, we effectively switch off those bits. - Span foundItems = stackalloc int[64]; - - // Walk from largest to smallest. It's common to have a flags enum with a single - // value that matches a single entry, in which case we can just return the existing - // name string. - int index = enumMemberValues.Count - 1; - while (index >= 0) - { - if (enumMemberValues[index] == valueToFormat) - { - return names[index]; - } - - if (enumMemberValues[index] < valueToFormat) - { - break; - } - - index--; - } - - // Now look for multiple matches, storing the indices of the values - // into our span. - int resultLength = 0; - int foundItemsCount = 0; - while (index >= 0) - { - ulong currentValue = enumMemberValues[index]; - if (index == 0 && currentValue == 0) - { - break; - } - - if ((valueToFormat & currentValue) == currentValue) - { - valueToFormat -= currentValue; - foundItems[foundItemsCount++] = index; - resultLength += names[index].Length; - } - - index--; - } - - // If we exhausted looking through all the values and we still have - // a non-zero result, we couldn't match the result to only named values. - // In that case, we return null and let the call site just generate - // a string for the integral value. - if (valueToFormat != 0) - { - return originalValueToFormat.ToString(CultureInfo.InvariantCulture); - } - - // We know what strings to concatenate. Do so. - - const int SeparatorStringLength = 2; // ", " - resultLength += SeparatorStringLength * (foundItemsCount - 1); - char[] result = new char[resultLength]; - - Span resultSpan = result.AsSpan(); - string name = names[foundItems[--foundItemsCount]]; - for (int i = 0; i < name.Length; i++) - { - resultSpan[i] = name[i]; - } - - resultSpan = resultSpan.Slice(name.Length); - while (--foundItemsCount >= 0) - { - resultSpan[0] = ','; - resultSpan[1] = ' '; - resultSpan = resultSpan.Slice(SeparatorStringLength); - - name = names[foundItems[foundItemsCount]]; - for (int i = 0; i < name.Length; i++) - { - resultSpan[i] = name[i]; - } - - resultSpan = resultSpan.Slice(name.Length); - } - - return new string(result); - } - - private static bool IsBigEnum(ToStringMethod tsm) => tsm.UnderlyingType is "ulong" or "long"; - - private void GenExtension(ToStringMethod tsm, bool frozenDictionaryAvailable) - { - if (tsm.ExtensionNamespace.Length > 0) - { - OutLn($"namespace {tsm.ExtensionNamespace}"); - OutOpenBrace(); - } - - OutLn(); - OutLn($"/// "); - OutLn($"/// Extension methods for the enum."); - OutLn($"/// "); - OutLn($"{tsm.ExtensionClassModifiers} class {tsm.ExtensionClass}"); - OutOpenBrace(); - - var names = tsm.MemberNames; - var values = tsm.MemberValues; - var lookupType = PickLookupType(tsm, out var flagRange, values); - var fieldPrefix = "__" + tsm.ExtensionMethod + "_"; - - GenFields(); - GenMethod(); - - OutCloseBrace(); - - if (tsm.ExtensionNamespace.Length > 0) - { - OutCloseBrace(); - } - - static LookupType PickLookupType(ToStringMethod tsm, out ulong flagRange, List values) - { - flagRange = 0; - var lookupType = LookupType.Nothing; - - if (tsm.FlagsEnum) - { - foreach (var v in values) - { - flagRange |= v; - } - - if (values.Count == 1) - { - lookupType = LookupType.Conditionals; - } - else if (flagRange < ArrayLookupThreshold) - { - lookupType = LookupType.Array; - } - else - { - lookupType = LookupType.Dictionary; - } - } - else - { - if (values.Count < 3) - { - lookupType = LookupType.Conditionals; - } - else - { - var delta = values[values.Count - 1] - values[0] + 1; - if (delta == (ulong)values.Count) - { - lookupType = LookupType.Array; - } - else if (values[values.Count - 1] < ArrayLookupThreshold) - { - lookupType = LookupType.Array; - } - else - { - lookupType = LookupType.Array; - - var numEmptySlots = delta - (ulong)values.Count; - var percenEmptySlots = (numEmptySlots * 100) / (ulong)values.Count; - if (percenEmptySlots > MaxSparsePercent) - { - lookupType = LookupType.Dictionary; - } - } - } - } - - return lookupType; - } - - void GenMethod() - { - OutLn($"/// "); - OutLn($"/// Efficiently returns a string representation for a value of the enum."); - OutLn($"/// "); - OutLn($"/// The value to use."); - OutLn($"/// A string representation of the value, equivalent to what ToString would return."); - OutLn($"/// This function is equivalent to calling ToString on an enum's value, except that it is considerably faster."); - OutGeneratedCodeAttribute(); - OutLn($"public static string {tsm.ExtensionMethod}(this {tsm.EnumTypeName} value)"); - OutOpenBrace(); - - var valueType = IsBigEnum(tsm) ? "ulong" : "uint"; - var valueText = IsBigEnum(tsm) ? "v" : "(int)v"; - - OutLn($"var v = ({valueType})value;"); - - switch (lookupType) - { - case LookupType.Conditionals: - { - for (int i = 0; i < values.Count; i++) - { - var e = (i > 0) ? "else " : string.Empty; - OutLn($"{e}if (v == {GetLiteral(values[i])})"); - OutOpenBrace(); - OutLn($"return \"{names[i]}\";"); - OutCloseBrace(); - } - - break; - } - - case LookupType.Array: - { - if (tsm.FlagsEnum) - { - OutLn($"if (v <= {flagRange})"); - OutOpenBrace(); - OutLn($"return {fieldPrefix}LookupArray[v];"); - OutCloseBrace(); - } - else - { - var upper = GetLiteral(values[values.Count - 1]); - if (values[0] > 0) - { - var lower = GetLiteral(values[0]); - OutLn($"if (v >= {lower} && v <= {upper})"); - OutOpenBrace(); - OutLn($"return {fieldPrefix}LookupArray[v - {lower}];"); - } - else - { - if (IsBigEnum(tsm)) - { - OutLn($"if (v <= {upper})"); - } - else - { - OutLn($"if (v < {fieldPrefix}LookupArray.Length)"); - } - - OutOpenBrace(); - OutLn($"return {fieldPrefix}LookupArray[v];"); - } - - OutCloseBrace(); - } - - break; - } - - case LookupType.Dictionary: - { - OutLn($"if ({fieldPrefix}LookupDictionary.TryGetValue({valueText}, out var lookupResult))"); - OutOpenBrace(); - OutLn($"return lookupResult;"); - OutCloseBrace(); - break; - } - } - - OutLn(); - OutLn($"{fieldPrefix}CacheDictionary ??= new();"); - OutLn($"if ({fieldPrefix}CacheDictionary.TryGetValue({valueText}, out var cachedResult))"); - OutOpenBrace(); - OutLn("return cachedResult;"); - OutCloseBrace(); - - OutLn(); - OutLn($"var result = value.ToString();"); - - OutLn(); - OutLn($"if ({fieldPrefix}ApproximateCacheCount < {MaxCacheEntries})"); - OutOpenBrace(); - OutLn($"_ = global::System.Threading.Interlocked.Increment(ref {fieldPrefix}ApproximateCacheCount);"); - OutLn($"{fieldPrefix}CacheDictionary[{valueText}] = result;"); - OutCloseBrace(); - - OutLn(); - OutLn($"return result;"); - - OutCloseBrace(); - - string GetLiteral(ulong value) => IsBigEnum(tsm) ? value.ToString(CultureInfo.InvariantCulture) + "UL" : ((uint)value).ToString(CultureInfo.InvariantCulture) + "U"; - } - - void GenFields() - { - if (lookupType == LookupType.Array) - { - OutGeneratedCodeAttribute(); - OutLn($"private static readonly string[] {fieldPrefix}LookupArray = new string[]"); - OutOpenBrace(); - - if (tsm.FlagsEnum) - { - for (ulong i = 0; i <= flagRange; i++) - { - OutLn($"\"{FlagsName(names, values, i)}\","); - } - } - else - { - OutLn($"\"{names[0]}\","); - - ulong previous = values[0]; - for (int i = 1; i < values.Count; i++) - { - while (previous < values[i] - 1) - { - previous++; - OutLn($"\"{previous.ToString(CultureInfo.InvariantCulture)}\","); - } - - OutLn($"\"{names[i]}\","); - previous = values[i]; - } - } - - OutCloseBraceWithExtra(";"); - } - else if (lookupType == LookupType.Dictionary) - { - OutGeneratedCodeAttribute(); - - var isBigEnum = IsBigEnum(tsm); - -#pragma warning disable S3358 // Ternary operators should not be nested -#pragma warning disable S103 // Lines should not be too long - var decl = frozenDictionaryAvailable - ? isBigEnum - ? $"private static readonly global::System.Collections.Frozen.FrozenDictionary {fieldPrefix}LookupDictionary = global::System.Collections.Frozen.FrozenDictionary.ToFrozenDictionary(new global::System.Collections.Generic.Dictionary({values.Count})" - : $"private static readonly global::System.Collections.Frozen.FrozenDictionary {fieldPrefix}LookupDictionary = global::System.Collections.Frozen.FrozenDictionary.ToFrozenDictionary(new global::System.Collections.Generic.Dictionary({values.Count})" - : isBigEnum - ? $"private static readonly global::System.Collections.Generic.Dictionary {fieldPrefix}LookupDictionary = new({values.Count})" - : $"private static readonly global::System.Collections.Generic.Dictionary {fieldPrefix}LookupDictionary = new({values.Count})"; -#pragma warning restore S103 // Lines should not be too long -#pragma warning restore S3358 // Ternary operators should not be nested - - OutLn(decl); - OutOpenBrace(); - - if (tsm.FlagsEnum) - { - for (int i = 0; i < ArrayLookupThreshold; i++) - { - OutLn($"{{ {i.ToString(CultureInfo.InvariantCulture)}, \"{FlagsName(names, values, (ulong)i)}\" }},"); - } - - for (int i = 0; i < values.Count; i++) - { - if (values[i] >= ArrayLookupThreshold) - { - if (isBigEnum) - { - OutLn($"{{ {values[i].ToString(CultureInfo.InvariantCulture)}, \"{names[i]}\" }},"); - } - else - { - OutLn($"{{ unchecked((int){(values[i] & 0xffffffff).ToString(CultureInfo.InvariantCulture)}), \"{names[i]}\" }},"); - } - } - } - } - else - { - for (int i = 0; i < values.Count; i++) - { - if (isBigEnum) - { - OutLn($"{{ {values[i].ToString(CultureInfo.InvariantCulture)}, \"{names[i]}\" }},"); - } - else - { - OutLn($"{{ unchecked((int){(values[i] & 0xffffffff).ToString(CultureInfo.InvariantCulture)}), \"{names[i]}\" }},"); - } - } - } - - OutCloseBraceWithExtra(frozenDictionaryAvailable ? ");" : ";"); - } - - var keyType = IsBigEnum(tsm) ? "ulong" : "int"; - - OutLn(); - OutGeneratedCodeAttribute(); - OutLn($"private static global::System.Collections.Concurrent.ConcurrentDictionary<{keyType}, string>? {fieldPrefix}CacheDictionary;"); - OutLn($"private static volatile int {fieldPrefix}ApproximateCacheCount;"); - OutLn(); - } - } - - private enum LookupType - { - Nothing, - Conditionals, - Array, - Dictionary, - } -} diff --git a/src/Generators/Microsoft.Gen.EnumStrings/EnumStringsGenerator.cs b/src/Generators/Microsoft.Gen.EnumStrings/EnumStringsGenerator.cs deleted file mode 100644 index 8664fcc56fd..00000000000 --- a/src/Generators/Microsoft.Gen.EnumStrings/EnumStringsGenerator.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Immutable; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.Gen.EnumStrings; - -[Generator] -public class EnumStringsGenerator : IIncrementalGenerator -{ - public void Initialize(IncrementalGeneratorInitializationContext context) - { - IncrementalValuesProvider typeDeclarations = context.SyntaxProvider - .ForAttributeWithMetadataName( - SymbolLoader.EnumStringsAttribute, - (_, _) => true, - (context, _) => context.TargetNode); - - IncrementalValueProvider<(Compilation, ImmutableArray)> compilationAndTypes = - context.CompilationProvider.Combine(typeDeclarations.Collect()); - - context.RegisterSourceOutput(compilationAndTypes, static (spc, source) => HandleAnnotatedNodes(source.Item1, source.Item2, spc)); - } - - private static void HandleAnnotatedNodes(Compilation compilation, ImmutableArray nodes, SourceProductionContext context) - { - SymbolLoader.Load(compilation, out var symbolHolder); - - var parser = new Parser(compilation, context.ReportDiagnostic, symbolHolder!, context.CancellationToken); - - var toStringMethods = parser.GetToStringMethods(nodes); - if (toStringMethods.Count > 0) - { - var emitter = new Emitter(); - var result = emitter.Emit(toStringMethods, symbolHolder!.FreezerSymbol != null, context.CancellationToken); - - context.AddSource("EnumStrings.g.cs", SourceText.From(result, Encoding.UTF8)); - } - } -} diff --git a/src/Generators/Microsoft.Gen.EnumStrings/Microsoft.Gen.EnumStrings.csproj b/src/Generators/Microsoft.Gen.EnumStrings/Microsoft.Gen.EnumStrings.csproj deleted file mode 100644 index 8aa639c6c76..00000000000 --- a/src/Generators/Microsoft.Gen.EnumStrings/Microsoft.Gen.EnumStrings.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - Microsoft.Gen.EnumStrings - Code generator to support Microsoft.Extensions.EnumStrings. - Fundamentals - - - - cs - true - - - - normal - 100 - 85 - - - - - - - - - - - - - - - - - - - - diff --git a/src/Generators/Microsoft.Gen.EnumStrings/Model/ToStringMethod.cs b/src/Generators/Microsoft.Gen.EnumStrings/Model/ToStringMethod.cs deleted file mode 100644 index 870c080440c..00000000000 --- a/src/Generators/Microsoft.Gen.EnumStrings/Model/ToStringMethod.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace Microsoft.Gen.EnumStrings.Model; - -[ExcludeFromCodeCoverage] -internal sealed record class ToStringMethod( - string EnumTypeName, - List MemberNames, - List MemberValues, - bool FlagsEnum, - string ExtensionNamespace, - string ExtensionClass, - string ExtensionMethod, - string ExtensionClassModifiers, - string UnderlyingType); diff --git a/src/Generators/Microsoft.Gen.EnumStrings/Parser.cs b/src/Generators/Microsoft.Gen.EnumStrings/Parser.cs deleted file mode 100644 index 3e5bb4c4547..00000000000 --- a/src/Generators/Microsoft.Gen.EnumStrings/Parser.cs +++ /dev/null @@ -1,249 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Globalization; -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.Gen.EnumStrings.Model; - -namespace Microsoft.Gen.EnumStrings; - -/// -/// Holds an internal parser class that extracts necessary information for generating IValidateOptions. -/// -internal sealed class Parser -{ - private readonly CancellationToken _cancellationToken; - private readonly Compilation _compilation; - private readonly Action _reportDiagnostic; - private readonly SymbolHolder _symbolHolder; - - public Parser( - Compilation compilation, - Action reportDiagnostic, - SymbolHolder symbolHolder, - CancellationToken cancellationToken) - { - _compilation = compilation; - _cancellationToken = cancellationToken; - _reportDiagnostic = reportDiagnostic; - _symbolHolder = symbolHolder; - } - - public IReadOnlyList GetToStringMethods(IEnumerable nodes) - { - var results = new List(); - - foreach (var group in nodes.GroupBy(x => x.SyntaxTree)) - { - SemanticModel? sm = null; - foreach (var node in group) - { - _cancellationToken.ThrowIfCancellationRequested(); - sm ??= _compilation.GetSemanticModel(node.SyntaxTree); - - if (node.IsKind(SyntaxKind.EnumDeclaration)) - { - // enum-level attribute usage - var enumDecl = (EnumDeclarationSyntax)node; - var enumSym = sm.GetDeclaredSymbol(node) as INamedTypeSymbol; - if (enumSym != null) - { - ParseAttributeList( - enumSym, - enumSym!.GetAttributes(), - results); - } - } - else if (node.IsKind(SyntaxKind.CompilationUnit)) - { - // assembly-level attribute usage - var compUnitDecl = (CompilationUnitSyntax)node; - ParseAttributeList( - null, - sm.Compilation.Assembly.GetAttributes().Where(ad => ad.ApplicationSyntaxReference!.SyntaxTree == node.SyntaxTree), - results); - } - } - } - - return results; - } - - private static (INamedTypeSymbol? explicitEnumType, string? nspace, string? className, string? methodName, string? classModifiers) - ExtractAttributeValues(AttributeData args) - { - INamedTypeSymbol? explicitEnumType = null; - string? nspace = null; - string? className = null; - string? methodName = null; - string? classModifiers = null; - - // Two constructor shapes: - // - // () - // (Type enumType) - if (args.ConstructorArguments.Length > 0) - { - explicitEnumType = args.ConstructorArguments[0].Value as INamedTypeSymbol; - } - - foreach (var a in args.NamedArguments) - { - switch (a.Key) - { - case "ExtensionClassModifiers": - classModifiers = a.Value.Value as string; - break; - - case "ExtensionNamespace": - nspace = a.Value.Value as string; - break; - - case "ExtensionClassName": - className = a.Value.Value as string; - break; - - case "ExtensionMethodName": - methodName = a.Value.Value as string; - break; - } - } - - return (explicitEnumType, nspace, className, methodName, classModifiers); - } - - private static ulong ConvertValue(object obj) => - obj switch - { - sbyte or short or int or long => (ulong)Convert.ToInt64(obj, CultureInfo.InvariantCulture), - _ => Convert.ToUInt64(obj, CultureInfo.InvariantCulture), - }; - - private static bool IsValidNamespace(string nspace) - { - var source = $"namespace {nspace} {{ }}"; - var st = CSharpSyntaxTree.ParseText(source); - return !st.GetDiagnostics().Any(); - } - - private static bool IsValidClassName(string className) - { - var source = $"class {className} {{ }}"; - var st = CSharpSyntaxTree.ParseText(source); - return !st.GetDiagnostics().Any(); - } - - private static bool IsValidMethodName(string methodName) - { - var source = $"class ___XYZ {{ public void {methodName}() {{ }} }}"; - var st = CSharpSyntaxTree.ParseText(source); - return !(st.GetDiagnostics().Any() || methodName.Contains('.')); - } - - private void ParseAttributeList(INamedTypeSymbol? implicitEnumType, IEnumerable attrDataList, List results) - { - foreach (var ad in attrDataList) - { - if (SymbolEqualityComparer.Default.Equals(ad.AttributeClass, _symbolHolder.EnumStringsAttributeSymbol)) - { - var attrSyntax = ad.ApplicationSyntaxReference?.GetSyntax(_cancellationToken) as AttributeSyntax; - - if (attrSyntax != null) - { - var (explicitEnumType, nspace, className, methodName, classModifiers) = ExtractAttributeValues(ad); - - if (nspace != null && !IsValidNamespace(nspace)) - { - Diag(DiagDescriptors.InvalidExtensionNamespace, attrSyntax.GetLocation(), nspace); - } - - if (className != null && !IsValidClassName(className)) - { - Diag(DiagDescriptors.InvalidExtensionClassName, attrSyntax.GetLocation(), className); - } - - if (methodName != null && !IsValidMethodName(methodName)) - { - Diag(DiagDescriptors.InvalidExtensionMethodName, attrSyntax.GetLocation(), methodName); - } - - var enumType = implicitEnumType ?? explicitEnumType; - - if ((implicitEnumType != null && explicitEnumType != null) || enumType == null) - { - // must have one and only one enum type - Diag(DiagDescriptors.IncorrectOverload, attrSyntax.GetLocation()); - return; - } - - if (enumType.TypeKind != TypeKind.Enum) - { - Diag(DiagDescriptors.InvalidEnumType, attrSyntax.GetLocation(), enumType); - return; - } - - var flags = enumType.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, _symbolHolder.FlagsAttributeSymbol)); - - // get a sorted list of enum members - IEnumerable> members = enumType - .GetMembers() - .OfType() - .Where(f => f.IsConst) - .Select(f => new KeyValuePair(f.Name, ConvertValue(f.ConstantValue!))) - .OrderBy(kvp => kvp.Value); - - if (flags) - { - members = members - .Reverse() // flip it so Distinct keeps the last instance of duplicates instead of the first to match what Enum.ToString does - .Distinct(new EntryComparer()) - .Reverse(); // flip it back to natural order - } - else - { - members = members.Distinct(new EntryComparer()); - } - - var resultingNamespace = nspace ?? - (enumType.ContainingNamespace.IsGlobalNamespace - ? string.Empty - : enumType.ContainingNamespace.ToString()); - - results.Add(new ToStringMethod( - enumType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), - members.Select(kvp => kvp.Key).ToList(), - members.Select(kvp => kvp.Value).ToList(), - flags, - resultingNamespace, - className ?? enumType.Name + "Extensions", - methodName ?? "ToInvariantString", - classModifiers ?? "internal static", - enumType.EnumUnderlyingType!.ToString())); - } - } - } - } - - private void Diag(DiagnosticDescriptor desc, Location? location) - { - _reportDiagnostic(Diagnostic.Create(desc, location, Array.Empty())); - } - - private void Diag(DiagnosticDescriptor desc, Location? location, params object?[]? messageArgs) - { - _reportDiagnostic(Diagnostic.Create(desc, location, messageArgs)); - } - - private sealed class EntryComparer : IEqualityComparer> - { - public bool Equals(KeyValuePair x, KeyValuePair y) => x.Value == y.Value; - public int GetHashCode(KeyValuePair obj) => obj.Value.GetHashCode(); - } -} diff --git a/src/Generators/Microsoft.Gen.EnumStrings/Resources.Designer.cs b/src/Generators/Microsoft.Gen.EnumStrings/Resources.Designer.cs deleted file mode 100644 index 54b8cf2d979..00000000000 --- a/src/Generators/Microsoft.Gen.EnumStrings/Resources.Designer.cs +++ /dev/null @@ -1,153 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.Gen.EnumStrings { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Gen.EnumStrings.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to This attribute constructor is not valid in this context, please try a different constructor. - /// - internal static string IncorrectOverloadMessage { - get { - return ResourceManager.GetString("IncorrectOverloadMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid attribute constructor used. - /// - internal static string IncorrectOverloadTitle { - get { - return ResourceManager.GetString("IncorrectOverloadTitle", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} is not an enum. - /// - internal static string InvalidEnumTypeMessage { - get { - return ResourceManager.GetString("InvalidEnumTypeMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid enum type specified. - /// - internal static string InvalidEnumTypeTitle { - get { - return ResourceManager.GetString("InvalidEnumTypeTitle", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} is not a valid class name. - /// - internal static string InvalidExtensionClassNameMessage { - get { - return ResourceManager.GetString("InvalidExtensionClassNameMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid extension class name. - /// - internal static string InvalidExtensionClassNameTitle { - get { - return ResourceManager.GetString("InvalidExtensionClassNameTitle", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} is not a valid method name. - /// - internal static string InvalidExtensionMethodNameMessage { - get { - return ResourceManager.GetString("InvalidExtensionMethodNameMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid extension method name. - /// - internal static string InvalidExtensionMethodNameTitle { - get { - return ResourceManager.GetString("InvalidExtensionMethodNameTitle", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} is not a valid namespace. - /// - internal static string InvalidExtensionNamespaceMessage { - get { - return ResourceManager.GetString("InvalidExtensionNamespaceMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid extension namespace. - /// - internal static string InvalidExtensionNamespaceTitle { - get { - return ResourceManager.GetString("InvalidExtensionNamespaceTitle", resourceCulture); - } - } - } -} diff --git a/src/Generators/Microsoft.Gen.EnumStrings/Resources.resx b/src/Generators/Microsoft.Gen.EnumStrings/Resources.resx deleted file mode 100644 index 32d6fde1aa4..00000000000 --- a/src/Generators/Microsoft.Gen.EnumStrings/Resources.resx +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - This attribute constructor is not valid in this context, please try a different constructor - - - Invalid attribute constructor used - - - {0} is not an enum - - - Invalid enum type specified - - - {0} is not a valid class name - - - Invalid extension class name - - - {0} is not a valid method name - - - Invalid extension method name - - - {0} is not a valid namespace - - - Invalid extension namespace - - \ No newline at end of file diff --git a/src/Generators/Microsoft.Gen.EnumStrings/SymbolHolder.cs b/src/Generators/Microsoft.Gen.EnumStrings/SymbolHolder.cs deleted file mode 100644 index 00aa8801326..00000000000 --- a/src/Generators/Microsoft.Gen.EnumStrings/SymbolHolder.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.CodeAnalysis; -using Microsoft.CodeAnalysis; - -namespace Microsoft.Gen.EnumStrings; - -[ExcludeFromCodeCoverage] -internal sealed record class SymbolHolder( - INamedTypeSymbol FlagsAttributeSymbol, - INamedTypeSymbol EnumStringsAttributeSymbol, - INamedTypeSymbol? FreezerSymbol); diff --git a/src/Generators/Microsoft.Gen.EnumStrings/SymbolLoader.cs b/src/Generators/Microsoft.Gen.EnumStrings/SymbolLoader.cs deleted file mode 100644 index ac5a3ff420e..00000000000 --- a/src/Generators/Microsoft.Gen.EnumStrings/SymbolLoader.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.CodeAnalysis; - -namespace Microsoft.Gen.EnumStrings; - -internal static class SymbolLoader -{ - public const string EnumStringsAttribute = "Microsoft.Extensions.EnumStrings.EnumStringsAttribute"; - public const string FlagsAttribute = "System.FlagsAttribute"; - public const string FreezerClass = "System.Collections.Frozen.FrozenDictionary"; - - public static void Load(Compilation compilation, out SymbolHolder? symbolHolder) - { - symbolHolder = new( - compilation.GetTypeByMetadataName(FlagsAttribute)!, - compilation.GetTypeByMetadataName(EnumStringsAttribute)!, - compilation.GetTypeByMetadataName(FreezerClass)); - } -} diff --git a/src/Generators/Microsoft.Gen.Logging/Emission/Emitter.Method.cs b/src/Generators/Microsoft.Gen.Logging/Emission/Emitter.Method.cs index 9a8f48d4bbb..d1f02403682 100644 --- a/src/Generators/Microsoft.Gen.Logging/Emission/Emitter.Method.cs +++ b/src/Generators/Microsoft.Gen.Logging/Emission/Emitter.Method.cs @@ -316,7 +316,7 @@ void GenPropertyLoads(LoggingMethod lm, string stateName, out int numReservedUnc { if (!member.HasDataClassification) { - var propName = PropertyChainToString(propertyChain, member, "_", omitReferenceName: p.OmitReferenceName); + var propName = PropertyChainToString(propertyChain, member, ".", omitReferenceName: p.OmitReferenceName); var accessExpression = PropertyChainToString(propertyChain, member, "?.", nonNullSeparator: "."); var ts = ShouldStringifyProperty(member) @@ -367,7 +367,7 @@ void GenPropertyLoads(LoggingMethod lm, string stateName, out int numReservedUnc { if (member.HasDataClassification) { - var propName = PropertyChainToString(propertyChain, member, "_", omitReferenceName: p.OmitReferenceName); + var propName = PropertyChainToString(propertyChain, member, ".", omitReferenceName: p.OmitReferenceName); var accessExpression = PropertyChainToString(propertyChain, member, "?.", nonNullSeparator: "."); var value = ShouldStringifyProperty(member) @@ -391,7 +391,7 @@ void GenPropertyLoads(LoggingMethod lm, string stateName, out int numReservedUnc { if (member.HasDataClassification) { - var propName = PropertyChainToString(propertyChain, member, "_", omitReferenceName: p.OmitReferenceName); + var propName = PropertyChainToString(propertyChain, member, ".", omitReferenceName: p.OmitReferenceName); var accessExpression = PropertyChainToString(propertyChain, member, "?.", nonNullSeparator: "."); var value = ShouldStringifyProperty(member) @@ -418,7 +418,7 @@ void GenPropertyLoads(LoggingMethod lm, string stateName, out int numReservedUnc } else { - var propName = PropertyChainToString(propertyChain, member, "_", omitReferenceName: p.OmitReferenceName); + var propName = PropertyChainToString(propertyChain, member, ".", omitReferenceName: p.OmitReferenceName); var accessExpression = PropertyChainToString(propertyChain, member, "?.", nonNullSeparator: "."); var ts = ShouldStringifyProperty(member) @@ -559,23 +559,25 @@ bool GenVariableAssignments(LoggingMethod lm, string lambdaStateName, int numRes private string MakeClassificationValue(HashSet classificationTypes) { - if (classificationTypes.Count == 1) - { - return _classificationMap[classificationTypes.First()]; - } - var sb = _sbPool.GetStringBuilder(); + _ = sb.Append("new global::Microsoft.Extensions.Compliance.Classification.DataClassificationSet("); + + int i = 0; + foreach (var ct in classificationTypes) { - if (sb.Length > 0) + if (i > 0) { - _ = sb.Append(" | "); + _ = sb.Append(", "); } _ = sb.Append(_classificationMap[ct]); + i++; } + _ = sb.Append(')'); + return sb.ToString(); } diff --git a/src/Generators/Microsoft.Gen.Logging/Microsoft.Gen.Logging.csproj b/src/Generators/Microsoft.Gen.Logging/Microsoft.Gen.Logging.csproj index e5284de8b5e..f68c968ef9e 100644 --- a/src/Generators/Microsoft.Gen.Logging/Microsoft.Gen.Logging.csproj +++ b/src/Generators/Microsoft.Gen.Logging/Microsoft.Gen.Logging.csproj @@ -1,7 +1,7 @@  Microsoft.Gen.Logging - Code generator to support Microsoft.Extensions.Diagnostics.Extra's logging features. + Code generator to support Microsoft.Extensions.Telemetry's logging features. Telemetry diff --git a/src/Generators/Microsoft.Gen.Logging/Parsing/DiagDescriptors.cs b/src/Generators/Microsoft.Gen.Logging/Parsing/DiagDescriptors.cs index 4369e96d42a..275fc2dc82c 100644 --- a/src/Generators/Microsoft.Gen.Logging/Parsing/DiagDescriptors.cs +++ b/src/Generators/Microsoft.Gen.Logging/Parsing/DiagDescriptors.cs @@ -3,231 +3,238 @@ using Microsoft.CodeAnalysis; using Microsoft.Gen.Shared; +using Microsoft.Shared.DiagnosticIds; namespace Microsoft.Gen.Logging.Parsing; internal sealed class DiagDescriptors : DiagDescriptorsBase { - private const string Category = "LoggerMessage"; + private const string Category = nameof(DiagnosticIds.LoggerMessage); public static DiagnosticDescriptor ShouldntMentionLogLevelInMessage { get; } = Make( - id: "LOGGEN000", + id: DiagnosticIds.LoggerMessage.LOGGEN000, title: Resources.ShouldntMentionLogLevelInMessageTitle, messageFormat: Resources.ShouldntMentionLogLevelInMessageMessage, category: Category, DiagnosticSeverity.Warning); public static DiagnosticDescriptor MissingRequiredType { get; } = Make( - id: "LOGGEN001", + id: DiagnosticIds.LoggerMessage.LOGGEN001, title: Resources.MissingRequiredTypeTitle, messageFormat: Resources.MissingRequiredTypeMessage, category: Category); public static DiagnosticDescriptor ShouldntReuseEventIds { get; } = Make( - id: "LOGGEN002", + id: DiagnosticIds.LoggerMessage.LOGGEN002, title: Resources.ShouldntReuseEventIdsTitle, messageFormat: Resources.ShouldntReuseEventIdsMessage, category: Category, DiagnosticSeverity.Warning); public static DiagnosticDescriptor LoggingMethodMustReturnVoid { get; } = Make( - id: "LOGGEN003", + id: DiagnosticIds.LoggerMessage.LOGGEN003, title: Resources.LoggingMethodMustReturnVoidTitle, messageFormat: Resources.LoggingMethodMustReturnVoidMessage, category: Category); public static DiagnosticDescriptor MissingLoggerParameter { get; } = Make( - id: "LOGGEN004", + id: DiagnosticIds.LoggerMessage.LOGGEN004, title: Resources.MissingLoggerParameterTitle, messageFormat: Resources.MissingLoggerParameterMessage, category: Category); public static DiagnosticDescriptor LoggingMethodShouldBeStatic { get; } = Make( - id: "LOGGEN005", + id: DiagnosticIds.LoggerMessage.LOGGEN005, title: Resources.LoggingMethodShouldBeStaticTitle, messageFormat: Resources.LoggingMethodShouldBeStaticMessage, category: Category, DiagnosticSeverity.Warning); public static DiagnosticDescriptor LoggingMethodMustBePartial { get; } = Make( - id: "LOGGEN006", + id: DiagnosticIds.LoggerMessage.LOGGEN006, title: Resources.LoggingMethodMustBePartialTitle, messageFormat: Resources.LoggingMethodMustBePartialMessage, category: Category); public static DiagnosticDescriptor LoggingMethodIsGeneric { get; } = Make( - id: "LOGGEN007", + id: DiagnosticIds.LoggerMessage.LOGGEN007, title: Resources.LoggingMethodIsGenericTitle, messageFormat: Resources.LoggingMethodIsGenericMessage, category: Category); public static DiagnosticDescriptor RedundantQualifierInMessage { get; } = Make( - id: "LOGGEN008", + id: DiagnosticIds.LoggerMessage.LOGGEN008, title: Resources.RedundantQualifierInMessageTitle, messageFormat: Resources.RedundantQualifierInMessageMessage, category: Category, DiagnosticSeverity.Warning); public static DiagnosticDescriptor ShouldntMentionExceptionInMessage { get; } = Make( - id: "LOGGEN009", + id: DiagnosticIds.LoggerMessage.LOGGEN009, title: Resources.ShouldntMentionExceptionInMessageTitle, messageFormat: Resources.ShouldntMentionExceptionInMessageMessage, category: Category, DiagnosticSeverity.Warning); public static DiagnosticDescriptor TemplateHasNoCorrespondingParameter { get; } = Make( - id: "LOGGEN010", + id: DiagnosticIds.LoggerMessage.LOGGEN010, title: Resources.TemplateHasNoCorrespondingParameterTitle, messageFormat: Resources.TemplateHasNoCorrespondingParameterMessage, category: Category); public static DiagnosticDescriptor ParameterHasNoCorrespondingTemplate { get; } = Make( - id: "LOGGEN011", + id: DiagnosticIds.LoggerMessage.LOGGEN011, title: Resources.ParameterHasNoCorrespondingTemplateTitle, messageFormat: Resources.ParameterHasNoCorrespondingTemplateMessage, category: Category, DiagnosticSeverity.Info); public static DiagnosticDescriptor LoggingMethodHasBody { get; } = Make( - id: "LOGGEN012", + id: DiagnosticIds.LoggerMessage.LOGGEN012, title: Resources.LoggingMethodHasBodyTitle, messageFormat: Resources.LoggingMethodHasBodyMessage, category: Category); public static DiagnosticDescriptor MissingLogLevel { get; } = Make( - id: "LOGGEN013", + id: DiagnosticIds.LoggerMessage.LOGGEN013, title: Resources.MissingLogLevelTitle, messageFormat: Resources.MissingLogLevelMessage, category: Category); public static DiagnosticDescriptor ShouldntMentionLoggerInMessage { get; } = Make( - id: "LOGGEN014", + id: DiagnosticIds.LoggerMessage.LOGGEN014, title: Resources.ShouldntMentionLoggerInMessageTitle, messageFormat: Resources.ShouldntMentionLoggerInMessageMessage, category: Category, DiagnosticSeverity.Warning); public static DiagnosticDescriptor MissingLoggerField { get; } = Make( - id: "LOGGEN015", + id: DiagnosticIds.LoggerMessage.LOGGEN015, title: Resources.MissingLoggerFieldTitle, messageFormat: Resources.MissingLoggerFieldMessage, category: Category); public static DiagnosticDescriptor MultipleLoggerFields { get; } = Make( - id: "LOGGEN016", + id: DiagnosticIds.LoggerMessage.LOGGEN016, title: Resources.MultipleLoggerFieldsTitle, messageFormat: Resources.MultipleLoggerFieldsMessage, category: Category); public static DiagnosticDescriptor CantUseDataClassificationWithLogPropertiesOrTagProvider { get; } = Make( - id: "LOGGEN017", + id: DiagnosticIds.LoggerMessage.LOGGEN017, title: Resources.CantUseDataClassificationWithLogPropertiesOrTagProviderTitle, messageFormat: Resources.CantUseDataClassificationWithLogPropertiesOrTagProviderMessage, category: Category); public static DiagnosticDescriptor InvalidTypeToLogProperties { get; } = Make( - id: "LOGGEN018", + id: DiagnosticIds.LoggerMessage.LOGGEN018, title: Resources.InvalidTypeToLogPropertiesTitle, messageFormat: Resources.InvalidTypeToLogPropertiesMessage, category: Category, DiagnosticSeverity.Warning); public static DiagnosticDescriptor LogPropertiesInvalidUsage { get; } = Make( - id: "LOGGEN019", + id: DiagnosticIds.LoggerMessage.LOGGEN019, title: Resources.LogPropertiesInvalidUsageTitle, messageFormat: Resources.LogPropertiesInvalidUsageMessage, category: Category); public static DiagnosticDescriptor LogPropertiesParameterSkipped { get; } = Make( - id: "LOGGEN020", + id: DiagnosticIds.LoggerMessage.LOGGEN020, title: Resources.LogPropertiesParameterSkippedTitle, messageFormat: Resources.LogPropertiesParameterSkippedMessage, category: Category, DiagnosticSeverity.Warning); public static DiagnosticDescriptor LogPropertiesCycleDetected { get; } = Make( - id: "LOGGEN021", + id: DiagnosticIds.LoggerMessage.LOGGEN021, title: Resources.LogPropertiesCycleDetectedTitle, messageFormat: Resources.LogPropertiesCycleDetectedMessage, category: Category); public static DiagnosticDescriptor TagProviderMethodNotFound { get; } = Make( - id: "LOGGEN022", + id: DiagnosticIds.LoggerMessage.LOGGEN022, title: Resources.TagProviderMethodNotFoundTitle, messageFormat: Resources.TagProviderMethodNotFoundMessage, category: Category); public static DiagnosticDescriptor TagProviderMethodInaccessible { get; } = Make( - id: "LOGGEN023", + id: DiagnosticIds.LoggerMessage.LOGGEN023, title: Resources.TagProviderMethodInaccessibleTitle, messageFormat: Resources.TagProviderMethodInaccessibleMessage, category: Category); public static DiagnosticDescriptor TagProviderMethodInvalidSignature { get; } = Make( - id: "LOGGEN024", + id: DiagnosticIds.LoggerMessage.LOGGEN024, title: Resources.TagProviderMethodInvalidSignatureTitle, messageFormat: Resources.TagProviderMethodInvalidSignatureMessage, category: Category); public static DiagnosticDescriptor LoggingMethodParameterRefKind { get; } = Make( - id: "LOGGEN025", + id: DiagnosticIds.LoggerMessage.LOGGEN025, title: Resources.LoggingMethodParameterRefKindTitle, messageFormat: Resources.LoggingMethodParameterRefKindMessage, category: Category); public static DiagnosticDescriptor LogPropertiesProviderWithRedaction { get; } = Make( - id: "LOGGEN026", + id: DiagnosticIds.LoggerMessage.LOGGEN026, title: Resources.TagProviderWithRedactionTitle, messageFormat: Resources.TagProviderWithRedactionMessage, category: Category, DiagnosticSeverity.Warning); public static DiagnosticDescriptor ShouldntReuseEventNames { get; } = Make( - id: "LOGGEN027", + id: DiagnosticIds.LoggerMessage.LOGGEN027, title: Resources.ShouldntReuseEventNamesTitle, messageFormat: Resources.ShouldntReuseEventNamesMessage, category: Category, DiagnosticSeverity.Warning); public static DiagnosticDescriptor LogPropertiesHiddenPropertyDetected { get; } = Make( - id: "LOGGEN028", + id: DiagnosticIds.LoggerMessage.LOGGEN028, title: Resources.LogPropertiesHiddenPropertyDetectedTitle, messageFormat: Resources.LogPropertiesHiddenPropertyDetectedMessage, category: Category); public static DiagnosticDescriptor LogPropertiesNameCollision { get; } = Make( - id: "LOGGEN029", + id: DiagnosticIds.LoggerMessage.LOGGEN029, title: Resources.LogPropertiesNameCollisionTitle, messageFormat: Resources.LogPropertiesNameCollisionMessage, category: Category); public static DiagnosticDescriptor EmptyLoggingMethod { get; } = Make( - id: "LOGGEN030", + id: DiagnosticIds.LoggerMessage.LOGGEN030, title: Resources.EmptyLoggingMethodTitle, messageFormat: Resources.EmptyLoggingMethodMessage, category: Category); public static DiagnosticDescriptor TemplateStartsWithAtSymbol { get; } = Make( - id: "LOGGEN031", + id: DiagnosticIds.LoggerMessage.LOGGEN031, title: Resources.TemplateStartsWithAtSymbolTitle, messageFormat: Resources.TemplateStartsWithAtSymbolMessage, category: Category); public static DiagnosticDescriptor CantMixAttributes { get; } = Make( - id: "LOGGEN032", + id: DiagnosticIds.LoggerMessage.LOGGEN032, title: Resources.CantMixAttributesTitle, messageFormat: Resources.CantMixAttributesMessage, category: Category); public static DiagnosticDescriptor TagProviderInvalidUsage { get; } = Make( - id: "LOGGEN033", + id: DiagnosticIds.LoggerMessage.LOGGEN033, title: Resources.TagProviderInvalidUsageTitle, messageFormat: Resources.TagProviderInvalidUsageMessage, category: Category); public static DiagnosticDescriptor InvalidAttributeUsage { get; } = Make( - id: "LOGGEN034", + id: DiagnosticIds.LoggerMessage.LOGGEN034, title: Resources.InvalidAttributeUsageTitle, messageFormat: Resources.InvalidAttributeUsageMessage, category: Category); + + public static DiagnosticDescriptor RecordTypeSensitiveArgumentIsInTemplate { get; } = Make( + id: DiagnosticIds.LoggerMessage.LOGGEN035, + title: Resources.RecordTypeSensitiveArgumentIsInTemplateTitle, + messageFormat: Resources.RecordTypeSensitiveArgumentIsInTemplateMessage, + category: Category); } diff --git a/src/Generators/Microsoft.Gen.Logging/Parsing/Parser.LogProperties.cs b/src/Generators/Microsoft.Gen.Logging/Parsing/Parser.LogProperties.cs index 88869bdf551..4fabb10e603 100644 --- a/src/Generators/Microsoft.Gen.Logging/Parsing/Parser.LogProperties.cs +++ b/src/Generators/Microsoft.Gen.Logging/Parsing/Parser.LogProperties.cs @@ -20,12 +20,13 @@ internal partial class Parser { private static readonly HashSet _allowedTypeKinds = [TypeKind.Class, TypeKind.Struct, TypeKind.Interface]; - private bool ProcessLogPropertiesForParameter( + internal bool ProcessLogPropertiesForParameter( AttributeData logPropertiesAttribute, LoggingMethod lm, LoggingMethodParameter lp, IParameterSymbol paramSymbol, - SymbolHolder symbols) + SymbolHolder symbols, + ref bool foundDataClassificationAttributes) { var paramName = paramSymbol.Name; if (!lp.IsNormalParameter) @@ -52,7 +53,7 @@ private bool ProcessLogPropertiesForParameter( _ = typesChain.Add(paramTypeSymbol); // Add itself - var props = GetTypePropertiesToLog(paramTypeSymbol, typesChain, symbols); + var props = GetTypePropertiesToLog(paramTypeSymbol, typesChain, symbols, ref foundDataClassificationAttributes); if (props == null) { return false; @@ -84,7 +85,8 @@ static string GetPropertyIdentifier(IPropertySymbol property, CancellationToken List? GetTypePropertiesToLog( ITypeSymbol type, ISet typesChain, - SymbolHolder symbols) + SymbolHolder symbols, + ref bool foundDataClassificationAttributes) { var result = new List(); var seenProps = new HashSet(); @@ -94,180 +96,212 @@ static string GetPropertyIdentifier(IPropertySymbol property, CancellationToken _cancellationToken.ThrowIfCancellationRequested(); var members = namedType.GetMembers(); - if (members != null) + if (members.IsDefaultOrEmpty) { - foreach (var property in members.Where(m => m.Kind == SymbolKind.Property).Cast()) - { - var logPropertiesAttribute = ParserUtilities.GetSymbolAttributeAnnotationOrDefault(symbols.LogPropertiesAttribute, property); - var logPropertyIgnoreAttribute = ParserUtilities.GetSymbolAttributeAnnotationOrDefault(symbols.LogPropertyIgnoreAttribute, property); - var tagProviderAttribute = ParserUtilities.GetSymbolAttributeAnnotationOrDefault(symbols.TagProviderAttribute, property); + namedType = namedType.BaseType; + continue; + } - var count = 0; - if (logPropertiesAttribute != null) + var sensitivePropsFromCtor = new Dictionary>(); + if (namedType.IsRecord && !namedType.InstanceConstructors.IsDefaultOrEmpty) + { + // We can also decide to skip here "protected", "internal" and "private" constructors in future: + foreach (var ctor in namedType.InstanceConstructors) + { + // Check if constructor has any sensitive parameters: + foreach (var parameter in ctor.Parameters) { - count++; + var maybeDataClasses = GetDataClassificationAttributes(parameter, symbols); + if (maybeDataClasses.Count > 0) + { + sensitivePropsFromCtor[parameter.Name] = maybeDataClasses; + } } + } + } - if (logPropertyIgnoreAttribute != null) - { - count++; - } + foreach (var property in members.Where(m => m.Kind == SymbolKind.Property).Cast()) + { + var logPropertiesAttribute = ParserUtilities.GetSymbolAttributeAnnotationOrDefault(symbols.LogPropertiesAttribute, property); + var logPropertyIgnoreAttribute = ParserUtilities.GetSymbolAttributeAnnotationOrDefault(symbols.LogPropertyIgnoreAttribute, property); + var tagProviderAttribute = ParserUtilities.GetSymbolAttributeAnnotationOrDefault(symbols.TagProviderAttribute, property); - if (tagProviderAttribute != null) - { - count++; - } + var count = 0; + if (logPropertiesAttribute != null) + { + count++; + } - if (count > 1) - { - Diag(DiagDescriptors.CantMixAttributes, property.GetLocation()); - continue; - } + if (logPropertyIgnoreAttribute != null) + { + count++; + } - if (!seenProps.Add(property.Name)) - { - // already saw this property in a derived type, skip it - continue; - } + if (tagProviderAttribute != null) + { + count++; + } - var classification = new HashSet(); - var current = type; - while (current != null) - { - classification.UnionWith(GetDataClassificationAttributes(current, symbols).Select(x => x.ToDisplayString())); - current = current.BaseType; - } + if (count > 1) + { + Diag(DiagDescriptors.CantMixAttributes, property.GetLocation()); + continue; + } - classification.UnionWith(GetDataClassificationAttributes(property, symbols).Select(x => x.ToDisplayString())); + if (!seenProps.Add(property.Name)) + { + // already saw this property in a derived type, skip it + continue; + } - var extractedType = property.Type; - if (extractedType.IsNullableOfT()) - { - // extract the T from a Nullable - extractedType = ((INamedTypeSymbol)extractedType).TypeArguments[0]; - } + var classification = new HashSet(); + var current = type; + while (current != null) + { + classification.UnionWith(GetDataClassificationAttributes(current, symbols).Select(x => x.ToDisplayString())); + current = current.BaseType; + } - var lp = new LoggingProperty - { - Name = property.Name, - Type = property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), - ClassificationAttributeTypes = classification, - IsReference = property.Type.IsReferenceType, - IsEnumerable = property.Type.IsEnumerable(symbols), - IsNullable = property.Type.NullableAnnotation == NullableAnnotation.Annotated, - ImplementsIConvertible = property.Type.ImplementsIConvertible(symbols), - ImplementsIFormattable = property.Type.ImplementsIFormattable(symbols), - ImplementsISpanFormattable = property.Type.ImplementsISpanFormattable(symbols), - }; - - if (!property.DeclaringSyntaxReferences.IsDefaultOrEmpty) - { - var propertyIdentifier = GetPropertyIdentifier(property, _cancellationToken); + classification.UnionWith(GetDataClassificationAttributes(property, symbols).Select(x => x.ToDisplayString())); - if (!string.IsNullOrEmpty(propertyIdentifier)) - { - lp.NeedsAtSign = propertyIdentifier[0] == '@'; - } - } + // A property might be a sensitive parameter of a constructor: + if (sensitivePropsFromCtor.TryGetValue(property.Name, out var dataClassesFromCtor)) + { + classification.UnionWith(dataClassesFromCtor.Select(x => x.ToDisplayString())); + } + + if (classification.Count > 0) + { + foundDataClassificationAttributes = true; + } + + var extractedType = property.Type; + if (extractedType.IsNullableOfT()) + { + // extract the T from a Nullable + extractedType = ((INamedTypeSymbol)extractedType).TypeArguments[0]; + } + + var lp = new LoggingProperty + { + Name = property.Name, + Type = property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + ClassificationAttributeTypes = classification, + IsReference = property.Type.IsReferenceType, + IsEnumerable = property.Type.IsEnumerable(symbols), + IsNullable = property.Type.NullableAnnotation == NullableAnnotation.Annotated, + ImplementsIConvertible = property.Type.ImplementsIConvertible(symbols), + ImplementsIFormattable = property.Type.ImplementsIFormattable(symbols), + ImplementsISpanFormattable = property.Type.ImplementsISpanFormattable(symbols), + }; + + if (!property.DeclaringSyntaxReferences.IsDefaultOrEmpty) + { + var propertyIdentifier = GetPropertyIdentifier(property, _cancellationToken); - if (property.Type.IsValueType && !property.Type.IsNullableOfT()) + if (!string.IsNullOrEmpty(propertyIdentifier)) { + lp.NeedsAtSign = propertyIdentifier[0] == '@'; + } + } + + if (property.Type.IsValueType && !property.Type.IsNullableOfT()) + { #pragma warning disable S125 - // deal with an oddity in Roslyn. If you have this case: - // - // class Gen - // { - // public T? Value { get; set; } - // } - // - // class Foo - // { - // public Gen MyInt { get; set; } - // } - // - // then Roslyn claims that MyInt has nullable annotations. Yet, doing x.MyInt?.ToString isn't valid. - // So we explicitly check whether the value is indeed a Nullable. - lp.IsNullable = false; + // deal with an oddity in Roslyn. If you have this case: + // + // class Gen + // { + // public T? Value { get; set; } + // } + // + // class Foo + // { + // public Gen MyInt { get; set; } + // } + // + // then Roslyn claims that MyInt has nullable annotations. Yet, doing x.MyInt?.ToString isn't valid. + // So we explicitly check whether the value is indeed a Nullable. + lp.IsNullable = false; #pragma warning restore S125 - } + } + + // Check if this property hides a base property + if (ParserUtilities.PropertyHasModifier(property, SyntaxKind.NewKeyword, _cancellationToken)) + { + Diag(DiagDescriptors.LogPropertiesHiddenPropertyDetected, paramSymbol.GetLocation(), paramName, lm.Name, property.Name); + return null; + } - // Check if this property hides a base property - if (ParserUtilities.PropertyHasModifier(property, SyntaxKind.NewKeyword, _cancellationToken)) + if (logPropertiesAttribute != null) + { + _ = CanLogProperties(property, property.Type, symbols); + + if ((property.DeclaredAccessibility != Accessibility.Public || property.IsStatic) + || (property.GetMethod == null || property.GetMethod.DeclaredAccessibility != Accessibility.Public)) { - Diag(DiagDescriptors.LogPropertiesHiddenPropertyDetected, paramSymbol.GetLocation(), paramName, lm.Name, property.Name); - return null; + Diag(DiagDescriptors.InvalidAttributeUsage, logPropertiesAttribute.ApplicationSyntaxReference?.GetSyntax(_cancellationToken).GetLocation(), "LogProperties"); + continue; } - if (logPropertiesAttribute != null) + // Checking "extractedType" here + if (typesChain.Contains(extractedType)) { - _ = CanLogProperties(property, property.Type, symbols); - - if ((property.DeclaredAccessibility != Accessibility.Public || property.IsStatic) - || (property.GetMethod == null || property.GetMethod.DeclaredAccessibility != Accessibility.Public)) - { - Diag(DiagDescriptors.InvalidAttributeUsage, logPropertiesAttribute.ApplicationSyntaxReference?.GetSyntax(_cancellationToken).GetLocation(), "LogProperties"); - continue; - } - - // Checking "extractedType" here - if (typesChain.Contains(extractedType)) - { - Diag(DiagDescriptors.LogPropertiesCycleDetected, paramSymbol.GetLocation(), paramName, namedType.ToDisplayString(), property.Type.ToDisplayString(), lm.Name); - return null; - } + Diag(DiagDescriptors.LogPropertiesCycleDetected, paramSymbol.GetLocation(), paramName, namedType.ToDisplayString(), property.Type.ToDisplayString(), lm.Name); + return null; + } - var propName = property.Name; + var propName = property.Name; - extractedType = property.Type; - if (extractedType.IsNullableOfT()) - { - // extract the T from a Nullable - extractedType = ((INamedTypeSymbol)extractedType).TypeArguments[0]; - } + extractedType = property.Type; + if (extractedType.IsNullableOfT()) + { + // extract the T from a Nullable + extractedType = ((INamedTypeSymbol)extractedType).TypeArguments[0]; + } - _ = typesChain.Add(namedType); - var props = GetTypePropertiesToLog(extractedType, typesChain, symbols); - _ = typesChain.Remove(namedType); + _ = typesChain.Add(namedType); + var props = GetTypePropertiesToLog(extractedType, typesChain, symbols, ref foundDataClassificationAttributes); + _ = typesChain.Remove(namedType); - if (props != null) - { - lp.Properties.AddRange(props); - } + if (props != null) + { + lp.Properties.AddRange(props); } + } - if (tagProviderAttribute != null) + if (tagProviderAttribute != null) + { + if ((property.DeclaredAccessibility != Accessibility.Public || property.IsStatic) + || (property.GetMethod == null || property.GetMethod.DeclaredAccessibility != Accessibility.Public)) { - if ((property.DeclaredAccessibility != Accessibility.Public || property.IsStatic) - || (property.GetMethod == null || property.GetMethod.DeclaredAccessibility != Accessibility.Public)) - { - Diag(DiagDescriptors.InvalidAttributeUsage, tagProviderAttribute.ApplicationSyntaxReference?.GetSyntax(_cancellationToken).GetLocation(), "TagProvider"); - } - - if (!ProcessTagProviderForProperty(tagProviderAttribute, lp, property, symbols)) - { - lp.TagProvider = null; - } + Diag(DiagDescriptors.InvalidAttributeUsage, tagProviderAttribute.ApplicationSyntaxReference?.GetSyntax(_cancellationToken).GetLocation(), "TagProvider"); } - if (tagProviderAttribute == null && logPropertiesAttribute == null) + if (!ProcessTagProviderForProperty(tagProviderAttribute, lp, property, symbols)) { - if ((property.DeclaredAccessibility != Accessibility.Public || property.IsStatic) - || (property.GetMethod == null || property.GetMethod.DeclaredAccessibility != Accessibility.Public) - || (property.Type.TypeKind == TypeKind.Delegate) - || (logPropertyIgnoreAttribute != null)) - { - continue; - } + lp.TagProvider = null; } + } - if (lp.HasDataClassification && (lp.HasProperties || lp.HasTagProvider)) + if (tagProviderAttribute == null && logPropertiesAttribute == null) + { + if ((property.DeclaredAccessibility != Accessibility.Public || property.IsStatic) + || (property.GetMethod == null || property.GetMethod.DeclaredAccessibility != Accessibility.Public) + || (property.Type.TypeKind == TypeKind.Delegate) + || (logPropertyIgnoreAttribute != null)) { - Diag(DiagDescriptors.CantUseDataClassificationWithLogPropertiesOrTagProvider, property.GetLocation()); - lp.ClassificationAttributeTypes.Clear(); + continue; } + } - result.Add(lp); + if (lp.HasDataClassification && (lp.HasProperties || lp.HasTagProvider)) + { + Diag(DiagDescriptors.CantUseDataClassificationWithLogPropertiesOrTagProvider, property.GetLocation()); + lp.ClassificationAttributeTypes.Clear(); } + + result.Add(lp); } // This is to support inheritance, i.e. to fetch public properties of base class(-es) as well: diff --git a/src/Generators/Microsoft.Gen.Logging/Parsing/Parser.Records.cs b/src/Generators/Microsoft.Gen.Logging/Parsing/Parser.Records.cs new file mode 100644 index 00000000000..c5f16008de7 --- /dev/null +++ b/src/Generators/Microsoft.Gen.Logging/Parsing/Parser.Records.cs @@ -0,0 +1,166 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.Gen.Shared; + +namespace Microsoft.Gen.Logging.Parsing +{ + internal partial class Parser + { + internal bool RecordHasSensitivePublicMembers(ITypeSymbol type, SymbolHolder symbols) + { + if (symbols.DataClassificationAttribute is null) + { + return false; + } + + // Check full type hierarchy for sensitive members, including base types and transitive fields/properties + var typesChain = new HashSet(SymbolEqualityComparer.Default); + _ = typesChain.Add(type); // Add itself + return RecordHasSensitivePublicMembers(type, typesChain, symbols, _cancellationToken); + } + + private readonly record struct MemberInfo( + string Name, + ITypeSymbol Type, + IPropertySymbol? Property); + + private static bool RecordHasSensitivePublicMembers(ITypeSymbol type, HashSet typesChain, SymbolHolder symbols, CancellationToken token) + { + var overriddenMembers = new HashSet(); + var namedType = type as INamedTypeSymbol; + while (namedType != null && namedType.SpecialType != SpecialType.System_Object) + { + token.ThrowIfCancellationRequested(); + + // Check if the type itself has any data classification attributes applied: + var recordDataClassAttributes = GetDataClassificationAttributes(namedType, symbols); + if (recordDataClassAttributes.Count > 0) + { + return true; + } + + var members = namedType.GetMembers(); + if (members.IsDefaultOrEmpty) + { + namedType = namedType.BaseType; + continue; + } + + foreach (var m in members) + { + // Skip static, non-public members: + if (m.DeclaredAccessibility != Accessibility.Public || + m.IsStatic) + { + continue; + } + + MemberInfo? maybeMemberInfo = + m switch + { + IPropertySymbol property => new(property.Name, property.Type, property), + IFieldSymbol field => new(field.Name, field.Type, Property: null), + _ => null + }; + + if (maybeMemberInfo is null) + { + if (m is not IMethodSymbol { MethodKind: MethodKind.Constructor } ctorMethod) + { + continue; + } + + // Check if constructor has any sensitive parameters: + foreach (var param in ctorMethod.Parameters) + { + // We don't check for "DataClassifierWrappers" here as they will be covered in field/property checks: + var parameterDataClassAttributes = GetDataClassificationAttributes(param, symbols); + if (parameterDataClassAttributes.Count > 0) + { + return true; + } + } + + // No sensitive parameters in the ctor, continue with next member: + continue; + } + + var memberInfo = maybeMemberInfo.Value; + if (memberInfo.Property is not null) + { + var property = memberInfo.Property; + var getMethod = property.GetMethod; + + // We don't check if getter is "public", it doesn't matter: https://github.com/dotnet/runtime/issues/86700 + if (getMethod is null) + { + continue; + } + + // Property is already overridden in derived class, skip it: + if ((property.IsVirtual || property.IsOverride) && + overriddenMembers.Contains(property.Name)) + { + continue; + } + + // Adding property that overrides some base class virtual property: + if (property.IsOverride) + { + _ = overriddenMembers.Add(property.Name); + } + } + + // Skip members with delegate types: + var memberType = memberInfo.Type; + if (memberType.TypeKind == TypeKind.Delegate) + { + continue; + } + + var extractedType = memberType; + if (memberType.IsNullableOfT()) + { + // extract the T from a Nullable + extractedType = ((INamedTypeSymbol)memberType).TypeArguments[0]; + } + + // Checking "extractedType" here: + if (typesChain.Contains(extractedType)) + { + continue; // Move to the next member + } + + var propertyDataClassAttributes = GetDataClassificationAttributes(m, symbols); + if (propertyDataClassAttributes.Count > 0) + { + return true; + } + + if (extractedType.IsRecord && // Going inside record types only + extractedType.Kind == SymbolKind.NamedType && + !extractedType.IsSpecialType(symbols)) + { + _ = typesChain.Add(namedType); + if (RecordHasSensitivePublicMembers(extractedType, typesChain, symbols, token)) + { + return true; + } + + _ = typesChain.Remove(namedType); + } + } + + // This is to support inheritance, i.e. to fetch public members of base class(-es) as well: + namedType = namedType.BaseType; + } + + return false; + } + } +} diff --git a/src/Generators/Microsoft.Gen.Logging/Parsing/Parser.cs b/src/Generators/Microsoft.Gen.Logging/Parsing/Parser.cs index 257095c4a06..bc7b101fdcc 100644 --- a/src/Generators/Microsoft.Gen.Logging/Parsing/Parser.cs +++ b/src/Generators/Microsoft.Gen.Logging/Parsing/Parser.cs @@ -93,6 +93,7 @@ public IReadOnlyList GetLogTypes(IEnumerable parameterSymbols[lp] = paramSymbol; + var foundDataClassificationAttributesInProps = false; var logPropertiesAttribute = ParserUtilities.GetSymbolAttributeAnnotationOrDefault(symbols.LogPropertiesAttribute, paramSymbol); if (logPropertiesAttribute is not null) { @@ -101,7 +102,8 @@ public IReadOnlyList GetLogTypes(IEnumerable lm, lp, paramSymbol, - symbols)) + symbols, + ref foundDataClassificationAttributesInProps)) { lp.Properties.Clear(); } @@ -134,36 +136,49 @@ public IReadOnlyList GetLogTypes(IEnumerable } bool forceAsTemplateParam = false; - if (lp.IsLogger && lm.TemplateToParameterName.ContainsKey(lp.Name)) + bool parameterInTemplate = lm.TemplateToParameterName.ContainsKey(lp.Name); + var loggingProperties = logPropertiesAttribute != null || tagProviderAttribute != null; + if (lp.IsLogger && parameterInTemplate) { Diag(DiagDescriptors.ShouldntMentionLoggerInMessage, attrLoc, lp.Name); forceAsTemplateParam = true; } - else if (lp.IsException && lm.TemplateToParameterName.ContainsKey(lp.Name)) + else if (lp.IsException && parameterInTemplate) { Diag(DiagDescriptors.ShouldntMentionExceptionInMessage, attrLoc, lp.Name); forceAsTemplateParam = true; } - else if (lp.IsLogLevel && lm.TemplateToParameterName.ContainsKey(lp.Name)) + else if (lp.IsLogLevel && parameterInTemplate) { Diag(DiagDescriptors.ShouldntMentionLogLevelInMessage, attrLoc, lp.Name); forceAsTemplateParam = true; } -#pragma warning disable S1067 // Expressions should not be too complex else if (lp.IsNormalParameter - && !lm.TemplateToParameterName.ContainsKey(lp.Name) - && logPropertiesAttribute == null - && tagProviderAttribute == null + && !parameterInTemplate + && !loggingProperties && !string.IsNullOrEmpty(lm.Message)) { Diag(DiagDescriptors.ParameterHasNoCorrespondingTemplate, paramSymbol.GetLocation(), lp.Name); } -#pragma warning restore S1067 // Expressions should not be too complex + + var purelyStructuredLoggingParameter = loggingProperties && !parameterInTemplate; + if (lp.IsNormalParameter && + !lp.HasDataClassification && + !purelyStructuredLoggingParameter && + paramSymbol.Type.IsRecord) + { + if (foundDataClassificationAttributesInProps || + RecordHasSensitivePublicMembers(paramSymbol.Type, symbols)) + { + Diag(DiagDescriptors.RecordTypeSensitiveArgumentIsInTemplate, paramSymbol.GetLocation(), lp.Name, lm.Name); + keepMethod = false; + } + } lm.Parameters.Add(lp); if (lp.IsNormalParameter || forceAsTemplateParam) { - if (lm.TemplateToParameterName.ContainsKey(lp.Name)) + if (parameterInTemplate) { lp.UsedAsTemplate = true; } diff --git a/src/Generators/Microsoft.Gen.Logging/Parsing/Resources.Designer.cs b/src/Generators/Microsoft.Gen.Logging/Parsing/Resources.Designer.cs index e5fb9fc6494..63fce874d64 100644 --- a/src/Generators/Microsoft.Gen.Logging/Parsing/Resources.Designer.cs +++ b/src/Generators/Microsoft.Gen.Logging/Parsing/Resources.Designer.cs @@ -456,6 +456,24 @@ internal static string ParameterHasNoCorrespondingTemplateTitle { } } + /// + /// Looks up a localized string similar to Parameter "{0}" of logging method "{1}" has sensitive field/property in its type. + /// + internal static string RecordTypeSensitiveArgumentIsInTemplateMessage { + get { + return ResourceManager.GetString("RecordTypeSensitiveArgumentIsInTemplateMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The logging method parameter leaks sensitive data. + /// + internal static string RecordTypeSensitiveArgumentIsInTemplateTitle { + get { + return ResourceManager.GetString("RecordTypeSensitiveArgumentIsInTemplateTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.. /// diff --git a/src/Generators/Microsoft.Gen.Logging/Parsing/Resources.resx b/src/Generators/Microsoft.Gen.Logging/Parsing/Resources.resx index 4dab52fa763..6273eee7246 100644 --- a/src/Generators/Microsoft.Gen.Logging/Parsing/Resources.resx +++ b/src/Generators/Microsoft.Gen.Logging/Parsing/Resources.resx @@ -327,4 +327,10 @@ Attribute can't be used in this context - + + Parameter "{0}" of logging method "{1}" has a sensitive field/property in its type + + + The logging method parameter leaks sensitive data + + \ No newline at end of file diff --git a/src/Generators/Microsoft.Gen.Logging/Parsing/SymbolLoader.cs b/src/Generators/Microsoft.Gen.Logging/Parsing/SymbolLoader.cs index 11af20b794a..4dce3561635 100644 --- a/src/Generators/Microsoft.Gen.Logging/Parsing/SymbolLoader.cs +++ b/src/Generators/Microsoft.Gen.Logging/Parsing/SymbolLoader.cs @@ -52,7 +52,7 @@ internal static class SymbolLoader { var loggerSymbol = compilation.GetTypeByMetadataName(ILoggerType); var logLevelSymbol = compilation.GetTypeByMetadataName(LogLevelType); - var logMethodAttributeSymbol = compilation.GetTypeByMetadataName(LoggerMessageAttribute); + var loggerMessageAttributeSymbol = compilation.GetTypeByMetadataName(LoggerMessageAttribute); var logPropertiesAttributeSymbol = compilation.GetTypeByMetadataName(LogPropertiesAttribute); var tagProviderAttributeSymbol = compilation.GetTypeByMetadataName(TagProviderAttribute); var tagCollectorSymbol = compilation.GetTypeByMetadataName(ITagCollectorType); @@ -62,7 +62,7 @@ internal static class SymbolLoader #pragma warning disable S1067 // Expressions should not be too complex if (loggerSymbol == null || logLevelSymbol == null - || logMethodAttributeSymbol == null + || loggerMessageAttributeSymbol == null || logPropertiesAttributeSymbol == null || tagProviderAttributeSymbol == null || tagCollectorSymbol == null @@ -97,7 +97,7 @@ internal static class SymbolLoader return new( compilation, - logMethodAttributeSymbol, + loggerMessageAttributeSymbol, logPropertiesAttributeSymbol, tagProviderAttributeSymbol, logPropertyIgnoreAttributeSymbol, diff --git a/src/Generators/Microsoft.Gen.Metrics/DiagDescriptors.cs b/src/Generators/Microsoft.Gen.Metrics/DiagDescriptors.cs index c888a0c4728..87f17c25615 100644 --- a/src/Generators/Microsoft.Gen.Metrics/DiagDescriptors.cs +++ b/src/Generators/Microsoft.Gen.Metrics/DiagDescriptors.cs @@ -3,129 +3,130 @@ using Microsoft.CodeAnalysis; using Microsoft.Gen.Shared; +using Microsoft.Shared.DiagnosticIds; namespace Microsoft.Gen.Metrics; internal sealed class DiagDescriptors : DiagDescriptorsBase { - private const string Category = "Metrics"; + private const string Category = nameof(DiagnosticIds.Metrics); public static DiagnosticDescriptor ErrorInvalidMethodName { get; } = Make( - id: "METGEN000", + id: DiagnosticIds.Metrics.METGEN000, title: Resources.ErrorInvalidMethodNameTitle, messageFormat: Resources.ErrorInvalidMethodNameMessage, category: Category); public static DiagnosticDescriptor ErrorInvalidParameterName { get; } = Make( - id: "METGEN001", + id: DiagnosticIds.Metrics.METGEN001, title: Resources.ErrorInvalidParameterNameTitle, messageFormat: Resources.ErrorInvalidParameterNameMessage, category: Category); public static DiagnosticDescriptor ErrorInvalidMetricName { get; } = Make( - id: "METGEN002", + id: DiagnosticIds.Metrics.METGEN002, title: Resources.ErrorInvalidMetricNameTitle, messageFormat: Resources.ErrorInvalidMetricNameMessage, category: Category); public static DiagnosticDescriptor ErrorMetricNameReuse { get; } = Make( - id: "METGEN003", + id: DiagnosticIds.Metrics.METGEN003, title: Resources.ErrorMetricNameReuseTitle, messageFormat: Resources.ErrorMetricNameReuseMessage, category: Category); public static DiagnosticDescriptor ErrorInvalidMethodReturnType { get; } = Make( - id: "METGEN004", + id: DiagnosticIds.Metrics.METGEN004, title: Resources.ErrorInvalidMethodReturnTypeTitle, messageFormat: Resources.ErrorInvalidMethodReturnTypeMessage, category: Category); public static DiagnosticDescriptor ErrorMissingMeter { get; } = Make( - id: "METGEN005", + id: DiagnosticIds.Metrics.METGEN005, title: Resources.ErrorMissingMeterTitle, messageFormat: Resources.ErrorMissingMeterMessage, category: Category); public static DiagnosticDescriptor ErrorNotPartialMethod { get; } = Make( - id: "METGEN006", + id: DiagnosticIds.Metrics.METGEN006, title: Resources.ErrorNotPartialMethodTitle, messageFormat: Resources.ErrorNotPartialMethodMessage, category: Category); public static DiagnosticDescriptor ErrorMethodIsGeneric { get; } = Make( - id: "METGEN007", + id: DiagnosticIds.Metrics.METGEN007, title: Resources.ErrorMethodIsGenericTitle, messageFormat: Resources.ErrorMethodIsGenericMessage, category: Category); public static DiagnosticDescriptor ErrorMethodHasBody { get; } = Make( - id: "METGEN008", + id: DiagnosticIds.Metrics.METGEN008, title: Resources.ErrorMethodHasBodyTitle, messageFormat: Resources.ErrorMethodHasBodyMessage, category: Category); public static DiagnosticDescriptor ErrorInvalidTagNames { get; } = Make( - id: "METGEN009", + id: DiagnosticIds.Metrics.METGEN009, title: Resources.ErrorInvalidTagNamesMessage, messageFormat: Resources.ErrorInvalidTagNamesTitle, category: Category); public static DiagnosticDescriptor ErrorNotStaticMethod { get; } = Make( - id: "METGEN010", + id: DiagnosticIds.Metrics.METGEN010, title: Resources.ErrorNotStaticMethodTitle, messageFormat: Resources.ErrorNotStaticMethodMessage, category: Category); public static DiagnosticDescriptor ErrorDuplicateTagName { get; } = Make( - id: "METGEN011", + id: DiagnosticIds.Metrics.METGEN011, title: Resources.ErrorDuplicateTagNameTitle, messageFormat: Resources.ErrorDuplicateTagNameMessage, category: Category); public static DiagnosticDescriptor ErrorInvalidTagNameType { get; } = Make( - id: "METGEN012", + id: DiagnosticIds.Metrics.METGEN012, title: Resources.ErrorInvalidTagTypeTitle, messageFormat: Resources.ErrorInvalidTagTypeMessage, category: Category); public static DiagnosticDescriptor ErrorTooManyTagNames { get; } = Make( - id: "METGEN013", + id: DiagnosticIds.Metrics.METGEN013, title: Resources.ErrorTooManyTagNamesTitle, messageFormat: Resources.ErrorTooManyTagNamesMessage, category: Category); public static DiagnosticDescriptor ErrorInvalidAttributeGenericType { get; } = Make( - id: "METGEN014", + id: DiagnosticIds.Metrics.METGEN014, title: Resources.ErrorInvalidAttributeGenericTypeTitle, messageFormat: Resources.ErrorInvalidAttributeGenericTypeMessage, category: Category); public static DiagnosticDescriptor ErrorInvalidMethodReturnTypeLocation { get; } = Make( - id: "METGEN015", + id: DiagnosticIds.Metrics.METGEN015, title: Resources.ErrorInvalidMethodReturnTypeLocationTitle, messageFormat: Resources.ErrorInvalidMethodReturnTypeLocationMessage, category: Category); public static DiagnosticDescriptor ErrorInvalidMethodReturnTypeArity { get; } = Make( - id: "METGEN016", + id: DiagnosticIds.Metrics.METGEN016, title: Resources.ErrorInvalidMethodReturnTypeArityTitle, messageFormat: Resources.ErrorInvalidMethodReturnTypeArityMessage, category: Category); public static DiagnosticDescriptor ErrorGaugeNotSupported { get; } = Make( - id: "METGEN017", + id: DiagnosticIds.Metrics.METGEN017, title: Resources.ErrorGaugeNotSupportedTitle, messageFormat: Resources.ErrorGaugeNotSupportedMessage, category: Category); public static DiagnosticDescriptor ErrorXmlNotLoadedCorrectly { get; } = Make( - id: "METGEN018", + id: DiagnosticIds.Metrics.METGEN018, title: Resources.ErrorXmlNotLoadedCorrectlyTitle, messageFormat: Resources.ErrorXmlNotLoadedCorrectlyMessage, category: Category); public static DiagnosticDescriptor ErrorTagTypeCycleDetected { get; } = Make( - id: "METGEN019", + id: DiagnosticIds.Metrics.METGEN019, title: Resources.ErrorTagTypeCycleDetectedTitle, messageFormat: Resources.ErrorTagTypeCycleDetectedMessage, category: Category); diff --git a/src/Generators/Microsoft.Gen.Metrics/Emitter.cs b/src/Generators/Microsoft.Gen.Metrics/Emitter.cs index d6b0decec0d..0e6e7da0091 100644 --- a/src/Generators/Microsoft.Gen.Metrics/Emitter.cs +++ b/src/Generators/Microsoft.Gen.Metrics/Emitter.cs @@ -297,7 +297,7 @@ private void GenTagList(MetricMethod metricMethod) foreach (var tagName in metricMethod.TagKeys) { var paramName = GetSanitizedParamName(tagName); - OutLn($"new global::System.Collections.Generic.KeyValuePair(\"{paramName}\", {paramName}),"); + OutLn($"new global::System.Collections.Generic.KeyValuePair(\"{tagName}\", {paramName}),"); } } else diff --git a/src/Generators/Microsoft.Gen.Metrics/Microsoft.Gen.Metrics.csproj b/src/Generators/Microsoft.Gen.Metrics/Microsoft.Gen.Metrics.csproj index 9ac53155aa9..e657b76e812 100644 --- a/src/Generators/Microsoft.Gen.Metrics/Microsoft.Gen.Metrics.csproj +++ b/src/Generators/Microsoft.Gen.Metrics/Microsoft.Gen.Metrics.csproj @@ -1,7 +1,7 @@ Microsoft.Gen.Metrics - Code generator to support Microsoft.Extensions.Diagnostics.Extra's metering features. + Code generator to support Microsoft.Extensions.Telemetry's metering features. Telemetry diff --git a/src/Generators/Microsoft.Gen.Metrics/Parser.cs b/src/Generators/Microsoft.Gen.Metrics/Parser.cs index ed7342f999a..20c60fb0529 100644 --- a/src/Generators/Microsoft.Gen.Metrics/Parser.cs +++ b/src/Generators/Microsoft.Gen.Metrics/Parser.cs @@ -19,7 +19,7 @@ namespace Microsoft.Gen.Metrics; internal sealed class Parser { - private const int MaxTagNames = 20; + private const int MaxTagNames = 30; private static readonly Regex _regex = new("^[A-Z]+[A-za-z0-9]*$", RegexOptions.Compiled); private static readonly Regex _regexTagNames = new("^[A-Za-z]+[A-Za-z0-9_.:-]*$", RegexOptions.Compiled); @@ -656,7 +656,7 @@ private StrongTypeAttributeParameters ExtractStrongTypeAttributeParameters( ex.NamedType.ToDisplayString()); } - if (strongTypeAttributeParameters.StrongTypeConfigs.Count > MaxTagNames) + if (strongTypeAttributeParameters.TagHashSet.Count > MaxTagNames) { Diag(DiagDescriptors.ErrorTooManyTagNames, strongTypeSymbol.Locations[0]); } diff --git a/src/Generators/Microsoft.Gen.MetricsReports/Directory.Build.props b/src/Generators/Microsoft.Gen.MetricsReports/Directory.Build.props new file mode 100644 index 00000000000..142bcd3852c --- /dev/null +++ b/src/Generators/Microsoft.Gen.MetricsReports/Directory.Build.props @@ -0,0 +1,9 @@ + + + + dev + + + \ No newline at end of file diff --git a/src/Generators/Microsoft.Gen.MetricsReports/Microsoft.Gen.MetricsReports.csproj b/src/Generators/Microsoft.Gen.MetricsReports/Microsoft.Gen.MetricsReports.csproj index 8f152980d13..0448e091cbf 100644 --- a/src/Generators/Microsoft.Gen.MetricsReports/Microsoft.Gen.MetricsReports.csproj +++ b/src/Generators/Microsoft.Gen.MetricsReports/Microsoft.Gen.MetricsReports.csproj @@ -11,7 +11,6 @@ - dev 65 85 diff --git a/src/Generators/Shared/DiagDescriptorsBase.cs b/src/Generators/Shared/DiagDescriptorsBase.cs index 03cc0a2eec3..ece15b7a446 100644 --- a/src/Generators/Shared/DiagDescriptorsBase.cs +++ b/src/Generators/Shared/DiagDescriptorsBase.cs @@ -3,6 +3,7 @@ using System; using Microsoft.CodeAnalysis; +using Microsoft.Shared.DiagnosticIds; #pragma warning disable CA1716 namespace Microsoft.Gen.Shared; @@ -13,10 +14,6 @@ namespace Microsoft.Gen.Shared; #endif internal class DiagDescriptorsBase { -#pragma warning disable S1075 // URIs should not be hardcoded - public const string HelpLinkBase = "https://TODO/"; -#pragma warning restore S1075 // URIs should not be hardcoded - protected static DiagnosticDescriptor Make( string id, string title, @@ -25,6 +22,9 @@ protected static DiagnosticDescriptor Make( DiagnosticSeverity defaultSeverity = DiagnosticSeverity.Error, bool isEnabledByDefault = true) { +#pragma warning disable CA1305 // Specify IFormatProvider +#pragma warning disable CA1863 // Use 'CompositeFormat' +#pragma warning disable CS0436 // Type conflicts with imported type return new( id, title, @@ -33,9 +33,10 @@ protected static DiagnosticDescriptor Make( defaultSeverity, isEnabledByDefault, null, -#pragma warning disable CA1308 // Normalize strings to uppercase - HelpLinkBase + id.ToLowerInvariant(), -#pragma warning restore CA1308 // Normalize strings to uppercase + string.Format(DiagnosticIds.UrlFormat, id), Array.Empty()); +#pragma warning restore CS0436 // Type conflicts with imported type +#pragma warning restore CA1863 // Use 'CompositeFormat' +#pragma warning restore CA1305 // Specify IFormatProvider } } diff --git a/src/LegacySupport/.editorconfig b/src/LegacySupport/.editorconfig index 2054f20b775..4ae3d209188 100644 --- a/src/LegacySupport/.editorconfig +++ b/src/LegacySupport/.editorconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:52Z +# Generated : 2023-10-22 00:37:46Z # Max Tier : 2147483647 # Attributes: general, performance, production # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CodeStyle, Microsoft.CodeAnalysis.CSharp.CodeStyle, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp, StyleCop.Analyzers @@ -869,7 +869,7 @@ dotnet_diagnostic.CA1860.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = warning @@ -904,6 +904,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = warning + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = warning + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1721,62 +1731,77 @@ dotnet_diagnostic.CA5405.severity = warning # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = warning # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = warning # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = warning # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = warning # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = warning # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = warning # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = warning # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = warning # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = warning # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = warning # Title : Set MSBuild property 'GenerateDocumentationFile' to 'true' @@ -2410,6 +2435,26 @@ dotnet_diagnostic.IDE0300.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0301 dotnet_diagnostic.IDE0301.severity = suggestion +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0302 +dotnet_diagnostic.IDE0302.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0303 +dotnet_diagnostic.IDE0303.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0304 +dotnet_diagnostic.IDE0304.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0305 +dotnet_diagnostic.IDE0305.severity = none + # Title : Delegate invocation can be simplified. # Category : Style # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1005 diff --git a/src/Libraries/.editorconfig b/src/Libraries/.editorconfig index 6aa37ca1689..30a0b07945d 100644 --- a/src/Libraries/.editorconfig +++ b/src/Libraries/.editorconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:51Z +# Generated : 2023-10-22 00:37:46Z # Max Tier : 2147483647 # Attributes: api, general, performance, production # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CodeStyle, Microsoft.CodeAnalysis.CSharp.CodeStyle, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp, StyleCop.Analyzers @@ -889,7 +889,7 @@ dotnet_diagnostic.CA1860.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = warning @@ -924,6 +924,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = warning + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = warning + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1743,62 +1753,77 @@ dotnet_diagnostic.CA5405.severity = warning # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = warning # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = warning # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = warning # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = warning # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = warning # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = warning # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = warning # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = warning # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = warning # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = warning # Title : Set MSBuild property 'GenerateDocumentationFile' to 'true' @@ -2432,6 +2457,26 @@ dotnet_diagnostic.IDE0300.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0301 dotnet_diagnostic.IDE0301.severity = suggestion +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0302 +dotnet_diagnostic.IDE0302.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0303 +dotnet_diagnostic.IDE0303.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0304 +dotnet_diagnostic.IDE0304.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0305 +dotnet_diagnostic.IDE0305.severity = none + # Title : Delegate invocation can be simplified. # Category : Style # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1005 diff --git a/src/Libraries/Microsoft.AspNetCore.AsyncState/README.md b/src/Libraries/Microsoft.AspNetCore.AsyncState/README.md index e845566c06f..2eb02048a36 100644 --- a/src/Libraries/Microsoft.AspNetCore.AsyncState/README.md +++ b/src/Libraries/Microsoft.AspNetCore.AsyncState/README.md @@ -1 +1,55 @@ -README +# Microsoft.AspNetCore.AsyncState + +This provides the ability to store and retrieve state objects that flow with the current `HttpContext` across asynchronous operations. See `Microsoft.Extensions.AsyncState` for additional information. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.AspNetCore.AsyncState +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Example + +### Registering Services + +The services can be registered using the following method: + +```csharp +public static IServiceCollection AddAsyncStateHttpContext(this IServiceCollection services) +``` + +### Consuming Services + +The `IAsyncContext` can be injected wherever async state is needed. For example: + +```csharp +public class MyClass +{ + public MyClass(IAsyncContext asyncContext) { Context = asyncContext } + + private IAsyncContext Context { get; } + + public async Task DoWork() + { + var state = Context.Get(); + // or + Context.Set(new MyState()); + // or + if (Context.TryGet(out var state)) { ... } + } +} +``` + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HeaderNormalizer.cs b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HeaderNormalizer.cs new file mode 100644 index 00000000000..600811d598a --- /dev/null +++ b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HeaderNormalizer.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Compliance.Classification; + +internal static class HeaderNormalizer +{ + public static string[] PrepareNormalizedHeaderNames(KeyValuePair[] headers, string prefix) + { + var normalizedHeaders = new string[headers.Length]; + + for (int i = 0; i < headers.Length; i++) + { + normalizedHeaders[i] = prefix + Normalize(headers[i].Key); + } + + return normalizedHeaders; + } + + [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", + Justification = "Normalization to lower case is required by OTel's semantic conventions")] + private static string Normalize(string header) + { + return header.ToLowerInvariant(); + } +} diff --git a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HeaderReader.cs b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HeaderReader.cs index eb5792dad40..3c02adb498f 100644 --- a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HeaderReader.cs +++ b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HeaderReader.cs @@ -16,28 +16,32 @@ internal sealed class HeaderReader { private readonly IRedactorProvider _redactorProvider; private readonly KeyValuePair[] _headers; + private readonly string[] _normalizedHeaders; - public HeaderReader(IDictionary headersToLog, IRedactorProvider redactorProvider) + public HeaderReader(IDictionary headersToLog, IRedactorProvider redactorProvider, string prefix) { _redactorProvider = redactorProvider; _headers = headersToLog.Count == 0 ? [] : headersToLog.ToArray(); + _normalizedHeaders = HeaderNormalizer.PrepareNormalizedHeaderNames(_headers, prefix); } - public void Read(IHeaderDictionary headers, IList> logContext, string prefix) + public void Read(IHeaderDictionary headers, IList> logContext) { if (headers.Count == 0) { return; } - foreach (var header in _headers) + for (int i = 0; i < _headers.Length; i++) { + var header = _headers[i]; + if (headers.TryGetValue(header.Key, out var headerValue)) { var provider = _redactorProvider.GetRedactor(header.Value); var redacted = provider.Redact(headerValue.ToString()); - logContext.Add(new(prefix + header.Key, redacted)); + logContext.Add(new(_normalizedHeaders[i], redacted)); } } } diff --git a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HttpLoggingRedactionInterceptor.cs b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HttpLoggingRedactionInterceptor.cs index 330c7d0c376..050f0bb092d 100644 --- a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HttpLoggingRedactionInterceptor.cs +++ b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HttpLoggingRedactionInterceptor.cs @@ -55,8 +55,8 @@ public HttpLoggingRedactionInterceptor( _requestPathLogMode = EnsureRequestPathLoggingModeIsValid(optionsValue.RequestPathLoggingMode); _parameterRedactionMode = optionsValue.RequestPathParameterRedactionMode; - _requestHeadersReader = new(optionsValue.RequestHeadersDataClasses, redactorProvider); - _responseHeadersReader = new(optionsValue.ResponseHeadersDataClasses, redactorProvider); + _requestHeadersReader = new(optionsValue.RequestHeadersDataClasses, redactorProvider, HttpLoggingTagNames.RequestHeaderPrefix); + _responseHeadersReader = new(optionsValue.ResponseHeadersDataClasses, redactorProvider, HttpLoggingTagNames.ResponseHeaderPrefix); _excludePathStartsWith = optionsValue.ExcludePathStartsWith.ToArray(); } @@ -126,7 +126,7 @@ public ValueTask OnRequestAsync(HttpLoggingInterceptorContext logContext) if (logContext.TryDisable(HttpLoggingFields.RequestHeaders)) { - _requestHeadersReader.Read(context.Request.Headers, logContext.Parameters, HttpLoggingTagNames.RequestHeaderPrefix); + _requestHeadersReader.Read(context.Request.Headers, logContext.Parameters); } return default; @@ -138,7 +138,7 @@ public ValueTask OnResponseAsync(HttpLoggingInterceptorContext logContext) if (logContext.TryDisable(HttpLoggingFields.ResponseHeaders)) { - _responseHeadersReader.Read(context.Response.Headers, logContext.Parameters, HttpLoggingTagNames.ResponseHeaderPrefix); + _responseHeadersReader.Read(context.Response.Headers, logContext.Parameters); } // Don't enrich if we're not going to log any part of the response diff --git a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HttpLoggingServiceCollectionExtensions.cs b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HttpLoggingServiceCollectionExtensions.cs index 1ac60bb5598..a8c6da4bd75 100644 --- a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HttpLoggingServiceCollectionExtensions.cs +++ b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HttpLoggingServiceCollectionExtensions.cs @@ -18,7 +18,7 @@ namespace Microsoft.Extensions.DependencyInjection; /// /// Extension methods to register the HTTP logging feature within the service. /// -[Experimental(diagnosticId: Experiments.HttpLogging, UrlFormat = Experiments.UrlFormat)] +[Experimental(diagnosticId: DiagnosticIds.Experiments.HttpLogging, UrlFormat = DiagnosticIds.UrlFormat)] public static class HttpLoggingServiceCollectionExtensions { /// diff --git a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HttpLoggingTagNames.cs b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HttpLoggingTagNames.cs index 548c82babfc..4ce5bb6055b 100644 --- a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HttpLoggingTagNames.cs +++ b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/HttpLoggingTagNames.cs @@ -19,7 +19,7 @@ public static class HttpLoggingTagNames /// /// HTTP Host. /// - public const string Host = "Host"; + public const string Host = "server.address"; /// /// HTTP Method. @@ -34,12 +34,12 @@ public static class HttpLoggingTagNames /// /// HTTP Request Headers prefix. /// - public const string RequestHeaderPrefix = "RequestHeader."; + public const string RequestHeaderPrefix = "http.request.header."; /// /// HTTP Response Headers prefix. /// - public const string ResponseHeaderPrefix = "ResponseHeader."; + public const string ResponseHeaderPrefix = "http.response.header."; /// /// HTTP Request Body. diff --git a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/IHttpLogEnricher.cs b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/IHttpLogEnricher.cs index f559415ff94..5c221368cd5 100644 --- a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/IHttpLogEnricher.cs +++ b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/IHttpLogEnricher.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Diagnostics.Logging; /// /// Interface for implementing log enrichers for incoming HTTP requests. /// -[Experimental(diagnosticId: Experiments.HttpLogging, UrlFormat = Experiments.UrlFormat)] +[Experimental(diagnosticId: DiagnosticIds.Experiments.HttpLogging, UrlFormat = DiagnosticIds.UrlFormat)] public interface IHttpLogEnricher { /// diff --git a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/LoggingRedactionOptions.cs b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/LoggingRedactionOptions.cs index 69d46ea4735..0f6f1324e97 100644 --- a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/LoggingRedactionOptions.cs +++ b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/LoggingRedactionOptions.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Diagnostics.Logging; /// /// Top-level model for redacting incoming HTTP requests and their corresponding responses. /// -[Experimental(diagnosticId: Experiments.HttpLogging, UrlFormat = Experiments.UrlFormat)] +[Experimental(diagnosticId: DiagnosticIds.Experiments.HttpLogging, UrlFormat = DiagnosticIds.UrlFormat)] public class LoggingRedactionOptions { private const IncomingPathLoggingMode DefaultRequestPathLoggingMode = IncomingPathLoggingMode.Formatted; diff --git a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/RequestHeadersLogEnricher.cs b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/RequestHeadersLogEnricher.cs index 8d0d8b028ab..6388e51db97 100644 --- a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/RequestHeadersLogEnricher.cs +++ b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/RequestHeadersLogEnricher.cs @@ -2,7 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Frozen; +using System.Collections.Generic; +using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Compliance.Classification; using Microsoft.Extensions.Compliance.Redaction; @@ -17,7 +18,8 @@ namespace Microsoft.AspNetCore.Diagnostics.Logging; /// internal sealed class RequestHeadersLogEnricher : ILogEnricher { - private readonly FrozenDictionary _headersDataClasses; + private readonly KeyValuePair[] _headersDataClasses; + private readonly string[] _normalizedHeaders; private readonly IHttpContextAccessor _httpContextAccessor; private readonly IRedactorProvider? _redactorProvider; @@ -33,11 +35,10 @@ public RequestHeadersLogEnricher(IHttpContextAccessor httpContextAccessor, IOpti var opt = Throw.IfMemberNull(options, options.Value); _httpContextAccessor = httpContextAccessor; - _headersDataClasses = opt.HeadersDataClasses.Count == 0 - ? FrozenDictionary.Empty - : opt.HeadersDataClasses.ToFrozenDictionary(StringComparer.Ordinal); + _headersDataClasses = opt.HeadersDataClasses.Count == 0 ? [] : opt.HeadersDataClasses.ToArray(); + _normalizedHeaders = HeaderNormalizer.PrepareNormalizedHeaderNames(_headersDataClasses, HttpLoggingTagNames.RequestHeaderPrefix); - if (_headersDataClasses.Count > 0) + if (_headersDataClasses.Length > 0) { _redactorProvider = Throw.IfNull(redactorProvider); } @@ -54,20 +55,22 @@ public void Enrich(IEnrichmentTagCollector collector) var request = _httpContextAccessor.HttpContext.Request; - if (_headersDataClasses.Count == 0) + if (_headersDataClasses.Length == 0) { return; } - if (_headersDataClasses.Count != 0) + if (_headersDataClasses.Length != 0) { - foreach (var header in _headersDataClasses) + for (int i = 0; i < _headersDataClasses.Length; i++) { + var header = _headersDataClasses[i]; + if (request.Headers.TryGetValue(header.Key, out var headerValue) && !string.IsNullOrEmpty(headerValue)) { var redactor = _redactorProvider!.GetRedactor(header.Value); var redacted = redactor.Redact(headerValue); - collector.Add(header.Key, redacted); + collector.Add(_normalizedHeaders[i], redacted); } } } diff --git a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/RequestHeadersLogEnricherOptions.cs b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/RequestHeadersLogEnricherOptions.cs index bf4e3456fd0..81483224810 100644 --- a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/RequestHeadersLogEnricherOptions.cs +++ b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Logging/RequestHeadersLogEnricherOptions.cs @@ -22,7 +22,7 @@ public class RequestHeadersLogEnricherOptions /// Default value is an empty dictionary. /// [Required] - [Experimental(diagnosticId: Experiments.Telemetry, UrlFormat = Experiments.UrlFormat)] + [Experimental(diagnosticId: DiagnosticIds.Experiments.Telemetry, UrlFormat = DiagnosticIds.UrlFormat)] #pragma warning disable CA2227 // Collection properties should be read only public IDictionary HeadersDataClasses { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); #pragma warning restore CA2227 // Collection properties should be read only diff --git a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Microsoft.AspNetCore.Diagnostics.Middleware.csproj b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Microsoft.AspNetCore.Diagnostics.Middleware.csproj index 6910eea7025..89262b07f64 100644 --- a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Microsoft.AspNetCore.Diagnostics.Middleware.csproj +++ b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Microsoft.AspNetCore.Diagnostics.Middleware.csproj @@ -22,7 +22,7 @@ normal - 99 + 100 85 @@ -31,10 +31,10 @@ - + - + diff --git a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Microsoft.AspNetCore.Diagnostics.Middleware.json b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Microsoft.AspNetCore.Diagnostics.Middleware.json index 777e30c8db5..e76e745c5f1 100644 --- a/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Microsoft.AspNetCore.Diagnostics.Middleware.json +++ b/src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Microsoft.AspNetCore.Diagnostics.Middleware.json @@ -1,6 +1,24 @@ { "Name": "Microsoft.AspNetCore.Diagnostics.Middleware, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", "Types": [ + { + "Type": "static class Microsoft.Extensions.DependencyInjection.HttpLoggingServiceCollectionExtensions", + "Stage": "Experimental", + "Methods": [ + { + "Member": "static Microsoft.Extensions.DependencyInjection.IServiceCollection Microsoft.Extensions.DependencyInjection.HttpLoggingServiceCollectionExtensions.AddHttpLogEnricher(this Microsoft.Extensions.DependencyInjection.IServiceCollection services);", + "Stage": "Experimental" + }, + { + "Member": "static Microsoft.Extensions.DependencyInjection.IServiceCollection Microsoft.Extensions.DependencyInjection.HttpLoggingServiceCollectionExtensions.AddHttpLoggingRedaction(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action? configure = null);", + "Stage": "Experimental" + }, + { + "Member": "static Microsoft.Extensions.DependencyInjection.IServiceCollection Microsoft.Extensions.DependencyInjection.HttpLoggingServiceCollectionExtensions.AddHttpLoggingRedaction(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.Configuration.IConfigurationSection section);", + "Stage": "Experimental" + } + ] + }, { "Type": "static class Microsoft.AspNetCore.Diagnostics.Logging.HttpLoggingTagNames", "Stage": "Stable", @@ -13,7 +31,7 @@ { "Member": "const string Microsoft.AspNetCore.Diagnostics.Logging.HttpLoggingTagNames.Host", "Stage": "Stable", - "Value": "Host" + "Value": "server.address" }, { "Member": "const string Microsoft.AspNetCore.Diagnostics.Logging.HttpLoggingTagNames.Method", @@ -33,7 +51,7 @@ { "Member": "const string Microsoft.AspNetCore.Diagnostics.Logging.HttpLoggingTagNames.RequestHeaderPrefix", "Stage": "Stable", - "Value": "RequestHeader_" + "Value": "http.request.header." }, { "Member": "const string Microsoft.AspNetCore.Diagnostics.Logging.HttpLoggingTagNames.ResponseBody", @@ -43,7 +61,7 @@ { "Member": "const string Microsoft.AspNetCore.Diagnostics.Logging.HttpLoggingTagNames.ResponseHeaderPrefix", "Stage": "Stable", - "Value": "ResponseHeader_" + "Value": "http.response.header." }, { "Member": "const string Microsoft.AspNetCore.Diagnostics.Logging.HttpLoggingTagNames.StatusCode", @@ -58,6 +76,16 @@ } ] }, + { + "Type": "interface Microsoft.AspNetCore.Diagnostics.Logging.IHttpLogEnricher", + "Stage": "Experimental", + "Methods": [ + { + "Member": "void Microsoft.AspNetCore.Diagnostics.Logging.IHttpLogEnricher.Enrich(Microsoft.Extensions.Diagnostics.Enrichment.IEnrichmentTagCollector collector, Microsoft.AspNetCore.Http.HttpContext httpContext);", + "Stage": "Experimental" + } + ] + }, { "Type": "enum Microsoft.AspNetCore.Diagnostics.Logging.IncomingPathLoggingMode", "Stage": "Stable", @@ -80,6 +108,42 @@ } ] }, + { + "Type": "class Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions", + "Stage": "Experimental", + "Methods": [ + { + "Member": "Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions.LoggingRedactionOptions();", + "Stage": "Experimental" + } + ], + "Properties": [ + { + "Member": "System.Collections.Generic.ISet Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions.ExcludePathStartsWith { get; set; }", + "Stage": "Experimental" + }, + { + "Member": "System.Collections.Generic.IDictionary Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions.RequestHeadersDataClasses { get; set; }", + "Stage": "Experimental" + }, + { + "Member": "Microsoft.AspNetCore.Diagnostics.Logging.IncomingPathLoggingMode Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions.RequestPathLoggingMode { get; set; }", + "Stage": "Experimental" + }, + { + "Member": "Microsoft.Extensions.Http.Diagnostics.HttpRouteParameterRedactionMode Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions.RequestPathParameterRedactionMode { get; set; }", + "Stage": "Experimental" + }, + { + "Member": "System.Collections.Generic.IDictionary Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions.ResponseHeadersDataClasses { get; set; }", + "Stage": "Experimental" + }, + { + "Member": "System.Collections.Generic.IDictionary Microsoft.AspNetCore.Diagnostics.Logging.LoggingRedactionOptions.RouteParameterDataClasses { get; set; }", + "Stage": "Experimental" + } + ] + }, { "Type": "static class Microsoft.AspNetCore.Diagnostics.Latency.RequestCheckpointConstants", "Stage": "Stable", @@ -136,7 +200,7 @@ ], "Properties": [ { - "Member": "System.Collections.Generic.IDictionary Microsoft.AspNetCore.Diagnostics.RequestHeadersLogEnricherOptions.Logging.HeadersDataClasses { get; set; }", + "Member": "System.Collections.Generic.IDictionary Microsoft.AspNetCore.Diagnostics.Logging.RequestHeadersLogEnricherOptions.HeadersDataClasses { get; set; }", "Stage": "Experimental" } ] @@ -194,4 +258,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/src/Libraries/Microsoft.AspNetCore.HeaderParsing/HeaderParsingFeature.cs b/src/Libraries/Microsoft.AspNetCore.HeaderParsing/HeaderParsingFeature.cs index 86f49b3aaeb..b3974603efb 100644 --- a/src/Libraries/Microsoft.AspNetCore.HeaderParsing/HeaderParsingFeature.cs +++ b/src/Libraries/Microsoft.AspNetCore.HeaderParsing/HeaderParsingFeature.cs @@ -198,12 +198,12 @@ private void CopyTo(Box to) } } - [LoggerMessage(LogLevel.Debug, "Can't parse header '{headerName}' due to '{error}'.")] + [LoggerMessage(LogLevel.Debug, "Can't parse header '{HeaderName}' due to '{Error}'.")] private partial void LogParsingError(string headerName, string error); - [LoggerMessage(LogLevel.Debug, "Using a default value for header '{headerName}'.")] + [LoggerMessage(LogLevel.Debug, "Using a default value for header '{HeaderName}'.")] private partial void LogDefaultUsage(string headerName); - [LoggerMessage(LogLevel.Debug, "Header '{headerName}' not found.")] + [LoggerMessage(LogLevel.Debug, "Header '{HeaderName}' not found.")] private partial void LogNotFound(string headerName); } diff --git a/src/Libraries/Microsoft.AspNetCore.HeaderParsing/Metric.cs b/src/Libraries/Microsoft.AspNetCore.HeaderParsing/Metric.cs index 944fa08970f..5237c8a528f 100644 --- a/src/Libraries/Microsoft.AspNetCore.HeaderParsing/Metric.cs +++ b/src/Libraries/Microsoft.AspNetCore.HeaderParsing/Metric.cs @@ -8,9 +8,9 @@ namespace Microsoft.AspNetCore.HeaderParsing; internal static partial class Metric { - [Counter("HeaderName", "Kind", Name = @"HeaderParsing.ParsingErrors")] + [Counter("aspnetcore.header_parsing.header.name", "error.type", Name = "aspnetcore.header_parsing.parse_errors")] public static partial ParsingErrorCounter CreateParsingErrorCounter(Meter meter); - [Counter("HeaderName", "Type", Name = @"HeaderParsing.CacheAccess")] + [Counter("aspnetcore.header_parsing.header.name", "aspnetcore.header_parsing.cache_access.type", Name = "aspnetcore.header_parsing.cache_accesses")] public static partial CacheAccessCounter CreateCacheAccessCounter(Meter meter); } diff --git a/src/Libraries/Microsoft.AspNetCore.HeaderParsing/Microsoft.AspNetCore.HeaderParsing.csproj b/src/Libraries/Microsoft.AspNetCore.HeaderParsing/Microsoft.AspNetCore.HeaderParsing.csproj index 34a815b038e..1a37baabfdf 100644 --- a/src/Libraries/Microsoft.AspNetCore.HeaderParsing/Microsoft.AspNetCore.HeaderParsing.csproj +++ b/src/Libraries/Microsoft.AspNetCore.HeaderParsing/Microsoft.AspNetCore.HeaderParsing.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/Libraries/Microsoft.AspNetCore.HeaderParsing/README.md b/src/Libraries/Microsoft.AspNetCore.HeaderParsing/README.md index 5240588b9af..65938529926 100644 --- a/src/Libraries/Microsoft.AspNetCore.HeaderParsing/README.md +++ b/src/Libraries/Microsoft.AspNetCore.HeaderParsing/README.md @@ -1,6 +1,6 @@ # Microsoft.AspNetCore.HeaderParsing -This package provides services to for strongly typed header parsing and value caching. +This package provides services for strongly typed header parsing and value caching. In particular: diff --git a/src/Libraries/Microsoft.AspNetCore.Testing/Directory.Build.props b/src/Libraries/Microsoft.AspNetCore.Testing/Directory.Build.props new file mode 100644 index 00000000000..4503261437d --- /dev/null +++ b/src/Libraries/Microsoft.AspNetCore.Testing/Directory.Build.props @@ -0,0 +1,11 @@ + + + + dev + EXTEXP0014 + + + + \ No newline at end of file diff --git a/src/Libraries/Microsoft.AspNetCore.Testing/Microsoft.AspNetCore.Testing.csproj b/src/Libraries/Microsoft.AspNetCore.Testing/Microsoft.AspNetCore.Testing.csproj index c9ce4832d0d..e7fd38cecca 100644 --- a/src/Libraries/Microsoft.AspNetCore.Testing/Microsoft.AspNetCore.Testing.csproj +++ b/src/Libraries/Microsoft.AspNetCore.Testing/Microsoft.AspNetCore.Testing.csproj @@ -12,7 +12,6 @@ - dev 100 100 diff --git a/src/Libraries/Microsoft.AspNetCore.Testing/Microsoft.AspNetCore.Testing.json b/src/Libraries/Microsoft.AspNetCore.Testing/Microsoft.AspNetCore.Testing.json index 691c1d000e6..1d15f67bee5 100644 --- a/src/Libraries/Microsoft.AspNetCore.Testing/Microsoft.AspNetCore.Testing.json +++ b/src/Libraries/Microsoft.AspNetCore.Testing/Microsoft.AspNetCore.Testing.json @@ -2,27 +2,33 @@ "Name": "Microsoft.AspNetCore.Testing, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", "Types": [ { - "Type": "static class Microsoft.AspNetCore.Testing.ServiceFakesExtensions", + "Type": "static class Microsoft.Extensions.Hosting.ServiceFakesHostExtensions", "Stage": "Experimental", "Methods": [ { - "Member": "static System.Net.Http.HttpClient Microsoft.AspNetCore.Testing.ServiceFakesExtensions.CreateClient(this Microsoft.Extensions.Hosting.IHost host, System.Net.Http.HttpMessageHandler? handler = null, System.Func? addressFilter = null);", + "Member": "static System.Net.Http.HttpClient Microsoft.Extensions.Hosting.ServiceFakesHostExtensions.CreateClient(this Microsoft.Extensions.Hosting.IHost host, System.Net.Http.HttpMessageHandler? handler = null, System.Func? addressFilter = null);", "Stage": "Experimental" }, { - "Member": "static System.Collections.Generic.IEnumerable Microsoft.AspNetCore.Testing.ServiceFakesExtensions.GetListenUris(this Microsoft.Extensions.Hosting.IHost host);", + "Member": "static System.Collections.Generic.IEnumerable Microsoft.Extensions.Hosting.ServiceFakesHostExtensions.GetListenUris(this Microsoft.Extensions.Hosting.IHost host);", "Stage": "Experimental" - }, + } + ] + }, + { + "Type": "static class Microsoft.AspNetCore.Hosting.ServiceFakesWebHostExtensions", + "Stage": "Experimental", + "Methods": [ { - "Member": "static Microsoft.AspNetCore.Hosting.IWebHostBuilder Microsoft.AspNetCore.Testing.ServiceFakesExtensions.ListenHttpOnAnyPort(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder);", + "Member": "static Microsoft.AspNetCore.Hosting.IWebHostBuilder Microsoft.AspNetCore.Hosting.ServiceFakesWebHostExtensions.ListenHttpOnAnyPort(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder);", "Stage": "Experimental" }, { - "Member": "static Microsoft.AspNetCore.Hosting.IWebHostBuilder Microsoft.AspNetCore.Testing.ServiceFakesExtensions.ListenHttpsOnAnyPort(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2? sslCertificate = null);", + "Member": "static Microsoft.AspNetCore.Hosting.IWebHostBuilder Microsoft.AspNetCore.Hosting.ServiceFakesWebHostExtensions.ListenHttpsOnAnyPort(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2? sslCertificate = null);", "Stage": "Experimental" }, { - "Member": "static Microsoft.AspNetCore.Hosting.IWebHostBuilder Microsoft.AspNetCore.Testing.ServiceFakesExtensions.UseFakeStartup(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder);", + "Member": "static Microsoft.AspNetCore.Hosting.IWebHostBuilder Microsoft.AspNetCore.Hosting.ServiceFakesWebHostExtensions.UseFakeStartup(this Microsoft.AspNetCore.Hosting.IWebHostBuilder builder);", "Stage": "Experimental" } ] diff --git a/src/Libraries/Microsoft.AspNetCore.Testing/README.md b/src/Libraries/Microsoft.AspNetCore.Testing/README.md index e845566c06f..5b109a0eded 100644 --- a/src/Libraries/Microsoft.AspNetCore.Testing/README.md +++ b/src/Libraries/Microsoft.AspNetCore.Testing/README.md @@ -1 +1,50 @@ -README +# Microsoft.AspNetCore.Testing + +This package provides test fakes for integration testing of ASP.NET Core applications. + +In particular: + +- `IWebHostBuilder` extensions to setup the test web app. +- `IHost` extensions to access that test web app. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.AspNetCore.Testing +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Example + +### Creating a Test Web App + +The [`IWebHostBuilder`](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.hosting.iwebhostbuilder) extensions can help set up a host for testing. + +```csharp +using var host = await FakeHost.CreateBuilder() + .ConfigureWebHost(webHost => webHost.UseFakeStartup().ListenHttpOnAnyPort()) + .StartAsync(); +``` + +### Accessing the test Web App + +The [`IHost`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.hosting.ihost) extensions can help access the test host that was created above. + +```csharp +using var client = host.CreateClient(); + +var response = await client.GetAsync("/"); +``` + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.AspNetCore.Testing/ServiceFakesExtensions.cs b/src/Libraries/Microsoft.AspNetCore.Testing/ServiceFakesHostExtensions.cs similarity index 57% rename from src/Libraries/Microsoft.AspNetCore.Testing/ServiceFakesExtensions.cs rename to src/Libraries/Microsoft.AspNetCore.Testing/ServiceFakesHostExtensions.cs index 8fe4a693566..5b88adcf2f8 100644 --- a/src/Libraries/Microsoft.AspNetCore.Testing/ServiceFakesExtensions.cs +++ b/src/Libraries/Microsoft.AspNetCore.Testing/ServiceFakesHostExtensions.cs @@ -5,71 +5,24 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Net; using System.Net.Http; -using System.Security.Cryptography.X509Certificates; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Microsoft.Shared.Diagnostics; -namespace Microsoft.AspNetCore.Testing; +namespace Microsoft.Extensions.Hosting; /// /// Extension methods supporting Kestrel server unit testing scenarios. /// -public static class ServiceFakesExtensions +public static class ServiceFakesHostExtensions { private static readonly Func _defaultAddressFilter = static _ => true; - /// - /// Adds an empty Startup class to satisfy ASP.NET check. - /// - /// An instance. - /// The value of . - public static IWebHostBuilder UseFakeStartup(this IWebHostBuilder builder) - { - return builder.UseStartup(); - } - - /// - /// Adds Kestrel server instance listening on the given HTTP port. - /// - /// An instance. - /// The value of . - /// When a concrete port is set by caller, it's not further validated if the port is really free. - public static IWebHostBuilder ListenHttpOnAnyPort(this IWebHostBuilder builder) - => Throw.IfNull(builder) - .UseKestrel(options => options.Listen(new IPEndPoint(IPAddress.Loopback, 0))); - - /// - /// Adds Kestrel server instance listening on a random HTTPS port. - /// - /// An instance. - /// An SSL certificate for the port. If null, a self-signed certificate is created and used. - /// The value of . - /// When a concrete port is set by caller, it's not further validated if the port is really free. - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Dispose objects before losing scope")] - public static IWebHostBuilder ListenHttpsOnAnyPort(this IWebHostBuilder builder, X509Certificate2? sslCertificate = null) - { - sslCertificate ??= FakeSslCertificateFactory.CreateSslCertificate(); - - return builder - .UseKestrel(options => - { - options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions => - { - _ = listenOptions.UseHttps(sslCertificate); - }); - }) - .ConfigureServices(services => - services.Configure(options => - options.Certificate = sslCertificate)); - } - /// /// Creates an to call the hosted application. /// diff --git a/src/Libraries/Microsoft.AspNetCore.Testing/ServiceFakesWebHostExtensions.cs b/src/Libraries/Microsoft.AspNetCore.Testing/ServiceFakesWebHostExtensions.cs new file mode 100644 index 00000000000..cbead7374aa --- /dev/null +++ b/src/Libraries/Microsoft.AspNetCore.Testing/ServiceFakesWebHostExtensions.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.AspNetCore.Hosting; + +/// +/// Extension methods supporting Kestrel server unit testing scenarios. +/// +public static class ServiceFakesWebHostExtensions +{ + /// + /// Adds an empty Startup class to satisfy ASP.NET check. + /// + /// An instance. + /// The value of . + public static IWebHostBuilder UseFakeStartup(this IWebHostBuilder builder) + { + return builder.UseStartup(); + } + + /// + /// Adds Kestrel server instance listening on the given HTTP port. + /// + /// An instance. + /// The value of . + /// When a concrete port is set by caller, it's not further validated if the port is really free. + public static IWebHostBuilder ListenHttpOnAnyPort(this IWebHostBuilder builder) + => Throw.IfNull(builder) + .UseKestrel(options => options.Listen(new IPEndPoint(IPAddress.Loopback, 0))); + + /// + /// Adds Kestrel server instance listening on a random HTTPS port. + /// + /// An instance. + /// An SSL certificate for the port. If null, a self-signed certificate is created and used. + /// The value of . + /// When a concrete port is set by caller, it's not further validated if the port is really free. + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Dispose objects before losing scope")] + public static IWebHostBuilder ListenHttpsOnAnyPort(this IWebHostBuilder builder, X509Certificate2? sslCertificate = null) + { + sslCertificate ??= FakeSslCertificateFactory.CreateSslCertificate(); + + return builder + .UseKestrel(options => + { + options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions => + { + _ = listenOptions.UseHttps(sslCertificate); + }); + }) + .ConfigureServices(services => + services.Configure(options => + options.Certificate = sslCertificate)); + } +} diff --git a/src/Libraries/Microsoft.Extensions.AmbientMetadata.Application/README.md b/src/Libraries/Microsoft.Extensions.AmbientMetadata.Application/README.md index e845566c06f..c4f962f8254 100644 --- a/src/Libraries/Microsoft.Extensions.AmbientMetadata.Application/README.md +++ b/src/Libraries/Microsoft.Extensions.AmbientMetadata.Application/README.md @@ -1 +1,67 @@ -README +# Microsoft.Extensions.AmbientMetadata.Application + +This flows runtime information for application-level ambient metadata such as the version, deployment ring, environment, and name. This information can be useful to enrich telemetry. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.AmbientMetadata.Application +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Example + +### Registering Services + +The services can be registered using any of the following methods: + +```csharp +public static IHostBuilder UseApplicationMetadata(this IHostBuilder builder, string sectionName = DefaultSectionName) +public static IServiceCollection AddApplicationMetadata(this IServiceCollection services, Action configure) +``` + +### Configuration + +When loading from configuration, the version and deployment ring metadata are read from the `ambientmetadata:application` section. The environment and application names are read from the `IHostEnvironment`. + +```json +{ + "AmbientMetadata" { + "Application" { + "BuildVersion": "1.0-alpha1.2346", + "DeploymentRing": "InnerRing" + } + } +} +``` + +### Consuming Services + +The `ApplicationMetadata` can be injected wherever needed. For example: + +```csharp +public class MyClass +{ + public MyClass(IOptions options) { Application = options.Value; } + + private ApplicationMetadata Application { get; } + + public void DoWork() + { + Log.LogEnvironment(Application.Version, Application.DeploymentRing, Application.Environment, Application.Name); + } +} +``` + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.AsyncState/IAsyncLocalContext.cs b/src/Libraries/Microsoft.Extensions.AsyncState/IAsyncLocalContext.cs index 0a435267b1d..8934fef1b9d 100644 --- a/src/Libraries/Microsoft.Extensions.AsyncState/IAsyncLocalContext.cs +++ b/src/Libraries/Microsoft.Extensions.AsyncState/IAsyncLocalContext.cs @@ -12,7 +12,7 @@ namespace Microsoft.Extensions.AsyncState; /// /// The type of the asynchronous state. /// This type is intended for internal use. Use instead. -[Experimental(diagnosticId: Experiments.AsyncState, UrlFormat = Experiments.UrlFormat)] +[Experimental(diagnosticId: DiagnosticIds.Experiments.AsyncState, UrlFormat = DiagnosticIds.UrlFormat)] [EditorBrowsable(EditorBrowsableState.Never)] #pragma warning disable S4023 // Interfaces should not be empty public interface IAsyncLocalContext : IAsyncContext diff --git a/src/Libraries/Microsoft.Extensions.AsyncState/README.md b/src/Libraries/Microsoft.Extensions.AsyncState/README.md index e845566c06f..c905aed1585 100644 --- a/src/Libraries/Microsoft.Extensions.AsyncState/README.md +++ b/src/Libraries/Microsoft.Extensions.AsyncState/README.md @@ -1 +1,55 @@ -README +# Microsoft.Extensions.AsyncState + +This provides the ability to store and retrieve state objects that flow with the current asynchronous context. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.AsyncState +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Example + +### Registering Services + +The services can be registered using the following method: + +```csharp +public static IServiceCollection AddAsyncState(this IServiceCollection services) +``` + +### Consuming Services + +The `IAsyncContext` can be injected wherever async state is needed. For example: + +```csharp +public class MyClass +{ + public MyClass(IAsyncContext asyncContext) { Context = asyncContext } + + private IAsyncContext Context { get; } + + public async Task DoWork() + { + var state = Context.Get(); + // or + Context.Set(new MyState()); + // or + if (Context.TryGet(out var state)) { ... } + } +} +``` + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Classification/DataClassification.cs b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Classification/DataClassification.cs index f5d33b8e162..dc8fc03273b 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Classification/DataClassification.cs +++ b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Classification/DataClassification.cs @@ -2,35 +2,24 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics.CodeAnalysis; using Microsoft.Shared.Diagnostics; namespace Microsoft.Extensions.Compliance.Classification; /// -/// Represents a set of data classes as a part of a data taxonomy. +/// Represents a single classification which is part of a data taxonomy. /// public readonly struct DataClassification : IEquatable { - /// - /// Represents unclassified data. - /// - public const ulong NoneTaxonomyValue = 0UL; - - /// - /// Represents the unknown classification. - /// - public const ulong UnknownTaxonomyValue = 1UL << 63; - /// /// Gets the value to represent data with no defined classification. /// - public static DataClassification None => new(NoneTaxonomyValue); + public static DataClassification None => new(nameof(None)); /// /// Gets the value to represent data with an unknown classification. /// - public static DataClassification Unknown => new(UnknownTaxonomyValue); + public static DataClassification Unknown => new(nameof(Unknown)); /// /// Gets the name of the taxonomy that recognizes this classification. @@ -38,55 +27,46 @@ namespace Microsoft.Extensions.Compliance.Classification; public string TaxonomyName { get; } /// - /// Gets the bit mask representing the data classes. + /// Gets the value representing the classification within the taxonomy. /// - public ulong Value { get; } + public string Value { get; } /// /// Initializes a new instance of the struct. /// - /// The name of the taxonomy this classification belongs to. - /// The taxonomy-specific bit vector representing the data classes. - /// Bit 63, which corresponds to , is set in the value. - public DataClassification(string taxonomyName, ulong value) + /// The name of the data taxonomy this classification belongs to. + /// The taxonomy-specific value representing the classification. + public DataClassification(string taxonomyName, string value) { - TaxonomyName = Throw.IfNullOrEmpty(taxonomyName); - Value = value; - - if (((value & UnknownTaxonomyValue) != 0) || (value == NoneTaxonomyValue)) - { - Throw.ArgumentException(nameof(value), $"Cannot create a classification with a value of 0x{value:x}."); - } + TaxonomyName = Throw.IfNullOrWhitespace(taxonomyName); + Value = Throw.IfNullOrWhitespace(value); } - private DataClassification(ulong taxonomyValue) + private DataClassification(string value) { TaxonomyName = string.Empty; - Value = taxonomyValue; + Value = value; } /// - /// Checks if object is equal to this instance of . + /// Checks if an object is equal to this instance of . /// /// Object to check for equality. /// if object instances are equal otherwise. public override bool Equals(object? obj) => (obj is DataClassification dc) && Equals(dc); /// - /// Checks if object is equal to this instance of . + /// Checks if an object is equal to this instance of . /// /// Instance of to check for equality. /// if object instances are equal otherwise. public bool Equals(DataClassification other) => other.TaxonomyName == TaxonomyName && other.Value == Value; /// - /// Get the hash code the current instance. + /// Get the hash code for the current instance. /// /// Hash code. - public override int GetHashCode() - { - return HashCode.Combine(TaxonomyName, Value); - } + public override int GetHashCode() => HashCode.Combine(TaxonomyName, Value); /// /// Check if two instances are equal. @@ -110,48 +90,9 @@ public override int GetHashCode() return !left.Equals(right); } - /// - /// Combines together two data classifications. - /// - /// The first classification to combine. - /// The second classification to combine. - /// A new classification object representing the combination of the two input classifications. - /// The two classifications aren't part of the same taxonomy. - public static DataClassification Or(DataClassification left, DataClassification right) - { - if (string.IsNullOrEmpty(left.TaxonomyName)) - { - return (left.Value == NoneTaxonomyValue) ? right : Unknown; - } - else if (string.IsNullOrEmpty(right.TaxonomyName)) - { - return (right.Value == NoneTaxonomyValue) ? left : Unknown; - } - - if (left.TaxonomyName != right.TaxonomyName) - { - Throw.ArgumentException(nameof(right), $"Mismatched data taxonomies: {left.TaxonomyName} and {right.TaxonomyName} cannot be combined"); - } - - return new(left.TaxonomyName, left.Value | right.Value); - } - - /// - /// Combines together two data classifications. - /// - /// The first classification to combine. - /// The second classification to combine. - /// A new classification object representing the combination of the two input classifications. - /// The two classifications aren't part of the same taxonomy. - [SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "It's called Combine")] - public static DataClassification operator |(DataClassification left, DataClassification right) - { - return Or(left, right); - } - /// /// Gets a string representation of this object. /// /// A string representing the object. - public override string ToString() => $"{TaxonomyName}:{Value:x}"; + public override string ToString() => string.IsNullOrWhiteSpace(TaxonomyName) ? Value : $"{TaxonomyName}:{Value}"; } diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Classification/DataClassificationAttribute.cs b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Classification/DataClassificationAttribute.cs index 117b793e183..ccbbf5d3ef0 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Classification/DataClassificationAttribute.cs +++ b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Classification/DataClassificationAttribute.cs @@ -29,7 +29,7 @@ public class DataClassificationAttribute : Attribute public string Notes { get; set; } = string.Empty; /// - /// Gets the data class represented by this attribute. + /// Gets the classification represented by this attribute. /// public DataClassification Classification { get; } diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Classification/DataClassificationSet.cs b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Classification/DataClassificationSet.cs new file mode 100644 index 00000000000..6a041e49524 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Classification/DataClassificationSet.cs @@ -0,0 +1,134 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Shared.Diagnostics; +using Microsoft.Shared.Pools; + +namespace Microsoft.Extensions.Compliance.Classification; + +/// +/// Represents a set of data classifications. +/// +public sealed class DataClassificationSet : IEquatable +{ + private readonly HashSet _classifications = []; + + /// + /// Initializes a new instance of the class. + /// + /// The class to include in the set. + public DataClassificationSet(DataClassification classification) + { + _ = _classifications.Add(classification); + } + + /// + /// Initializes a new instance of the class. + /// + /// The classes to include in the set. + public DataClassificationSet(IEnumerable classifications) + { + _ = Throw.IfNull(classifications); + _classifications.UnionWith(classifications); + } + + /// + /// Initializes a new instance of the class. + /// + /// The classes to include in the set. + public DataClassificationSet(params DataClassification[] classifications) + { + _ = Throw.IfNull(classifications); + _classifications.UnionWith(classifications); + } + + /// + /// Converts a single classification to a data classification set. + /// + /// The classification to include in the set. + public static implicit operator DataClassificationSet(DataClassification classification) + { + return FromDataClassification(classification); + } + + /// + /// Converts a single classification to a data classification set. + /// + /// The classification to include in the set. + /// The resulting data classification set. + public static DataClassificationSet FromDataClassification(DataClassification classification) => new(classification); + + /// + /// Creates a new data classification set that combines the current classifications with another set. + /// + /// The other set. + /// + /// This method doesn't modify the two input sets, it creates a new set. + /// + /// The resulting set which combines the current instance's classifications and the other ones. + public DataClassificationSet Union(DataClassificationSet other) + { + _ = Throw.IfNull(other); + + var result = new DataClassificationSet(other._classifications); + result._classifications.UnionWith(_classifications); + + return result; + } + + /// + /// Gets a hash code for the current object instance. + /// + /// The hash code value. + public override int GetHashCode() => _classifications.GetHashCode(); + + /// + /// Compares an object with the current instance to see if they contain the same classifications. + /// + /// The other data classification to compare to. + /// if the two sets contain the same classifications. + public override bool Equals(object? obj) => Equals(obj as DataClassificationSet); + + /// + /// Compares an object with the current instance to see if they contain the same classifications. + /// + /// The other data classification to compare to. + /// if the two sets contain the same classifications. + public bool Equals(DataClassificationSet? other) => other != null && _classifications.SetEquals(other._classifications); + + /// + /// Returns a string representation of this object. + /// + /// The string representation of this object. + public override string ToString() + { + var sb = PoolFactory.SharedStringBuilderPool.Get(); + + var a = _classifications.ToArray(); + Array.Sort(a, (x, y) => + { + var result = string.Compare(x.TaxonomyName, y.TaxonomyName, StringComparison.Ordinal); + return result != 0 ? result : string.Compare(x.Value, y.Value, StringComparison.Ordinal); + }); + + _ = sb.Append('['); + foreach (var c in a) + { + if (sb.Length > 1) + { + _ = sb.Append(','); + } + + _ = sb.Append(c); + } + + _ = sb.Append(']'); + var result = sb.ToString(); + PoolFactory.SharedStringBuilderPool.Return(sb); + + return result; + } +} diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Microsoft.Extensions.Compliance.Abstractions.csproj b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Microsoft.Extensions.Compliance.Abstractions.csproj index f33d2b8b8ad..5a6c93e1dc7 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Microsoft.Extensions.Compliance.Abstractions.csproj +++ b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Microsoft.Extensions.Compliance.Abstractions.csproj @@ -7,10 +7,11 @@ true - true + true true true true + true diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Microsoft.Extensions.Compliance.Abstractions.json b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Microsoft.Extensions.Compliance.Abstractions.json index 952aacdacc2..23b530ae2a9 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Microsoft.Extensions.Compliance.Abstractions.json +++ b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Microsoft.Extensions.Compliance.Abstractions.json @@ -6,7 +6,7 @@ "Stage": "Stable", "Methods": [ { - "Member": "Microsoft.Extensions.Compliance.Classification.DataClassification.DataClassification(string taxonomyName, ulong value);", + "Member": "Microsoft.Extensions.Compliance.Classification.DataClassification.DataClassification(string taxonomyName, string value);", "Stage": "Stable" }, { @@ -25,10 +25,6 @@ "Member": "override int Microsoft.Extensions.Compliance.Classification.DataClassification.GetHashCode();", "Stage": "Stable" }, - { - "Member": "static Microsoft.Extensions.Compliance.Classification.DataClassification Microsoft.Extensions.Compliance.Classification.DataClassification.operator |(Microsoft.Extensions.Compliance.Classification.DataClassification left, Microsoft.Extensions.Compliance.Classification.DataClassification right);", - "Stage": "Stable" - }, { "Member": "static bool Microsoft.Extensions.Compliance.Classification.DataClassification.operator ==(Microsoft.Extensions.Compliance.Classification.DataClassification left, Microsoft.Extensions.Compliance.Classification.DataClassification right);", "Stage": "Stable" @@ -37,27 +33,11 @@ "Member": "static bool Microsoft.Extensions.Compliance.Classification.DataClassification.operator !=(Microsoft.Extensions.Compliance.Classification.DataClassification left, Microsoft.Extensions.Compliance.Classification.DataClassification right);", "Stage": "Stable" }, - { - "Member": "static Microsoft.Extensions.Compliance.Classification.DataClassification Microsoft.Extensions.Compliance.Classification.DataClassification.Or(Microsoft.Extensions.Compliance.Classification.DataClassification left, Microsoft.Extensions.Compliance.Classification.DataClassification right);", - "Stage": "Stable" - }, { "Member": "override string Microsoft.Extensions.Compliance.Classification.DataClassification.ToString();", "Stage": "Stable" } ], - "Fields": [ - { - "Member": "const ulong Microsoft.Extensions.Compliance.Classification.DataClassification.NoneTaxonomyValue", - "Stage": "Stable", - "Value": "0" - }, - { - "Member": "const ulong Microsoft.Extensions.Compliance.Classification.DataClassification.UnknownTaxonomyValue", - "Stage": "Stable", - "Value": "9223372036854775808" - } - ], "Properties": [ { "Member": "static Microsoft.Extensions.Compliance.Classification.DataClassification Microsoft.Extensions.Compliance.Classification.DataClassification.None { get; }", @@ -72,7 +52,7 @@ "Stage": "Stable" }, { - "Member": "ulong Microsoft.Extensions.Compliance.Classification.DataClassification.Value { get; }", + "Member": "string Microsoft.Extensions.Compliance.Classification.DataClassification.Value { get; }", "Stage": "Stable" } ] @@ -97,6 +77,52 @@ } ] }, + { + "Type": "sealed class Microsoft.Extensions.Compliance.Classification.DataClassificationSet : System.IEquatable", + "Stage": "Stable", + "Methods": [ + { + "Member": "Microsoft.Extensions.Compliance.Classification.DataClassificationSet.DataClassificationSet(Microsoft.Extensions.Compliance.Classification.DataClassification classification);", + "Stage": "Stable" + }, + { + "Member": "Microsoft.Extensions.Compliance.Classification.DataClassificationSet.DataClassificationSet(System.Collections.Generic.IEnumerable classifications);", + "Stage": "Stable" + }, + { + "Member": "Microsoft.Extensions.Compliance.Classification.DataClassificationSet.DataClassificationSet(params Microsoft.Extensions.Compliance.Classification.DataClassification[] classifications);", + "Stage": "Stable" + }, + { + "Member": "override bool Microsoft.Extensions.Compliance.Classification.DataClassificationSet.Equals(object? obj);", + "Stage": "Stable" + }, + { + "Member": "bool Microsoft.Extensions.Compliance.Classification.DataClassificationSet.Equals(Microsoft.Extensions.Compliance.Classification.DataClassificationSet? other);", + "Stage": "Stable" + }, + { + "Member": "static Microsoft.Extensions.Compliance.Classification.DataClassificationSet Microsoft.Extensions.Compliance.Classification.DataClassificationSet.FromDataClassification(Microsoft.Extensions.Compliance.Classification.DataClassification classification);", + "Stage": "Stable" + }, + { + "Member": "override int Microsoft.Extensions.Compliance.Classification.DataClassificationSet.GetHashCode();", + "Stage": "Stable" + }, + { + "Member": "static Microsoft.Extensions.Compliance.Classification.DataClassificationSet.implicit operator Microsoft.Extensions.Compliance.Classification.DataClassificationSet(Microsoft.Extensions.Compliance.Classification.DataClassification classification);", + "Stage": "Stable" + }, + { + "Member": "override string Microsoft.Extensions.Compliance.Classification.DataClassificationSet.ToString();", + "Stage": "Stable" + }, + { + "Member": "Microsoft.Extensions.Compliance.Classification.DataClassificationSet Microsoft.Extensions.Compliance.Classification.DataClassificationSet.Union(Microsoft.Extensions.Compliance.Classification.DataClassificationSet other);", + "Stage": "Stable" + } + ] + }, { "Type": "interface Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder", "Stage": "Stable", @@ -106,7 +132,7 @@ "Stage": "Stable" }, { - "Member": "Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder.SetRedactor(params Microsoft.Extensions.Compliance.Classification.DataClassification[] classifications);", + "Member": "Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder.SetRedactor(params Microsoft.Extensions.Compliance.Classification.DataClassificationSet[] classifications);", "Stage": "Stable" } ], @@ -122,7 +148,7 @@ "Stage": "Stable", "Methods": [ { - "Member": "Microsoft.Extensions.Compliance.Redaction.Redactor Microsoft.Extensions.Compliance.Redaction.IRedactorProvider.GetRedactor(Microsoft.Extensions.Compliance.Classification.DataClassification classification);", + "Member": "Microsoft.Extensions.Compliance.Redaction.Redactor Microsoft.Extensions.Compliance.Redaction.IRedactorProvider.GetRedactor(Microsoft.Extensions.Compliance.Classification.DataClassificationSet classifications);", "Stage": "Stable" } ] @@ -174,7 +200,7 @@ "Stage": "Stable" }, { - "Member": "Microsoft.Extensions.Compliance.Redaction.Redactor Microsoft.Extensions.Compliance.Redaction.NullRedactorProvider.GetRedactor(Microsoft.Extensions.Compliance.Classification.DataClassification classification);", + "Member": "Microsoft.Extensions.Compliance.Redaction.Redactor Microsoft.Extensions.Compliance.Redaction.NullRedactorProvider.GetRedactor(Microsoft.Extensions.Compliance.Classification.DataClassificationSet classifications);", "Stage": "Stable" } ], diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/README.md b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/README.md index e845566c06f..ad1935d91dd 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/README.md +++ b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/README.md @@ -1 +1,65 @@ -README +# Microsoft.Extensions.Compliance.Abstractions + +This package introduces data classification and data redaction features. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.Compliance.Abstractions +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Example + +### Implementing Redactors + +Redactors can be implemented by inheriting from `Microsoft.Extensions.Compliance.Redaction.Redactor`. For example: + +```csharp +using Microsoft.Extensions.Compliance.Redaction; + +public class StarRedactor : Redactor +{ + private const string Stars = "****"; + + public override int GetRedactedLength(ReadOnlySpan input) => Stars.Length; + + public override int Redact(ReadOnlySpan source, Span destination) + { + Stars.CopyTo(destination); + return Stars.Length; + } +} +``` + +### Implementing Redactor Providers + +Redactor Providers implement `Microsoft.Extensions.Compliance.Redaction.IRedactorProvider`. +For example: + +```csharp +using Microsoft.Extensions.Compliance.Classification; +using Microsoft.Extensions.Compliance.Redaction; + +public sealed class StarRedactorProvider : IRedactorProvider +{ + private static readonly StarRedactor _starRedactor = new(); + + public static StarRedactorProvider Instance { get; } = new(); + + public Redactor GetRedactor(DataClassificationSet classifications) => _starRedactor; +} +``` + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Redaction/IRedactionBuilder.cs b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Redaction/IRedactionBuilder.cs index 30148f45a15..837492e633a 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Redaction/IRedactionBuilder.cs +++ b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Redaction/IRedactionBuilder.cs @@ -18,13 +18,13 @@ public interface IRedactionBuilder IServiceCollection Services { get; } /// - /// Sets the redactor to use for a set of data classes. + /// Sets the redactor to use for a set of data classifications. /// /// Redactor type. - /// The data classes for which the redactor type should be used. + /// The data classifications for which the redactor type should be used. /// The value of this instance. /// is . - IRedactionBuilder SetRedactor(params DataClassification[] classifications) + IRedactionBuilder SetRedactor(params DataClassificationSet[] classifications) where T : Redactor; /// diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Redaction/IRedactorProvider.cs b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Redaction/IRedactorProvider.cs index fc6e6f14d59..b8e959fdcd5 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Redaction/IRedactorProvider.cs +++ b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Redaction/IRedactorProvider.cs @@ -6,14 +6,14 @@ namespace Microsoft.Extensions.Compliance.Redaction; /// -/// Provides redactors for different data classes. +/// Provides redactors for different data classifications. /// public interface IRedactorProvider { /// /// Gets the redactor configured to handle the specified data class. /// - /// Data classification of the data to redact. + /// Data classifications of the data to redact. /// A redactor suitable to redact data of the given class. - Redactor GetRedactor(DataClassification classification); + Redactor GetRedactor(DataClassificationSet classifications); } diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Redaction/NullRedactorProvider.cs b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Redaction/NullRedactorProvider.cs index 0591d0590df..e4fda65ded0 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Redaction/NullRedactorProvider.cs +++ b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Redaction/NullRedactorProvider.cs @@ -6,7 +6,7 @@ namespace Microsoft.Extensions.Compliance.Redaction; /// -/// A provider that only returns the redactor implementation used for situations that don't require redaction. +/// A provider that only returns the redactor used for situations that don't require redaction. /// public sealed class NullRedactorProvider : IRedactorProvider { @@ -16,5 +16,5 @@ public sealed class NullRedactorProvider : IRedactorProvider public static NullRedactorProvider Instance { get; } = new(); /// - public Redactor GetRedactor(DataClassification classification) => NullRedactor.Instance; + public Redactor GetRedactor(DataClassificationSet classifications) => NullRedactor.Instance; } diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Redaction/Redactor.cs b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Redaction/Redactor.cs index 56ae554dc1b..27c78d929a0 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Redaction/Redactor.cs +++ b/src/Libraries/Microsoft.Extensions.Compliance.Abstractions/Redaction/Redactor.cs @@ -245,17 +245,20 @@ public bool TryRedact(T value, Span destination, out int charsWritten, } #endif - string? str = null; ReadOnlySpan ros = default; if (value is IFormattable) { var fmt = format.Length > 0 ? format.ToString() : string.Empty; - str = ((IFormattable)value).ToString(fmt, provider); + var str = ((IFormattable)value).ToString(fmt, provider); + if (str != null) + { + ros = str.AsSpan(); + } } else if (value is char[]) { - // An attempt to call value.ToString() on a char[] will produce a string "System.Char[]" and all redaction will be attempted on it, - // instead of the provided array. This will lead to incorrectly allocated buffers. + // An attempt to call value.ToString() on a char[] will produce the string "System.Char[]" and redaction will be attempted on it, + // instead of the provided array. // // Not using pattern matching as it is recognized by the JIT and since this is a generic type, the JIT ends up generating code // without any of those conditional statements being present. But this only happens when not using pattern matching. @@ -263,12 +266,11 @@ public bool TryRedact(T value, Span destination, out int charsWritten, } else { - str = value?.ToString(); - } - - if (str is not null) - { - ros = str.AsSpan(); + var str = value?.ToString(); + if (str != null) + { + ros = str.AsSpan(); + } } int len = GetRedactedLength(ros); diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Redaction/HmacRedactor.cs b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/HmacRedactor.cs new file mode 100644 index 00000000000..0f76e3a9c00 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/HmacRedactor.cs @@ -0,0 +1,160 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Security.Cryptography; +using Microsoft.Extensions.Options; +using Microsoft.Shared.Diagnostics; +using Microsoft.Shared.Text; + +#if NET6_0_OR_GREATER +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +#else +using System.Diagnostics.CodeAnalysis; +using System.Text; +#endif + +namespace Microsoft.Extensions.Compliance.Redaction; + +internal sealed class HmacRedactor : Redactor +{ +#if NET6_0_OR_GREATER + private const int SHA256HashSizeInBytes = 32; +#endif + private const int BytesOfHashWeUse = 16; + + /// + /// Magic numbers are formula for calculating base64 length with padding. + /// + private const int Base64HashLength = ((BytesOfHashWeUse + 2) / 3) * 4; + + private readonly int _redactedLength; + private readonly byte[] _hashKey; + private readonly string _keyId; + + public HmacRedactor(IOptions options) + { + var value = Throw.IfMemberNull(options, options.Value); + + _hashKey = Convert.FromBase64String(value.Key); + _keyId = value.KeyId.HasValue ? value.KeyId.Value.ToInvariantString() + ':' : string.Empty; + _redactedLength = Base64HashLength + _keyId.Length; + } + + public override int GetRedactedLength(ReadOnlySpan source) + { + if (source.IsEmpty) + { + return 0; + } + + return _redactedLength; + } + +#if NET6_0_OR_GREATER + public override int Redact(ReadOnlySpan source, Span destination) + { + var length = GetRedactedLength(source); + if (length == 0) + { + return 0; + } + + Throw.IfBufferTooSmall(destination.Length, length, nameof(destination)); + + _keyId.AsSpan().CopyTo(destination); + return CreateSha256Hash(source, destination[_keyId.Length..], _hashKey) + _keyId.Length; + } + + [SkipLocalsInit] + private static int CreateSha256Hash(ReadOnlySpan source, Span destination, byte[] hashKey) + { + Span hashBuffer = stackalloc byte[SHA256HashSizeInBytes]; + + _ = HMACSHA256.HashData(hashKey, MemoryMarshal.AsBytes(source), hashBuffer); + + // this won't fail, we ensured the destination is big enough previously + _ = Convert.TryToBase64Chars(hashBuffer.Slice(0, BytesOfHashWeUse), destination, out int charsWritten); + + return charsWritten; + } + +#else + + public override int Redact(ReadOnlySpan source, Span destination) + { + const int RemainingBytesToPadForBase64Hash = BytesOfHashWeUse % 3; + + var length = GetRedactedLength(source); + if (length == 0) + { + return 0; + } + + Throw.IfBufferTooSmall(destination.Length, length, nameof(destination)); + + _keyId.AsSpan().CopyTo(destination); + return ConvertBytesToBase64(CreateSha256Hash(source, _hashKey), destination, RemainingBytesToPadForBase64Hash, _keyId.Length) + _keyId.Length; + } + + private static byte[] CreateSha256Hash(ReadOnlySpan value, byte[] hashKey) + { + using var hmac = new HMACSHA256(hashKey); + return hmac.ComputeHash(Encoding.Unicode.GetBytes(value.ToArray())); + } + + private static readonly char[] _base64CharactersTable = + { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', + 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '+', '/', '=', + }; + + [SuppressMessage("Code smell", "S109", Justification = "Bit operation.")] + private static int ConvertBytesToBase64(byte[] hashToConvert, Span destination, int remainingBytesToPad, int startOffset) + { + var iterations = BytesOfHashWeUse - remainingBytesToPad; + var offset = startOffset; + + unchecked + { + for (var i = 0; i < iterations; i += 3) + { + destination[offset] = _base64CharactersTable[(hashToConvert[i] & 0xfc) >> 2]; + destination[offset + 1] = _base64CharactersTable[((hashToConvert[i] & 0x03) << 4) | ((hashToConvert[i + 1] & 0xf0) >> 4)]; + destination[offset + 2] = _base64CharactersTable[((hashToConvert[i + 1] & 0x0f) << 2) | ((hashToConvert[i + 2] & 0xc0) >> 6)]; + destination[offset + 3] = _base64CharactersTable[hashToConvert[i + 2] & 0x3f]; + offset += 4; + } + +#if false +// this code is disabled since it is never visited given the limited use of this function. We leave it here in case the code is needed in the future + if (remainingBytesToPad == 2) + { + destination[offset] = _base64CharactersTable[(hashToConvert[iterations] & 0xfc) >> 2]; + destination[offset + 1] = _base64CharactersTable[((hashToConvert[iterations] & 0x03) << 4) | ((hashToConvert[iterations + 1] & 0xf0) >> 4)]; + destination[offset + 2] = _base64CharactersTable[(hashToConvert[iterations + 1] & 0x0f) << 2]; + destination[offset + 3] = _base64CharactersTable[64]; + offset += 4; + } +#endif + + if (remainingBytesToPad == 1) + { + destination[offset] = _base64CharactersTable[(hashToConvert[iterations] & 0xfc) >> 2]; + destination[offset + 1] = _base64CharactersTable[(hashToConvert[iterations] & 0x03) << 4]; + destination[offset + 2] = _base64CharactersTable[64]; + destination[offset + 3] = _base64CharactersTable[64]; + offset += 4; + } + } + + var charsWritten = offset - startOffset; + return charsWritten; + } + +#endif +} diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Redaction/HmacRedactorOptions.cs b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/HmacRedactorOptions.cs new file mode 100644 index 00000000000..3f0303c9e4b --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/HmacRedactorOptions.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.Shared.DiagnosticIds; + +namespace Microsoft.Extensions.Compliance.Redaction; + +/// +/// A redactor using HMACSHA256 to encode data being redacted. +/// +[Experimental(diagnosticId: DiagnosticIds.Experiments.Compliance, UrlFormat = DiagnosticIds.UrlFormat)] +public class HmacRedactorOptions +{ + /// + /// Gets or sets the key ID. + /// + /// + /// Default set to . + /// + /// + /// The key id is appended to each redacted value and is intended to identity the key that was used to hash the data. + /// In general, every distinct key should have a unique id associated with it. When the hashed values have different key ids, + /// it means the values are unrelated and can't be used for correlation. + /// + public int? KeyId { get; set; } + + /// + /// Gets or sets the hashing key. + /// + /// + /// The key is specified in base 64 format, and must be a minimum of 44 characters long. + /// + /// We recommend using a distinct key for each major deployment of a service (say for each region the service is in). Additionally, + /// the key material should be kept secret, and rotated on a regular basis. + /// + /// + /// Default set to . + /// + [StringSyntax("Base64")] +#if NET8_0_OR_GREATER + [System.ComponentModel.DataAnnotations.Base64String] +#endif + [Microsoft.Shared.Data.Validation.Length(44)] + public string Key { get; set; } = string.Empty; +} diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Redaction/HmacRedactorOptionsValidator.cs b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/HmacRedactorOptionsValidator.cs new file mode 100644 index 00000000000..69a9b2670ec --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/HmacRedactorOptionsValidator.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.Compliance.Redaction; + +[OptionsValidator] +internal sealed partial class HmacRedactorOptionsValidator : IValidateOptions +{ +} diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Redaction/Microsoft.Extensions.Compliance.Redaction.csproj b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/Microsoft.Extensions.Compliance.Redaction.csproj index 535e30e7492..9f9f4149ca1 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Redaction/Microsoft.Extensions.Compliance.Redaction.csproj +++ b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/Microsoft.Extensions.Compliance.Redaction.csproj @@ -6,7 +6,12 @@ - true + true + true + true + true + true + true diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Redaction/Microsoft.Extensions.Compliance.Redaction.json b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/Microsoft.Extensions.Compliance.Redaction.json index 856d914aa85..88f44e26362 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Redaction/Microsoft.Extensions.Compliance.Redaction.json +++ b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/Microsoft.Extensions.Compliance.Redaction.json @@ -25,6 +25,40 @@ } ] }, + { + "Type": "class Microsoft.Extensions.Compliance.Redaction.HmacRedactorOptions", + "Stage": "Experimental", + "Methods": [ + { + "Member": "Microsoft.Extensions.Compliance.Redaction.HmacRedactorOptions.HmacRedactorOptions();", + "Stage": "Experimental" + } + ], + "Properties": [ + { + "Member": "string Microsoft.Extensions.Compliance.Redaction.HmacRedactorOptions.Key { get; set; }", + "Stage": "Experimental" + }, + { + "Member": "int? Microsoft.Extensions.Compliance.Redaction.HmacRedactorOptions.KeyId { get; set; }", + "Stage": "Experimental" + } + ] + }, + { + "Type": "static class Microsoft.Extensions.Compliance.Redaction.RedactionExtensions", + "Stage": "Stable", + "Methods": [ + { + "Member": "static Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder Microsoft.Extensions.Compliance.Redaction.RedactionExtensions.SetHmacRedactor(this Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder builder, System.Action configure, params Microsoft.Extensions.Compliance.Classification.DataClassificationSet[] classifications);", + "Stage": "Experimental" + }, + { + "Member": "static Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder Microsoft.Extensions.Compliance.Redaction.RedactionExtensions.SetHmacRedactor(this Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder builder, Microsoft.Extensions.Configuration.IConfigurationSection section, params Microsoft.Extensions.Compliance.Classification.DataClassificationSet[] classifications);", + "Stage": "Experimental" + } + ] + }, { "Type": "static class Microsoft.Extensions.DependencyInjection.RedactionServiceCollectionExtensions", "Stage": "Stable", diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Redaction/README.md b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/README.md index e845566c06f..31d5f51080c 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Redaction/README.md +++ b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/README.md @@ -1 +1,66 @@ -README +# Microsoft.Extensions.Compliance.Redaction + +A redaction engine and canonical redactors. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.Compliance.Redaction +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Example + +### Registering the services + +The services can be registered using one of the `AddRedaction` overloads: + +```csharp +public static IServiceCollection AddRedaction(this IServiceCollection services); +public static IServiceCollection AddRedaction(this IServiceCollection services, Action configure); +``` + +### Configuring a redactor + +Redactors are fetched at runtime using an `IRedactorProvider`. You can choose to implement your own provider and register it inside the `AddRedaction` call, or alternatively you can just use the default provider. Redactors can be configured using one of these `IRedactionBuilder` extension methods: + +```csharp +// Using the default redactor provider: +builder.Services.AddRedaction(redactionBuilder => +{ + // Assigns a redactor to use for a set of data classifications. + redactionBuilder.SetRedactor(MySensitiveDataClassification); + // Assigns a fallback redactor to use when processing classified data for which no specific redactor has been registered. + // The `ErasingRedactor` is the default fallback redactor. If no redactor is configured for a data classification then the data will be erased. + redactionBuilder.SetFallbackRedactor(); +}); + +// Using a custom redactor provider: +builder.Services.AddSingleton(); +builder.Services.AddRedaction(redactionBuilder => { }); +``` + +### Configuring the HMAC redactor + +The HMAC redactor can be configured using one these `IRedactionBuilder` extension methods: + +```csharp +public static IRedactionBuilder SetHmacRedactor(this IRedactionBuilder builder, Action configure, params DataClassificationSet[] classifications); + +public static IRedactionBuilder SetHmacRedactor(this IRedactionBuilder builder, IConfigurationSection section, params DataClassificationSet[] classifications); +``` + +The `HmacRedactorOptions` requires its `KeyId` and `Key` properties to be set. The `HmacRedactor` is still in the experimental phase, which means that the above two methods will show warning `EXTEXP0002` notifying you that the `HmacRedactor` is not yet stable. In order to use it, you will need to either add `$(NoWarn);EXTEXP0002` to your project file or add `#pragma warning disable EXTEXP0002` around the calls to `SetHmacRedactor`. + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Redaction/RedactionBuilder.cs b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/RedactionBuilder.cs index 46a88fd6c52..493ff3682dd 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Redaction/RedactionBuilder.cs +++ b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/RedactionBuilder.cs @@ -29,7 +29,7 @@ public RedactionBuilder(IServiceCollection services) Services.TryAddEnumerable(ServiceDescriptor.Singleton(NullRedactor.Instance)); } - public IRedactionBuilder SetRedactor(params DataClassification[] classifications) + public IRedactionBuilder SetRedactor(params DataClassificationSet[] classifications) where T : Redactor { _ = Throw.IfNull(classifications); diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Redaction/RedactionExtensions.cs b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/RedactionExtensions.cs new file mode 100644 index 00000000000..0fcc71622a5 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/RedactionExtensions.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Compliance.Classification; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Shared.DiagnosticIds; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Extensions.Compliance.Redaction; + +/// +/// Extensions to configure specific redactors. +/// +public static class RedactionExtensions +{ + /// + /// Sets the HMAC redactor to use for a set of data classifications. + /// + /// The builder to attach the redactor to. + /// Configuration function. + /// The data classifications for which the redactor type should be used. + /// The value of . + /// , or are . + [Experimental(diagnosticId: DiagnosticIds.Experiments.Compliance, UrlFormat = DiagnosticIds.UrlFormat)] + public static IRedactionBuilder SetHmacRedactor(this IRedactionBuilder builder, Action configure, params DataClassificationSet[] classifications) + { + _ = Throw.IfNull(builder); + _ = Throw.IfNull(configure); + _ = Throw.IfNull(classifications); + + _ = builder + .Services + .AddOptionsWithValidateOnStart() + .Configure(configure); + + return builder.SetRedactor(classifications); + } + + /// + /// Sets the HMAC redactor to use for a set of data classifications. + /// + /// The builder to attach the redactor to. + /// Configuration section. + /// The data classifications for which the redactor type should be used. + /// The value of . + /// , or are . + [Experimental(diagnosticId: DiagnosticIds.Experiments.Compliance, UrlFormat = DiagnosticIds.UrlFormat)] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(HmacRedactorOptions))] + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = "Addressed with [DynamicDependency]")] + public static IRedactionBuilder SetHmacRedactor(this IRedactionBuilder builder, IConfigurationSection section, params DataClassificationSet[] classifications) + { + _ = Throw.IfNull(builder); + _ = Throw.IfNull(section); + _ = Throw.IfNull(classifications); + + _ = builder + .Services.AddOptionsWithValidateOnStart() + .Services.Configure(section); + + return builder.SetRedactor(classifications); + } +} diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Redaction/RedactorProvider.cs b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/RedactorProvider.cs index dd47deb1454..0963784fa80 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Redaction/RedactorProvider.cs +++ b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/RedactorProvider.cs @@ -14,7 +14,7 @@ namespace Microsoft.Extensions.Compliance.Redaction; [SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes", Justification = "Instantiated via reflection.")] internal sealed class RedactorProvider : IRedactorProvider { - private readonly FrozenDictionary _classRedactors; + private readonly FrozenDictionary _classRedactors; private readonly Redactor _fallbackRedactor; public RedactorProvider(IEnumerable redactors, IOptions options) @@ -25,9 +25,9 @@ public RedactorProvider(IEnumerable redactors, IOptions GetClassRedactorMap(IEnumerable redactors, Dictionary map) + private static FrozenDictionary GetClassRedactorMap(IEnumerable redactors, Dictionary map) { - var dict = new Dictionary(map.Count); + var dict = new Dictionary(map.Count); foreach (var m in map) { foreach (var r in redactors) diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Redaction/RedactorProviderOptions.cs b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/RedactorProviderOptions.cs index 9df4b724e30..357e9c23b88 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Redaction/RedactorProviderOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Compliance.Redaction/RedactorProviderOptions.cs @@ -20,5 +20,5 @@ internal sealed class RedactorProviderOptions /// /// Gets a dictionary of classification-specific redactors. /// - public Dictionary Redactors { get; } = []; + public Dictionary Redactors { get; } = []; } diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeClassifications.cs b/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeClassifications.cs deleted file mode 100644 index 75e2488d466..00000000000 --- a/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeClassifications.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.Compliance.Classification; - -namespace Microsoft.Extensions.Compliance.Testing; - -/// -/// Simple data classifications. -/// -public static class FakeClassifications -{ - /// - /// Gets the name of this classification taxonomy. - /// - public static string TaxonomyName => typeof(FakeTaxonomy).FullName!; - - /// - /// Gets the private data classification. - /// - public static DataClassification PrivateData => new(TaxonomyName, (ulong)FakeTaxonomy.PrivateData); - - /// - /// Gets the public data classification. - /// - public static DataClassification PublicData => new(TaxonomyName, (ulong)FakeTaxonomy.PublicData); -} diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeRedactionBuilderExtensions.cs b/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeRedactionBuilderExtensions.cs index 1c9e7185734..a954985c881 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeRedactionBuilderExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeRedactionBuilderExtensions.cs @@ -20,13 +20,13 @@ namespace Microsoft.Extensions.Compliance.Redaction; public static class FakeRedactionBuilderExtensions { /// - /// Sets the fake redactor to use for a set of data classes. + /// Sets the fake redactor to use for a set of data classifications. /// /// The builder to attach the redactor to. - /// The data classes for which the redactor type should be used. + /// The data classifications for which the redactor type should be used. /// The value of . /// is . - public static IRedactionBuilder SetFakeRedactor(this IRedactionBuilder builder, params DataClassification[] classifications) + public static IRedactionBuilder SetFakeRedactor(this IRedactionBuilder builder, params DataClassificationSet[] classifications) { _ = Throw.IfNull(builder); @@ -36,14 +36,14 @@ public static IRedactionBuilder SetFakeRedactor(this IRedactionBuilder builder, } /// - /// Sets the fake redactor to use for a set of data classes. + /// Sets the fake redactor to use for a set of data classifications. /// /// The builder to attach the redactor to. /// Configuration function. - /// The data classes for which the redactor type should be used. + /// The data classifications for which the redactor type should be used. /// The value of . /// or is . - public static IRedactionBuilder SetFakeRedactor(this IRedactionBuilder builder, Action configure, params DataClassification[] classifications) + public static IRedactionBuilder SetFakeRedactor(this IRedactionBuilder builder, Action configure, params DataClassificationSet[] classifications) { _ = Throw.IfNull(builder); _ = Throw.IfNull(configure); @@ -58,17 +58,17 @@ public static IRedactionBuilder SetFakeRedactor(this IRedactionBuilder builder, } /// - /// Sets the fake redactor to use for a set of data classes. + /// Sets the fake redactor to use for a set of data classifications. /// /// The builder to attach the redactor to. /// Configuration section. - /// The data classes for which the redactor type should be used. + /// The data classifications for which the redactor type should be used. /// The value of . /// or is . [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "The type is FakeRedactorOptions and we know it.")] - public static IRedactionBuilder SetFakeRedactor(this IRedactionBuilder builder, IConfigurationSection section, params DataClassification[] classifications) + public static IRedactionBuilder SetFakeRedactor(this IRedactionBuilder builder, IConfigurationSection section, params DataClassificationSet[] classifications) { _ = Throw.IfNull(builder); _ = Throw.IfNull(section); diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeRedactorProvider.cs b/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeRedactorProvider.cs index 25b9d57d1b4..38b186688f4 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeRedactorProvider.cs +++ b/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeRedactorProvider.cs @@ -32,11 +32,11 @@ public FakeRedactorProvider(FakeRedactorOptions? options = null, FakeRedactionCo } /// - public Redactor GetRedactor(DataClassification classification) + public Redactor GetRedactor(DataClassificationSet classifications) { var order = Interlocked.Increment(ref _redactorsRequestedSoFar); - Collector.Append(new RedactorRequested(classification, order)); + Collector.Append(new RedactorRequested(classifications, order)); return _redactor; } diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeTaxonomy.cs b/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeTaxonomy.cs index 83d5f8741df..aa3e0dab597 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeTaxonomy.cs +++ b/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeTaxonomy.cs @@ -1,34 +1,27 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using Microsoft.Extensions.Compliance.Classification; namespace Microsoft.Extensions.Compliance.Testing; /// -/// Classes of data used for simple scenarios. +/// Simple data classifications for testing. /// -[Flags] -public enum FakeTaxonomy : ulong +public static class FakeTaxonomy { /// - /// No data classification. + /// Gets the name of this classification taxonomy. /// - None = DataClassification.NoneTaxonomyValue, + public static string TaxonomyName => typeof(FakeTaxonomy).FullName!; /// - /// This is public data. + /// Gets the private data classification. /// - PublicData = 1 << 0, + public static DataClassification PrivateData => new(TaxonomyName, nameof(PrivateData)); /// - /// This is private data. + /// Gets the public data classification. /// - PrivateData = 1 << 1, - - /// - /// Unknown data classification, handle with care. - /// - Unknown = DataClassification.UnknownTaxonomyValue, + public static DataClassification PublicData => new(TaxonomyName, nameof(PublicData)); } diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeTaxonomyExtensions.cs b/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeTaxonomyExtensions.cs deleted file mode 100644 index 4619ecce2f7..00000000000 --- a/src/Libraries/Microsoft.Extensions.Compliance.Testing/FakeTaxonomyExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.Compliance.Testing; -using Microsoft.Shared.Diagnostics; - -namespace Microsoft.Extensions.Compliance.Classification; - -/// -/// Extensions for working with the simple data classification taxonomy. -/// -public static class FakeTaxonomyExtensions -{ - /// - /// Gets the taxonomy value associated with a particular data classification. - /// - /// The data classification of interest. - /// The resulting taxonomy value for the given data classification. - public static FakeTaxonomy AsFakeTaxonomy(this DataClassification classification) - { - if (classification.TaxonomyName != FakeClassifications.TaxonomyName && !string.IsNullOrEmpty(classification.TaxonomyName)) - { - Throw.ArgumentException(nameof(classification), $"Unknown data taxonomy: {classification.TaxonomyName}"); - } - - return (FakeTaxonomy)classification.Value; - } -} diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Testing/Microsoft.Extensions.Compliance.Testing.csproj b/src/Libraries/Microsoft.Extensions.Compliance.Testing/Microsoft.Extensions.Compliance.Testing.csproj index 58c2467d412..1883b86af55 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Testing/Microsoft.Extensions.Compliance.Testing.csproj +++ b/src/Libraries/Microsoft.Extensions.Compliance.Testing/Microsoft.Extensions.Compliance.Testing.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Testing/Microsoft.Extensions.Compliance.Testing.json b/src/Libraries/Microsoft.Extensions.Compliance.Testing/Microsoft.Extensions.Compliance.Testing.json index 0579203717b..4d709edce76 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Testing/Microsoft.Extensions.Compliance.Testing.json +++ b/src/Libraries/Microsoft.Extensions.Compliance.Testing/Microsoft.Extensions.Compliance.Testing.json @@ -1,38 +1,20 @@ { "Name": "Microsoft.Extensions.Compliance.Testing, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", "Types": [ - { - "Type": "static class Microsoft.Extensions.Compliance.Testing.FakeClassifications", - "Stage": "Stable", - "Properties": [ - { - "Member": "static Microsoft.Extensions.Compliance.Classification.DataClassification Microsoft.Extensions.Compliance.Testing.FakeClassifications.PrivateData { get; }", - "Stage": "Stable" - }, - { - "Member": "static Microsoft.Extensions.Compliance.Classification.DataClassification Microsoft.Extensions.Compliance.Testing.FakeClassifications.PublicData { get; }", - "Stage": "Stable" - }, - { - "Member": "static string Microsoft.Extensions.Compliance.Testing.FakeClassifications.TaxonomyName { get; }", - "Stage": "Stable" - } - ] - }, { "Type": "static class Microsoft.Extensions.Compliance.Redaction.FakeRedactionBuilderExtensions", "Stage": "Stable", "Methods": [ { - "Member": "static Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder Microsoft.Extensions.Compliance.Redaction.FakeRedactionBuilderExtensions.SetFakeRedactor(this Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder builder, params Microsoft.Extensions.Compliance.Classification.DataClassification[] classifications);", + "Member": "static Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder Microsoft.Extensions.Compliance.Redaction.FakeRedactionBuilderExtensions.SetFakeRedactor(this Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder builder, params Microsoft.Extensions.Compliance.Classification.DataClassificationSet[] classifications);", "Stage": "Stable" }, { - "Member": "static Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder Microsoft.Extensions.Compliance.Redaction.FakeRedactionBuilderExtensions.SetFakeRedactor(this Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder builder, System.Action configure, params Microsoft.Extensions.Compliance.Classification.DataClassification[] classifications);", + "Member": "static Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder Microsoft.Extensions.Compliance.Redaction.FakeRedactionBuilderExtensions.SetFakeRedactor(this Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder builder, System.Action configure, params Microsoft.Extensions.Compliance.Classification.DataClassificationSet[] classifications);", "Stage": "Stable" }, { - "Member": "static Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder Microsoft.Extensions.Compliance.Redaction.FakeRedactionBuilderExtensions.SetFakeRedactor(this Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder builder, Microsoft.Extensions.Configuration.IConfigurationSection section, params Microsoft.Extensions.Compliance.Classification.DataClassification[] classifications);", + "Member": "static Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder Microsoft.Extensions.Compliance.Redaction.FakeRedactionBuilderExtensions.SetFakeRedactor(this Microsoft.Extensions.Compliance.Redaction.IRedactionBuilder builder, Microsoft.Extensions.Configuration.IConfigurationSection section, params Microsoft.Extensions.Compliance.Classification.DataClassificationSet[] classifications);", "Stage": "Stable" } ] @@ -142,7 +124,7 @@ "Stage": "Stable" }, { - "Member": "Microsoft.Extensions.Compliance.Redaction.Redactor Microsoft.Extensions.Compliance.Testing.FakeRedactorProvider.GetRedactor(Microsoft.Extensions.Compliance.Classification.DataClassification classification);", + "Member": "Microsoft.Extensions.Compliance.Redaction.Redactor Microsoft.Extensions.Compliance.Testing.FakeRedactorProvider.GetRedactor(Microsoft.Extensions.Compliance.Classification.DataClassificationSet classifications);", "Stage": "Stable" } ], @@ -154,43 +136,19 @@ ] }, { - "Type": "enum Microsoft.Extensions.Compliance.Testing.FakeTaxonomy : ulong", + "Type": "static class Microsoft.Extensions.Compliance.Testing.FakeTaxonomy", "Stage": "Stable", - "Methods": [ + "Properties": [ { - "Member": "Microsoft.Extensions.Compliance.Testing.FakeTaxonomy.FakeTaxonomy();", + "Member": "static Microsoft.Extensions.Compliance.Classification.DataClassification Microsoft.Extensions.Compliance.Testing.FakeTaxonomy.PrivateData { get; }", "Stage": "Stable" - } - ], - "Fields": [ - { - "Member": "const Microsoft.Extensions.Compliance.Testing.FakeTaxonomy Microsoft.Extensions.Compliance.Testing.FakeTaxonomy.None", - "Stage": "Stable", - "Value": "0" }, { - "Member": "const Microsoft.Extensions.Compliance.Testing.FakeTaxonomy Microsoft.Extensions.Compliance.Testing.FakeTaxonomy.PrivateData", - "Stage": "Stable", - "Value": "2" - }, - { - "Member": "const Microsoft.Extensions.Compliance.Testing.FakeTaxonomy Microsoft.Extensions.Compliance.Testing.FakeTaxonomy.PublicData", - "Stage": "Stable", - "Value": "1" + "Member": "static Microsoft.Extensions.Compliance.Classification.DataClassification Microsoft.Extensions.Compliance.Testing.FakeTaxonomy.PublicData { get; }", + "Stage": "Stable" }, { - "Member": "const Microsoft.Extensions.Compliance.Testing.FakeTaxonomy Microsoft.Extensions.Compliance.Testing.FakeTaxonomy.Unknown", - "Stage": "Stable", - "Value": "9223372036854775808" - } - ] - }, - { - "Type": "static class Microsoft.Extensions.Compliance.Classification.FakeTaxonomyExtensions", - "Stage": "Stable", - "Methods": [ - { - "Member": "static Microsoft.Extensions.Compliance.Testing.FakeTaxonomy Microsoft.Extensions.Compliance.Classification.FakeTaxonomyExtensions.AsFakeTaxonomy(this Microsoft.Extensions.Compliance.Classification.DataClassification classification);", + "Member": "static string Microsoft.Extensions.Compliance.Testing.FakeTaxonomy.TaxonomyName { get; }", "Stage": "Stable" } ] @@ -268,7 +226,7 @@ "Stage": "Stable", "Methods": [ { - "Member": "Microsoft.Extensions.Compliance.Testing.RedactorRequested.RedactorRequested(Microsoft.Extensions.Compliance.Classification.DataClassification classification, int sequenceNumber);", + "Member": "Microsoft.Extensions.Compliance.Testing.RedactorRequested.RedactorRequested(Microsoft.Extensions.Compliance.Classification.DataClassificationSet classifications, int sequenceNumber);", "Stage": "Stable" }, { @@ -298,7 +256,7 @@ ], "Properties": [ { - "Member": "Microsoft.Extensions.Compliance.Classification.DataClassification Microsoft.Extensions.Compliance.Testing.RedactorRequested.DataClassification { get; }", + "Member": "Microsoft.Extensions.Compliance.Classification.DataClassificationSet Microsoft.Extensions.Compliance.Testing.RedactorRequested.DataClassifications { get; }", "Stage": "Stable" }, { diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Testing/PrivateDataAttribute.cs b/src/Libraries/Microsoft.Extensions.Compliance.Testing/PrivateDataAttribute.cs index a68930d1477..9b2030a6c73 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Testing/PrivateDataAttribute.cs +++ b/src/Libraries/Microsoft.Extensions.Compliance.Testing/PrivateDataAttribute.cs @@ -14,7 +14,7 @@ public sealed class PrivateDataAttribute : DataClassificationAttribute /// Initializes a new instance of the class. /// public PrivateDataAttribute() - : base(FakeClassifications.PrivateData) + : base(FakeTaxonomy.PrivateData) { } } diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Testing/PublicDataAttribute.cs b/src/Libraries/Microsoft.Extensions.Compliance.Testing/PublicDataAttribute.cs index f98af6bc477..59b8d17191d 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Testing/PublicDataAttribute.cs +++ b/src/Libraries/Microsoft.Extensions.Compliance.Testing/PublicDataAttribute.cs @@ -14,7 +14,7 @@ public sealed class PublicDataAttribute : DataClassificationAttribute /// Initializes a new instance of the class. /// public PublicDataAttribute() - : base(FakeClassifications.PublicData) + : base(FakeTaxonomy.PublicData) { } } diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Testing/README.md b/src/Libraries/Microsoft.Extensions.Compliance.Testing/README.md index e845566c06f..8e634d0d18c 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Testing/README.md +++ b/src/Libraries/Microsoft.Extensions.Compliance.Testing/README.md @@ -1 +1,75 @@ -README +# Microsoft.Extensions.Compliance.Testing + +This package provides test fakes for testing data classification and redaction. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.Compliance.Testing +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage + +### Fake Redactor + +The `FakeRedactor` class provides options and services to verify redaction events. + +The fake redactor services can be registered using one of the `AddFakeRedaction` overloads: + +```csharp +public static IServiceCollection AddFakeRedaction(this IServiceCollection services); +public static IServiceCollection AddFakeRedaction(this IServiceCollection services, Action configure); +``` + +For example: + +```csharp +IServiceCollection services = new ServiceCollection(); +services = services.AddFakeRedaction(options => options.RedactionFormat = "Redacted: {0}"); +``` + +It also registers an instance of `Microsoft.Extensions.Compliance.Testing.FakeRedactionCollector` which can be used to inspect what was redacted. + +For example: + +```csharp +var serviceProvider = services.BuildServiceProvider(); +var collector = serviceProvider.GetRequiredService(); +Console.WriteLine(collector.AllRedactorRequests.Count); +``` + +### Fake Taxonomy + +The `Microsoft.Extensions.Compliance.Testing.FakeTaxonomy` taxonomy class contains simple data classification values to use while testing redaction. + +It consists of these data classification properties: + +- `FakeTaxonomy.PublicData` +- `FakeTaxonomy.PrivateData` + +These properties have their corresponding data classification attributes that can be used on method arguments and properties when features require it: + +- `[PublicData]` usually represents some data that should not be redacted. +- `[PrivateData]` usually extensions to setup that should be redacted. + +Example: + +```csharp +var redactionProvider = serviceProvider.GetFakeRedactionCollector(); +var redactor = redactionProvider.GetRedactor(FakeTaxonomy.PublicData); +Console.WriteLine(redactor.Redact("Hello")); // "Redacted: Hello" +``` + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.Compliance.Testing/RedactorRequested.cs b/src/Libraries/Microsoft.Extensions.Compliance.Testing/RedactorRequested.cs index c123e8e5fff..406d66ec211 100644 --- a/src/Libraries/Microsoft.Extensions.Compliance.Testing/RedactorRequested.cs +++ b/src/Libraries/Microsoft.Extensions.Compliance.Testing/RedactorRequested.cs @@ -12,9 +12,9 @@ namespace Microsoft.Extensions.Compliance.Testing; public readonly struct RedactorRequested : IEquatable { /// - /// Gets the data classification for which the redactor was returned. + /// Gets the data classifications for which the redactor was returned. /// - public DataClassification DataClassification { get; } + public DataClassificationSet DataClassifications { get; } /// /// Gets the order in which the redactor was requested. @@ -24,11 +24,11 @@ namespace Microsoft.Extensions.Compliance.Testing; /// /// Initializes a new instance of the struct. /// - /// Data class for which redactor was used. + /// Data classifications for which redactor was used. /// Order in which the request was used. - public RedactorRequested(DataClassification classification, int sequenceNumber) + public RedactorRequested(DataClassificationSet classifications, int sequenceNumber) { - DataClassification = classification; + DataClassifications = classifications; SequenceNumber = sequenceNumber; } @@ -44,13 +44,13 @@ public RedactorRequested(DataClassification classification, int sequenceNumber) /// /// Instance to check for equality. /// if object instances are equal otherwise. - public bool Equals(RedactorRequested other) => other.SequenceNumber == SequenceNumber && other.DataClassification == DataClassification; + public bool Equals(RedactorRequested other) => other.SequenceNumber == SequenceNumber && other.DataClassifications.Equals(DataClassifications); /// - /// Get hashcode of given . + /// Get the hash code of given . /// /// Hash code. - public override int GetHashCode() => HashCode.Combine(SequenceNumber, DataClassification); + public override int GetHashCode() => HashCode.Combine(SequenceNumber, DataClassifications); /// /// Compares two instances. diff --git a/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.Keyed.cs b/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.Keyed.cs index 74a7339a356..03a9f17810c 100644 --- a/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.Keyed.cs +++ b/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.Keyed.cs @@ -12,7 +12,7 @@ namespace Microsoft.Extensions.DependencyInjection; public static partial class AutoActivationExtensions { /// - /// Enforces keyed singleton activation at startup time rather then at runtime. + /// Enforces keyed singleton activation at startup time rather than at runtime. /// /// The type of the service to activate. /// The service collection containing the service. @@ -49,7 +49,7 @@ public static IServiceCollection ActivateKeyedSingleton( } /// - /// Enforces keyed singleton activation at startup time rather then at runtime. + /// Enforces keyed singleton activation at startup time rather than at runtime. /// /// The service collection to add the service to. /// The type of the service to activate. diff --git a/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.cs b/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.cs index d1fac9ba941..136a704ecfd 100644 --- a/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/AutoActivationExtensions.cs @@ -16,7 +16,7 @@ namespace Microsoft.Extensions.DependencyInjection; public static partial class AutoActivationExtensions { /// - /// Enforces singleton activation at startup time rather then at runtime. + /// Enforces singleton activation at startup time rather than at runtime. /// /// The type of the service to activate. /// The service collection containing the service. @@ -50,7 +50,7 @@ public static IServiceCollection ActivateSingleton(this IServiceCollec } /// - /// Enforces singleton activation at startup time rather then at runtime. + /// Enforces singleton activation at startup time rather than at runtime. /// /// The service collection containing the service. /// The type of the service to activate. diff --git a/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/README.md b/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/README.md index e845566c06f..f3ef10ab1e0 100644 --- a/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/README.md +++ b/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation/README.md @@ -1 +1,104 @@ -README +# Microsoft.Extensions.DependencyInjection.AutoActivation + +This provides the ability to instantiate registered singletons during startup instead of during the first time it is used. + +A singleton is typically created when it is first used, which can lead to higher than usual latency in responding to incoming requests. Creating the instances on startup helps prevent the service from exceeding its SLA for the first set of requests it processes. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.DependencyInjection.AutoActivation +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Example + +### Registering Services + +The services to auto-activate can be registered using the following methods: + +```csharp +static IServiceCollection ActivateSingleton(this IServiceCollection services); +static IServiceCollection ActivateSingleton(this IServiceCollection services, Type serviceType); +static IServiceCollection AddActivatedSingleton(this IServiceCollection services, Func implementationFactory); +static IServiceCollection AddActivatedSingleton(this IServiceCollection services); +static IServiceCollection AddActivatedSingleton(this IServiceCollection services, Func implementationFactory); +static IServiceCollection AddActivatedSingleton(this IServiceCollection services); +static IServiceCollection AddActivatedSingleton(this IServiceCollection services, Type serviceType); +static IServiceCollection AddActivatedSingleton(this IServiceCollection services, Type serviceType, Func implementationFactory); +static IServiceCollection AddActivatedSingleton(this IServiceCollection services, Type serviceType, Type implementationType); +static void TryAddActivatedSingleton(this IServiceCollection services, Type serviceType); +static void TryAddActivatedSingleton(this IServiceCollection services, Type serviceType, Type implementationType); +static void TryAddActivatedSingleton(this IServiceCollection services, Type serviceType, Func implementationFactory); +static void TryAddActivatedSingleton(this IServiceCollection services); +static void TryAddActivatedSingleton(this IServiceCollection services); +static void TryAddActivatedSingleton(this IServiceCollection services, Func implementationFactory); + +static IServiceCollection ActivateKeyedSingleton(this IServiceCollection services, object? serviceKey); +static IServiceCollection ActivateKeyedSingleton(this IServiceCollection services, Type serviceType, object? serviceKey); +static IServiceCollection AddActivatedKeyedSingleton(this IServiceCollection services, object? serviceKey, Func implementationFactory); +static IServiceCollection AddActivatedKeyedSingleton(this IServiceCollection services, object? serviceKey); +static IServiceCollection AddActivatedKeyedSingleton(this IServiceCollection services, object? serviceKey, Func implementationFactory); +static IServiceCollection AddActivatedKeyedSingleton(this IServiceCollection services, object? serviceKey); +static IServiceCollection AddActivatedKeyedSingleton(this IServiceCollection services, Type serviceType, object? serviceKey); +static IServiceCollection AddActivatedKeyedSingleton(this IServiceCollection services, Type serviceType, object? serviceKey, Func implementationFactory); +static IServiceCollection AddActivatedKeyedSingleton(this IServiceCollection services, Type serviceType, object? serviceKey, Type implementationType); +static void TryAddActivatedKeyedSingleton(this IServiceCollection services, Type serviceType, object? serviceKey); +static void TryAddActivatedKeyedSingleton(this IServiceCollection services, Type serviceType, object? serviceKey, Type implementationType); +static void TryAddActivatedKeyedSingleton(this IServiceCollection services, Type serviceType, object? serviceKey, Func implementationFactory); +static void TryAddActivatedKeyedSingleton(this IServiceCollection services, object? serviceKey); +static void TryAddActivatedKeyedSingleton(this IServiceCollection services, object? serviceKey); +TryAddActivatedKeyedSingleton(this IServiceCollection services, object? serviceKey, Func implementationFactory) +``` + +For example: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddActivatedSingleton(); + +var app = builder.Build(); + +app.Run(); + +public class MyService +{ + public MyService() + { + Console.WriteLine("MyService is created"); + } +} +``` + +Result: + +``` +MyService is created +info: Microsoft.Hosting.Lifetime[14] + Now listening on: http://localhost:5297 +info: Microsoft.Hosting.Lifetime[0] + Application started. Press Ctrl+C to shut down. +``` + +Services that are already registered can also be auto-activated: + +```csharp + +builder.Services.AddSingleton(); +// ... +builder.Services.ActivateSingleton(); +``` + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExceptionSummarization/ExceptionSummary.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ExceptionSummarization/ExceptionSummary.cs index f47a6a2c9d2..cd8c40e0d6f 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ExceptionSummarization/ExceptionSummary.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ExceptionSummarization/ExceptionSummary.cs @@ -15,7 +15,7 @@ namespace Microsoft.Extensions.Diagnostics.ExceptionSummarization; /// An exception summary represents a low-cardinality version of an exception's information, suitable for such /// cases. /// -/// The property never include sensitive information. +/// The property never includes sensitive information. /// But the property might contain sensitive information and should thus not be used in telemetry. /// public readonly struct ExceptionSummary : IEquatable diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExceptionSummarization/HttpExceptionSummaryProvider.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ExceptionSummarization/HttpExceptionSummaryProvider.cs index df331890863..fa31f2f2b5d 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ExceptionSummarization/HttpExceptionSummaryProvider.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ExceptionSummarization/HttpExceptionSummaryProvider.cs @@ -9,12 +9,8 @@ using System.Net; using System.Net.Sockets; using System.Threading.Tasks; -using Microsoft.Extensions.EnumStrings; using Microsoft.Shared.Diagnostics; -[assembly: EnumStrings(typeof(WebExceptionStatus))] -[assembly: EnumStrings(typeof(SocketError))] - namespace Microsoft.Extensions.Diagnostics.ExceptionSummarization; /// @@ -42,7 +38,7 @@ static HttpExceptionSummaryProvider() foreach (var v in Enum.GetValues(typeof(SocketError))) { var socketError = (SocketError)v!; - var name = socketError.ToInvariantString(); + var name = socketError.ToString(); socketErrors[socketError] = descriptions.Count; descriptions.Add(name); @@ -52,7 +48,7 @@ static HttpExceptionSummaryProvider() foreach (var v in Enum.GetValues(typeof(WebExceptionStatus))) { var status = (WebExceptionStatus)v!; - var name = status.ToInvariantString(); + var name = status.ToString(); webStatuses[status] = descriptions.Count; descriptions.Add(name); diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExceptionSummarization/Microsoft.Extensions.Diagnostics.ExceptionSummarization.csproj b/src/Libraries/Microsoft.Extensions.Diagnostics.ExceptionSummarization/Microsoft.Extensions.Diagnostics.ExceptionSummarization.csproj index 18276a08ddd..0073e039f8f 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ExceptionSummarization/Microsoft.Extensions.Diagnostics.ExceptionSummarization.csproj +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ExceptionSummarization/Microsoft.Extensions.Diagnostics.ExceptionSummarization.csproj @@ -8,7 +8,6 @@ true true - true @@ -17,10 +16,6 @@ 100 - - - - diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExceptionSummarization/README.md b/src/Libraries/Microsoft.Extensions.Diagnostics.ExceptionSummarization/README.md index e845566c06f..3b403c69a92 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ExceptionSummarization/README.md +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ExceptionSummarization/README.md @@ -1 +1,77 @@ -README +# Microsoft.Extensions.Diagnostics.ExceptionSummarization + +This provides the ability to extract essential information from well-known exception types and return a single string that can be used to create low-cardinality diagnostic messages for use in telemetry. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.Diagnostics.ExceptionSummarization +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Example + +### Registering Services + +The services can be registered using the following methods: + +```csharp +public static IServiceCollection AddExceptionSummarizer(this IServiceCollection services); +public static IServiceCollection AddExceptionSummarizer(this IServiceCollection services, Action configure); +``` + +For example: + +```csharp +services.AddExceptionSummarizer(); +``` + +The package comes with a predefined `IExceptionSummaryProvider` implementation that handles common +exceptions that might be sent during a web request. Here is how to register it: + +```csharp +using Microsoft.Extensions.Diagnostics.ExceptionSummarization; + +services.AddExceptionSummarizer(b => b.AddHttpProvider()); +``` + +### Consuming Services + +Once registered, the `IExceptionSummarizer` class can be resolved. For example: + +```csharp +using Microsoft.Extensions.Diagnostics.ExceptionSummarization; + +var summarizer = services.BuildServiceProvider().GetRequiredService(); + +try +{ + throw new SocketException((int)SocketError.NetworkDown); +} +catch (Exception e) +{ + ExceptionSummary summary = summarizer.Summarize(e); + + Console.WriteLine(summary.Description); // writes NetworkDown +} +``` + +The `ExceptionSummary.Description` property never includes sensitive information and can be safely used. +As opposed to `ExceptionSummary.AdditionalDetails` which can contain sensitive information that should not be stored and can only be used for debugging purpose. + +## Custom exception summarization + +Custom implementations of the `IExceptionSummaryProvider` interface can be used to provide a summary for any type of exception. + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). \ No newline at end of file diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/JustInTimeRedactor.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/JustInTimeRedactor.cs deleted file mode 100644 index fd7adb1ef09..00000000000 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/JustInTimeRedactor.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Globalization; -using Microsoft.Extensions.Compliance.Redaction; -using Microsoft.Extensions.ObjectPool; -using Microsoft.Shared.Pools; - -namespace Microsoft.Extensions.Logging; - -/// -/// Performs delayed redaction in order to avoid allocating intermediate strings and redact from span to span. -/// -internal sealed class JustInTimeRedactor : IResettable -#if NET6_0_OR_GREATER - , ISpanFormattable -#else - , IFormattable -#endif -{ - public static JustInTimeRedactor Get() => _pool.Get(); - public void Return() => _pool.Return(this); - - public JustInTimeRedactor? Next { get; set; } - public Redactor? Redactor { get; set; } - public object? Value { get; set; } - public override string ToString() => Redactor!.Redact(Value, null, CultureInfo.InvariantCulture); - public string ToString(string? format, IFormatProvider? formatProvider) => Redactor!.Redact(Value, format, formatProvider); - - public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) - => Redactor!.TryRedact(Value, destination, out charsWritten, format, provider); - - public bool TryReset() - { - Value = null; - return true; - } - - private static readonly ObjectPool _pool = PoolFactory.CreatePool(); -} diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggerRedactionOptions.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggerRedactionOptions.cs deleted file mode 100644 index e3307dc11b4..00000000000 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggerRedactionOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.Extensions.Logging; - -internal class LoggerRedactionOptions -{ -} diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggingRedactionExtensions.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggingRedactionExtensions.cs deleted file mode 100644 index 902c9cb20c0..00000000000 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggingRedactionExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; -using Microsoft.Shared.Diagnostics; - -namespace Microsoft.Extensions.Logging; - -/// -/// Extensions for configuring logging redaction features. -/// -public static class LoggingRedactionExtensions -{ - /// - /// Enables redaction functionality within the logging infrastructure. - /// - /// The dependency injection container to add logging to. - /// The value of . - public static ILoggingBuilder EnableRedaction(this ILoggingBuilder builder) - { - _ = Throw.IfNull(builder); - - builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); - _ = builder.Services.AddOptions(); - - return builder; - } -} diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/README.md b/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/README.md deleted file mode 100644 index e845566c06f..00000000000 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/README.md +++ /dev/null @@ -1 +0,0 @@ -README diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/README.md b/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/README.md deleted file mode 100644 index e845566c06f..00000000000 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/README.md +++ /dev/null @@ -1 +0,0 @@ -README diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/Log.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/Log.cs index 7c4927540ee..8ed8eaf7639 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/Log.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/Log.cs @@ -8,13 +8,13 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks; internal static partial class Log { - [LoggerMessage(0, LogLevel.Warning, "Process reporting unhealthy: {status}. Health check entries are {entries}")] + [LoggerMessage(0, LogLevel.Warning, "Process reporting unhealthy: {Status}. Health check entries are {Entries}")] public static partial void Unhealthy( ILogger logger, HealthStatus status, StringBuilder entries); - [LoggerMessage(1, LogLevel.Debug, "Process reporting healthy: {status}.")] + [LoggerMessage(1, LogLevel.Debug, "Process reporting healthy: {Status}.")] public static partial void Healthy( ILogger logger, HealthStatus status); diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/Metric.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/Metric.cs index e366c714bbe..22d12a14519 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/Metric.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/Metric.cs @@ -2,25 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.Metrics; -using System.Globalization; using Microsoft.Extensions.Diagnostics.Metrics; -using Microsoft.Extensions.EnumStrings; - -[assembly: EnumStrings(typeof(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus))] namespace Microsoft.Extensions.Diagnostics.HealthChecks; internal static partial class Metric { - [Counter("healthy", "status", Name = @"R9\\HealthCheck\\Report")] + [Counter("dotnet.health_check.status", Name = "dotnet.health_check.reports")] public static partial HealthCheckReportCounter CreateHealthCheckReportCounter(Meter meter); - [Counter("name", "status", Name = @"R9\\HealthCheck\\UnhealthyHealthCheck")] + [Counter("dotnet.health_check.name", "dotnet.health_check.status", Name = "dotnet.health_check.unhealthy_checks")] public static partial UnhealthyHealthCheckCounter CreateUnhealthyHealthCheckCounter(Meter meter); - public static void RecordMetric(this HealthCheckReportCounter counterMetric, bool isHealthy, HealthStatus status) - => counterMetric.Add(1, isHealthy.ToString(CultureInfo.InvariantCulture), status.ToInvariantString()); + public static void RecordMetric(this HealthCheckReportCounter counterMetric, HealthStatus status) + => counterMetric.Add(1, status.ToString()); public static void RecordMetric(this UnhealthyHealthCheckCounter counterMetric, string name, HealthStatus status) - => counterMetric.Add(1, name, status.ToInvariantString()); + => counterMetric.Add(1, name, status.ToString()); } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/Microsoft.Extensions.Diagnostics.HealthChecks.Common.csproj b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/Microsoft.Extensions.Diagnostics.HealthChecks.Common.csproj index cc867c62866..9d98141bfae 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/Microsoft.Extensions.Diagnostics.HealthChecks.Common.csproj +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/Microsoft.Extensions.Diagnostics.HealthChecks.Common.csproj @@ -8,7 +8,6 @@ true true - true true @@ -19,8 +18,7 @@ - - + diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/README.md b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/README.md index e845566c06f..4a48b1739db 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/README.md +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/README.md @@ -1 +1,150 @@ -README +# Microsoft.Extensions.Diagnostics.HealthChecks.Common + +This package contains common health check implementations. Here are the main features it provides: + +Application Lifecycle Health Check +- A provider that's tied to the application's lifecycle based on `IHostApplicationLifetime` +- Emits unhealthy status when the application is not started, stopping, or stopped +- Emits healthy status when the application is started and running + +Manual Health Check +- A provider that enables manual control of the application's health +- Emits unhealthy status when `ReportUnhealthy` is called +- Emits healthy status when `ReportHealthy` is called + +Telemetry Publisher +- A publisher which emits telemetry (logs and counters) representing the application's health +- Can be configured to log only when unhealthy reports are received + +See the [health checks](https://learn.microsoft.com/aspnet/core/host-and-deploy/health-checks) documentation for general usage guidance. + +See the [built in metrics](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/built-in-metrics-diagnostics#meter-microsoftextensionsdiagnosticshealthchecks) page for information regarding the metrics emitted by the telemetry publisher. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.Diagnostics.HealthChecks.Common +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Example + +### Application Lifecycle Health Check + +The application's lifecycle health check service can be registered and configured using any of the following API: + +```csharp +public static IHealthChecksBuilder AddApplicationLifecycleHealthCheck(this IHealthChecksBuilder builder, params string[] tags) +public static IHealthChecksBuilder AddApplicationLifecycleHealthCheck(this IHealthChecksBuilder builder, IEnumerable tags) +``` + +For example: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddHealthChecks() + .AddApplicationLifecycleHealthCheck(); + +var app = builder.Build(); + +app.MapHealthChecks("/healthz"); + +app.Run(); +``` + +### Manual Health Check + +The manual health check service can be registered and configured using any of the following API: + +```csharp +public static IHealthChecksBuilder AddManualHealthCheck(this IHealthChecksBuilder builder, params string[] tags) +public static IHealthChecksBuilder AddManualHealthCheck(this IHealthChecksBuilder builder, IEnumerable tags) +``` + +Then you can inject `IManualHealthCheck<>` into your services and call the following methods: + +```csharp +public static void ReportHealthy(this IManualHealthCheck manualHealthCheck) +public static void ReportUnhealthy(this IManualHealthCheck manualHealthCheck, string reason) +``` + +For example: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddHealthChecks() + .AddManualHealthCheck(); + +var app = builder.Build(); + +app.MapHealthChecks("/healthz"); + +app.Run(); + +public class MyService +{ + private readonly IManualHealthCheck healthCheck; + + // inject IManualHealthCheck<> into your service + public MyService(IManualHealthCheck healthCheck) + { + this.healthCheck = healthCheck; + } + + public void DoSomething() + { + // ... do something ... + + if (somethingBadHappened) + { + this.healthCheck.ReportUnhealthy("reason"); + } + + this.healthCheck.ReportHealthy(); + } +} +``` + +### Telemetry Publisher + +The telemetry publisher can be registered and configured using any of the following API: + +```csharp +public static IServiceCollection AddTelemetryHealthCheckPublisher(this IServiceCollection services) +public static IServiceCollection AddTelemetryHealthCheckPublisher(this IServiceCollection services, IConfigurationSection section) +public static IServiceCollection AddTelemetryHealthCheckPublisher(this IServiceCollection services, Action configure) +``` + +For example: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +// register health check services as needed +builder.Services.AddHealthChecks() + .AddCheck("Sample"); + +// register telemetry publisher +builder.Services.AddTelemetryHealthCheckPublisher(); + +var app = builder.Build(); + +app.MapHealthChecks("/healthz"); + +app.Run(); +``` + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/TelemetryHealthCheckPublisher.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/TelemetryHealthCheckPublisher.cs index 217bef39e8f..019ec5986da 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/TelemetryHealthCheckPublisher.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.Common/TelemetryHealthCheckPublisher.cs @@ -49,8 +49,6 @@ public Task PublishAsync(HealthReport report, CancellationToken cancellationToke { Log.Healthy(_logger, report.Status); } - - _metrics.HealthCheckReportCounter.RecordMetric(true, report.Status); } else { @@ -69,7 +67,7 @@ public Task PublishAsync(HealthReport report, CancellationToken cancellationToke .Append(entry.Key) .Append(": {") .Append("status: ") - .Append(entry.Value.Status.ToInvariantString()) + .Append(entry.Value.Status.ToString()) .Append(", description: ") .Append(entry.Value.Description) .Append('}'); @@ -79,10 +77,10 @@ public Task PublishAsync(HealthReport report, CancellationToken cancellationToke Log.Unhealthy(_logger, report.Status, stringBuilder); PoolFactory.SharedStringBuilderPool.Return(stringBuilder); - - _metrics.HealthCheckReportCounter.RecordMetric(false, report.Status); } + _metrics.HealthCheckReportCounter.RecordMetric(report.Status); + return Task.CompletedTask; } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/README.md b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/README.md index e845566c06f..d94223dbe4e 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/README.md +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization/README.md @@ -1 +1,68 @@ -README +# Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization + +This provides configurable health check reporting based on the current system resource utilization. See `Microsoft.Extensions.Diagnostics.ResourceMonitoring` for details on how resources are measured. See the [health checks](https://learn.microsoft.com/aspnet/core/host-and-deploy/health-checks) documentation for general usage guidance. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Example + +The health check services can be registered and configured using any of the following API: + +```csharp +public static IHealthChecksBuilder AddResourceUtilizationHealthCheck(this IHealthChecksBuilder builder, params string[] tags) +public static IHealthChecksBuilder AddResourceUtilizationHealthCheck(this IHealthChecksBuilder builder, IEnumerable tags) +public static IHealthChecksBuilder AddResourceUtilizationHealthCheck(this IHealthChecksBuilder builder, IConfigurationSection section) +public static IHealthChecksBuilder AddResourceUtilizationHealthCheck(this IHealthChecksBuilder builder, IConfigurationSection section, params string[] tags) +public static IHealthChecksBuilder AddResourceUtilizationHealthCheck(this IHealthChecksBuilder builder, IConfigurationSection section, IEnumerable tags) +public static IHealthChecksBuilder AddResourceUtilizationHealthCheck(this IHealthChecksBuilder builder, Action configure) +public static IHealthChecksBuilder AddResourceUtilizationHealthCheck(this IHealthChecksBuilder builder, Action configure, params string[] tags) +public static IHealthChecksBuilder AddResourceUtilizationHealthCheck(this IHealthChecksBuilder builder, Action configure, IEnumerable tags) +``` + +For example: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddHealthChecks() + .AddResourceUtilizationHealthCheck(o => + { + o.CpuThresholds = new ResourceUsageThresholds + { + DegradedUtilizationPercentage = 80, + UnhealthyUtilizationPercentage = 90, + }; + o.MemoryThresholds = new ResourceUsageThresholds + { + DegradedUtilizationPercentage = 80, + UnhealthyUtilizationPercentage = 90, + }; + o.SamplingWindow = TimeSpan.FromSeconds(5); + }); + +var app = builder.Build(); + +app.MapHealthChecks("/healthz"); + +app.Run(); +``` + +`CpuThresholds` and `MemoryThresholds`'s percentages default to `null` and will not be reported as degraded or unhealthy unless configured. The `SamplingWindow` defaults to 5 seconds. + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Probes/Directory.Build.props b/src/Libraries/Microsoft.Extensions.Diagnostics.Probes/Directory.Build.props new file mode 100644 index 00000000000..0f62eaa4953 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.Probes/Directory.Build.props @@ -0,0 +1,11 @@ + + + + dev + EXTEXP0015 + + + + \ No newline at end of file diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Probes/Microsoft.Extensions.Diagnostics.Probes.csproj b/src/Libraries/Microsoft.Extensions.Diagnostics.Probes/Microsoft.Extensions.Diagnostics.Probes.csproj index 407126733ab..529a9c73fa2 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Probes/Microsoft.Extensions.Diagnostics.Probes.csproj +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.Probes/Microsoft.Extensions.Diagnostics.Probes.csproj @@ -1,7 +1,7 @@  Microsoft.Extensions.Diagnostics.Probes - Provides support for environmental probes. + Provides support for Kubernetes TCP health probes. Resilience @@ -12,13 +12,12 @@ - dev 70 75 - + diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Probes/README.md b/src/Libraries/Microsoft.Extensions.Diagnostics.Probes/README.md index e845566c06f..8d6670a1d25 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Probes/README.md +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.Probes/README.md @@ -1 +1,48 @@ -README +# Microsoft.Extensions.Diagnostics.Probes + +Answers Kubernetes liveness, startup, and readiness TCP probes based on the results from the [Health Checks service](https://learn.microsoft.com/aspnet/core/host-and-deploy/health-checks). + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.Diagnostics.Probes +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Example + +The health check endpoints can be registered and configured with the following methods: + +```csharp +public static IServiceCollection AddKubernetesProbes(this IServiceCollection services) +public static IServiceCollection AddKubernetesProbes(this IServiceCollection services, IConfigurationSection section) +public static IServiceCollection AddKubernetesProbes(this IServiceCollection services, Action configure) +``` + +Each type of probe handler can have its details configured separately. + +```csharp +services.AddKubernetesProbes(options => +{ + options.LivenessProbe.TcpPort = 2305; + options.StartupProbe.TcpPort = 2306; + options.ReadinessProbe.TcpPort = 2307; +}) +``` + +The `HealthAssessmentPeriod` property defines how often the health-checks are assessed. By default 30 seconds. + +Each probe can also specify `Func? FilterChecks` to customize which health checks are run. + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationProvider.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationProvider.cs index 679d5a1c3bf..b2c2f259216 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationProvider.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/LinuxUtilizationProvider.cs @@ -9,7 +9,9 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux; internal sealed class LinuxUtilizationProvider : ISnapshotProvider { - private const float Hundred = 100.0f; + private const double One = 1.0; + private const long Hundred = 100L; + private readonly object _cpuLocker = new(); private readonly object _memoryLocker = new(); private readonly LinuxUtilizationParser _parser; @@ -48,7 +50,7 @@ public LinuxUtilizationProvider(IOptions options, Lin var hostCpus = _parser.GetHostCpuCount(); var availableCpus = _parser.GetCgroupLimitedCpus(); - _scale = hostCpus * Hundred / availableCpus; + _scale = hostCpus / availableCpus; _scaleForTrackerApi = hostCpus / availableCpus; #pragma warning disable CA2000 // Dispose objects before losing scope @@ -58,13 +60,13 @@ public LinuxUtilizationProvider(IOptions options, Lin var meter = meterFactory.Create("Microsoft.Extensions.Diagnostics.ResourceMonitoring"); #pragma warning restore CA2000 // Dispose objects before losing scope - _ = meter.CreateObservableGauge(name: ResourceUtilizationCounters.CpuConsumptionPercentage, observeValue: CpuPercentage); - _ = meter.CreateObservableGauge(name: ResourceUtilizationCounters.MemoryConsumptionPercentage, observeValue: MemoryPercentage); + _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.CpuUtilization, observeValue: CpuUtilization, unit: "1"); + _ = meter.CreateObservableGauge(name: ResourceUtilizationInstruments.MemoryUtilization, observeValue: MemoryUtilization, unit: "1"); Resources = new SystemResources(1, hostCpus, _totalMemoryInBytes, hostMemory); } - public double CpuPercentage() + public double CpuUtilization() { var now = _timeProvider.GetUtcNow(); bool needUpdate = false; @@ -91,7 +93,7 @@ public double CpuPercentage() if (deltaHost > 0 && deltaCgroup > 0) { - var percentage = Math.Min(Hundred, deltaCgroup / deltaHost * _scale); + var percentage = Math.Min(One, deltaCgroup / deltaHost * _scale); _cpuPercentage = percentage; _refreshAfterCpu = now.Add(_cpuRefreshInterval); @@ -105,7 +107,7 @@ public double CpuPercentage() return _cpuPercentage; } - public double MemoryPercentage() + public double MemoryUtilization() { var now = _timeProvider.GetUtcNow(); bool needUpdate = false; @@ -126,7 +128,7 @@ public double MemoryPercentage() { if (now >= _refreshAfterMemory) { - var memoryPercentage = Math.Min(Hundred, (double)memoryUsed / _totalMemoryInBytes * Hundred); + var memoryPercentage = Math.Min(One, (double)memoryUsed / _totalMemoryInBytes); _memoryPercentage = memoryPercentage; _refreshAfterMemory = now.Add(_memoryRefreshInterval); @@ -149,9 +151,9 @@ public Snapshot GetSnapshot() var memoryUsed = _parser.GetMemoryUsageInBytes(); return new Snapshot( - totalTimeSinceStart: TimeSpan.FromTicks(hostTime / (long)Hundred), + totalTimeSinceStart: TimeSpan.FromTicks(hostTime / Hundred), kernelTimeSinceStart: TimeSpan.Zero, - userTimeSinceStart: TimeSpan.FromTicks((long)(cgroupTime / (long)Hundred * _scaleForTrackerApi)), + userTimeSinceStart: TimeSpan.FromTicks((long)(cgroupTime / Hundred * _scaleForTrackerApi)), memoryUsageInBytes: memoryUsed); } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Log.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Log.cs index 9e241aa0a4d..d81e431fa7e 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Log.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Log.cs @@ -14,6 +14,6 @@ internal static partial class Log [LoggerMessage(1, LogLevel.Error, "Unable to gather utilization statistics.")] public static partial void HandledGatherStatisticsException(ILogger logger, Exception e); - [LoggerMessage(2, LogLevel.Error, "Publisher `{publisher}` was unable to publish utilization statistics.")] + [LoggerMessage(2, LogLevel.Error, "Publisher `{Publisher}` was unable to publish utilization statistics.")] public static partial void HandlePublishUtilizationException(ILogger logger, Exception e, string publisher); } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj index 333067cd272..e9ed2cd0e49 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj @@ -26,7 +26,7 @@ - + @@ -42,8 +42,6 @@ - - diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.json b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.json index 3bb53fbb05d..f20b3d49685 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.json +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.json @@ -133,20 +133,6 @@ } ] }, - { - "Type": "static class Microsoft.Extensions.Diagnostics.ResourceMonitoring.ResourceUtilizationCounters", - "Stage": "Stable", - "Properties": [ - { - "Member": "static string Microsoft.Extensions.Diagnostics.ResourceMonitoring.ResourceUtilizationCounters.CpuConsumptionPercentage { get; }", - "Stage": "Stable" - }, - { - "Member": "static string Microsoft.Extensions.Diagnostics.ResourceMonitoring.ResourceUtilizationCounters.MemoryConsumptionPercentage { get; }", - "Stage": "Stable" - } - ] - }, { "Type": "readonly struct Microsoft.Extensions.Diagnostics.ResourceMonitoring.SystemResources", "Stage": "Stable", diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/README.md b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/README.md index e845566c06f..8230d8bbdd9 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/README.md +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/README.md @@ -1 +1,24 @@ -README +# Microsoft.Extensions.Diagnostics.ResourceMonitoring + +Measures and reports processor and memory usage. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.Diagnostics.ResourceMonitoring +``` + +Or directly in the C# project file: + +```xml + + + +``` + + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringOptions.Linux.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringOptions.Linux.cs index 5ec54ef4d57..cb6038826f9 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringOptions.Linux.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringOptions.Linux.cs @@ -13,7 +13,7 @@ public partial class ResourceMonitoringOptions internal static readonly TimeSpan DefaultRefreshInterval = TimeSpan.FromSeconds(5); /// - /// Gets or sets the default interval used for refreshing values reported by . + /// Gets or sets the default interval used for refreshing values reported by "process.cpu.utilization" metrics. /// /// /// The default value is 5 seconds. @@ -26,7 +26,7 @@ public partial class ResourceMonitoringOptions public TimeSpan CpuConsumptionRefreshInterval { get; set; } = DefaultRefreshInterval; /// - /// Gets or sets the default interval used for refreshing values reported by . + /// Gets or sets the default interval used for refreshing values reported by "dotnet.process.memory.virtual.utilization" metrics. /// /// /// The default value is 5 seconds. diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilizationCounters.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilizationInstruments.cs similarity index 68% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilizationCounters.cs rename to src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilizationInstruments.cs index f873bef44d6..da38092353b 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilizationCounters.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilizationInstruments.cs @@ -7,24 +7,24 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring; /// Represents the names of instruments published by this package. /// /// -/// These counters are currently only published on Linux. +/// These metrics are currently only published on Linux. /// /// -public static class ResourceUtilizationCounters +internal static class ResourceUtilizationInstruments { /// - /// Gets the CPU consumption of the running application in percentages. + /// Gets the CPU consumption of the running application in range [0, 1]. /// /// /// The type of an instrument is . /// - public static string CpuConsumptionPercentage => "cpu_consumption_percentage"; + public const string CpuUtilization = "process.cpu.utilization"; /// - /// Gets the memory consumption of the running application in percentages. + /// Gets the memory consumption of the running application in range [0, 1]. /// /// /// The type of an instrument is . /// - public static string MemoryConsumptionPercentage => "memory_consumption_percentage"; + public const string MemoryUtilization = "dotnet.process.memory.virtual.utilization"; } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsCounters.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsCounters.cs index 5f87b9dfc19..a9510355125 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsCounters.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsCounters.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.Metrics; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Network; @@ -8,8 +10,12 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows; internal sealed class WindowsCounters { + private readonly TcpTableInfo _tcpTableInfo; + public WindowsCounters(IMeterFactory meterFactory, TcpTableInfo tcpTableInfo) { + _tcpTableInfo = tcpTableInfo; + #pragma warning disable CA2000 // Dispose objects before losing scope // We don't dispose the meter because IMeterFactory handles that // An issue on analyzer side: https://github.com/dotnet/roslyn-analyzers/issues/6912 @@ -17,196 +23,63 @@ public WindowsCounters(IMeterFactory meterFactory, TcpTableInfo tcpTableInfo) var meter = meterFactory.Create("Microsoft.Extensions.Diagnostics.ResourceMonitoring"); #pragma warning restore CA2000 // Dispose objects before losing scope - _ = meter.CreateObservableGauge( - "ipv4_tcp_connection_closed_count", - () => - { - var snapshot = tcpTableInfo.GetIPv4CachingSnapshot(); - return snapshot.ClosedCount; - }); - - _ = meter.CreateObservableGauge( - "ipv4_tcp_connection_listen_count", - () => - { - var snapshot = tcpTableInfo.GetIPv4CachingSnapshot(); - return snapshot.ListenCount; - }); - - _ = meter.CreateObservableGauge( - "ipv4_tcp_connection_syn_sent_count", - () => - { - var snapshot = tcpTableInfo.GetIPv4CachingSnapshot(); - return snapshot.SynSentCount; - }); - - _ = meter.CreateObservableGauge( - "ipv4_tcp_connection_syn_received_count", - () => - { - var snapshot = tcpTableInfo.GetIPv4CachingSnapshot(); - return snapshot.SynRcvdCount; - }); - - _ = meter.CreateObservableGauge( - "ipv4_tcp_connection_established_count", - () => - { - var snapshot = tcpTableInfo.GetIPv4CachingSnapshot(); - return snapshot.EstabCount; - }); - - _ = meter.CreateObservableGauge( - "ipv4_tcp_connection_fin_wait_1_count", - () => - { - var snapshot = tcpTableInfo.GetIPv4CachingSnapshot(); - return snapshot.FinWait1Count; - }); - - _ = meter.CreateObservableGauge( - "ipv4_tcp_connection_fin_wait_2_count", - () => - { - var snapshot = tcpTableInfo.GetIPv4CachingSnapshot(); - return snapshot.FinWait2Count; - }); - - _ = meter.CreateObservableGauge( - "ipv4_tcp_connection_close_wait_count", - () => - { - var snapshot = tcpTableInfo.GetIPv4CachingSnapshot(); - return snapshot.CloseWaitCount; - }); - - _ = meter.CreateObservableGauge( - "ipv4_tcp_connection_closing_count", - () => - { - var snapshot = tcpTableInfo.GetIPv4CachingSnapshot(); - return snapshot.ClosingCount; - }); - - _ = meter.CreateObservableGauge( - "ipv4_tcp_connection_last_ack_count", - () => - { - var snapshot = tcpTableInfo.GetIPv4CachingSnapshot(); - return snapshot.LastAckCount; - }); - - _ = meter.CreateObservableGauge( - "ipv4_tcp_connection_time_wait_count", - () => - { - var snapshot = tcpTableInfo.GetIPv4CachingSnapshot(); - return snapshot.TimeWaitCount; - }); - - _ = meter.CreateObservableGauge( - "ipv4_tcp_connection_delete_tcb_count", - () => - { - var snapshot = tcpTableInfo.GetIPv4CachingSnapshot(); - return snapshot.DeleteTcbCount; - }); - - _ = meter.CreateObservableGauge( - "ipv6_tcp_connection_closed_count", - () => - { - var snapshot = tcpTableInfo.GetIPv6CachingSnapshot(); - return snapshot.ClosedCount; - }); - - _ = meter.CreateObservableGauge( - "ipv6_tcp_connection_listen_count", - () => - { - var snapshot = tcpTableInfo.GetIPv6CachingSnapshot(); - return snapshot.ListenCount; - }); - - _ = meter.CreateObservableGauge( - "ipv6_tcp_connection_syn_sent_count", - () => - { - var snapshot = tcpTableInfo.GetIPv6CachingSnapshot(); - return snapshot.SynSentCount; - }); - - _ = meter.CreateObservableGauge( - "ipv6_tcp_connection_syn_received_count", - () => - { - var snapshot = tcpTableInfo.GetIPv6CachingSnapshot(); - return snapshot.SynRcvdCount; - }); - - _ = meter.CreateObservableGauge( - "ipv6_tcp_connection_established_count", - () => - { - var snapshot = tcpTableInfo.GetIPv6CachingSnapshot(); - return snapshot.EstabCount; - }); - - _ = meter.CreateObservableGauge( - "ipv6_tcp_connection_fin_wait_1_count", - () => - { - var snapshot = tcpTableInfo.GetIPv6CachingSnapshot(); - return snapshot.FinWait1Count; - }); - - _ = meter.CreateObservableGauge( - "ipv6_tcp_connection_fin_wait_2_count", - () => - { - var snapshot = tcpTableInfo.GetIPv6CachingSnapshot(); - return snapshot.FinWait2Count; - }); - - _ = meter.CreateObservableGauge( - "ipv6_tcp_connection_close_wait_count", - () => - { - var snapshot = tcpTableInfo.GetIPv6CachingSnapshot(); - return snapshot.CloseWaitCount; - }); - - _ = meter.CreateObservableGauge( - "ipv6_tcp_connection_closing_count", - () => - { - var snapshot = tcpTableInfo.GetIPv6CachingSnapshot(); - return snapshot.ClosingCount; - }); - - _ = meter.CreateObservableGauge( - "ipv6_tcp_connection_last_ack_count", - () => - { - var snapshot = tcpTableInfo.GetIPv6CachingSnapshot(); - return snapshot.LastAckCount; - }); - - _ = meter.CreateObservableGauge( - "ipv6_tcp_connection_time_wait_count", - () => - { - var snapshot = tcpTableInfo.GetIPv6CachingSnapshot(); - return snapshot.TimeWaitCount; - }); + var tcpTag = new KeyValuePair("network.transport", "tcp"); + var commonTags = new TagList + { + tcpTag + }; + + // The metric is aligned with + // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/system/system-metrics.md#metric-systemnetworkconnections + + _ = meter.CreateObservableUpDownCounter( + "system.network.connections", + GetMeasurements, + unit: "{connection}", + description: null, + tags: commonTags); + } - _ = meter.CreateObservableGauge( - "ipv6_tcp_connection_delete_tcb_count", - () => - { - var snapshot = tcpTableInfo.GetIPv6CachingSnapshot(); - return snapshot.DeleteTcbCount; - }); + private IEnumerable> GetMeasurements() + { + const string NetworkStateKey = "system.network.state"; + + // These are covered in https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md#attributes: + var tcpVersionFourTag = new KeyValuePair("network.type", "ipv4"); + var tcpVersionSixTag = new KeyValuePair("network.type", "ipv6"); + + var measurements = new List>(24); + + // IPv4: + var snapshotV4 = _tcpTableInfo.GetIPv4CachingSnapshot(); + measurements.Add(new Measurement(snapshotV4.ClosedCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "close") })); + measurements.Add(new Measurement(snapshotV4.ListenCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "listen") })); + measurements.Add(new Measurement(snapshotV4.SynSentCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "syn_sent") })); + measurements.Add(new Measurement(snapshotV4.SynRcvdCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "syn_recv") })); + measurements.Add(new Measurement(snapshotV4.EstabCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "established") })); + measurements.Add(new Measurement(snapshotV4.FinWait1Count, new TagList { tcpVersionFourTag, new(NetworkStateKey, "fin_wait_1") })); + measurements.Add(new Measurement(snapshotV4.FinWait2Count, new TagList { tcpVersionFourTag, new(NetworkStateKey, "fin_wait_2") })); + measurements.Add(new Measurement(snapshotV4.CloseWaitCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "close_wait") })); + measurements.Add(new Measurement(snapshotV4.ClosingCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "closing") })); + measurements.Add(new Measurement(snapshotV4.LastAckCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "last_ack") })); + measurements.Add(new Measurement(snapshotV4.TimeWaitCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "time_wait") })); + measurements.Add(new Measurement(snapshotV4.DeleteTcbCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "delete") })); + + // IPv6: + var snapshotV6 = _tcpTableInfo.GetIPv6CachingSnapshot(); + measurements.Add(new Measurement(snapshotV6.ClosedCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "close") })); + measurements.Add(new Measurement(snapshotV6.ListenCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "listen") })); + measurements.Add(new Measurement(snapshotV6.SynSentCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "syn_sent") })); + measurements.Add(new Measurement(snapshotV6.SynRcvdCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "syn_recv") })); + measurements.Add(new Measurement(snapshotV6.EstabCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "established") })); + measurements.Add(new Measurement(snapshotV6.FinWait1Count, new TagList { tcpVersionSixTag, new(NetworkStateKey, "fin_wait_1") })); + measurements.Add(new Measurement(snapshotV6.FinWait2Count, new TagList { tcpVersionSixTag, new(NetworkStateKey, "fin_wait_2") })); + measurements.Add(new Measurement(snapshotV6.CloseWaitCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "close_wait") })); + measurements.Add(new Measurement(snapshotV6.ClosingCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "closing") })); + measurements.Add(new Measurement(snapshotV6.LastAckCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "last_ack") })); + measurements.Add(new Measurement(snapshotV6.TimeWaitCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "time_wait") })); + measurements.Add(new Measurement(snapshotV6.DeleteTcbCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "delete") })); + + return measurements; } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Testing/Microsoft.Extensions.Diagnostics.Testing.csproj b/src/Libraries/Microsoft.Extensions.Diagnostics.Testing/Microsoft.Extensions.Diagnostics.Testing.csproj index 00e0e527218..9ffe5557c0c 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Testing/Microsoft.Extensions.Diagnostics.Testing.csproj +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.Testing/Microsoft.Extensions.Diagnostics.Testing.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Testing/README.md b/src/Libraries/Microsoft.Extensions.Diagnostics.Testing/README.md index e845566c06f..78ba11daeab 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Testing/README.md +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.Testing/README.md @@ -1 +1,24 @@ -README +# Microsoft.Extensions.Diagnostics.Testing + +Hand-crafted fakes to make telemetry-related testing easier. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.Diagnostics.Testing +``` + +Or directly in the C# project file: + +```xml + + + +``` + + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.EnumStrings/EnumStringsAttribute.cs b/src/Libraries/Microsoft.Extensions.EnumStrings/EnumStringsAttribute.cs deleted file mode 100644 index ab294d1b3f5..00000000000 --- a/src/Libraries/Microsoft.Extensions.EnumStrings/EnumStringsAttribute.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics; - -namespace Microsoft.Extensions.EnumStrings; - -/// -/// Provides information to guide the production of an extension method to efficiently convert an enum value into string form. -/// -[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Enum, AllowMultiple = true)] -[Conditional("CODE_GENERATION_ATTRIBUTES")] -public sealed class EnumStringsAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// - /// Use this overload when directly annotating an enum type. - /// - public EnumStringsAttribute() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The type of the enum to work with. - /// - /// Use this overload when applying the attribute at the assembly level when working with an enum declared in a - /// different assembly. - /// - /// - /// - /// [assembly: EnumStrings(typeof(System.ConsoleKey))] - /// - /// - public EnumStringsAttribute(Type enumType) - { - EnumType = enumType; - } - - /// - /// Gets the type of the enum to annotate. - /// - /// - /// This is when the attribute is applied directly to an enum type. - /// - public Type? EnumType { get; } - - /// - /// Gets or sets the namespace of the generated class. - /// - /// The default is . - /// - /// If , then the class is generated in the same namespace as the enum. - /// - public string? ExtensionNamespace { get; set; } - - /// - /// Gets or sets the name of the generated class. - /// - /// The default is . - /// - /// If , then the class name is generated by appending Extensions to the enum type name. - /// - public string? ExtensionClassName { get; set; } - - /// - /// Gets or sets the name of the generated extension method. - /// - /// The default is ToInvariantString. - public string ExtensionMethodName { get; set; } = "ToInvariantString"; - - /// - /// Gets or sets the modifiers to apply to the generated class. - /// - /// The default is internal static. - public string ExtensionClassModifiers { get; set; } = "internal static"; -} diff --git a/src/Libraries/Microsoft.Extensions.EnumStrings/Microsoft.Extensions.EnumStrings.csproj b/src/Libraries/Microsoft.Extensions.EnumStrings/Microsoft.Extensions.EnumStrings.csproj deleted file mode 100644 index 7f56054b16c..00000000000 --- a/src/Libraries/Microsoft.Extensions.EnumStrings/Microsoft.Extensions.EnumStrings.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - Microsoft.Extensions.EnumStrings - Abstractions to support the enum-to-string code generator. - Fundamentals - - - - true - - - - normal - 100 - 100 - - - - - - - - - - - diff --git a/src/Libraries/Microsoft.Extensions.EnumStrings/Microsoft.Extensions.EnumStrings.json b/src/Libraries/Microsoft.Extensions.EnumStrings/Microsoft.Extensions.EnumStrings.json deleted file mode 100644 index 5337cecb463..00000000000 --- a/src/Libraries/Microsoft.Extensions.EnumStrings/Microsoft.Extensions.EnumStrings.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "Name": "Microsoft.Extensions.EnumStrings, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", - "Types": [ - { - "Type": "sealed class Microsoft.Extensions.EnumStrings.EnumStringsAttribute : System.Attribute", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.EnumStrings.EnumStringsAttribute.EnumStringsAttribute();", - "Stage": "Stable" - }, - { - "Member": "Microsoft.Extensions.EnumStrings.EnumStringsAttribute.EnumStringsAttribute(System.Type enumType);", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "System.Type? Microsoft.Extensions.EnumStrings.EnumStringsAttribute.EnumType { get; }", - "Stage": "Stable" - }, - { - "Member": "string Microsoft.Extensions.EnumStrings.EnumStringsAttribute.ExtensionClassModifiers { get; set; }", - "Stage": "Stable" - }, - { - "Member": "string? Microsoft.Extensions.EnumStrings.EnumStringsAttribute.ExtensionClassName { get; set; }", - "Stage": "Stable" - }, - { - "Member": "string Microsoft.Extensions.EnumStrings.EnumStringsAttribute.ExtensionMethodName { get; set; }", - "Stage": "Stable" - }, - { - "Member": "string? Microsoft.Extensions.EnumStrings.EnumStringsAttribute.ExtensionNamespace { get; set; }", - "Stage": "Stable" - } - ] - } - ] -} \ No newline at end of file diff --git a/src/Libraries/Microsoft.Extensions.EnumStrings/README.md b/src/Libraries/Microsoft.Extensions.EnumStrings/README.md deleted file mode 100644 index e845566c06f..00000000000 --- a/src/Libraries/Microsoft.Extensions.EnumStrings/README.md +++ /dev/null @@ -1 +0,0 @@ -README diff --git a/src/Libraries/Microsoft.Extensions.EnumStrings/buildTransitive/Microsoft.Extensions.EnumStrings.props b/src/Libraries/Microsoft.Extensions.EnumStrings/buildTransitive/Microsoft.Extensions.EnumStrings.props deleted file mode 100644 index 7bc91e44385..00000000000 --- a/src/Libraries/Microsoft.Extensions.EnumStrings/buildTransitive/Microsoft.Extensions.EnumStrings.props +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/Libraries/Microsoft.Extensions.EnumStrings/buildTransitive/Microsoft.Extensions.EnumStrings.targets b/src/Libraries/Microsoft.Extensions.EnumStrings/buildTransitive/Microsoft.Extensions.EnumStrings.targets deleted file mode 100644 index 8c119d5413b..00000000000 --- a/src/Libraries/Microsoft.Extensions.EnumStrings/buildTransitive/Microsoft.Extensions.EnumStrings.targets +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/Libraries/Microsoft.Extensions.Hosting.Testing/Directory.Build.props b/src/Libraries/Microsoft.Extensions.Hosting.Testing/Directory.Build.props new file mode 100644 index 00000000000..6ad6add3254 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Hosting.Testing/Directory.Build.props @@ -0,0 +1,11 @@ + + + + dev + EXTEXP0016 + + + + \ No newline at end of file diff --git a/src/Libraries/Microsoft.Extensions.Hosting.Testing/FakeHost.cs b/src/Libraries/Microsoft.Extensions.Hosting.Testing/FakeHost.cs index f8ccc54cf97..068f1ca9a45 100644 --- a/src/Libraries/Microsoft.Extensions.Hosting.Testing/FakeHost.cs +++ b/src/Libraries/Microsoft.Extensions.Hosting.Testing/FakeHost.cs @@ -4,7 +4,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; using Microsoft.Shared.Diagnostics; namespace Microsoft.Extensions.Hosting.Testing; diff --git a/src/Libraries/Microsoft.Extensions.Hosting.Testing/FakeHostingExtensions.cs b/src/Libraries/Microsoft.Extensions.Hosting.Testing/FakeHostingExtensions.cs index 95684ddcc05..a503ac75985 100644 --- a/src/Libraries/Microsoft.Extensions.Hosting.Testing/FakeHostingExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Hosting.Testing/FakeHostingExtensions.cs @@ -20,7 +20,7 @@ namespace Microsoft.Extensions.Hosting; /// /// Extension methods supporting host unit testing scenarios. /// -[Experimental(diagnosticId: Experiments.Hosting, UrlFormat = Experiments.UrlFormat)] +[Experimental(diagnosticId: DiagnosticIds.Experiments.Hosting, UrlFormat = DiagnosticIds.UrlFormat)] public static class FakeHostingExtensions { /// diff --git a/src/Libraries/Microsoft.Extensions.Hosting.Testing/HostTerminatorService.cs b/src/Libraries/Microsoft.Extensions.Hosting.Testing/HostTerminatorService.cs index 54b807a1a14..d72936a312d 100644 --- a/src/Libraries/Microsoft.Extensions.Hosting.Testing/HostTerminatorService.cs +++ b/src/Libraries/Microsoft.Extensions.Hosting.Testing/HostTerminatorService.cs @@ -60,7 +60,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) _host.Dispose(); } - [LoggerMessage(0, LogLevel.Warning, "FakeHostOptions.TimeToLive set to {timeToLive} is up, disposing the host.")] + [LoggerMessage(0, LogLevel.Warning, "FakeHostOptions.TimeToLive set to {TimeToLive} is up, disposing the host.")] private partial void LogTimeToLiveUp(TimeSpan timeToLive); [LoggerMessage(1, LogLevel.Information, "Debugger is attached. The host won't be automatically disposed.")] diff --git a/src/Libraries/Microsoft.Extensions.Hosting.Testing/Microsoft.Extensions.Hosting.Testing.csproj b/src/Libraries/Microsoft.Extensions.Hosting.Testing/Microsoft.Extensions.Hosting.Testing.csproj index 2a7c07f74e5..942468ef795 100644 --- a/src/Libraries/Microsoft.Extensions.Hosting.Testing/Microsoft.Extensions.Hosting.Testing.csproj +++ b/src/Libraries/Microsoft.Extensions.Hosting.Testing/Microsoft.Extensions.Hosting.Testing.csproj @@ -1,7 +1,7 @@  Microsoft.Extensions.Hosting - Testing for integration test hosting and related test oriented helpers + Tools for integration testing of apps built with Microsoft.Extensions.Hosting Fundamentals $(PackageTags);Testing @@ -13,20 +13,18 @@ - dev 100 90 - + - diff --git a/src/Libraries/Microsoft.Extensions.Hosting.Testing/README.md b/src/Libraries/Microsoft.Extensions.Hosting.Testing/README.md index e845566c06f..33f082406ce 100644 --- a/src/Libraries/Microsoft.Extensions.Hosting.Testing/README.md +++ b/src/Libraries/Microsoft.Extensions.Hosting.Testing/README.md @@ -1 +1,52 @@ -README +# Microsoft.Extensions.Hosting.Testing + +Tools for integration testing of apps built with Microsoft.Extensions.Hosting. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.Hosting.Testing +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Example + +### FakeHost + +`FakeHost` enables creating an application host pre-configured for a unit test environment. + +The host can be created using the following APIs: + +```csharp +public static IHostBuilder CreateBuilder() +public static IHostBuilder CreateBuilder(Action configure) +public static IHostBuilder CreateBuilder(FakeHostOptions options) +``` + +For example: + +```csharp +using var host = await FakeHost.CreateBuilder(options => { }); + .ConfigureServices(x => + { + // ... + }) + .StartAsync(); +``` + +### FakeHostingExtensions + +`FakeHostingExtensions` is a collection of [`IHost`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.hosting.ihost) and [`IHostBuilder`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.hosting.ihostbuilder) extension methods for use within a unit test environment. These help collect logs as well as augment the host and app configurations. + +## Feedback & Contributing + +For any feedback or contributions, please visit us in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientAttribute.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientAttribute.cs deleted file mode 100644 index da5288cab56..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientAttribute.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics; -using System.Net.Http; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Triggers the generation of REST APIs and provides information about the HTTP client and, optionally, the name of the dependency. -/// -/// -/// This attribute triggers the production of REST APIs and provides information about the HTTP client and optionally the name of the dependency. -/// It can only be applied to interfaces and their name must start with an 'I', for example IMyClient. -/// This attribute must receive as a first parameter the HTTP client name to be retrieved from the . -/// Optionally, it may receive a second parameter that will set the dependency name used in generated telemetry. If this value is not set, it will use the name of the interface -/// without the leading 'I'. -/// If the interface name ends in 'Client' or 'Api', the dependency name will exclude that. Example: IMyDependencyClient would result in dependency name MyDependency. -/// -/// -/// -/// [AutoClient("MyClient")] -/// interface IMyDependencyClient -/// { -/// } -/// -/// -[AttributeUsage(AttributeTargets.Interface)] -[Conditional("CODE_GENERATION_ATTRIBUTES")] -public sealed class AutoClientAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The name of the HTTP client to be retrieved from . - public AutoClientAttribute(string httpClientName) - { - HttpClientName = httpClientName; - } - - /// - /// Initializes a new instance of the class. - /// - /// The name of the HTTP client to be retrieved from . - /// The dependency name override to use. - public AutoClientAttribute(string httpClientName, string customDependencyName) - { - HttpClientName = httpClientName; - CustomDependencyName = customDependencyName; - } - - /// - /// Gets the HTTP client name of the API. - /// - public string HttpClientName { get; } - - /// - /// Gets the custom dependency name of the API. This is used in generated telemetry. - /// - /// - /// If this value is not set, then for the dependency name it will use the name of the interface without the leading 'I' with trimming 'Client' or 'Api' at the end. - /// - public string? CustomDependencyName { get; } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientException.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientException.cs deleted file mode 100644 index 54d18e55ad9..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientException.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.Shared.Diagnostics; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// The exception that's thrown when REST API requests aren't successful. -/// -/// -/// This exception is thrown whenever a REST API call returns a non-successful status code. It contains the status code -/// and the HTTP content returned by the dependency, so that the user can handle exceptions accordingly. -/// -/// -/// -/// try -/// { -/// await _myClient.SendRequest(); -/// } -/// catch (AutoClientException ex) when (ex.StatusCode == 403) -/// { -/// // Handle forbidden scenario -/// } -/// -/// -[SuppressMessage("Design", "CA1032:Implement standard exception constructors", Justification = "Not applicable to this exception")] -public class AutoClientException : Exception -{ - /// - /// Gets the HTTP response. - /// - public AutoClientHttpError? HttpError { get; } - - /// - /// Gets the initial path of the HTTP request. - /// - public string Path { get; } - - /// - /// Gets the HTTP status code. - /// - public int? StatusCode => HttpError?.StatusCode; - - /// - /// Initializes a new instance of the class. - /// - /// The exception message. - /// The path of the request. - /// The HTTP error details. - public AutoClientException(string message, string path, AutoClientHttpError? error = null) - : base(message) - { - Path = Throw.IfNull(path); - HttpError = error; - } - - /// - /// Initializes a new instance of the class. - /// - /// The exception message. - /// The exception that is the cause of the current exception. - /// The path of the request. - /// The HTTP error details. - public AutoClientException(string message, Exception innerException, string path, AutoClientHttpError? error = null) - : base(message, innerException) - { - Path = Throw.IfNull(path); - HttpError = error; - } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientHttpError.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientHttpError.cs deleted file mode 100644 index 6e2fb12406e..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientHttpError.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Primitives; -using Microsoft.Shared.Diagnostics; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Holds details about an HTTP error. -/// -/// -/// When a REST API client fails, it will throw a . -/// This exception contains a instance that holds details like content, headers and status code. -/// -public class AutoClientHttpError -{ - /// - /// Initializes a new instance of the class. - /// - /// The HTTP status code of the response. - /// The response headers. - /// The raw string content of the response. - /// The HTTP error reason. - public AutoClientHttpError(int statusCode, IReadOnlyDictionary responseHeaders, string rawContent, string? reasonPhrase) - { - StatusCode = Throw.IfNull(statusCode); - ResponseHeaders = Throw.IfNull(responseHeaders); - RawContent = Throw.IfNull(rawContent); - ReasonPhrase = reasonPhrase; - } - - /// - /// Gets the HTTP status code returned in the response. - /// - public int StatusCode { get; } - - /// - /// Gets the HTTP response headers. - /// - public IReadOnlyDictionary ResponseHeaders { get; } - - /// - /// Gets the raw string content returned in the response. - /// - public string RawContent { get; } - - /// - /// Gets the HTTP error reason. - /// - public string? ReasonPhrase { get; } - - /// - /// Creates an instance of based on an . - /// - /// The response to be used. - /// Cancellation token used on asynchronous calls. - /// An instance of . - public static async Task CreateAsync(HttpResponseMessage response, CancellationToken cancellationToken) - { - Throw.IfNull(response); - -#if NET5_0_OR_GREATER - var rawContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); -#else - cancellationToken.ThrowIfCancellationRequested(); - var rawContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); -#endif - - var responseHeaders = response.Headers.ToDictionary(p => p.Key, p => new StringValues(p.Value.ToArray())); - - foreach (var header in response.Content.Headers) - { - responseHeaders[header.Key] = new StringValues(header.Value.ToArray()); - } - - return new AutoClientHttpError((int)response.StatusCode, responseHeaders, rawContent, response.ReasonPhrase); - } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientOptions.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientOptions.cs deleted file mode 100644 index 8caa124cc88..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientOptions.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.ComponentModel.DataAnnotations; -using System.Text.Json; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Options to configure generated REST API clients. -/// -/// -/// This options class is used to configure generated REST API clients. -/// -/// -/// -/// services.AddMyDependencyClient(options => -/// { -/// options.JsonSerializerOptions = new MyJsonSerializerOptions(); -/// }); -/// -/// -public class AutoClientOptions -{ - /// - /// Gets or sets JSON payload serialization options. - /// - [Required] - public JsonSerializerOptions JsonSerializerOptions { get; set; } = new(); -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientOptionsValidator.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientOptionsValidator.cs deleted file mode 100644 index 29f287cd487..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientOptionsValidator.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.ComponentModel; -using Microsoft.Extensions.Options; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Validator for . -/// -/// -/// This type is not intended to be directly invoked by application code. -/// It's intended to be invoked by generated code. -/// -[EditorBrowsable(EditorBrowsableState.Never)] -[OptionsValidator] -public sealed partial class AutoClientOptionsValidator : IValidateOptions -{ -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientUtilities.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientUtilities.cs deleted file mode 100644 index d3f8f42b372..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/AutoClientUtilities.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.ComponentModel; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Utilities for AutoClient feature. -/// -[EditorBrowsable(EditorBrowsableState.Never)] -public static class AutoClientUtilities -{ - private static readonly char[] _slashes = { '/', '\\' }; - private static readonly char[] _slashesOrDot = { '/', '\\', '.' }; - - /// - /// Returns whether a value is a valid path argument. - /// - /// The value to validate. - /// Whether the value is a valid path argument. - public static bool IsPathParameterValid(string value) - { - var trimmed = value.AsSpan().Trim(); - - if (trimmed.Length == 0) - { - return false; - } - - // Fast path for most common cases - var index = trimmed.IndexOfAny(_slashesOrDot); - if (index == -1) - { - return true; - } - - // Slashes can't be used - if (trimmed.Slice(index).IndexOfAny(_slashes) >= 0) - { - return false; - } - - // The trimmed string can't be made of dots only - -#if NET7_0_OR_GREATER - - if (trimmed.IndexOfAnyExcept('.') >= 0) - { - return true; - } - -#else - - for (var i = 0; i < trimmed.Length; i++) - { - if (trimmed[i] != '.') - { - return true; - } - } - -#endif - - return false; - } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/BodyAttribute.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/BodyAttribute.cs deleted file mode 100644 index 957ebfd5a7d..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/BodyAttribute.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Defines the body for the API request. -/// -/// -/// Marks a method parameter as the body for the request. -/// This attribute cannot be used with a GET or HEAD request. -/// -/// -/// -/// [AutoClient("MyClient")] -/// interface IMyDependencyClient -/// { -/// [Post("/api/users")] -/// Task<User> PostUserAsync([Body] User user, CancellationToken cancellationToken); -/// } -/// -/// -[AttributeUsage(AttributeTargets.Parameter)] -[Conditional("CODE_GENERATION_ATTRIBUTES")] -public sealed class BodyAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// - /// This defaults to a body content type of . - /// - public BodyAttribute() - : this(BodyContentType.ApplicationJson) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The content type to be used on the request content. - public BodyAttribute(BodyContentType contentType) - { - ContentType = contentType; - } - - /// - /// Gets the body content type. - /// - public BodyContentType ContentType { get; } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/BodyContentType.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/BodyContentType.cs deleted file mode 100644 index a87a782c1bc..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/BodyContentType.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Defines the types of encoding possible for request bodies. -/// -public enum BodyContentType -{ - /// - /// Represents the "application/json" content type. - /// - /// - /// With this content type, the parameter value is serialized to JSON before sending it in the request. - /// - ApplicationJson, - - /// - /// Represents the "text/plain" content type. - /// - /// - /// With this content type, .ToString() is called on the parameter value before sending it in the request. - /// - TextPlain -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/DeleteAttribute.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/DeleteAttribute.cs deleted file mode 100644 index 4c8e8b169ee..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/DeleteAttribute.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Net.Http; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Defines an API DELETE method. -/// -/// -/// Marks a method within an interface annotated with as an API DELETE method. -/// -/// The return type of an API method must be a Task<T>. -/// If T is a and the dependency returns "text/plain" content type, the result will be the raw content of the response. Otherwise, it will be deserialized from JSON. -/// If T is of type , the result will be the actual response message without further processing. -/// -/// If you provide an extra parameter to the method, you should use it between curly braces in the URL to make it a URL parameter. For example: /api/users/{userId}. -/// -/// -/// -/// [AutoClient("MyClient")] -/// interface IMyDependencyClient -/// { -/// [Delete("/api/users/{userId}")] -/// Task<bool> DeleteUserAsync(string userId, CancellationToken cancellationToken = default); -/// } -/// -/// -[AttributeUsage(AttributeTargets.Method)] -public sealed class DeleteAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The path of the request. Cannot be empty or null. - public DeleteAttribute(string path) - { - Path = path; - } - - /// - /// Gets the path of the request. - /// - public string Path { get; } - - /// - /// Gets or sets the name to use for this request within telemetry. - /// - /// - /// If this property is not provided, the request name is obtained from the method name. - /// If the method name ends in 'Async', the request name will exclude that. - /// For example, if the method is called DeleteUserAsync, the request name, by default, will be DeleteUser. - /// - /// - /// - /// [AutoClient("MyClient")] - /// interface IMyDependencyClient - /// { - /// [Delete("/api/users/{userId}", RequestName = "RemoveUsers")] - /// Task<bool> DeleteUserAsync(string userId, CancellationToken cancellationToken = default); - /// } - /// - /// - public string? RequestName { get; set; } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/GetAttribute.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/GetAttribute.cs deleted file mode 100644 index de16b0226f2..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/GetAttribute.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Net.Http; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Defines an API GET method. -/// -/// -/// Marks a method within an interface annotated with as an API GET method. -/// -/// The return type of an API method must be a Task<T>. -/// If T is a and the dependency returns "text/plain" content type, the result will be the raw content of the response. Otherwise, it will be deserialized from JSON. -/// If T is of type , the result will be the actual response message without further processing. -/// -/// If you provide an extra parameter to the method, you should use it between curly braces in the URL to make it an URL parameter. For example: /api/users/{userId}. -/// -/// -/// -/// [AutoClient("MyClient")] -/// interface IMyDependencyClient -/// { -/// [Get("/api/users/{userId}")] -/// Task<User> GetUserAsync(string userId, [Query] string filter, CancellationToken cancellationToken); -/// } -/// -/// -[AttributeUsage(AttributeTargets.Method)] -public sealed class GetAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The path of the request. Cannot be empty or null. - public GetAttribute(string path) - { - Path = path; - } - - /// - /// Gets the path of the request. - /// - public string Path { get; } - - /// - /// Gets or sets the name to use for this request within telemetry. - /// - /// - /// If this property is not provided, the request name is obtained from the method name. - /// If the method name ends in 'Async', the request name will exclude that. - /// For example, if the method is called GetUsersAsync, the request name, by default, will be GetUsers. - /// - /// - /// - /// [AutoClient("MyClient")] - /// interface IMyDependencyClient - /// { - /// [Get("/api/users", RequestName = "ObtainUsers")] - /// Task<string> GetUsersAsync(CancellationToken cancellationToken = default); - /// } - /// - /// - public string? RequestName { get; set; } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/HeadAttribute.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/HeadAttribute.cs deleted file mode 100644 index 8a31fdb0d2b..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/HeadAttribute.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Net.Http; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Defines an API HEAD method. -/// -/// -/// Marks a method within an interface annotated with as an API HEAD method. -/// -/// The return type of an API method must be a Task<T>. -/// If T is a and the dependency returns "text/plain" content type, the result will be the raw content of the response. Otherwise, it will be deserialized from JSON. -/// If T is of type , the result will be the actual response message without further processing. -/// -/// If you provide an extra parameter to the method, you should use it between curly braces in the URL to make it an URL parameter. For example: /api/users/{userId}. -/// -/// -/// -/// [AutoClient("MyClient")] -/// interface IMyDependencyClient -/// { -/// [Head("/api/users/{userId}")] -/// Task<UserHead> HeadUserAsync(string userId, CancellationToken cancellationToken); -/// } -/// -/// -[AttributeUsage(AttributeTargets.Method)] -public sealed class HeadAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The path of the request. Cannot be empty or null. - public HeadAttribute(string path) - { - Path = path; - } - - /// - /// Gets the path of the request. - /// - public string Path { get; } - - /// - /// Gets or sets the name to use for this request within telemetry. - /// - /// - /// If this property is not provided, the request name is obtained from the method name. - /// If the method name ends in 'Async', the request name will exclude that. - /// For example, if the method is called HeadUserAsync, the request name, by default, will be HeadUser. - /// - /// - /// - /// [AutoClient("MyClient")] - /// interface IMyDependencyClient - /// { - /// [Head("/api/users/{userId}", RequestName = "Head")] - /// Task<UserHead> HeadUserAsync(string userId, CancellationToken cancellationToken); - /// } - /// - /// - public string? RequestName { get; set; } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/HeaderAttribute.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/HeaderAttribute.cs deleted file mode 100644 index 7c5f3ade3d5..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/HeaderAttribute.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Defines a header to be used in the API request. -/// -/// -/// Marks a method parameter as a header to insert in the request. -/// -/// -/// -/// [AutoClient("MyClient")] -/// interface IMyDependencyClient -/// { -/// [Get("/api/users")] -/// Task<string> GetUsersAsync([Header("X-UserName")] string userName, CancellationToken cancellationToken); -/// } -/// -/// -[AttributeUsage(AttributeTargets.Parameter)] -[Conditional("CODE_GENERATION_ATTRIBUTES")] -public sealed class HeaderAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The name of the header. - public HeaderAttribute(string header) - { - Header = header; - } - - /// - /// Gets the name of the header. - /// - public string Header { get; } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/Microsoft.Extensions.Http.AutoClient.csproj b/src/Libraries/Microsoft.Extensions.Http.AutoClient/Microsoft.Extensions.Http.AutoClient.csproj deleted file mode 100644 index 55f35334d84..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/Microsoft.Extensions.Http.AutoClient.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - Microsoft.Extensions.Http.AutoClient - Makes it easy to automatically create efficient and easy to use client code to invoke REST HTTP APIs. - Fundamentals - - - - true - - - - normal - 94 - 100 - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/Microsoft.Extensions.Http.AutoClient.json b/src/Libraries/Microsoft.Extensions.Http.AutoClient/Microsoft.Extensions.Http.AutoClient.json deleted file mode 100644 index 5fd463b8e74..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/Microsoft.Extensions.Http.AutoClient.json +++ /dev/null @@ -1,367 +0,0 @@ -{ - "Name": "Microsoft.Extensions.Http.AutoClient, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", - "Types": [ - { - "Type": "sealed class Microsoft.Extensions.Http.AutoClient.AutoClientAttribute : System.Attribute", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.AutoClientAttribute.AutoClientAttribute(string httpClientName);", - "Stage": "Stable" - }, - { - "Member": "Microsoft.Extensions.Http.AutoClient.AutoClientAttribute.AutoClientAttribute(string httpClientName, string customDependencyName);", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "string? Microsoft.Extensions.Http.AutoClient.AutoClientAttribute.CustomDependencyName { get; }", - "Stage": "Stable" - }, - { - "Member": "string Microsoft.Extensions.Http.AutoClient.AutoClientAttribute.HttpClientName { get; }", - "Stage": "Stable" - } - ] - }, - { - "Type": "class Microsoft.Extensions.Http.AutoClient.AutoClientException : System.Exception", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.AutoClientException.AutoClientException(string message, string path, Microsoft.Extensions.Http.AutoClient.AutoClientHttpError? error = null);", - "Stage": "Stable" - }, - { - "Member": "Microsoft.Extensions.Http.AutoClient.AutoClientException.AutoClientException(string message, System.Exception innerException, string path, Microsoft.Extensions.Http.AutoClient.AutoClientHttpError? error = null);", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.AutoClientHttpError? Microsoft.Extensions.Http.AutoClient.AutoClientException.HttpError { get; }", - "Stage": "Stable" - }, - { - "Member": "string Microsoft.Extensions.Http.AutoClient.AutoClientException.Path { get; }", - "Stage": "Stable" - }, - { - "Member": "int? Microsoft.Extensions.Http.AutoClient.AutoClientException.StatusCode { get; }", - "Stage": "Stable" - } - ] - }, - { - "Type": "class Microsoft.Extensions.Http.AutoClient.AutoClientHttpError", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.AutoClientHttpError.AutoClientHttpError(int statusCode, System.Collections.Generic.IReadOnlyDictionary responseHeaders, string rawContent, string? reasonPhrase);", - "Stage": "Stable" - }, - { - "Member": "static System.Threading.Tasks.Task Microsoft.Extensions.Http.AutoClient.AutoClientHttpError.CreateAsync(System.Net.Http.HttpResponseMessage response, System.Threading.CancellationToken cancellationToken);", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "string Microsoft.Extensions.Http.AutoClient.AutoClientHttpError.RawContent { get; }", - "Stage": "Stable" - }, - { - "Member": "string? Microsoft.Extensions.Http.AutoClient.AutoClientHttpError.ReasonPhrase { get; }", - "Stage": "Stable" - }, - { - "Member": "System.Collections.Generic.IReadOnlyDictionary Microsoft.Extensions.Http.AutoClient.AutoClientHttpError.ResponseHeaders { get; }", - "Stage": "Stable" - }, - { - "Member": "int Microsoft.Extensions.Http.AutoClient.AutoClientHttpError.StatusCode { get; }", - "Stage": "Stable" - } - ] - }, - { - "Type": "class Microsoft.Extensions.Http.AutoClient.AutoClientOptions", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.AutoClientOptions.AutoClientOptions();", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "System.Text.Json.JsonSerializerOptions Microsoft.Extensions.Http.AutoClient.AutoClientOptions.JsonSerializerOptions { get; set; }", - "Stage": "Stable" - } - ] - }, - { - "Type": "sealed class Microsoft.Extensions.Http.AutoClient.AutoClientOptionsValidator : Microsoft.Extensions.Options.IValidateOptions", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.AutoClientOptionsValidator.AutoClientOptionsValidator();", - "Stage": "Stable" - }, - { - "Member": "Microsoft.Extensions.Options.ValidateOptionsResult Microsoft.Extensions.Http.AutoClient.AutoClientOptionsValidator.Validate(string? name, Microsoft.Extensions.Http.AutoClient.AutoClientOptions options);", - "Stage": "Stable" - } - ] - }, - { - "Type": "static class Microsoft.Extensions.Http.AutoClient.AutoClientUtilities", - "Stage": "Stable", - "Methods": [ - { - "Member": "static bool Microsoft.Extensions.Http.AutoClient.AutoClientUtilities.IsPathParameterValid(string value);", - "Stage": "Stable" - } - ] - }, - { - "Type": "sealed class Microsoft.Extensions.Http.AutoClient.BodyAttribute : System.Attribute", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.BodyAttribute.BodyAttribute();", - "Stage": "Stable" - }, - { - "Member": "Microsoft.Extensions.Http.AutoClient.BodyAttribute.BodyAttribute(Microsoft.Extensions.Http.AutoClient.BodyContentType contentType);", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.BodyContentType Microsoft.Extensions.Http.AutoClient.BodyAttribute.ContentType { get; }", - "Stage": "Stable" - } - ] - }, - { - "Type": "enum Microsoft.Extensions.Http.AutoClient.BodyContentType", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.BodyContentType.BodyContentType();", - "Stage": "Stable" - } - ], - "Fields": [ - { - "Member": "const Microsoft.Extensions.Http.AutoClient.BodyContentType Microsoft.Extensions.Http.AutoClient.BodyContentType.ApplicationJson", - "Stage": "Stable", - "Value": "0" - }, - { - "Member": "const Microsoft.Extensions.Http.AutoClient.BodyContentType Microsoft.Extensions.Http.AutoClient.BodyContentType.TextPlain", - "Stage": "Stable", - "Value": "1" - } - ] - }, - { - "Type": "sealed class Microsoft.Extensions.Http.AutoClient.DeleteAttribute : System.Attribute", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.DeleteAttribute.DeleteAttribute(string path);", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "string Microsoft.Extensions.Http.AutoClient.DeleteAttribute.Path { get; }", - "Stage": "Stable" - }, - { - "Member": "string? Microsoft.Extensions.Http.AutoClient.DeleteAttribute.RequestName { get; set; }", - "Stage": "Stable" - } - ] - }, - { - "Type": "sealed class Microsoft.Extensions.Http.AutoClient.GetAttribute : System.Attribute", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.GetAttribute.GetAttribute(string path);", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "string Microsoft.Extensions.Http.AutoClient.GetAttribute.Path { get; }", - "Stage": "Stable" - }, - { - "Member": "string? Microsoft.Extensions.Http.AutoClient.GetAttribute.RequestName { get; set; }", - "Stage": "Stable" - } - ] - }, - { - "Type": "sealed class Microsoft.Extensions.Http.AutoClient.HeadAttribute : System.Attribute", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.HeadAttribute.HeadAttribute(string path);", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "string Microsoft.Extensions.Http.AutoClient.HeadAttribute.Path { get; }", - "Stage": "Stable" - }, - { - "Member": "string? Microsoft.Extensions.Http.AutoClient.HeadAttribute.RequestName { get; set; }", - "Stage": "Stable" - } - ] - }, - { - "Type": "sealed class Microsoft.Extensions.Http.AutoClient.HeaderAttribute : System.Attribute", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.HeaderAttribute.HeaderAttribute(string header);", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "string Microsoft.Extensions.Http.AutoClient.HeaderAttribute.Header { get; }", - "Stage": "Stable" - } - ] - }, - { - "Type": "sealed class Microsoft.Extensions.Http.AutoClient.OptionsAttribute : System.Attribute", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.OptionsAttribute.OptionsAttribute(string path);", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "string Microsoft.Extensions.Http.AutoClient.OptionsAttribute.Path { get; }", - "Stage": "Stable" - }, - { - "Member": "string? Microsoft.Extensions.Http.AutoClient.OptionsAttribute.RequestName { get; set; }", - "Stage": "Stable" - } - ] - }, - { - "Type": "sealed class Microsoft.Extensions.Http.AutoClient.PatchAttribute : System.Attribute", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.PatchAttribute.PatchAttribute(string path);", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "string Microsoft.Extensions.Http.AutoClient.PatchAttribute.Path { get; }", - "Stage": "Stable" - }, - { - "Member": "string? Microsoft.Extensions.Http.AutoClient.PatchAttribute.RequestName { get; set; }", - "Stage": "Stable" - } - ] - }, - { - "Type": "sealed class Microsoft.Extensions.Http.AutoClient.PostAttribute : System.Attribute", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.PostAttribute.PostAttribute(string path);", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "string Microsoft.Extensions.Http.AutoClient.PostAttribute.Path { get; }", - "Stage": "Stable" - }, - { - "Member": "string? Microsoft.Extensions.Http.AutoClient.PostAttribute.RequestName { get; set; }", - "Stage": "Stable" - } - ] - }, - { - "Type": "sealed class Microsoft.Extensions.Http.AutoClient.PutAttribute : System.Attribute", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.PutAttribute.PutAttribute(string path);", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "string Microsoft.Extensions.Http.AutoClient.PutAttribute.Path { get; }", - "Stage": "Stable" - }, - { - "Member": "string? Microsoft.Extensions.Http.AutoClient.PutAttribute.RequestName { get; set; }", - "Stage": "Stable" - } - ] - }, - { - "Type": "sealed class Microsoft.Extensions.Http.AutoClient.QueryAttribute : System.Attribute", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.QueryAttribute.QueryAttribute();", - "Stage": "Stable" - }, - { - "Member": "Microsoft.Extensions.Http.AutoClient.QueryAttribute.QueryAttribute(string key);", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "string? Microsoft.Extensions.Http.AutoClient.QueryAttribute.Key { get; }", - "Stage": "Stable" - } - ] - }, - { - "Type": "sealed class Microsoft.Extensions.Http.AutoClient.StaticHeaderAttribute : System.Attribute", - "Stage": "Stable", - "Methods": [ - { - "Member": "Microsoft.Extensions.Http.AutoClient.StaticHeaderAttribute.StaticHeaderAttribute(string header, string value);", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "string Microsoft.Extensions.Http.AutoClient.StaticHeaderAttribute.Header { get; }", - "Stage": "Stable" - }, - { - "Member": "string Microsoft.Extensions.Http.AutoClient.StaticHeaderAttribute.Value { get; }", - "Stage": "Stable" - } - ] - } - ] -} \ No newline at end of file diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/OptionsAttribute.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/OptionsAttribute.cs deleted file mode 100644 index 079a823c4ba..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/OptionsAttribute.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Net.Http; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Defines an API OPTIONS method. -/// -/// -/// Marks a method within an interface annotated with as an API OPTIONS method. -/// -/// The return type of an API method must be a Task<T>. -/// If T is a and the dependency returns "text/plain" content type, the result will be the raw content of the response. Otherwise, it will be deserialized from JSON. -/// If T is of type , the result will be the actual response message without further processing. -/// -/// If you provide an extra parameter to the method, you should use it between curly braces in the URL to make it an URL parameter. For example: /api/users/{userId}. -/// -/// -/// -/// [AutoClient("MyClient")] -/// interface IMyDependencyClient -/// { -/// [Options("/api/users/{userId}")] -/// Task<UserOptions> UserOptionsAsync(string userId, CancellationToken cancellationToken); -/// } -/// -/// -[AttributeUsage(AttributeTargets.Method)] -public sealed class OptionsAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The path of the request. Cannot be empty or null. - public OptionsAttribute(string path) - { - Path = path; - } - - /// - /// Gets the path of the request. - /// - public string Path { get; } - - /// - /// Gets or sets the name to use for this request within telemetry. - /// - /// - /// If this property is not provided, the request name is obtained from the method name. - /// If the method name ends in 'Async', the request name will exclude that. - /// For example, if the method is called UserOptionsAsync, the request name, by default, will be UserOptions. - /// - /// - /// - /// [AutoClient("MyClient")] - /// interface IMyDependencyClient - /// { - /// [Options("/api/users/{userId}", RequestName = "Users")] - /// Task<UserOptions> UserOptionsAsync(string userId, CancellationToken cancellationToken); - /// } - /// - /// - public string? RequestName { get; set; } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/PatchAttribute.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/PatchAttribute.cs deleted file mode 100644 index 08eb10ba8d0..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/PatchAttribute.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Net.Http; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Defines an API PATCH method. -/// -/// -/// Marks a method within an interface annotated with as an API PATCH method. -/// -/// The return type of an API method must be a Task<T>. -/// If T is a and the dependency returns "text/plain" content type, the result will be the raw content of the response. Otherwise, it will be deserialized from JSON. -/// If T is of type , the result will be the actual response message without further processing. -/// -/// If you provide an extra parameter to the method, you should use it between curly braces in the URL to make it an URL parameter. For example: /api/users/{userId}. -/// -/// -/// -/// [AutoClient("MyClient")] -/// interface IMyDependencyClient -/// { -/// [Patch("/api/users/{userId}")] -/// Task<User> UpdateUserAsync(string userId, CancellationToken cancellationToken); -/// } -/// -/// -[AttributeUsage(AttributeTargets.Method)] -public sealed class PatchAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The path of the request. Cannot be empty or null. - public PatchAttribute(string path) - { - Path = path; - } - - /// - /// Gets the path of the request. - /// - public string Path { get; } - - /// - /// Gets or sets the name to use for this request within telemetry. - /// - /// - /// If this property is not provided, the request name is obtained from the method name. - /// If the method name ends in 'Async', the request name will exclude that. - /// For example, if the method is called UpdateUserAsync, the request name, by default, will be UpdateUser. - /// - /// - /// - /// [AutoClient("MyClient")] - /// interface IMyDependencyClient - /// { - /// [Patch("/api/users/{userId}", RequestName = "PatchUser")] - /// Task<User> UpdateUserAsync(string userId, CancellationToken cancellationToken); - /// } - /// - /// - public string? RequestName { get; set; } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/PostAttribute.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/PostAttribute.cs deleted file mode 100644 index eb4d529472d..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/PostAttribute.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Net.Http; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Defines an API POST method. -/// -/// -/// Marks a method within an interface annotated with as an API POST method. -/// -/// The return type of an API method must be a Task<T>. -/// If T is a and the dependency returns "text/plain" content type, the result will be the raw content of the response. Otherwise, it will be deserialized from JSON. -/// If T is of type , the result will be the actual response message without further processing. -/// -/// If you provide an extra parameter to the method, you should use it between curly braces in the URL to make it an URL parameter. For example: /api/users/{userId}. -/// -/// -/// -/// [AutoClient("MyClient")] -/// interface IMyDependencyClient -/// { -/// [Post("/api/users/{userId}")] -/// Task<User> AddUserAsync(string userId, CancellationToken cancellationToken); -/// } -/// -/// -[AttributeUsage(AttributeTargets.Method)] -public sealed class PostAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The path of the request. Cannot be empty or null. - public PostAttribute(string path) - { - Path = path; - } - - /// - /// Gets the path of the request. - /// - public string Path { get; } - - /// - /// Gets or sets the name to use for this request within telemetry. - /// - /// - /// If this property is not provided, the request name is obtained from the method name. - /// If the method name ends in 'Async', the request name will exclude that. - /// For example, if the method is called AddUserAsync, the request name, by default, will be AddUser. - /// - /// - /// - /// [AutoClient("MyClient")] - /// interface IMyDependencyClient - /// { - /// [Post("/api/users/{userId}", RequestName = "AddUser")] - /// Task<User> AddUserAsync(string userId, CancellationToken cancellationToken); - /// } - /// - /// - public string? RequestName { get; set; } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/PutAttribute.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/PutAttribute.cs deleted file mode 100644 index 03bad7896fd..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/PutAttribute.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Net.Http; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Defines an API PUT method. -/// -/// -/// Marks a method within an interface annotated with as an API PUT method. -/// -/// The return type of an API method must be a Task<T>. -/// If T is a and the dependency returns "text/plain" content type, the result will be the raw content of the response. Otherwise, it will be deserialized from JSON. -/// If T is of type , the result will be the actual response message without further processing. -/// -/// If you provide an extra parameter to the method, you should use it between curly braces in the URL to make it an URL parameter. For example: /api/users/{userId}. -/// -/// -/// -/// [AutoClient("MyClient")] -/// interface IMyDependencyClient -/// { -/// [Put("/api/users/{userId}")] -/// Task<User> InsertUserAsync(string userId, CancellationToken cancellationToken); -/// } -/// -/// -[AttributeUsage(AttributeTargets.Method)] -public sealed class PutAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The path of the request. Cannot be empty or null. - public PutAttribute(string path) - { - Path = path; - } - - /// - /// Gets the path of the request. - /// - public string Path { get; } - - /// - /// Gets or sets the name to use for this request within telemetry. - /// - /// - /// If this property is not provided, the request name is obtained from the method name. - /// If the method name ends in 'Async', the request name will exclude that. - /// For example, if the method is called InsertUserAsync, the request name, by default, will be InsertUser. - /// - /// - /// - /// [AutoClient("MyClient")] - /// interface IMyDependencyClient - /// { - /// [Put("/api/users/{userId}", RequestName = "InsertUser")] - /// Task<User> InsertUserAsync(string userId, CancellationToken cancellationToken); - /// } - /// - /// - public string? RequestName { get; set; } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/QueryAttribute.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/QueryAttribute.cs deleted file mode 100644 index b6d3c935c4b..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/QueryAttribute.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Defines a query string to be used in the API request. -/// -/// -/// Marks a method parameter as a query string for the request. -/// -/// -/// -/// [AutoClient("MyClient")] -/// interface IMyDependencyClient -/// { -/// [Get("/api/users")] -/// Task<string> GetUsersAsync([Query] string userName, [Query("id")] string userId, CancellationToken cancellationToken = default); -/// } -/// -/// -[AttributeUsage(AttributeTargets.Parameter)] -[Conditional("CODE_GENERATION_ATTRIBUTES")] -public sealed class QueryAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// - /// This overload uses the name of the associated method parameter as the query string key. - /// - public QueryAttribute() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The query key to use in the request. - public QueryAttribute(string key) - { - Key = key; - } - - /// - /// Gets the query key, if set. - /// - public string? Key { get; } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/README.md b/src/Libraries/Microsoft.Extensions.Http.AutoClient/README.md deleted file mode 100644 index e845566c06f..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/README.md +++ /dev/null @@ -1 +0,0 @@ -README diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/StaticHeaderAttribute.cs b/src/Libraries/Microsoft.Extensions.Http.AutoClient/StaticHeaderAttribute.cs deleted file mode 100644 index 657dfd1bbfe..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/StaticHeaderAttribute.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics; - -namespace Microsoft.Extensions.Http.AutoClient; - -/// -/// Defines a static header to be sent on every API request. -/// -/// -/// Injects a static header to be sent with every request. When this attribute is applied -/// to an interface, then it impacts every method described by the interface. Otherwise, it only -/// affects the method where it is applied. -/// The header name must not be null or empty. The value, on the other hand, can be empty, but not null. -/// -/// -/// -/// [AutoClient("MyClient")] -/// [StaticHeader("X-MyHeader", "MyHeaderValue")] -/// interface IMyDependencyClient -/// { -/// [Get("/api/users")] -/// [StaticHeader("X-GetUsersHeader", "Value")] -/// public Task<Users> GetUsers(CancellationToken cancellationToken = default); -/// } -/// -/// -[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method, AllowMultiple = true)] -[Conditional("CODE_GENERATION_ATTRIBUTES")] -public sealed class StaticHeaderAttribute : Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// The name of the header. Cannot be empty or null. - /// The value of the header. Cannot be null. - public StaticHeaderAttribute(string header, string value) - { - Header = header; - Value = value; - } - - /// - /// Gets the name of the header. - /// - public string Header { get; } - - /// - /// Gets the value of the header. - /// - public string Value { get; } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/buildTransitive/Microsoft.Extensions.Http.AutoClient.props b/src/Libraries/Microsoft.Extensions.Http.AutoClient/buildTransitive/Microsoft.Extensions.Http.AutoClient.props deleted file mode 100644 index 7bc91e44385..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/buildTransitive/Microsoft.Extensions.Http.AutoClient.props +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/Libraries/Microsoft.Extensions.Http.AutoClient/buildTransitive/Microsoft.Extensions.Http.AutoClient.targets b/src/Libraries/Microsoft.Extensions.Http.AutoClient/buildTransitive/Microsoft.Extensions.Http.AutoClient.targets deleted file mode 100644 index 8c119d5413b..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.AutoClient/buildTransitive/Microsoft.Extensions.Http.AutoClient.targets +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/DownstreamDependencyMetadataManager.cs b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/DownstreamDependencyMetadataManager.cs index dd5f6c1724e..fbe54413cbf 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/DownstreamDependencyMetadataManager.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/DownstreamDependencyMetadataManager.cs @@ -100,24 +100,27 @@ private static void AddRouteToTrie(RequestMetadata routeMetadata, Dictionary requestRouteAsSpan; + ReadOnlySpan requestRouteAsSpan = routeMetadata.RequestRoute.AsSpan(); - if (routeMetadata.RequestRoute[0] != '/') + if (requestRouteAsSpan.Length > 0) { - requestRouteAsSpan = $"/{routeMetadata.RequestRoute}".AsSpan(); - } - else if (routeMetadata.RequestRoute.StartsWith("//", StringComparison.OrdinalIgnoreCase)) - { - requestRouteAsSpan = routeMetadata.RequestRoute.AsSpan(1); + if (requestRouteAsSpan[0] != '/') + { + requestRouteAsSpan = $"/{routeMetadata.RequestRoute}".AsSpan(); + } + else if (requestRouteAsSpan.StartsWith("//".AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + requestRouteAsSpan = requestRouteAsSpan.Slice(1); + } + + if (requestRouteAsSpan.Length > 1 && requestRouteAsSpan[requestRouteAsSpan.Length - 1] == '/') + { + requestRouteAsSpan = requestRouteAsSpan.Slice(0, requestRouteAsSpan.Length - 1); + } } else { - requestRouteAsSpan = routeMetadata.RequestRoute.AsSpan(); - } - - if (requestRouteAsSpan[requestRouteAsSpan.Length - 1] == '/') - { - requestRouteAsSpan = requestRouteAsSpan.Slice(0, requestRouteAsSpan.Length - 1); + requestRouteAsSpan = "/".AsSpan(); } var route = _routeRegex.Replace(requestRouteAsSpan.ToString(), "*"); @@ -349,19 +352,19 @@ private void AddHostnameToTrie(string hostNameSuffix, string dependencyName) return hostMetadata.RequestMetadata; } - ReadOnlySpan requestRouteAsSpan; - if (requestPath[requestPath.Length - 1] == '/') - { - requestRouteAsSpan = requestPath.AsSpan(0, requestPath.Length - 1); - } - else - { - requestRouteAsSpan = requestPath.AsSpan(); - } + ReadOnlySpan requestRouteAsSpan = requestPath.AsSpan(); - if (requestPath.StartsWith("//", StringComparison.OrdinalIgnoreCase)) + if (requestRouteAsSpan.Length > 1) { - requestRouteAsSpan = requestRouteAsSpan.Slice(1); + if (requestRouteAsSpan[requestRouteAsSpan.Length - 1] == '/') + { + requestRouteAsSpan = requestRouteAsSpan.Slice(0, requestRouteAsSpan.Length - 1); + } + + if (requestRouteAsSpan.StartsWith("//".AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + requestRouteAsSpan = requestRouteAsSpan.Slice(1); + } } var trieCurrent = routeMetadataTrieRoot.Nodes[0]; diff --git a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Latency/Internal/HttpClientLatencyLogEnricher.cs b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Latency/Internal/HttpClientLatencyLogEnricher.cs index 12cc3d98149..bad9b23a415 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Latency/Internal/HttpClientLatencyLogEnricher.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Latency/Internal/HttpClientLatencyLogEnricher.cs @@ -50,7 +50,7 @@ public void Enrich(IEnrichmentTagCollector collector, HttpRequestMessage request AppendCheckpoints(lc, stringBuilder); } - collector.Add("latencyInfo", stringBuilder.ToString()); + collector.Add("LatencyInfo", stringBuilder.ToString()); _builderPool.Return(stringBuilder); } diff --git a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/HttpClientLoggingTagNames.cs b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/HttpClientLoggingTagNames.cs index 134db3f16e6..803eb03d082 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/HttpClientLoggingTagNames.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/HttpClientLoggingTagNames.cs @@ -14,47 +14,47 @@ public static class HttpClientLoggingTagNames /// /// HTTP Request duration. /// - public const string Duration = "duration"; + public const string Duration = "Duration"; /// /// HTTP Host. /// - public const string Host = "httpHost"; + public const string Host = "server.address"; /// /// HTTP Method. /// - public const string Method = "httpMethod"; + public const string Method = "http.request.method"; /// /// HTTP Path. /// - public const string Path = "httpPath"; + public const string Path = "url.path"; /// /// HTTP Request Body. /// - public const string RequestBody = "httpRequestBody"; + public const string RequestBody = "RequestBody"; /// /// HTTP Response Body. /// - public const string ResponseBody = "httpResponseBody"; + public const string ResponseBody = "ResponseBody"; /// /// HTTP Request Headers prefix. /// - public const string RequestHeaderPrefix = "httpRequestHeader_"; + public const string RequestHeaderPrefix = "http.request.header."; /// /// HTTP Response Headers prefix. /// - public const string ResponseHeaderPrefix = "httpResponseHeader_"; + public const string ResponseHeaderPrefix = "http.response.header."; /// /// HTTP Status Code. /// - public const string StatusCode = "httpStatusCode"; + public const string StatusCode = "http.response.status_code"; /// /// Gets a list of all tag names. diff --git a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/Internal/Log.cs b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/Internal/Log.cs index eda39c9833b..f7e6896b14b 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/Internal/Log.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/Internal/Log.cs @@ -12,10 +12,10 @@ namespace Microsoft.Extensions.Http.Logging.Internal; /// Logs , and the exceptions due to errors of request/response. /// [SuppressMessage("Major Code Smell", "S109:Magic numbers should not be used", Justification = "Event ID's.")] -internal static partial class Log +internal static class Log { internal const string OriginalFormat = "{OriginalFormat}"; - internal const string OriginalFormatValue = "{httpMethod} {httpHost}/{httpPath}"; + private const string NullString = "(null)"; private const int MinimalPropertyCount = 4; @@ -28,10 +28,10 @@ internal static partial class Log $"{{{HttpClientLoggingTagNames.Method}}} {{{HttpClientLoggingTagNames.Host}}}/{{{HttpClientLoggingTagNames.Path}}}"; private const string LoggerContextMissingMessage = - $"The logger couldn't read its context for {{requestState}} request: {{{HttpClientLoggingTagNames.Method}}} {{{HttpClientLoggingTagNames.Host}}}"; + $"The logger couldn't read its context for {{RequestState}} request: {{{HttpClientLoggingTagNames.Method}}} {{{HttpClientLoggingTagNames.Host}}}"; private const string EnrichmentErrorMessage = - "An error occurred in enricher '{enricherType}' while enriching the logger context for request: " + + "An error occurred in enricher '{Enricher}' while enriching the logger context for request: " + $"{{{HttpClientLoggingTagNames.Method}}} {{{HttpClientLoggingTagNames.Host}}}/{{{HttpClientLoggingTagNames.Path}}}"; private static readonly Func _originalFormatValueFMTFunc = OriginalFormatValueFMT; @@ -46,17 +46,114 @@ public static void OutgoingRequestError(ILogger logger, LogRecord record, Except OutgoingRequest(logger, LogLevel.Error, 2, nameof(OutgoingRequestError), record, exception); } - [LoggerMessage(LogLevel.Error, RequestReadErrorMessage)] - public static partial void RequestReadError(ILogger logger, Exception exception, HttpMethod httpMethod, string? httpHost, string? httpPath); + public static void RequestReadError(ILogger logger, Exception exception, HttpMethod method, string? host, string? path) + { + var state = LoggerMessageHelper.ThreadLocalState; + + _ = state.ReserveTagSpace(4); + state.TagArray[3] = new(HttpClientLoggingTagNames.Method, method); + state.TagArray[2] = new(HttpClientLoggingTagNames.Host, host); + state.TagArray[1] = new(HttpClientLoggingTagNames.Path, path); + state.TagArray[0] = new(OriginalFormat, RequestReadErrorMessage); + + logger.Log( + LogLevel.Error, + new(0, nameof(RequestReadError)), + state, + exception, + static (s, _) => + { + var method = s.TagArray[3].Value ?? NullString; + var host = s.TagArray[2].Value ?? NullString; + var path = s.TagArray[1].Value ?? NullString; + return FormattableString.Invariant( + $"An error occurred while reading the request data to fill the logger context for request: {method} {host}/{path}"); + }); + + state.Clear(); + } - [LoggerMessage(LogLevel.Error, ResponseReadErrorMessage)] - public static partial void ResponseReadError(ILogger logger, Exception exception, HttpMethod httpMethod, string httpHost, string httpPath); + public static void ResponseReadError(ILogger logger, Exception exception, HttpMethod method, string host, string path) + { + var state = LoggerMessageHelper.ThreadLocalState; - [LoggerMessage(LogLevel.Error, LoggerContextMissingMessage)] - public static partial void LoggerContextMissing(ILogger logger, Exception? exception, string requestState, HttpMethod httpMethod, string? httpHost); + _ = state.ReserveTagSpace(4); + state.TagArray[3] = new(HttpClientLoggingTagNames.Method, method); + state.TagArray[2] = new(HttpClientLoggingTagNames.Host, host); + state.TagArray[1] = new(HttpClientLoggingTagNames.Path, path); + state.TagArray[0] = new(OriginalFormat, ResponseReadErrorMessage); + + logger.Log( + LogLevel.Error, + new(0, nameof(ResponseReadError)), + state, + exception, + static (s, _) => + { + var method = s.TagArray[3].Value ?? NullString; + var host = s.TagArray[2].Value ?? NullString; + var path = s.TagArray[1].Value ?? NullString; + return FormattableString.Invariant( + $"An error occurred while reading the response data to fill the logger context for request: {method} {host}/{path}"); + }); + + state.Clear(); + } + + public static void LoggerContextMissing(ILogger logger, Exception? exception, string requestState, HttpMethod method, string? host) + { + var state = LoggerMessageHelper.ThreadLocalState; - [LoggerMessage(LogLevel.Error, EnrichmentErrorMessage)] - public static partial void EnrichmentError(ILogger logger, Exception exception, string? enricherType, HttpMethod httpMethod, string httpHost, string httpPath); + _ = state.ReserveTagSpace(4); + state.TagArray[3] = new("RequestState", requestState); + state.TagArray[2] = new(HttpClientLoggingTagNames.Method, method); + state.TagArray[1] = new(HttpClientLoggingTagNames.Host, host); + state.TagArray[0] = new(OriginalFormat, LoggerContextMissingMessage); + + logger.Log( + LogLevel.Error, + new(0, nameof(LoggerContextMissing)), + state, + exception, + (s, _) => + { + var requestState = s.TagArray[3].Value ?? NullString; + var method = s.TagArray[2].Value ?? NullString; + var host = s.TagArray[1].Value ?? NullString; + return FormattableString.Invariant($"The logger couldn't read its context for {requestState} request: {method} {host}"); + }); + + state.Clear(); + } + + public static void EnrichmentError(ILogger logger, Exception exception, string? enricher, HttpMethod method, string host, string path) + { + var state = LoggerMessageHelper.ThreadLocalState; + + _ = state.ReserveTagSpace(5); + state.TagArray[4] = new("Enricher", enricher); + state.TagArray[3] = new(HttpClientLoggingTagNames.Method, method); + state.TagArray[2] = new(HttpClientLoggingTagNames.Host, host); + state.TagArray[1] = new(HttpClientLoggingTagNames.Path, path); + state.TagArray[0] = new(OriginalFormat, EnrichmentErrorMessage); + + logger.Log( + LogLevel.Error, + new(0, nameof(EnrichmentError)), + state, + exception, + (s, _) => + { + var enricher = s.TagArray[4].Value ?? NullString; + var method = s.TagArray[3].Value ?? NullString; + var host = s.TagArray[2].Value ?? NullString; + var path = s.TagArray[1].Value ?? NullString; + return FormattableString.Invariant( + $"An error occurred in enricher '{enricher}' while enriching the logger context for request: {method} {host}/{path}"); + }); + + state.Clear(); + } // Using the code below instead of generated logging method because we have a custom formatter and custom tag keys for headers. private static void OutgoingRequest( diff --git a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/Internal/LoggerMessageStateExtensions.cs b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/Internal/LoggerMessageStateExtensions.cs index 744152fec0f..fa9912be06e 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/Internal/LoggerMessageStateExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/Internal/LoggerMessageStateExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; namespace Microsoft.Extensions.Http.Logging.Internal; @@ -25,7 +26,7 @@ public static void AddRequestHeaders(this LoggerMessageState state, List p + x, + static (x, p) => p + Normalize(x), HttpClientLoggingTagNames.RequestHeaderPrefix); state.TagArray[index++] = new(key, items[i].Value); @@ -45,10 +46,17 @@ public static void AddResponseHeaders(this LoggerMessageState state, List p + x, + static (x, p) => p + Normalize(x), HttpClientLoggingTagNames.ResponseHeaderPrefix); state.TagArray[index++] = new(key, items[i].Value); } } + + [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", + Justification = "Normalization to lower case is required by OTel's semantic conventions")] + private static string Normalize(string header) + { + return header.ToLowerInvariant(); + } } diff --git a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/LoggingOptions.cs b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/LoggingOptions.cs index 7637ae6737c..4359f22fb5d 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/LoggingOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/LoggingOptions.cs @@ -18,7 +18,7 @@ public class LoggingOptions { private const int MaxIncomingBodySize = 1_572_864; // 1.5 MB private const int Millisecond = 1; - private const int Hour = 60000 * 60; // 1 hour + private const int Minute = 60000; private const int DefaultReadSizeLimit = 32 * 1024; // ≈ 32K private const OutgoingPathLoggingMode DefaultPathLoggingMode = OutgoingPathLoggingMode.Formatted; private const HttpRouteParameterRedactionMode DefaultPathParameterRedactionMode = HttpRouteParameterRedactionMode.Strict; @@ -67,7 +67,7 @@ public class LoggingOptions /// /// The value should be in the range of 1 millisecond to 1 minute. /// - [TimeSpan(Millisecond, Hour)] + [TimeSpan(Millisecond, Minute)] public TimeSpan BodyReadTimeout { get; set; } = TimeSpan.FromSeconds(1); /// @@ -87,7 +87,7 @@ public class LoggingOptions public ISet ResponseBodyContentTypes { get; set; } = new HashSet(); /// - /// Gets or sets the set of HTTP request headers to log and their respective data classes to use for redaction. + /// Gets or sets the set of HTTP request headers to log and their respective data classifications to use for redaction. /// /// /// The default value is . @@ -102,7 +102,7 @@ public class LoggingOptions public IDictionary RequestHeadersDataClasses { get; set; } = new Dictionary(); /// - /// Gets or sets the set of HTTP response headers to log and their respective data classes to use for redaction. + /// Gets or sets the set of HTTP response headers to log and their respective data classifications to use for redaction. /// /// /// The default value is . @@ -137,7 +137,7 @@ public class LoggingOptions public HttpRouteParameterRedactionMode RequestPathParameterRedactionMode { get; set; } = DefaultPathParameterRedactionMode; /// - /// Gets or sets the route parameters to redact with their corresponding data classes to apply appropriate redaction. + /// Gets or sets the route parameters to redact with their corresponding data classifications to apply appropriate redaction. /// [Required] [SuppressMessage("Usage", "CA2227:Collection properties should be read only", diff --git a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Microsoft.Extensions.Http.Diagnostics.csproj b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Microsoft.Extensions.Http.Diagnostics.csproj index a74f7ef6f10..b85b90b4c6f 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Microsoft.Extensions.Http.Diagnostics.csproj +++ b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Microsoft.Extensions.Http.Diagnostics.csproj @@ -9,7 +9,6 @@ $(NoWarn);LA0006 - true true true false @@ -27,14 +26,13 @@ normal - 94 + 91 78 - - - + + diff --git a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Microsoft.Extensions.Http.Diagnostics.json b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Microsoft.Extensions.Http.Diagnostics.json index 66b635f00b2..eabd53cbf75 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Microsoft.Extensions.Http.Diagnostics.json +++ b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Microsoft.Extensions.Http.Diagnostics.json @@ -82,47 +82,47 @@ { "Member": "const string Microsoft.Extensions.Http.Logging.HttpClientLoggingTagNames.Duration", "Stage": "Stable", - "Value": "duration" + "Value": "Duration" }, { "Member": "const string Microsoft.Extensions.Http.Logging.HttpClientLoggingTagNames.Host", "Stage": "Stable", - "Value": "httpHost" + "Value": "server.address" }, { "Member": "const string Microsoft.Extensions.Http.Logging.HttpClientLoggingTagNames.Method", "Stage": "Stable", - "Value": "httpMethod" + "Value": "http.request.method" }, { "Member": "const string Microsoft.Extensions.Http.Logging.HttpClientLoggingTagNames.Path", "Stage": "Stable", - "Value": "httpPath" + "Value": "url.path" }, { "Member": "const string Microsoft.Extensions.Http.Logging.HttpClientLoggingTagNames.RequestBody", "Stage": "Stable", - "Value": "httpRequestBody" + "Value": "RequestBody" }, { "Member": "const string Microsoft.Extensions.Http.Logging.HttpClientLoggingTagNames.RequestHeaderPrefix", "Stage": "Stable", - "Value": "httpRequestHeader_" + "Value": "http.request.header." }, { "Member": "const string Microsoft.Extensions.Http.Logging.HttpClientLoggingTagNames.ResponseBody", "Stage": "Stable", - "Value": "httpResponseBody" + "Value": "ResponseBody" }, { "Member": "const string Microsoft.Extensions.Http.Logging.HttpClientLoggingTagNames.ResponseHeaderPrefix", "Stage": "Stable", - "Value": "httpResponseHeader_" + "Value": "http.response.header." }, { "Member": "const string Microsoft.Extensions.Http.Logging.HttpClientLoggingTagNames.StatusCode", "Stage": "Stable", - "Value": "httpStatusCode" + "Value": "http.response.status_code" } ], "Properties": [ diff --git a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/README.md b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/README.md index e845566c06f..309112b3052 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/README.md +++ b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/README.md @@ -1 +1,120 @@ -README +# Microsoft.Extensions.Http.Diagnostics + +Telemetry support for `HttpClient` that allows tracking latency and enriching and redacting log output. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.Http.Diagnostics +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Example + +### HTTP Client Logs Enrichment and Redaction + +These components enable enriching and redacting `HttpClient` request logs. They remove built-it HTTP Client logging. + +In order to use the redaction feature, you need to reference the `Microsoft.Extensions.Compliance.Redaction` package. + +The services can be registered using the following methods: + +```csharp +public static IServiceCollection AddExtendedHttpClientLogging(this IServiceCollection services); +public static IServiceCollection AddExtendedHttpClientLogging(this IServiceCollection services, IConfigurationSection section); +public static IServiceCollection AddExtendedHttpClientLogging(this IServiceCollection services, Action configure); +public static IServiceCollection AddHttpClientLogEnricher(this IServiceCollection services) where T : class, IHttpClientLogEnricher; +``` + +For example: + +```csharp +var builder = Host.CreateApplicationBuilder(args); + +// Register IHttpClientFactory: +builder.Services.AddHttpClient(); + +// Register redaction services: +builder.Services.AddRedaction(); + +// Register HttpClient logging enrichment & redaction services: +builder.Services.AddExtendedHttpClientLogging(); + +// Register a logging enricher (the type should implement IHttpClientLogEnricher): +builder.Services.AddHttpClientLogEnricher(); + +var host = builder.Build(); +``` + +You can also use the following extension methods to apply the logging to the specific `IHttpClientBuilder`: + +```csharp +public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBuilder builder); +public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBuilder builder, IConfigurationSection section); +public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBuilder builder, Action configure); +``` + +For example: + +```csharp +var builder = Host.CreateApplicationBuilder(args); + +// Register redaction services: +builder.Services.AddRedaction(); + +// Register named HttpClient: +var httpClientBuilder = builder.Services.AddHttpClient("MyNamedClient"); + +// Configure named HttpClient to use logging enrichment & redaction: +httpClientBuilder.AddExtendedHttpClientLogging(); + +var host = builder.Build(); +``` + +### Tracking HTTP Request Client Latency + +These components enable tracking and reporting the latency of HTTP Client request processing. + +The services can be registered using the following methods: + +```csharp +public static IServiceCollection AddHttpClientLatencyTelemetry(this IServiceCollection services); +public static IServiceCollection AddHttpClientLatencyTelemetry(this IServiceCollection services, IConfigurationSection section); +public static IServiceCollection AddHttpClientLatencyTelemetry(this IServiceCollection services, Action configure); +``` + +For example: + +```csharp +var builder = Host.CreateApplicationBuilder(args); + +// Register IHttpClientFactory: +builder.Services.AddHttpClient(); + +// Register redaction services: +builder.Services.AddRedaction(); + +// Register latency context services: +builder.Services.AddLatencyContext(); + +// Register HttpClient logging enrichment & redaction services: +builder.Services.AddExtendedHttpClientLogging(); + +// Register HttpClient latency telemetry services: +builder.Services.AddHttpClientLatencyTelemetry(); + +var host = builder.Build(); +``` + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpStandardHedgingResilienceOptions.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpStandardHedgingResilienceOptions.cs index e0c9c2f1afb..74a00aafc82 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpStandardHedgingResilienceOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpStandardHedgingResilienceOptions.cs @@ -22,7 +22,7 @@ namespace Microsoft.Extensions.Http.Resilience; /// Total request timeout strategy applies an overall timeout to the execution, /// ensuring that the request including hedging attempts does not exceed the configured limit. /// The hedging strategy executes the requests against multiple endpoints in case the dependency is slow or returns a transient error. -/// The bulkhead policy limits the maximum number of concurrent requests being send to the dependency. +/// The rate limiter pipeline limits the maximum number of requests being send to the dependency. /// The circuit breaker blocks the execution if too many direct failures or timeouts are detected. /// The attempt timeout strategy limits each request attempt duration and throws if its exceeded. /// diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/ResilienceHttpClientBuilderExtensions.Hedging.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/ResilienceHttpClientBuilderExtensions.Hedging.cs index e951d36745f..33f496b4c74 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/ResilienceHttpClientBuilderExtensions.Hedging.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/ResilienceHttpClientBuilderExtensions.Hedging.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Net.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Http.Resilience; @@ -88,6 +89,9 @@ public static IStandardHedgingHandlerBuilder AddStandardHedgingHandler(this IHtt var requestMessage = snapshot.CreateRequestMessage(); + // The secondary request message should use the action resilience context + requestMessage.SetResilienceContext(args.ActionContext); + // replace the request message args.ActionContext.Properties.Set(ResilienceKeys.RequestMessage, requestMessage); diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Microsoft.Extensions.Http.Resilience.csproj b/src/Libraries/Microsoft.Extensions.Http.Resilience/Microsoft.Extensions.Http.Resilience.csproj index d6df301d96b..26ad109d592 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Microsoft.Extensions.Http.Resilience.csproj +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Microsoft.Extensions.Http.Resilience.csproj @@ -1,14 +1,13 @@  Microsoft.Extensions.Http.Resilience - Resilience mechanisms for HTTP Client. + Resilience mechanisms for HttpClient. Resilience true true - true true true true @@ -20,7 +19,7 @@ normal - 98 + 97 100 diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Microsoft.Extensions.Http.Resilience.json b/src/Libraries/Microsoft.Extensions.Http.Resilience/Microsoft.Extensions.Http.Resilience.json index 97da4c129a9..4b192650a52 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Microsoft.Extensions.Http.Resilience.json +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Microsoft.Extensions.Http.Resilience.json @@ -296,7 +296,7 @@ "Stage": "Stable" }, { - "Member": "TOptions Microsoft.Extensions.Http.Resilience.ResilienceHandlerContext.GetOptions(string name);", + "Member": "TOptions Microsoft.Extensions.Http.Resilience.ResilienceHandlerContext.GetOptions(string? name = null);", "Stage": "Stable" }, { @@ -520,4 +520,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/README.md b/src/Libraries/Microsoft.Extensions.Http.Resilience/README.md index e845566c06f..676ffc34118 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/README.md +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/README.md @@ -1 +1,78 @@ -README +# Microsoft.Extensions.Http.Resilience + +Resilience mechanisms for `HttpClient` built on the [Polly framework](https://www.pollydocs.org/). + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.Http.Resilience +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Examples + +When configuring an HttpClient through the [HTTP client factory](https://learn.microsoft.com/dotnet/core/extensions/httpclient-factory) the following extensions can add a set of pre-configured hedging or resilience behaviors. These pipelines combine multiple resilience strategies with pre-configured defaults. +- The total request timeout pipeline applies an overall timeout to the execution, ensuring that the request including hedging attempts, does not exceed the configured limit. +- The retry pipeline retries the request in case the dependency is slow or returns a transient error. +- The rate limiter pipeline limits the maximum number of requests being send to the dependency. +- The circuit breaker blocks the execution if too many direct failures or timeouts are detected. +- The attempt timeout pipeline limits each request attempt duration and throws if its exceeded. + +### Resilience + +The standard resilience pipeline makes use of the above strategies to ensure HTTP requests can be sent reliably. + +```csharp +var clientBuilder = services.AddHttpClient("MyClient"); + +clientBuilder.AddStandardResilienceHandler().Configure(o => +{ + o.CircuitBreaker.MinimumThroughput = 10; +}); +``` + +### Hedging + +The standard hedging pipeline uses a pool of circuit breakers to ensure that unhealthy endpoints are not hedged against. By default, the selection from pool is based on the URL Authority (scheme + host + port). It is recommended that you configure the way the strategies are selected by calling the `SelectPipelineByAuthority()` extensions. The last three strategies are applied to each individual endpoint. + +```csharp +var clientBuilder = services.AddHttpClient("MyClient"); + +clientBuilder.AddStandardHedgingHandler().Configure(o => +{ + o.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(10); +}); +``` + +### Custom Resilience + +For more granular control a custom pipeline can be constructed. + +```csharp +var clientBuilder = services.AddHttpClient("MyClient"); + +clientBuilder.AddResilienceHandler("myHandler", b => +{ + b.AddFallback(new FallbackStrategyOptions() + { + FallbackAction = _ => Outcome.FromResultAsValueTask(new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)) + }) + .AddConcurrencyLimiter(100) + .AddRetry(new HttpRetryStrategyOptions()) + .AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions()) + .AddTimeout(new HttpTimeoutStrategyOptions()); +}); +``` + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/HttpResilienceMetricsEnricher.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/HttpResilienceMetricsEnricher.cs new file mode 100644 index 00000000000..ac9f7a62559 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/HttpResilienceMetricsEnricher.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.Http; +using Microsoft.Shared.Text; +using Polly.Telemetry; + +namespace Microsoft.Extensions.Http.Resilience.Internal; + +internal sealed class HttpResilienceMetricsEnricher : MeteringEnricher +{ + public override void Enrich(in EnrichmentContext context) + { + if (typeof(TResult) != typeof(HttpResponseMessage)) + { + return; + } + + if (context.TelemetryEvent.Outcome.HasValue && context.TelemetryEvent.Outcome.Value.Result is HttpResponseMessage response) + { + context.Tags.Add(new(HttpResilienceTagNames.ErrorType, ((int)response.StatusCode).ToInvariantString())); + } + } +} diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/HttpResilienceTagNames.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/HttpResilienceTagNames.cs new file mode 100644 index 00000000000..5aa288276ee --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/HttpResilienceTagNames.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Extensions.Http.Resilience.Internal; + +internal static class HttpResilienceTagNames +{ + public const string ErrorType = "error.type"; +} diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ResilienceHandler.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ResilienceHandler.cs index 9b2a7ac01c5..5e979cf835c 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ResilienceHandler.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ResilienceHandler.cs @@ -48,6 +48,11 @@ static async (context, state) => { var request = context.Properties.GetValue(ResilienceKeys.RequestMessage, state.request); + // Always re-assign the context to this request message before execution. + // This is because for primary actions the context is also cloned and we need to re-assign it + // here because Polly doesn't have any other events that we can hook into. + request.SetResilienceContext(context); + try { var response = await state.instance.SendCoreAsync(request, context.CancellationToken).ConfigureAwait(context.ContinueOnCapturedContext); @@ -75,6 +80,11 @@ static async (context, state) => ResilienceContextPool.Shared.Return(context); request.SetResilienceContext(null); } + else + { + // Restore the original context + request.SetResilienceContext(context); + } } } diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/ResilienceHandlerContext.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/ResilienceHandlerContext.cs index a0e4e4ccdd1..3f5e1691abc 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/ResilienceHandlerContext.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/ResilienceHandlerContext.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Http.Resilience.Internal; using Microsoft.Shared.Diagnostics; using Polly.DependencyInjection; @@ -43,11 +44,12 @@ internal ResilienceHandlerContext(AddResiliencePipelineContext context) /// /// You can decide based on the to listen for changes in global options or named options. /// If is then the global options are listened to. + /// By default, the parameter is . /// /// You can listen for changes only for single options. If you call this method multiple times, the preceding calls are ignored and only the last one wins. /// /// - public void EnableReloads(string? name = null) => _context.EnableReloads(name); + public void EnableReloads<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions>(string? name = null) => _context.EnableReloads(name); /// /// Gets the options identified by . @@ -57,8 +59,9 @@ internal ResilienceHandlerContext(AddResiliencePipelineContext context) /// The options instance. /// /// If is then the global options are returned. + /// By default, the parameter is . /// - public TOptions GetOptions(string name) => _context.GetOptions(name); + public TOptions GetOptions(string? name = null) => _context.GetOptions(name); /// /// Registers a callback that is called when the pipeline instance being configured is disposed. diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/ResilienceHttpClientBuilderExtensions.Resilience.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/ResilienceHttpClientBuilderExtensions.Resilience.cs index f7eb8cfd99f..e78f4130690 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/ResilienceHttpClientBuilderExtensions.Resilience.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/ResilienceHttpClientBuilderExtensions.Resilience.cs @@ -2,20 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Net; using System.Net.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.ExceptionSummarization; -using Microsoft.Extensions.EnumStrings; using Microsoft.Extensions.Http.Resilience; using Microsoft.Extensions.Http.Resilience.Internal; -using Microsoft.Extensions.Resilience; using Microsoft.Shared.Diagnostics; -using Microsoft.Shared.Text; using Polly; using Polly.Registry; - -[assembly: EnumStrings(typeof(HttpStatusCode))] +using Polly.Telemetry; namespace Microsoft.Extensions.DependencyInjection; @@ -44,9 +39,7 @@ public static IHttpResiliencePipelineBuilder AddResilienceHandler( _ = Throw.IfNullOrEmpty(pipelineName); _ = Throw.IfNull(configure); - return builder.AddResilienceHandler(pipelineName, ConfigureBuilder); - - void ConfigureBuilder(ResiliencePipelineBuilder builder, ResilienceHandlerContext context) => configure(builder); + return builder.AddResilienceHandler(pipelineName, (builder, _) => configure(builder)); } /// @@ -149,17 +142,7 @@ private static void ConfigureHttpServices(IServiceCollection services) _ = services .AddExceptionSummarizer(b => b.AddHttpProvider()) - .ConfigureFailureResultContext((response) => - { - if (response != null) - { - return FailureResultContext.Create( - failureReason: ((int)response.StatusCode).ToInvariantString(), - additionalInformation: response.StatusCode.ToInvariantString()); - } - - return FailureResultContext.Create(); - }); + .Configure(options => options.MeteringEnrichers.Add(new HttpResilienceMetricsEnricher())); } private sealed class Marker diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Routing/Internal/OrderedGroups/OrderedGroupsRoutingStrategyFactory.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Routing/Internal/OrderedGroups/OrderedGroupsRoutingStrategyFactory.cs index 1dc12762f2d..81854db0d82 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Routing/Internal/OrderedGroups/OrderedGroupsRoutingStrategyFactory.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Routing/Internal/OrderedGroups/OrderedGroupsRoutingStrategyFactory.cs @@ -29,8 +29,6 @@ public OrderedGroupsRoutingStrategy Get() return strategy; } - public void Return(OrderedGroupsRoutingStrategy strategy) => _pool.Return(strategy); - OrderedGroupsRoutingStrategy IPooledObjectPolicy.Create() => new(_randomizer, _pool); bool IPooledObjectPolicy.Return(OrderedGroupsRoutingStrategy obj) => obj.TryReset(); diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Routing/Internal/WeightedGroups/WeightedGroupsRoutingStrategyFactory.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Routing/Internal/WeightedGroups/WeightedGroupsRoutingStrategyFactory.cs index d5147ae119b..b3e234b98d4 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Routing/Internal/WeightedGroups/WeightedGroupsRoutingStrategyFactory.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Routing/Internal/WeightedGroups/WeightedGroupsRoutingStrategyFactory.cs @@ -29,8 +29,6 @@ public WeightedGroupsRoutingStrategy Get() return strategy; } - public void Return(WeightedGroupsRoutingStrategy strategy) => _pool.Return(strategy); - WeightedGroupsRoutingStrategy IPooledObjectPolicy.Create() => new(_randomizer, _pool); bool IPooledObjectPolicy.Return(WeightedGroupsRoutingStrategy obj) => obj.TryReset(); diff --git a/src/Libraries/Microsoft.Extensions.ObjectPool.DependencyInjection/README.md b/src/Libraries/Microsoft.Extensions.ObjectPool.DependencyInjection/README.md index e845566c06f..26d046d63e6 100644 --- a/src/Libraries/Microsoft.Extensions.ObjectPool.DependencyInjection/README.md +++ b/src/Libraries/Microsoft.Extensions.ObjectPool.DependencyInjection/README.md @@ -1 +1,94 @@ -README +# Microsoft.Extensions.ObjectPool.DependencyInjection + +This provides the ability to retrieve pooled instances that can be initialized using dependency injection. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.ObjectPool.DependencyInjection +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Example + +### Registering Pools + +The object pools can be registered using the following methods: + +```csharp + public static IServiceCollection AddPooled(this IServiceCollection services, Action? configure = null) + + public static IServiceCollection AddPooled(this IServiceCollection services, Action? configure = null) +``` + +For example: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddSingleton(); +builder.Services.AddPooled(); + +var app = builder.Build(); +``` + +### Consuming Pools + +Once registered, pools can be resolved using dependency injection. For example: + +```csharp +var pool = context.RequestServices.GetRequiredService>(); + +var obj = pool.Get(); + +// Use the pooled object ... + +pool.Return(obj); +``` + +Pooled instances will be resolved from the root dependency injection container and can only +use singleton dependencies. + +Pooled instances can implement `Microsoft.Extensions.ObjectPool.IResettable` in order to +be initialized when they are returned to the pool. + +```csharp +public class MyPooledClass : IResettable +{ + private MyService _myService; + + public MyPooledClass(MyService myService) + { + _myService = myService; + } + + public bool TryReset() + { + // Clean instance here + return true; + } +} +``` + +## Options + +The `DependencyInjectionPoolOptions.Capacity` property is used to configure the maximum capacity of each pool. The default value is `1024`. + +This value can also be set during the pool registration: + +```csharp +builder.Services.AddPooled(options => options.Capacity = 64); +``` + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). \ No newline at end of file diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/Directory.Build.props b/src/Libraries/Microsoft.Extensions.Options.Contextual/Directory.Build.props new file mode 100644 index 00000000000..35ff2ae323e --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/Directory.Build.props @@ -0,0 +1,11 @@ + + + + dev + EXTEXP0017 + + + + \ No newline at end of file diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/Microsoft.Extensions.Options.Contextual.csproj b/src/Libraries/Microsoft.Extensions.Options.Contextual/Microsoft.Extensions.Options.Contextual.csproj index add66596d28..4f1c3825b48 100644 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/Microsoft.Extensions.Options.Contextual.csproj +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/Microsoft.Extensions.Options.Contextual.csproj @@ -11,7 +11,6 @@ - dev 100 80 diff --git a/src/Libraries/Microsoft.Extensions.Options.Contextual/README.md b/src/Libraries/Microsoft.Extensions.Options.Contextual/README.md index e845566c06f..48a2585aa75 100644 --- a/src/Libraries/Microsoft.Extensions.Options.Contextual/README.md +++ b/src/Libraries/Microsoft.Extensions.Options.Contextual/README.md @@ -1 +1,122 @@ -README +# Microsoft.Extensions.Options.Contextual + +APIs for dynamically configuring options based on a given context. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.Options.Contextual +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Example + +Start with an option type. + +```csharp +internal class WeatherForecastOptions +{ + public string TemperatureScale { get; set; } = "Celcius"; // Celcius or Farenheit + public int ForecastDays { get; set; } +} +``` + +Define a context and a receiver that will be used as inputs to dynamically configure the options. + +```csharp +[OptionsContext] +internal partial class WeatherForecastContext // Note class must be partial +{ + public Guid UserId { get; set; } + public string? Country { get; set; } +} + +internal class CountryContextReceiver : IOptionsContextReceiver +{ + public string? Country { get; private set; } + + public void Receive(string key, T value) + { + if (key == nameof(Country)) + { + Country = value?.ToString(); + } + } +} +``` + +Create a service that consumes the options for a given context. + +```csharp +internal class WeatherForecast +{ + public DateTime Date { get; set; } + public int Temperature { get; set; } + public string TemperatureScale { get; set; } = string.Empty; +} + +internal class WeatherForecastService +{ + private readonly IContextualOptions _contextualOptions; + private readonly Random _rng = new(0); + + public WeatherForecastService(IContextualOptions contextualOptions) + { + _contextualOptions = contextualOptions; + } + + public async Task> GetForecast(WeatherForecastContext context, CancellationToken cancellationToken) + { + WeatherForecastOptions options = await _contextualOptions.GetAsync(context, cancellationToken).ConfigureAwait(false); + return Enumerable.Range(1, options.ForecastDays).Select(index => new WeatherForecast + { + Date = new DateTime(2000, 1, 1).AddDays(index), + Temperature = _rng.Next(-20, 55), + TemperatureScale = options.TemperatureScale, + }); + } +} +``` + +The options can be configured with both global options (ForecastDays), and options that vary depending on the current context (TemperatureScale). + +```csharp +using var host = FakeHost.CreateBuilder() + .ConfigureServices(services => services + .Configure(options => options.ForecastDays = 7) + .Configure(ConfigureTemperatureScaleBasedOnCountry) + .AddSingleton()) + .Build(); + +static void ConfigureTemperatureScaleBasedOnCountry(IOptionsContext context, WeatherForecastOptions options) +{ + CountryContextReceiver receiver = new(); + context.PopulateReceiver(receiver); + if (receiver.Country == "US") + { + options.TemperatureScale = "Farenheit"; + } +} +``` + +And lastly, the service is called with some context. + +```csharp +var forecastService = host.Services.GetRequiredService(); + +var usForcast = await forecastService.GetForecast(new WeatherForecastContext { Country = "US" }, CancellationToken.None); +var caForcast = await forecastService.GetForecast(new WeatherForecastContext { Country = "CA" }, CancellationToken.None); +``` + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.Resilience/Microsoft.Extensions.Resilience.csproj b/src/Libraries/Microsoft.Extensions.Resilience/Microsoft.Extensions.Resilience.csproj index 612eaf87f0a..672833718fc 100644 --- a/src/Libraries/Microsoft.Extensions.Resilience/Microsoft.Extensions.Resilience.csproj +++ b/src/Libraries/Microsoft.Extensions.Resilience/Microsoft.Extensions.Resilience.csproj @@ -1,7 +1,7 @@  Microsoft.Extensions.Resilience - Mechanisms to harden applications against transient failures. + Extensions to the Polly libraries to enrich telemetry with metadata and exception summaries. Resilience @@ -9,7 +9,6 @@ true true true - true true true true @@ -25,7 +24,7 @@ - + diff --git a/src/Libraries/Microsoft.Extensions.Resilience/Microsoft.Extensions.Resilience.json b/src/Libraries/Microsoft.Extensions.Resilience/Microsoft.Extensions.Resilience.json index c78f741542a..2474bf8ab61 100644 --- a/src/Libraries/Microsoft.Extensions.Resilience/Microsoft.Extensions.Resilience.json +++ b/src/Libraries/Microsoft.Extensions.Resilience/Microsoft.Extensions.Resilience.json @@ -2,43 +2,15 @@ "Name": "Microsoft.Extensions.Resilience, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", "Types": [ { - "Type": "readonly struct Microsoft.Extensions.Resilience.FailureResultContext", + "Type": "static class Polly.ResilienceContextExtensions", "Stage": "Stable", "Methods": [ { - "Member": "Microsoft.Extensions.Resilience.FailureResultContext.FailureResultContext();", + "Member": "static Microsoft.Extensions.Http.Diagnostics.RequestMetadata? Polly.ResilienceContextExtensions.GetRequestMetadata(this Polly.ResilienceContext context);", "Stage": "Stable" }, { - "Member": "static Microsoft.Extensions.Resilience.FailureResultContext Microsoft.Extensions.Resilience.FailureResultContext.Create(string failureSource = \"unknown\", string failureReason = \"unknown\", string additionalInformation = \"unknown\");", - "Stage": "Stable" - } - ], - "Properties": [ - { - "Member": "string Microsoft.Extensions.Resilience.FailureResultContext.AdditionalInformation { get; }", - "Stage": "Stable" - }, - { - "Member": "string Microsoft.Extensions.Resilience.FailureResultContext.FailureReason { get; }", - "Stage": "Stable" - }, - { - "Member": "string Microsoft.Extensions.Resilience.FailureResultContext.FailureSource { get; }", - "Stage": "Stable" - } - ] - }, - { - "Type": "static class Microsoft.Extensions.Resilience.ResilienceContextExtensions", - "Stage": "Stable", - "Methods": [ - { - "Member": "static Microsoft.Extensions.Http.Diagnostics.RequestMetadata? Microsoft.Extensions.Resilience.ResilienceContextExtensions.GetRequestMetadata(this Polly.ResilienceContext context);", - "Stage": "Stable" - }, - { - "Member": "static void Microsoft.Extensions.Resilience.ResilienceContextExtensions.SetRequestMetadata(this Polly.ResilienceContext context, Microsoft.Extensions.Http.Diagnostics.RequestMetadata requestMetadata);", + "Member": "static void Polly.ResilienceContextExtensions.SetRequestMetadata(this Polly.ResilienceContext context, Microsoft.Extensions.Http.Diagnostics.RequestMetadata requestMetadata);", "Stage": "Stable" } ] @@ -50,12 +22,8 @@ { "Member": "static Microsoft.Extensions.DependencyInjection.IServiceCollection Microsoft.Extensions.DependencyInjection.ResilienceServiceCollectionExtensions.AddResilienceEnricher(this Microsoft.Extensions.DependencyInjection.IServiceCollection services);", "Stage": "Stable" - }, - { - "Member": "static Microsoft.Extensions.DependencyInjection.IServiceCollection Microsoft.Extensions.DependencyInjection.ResilienceServiceCollectionExtensions.ConfigureFailureResultContext(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Func configure);", - "Stage": "Stable" } ] } ] -} \ No newline at end of file +} diff --git a/src/Libraries/Microsoft.Extensions.Resilience/README.md b/src/Libraries/Microsoft.Extensions.Resilience/README.md index e845566c06f..28ec491febb 100644 --- a/src/Libraries/Microsoft.Extensions.Resilience/README.md +++ b/src/Libraries/Microsoft.Extensions.Resilience/README.md @@ -1 +1,40 @@ -README +# Microsoft.Extensions.Resilience + +Extensions to the Polly libraries to enrich telemetry with metadata and exception summaries. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.Resilience +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage Examples + +The services can be registered using the following method: + +```csharp +public static IServiceCollection AddResilienceEnricher(this IServiceCollection services) +``` + +This will optionally consume the `IExceptionSummarizer` service if it has been registered and add that data to Polly's telemetry. It will also include `RequestMetadata` that can be set or retrieved with these extensions: + +```csharp +public static void SetRequestMetadata(this ResilienceContext context, RequestMetadata requestMetadata) +public static RequestMetadata? GetRequestMetadata(this ResilienceContext context) +``` + +See the Polly docs for details about working with [`ResilienceContext`](https://www.pollydocs.org/advanced/resilience-context.html). + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/FailureResultContext.cs b/src/Libraries/Microsoft.Extensions.Resilience/Resilience/FailureResultContext.cs deleted file mode 100644 index 48fe0ad0296..00000000000 --- a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/FailureResultContext.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.Http.Diagnostics; -using Microsoft.Shared.Diagnostics; - -namespace Microsoft.Extensions.Resilience; - -/// -/// Captures the dimensions metered for a transient failure result. -/// -#pragma warning disable CA1815 // Override equals and operator equals on value types (Such usage is not expected in this scenario) -public readonly struct FailureResultContext -#pragma warning restore CA1815 -{ - /// - /// Initializes a new instance of the structure. - /// - /// The source of the failure. - /// The reason of the failure. - /// Additional information for the failure. - /// To be added. - public static FailureResultContext Create( - string failureSource = TelemetryConstants.Unknown, - string failureReason = TelemetryConstants.Unknown, - string additionalInformation = TelemetryConstants.Unknown) - => new(failureSource, failureReason, additionalInformation); - - private FailureResultContext(string failureSource, string failureReason, string additionalInformation) - { - FailureSource = Throw.IfNullOrEmpty(failureSource); - FailureReason = Throw.IfNullOrEmpty(failureReason); - AdditionalInformation = Throw.IfNullOrEmpty(additionalInformation); - } - - /// - /// Gets the source of the failure presented in the delegate result. - /// - public string FailureSource { get; } - - /// - /// Gets the reason of the failure presented in the delegate result. - /// - public string FailureReason { get; } - - /// - /// Gets additional information of the failure presented in the delegate result. - /// - public string AdditionalInformation { get; } -} diff --git a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/FailureEventMetricsOptions.cs b/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/FailureEventMetricsOptions.cs deleted file mode 100644 index a964c2283d0..00000000000 --- a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/FailureEventMetricsOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; - -namespace Microsoft.Extensions.Resilience.Internal; - -internal sealed class FailureEventMetricsOptions -{ - public Dictionary> Factories { get; } = []; - - public void ConfigureFailureResultContext(Func configure) - { - Factories[typeof(TResult)] = value => configure((TResult)value); - } -} diff --git a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/ResilienceMetricsEnricher.cs b/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/ResilienceMetricsEnricher.cs index c0621654715..545ce4b6134 100644 --- a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/ResilienceMetricsEnricher.cs +++ b/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/ResilienceMetricsEnricher.cs @@ -2,29 +2,24 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Frozen; using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.Diagnostics.ExceptionSummarization; using Microsoft.Extensions.Http.Diagnostics; -using Microsoft.Extensions.Options; +using Polly; using Polly.Telemetry; namespace Microsoft.Extensions.Resilience.Internal; internal sealed class ResilienceMetricsEnricher : MeteringEnricher { - private readonly FrozenDictionary> _faultFactories; private readonly IOutgoingRequestContext? _outgoingRequestContext; private readonly IExceptionSummarizer? _exceptionSummarizer; public ResilienceMetricsEnricher( - IOptions metricsOptions, - IEnumerable outgoingRequestContext, + IOutgoingRequestContext? outgoingRequestContext = null, IExceptionSummarizer? exceptionSummarizer = null) { - _faultFactories = metricsOptions.Value.Factories.ToFrozenDictionary(); - _outgoingRequestContext = outgoingRequestContext.FirstOrDefault(); + _outgoingRequestContext = outgoingRequestContext; _exceptionSummarizer = exceptionSummarizer; } @@ -34,16 +29,7 @@ public override void Enrich(in EnrichmentContext if (_exceptionSummarizer is not null && outcome?.Exception is Exception e) { - context.Tags.Add(new(ResilienceTagNames.FailureSource, e.Source)); - context.Tags.Add(new(ResilienceTagNames.FailureReason, e.GetType().Name)); - context.Tags.Add(new(ResilienceTagNames.FailureSummary, _exceptionSummarizer.Summarize(e).ToString())); - } - else if (outcome is not null && outcome.Value.Result is object result && _faultFactories.TryGetValue(result.GetType(), out var factory)) - { - var failureContext = factory(result); - context.Tags.Add(new(ResilienceTagNames.FailureSource, failureContext.FailureSource)); - context.Tags.Add(new(ResilienceTagNames.FailureReason, failureContext.FailureReason)); - context.Tags.Add(new(ResilienceTagNames.FailureSummary, failureContext.AdditionalInformation)); + context.Tags.Add(new(ResilienceTagNames.ErrorType, _exceptionSummarizer.Summarize(e).Description)); } if ((context.TelemetryEvent.Context.GetRequestMetadata() ?? _outgoingRequestContext?.RequestMetadata) is RequestMetadata requestMetadata) diff --git a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/ResilienceTagNames.cs b/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/ResilienceTagNames.cs index 158f77e362e..587a044dae9 100644 --- a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/ResilienceTagNames.cs +++ b/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/ResilienceTagNames.cs @@ -5,13 +5,9 @@ namespace Microsoft.Extensions.Resilience.Internal; internal static class ResilienceTagNames { - public const string FailureSource = "failure-source"; + public const string ErrorType = "error.type"; - public const string FailureReason = "failure-reason"; + public const string DependencyName = "request.dependency.name"; - public const string FailureSummary = "failure-summary"; - - public const string DependencyName = "dep-name"; - - public const string RequestName = "req-name"; + public const string RequestName = "request.name"; } diff --git a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/ResilienceContextExtensions.cs b/src/Libraries/Microsoft.Extensions.Resilience/Resilience/ResilienceContextExtensions.cs index 89cc75533d7..655aa7c259e 100644 --- a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/ResilienceContextExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Resilience/Resilience/ResilienceContextExtensions.cs @@ -4,9 +4,8 @@ using System; using Microsoft.Extensions.Http.Diagnostics; using Microsoft.Shared.Diagnostics; -using Polly; -namespace Microsoft.Extensions.Resilience; +namespace Polly; /// /// Extensions for . diff --git a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/ResilienceServiceCollectionExtensions.cs b/src/Libraries/Microsoft.Extensions.Resilience/Resilience/ResilienceServiceCollectionExtensions.cs index 927b2eb0bc0..243c046b80e 100644 --- a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/ResilienceServiceCollectionExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Resilience/Resilience/ResilienceServiceCollectionExtensions.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Diagnostics.ExceptionSummarization; using Microsoft.Extensions.Http.Diagnostics; using Microsoft.Extensions.Options; -using Microsoft.Extensions.Resilience; using Microsoft.Extensions.Resilience.Internal; using Microsoft.Shared.Diagnostics; using Polly.Telemetry; @@ -31,9 +30,6 @@ public static class ResilienceServiceCollectionExtensions /// Exception enrichment based on . /// /// - /// Result enrichment based on and . - /// - /// /// Request metadata enrichment based on . /// /// @@ -57,24 +53,4 @@ public static IServiceCollection AddResilienceEnricher(this IServiceCollection s return services; } - - /// - /// Configures the failure result dimensions. - /// - /// The type of the policy result. - /// The services. - /// The configure result dimensions. - /// The input . - /// - /// or is . - /// - public static IServiceCollection ConfigureFailureResultContext( - this IServiceCollection services, - Func configure) - { - _ = Throw.IfNull(services); - _ = Throw.IfNull(configure); - - return services.Configure(options => options.ConfigureFailureResultContext(configure)); - } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Enrichment/EnrichmentServiceCollectionExtensions.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Enrichment/EnrichmentServiceCollectionExtensions.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Enrichment/EnrichmentServiceCollectionExtensions.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Enrichment/EnrichmentServiceCollectionExtensions.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Enrichment/IEnrichmentTagCollector.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Enrichment/IEnrichmentTagCollector.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Enrichment/IEnrichmentTagCollector.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Enrichment/IEnrichmentTagCollector.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Enrichment/ILogEnricher.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Enrichment/ILogEnricher.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Enrichment/ILogEnricher.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Enrichment/ILogEnricher.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Enrichment/IStaticLogEnricher.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Enrichment/IStaticLogEnricher.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Enrichment/IStaticLogEnricher.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Enrichment/IStaticLogEnricher.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Http/HttpRouteParameterRedactionMode.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Http/HttpRouteParameterRedactionMode.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Http/HttpRouteParameterRedactionMode.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Http/HttpRouteParameterRedactionMode.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Http/IDownstreamDependencyMetadata.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Http/IDownstreamDependencyMetadata.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Http/IDownstreamDependencyMetadata.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Http/IDownstreamDependencyMetadata.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Http/IOutgoingRequestContext.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Http/IOutgoingRequestContext.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Http/IOutgoingRequestContext.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Http/IOutgoingRequestContext.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Http/RequestMetadata.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Http/RequestMetadata.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Http/RequestMetadata.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Http/RequestMetadata.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Http/TelemetryConstants.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Http/TelemetryConstants.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Http/TelemetryConstants.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Http/TelemetryConstants.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Checkpoint.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Checkpoint.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Checkpoint.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Checkpoint.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/ILatencyContext.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/ILatencyContext.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/ILatencyContext.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/ILatencyContext.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/ILatencyContextProvider.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/ILatencyContextProvider.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/ILatencyContextProvider.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/ILatencyContextProvider.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/ILatencyDataExporter.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/ILatencyDataExporter.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/ILatencyDataExporter.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/ILatencyDataExporter.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/LatencyData.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/LatencyData.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/LatencyData.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/LatencyData.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Measure.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Measure.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Measure.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Measure.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/NullLatencyContext.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/NullLatencyContext.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/NullLatencyContext.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/NullLatencyContext.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/NullLatencyContextServiceCollectionExtensions.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/NullLatencyContextServiceCollectionExtensions.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/NullLatencyContextServiceCollectionExtensions.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/NullLatencyContextServiceCollectionExtensions.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Registration/CheckpointToken.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Registration/CheckpointToken.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Registration/CheckpointToken.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Registration/CheckpointToken.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Registration/ILatencyContextTokenIssuer.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Registration/ILatencyContextTokenIssuer.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Registration/ILatencyContextTokenIssuer.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Registration/ILatencyContextTokenIssuer.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Registration/LatencyContextRegistrationOptions.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Registration/LatencyContextRegistrationOptions.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Registration/LatencyContextRegistrationOptions.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Registration/LatencyContextRegistrationOptions.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Registration/LatencyContextRegistrationOptionsValidator.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Registration/LatencyContextRegistrationOptionsValidator.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Registration/LatencyContextRegistrationOptionsValidator.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Registration/LatencyContextRegistrationOptionsValidator.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Registration/LatencyRegistryServiceCollectionExtensions.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Registration/LatencyRegistryServiceCollectionExtensions.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Registration/LatencyRegistryServiceCollectionExtensions.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Registration/LatencyRegistryServiceCollectionExtensions.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Registration/MeasureToken.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Registration/MeasureToken.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Registration/MeasureToken.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Registration/MeasureToken.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Registration/TagToken.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Registration/TagToken.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Registration/TagToken.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Registration/TagToken.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Tag.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Tag.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Latency/Tag.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Latency/Tag.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/ITagCollector.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/ITagCollector.cs similarity index 93% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/ITagCollector.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/ITagCollector.cs index 3d0c1b10760..89874a6388f 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/ITagCollector.cs +++ b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/ITagCollector.cs @@ -30,10 +30,10 @@ public interface ITagCollector /// /// The name of the tag to add. /// The value of the tag to add. - /// The data classification of the tag value. + /// The data classifications of the tag value. /// is . /// is empty or contains exclusively whitespace, /// or when a tag of the same name has already been added. /// - void Add(string tagName, object? tagValue, DataClassification classification); + void Add(string tagName, object? tagValue, DataClassificationSet classifications); } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LogPropertiesAttribute.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LogPropertiesAttribute.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LogPropertiesAttribute.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LogPropertiesAttribute.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LogPropertyIgnoreAttribute.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LogPropertyIgnoreAttribute.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LogPropertyIgnoreAttribute.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LogPropertyIgnoreAttribute.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LoggerMessageHelper.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageHelper.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LoggerMessageHelper.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageHelper.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LoggerMessageState.ClassifiedTag.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageState.ClassifiedTag.cs similarity index 90% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LoggerMessageState.ClassifiedTag.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageState.ClassifiedTag.cs index f3e134a76c9..dcb38190d80 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LoggerMessageState.ClassifiedTag.cs +++ b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageState.ClassifiedTag.cs @@ -30,16 +30,16 @@ public readonly struct ClassifiedTag /// /// Gets the tag's data classification. /// - public readonly DataClassification Classification { get; } + public readonly DataClassificationSet Classifications { get; } /// /// Initializes a new instance of the struct. /// - public ClassifiedTag(string name, object? value, DataClassification classification) + public ClassifiedTag(string name, object? value, DataClassificationSet classifications) { Name = name; Value = value; - Classification = classification; + Classifications = classifications; } } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LoggerMessageState.EnrichmentTagCollector.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageState.EnrichmentTagCollector.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LoggerMessageState.EnrichmentTagCollector.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageState.EnrichmentTagCollector.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LoggerMessageState.ReadOnlyList.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageState.ReadOnlyList.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LoggerMessageState.ReadOnlyList.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageState.ReadOnlyList.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LoggerMessageState.TagCollector.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageState.TagCollector.cs similarity index 83% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LoggerMessageState.TagCollector.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageState.TagCollector.cs index c9b9dbe6296..a19563a5865 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LoggerMessageState.TagCollector.cs +++ b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageState.TagCollector.cs @@ -7,7 +7,7 @@ namespace Microsoft.Extensions.Logging; public partial class LoggerMessageState : ITagCollector { - private const char Separator = '_'; + private const char Separator = '.'; /// void ITagCollector.Add(string tagName, object? tagValue) @@ -17,15 +17,15 @@ void ITagCollector.Add(string tagName, object? tagValue) } /// - void ITagCollector.Add(string tagName, object? tagValue, DataClassification classification) + void ITagCollector.Add(string tagName, object? tagValue, DataClassificationSet classifications) { string fullName = TagNamePrefix.Length > 0 ? TagNamePrefix + Separator + tagName : tagName; - AddClassifiedTag(fullName, tagValue, classification); + AddClassifiedTag(fullName, tagValue, classifications); } /// /// Gets or sets the parameter name that is prepended to all tag names added to this instance using the - /// or + /// or /// methods. /// public string TagNamePrefix { get; set; } = string.Empty; diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LoggerMessageState.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageState.cs similarity index 94% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LoggerMessageState.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageState.cs index efebea99ea6..6cc10f20086 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/LoggerMessageState.cs +++ b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageState.cs @@ -92,11 +92,11 @@ public void AddTag(string name, object? value) /// /// The name of the tag. /// The value. - /// The data classification of the tag. - public void AddClassifiedTag(string name, object? value, DataClassification classification) + /// The data classification of the tag. + public void AddClassifiedTag(string name, object? value, DataClassificationSet classifications) { var index = ReserveClassifiedTagSpace(1); - ClassifiedTagArray[index] = new(name, value, classification); + ClassifiedTagArray[index] = new(name, value, classifications); } /// @@ -151,8 +151,9 @@ public override string ToString() // note we don't emit the value here as that could lead to a privacy incident. _ = sb.Append(_classifiedTags[i].Name); - _ = sb.Append('='); - _ = sb.Append(_classifiedTags[i].Classification.ToString()); + _ = sb.Append("= ("); + _ = sb.Append(_classifiedTags[i].Classifications.ToString()); + _ = sb.Append(')'); } var result = sb.ToString(); diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/TagProviderAttribute.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/TagProviderAttribute.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Logging/TagProviderAttribute.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/TagProviderAttribute.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Metrics/CounterAttribute.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Metrics/CounterAttribute.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Metrics/CounterAttribute.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Metrics/CounterAttribute.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Metrics/CounterAttributeT.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Metrics/CounterAttributeT.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Metrics/CounterAttributeT.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Metrics/CounterAttributeT.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Metrics/GaugeAttribute.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Metrics/GaugeAttribute.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Metrics/GaugeAttribute.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Metrics/GaugeAttribute.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Metrics/HistogramAttribute.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Metrics/HistogramAttribute.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Metrics/HistogramAttribute.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Metrics/HistogramAttribute.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Metrics/HistogramAttributeT.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Metrics/HistogramAttributeT.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Metrics/HistogramAttributeT.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Metrics/HistogramAttributeT.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Metrics/TagNameAttribute.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Metrics/TagNameAttribute.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Metrics/TagNameAttribute.cs rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Metrics/TagNameAttribute.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Microsoft.Extensions.Diagnostics.ExtraAbstractions.csproj b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Microsoft.Extensions.Telemetry.Abstractions.csproj similarity index 97% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Microsoft.Extensions.Diagnostics.ExtraAbstractions.csproj rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Microsoft.Extensions.Telemetry.Abstractions.csproj index 01ca7f61b49..0e16deda969 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Microsoft.Extensions.Diagnostics.ExtraAbstractions.csproj +++ b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Microsoft.Extensions.Telemetry.Abstractions.csproj @@ -1,6 +1,6 @@  - Microsoft.Extensions.Diagnostics.Extra + Microsoft.Extensions.Telemetry Common abstractions for high-level telemetry primitives. Telemetry diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Microsoft.Extensions.Diagnostics.ExtraAbstractions.json b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Microsoft.Extensions.Telemetry.Abstractions.json similarity index 98% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Microsoft.Extensions.Diagnostics.ExtraAbstractions.json rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Microsoft.Extensions.Telemetry.Abstractions.json index adecc16174c..bf4b445319d 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/Microsoft.Extensions.Diagnostics.ExtraAbstractions.json +++ b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Microsoft.Extensions.Telemetry.Abstractions.json @@ -1,5 +1,5 @@ { - "Name": "Microsoft.Extensions.Diagnostics.ExtraAbstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", + "Name": "Microsoft.Extensions.Telemetry.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", "Types": [ { "Type": "readonly struct Microsoft.Extensions.Diagnostics.Latency.Checkpoint : System.IEquatable", @@ -405,7 +405,7 @@ "Stage": "Stable" }, { - "Member": "void Microsoft.Extensions.Logging.ITagCollector.Add(string tagName, object? tagValue, Microsoft.Extensions.Compliance.Classification.DataClassification classification);", + "Member": "void Microsoft.Extensions.Logging.ITagCollector.Add(string tagName, object? tagValue, Microsoft.Extensions.Compliance.Classification.DataClassificationSet classifications);", "Stage": "Stable" } ] @@ -517,7 +517,7 @@ "Stage": "Stable" }, { - "Member": "void Microsoft.Extensions.Logging.LoggerMessageState.AddClassifiedTag(string name, object? value, Microsoft.Extensions.Compliance.Classification.DataClassification classification);", + "Member": "void Microsoft.Extensions.Logging.LoggerMessageState.AddClassifiedTag(string name, object? value, Microsoft.Extensions.Compliance.Classification.DataClassificationSet classifications);", "Stage": "Stable" }, { @@ -577,7 +577,7 @@ "Stage": "Stable", "Methods": [ { - "Member": "Microsoft.Extensions.Logging.LoggerMessageState.ClassifiedTag.ClassifiedTag(string name, object? value, Microsoft.Extensions.Compliance.Classification.DataClassification classification);", + "Member": "Microsoft.Extensions.Logging.LoggerMessageState.ClassifiedTag.ClassifiedTag(string name, object? value, Microsoft.Extensions.Compliance.Classification.DataClassificationSet classifications);", "Stage": "Stable" }, { @@ -587,7 +587,7 @@ ], "Properties": [ { - "Member": "Microsoft.Extensions.Compliance.Classification.DataClassification Microsoft.Extensions.Logging.LoggerMessageState.ClassifiedTag.Classification { get; }", + "Member": "Microsoft.Extensions.Compliance.Classification.DataClassificationSet Microsoft.Extensions.Logging.LoggerMessageState.ClassifiedTag.Classifications { get; }", "Stage": "Stable" }, { @@ -860,4 +860,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/README.md b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/README.md new file mode 100644 index 00000000000..288ec8b3466 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/README.md @@ -0,0 +1,123 @@ +# Microsoft.Extensions.Telemetry.Abstractions + +This package contains common abstractions for high-level telemetry primitives. Here are the main features it provides: + +- Enhanced Logging Capabilities +- Log Enrichment +- Latency Measurement +- HTTP Request Metadata Handling + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.Telemetry.Abstractions +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage + +### Enhanced Logging Capabilities + +The package includes a custom logging generator that enhances the default .NET logging capabilities by replacing the default generator. This generator automatically logs the contents of collections and offers advanced logging features, significantly improving the debugging and monitoring process. + +```csharp +[LoggerMessage(1, LogLevel.Information, "These are the contents of my dictionary: {temperature}")] +internal static partial void LogMyDictionary(ILogger logger, Dictionary temperature); +``` + +It also adds the `LogProperties` attribute which can be applied to an object parameter of a `LoggerMessage` method. It introspects the passed-in object and automatically adds tags for all its properties. This leads to more informative logs without the need for manual tagging of each property. + +```csharp +[LoggerMessage(1, LogLevel.Information, "Detected a new temperature: {temperature}")] +internal static partial void LogNewTemperature(ILogger logger, [LogProperties] Temperature temperature); + +internal record Temperature(double value, TemperatureUnit unit); +``` + +### Log Enrichment + +Logging data can be enriched by adding custom log enrichers to the service collection. This can be done using specific implementations or generic types. + +```csharp +// Using a specific implementation +builder.Services.AddLogEnricher(new CustomLogEnricher()); + +// Using a generic type +builder.Services.AddLogEnricher(); +``` + +Create custom log enrichers by implementing the `ILogEnricher` interface. + +```csharp +public class CustomLogEnricher : ILogEnricher +{ + public void Enrich(IEnrichmentTagCollector collector) + { + // Add custom logic to enrich log data + collector.Add("CustomTag", "CustomValue"); + } +} +``` + +### Latency Measurement + +To track latency in an application it is possible to register checkpoint, measure, and tag names using the following methods: + +```csharp +builder.Services.RegisterCheckpointNames("databaseQuery", "externalApiCall"); +builder.Services.RegisterMeasureNames("responseTime", "processingTime"); +builder.Services.RegisterTagNames("userId", "transactionId"); +``` + +Implement the `ILatencyDataExporter` to export latency data. This can be integrated with external systems or logging frameworks. + +```csharp +public class CustomLatencyDataExporter : ILatencyDataExporter +{ + public async Task ExportAsync(LatencyData data, CancellationToken cancellationToken) + { + // Export logic here + } +} +``` + +Use the latency context to track performance metrics in your application. + +```csharp +public void YourMethod(ILatencyContextProvider contextProvider) +{ + var context = contextProvider.CreateContext(); + var checkpointToken = context.GetCheckpointToken("databaseQuery"); + + // Start measuring + context.AddCheckpoint(checkpointToken); + + // Perform operations... + + // End measuring + context.AddCheckpoint(checkpointToken); + + // Optionally, record measures and tags + context.RecordMeasure(context.GetMeasureToken("responseTime"), measureValue); + context.SetTag(context.GetTagToken("userId"), "User123"); +} +``` + +### Http Request Metadata Handling + +The `IDownstreamDependencyMetadata` interface is designed to capture and store metadata about the downstream dependencies of an HTTP request. This is particularly useful for understanding external service dependencies and their impact on your application's performance and reliability. + +The `IOutgoingRequestContext` interface provides a mechanism for associating metadata with outgoing HTTP requests. This allows you to enrich outbound requests with additional information that can be used for logging, telemetry, and analysis. + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/buildTransitive/net6.0/Microsoft.Extensions.Diagnostics.ExtraAbstractions.props b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/buildTransitive/net6.0/Microsoft.Extensions.Telemetry.Abstractions.props similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/buildTransitive/net6.0/Microsoft.Extensions.Diagnostics.ExtraAbstractions.props rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/buildTransitive/net6.0/Microsoft.Extensions.Telemetry.Abstractions.props diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/buildTransitive/net6.0/Microsoft.Extensions.Diagnostics.ExtraAbstractions.targets b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/buildTransitive/net6.0/Microsoft.Extensions.Telemetry.Abstractions.targets similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ExtraAbstractions/buildTransitive/net6.0/Microsoft.Extensions.Diagnostics.ExtraAbstractions.targets rename to src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/buildTransitive/net6.0/Microsoft.Extensions.Telemetry.Abstractions.targets diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ApplicationEnricherServiceCollectionExtensions.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ApplicationEnricherServiceCollectionExtensions.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ApplicationEnricherServiceCollectionExtensions.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ApplicationEnricherServiceCollectionExtensions.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ApplicationEnricherTags.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ApplicationEnricherTags.cs similarity index 80% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ApplicationEnricherTags.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ApplicationEnricherTags.cs index ea0807b0d44..60c4017c63c 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ApplicationEnricherTags.cs +++ b/src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ApplicationEnricherTags.cs @@ -14,22 +14,22 @@ public static class ApplicationEnricherTags /// /// Application name. /// - public const string ApplicationName = "AppName"; + public const string ApplicationName = "service.name"; /// /// Environment name. /// - public const string EnvironmentName = "CloudEnv"; + public const string EnvironmentName = "deployment.environment"; /// /// Deployment ring. /// - public const string DeploymentRing = "CloudDeploymentRing"; + public const string DeploymentRing = "DeploymentRing"; /// /// Build version. /// - public const string BuildVersion = "CloudRoleVer"; + public const string BuildVersion = "service.version"; /// /// Gets a list of all dimension names. diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ApplicationLogEnricher.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ApplicationLogEnricher.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ApplicationLogEnricher.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ApplicationLogEnricher.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ApplicationLogEnricherOptions.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ApplicationLogEnricherOptions.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ApplicationLogEnricherOptions.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ApplicationLogEnricherOptions.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ProcessEnricherServiceCollectionExtensions.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ProcessEnricherServiceCollectionExtensions.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ProcessEnricherServiceCollectionExtensions.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ProcessEnricherServiceCollectionExtensions.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ProcessEnricherTagNames.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ProcessEnricherTagNames.cs similarity index 89% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ProcessEnricherTagNames.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ProcessEnricherTagNames.cs index 807b1c122ef..fc7698f3caa 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ProcessEnricherTagNames.cs +++ b/src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ProcessEnricherTagNames.cs @@ -14,12 +14,12 @@ public static class ProcessEnricherTagNames /// /// Process ID. /// - public const string ProcessId = "pid"; + public const string ProcessId = "process.pid"; /// /// Thread ID. /// - public const string ThreadId = "tid"; + public const string ThreadId = "thread.id"; /// /// Gets a list of all dimension names. diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ProcessLogEnricher.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ProcessLogEnricher.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ProcessLogEnricher.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ProcessLogEnricher.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ProcessLogEnricherOptions.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ProcessLogEnricherOptions.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/ProcessLogEnricherOptions.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/ProcessLogEnricherOptions.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/StaticProcessLogEnricher.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/StaticProcessLogEnricher.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Enrichment/StaticProcessLogEnricher.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Enrichment/StaticProcessLogEnricher.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Http/HttpRouteFormatter.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Http/HttpRouteFormatter.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Http/HttpRouteFormatter.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Http/HttpRouteFormatter.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Http/HttpRouteParameter.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Http/HttpRouteParameter.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Http/HttpRouteParameter.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Http/HttpRouteParameter.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Http/HttpRouteParser.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Http/HttpRouteParser.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Http/HttpRouteParser.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Http/HttpRouteParser.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Http/IHttpRouteFormatter.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Http/IHttpRouteFormatter.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Http/IHttpRouteFormatter.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Http/IHttpRouteFormatter.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Http/IHttpRouteParser.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Http/IHttpRouteParser.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Http/IHttpRouteParser.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Http/IHttpRouteParser.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Http/ParsedRouteSegments.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Http/ParsedRouteSegments.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Http/ParsedRouteSegments.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Http/ParsedRouteSegments.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Http/Segment.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Http/Segment.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Http/Segment.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Http/Segment.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Http/TelemetryCommonExtensions.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Http/TelemetryCommonExtensions.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Http/TelemetryCommonExtensions.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Http/TelemetryCommonExtensions.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/CheckpointTracker.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/CheckpointTracker.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/CheckpointTracker.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/CheckpointTracker.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/LatencyConsoleExporter.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/LatencyConsoleExporter.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/LatencyConsoleExporter.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/LatencyConsoleExporter.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/LatencyContext.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/LatencyContext.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/LatencyContext.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/LatencyContext.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/LatencyContextPool.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/LatencyContextPool.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/LatencyContextPool.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/LatencyContextPool.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/LatencyContextProvider.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/LatencyContextProvider.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/LatencyContextProvider.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/LatencyContextProvider.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/LatencyContextRegistrySet.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/LatencyContextRegistrySet.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/LatencyContextRegistrySet.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/LatencyContextRegistrySet.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/LatencyContextTokenIssuer.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/LatencyContextTokenIssuer.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/LatencyContextTokenIssuer.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/LatencyContextTokenIssuer.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/LatencyInstrumentProvider.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/LatencyInstrumentProvider.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/LatencyInstrumentProvider.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/LatencyInstrumentProvider.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/MeasureTracker.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/MeasureTracker.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/MeasureTracker.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/MeasureTracker.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/Registry.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/Registry.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/Registry.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/Registry.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/ResetOnGetObjectPool.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/ResetOnGetObjectPool.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/ResetOnGetObjectPool.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/ResetOnGetObjectPool.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/TagCollection.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/TagCollection.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/Internal/TagCollection.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Latency/Internal/TagCollection.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/LatencyConsoleExtensions.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Latency/LatencyConsoleExtensions.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/LatencyConsoleExtensions.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Latency/LatencyConsoleExtensions.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/LatencyConsoleOptions.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Latency/LatencyConsoleOptions.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/LatencyConsoleOptions.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Latency/LatencyConsoleOptions.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/LatencyContextExtensions.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Latency/LatencyContextExtensions.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/LatencyContextExtensions.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Latency/LatencyContextExtensions.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/LatencyContextOptions.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Latency/LatencyContextOptions.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Latency/LatencyContextOptions.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Latency/LatencyContextOptions.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/ExtendedLogger.EnrichmentTagCollector.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLogger.EnrichmentTagCollector.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/ExtendedLogger.EnrichmentTagCollector.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLogger.EnrichmentTagCollector.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/ExtendedLogger.LegacyTagJoiner.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLogger.LegacyTagJoiner.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/ExtendedLogger.LegacyTagJoiner.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLogger.LegacyTagJoiner.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/ExtendedLogger.ModernTagJoiner.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLogger.ModernTagJoiner.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/ExtendedLogger.ModernTagJoiner.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLogger.ModernTagJoiner.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/ExtendedLogger.Scope.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLogger.Scope.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/ExtendedLogger.Scope.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLogger.Scope.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/ExtendedLogger.ThreadLocals.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLogger.ThreadLocals.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/ExtendedLogger.ThreadLocals.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLogger.ThreadLocals.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/ExtendedLogger.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLogger.cs similarity index 89% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/ExtendedLogger.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLogger.cs index 02711d97be7..693637781c8 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/ExtendedLogger.cs +++ b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLogger.cs @@ -12,7 +12,7 @@ namespace Microsoft.Extensions.Logging; #pragma warning disable CA1031 // NOTE: This implementation uses thread local storage. As a result, it will fail if formatter code, enricher code, or -// redsctor code calls recursively back into the logger. Don't do that. +// redactor code calls recursively back into the logger. Don't do that. // // NOTE: Unlike the original logger in dotnet/runtime, this logger eats exceptions thrown from invoked loggers, enrichers, // and redactors, rather than forwarding the exceptions to the caller. The fact an exception occured is recorded in @@ -21,7 +21,9 @@ namespace Microsoft.Extensions.Logging; internal sealed partial class ExtendedLogger : ILogger { - private const string ExceptionStackTrace = "stackTrace"; + private const string ExceptionType = "exception.type"; + private const string ExceptionMessage = "exception.message"; + private const string ExceptionStackTrace = "exception.stacktrace"; private readonly ExtendedLoggerFactory _factory; @@ -127,6 +129,21 @@ private static void HandleExceptions(IEnumerable? exceptions) } } + private static void RecordException(Exception exception, EnrichmentTagCollector tags, LoggerConfig config) + { + tags.Add(ExceptionType, exception.GetType().ToString()); + + if (config.IncludeExceptionMessage) + { + tags.Add(ExceptionMessage, exception.Message); + } + + if (config.CaptureStackTraces) + { + tags.Add(ExceptionStackTrace, GetExceptionStackTrace(exception, config)); + } + } + private static string GetExceptionStackTrace(Exception exception, LoggerConfig config) { const int IndentAmount = 3; @@ -166,7 +183,7 @@ void HandleException(Exception exception, int indent) _ = sb.Append(exception.GetType()); _ = sb.Append(": "); - if (config.IncludeExceptionMessageInStackTraces) + if (config.IncludeExceptionMessage) { _ = sb.AppendLine(exception.Message); _ = sb.Append(indentStr); @@ -200,9 +217,11 @@ private void ModernPath(LogLevel logLevel, EventId eventId, LoggerMessageState m ref var cp = ref msgState.ClassifiedTagArray[i]; if (cp.Value != null) { - var jr = JustInTimeRedactor.Get(); - jr.Value = cp.Value; - jr.Redactor = config.GetRedactor(cp.Classification); + var jr = JustInTimeRedactor.Get( + cp.Value, + config.GetRedactor(cp.Classifications), + config.AddRedactionDiscriminator ? cp.Name : string.Empty); + jr.Next = jitRedactors; jitRedactors = jr; @@ -237,9 +256,9 @@ private void ModernPath(LogLevel logLevel, EventId eventId, LoggerMessageState m } // one last dedicated bit of enrichment - if (exception != null && config.CaptureStackTraces) + if (exception != null) { - joiner.EnrichmentTagCollector.Add(ExceptionStackTrace, GetExceptionStackTrace(exception, config)); + RecordException(exception, joiner.EnrichmentTagCollector, config); } for (int i = 0; i < loggers.Length; i++) @@ -321,9 +340,9 @@ private void LegacyPath(LogLevel logLevel, EventId eventId, TState state } // one last dedicated bit of enrichment - if (exception != null && config.CaptureStackTraces) + if (exception != null) { - joiner.EnrichmentTagCollector.Add(ExceptionStackTrace, GetExceptionStackTrace(exception, config)); + RecordException(exception, joiner.EnrichmentTagCollector, config); } for (int i = 0; i < loggers.Length; i++) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/ExtendedLoggerFactory.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLoggerFactory.cs similarity index 85% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/ExtendedLoggerFactory.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLoggerFactory.cs index 340262ca414..4f3a0525aae 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/ExtendedLoggerFactory.cs +++ b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLoggerFactory.cs @@ -21,9 +21,10 @@ internal sealed class ExtendedLoggerFactory : ILoggerFactory private readonly IDisposable? _filterOptionsChangeTokenRegistration; private readonly LoggerFactoryOptions _factoryOptions; private readonly IDisposable? _enrichmentOptionsChangeTokenRegistration; + private readonly IDisposable? _redactionOptionsChangeTokenRegistration; private readonly Action[] _enrichers; private readonly KeyValuePair[] _staticTags; - private readonly Func _redactorProvider; + private readonly Func _redactorProvider; private volatile bool _disposed; private LoggerFilterOptions _filterOptions; private IExternalScopeProvider? _scopeProvider; @@ -63,7 +64,8 @@ public ExtendedLoggerFactory( RefreshFilters(filterOptions.CurrentValue); _enrichers = enrichers.Select>(e => e.Enrich).ToArray(); - _enrichmentOptionsChangeTokenRegistration = enrichmentOptions?.OnChange(UpdateStackTraceOptions); + _enrichmentOptionsChangeTokenRegistration = enrichmentOptions?.OnChange(UpdateEnrichmentOptions); + _redactionOptionsChangeTokenRegistration = redactionOptions?.OnChange(UpdateRedactionOptions); var provider = redactionOptions != null && redactorProvider != null ? redactorProvider @@ -77,8 +79,8 @@ public ExtendedLoggerFactory( enricher.Enrich(collector); } - _staticTags = tags.ToArray(); - Config = ComputeConfig(enrichmentOptions?.CurrentValue); + _staticTags = [.. tags]; + Config = ComputeConfig(enrichmentOptions?.CurrentValue, redactionOptions?.CurrentValue); } public void Dispose() @@ -89,6 +91,7 @@ public void Dispose() _filterOptionsChangeTokenRegistration?.Dispose(); _enrichmentOptionsChangeTokenRegistration?.Dispose(); + _redactionOptionsChangeTokenRegistration?.Dispose(); foreach (ProviderRegistration registration in _providerRegistrations) { @@ -248,20 +251,39 @@ private LoggerInformation[] CreateLoggers(string categoryName) /// internal LoggerConfig Config { get; private set; } - private LoggerConfig ComputeConfig(LoggerEnrichmentOptions? enrichmentOptions) + private LoggerConfig ComputeConfig(LoggerEnrichmentOptions? enrichmentOptions, LoggerRedactionOptions? redactionOptions) { - return enrichmentOptions == null - ? new(Array.Empty>(), Array.Empty>(), false, false, false, 0, _redactorProvider) - : new(_staticTags, + if (enrichmentOptions == null) + { + enrichmentOptions = new LoggerEnrichmentOptions + { + CaptureStackTraces = Config.CaptureStackTraces, + UseFileInfoForStackTraces = Config.UseFileInfoForStackTraces, + IncludeExceptionMessage = Config.IncludeExceptionMessage, + MaxStackTraceLength = Config.MaxStackTraceLength, + }; + } + + if (redactionOptions == null) + { + redactionOptions = new LoggerRedactionOptions + { + ApplyDiscriminator = Config.AddRedactionDiscriminator, + }; + } + + return new(_staticTags, _enrichers, enrichmentOptions.CaptureStackTraces, enrichmentOptions.UseFileInfoForStackTraces, - enrichmentOptions.IncludeExceptionMessageInStackTraces, + enrichmentOptions.IncludeExceptionMessage, enrichmentOptions.MaxStackTraceLength, - _redactorProvider); + _redactorProvider, + redactionOptions.ApplyDiscriminator); } - private void UpdateStackTraceOptions(LoggerEnrichmentOptions enrichmentOptions) => Config = ComputeConfig(enrichmentOptions); + private void UpdateEnrichmentOptions(LoggerEnrichmentOptions enrichmentOptions) => Config = ComputeConfig(enrichmentOptions, null); + private void UpdateRedactionOptions(LoggerRedactionOptions redactionOptions) => Config = ComputeConfig(null, redactionOptions); private struct ProviderRegistration { diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/Import/LoggerFactoryScopeProvider.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/Import/LoggerFactoryScopeProvider.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/Import/LoggerFactoryScopeProvider.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/Import/LoggerFactoryScopeProvider.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/Import/LoggerInformation.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/Import/LoggerInformation.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/Import/LoggerInformation.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/Import/LoggerInformation.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/Import/LoggerRuleSelector.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/Import/LoggerRuleSelector.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/Import/LoggerRuleSelector.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/Import/LoggerRuleSelector.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/Import/NullScope.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/Import/NullScope.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/Import/NullScope.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/Import/NullScope.cs diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/Import/ProviderAliasUtilities.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/Import/ProviderAliasUtilities.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/Import/ProviderAliasUtilities.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/Import/ProviderAliasUtilities.cs diff --git a/src/Libraries/Microsoft.Extensions.Telemetry/Logging/JustInTimeRedactor.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/JustInTimeRedactor.cs new file mode 100644 index 00000000000..175848399d3 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/JustInTimeRedactor.cs @@ -0,0 +1,184 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers; +using System.Globalization; +using System.Runtime.CompilerServices; +using Microsoft.Extensions.Compliance.Redaction; +using Microsoft.Extensions.ObjectPool; +using Microsoft.Shared.Pools; + +namespace Microsoft.Extensions.Logging; + +/// +/// Performs delayed redaction in order to avoid allocating intermediate strings and enabling redacting from span to span when possible. +/// +internal sealed class JustInTimeRedactor : IResettable +#if NET6_0_OR_GREATER + , ISpanFormattable +#else + , IFormattable +#endif +{ + private const int MaxStackAllocation = 256; + private const int MaxDayOfYearLength = 3; + private const string DiscriminatorSeparator = ":"; + + private Redactor? _redactor; + private string _discriminator = string.Empty; + private object? _value; + + public static JustInTimeRedactor Get(object? value, Redactor redactor, string discriminator) + { + var jr = _pool.Get(); + + jr._value = value; + jr._redactor = redactor; + jr._discriminator = discriminator; + + return jr; + } + + public void Return() => _pool.Return(this); + + public JustInTimeRedactor? Next { get; set; } + + public override string ToString() => ToString(null, CultureInfo.InvariantCulture); + + [SkipLocalsInit] + public string ToString(string? format, IFormatProvider? formatProvider) + { + if (_discriminator.Length == 0) + { + return _redactor!.Redact(_value, format, formatProvider); + } + + _ = TryRedactWithDiscriminator(_value, [], out int _, format.AsSpan(), formatProvider, out var result, generateResult: true); + return result; + } + + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) + { + if (_discriminator.Length == 0) + { + return _redactor!.TryRedact(_value, destination, out charsWritten, format, provider); + } + + return TryRedactWithDiscriminator(_value, destination, out charsWritten, format, provider, out string _, generateResult: false); + } + + bool IResettable.TryReset() + { + _value = null; + _redactor = null; + _discriminator = string.Empty; + return true; + } + + [SkipLocalsInit] + private bool TryRedactWithDiscriminator(object? value, Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? formatProvider, + out string result, bool generateResult) + { + Span workBuffer = stackalloc char[MaxStackAllocation]; + int charsInWorkBuffer = 0; + char[]? a = null; + + // first step is to acquire the actual text to be redacted + +#if NET6_0_OR_GREATER + bool done = false; + if (value is ISpanFormattable sf) + { + done = sf.TryFormat(workBuffer, out charsInWorkBuffer, format, formatProvider); + } + + if (done) + { + int maxLenToRedact = charsInWorkBuffer + DiscriminatorSeparator.Length + _discriminator.Length + MaxDayOfYearLength; + if (maxLenToRedact > workBuffer.Length) + { + a = ArrayPool.Shared.Rent(maxLenToRedact); + workBuffer.Slice(0, charsInWorkBuffer).CopyTo(a); + workBuffer = a; + } + } + else +#endif +#pragma warning disable S1199 + { +#pragma warning restore S1199 + ReadOnlySpan inputAsSpan = default; + if (value is IFormattable f) + { + inputAsSpan = f.ToString(format.Length > 0 ? format.ToString() : string.Empty, formatProvider).AsSpan(); + } + else if (value is char[] c) + { + // An attempt to call value.ToString() on a char[] will produce the string "System.Char[]" and redaction will be attempted on it, + // instead of the provided array. + inputAsSpan = c.AsSpan(); + } + else + { + var str = value?.ToString() ?? string.Empty; + inputAsSpan = str.AsSpan(); + } + + int maxLenToRedact = inputAsSpan.Length + DiscriminatorSeparator.Length + _discriminator.Length + MaxDayOfYearLength; + if (maxLenToRedact > workBuffer.Length) + { + a = ArrayPool.Shared.Rent(maxLenToRedact); + workBuffer = a; + } + + inputAsSpan.CopyTo(workBuffer); + charsInWorkBuffer = inputAsSpan.Length; + } + + // next step is to add the discriminator + + DiscriminatorSeparator.AsSpan().CopyTo(workBuffer.Slice(charsInWorkBuffer)); + charsInWorkBuffer += DiscriminatorSeparator.Length; + + _discriminator.AsSpan().CopyTo(workBuffer.Slice(charsInWorkBuffer)); + charsInWorkBuffer += _discriminator.Length; + + // final step is to invoke the actual redactor + + var finalToBeRedacted = workBuffer.Slice(0, charsInWorkBuffer); + result = string.Empty; + + int redactedLen = _redactor!.GetRedactedLength(finalToBeRedacted); + if (redactedLen > destination.Length) + { + if (generateResult) + { + var t = ArrayPool.Shared.Rent(redactedLen); + _redactor!.Redact(finalToBeRedacted, t); + result = t.AsSpan().Slice(0, redactedLen).ToString(); + ArrayPool.Shared.Return(t); + } + + charsWritten = 0; + + if (a != null) + { + ArrayPool.Shared.Return(a); + } + + return false; + } + + charsWritten = _redactor!.Redact(finalToBeRedacted, destination); + + if (a != null) + { + ArrayPool.Shared.Return(a); + } + + return true; + } + + private static readonly ObjectPool _pool = PoolFactory.CreateResettingPool(); +} diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggerConfig.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggerConfig.cs similarity index 67% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggerConfig.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggerConfig.cs index 785d08cc9ee..34716ca4e38 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggerConfig.cs +++ b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggerConfig.cs @@ -11,29 +11,34 @@ namespace Microsoft.Extensions.Logging; internal sealed class LoggerConfig { +#pragma warning disable S107 // Methods should not have too many parameters public LoggerConfig( KeyValuePair[] staticTags, Action[] enrichers, bool captureStackTraces, bool useFileInfoForStackTraces, - bool includeExceptionMessagesInStackTraces, + bool includeExceptionMessage, int maxStackTraceLength, - Func getRedactor) + Func getRedactor, + bool addRedactionDiscriminator) { +#pragma warning restore S107 // Methods should not have too many parameters StaticTags = staticTags; Enrichers = enrichers; CaptureStackTraces = captureStackTraces; UseFileInfoForStackTraces = useFileInfoForStackTraces; MaxStackTraceLength = maxStackTraceLength; - IncludeExceptionMessageInStackTraces = includeExceptionMessagesInStackTraces; + IncludeExceptionMessage = includeExceptionMessage; GetRedactor = getRedactor; + AddRedactionDiscriminator = addRedactionDiscriminator; } public KeyValuePair[] StaticTags { get; } public Action[] Enrichers { get; } public bool CaptureStackTraces { get; } public bool UseFileInfoForStackTraces { get; } - public bool IncludeExceptionMessageInStackTraces { get; } + public bool IncludeExceptionMessage { get; } public int MaxStackTraceLength { get; } - public Func GetRedactor { get; } + public Func GetRedactor { get; } + public bool AddRedactionDiscriminator { get; } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggerEnrichmentOptions.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggerEnrichmentOptions.cs similarity index 88% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggerEnrichmentOptions.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggerEnrichmentOptions.cs index d064f2d54fb..59aba9ac803 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggerEnrichmentOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggerEnrichmentOptions.cs @@ -42,15 +42,16 @@ public class LoggerEnrichmentOptions public bool UseFileInfoForStackTraces { get; set; } /// - /// Gets or sets a value indicating whether to include exception messages in stack traces. + /// Gets or sets a value indicating whether to include the exception message when an exception is logged. /// /// /// This defaults to . /// /// - /// This property has no effect if is . + /// When both and are set to + /// exception messages are also included in stack traces. /// - public bool IncludeExceptionMessageInStackTraces { get; set; } + public bool IncludeExceptionMessage { get; set; } /// /// Gets or sets the maximum stack trace length to emit for a given log record. diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggerEnrichmentOptionsValidator.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggerEnrichmentOptionsValidator.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggerEnrichmentOptionsValidator.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggerEnrichmentOptionsValidator.cs diff --git a/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggerRedactionOptions.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggerRedactionOptions.cs new file mode 100644 index 00000000000..7f9a7274621 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggerRedactionOptions.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Extensions.Logging; + +/// +/// Options to control redaction. +/// +public class LoggerRedactionOptions +{ + /// + /// Gets or sets a value indicating whether a discriminator value is applied to each redacted value. + /// + /// + /// Defaults to . + /// + /// + /// The discriminator is used to constrain the ability to correlate data between pieces of state. When this property is , + /// the tag name of each value is included as part of the redacted text, generally making it impossible to correlate data between + /// tags of different names. + /// + public bool ApplyDiscriminator { get; set; } = true; +} diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggingEnrichmentExtensions.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggingEnrichmentExtensions.cs similarity index 97% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggingEnrichmentExtensions.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggingEnrichmentExtensions.cs index 4d1e98fdba2..1d4262b454e 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggingEnrichmentExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggingEnrichmentExtensions.cs @@ -32,6 +32,7 @@ public static ILoggingBuilder EnableEnrichment(this ILoggingBuilder builder) public static ILoggingBuilder EnableEnrichment(this ILoggingBuilder builder, Action configure) { _ = Throw.IfNull(builder); + _ = Throw.IfNull(configure); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); _ = builder.Services.Configure(configure); @@ -49,6 +50,7 @@ public static ILoggingBuilder EnableEnrichment(this ILoggingBuilder builder, Act public static ILoggingBuilder EnableEnrichment(this ILoggingBuilder builder, IConfigurationSection section) { _ = Throw.IfNull(builder); + _ = Throw.IfNull(section); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); _ = builder.Services.AddOptionsWithValidateOnStart().Bind(section); diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggingEventSource.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggingEventSource.cs similarity index 100% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Logging/LoggingEventSource.cs rename to src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggingEventSource.cs diff --git a/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggingRedactionExtensions.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggingRedactionExtensions.cs new file mode 100644 index 00000000000..4ec3ea9ef3e --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggingRedactionExtensions.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Extensions.Logging; + +/// +/// Extensions for configuring logging redaction features. +/// +public static class LoggingRedactionExtensions +{ + /// + /// Enables redaction functionality within the logging infrastructure. + /// + /// The dependency injection container to add logging to. + /// The value of . + public static ILoggingBuilder EnableRedaction(this ILoggingBuilder builder) + => EnableRedaction(builder, _ => { }); + + /// + /// Enables redaction functionality within the logging infrastructure. + /// + /// The dependency injection container to add logging to. + /// Delegate the fine-tune the options. + /// The value of . + public static ILoggingBuilder EnableRedaction(this ILoggingBuilder builder, Action configure) + { + _ = Throw.IfNull(builder); + _ = Throw.IfNull(configure); + + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); + _ = builder.Services.Configure(configure); + + return builder; + } + + /// + /// Enables redaction functionality within the logging infrastructure. + /// + /// The dependency injection container to add logging to. + /// Configuration section that contains . + /// The value of . + public static ILoggingBuilder EnableRedaction(this ILoggingBuilder builder, IConfigurationSection section) + { + _ = Throw.IfNull(builder); + _ = Throw.IfNull(section); + + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); + _ = builder.Services.AddOptions().Bind(section); + + return builder; + } +} diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Microsoft.Extensions.Diagnostics.Extra.csproj b/src/Libraries/Microsoft.Extensions.Telemetry/Microsoft.Extensions.Telemetry.csproj similarity index 88% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Microsoft.Extensions.Diagnostics.Extra.csproj rename to src/Libraries/Microsoft.Extensions.Telemetry/Microsoft.Extensions.Telemetry.csproj index 6b0995e613a..bebeba6fb9a 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Microsoft.Extensions.Diagnostics.Extra.csproj +++ b/src/Libraries/Microsoft.Extensions.Telemetry/Microsoft.Extensions.Telemetry.csproj @@ -6,6 +6,7 @@ + true true true true @@ -18,7 +19,7 @@ normal - 92 + 93 90 @@ -26,7 +27,7 @@ - + @@ -42,7 +43,7 @@ - + diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Microsoft.Extensions.Diagnostics.Extra.json b/src/Libraries/Microsoft.Extensions.Telemetry/Microsoft.Extensions.Telemetry.json similarity index 89% rename from src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Microsoft.Extensions.Diagnostics.Extra.json rename to src/Libraries/Microsoft.Extensions.Telemetry/Microsoft.Extensions.Telemetry.json index 5366a7f1e29..89b08ce9676 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.Extra/Microsoft.Extensions.Diagnostics.Extra.json +++ b/src/Libraries/Microsoft.Extensions.Telemetry/Microsoft.Extensions.Telemetry.json @@ -1,5 +1,5 @@ { - "Name": "Microsoft.Extensions.Diagnostics.Extra, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", + "Name": "Microsoft.Extensions.Telemetry, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", "Types": [ { "Type": "static class Microsoft.Extensions.DependencyInjection.ApplicationEnricherServiceCollectionExtensions", @@ -26,22 +26,22 @@ { "Member": "const string Microsoft.Extensions.Diagnostics.Enrichment.ApplicationEnricherTags.ApplicationName", "Stage": "Stable", - "Value": "env_app_name" + "Value": "service.name" }, { "Member": "const string Microsoft.Extensions.Diagnostics.Enrichment.ApplicationEnricherTags.BuildVersion", "Stage": "Stable", - "Value": "env_cloud_roleVer" + "Value": "service.version" }, { "Member": "const string Microsoft.Extensions.Diagnostics.Enrichment.ApplicationEnricherTags.DeploymentRing", "Stage": "Stable", - "Value": "env_cloud_deploymentRing" + "Value": "DeploymentRing" }, { "Member": "const string Microsoft.Extensions.Diagnostics.Enrichment.ApplicationEnricherTags.EnvironmentName", "Stage": "Stable", - "Value": "env_cloud_env" + "Value": "deployment.environment" } ], "Properties": [ @@ -170,7 +170,7 @@ "Stage": "Stable" }, { - "Member": "bool Microsoft.Extensions.Logging.LoggerEnrichmentOptions.IncludeExceptionMessageInStackTraces { get; set; }", + "Member": "bool Microsoft.Extensions.Logging.LoggerEnrichmentOptions.IncludeExceptionMessage { get; set; }", "Stage": "Stable" }, { @@ -183,6 +183,22 @@ } ] }, + { + "Type": "class Microsoft.Extensions.Logging.LoggerRedactionOptions", + "Stage": "Stable", + "Methods": [ + { + "Member": "Microsoft.Extensions.Logging.LoggerRedactionOptions.LoggerRedactionOptions();", + "Stage": "Stable" + } + ], + "Properties": [ + { + "Member": "bool Microsoft.Extensions.Logging.LoggerRedactionOptions.ApplyDiscriminator { get; set; }", + "Stage": "Stable" + } + ] + }, { "Type": "static class Microsoft.Extensions.Logging.LoggingEnrichmentExtensions", "Stage": "Stable", @@ -208,6 +224,14 @@ { "Member": "static Microsoft.Extensions.Logging.ILoggingBuilder Microsoft.Extensions.Logging.LoggingRedactionExtensions.EnableRedaction(this Microsoft.Extensions.Logging.ILoggingBuilder builder);", "Stage": "Stable" + }, + { + "Member": "static Microsoft.Extensions.Logging.ILoggingBuilder Microsoft.Extensions.Logging.LoggingRedactionExtensions.EnableRedaction(this Microsoft.Extensions.Logging.ILoggingBuilder builder, System.Action configure);", + "Stage": "Stable" + }, + { + "Member": "static Microsoft.Extensions.Logging.ILoggingBuilder Microsoft.Extensions.Logging.LoggingRedactionExtensions.EnableRedaction(this Microsoft.Extensions.Logging.ILoggingBuilder builder, Microsoft.Extensions.Configuration.IConfigurationSection section);", + "Stage": "Stable" } ] }, @@ -236,12 +260,12 @@ { "Member": "const string Microsoft.Extensions.Diagnostics.Enrichment.ProcessEnricherTagNames.ProcessId", "Stage": "Stable", - "Value": "pid" + "Value": "process.pid" }, { "Member": "const string Microsoft.Extensions.Diagnostics.Enrichment.ProcessEnricherTagNames.ThreadId", "Stage": "Stable", - "Value": "tid" + "Value": "thread.id" } ], "Properties": [ diff --git a/src/Libraries/Microsoft.Extensions.Telemetry/README.md b/src/Libraries/Microsoft.Extensions.Telemetry/README.md new file mode 100644 index 00000000000..79a8a93e1cf --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Telemetry/README.md @@ -0,0 +1,108 @@ +# Microsoft.Extensions.Telemetry + +This library provides advanced logging and telemetry enrichment capabilities for .NET applications. It allows for detailed and configurable enrichment of log entries, along with enhanced latency monitoring and logging features. It is built for applications needing sophisticated telemetry and logging insights. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.Telemetry +``` + +Or directly in the C# project file: + +```xml + + + +``` + +## Usage + +### Service Log Enrichment + +Enriches logs with application-specific information based on `ApplicationMetadata` information. The bellow calls will add the service log enricher to the service collection. + +```csharp +// Add service log enricher with default settings +builder.Services.AddServiceLogEnricher(); + +// Or configure with options +builder.Services.AddServiceLogEnricher(options => +{ + options.ApplicationName = true; + options.BuildVersion = true; + options.DeploymentRing = true; + options.EnvironmentName = true; +}); +``` + +### Latency Monitoring + +Provides tools for latency data collection and export. The bellow example uses the built-in Console exporter, but custom exporters can be created by implementing the `ILatencyDataExporter` interface. + +```csharp +// Add latency console data exporter with configuration +builder.Services.AddConsoleLatencyDataExporter(options => +{ + options.OutputCheckpoints = true; + options.OutputMeasures = true; + options.OutputTags = true; +}); +``` + +In order for the latency data to be exported, a call to `ILatencyDataExporter.ExportAsync()` is required. This can either be called manually, or by using the Request Latency Middleware inside the `Microsoft.AspNetCore.Diagnostics.Middleware` package by adding: + +```csharp +// Add Latency Context +builder.Services.AddLatencyContext(); + +// Add Checkpoints, Measures, Tags +builder.Services.RegisterCheckpointNames("databaseQuery", "externalApiCall"); +builder.Services.RegisterMeasureNames("responseTime", "processingTime"); +builder.Services.RegisterTagNames("userId", "transactionId"); + +// Add Console Latency exporter. +builder.Services.AddConsoleLatencyDataExporter(); +// Optionally add custom exporters. +builder.Services.AddSingleton(); + +// Add Request latency telemetry. +builder.Services.AddRequestLatencyTelemetry(); + +// ... + +// Add Request Latency Middleware which will automatically call ExportAsync on all registered latency exporters. +app.UseRequestLatencyTelemetry(); +``` + +### Logging Enhancements + +Offers additional logging capabilities like stack trace capturing, exception message inclusion, and log redaction. + +```csharp +// Enable log enrichment. +builder.EnableEnrichment(options => +{ + options.CaptureStackTraces = true; + options.IncludeExceptionMessage = true; + options.MaxStackTraceLength = 500; + options.UseFileInfoForStackTraces = true; +}); + +builder.Services.AddServiceLogEnricher(); // <- This call is required in order for the enricher to be added into the service collection. + +// Enable log redaction +builder.EnableRedaction(options => +{ + options.ApplyDiscriminator = true; +}); + +builder.Services.AddRedaction(); // <- This call is required in order for the redactor provider to be added into the service collection. + +``` + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Packages/Microsoft.Extensions.AuditReports/README.md b/src/Packages/Microsoft.Extensions.AuditReports/README.md index e845566c06f..e9468508593 100644 --- a/src/Packages/Microsoft.Extensions.AuditReports/README.md +++ b/src/Packages/Microsoft.Extensions.AuditReports/README.md @@ -1 +1,24 @@ -README +# Microsoft.Extensions.AuditReports + +Produces reports about the code being compiled which are useful during privacy and telemetry audits. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.AuditReports +``` + +Or directly in the C# project file: + +```xml + + + +``` + + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Packages/Microsoft.Extensions.ExtraAnalyzers/EmptyInternalClass.cs b/src/Packages/Microsoft.Extensions.ExtraAnalyzers/EmptyInternalClass.cs deleted file mode 100644 index b9dab3ab9d5..00000000000 --- a/src/Packages/Microsoft.Extensions.ExtraAnalyzers/EmptyInternalClass.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -internal sealed class EmptyInternalClass -{ - // This class is a place - // holder so we can build a package without additional warnings. -} diff --git a/src/Packages/Microsoft.Extensions.ExtraAnalyzers/Microsoft.Extensions.ExtraAnalyzers.csproj b/src/Packages/Microsoft.Extensions.ExtraAnalyzers/Microsoft.Extensions.ExtraAnalyzers.csproj deleted file mode 100644 index 1eeed07c2bf..00000000000 --- a/src/Packages/Microsoft.Extensions.ExtraAnalyzers/Microsoft.Extensions.ExtraAnalyzers.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - Code analyzers and fixers - Fundamentals - Static Analysis - - - - n/a - true - true - false - true - - - - normal - n/a - n/a - - - - - - - - - - diff --git a/src/Packages/Microsoft.Extensions.ExtraAnalyzers/README.md b/src/Packages/Microsoft.Extensions.ExtraAnalyzers/README.md deleted file mode 100644 index e845566c06f..00000000000 --- a/src/Packages/Microsoft.Extensions.ExtraAnalyzers/README.md +++ /dev/null @@ -1 +0,0 @@ -README diff --git a/src/Packages/Microsoft.Extensions.ExtraAnalyzers/buildTransitive/Microsoft.Extensions.ExtraAnalyzers.props b/src/Packages/Microsoft.Extensions.ExtraAnalyzers/buildTransitive/Microsoft.Extensions.ExtraAnalyzers.props deleted file mode 100644 index 0782155be3c..00000000000 --- a/src/Packages/Microsoft.Extensions.ExtraAnalyzers/buildTransitive/Microsoft.Extensions.ExtraAnalyzers.props +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/src/Packages/Microsoft.Extensions.ExtraAnalyzers/buildTransitive/Microsoft.Extensions.ExtraAnalyzers.targets b/src/Packages/Microsoft.Extensions.ExtraAnalyzers/buildTransitive/Microsoft.Extensions.ExtraAnalyzers.targets deleted file mode 100644 index 8c119d5413b..00000000000 --- a/src/Packages/Microsoft.Extensions.ExtraAnalyzers/buildTransitive/Microsoft.Extensions.ExtraAnalyzers.targets +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/Microsoft.Extensions.StaticAnalysis.csproj b/src/Packages/Microsoft.Extensions.StaticAnalysis/Microsoft.Extensions.StaticAnalysis.csproj index f6e1daf13cb..1577599d25c 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/Microsoft.Extensions.StaticAnalysis.csproj +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/Microsoft.Extensions.StaticAnalysis.csproj @@ -1,7 +1,7 @@  Microsoft.Extensions.StaticAnalysis - Curated set of code analyzers and code analyzer settings. + A curated set of code analyzers and code analyzer settings. Fundamentals Static Analysis @@ -26,10 +26,6 @@ - - - - diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/README.md b/src/Packages/Microsoft.Extensions.StaticAnalysis/README.md index e845566c06f..7c0c0b79caf 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/README.md +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/README.md @@ -1 +1,52 @@ -README +# Microsoft.Extensions.StaticAnalysis + +A curated set of code analyzers and code analyzer settings. + +## Install the package + +From the command-line: + +```dotnetcli +dotnet add package Microsoft.Extensions.StaticAnalysis +``` + +Or directly in the C# project file: + +```xml + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + +``` + +## Usage Example + +On install, a warning will be displayed that `The StaticAnalysisCodeType property is not defined, assuming 'General'`. The General set of diagnostics is enabled by default. To select a different set of diagnostics (or hide the warning) add the `StaticAnalysisCodeType` property to your project as follows. + +```XML + + General + +``` + +## Available Sets Of Diagnostics + +Different pre-defined sets of diagnostics are available depending on the type of project being built. These can be specified in the StaticAnalysisCodeType property: +- Benchmark: Projects used for benchmarking. +- General: Any type of project. +- NonProdExe: Projects that produce an exe for non-production use. +- NonProdLib: Projects that produce a library (dll) for non-production use. +- ProdExe: Projects that produce an exe for production use. +- ProdLib: Projects that produce a library (dll) for production use. +- Test: Projects used for testing. + +Each of these also has an optional `-Tier1` and a `-Tier2` variant (e.g. `General-Tier1`). +- `Tier1` enables only the most important diagnostics from this set. +- `Tier2` includes Tier1 diagnostics and others that aren't as critical. +- The names without `Tier` suffixes include all diagnostics from tier's 1 and 2, and any others that are applicable. + +## Feedback & Contributing + +We welcome feedback and contributions in [our GitHub repo](https://github.com/dotnet/extensions). diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/Microsoft.Extensions.StaticAnalysis.props b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/Microsoft.Extensions.StaticAnalysis.props index 2a0a63b9d32..3afd1acd541 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/Microsoft.Extensions.StaticAnalysis.props +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/Microsoft.Extensions.StaticAnalysis.props @@ -39,6 +39,6 @@ - + diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Benchmark-Tier1.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Benchmark-Tier1.globalconfig index 02d2c77d2cd..e03e875307b 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Benchmark-Tier1.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Benchmark-Tier1.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:57Z +# Generated : 2023-10-22 00:37:51Z # Max Tier : 1 # Attributes: general, performance # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -845,7 +845,7 @@ dotnet_diagnostic.CA1860.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = warning @@ -880,6 +880,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = warning + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = warning + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1693,62 +1703,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = warning # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = warning # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = warning # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = warning # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = warning # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = warning # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = warning # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = warning # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Benchmark-Tier2.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Benchmark-Tier2.globalconfig index 5bbeb8f2d61..0dcd9c2e177 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Benchmark-Tier2.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Benchmark-Tier2.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 18:00:00Z +# Generated : 2023-10-22 00:37:55Z # Max Tier : 2 # Attributes: general, performance # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -857,7 +857,7 @@ dotnet_diagnostic.CA1860.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = warning @@ -892,6 +892,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = warning + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = warning + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1706,62 +1716,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = warning # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = warning # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = warning # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = warning # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = warning # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = warning # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = warning # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = warning # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Benchmark.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Benchmark.globalconfig index 25aa43548e3..a5f42c1d9b6 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Benchmark.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Benchmark.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 18:00:03Z +# Generated : 2023-10-22 00:37:58Z # Max Tier : 3 # Attributes: general, performance # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -868,7 +868,7 @@ dotnet_diagnostic.CA1860.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = warning @@ -903,6 +903,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = warning + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = warning + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1720,62 +1730,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = warning # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = warning # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = warning # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = warning # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = warning # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = warning # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = warning # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = warning # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/General-Tier1.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/General-Tier1.globalconfig index 4e356778751..03ffd63fd58 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/General-Tier1.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/General-Tier1.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:55Z +# Generated : 2023-10-22 00:37:49Z # Max Tier : 1 # Attributes: general # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -845,7 +845,7 @@ dotnet_diagnostic.CA1860.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = none @@ -880,6 +880,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = suggestion +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = none + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = none + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1693,62 +1703,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = none # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = none # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = none # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = none # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = none # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = none # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = none # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = none # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/General-Tier2.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/General-Tier2.globalconfig index 968f7486e87..1746e78a5a8 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/General-Tier2.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/General-Tier2.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:58Z +# Generated : 2023-10-22 00:37:52Z # Max Tier : 2 # Attributes: general # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -857,7 +857,7 @@ dotnet_diagnostic.CA1860.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = none @@ -892,6 +892,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = suggestion +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = none + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = none + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1706,62 +1716,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = none # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = none # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = none # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = none # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = none # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = none # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = none # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = none # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/General.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/General.globalconfig index 178caa96120..3bb10ed18e3 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/General.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/General.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 18:00:01Z +# Generated : 2023-10-22 00:37:56Z # Max Tier : 3 # Attributes: general # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -866,7 +866,7 @@ dotnet_diagnostic.CA1860.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = none @@ -901,6 +901,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = suggestion +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = none + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = none + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1718,62 +1728,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = none # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = none # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = none # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = none # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = none # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = none # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = none # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = none # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdExe-Tier1.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdExe-Tier1.globalconfig index a0b2414f06b..027b5368f1a 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdExe-Tier1.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdExe-Tier1.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:56Z +# Generated : 2023-10-22 00:37:51Z # Max Tier : 1 # Attributes: general # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -845,7 +845,7 @@ dotnet_diagnostic.CA1860.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = none @@ -880,6 +880,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = suggestion +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = none + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = none + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1693,62 +1703,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = none # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = none # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = none # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = none # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = none # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = none # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = none # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = none # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdExe-Tier2.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdExe-Tier2.globalconfig index 6585728bcd9..753d221bfed 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdExe-Tier2.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdExe-Tier2.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:59Z +# Generated : 2023-10-22 00:37:54Z # Max Tier : 2 # Attributes: general # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -857,7 +857,7 @@ dotnet_diagnostic.CA1860.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = none @@ -892,6 +892,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = suggestion +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = none + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = none + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1706,62 +1716,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = none # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = none # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = none # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = none # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = none # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = none # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = none # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = none # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdExe.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdExe.globalconfig index 7ae55d22c4d..3b10e62f523 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdExe.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdExe.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 18:00:02Z +# Generated : 2023-10-22 00:37:57Z # Max Tier : 3 # Attributes: general # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -866,7 +866,7 @@ dotnet_diagnostic.CA1860.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = none @@ -901,6 +901,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = suggestion +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = none + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = none + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1718,62 +1728,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = none # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = none # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = none # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = none # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = none # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = none # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = none # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = none # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdLib-Tier1.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdLib-Tier1.globalconfig index 9cbbbf453e6..28971d58b7e 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdLib-Tier1.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdLib-Tier1.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:57Z +# Generated : 2023-10-22 00:37:51Z # Max Tier : 1 # Attributes: api, general # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -845,7 +845,7 @@ dotnet_diagnostic.CA1860.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = none @@ -880,6 +880,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = suggestion +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = none + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = none + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1693,62 +1703,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = none # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = none # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = none # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = none # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = none # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = none # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = none # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = none # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdLib-Tier2.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdLib-Tier2.globalconfig index 1d30b171ac0..d1124e7f0c7 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdLib-Tier2.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdLib-Tier2.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 18:00:00Z +# Generated : 2023-10-22 00:37:54Z # Max Tier : 2 # Attributes: api, general # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -865,7 +865,7 @@ dotnet_diagnostic.CA1860.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = none @@ -900,6 +900,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = suggestion +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = none + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = none + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1716,62 +1726,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = none # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = none # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = none # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = none # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = none # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = none # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = none # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = none # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdLib.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdLib.globalconfig index f46a61748bc..3f0b7039479 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdLib.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/NonProdLib.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 18:00:03Z +# Generated : 2023-10-22 00:37:58Z # Max Tier : 3 # Attributes: api, general # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -889,7 +889,7 @@ dotnet_diagnostic.CA1860.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = none @@ -924,6 +924,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = suggestion +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = none + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = none + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1743,62 +1753,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = none # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = none # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = none # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = none # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = none # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = none # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = none # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = none # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdExe-Tier1.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdExe-Tier1.globalconfig index 04addb3b03a..16079cf2e72 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdExe-Tier1.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdExe-Tier1.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:55Z +# Generated : 2023-10-22 00:37:49Z # Max Tier : 1 # Attributes: general, performance, production # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -845,7 +845,7 @@ dotnet_diagnostic.CA1860.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = warning @@ -880,6 +880,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = warning + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = warning + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1693,62 +1703,77 @@ dotnet_diagnostic.CA5405.severity = warning # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = warning # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = warning # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = warning # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = warning # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = warning # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = warning # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = warning # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = warning # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = warning # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = warning # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdExe-Tier2.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdExe-Tier2.globalconfig index 73d00708e8b..1713acc92c4 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdExe-Tier2.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdExe-Tier2.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:58Z +# Generated : 2023-10-22 00:37:53Z # Max Tier : 2 # Attributes: general, performance, production # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -858,7 +858,7 @@ dotnet_diagnostic.CA1860.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = warning @@ -893,6 +893,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = warning + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = warning + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1707,62 +1717,77 @@ dotnet_diagnostic.CA5405.severity = warning # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = warning # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = warning # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = warning # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = warning # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = warning # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = warning # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = warning # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = warning # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = warning # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = warning # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdExe.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdExe.globalconfig index 09f9a5c5d7f..cbebc09695b 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdExe.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdExe.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 18:00:02Z +# Generated : 2023-10-22 00:37:56Z # Max Tier : 3 # Attributes: general, performance, production # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -869,7 +869,7 @@ dotnet_diagnostic.CA1860.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = warning @@ -904,6 +904,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = warning + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = warning + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1721,62 +1731,77 @@ dotnet_diagnostic.CA5405.severity = warning # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = warning # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = warning # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = warning # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = warning # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = warning # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = warning # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = warning # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = warning # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = warning # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = warning # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdLib-Tier1.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdLib-Tier1.globalconfig index 61e0d68d5a4..effc6fe04f4 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdLib-Tier1.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdLib-Tier1.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:56Z +# Generated : 2023-10-22 00:37:50Z # Max Tier : 1 # Attributes: api, general, performance, production # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -845,7 +845,7 @@ dotnet_diagnostic.CA1860.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = warning @@ -880,6 +880,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = warning + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = warning + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1693,62 +1703,77 @@ dotnet_diagnostic.CA5405.severity = warning # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = warning # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = warning # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = warning # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = warning # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = warning # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = warning # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = warning # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = warning # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = warning # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = warning # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdLib-Tier2.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdLib-Tier2.globalconfig index d9fab7efe87..68c1bef7ee8 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdLib-Tier2.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdLib-Tier2.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:59Z +# Generated : 2023-10-22 00:37:53Z # Max Tier : 2 # Attributes: api, general, performance, production # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -865,7 +865,7 @@ dotnet_diagnostic.CA1860.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = warning @@ -900,6 +900,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = warning + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = warning + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1716,62 +1726,77 @@ dotnet_diagnostic.CA5405.severity = warning # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = warning # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = warning # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = warning # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = warning # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = warning # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = warning # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = warning # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = warning # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = warning # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = warning # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdLib.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdLib.globalconfig index c9baa40edb3..233c544c10b 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdLib.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/ProdLib.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 18:00:02Z +# Generated : 2023-10-22 00:37:57Z # Max Tier : 3 # Attributes: api, general, performance, production # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp @@ -889,7 +889,7 @@ dotnet_diagnostic.CA1860.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = warning @@ -924,6 +924,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = warning + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = warning + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1743,62 +1753,77 @@ dotnet_diagnostic.CA5405.severity = warning # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = warning # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = warning # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = warning # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = warning # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = warning # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = warning # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = warning # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = warning # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = warning # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = warning # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Test-Tier1.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Test-Tier1.globalconfig index 270925f2358..0f48469ea6a 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Test-Tier1.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Test-Tier1.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:58Z +# Generated : 2023-10-22 00:37:52Z # Max Tier : 1 # Attributes: general, test # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp, xunit.analyzers @@ -845,7 +845,7 @@ dotnet_diagnostic.CA1860.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = none @@ -880,6 +880,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = suggestion +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = none + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = none + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1693,62 +1703,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = none # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = none # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = none # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = none # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = none # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = none # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = none # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = none # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Test-Tier2.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Test-Tier2.globalconfig index 2f718a5050a..8aee2179d9c 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Test-Tier2.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Test-Tier2.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 18:00:01Z +# Generated : 2023-10-22 00:37:55Z # Max Tier : 2 # Attributes: general, test # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp, xunit.analyzers @@ -857,7 +857,7 @@ dotnet_diagnostic.CA1860.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = none @@ -892,6 +892,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = suggestion +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = none + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = none + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1706,62 +1716,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = none # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = none # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = none # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = none # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = none # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = none # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = none # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = none # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Test.globalconfig b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Test.globalconfig index 3fdc2ccce2a..9a708301a99 100644 --- a/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Test.globalconfig +++ b/src/Packages/Microsoft.Extensions.StaticAnalysis/build/config/Test.globalconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 18:00:04Z +# Generated : 2023-10-22 00:37:59Z # Max Tier : 3 # Attributes: general, test # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp, xunit.analyzers @@ -866,7 +866,7 @@ dotnet_diagnostic.CA1860.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = none @@ -901,6 +901,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = suggestion +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = none + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = none + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1718,62 +1728,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = none # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = none # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = none # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = none # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = none # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = none # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = none # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = none # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code diff --git a/src/Shared/.editorconfig b/src/Shared/.editorconfig index 3c94c92dbfd..b0dfb5bdc6c 100644 --- a/src/Shared/.editorconfig +++ b/src/Shared/.editorconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:53Z +# Generated : 2023-10-22 00:37:47Z # Max Tier : 2147483647 # Attributes: general, performance, production # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CodeStyle, Microsoft.CodeAnalysis.CSharp.CodeStyle, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp, StyleCop.Analyzers @@ -869,7 +869,7 @@ dotnet_diagnostic.CA1860.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = warning @@ -904,6 +904,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = warning + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = warning + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1721,62 +1731,77 @@ dotnet_diagnostic.CA5405.severity = warning # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = warning # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = warning # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = warning # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = warning # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = warning # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = warning # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = warning # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = warning # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = warning # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = warning # Title : Set MSBuild property 'GenerateDocumentationFile' to 'true' @@ -2410,6 +2435,26 @@ dotnet_diagnostic.IDE0300.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0301 dotnet_diagnostic.IDE0301.severity = suggestion +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0302 +dotnet_diagnostic.IDE0302.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0303 +dotnet_diagnostic.IDE0303.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0304 +dotnet_diagnostic.IDE0304.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0305 +dotnet_diagnostic.IDE0305.severity = none + # Title : Delegate invocation can be simplified. # Category : Style # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1005 diff --git a/src/Shared/Data.Validation/ExclusiveRangeAttribute.cs b/src/Shared/Data.Validation/ExclusiveRangeAttribute.cs deleted file mode 100644 index faab259c09a..00000000000 --- a/src/Shared/Data.Validation/ExclusiveRangeAttribute.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.ComponentModel.DataAnnotations; -using Microsoft.Shared.Diagnostics; - -#pragma warning disable CA1716 -namespace Microsoft.Shared.Data.Validation; -#pragma warning restore CA1716 - -/// -/// Provides exclusive boundary validation for or values. -/// -#if !SHARED_PROJECT -[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] -#endif - -[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] -internal sealed class ExclusiveRangeAttribute : ValidationAttribute -{ - /// - /// Gets the minimum value for the range. - /// - public object Minimum { get; } - - /// - /// Gets the maximum value for the range. - /// - public object Maximum { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The minimum value, exclusive. - /// The maximum value, exclusive. - public ExclusiveRangeAttribute(int minimum, int maximum) - { - Minimum = minimum; - Maximum = maximum; - } - - /// - /// Initializes a new instance of the class. - /// - /// The minimum value, exclusive. - /// The maximum value, exclusive. - public ExclusiveRangeAttribute(double minimum, double maximum) - { - Minimum = minimum; - Maximum = maximum; - } - - /// - /// Validates that a given value is in range. - /// - /// The value to validate. - /// Additional context for this validation. - /// A value indicating success or failure. - protected override ValidationResult IsValid(object? value, ValidationContext? validationContext) - { - var comparableMin = Minimum as IComparable; - var comparableMax = Maximum as IComparable; - - // Minimum and Maximum are either of type int or double, so there is no need for - // nullability check here (or later) as both types are IComparable already. - if (comparableMin!.CompareTo(Maximum) >= 0) - { - Throw.InvalidOperationException($"{nameof(ExclusiveRangeAttribute)} requires the minimum to be less than the maximum (see field {validationContext.GetDisplayName()})"); - } - - if (value == null) - { - // use the [Required] attribute to force presence - return ValidationResult.Success!; - } - - var result = comparableMin!.CompareTo(value) < 0 && comparableMax!.CompareTo(value) > 0; - - if (!result) - { - return new ValidationResult($"The field {validationContext.GetDisplayName()} must be > {Minimum} and < {Maximum}.", validationContext.GetMemberName()); - } - - return ValidationResult.Success!; - } -} diff --git a/src/Shared/DiagnosticIds/DiagnosticIds.cs b/src/Shared/DiagnosticIds/DiagnosticIds.cs new file mode 100644 index 00000000000..dbc82065488 --- /dev/null +++ b/src/Shared/DiagnosticIds/DiagnosticIds.cs @@ -0,0 +1,142 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma warning disable S1144 // Remove the unused internal class + +#pragma warning disable CA1716 +namespace Microsoft.Shared.DiagnosticIds; +#pragma warning restore CA1716 + +/// +/// Various diagnostic IDs reported by this repo. +/// +/// +/// When adding a new diagnostic ID, add a corresponding suppression to the root Directory.Build.targets file, +/// and add a documentation entry to docs/list-of-diagnostics.md. +/// +internal static class DiagnosticIds +{ +#pragma warning disable S1075 // URIs should not be hardcoded + internal const string UrlFormat = "https://aka.ms/dotnet-extensions-warnings/{0}"; +#pragma warning restore S1075 // URIs should not be hardcoded + + internal static class ContextualOptions + { + internal const string CTXOPTGEN000 = nameof(CTXOPTGEN000); + internal const string CTXOPTGEN001 = nameof(CTXOPTGEN001); + internal const string CTXOPTGEN002 = nameof(CTXOPTGEN002); + internal const string CTXOPTGEN003 = nameof(CTXOPTGEN003); + } + + internal static class Design + { + internal const string AUTOCLIENTGEN001 = nameof(AUTOCLIENTGEN001); + internal const string AUTOCLIENTGEN002 = nameof(AUTOCLIENTGEN002); + internal const string AUTOCLIENTGEN003 = nameof(AUTOCLIENTGEN003); + internal const string AUTOCLIENTGEN004 = nameof(AUTOCLIENTGEN004); + internal const string AUTOCLIENTGEN005 = nameof(AUTOCLIENTGEN005); + internal const string AUTOCLIENTGEN006 = nameof(AUTOCLIENTGEN006); + internal const string AUTOCLIENTGEN007 = nameof(AUTOCLIENTGEN007); + internal const string AUTOCLIENTGEN008 = nameof(AUTOCLIENTGEN008); + internal const string AUTOCLIENTGEN009 = nameof(AUTOCLIENTGEN009); + internal const string AUTOCLIENTGEN010 = nameof(AUTOCLIENTGEN010); + internal const string AUTOCLIENTGEN011 = nameof(AUTOCLIENTGEN011); + internal const string AUTOCLIENTGEN012 = nameof(AUTOCLIENTGEN012); + internal const string AUTOCLIENTGEN013 = nameof(AUTOCLIENTGEN013); + internal const string AUTOCLIENTGEN014 = nameof(AUTOCLIENTGEN014); + internal const string AUTOCLIENTGEN015 = nameof(AUTOCLIENTGEN015); + internal const string AUTOCLIENTGEN016 = nameof(AUTOCLIENTGEN016); + internal const string AUTOCLIENTGEN017 = nameof(AUTOCLIENTGEN017); + internal const string AUTOCLIENTGEN018 = nameof(AUTOCLIENTGEN018); + internal const string AUTOCLIENTGEN019 = nameof(AUTOCLIENTGEN019); + internal const string AUTOCLIENTGEN020 = nameof(AUTOCLIENTGEN020); + internal const string AUTOCLIENTGEN021 = nameof(AUTOCLIENTGEN021); + internal const string AUTOCLIENTGEN022 = nameof(AUTOCLIENTGEN022); + } + + /// + /// Experiments supported by this repo. + /// + internal static class Experiments + { + internal const string Resilience = "EXTEXP0001"; + internal const string Compliance = "EXTEXP0002"; + internal const string Telemetry = "EXTEXP0003"; + internal const string TimeProvider = "EXTEXP0004"; + internal const string AutoClient = "EXTEXP0005"; + internal const string AsyncState = "EXTEXP0006"; + internal const string HealthChecks = "EXTEXP0007"; + internal const string ResourceMonitoring = "EXTEXP0008"; + internal const string Hosting = "EXTEXP0009"; + internal const string ObjectPool = "EXTEXP0010"; + internal const string DocumentDb = "EXTEXP0011"; + internal const string AutoActivation = "EXTEXP0012"; + internal const string HttpLogging = "EXTEXP0013"; + } + + internal static class LoggerMessage + { + internal const string LOGGEN000 = nameof(LOGGEN000); + internal const string LOGGEN001 = nameof(LOGGEN001); + internal const string LOGGEN002 = nameof(LOGGEN002); + internal const string LOGGEN003 = nameof(LOGGEN003); + internal const string LOGGEN004 = nameof(LOGGEN004); + internal const string LOGGEN005 = nameof(LOGGEN005); + internal const string LOGGEN006 = nameof(LOGGEN006); + internal const string LOGGEN007 = nameof(LOGGEN007); + internal const string LOGGEN008 = nameof(LOGGEN008); + internal const string LOGGEN009 = nameof(LOGGEN009); + internal const string LOGGEN010 = nameof(LOGGEN010); + internal const string LOGGEN011 = nameof(LOGGEN011); + internal const string LOGGEN012 = nameof(LOGGEN012); + internal const string LOGGEN013 = nameof(LOGGEN013); + internal const string LOGGEN014 = nameof(LOGGEN014); + internal const string LOGGEN015 = nameof(LOGGEN015); + internal const string LOGGEN016 = nameof(LOGGEN016); + internal const string LOGGEN017 = nameof(LOGGEN017); + internal const string LOGGEN018 = nameof(LOGGEN018); + internal const string LOGGEN019 = nameof(LOGGEN019); + internal const string LOGGEN020 = nameof(LOGGEN020); + internal const string LOGGEN021 = nameof(LOGGEN021); + internal const string LOGGEN022 = nameof(LOGGEN022); + internal const string LOGGEN023 = nameof(LOGGEN023); + internal const string LOGGEN024 = nameof(LOGGEN024); + internal const string LOGGEN025 = nameof(LOGGEN025); + internal const string LOGGEN026 = nameof(LOGGEN026); + internal const string LOGGEN027 = nameof(LOGGEN027); + internal const string LOGGEN028 = nameof(LOGGEN028); + internal const string LOGGEN029 = nameof(LOGGEN029); + internal const string LOGGEN030 = nameof(LOGGEN030); + internal const string LOGGEN031 = nameof(LOGGEN031); + internal const string LOGGEN032 = nameof(LOGGEN032); + internal const string LOGGEN033 = nameof(LOGGEN033); + internal const string LOGGEN034 = nameof(LOGGEN034); + internal const string LOGGEN035 = nameof(LOGGEN035); + } + + internal static class Metrics + { + internal const string METGEN000 = nameof(METGEN000); + internal const string METGEN001 = nameof(METGEN001); + internal const string METGEN002 = nameof(METGEN002); + internal const string METGEN003 = nameof(METGEN003); + internal const string METGEN004 = nameof(METGEN004); + internal const string METGEN005 = nameof(METGEN005); + internal const string METGEN006 = nameof(METGEN006); + internal const string METGEN007 = nameof(METGEN007); + internal const string METGEN008 = nameof(METGEN008); + internal const string METGEN009 = nameof(METGEN009); + internal const string METGEN010 = nameof(METGEN010); + internal const string METGEN011 = nameof(METGEN011); + internal const string METGEN012 = nameof(METGEN012); + internal const string METGEN013 = nameof(METGEN013); + internal const string METGEN014 = nameof(METGEN014); + internal const string METGEN015 = nameof(METGEN015); + internal const string METGEN016 = nameof(METGEN016); + internal const string METGEN017 = nameof(METGEN017); + internal const string METGEN018 = nameof(METGEN018); + internal const string METGEN019 = nameof(METGEN019); + } +} + +#pragma warning restore S1144 diff --git a/src/Shared/DiagnosticIds/Experiments.cs b/src/Shared/DiagnosticIds/Experiments.cs deleted file mode 100644 index 43deb56273c..00000000000 --- a/src/Shared/DiagnosticIds/Experiments.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#pragma warning disable CA1716 -namespace Microsoft.Shared.DiagnosticIds; -#pragma warning restore CA1716 - -/// -/// Experiments supported by this repo. -/// -/// -/// When adding a new experiment, add a corresponding suppression to the root Directory.Build.targets file, and add a documentation entry to -/// docs/list-of-diagnostics.md. -/// -internal static class Experiments -{ -#pragma warning disable S1075 // URIs should not be hardcoded - internal const string UrlFormat = "https://aka.ms/dotnet-extensions-warnings/{0}"; -#pragma warning restore S1075 // URIs should not be hardcoded - - internal const string Resilience = "EXTEXP0001"; - internal const string Compliance = "EXTEXP0002"; - internal const string Telemetry = "EXTEXP0003"; - internal const string TimeProvider = "EXTEXP0004"; - internal const string AutoClient = "EXTEXP0005"; - internal const string AsyncState = "EXTEXP0006"; - internal const string HealthChecks = "EXTEXP0007"; - internal const string ResourceMonitoring = "EXTEXP0008"; - internal const string Hosting = "EXTEXP0009"; - internal const string ObjectPool = "EXTEXP0010"; - internal const string DocumentDb = "EXTEXP0011"; - internal const string AutoActivation = "EXTEXP0012"; - internal const string HttpLogging = "EXTEXP0013"; -} diff --git a/src/Shared/DiagnosticIds/README.md b/src/Shared/DiagnosticIds/README.md index a9485983c3e..a445571bba4 100644 --- a/src/Shared/DiagnosticIds/README.md +++ b/src/Shared/DiagnosticIds/README.md @@ -1,6 +1,6 @@ -# Buffer Writer Pool +# Diagnostic IDs -Gives access to diagnostics ids for the [Experimental] attribute. +Defines various diagnostic IDs reported by this repo. To use this in your project, add the following to your `.csproj` file: diff --git a/test/.editorconfig b/test/.editorconfig index 7036f224f95..2368ab81caa 100644 --- a/test/.editorconfig +++ b/test/.editorconfig @@ -1,5 +1,5 @@ # Created by DiagConfig, the diagnostic config generator -# Generated : 2023-08-07 17:59:54Z +# Generated : 2023-10-22 00:37:48Z # Max Tier : 2147483647 # Attributes: general, test # Analyzers : ILLink.RoslynAnalyzer, Microsoft.Analyzers.Extra, Microsoft.Analyzers.Local, Microsoft.AspNetCore.App.Analyzers, Microsoft.AspNetCore.Components.Analyzers, Microsoft.CodeAnalysis.CodeStyle, Microsoft.CodeAnalysis.CSharp.CodeStyle, Microsoft.CodeAnalysis.CSharp.NetAnalyzers, Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.VisualStudio.Threading.Analyzers, Microsoft.VisualStudio.Threading.Analyzers.CSharp, SonarAnalyzer.CSharp, StyleCop.Analyzers, xunit.analyzers @@ -866,7 +866,7 @@ dotnet_diagnostic.CA1860.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861 dotnet_diagnostic.CA1861.severity = suggestion -# Title : Prefer using 'StringComparer' to perform case-insensitive string comparisons +# Title : Use the 'StringComparison' method overloads to perform case-insensitive string comparisons # Category : Performance # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862 dotnet_diagnostic.CA1862.severity = none @@ -901,6 +901,16 @@ dotnet_diagnostic.CA1867.severity = warning # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = suggestion +# Title : Cache and reuse 'JsonSerializerOptions' instances +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1869 +dotnet_diagnostic.CA1869.severity = none + +# Title : Use a cached 'SearchValues' instance +# Category : Performance +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1870 +dotnet_diagnostic.CA1870.severity = none + # Title : Dispose objects before losing scope # Category : Reliability # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000 @@ -1718,62 +1728,77 @@ dotnet_diagnostic.CA5405.severity = none # Title : Use source generated logging methods for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0000 dotnet_diagnostic.EA0000.severity = none # Title : Perform message formatting in the body of the logging method # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0001 dotnet_diagnostic.EA0001.severity = none # Title : Use 'System.TimeProvider' to make the code easier to test # Category : Reliability +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0002 dotnet_diagnostic.EA0002.severity = none # Title : Use the character-based overloads of 'String.StartsWith' or 'String.EndsWith' # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0003 dotnet_diagnostic.EA0003.severity = none # Title : Make types declared in an executable internal # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0004 dotnet_diagnostic.EA0004.severity = none # Title : Consider using an array instead of a collection # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0005 dotnet_diagnostic.EA0005.severity = none # Title : Replace uses of 'Enum.GetName' and 'Enum.ToString' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0006 dotnet_diagnostic.EA0006.severity = none # Title : Use 'System.ValueTuple' instead of 'System.Tuple' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0007 dotnet_diagnostic.EA0007.severity = none # Title : Use generic collections instead of legacy collections for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0008 dotnet_diagnostic.EA0008.severity = none # Title : Use 'System.MemoryExtensions.Split' for improved performance # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0009 dotnet_diagnostic.EA0009.severity = none # Title : Fire-and-forget async call inside a 'using' block # Category : Correctness +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0010 dotnet_diagnostic.EA0010.severity = warning # Title : Consider removing unnecessary conditional access operator (?) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0011 dotnet_diagnostic.EA0011.severity = warning # Title : Consider removing unnecessary null coalescing assignment (??=) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0012 dotnet_diagnostic.EA0012.severity = suggestion # Title : Consider removing unnecessary null coalescing operator (??) # Category : Performance +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0013 dotnet_diagnostic.EA0013.severity = suggestion # Title : The async method doesn't support cancellation # Category : Resilience +# Help Link: https://aka.ms/dotnet-extensions-warnings/EA0014 dotnet_diagnostic.EA0014.severity = none # Title : Set MSBuild property 'GenerateDocumentationFile' to 'true' @@ -2406,6 +2431,26 @@ dotnet_diagnostic.IDE0300.severity = none # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0301 dotnet_diagnostic.IDE0301.severity = suggestion +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0302 +dotnet_diagnostic.IDE0302.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0303 +dotnet_diagnostic.IDE0303.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0304 +dotnet_diagnostic.IDE0304.severity = none + +# Title : Simplify collection initialization +# Category : Style +# Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0305 +dotnet_diagnostic.IDE0305.severity = none + # Title : Delegate invocation can be simplified. # Category : Style # Help Link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1005 diff --git a/test/Analyzers/Microsoft.Analyzers.Extra.Tests/CallAnalysis/EnumStringsTests.cs b/test/Analyzers/Microsoft.Analyzers.Extra.Tests/CallAnalysis/EnumStringsTests.cs deleted file mode 100644 index 55642872be3..00000000000 --- a/test/Analyzers/Microsoft.Analyzers.Extra.Tests/CallAnalysis/EnumStringsTests.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading.Tasks; -using Microsoft.Extensions.ExtraAnalyzers.Test; -using Xunit; - -namespace Microsoft.Extensions.ExtraAnalyzers.CallAnalysis.Test; - -public static class EnumStringsTests -{ - [Fact] - public static async Task EnumStrings() - { - const string Source = @" - using System; - - namespace Example - { - public class TestClass - { - public enum Color { Red, Green, Blue } - - private void Foo() - { - var c = Color.Red; - _ = /*0+*/Enum.GetName(typeof(Color), c)/*-0*/; - _ = /*1+*/Enum.GetName(c)/*-1*/; - _ = /*2+*/c.ToString()/*-2*/; - _ = /*3+*/Color.Red.ToString()/*-3*/; - } - } - }"; - - var d = await RoslynTestUtils.RunAnalyzer( - new CallAnalyzer(), - null, - new[] { Source }).ConfigureAwait(false); - - Assert.Equal(4, d.Count); - for (int i = 0; i < d.Count; i++) - { - Source.AssertDiagnostic(i, DiagDescriptors.EnumStrings, d[i]); - } - } -} diff --git a/test/Analyzers/Microsoft.Analyzers.Extra.Tests/CallAnalysis/LegacyLoggingTests.cs b/test/Analyzers/Microsoft.Analyzers.Extra.Tests/CallAnalysis/LegacyLoggingTests.cs index 4f817a235cf..afe68ce5685 100644 --- a/test/Analyzers/Microsoft.Analyzers.Extra.Tests/CallAnalysis/LegacyLoggingTests.cs +++ b/test/Analyzers/Microsoft.Analyzers.Extra.Tests/CallAnalysis/LegacyLoggingTests.cs @@ -450,6 +450,176 @@ static partial class Log Assert.Equal(ExpectedTarget.Replace("\r\n", "\n", StringComparison.Ordinal), actualTarget); } + [Fact] + public static async Task TargetClassHasMethodsWithoutEventId() + { + const string OriginalTarget = @" + namespace Example + { + public static partial class Log + { + [Microsoft.Extensions.Logging.LoggerMessage(Microsoft.Extensions.Logging.LogLevel.Debug, message: ""Test"")] + internal static partial void WithoutEvent(Microsoft.Extensions.Logging.ILogger logger); + } + }"; + + const string OriginalSource = @" + using Microsoft.Extensions.Logging; + + namespace Example + { + public class TestClass + { + private ILogger _logger; + + public TestClass(ILogger logger) + { + _logger = logger; + } + + public void Test() + { + _logger.LogTrace(""Hello!""); + } + } + }"; + + const string ExpectedSource = @" + using Microsoft.Extensions.Logging; + + namespace Example + { + public class TestClass + { + private ILogger _logger; + + public TestClass(ILogger logger) + { + _logger = logger; + } + + public void Test() + { + _logger.Hello(); + } + } + }"; + + const string ExpectedTarget = @" + namespace Example + { + public static partial class Log + { + [Microsoft.Extensions.Logging.LoggerMessage(Microsoft.Extensions.Logging.LogLevel.Debug, message: ""Test"")] + internal static partial void WithoutEvent(Microsoft.Extensions.Logging.ILogger logger); + + [Microsoft.Extensions.Logging.LoggerMessage(0, Microsoft.Extensions.Logging.LogLevel.Trace, ""Hello!"")] + internal static partial void Hello(this Microsoft.Extensions.Logging.ILogger logger); + } + }"; + + var l = await RoslynTestUtils.RunAnalyzerAndFixer( + new CallAnalyzer(), + new LegacyLoggingFixer(), + new[] { Assembly.GetAssembly(typeof(ILogger))!, Assembly.GetAssembly(typeof(LoggerMessageAttribute))! }, + new[] { OriginalSource, OriginalTarget }, + defaultNamespace: "Example").ConfigureAwait(false); + + var actualSource = l[0]; + var actualTarget = l[1]; + + Assert.Equal(ExpectedSource.Replace("\r\n", "\n", StringComparison.Ordinal), actualSource); + Assert.Equal(ExpectedTarget.Replace("\r\n", "\n", StringComparison.Ordinal), actualTarget); + } + + [Fact] + public static async Task TargetClassHasMethodsWithAndWithoutEventId() + { + const string OriginalTarget = @" + namespace Example + { + public static partial class Log + { + [Microsoft.Extensions.Logging.LoggerMessage(message: ""Test"")] + internal static partial void WithoutEvent(Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.LogLevel level); + + [Microsoft.Extensions.Logging.LoggerMessage(1, Microsoft.Extensions.Logging.LogLevel.Debug, ""Test"")] + internal static partial void WithEvent(Microsoft.Extensions.Logging.ILogger logger, string param1); + } + }"; + + const string OriginalSource = @" + using Microsoft.Extensions.Logging; + + namespace Example + { + public class TestClass + { + private ILogger _logger; + + public TestClass(ILogger logger) + { + _logger = logger; + } + + public void Test() + { + _logger.LogTrace(""Hello!""); + } + } + }"; + + const string ExpectedSource = @" + using Microsoft.Extensions.Logging; + + namespace Example + { + public class TestClass + { + private ILogger _logger; + + public TestClass(ILogger logger) + { + _logger = logger; + } + + public void Test() + { + _logger.Hello(); + } + } + }"; + + const string ExpectedTarget = @" + namespace Example + { + public static partial class Log + { + [Microsoft.Extensions.Logging.LoggerMessage(message: ""Test"")] + internal static partial void WithoutEvent(Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.LogLevel level); + + [Microsoft.Extensions.Logging.LoggerMessage(1, Microsoft.Extensions.Logging.LogLevel.Debug, ""Test"")] + internal static partial void WithEvent(Microsoft.Extensions.Logging.ILogger logger, string param1); + + [Microsoft.Extensions.Logging.LoggerMessage(2, Microsoft.Extensions.Logging.LogLevel.Trace, ""Hello!"")] + internal static partial void Hello(this Microsoft.Extensions.Logging.ILogger logger); + } + }"; + + var l = await RoslynTestUtils.RunAnalyzerAndFixer( + new CallAnalyzer(), + new LegacyLoggingFixer(), + new[] { Assembly.GetAssembly(typeof(ILogger))!, Assembly.GetAssembly(typeof(LoggerMessageAttribute))! }, + new[] { OriginalSource, OriginalTarget }, + defaultNamespace: "Example").ConfigureAwait(false); + + var actualSource = l[0]; + var actualTarget = l[1]; + + Assert.Equal(ExpectedSource.Replace("\r\n", "\n", StringComparison.Ordinal), actualSource); + Assert.Equal(ExpectedTarget.Replace("\r\n", "\n", StringComparison.Ordinal), actualTarget); + } + [Fact] public static async Task DuplicateFilename() { diff --git a/test/Analyzers/Microsoft.Analyzers.Extra.Tests/Microsoft.Analyzers.Extra.Tests.csproj b/test/Analyzers/Microsoft.Analyzers.Extra.Tests/Microsoft.Analyzers.Extra.Tests.csproj index 6caf8cfdc3a..951cad02d05 100644 --- a/test/Analyzers/Microsoft.Analyzers.Extra.Tests/Microsoft.Analyzers.Extra.Tests.csproj +++ b/test/Analyzers/Microsoft.Analyzers.Extra.Tests/Microsoft.Analyzers.Extra.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/test/Analyzers/Microsoft.Analyzers.Local.Tests/Microsoft.Analyzers.Local.Tests.csproj b/test/Analyzers/Microsoft.Analyzers.Local.Tests/Microsoft.Analyzers.Local.Tests.csproj index c56ff97baf2..a149e1a5da6 100644 --- a/test/Analyzers/Microsoft.Analyzers.Local.Tests/Microsoft.Analyzers.Local.Tests.csproj +++ b/test/Analyzers/Microsoft.Analyzers.Local.Tests/Microsoft.Analyzers.Local.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/test/Generators/Microsoft.Gen.AutoClient/Generated/AutoClientOptionsTests.cs b/test/Generators/Microsoft.Gen.AutoClient/Generated/AutoClientOptionsTests.cs deleted file mode 100644 index 64bbc6d941e..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Generated/AutoClientOptionsTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Http.AutoClient; -using Microsoft.Extensions.Options; -using Moq; -using Moq.Protected; -using TestClasses; -using Xunit; - -namespace Microsoft.Gen.AutoClient.Test; - -[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "IDisposable inside mock setups")] -[System.Diagnostics.CodeAnalysis.SuppressMessage("Critical Code Smell", "S1067:Expressions should not be too complex", Justification = "Mock conditions")] -public class AutoClientOptionsTests -{ - private readonly Mock _handlerMock = new(MockBehavior.Strict); - private readonly Mock _factoryMock = new(MockBehavior.Strict); - - public AutoClientOptionsTests() - { - _factoryMock.Setup(m => m.CreateClient("MyClient")).Returns(new HttpClient(_handlerMock.Object) - { - BaseAddress = new Uri("https://example.com/") - }); - } - - [Fact] - public async Task JsonBodyUsesJsonSerializerOptions() - { - using var provider = new ServiceCollection() - .AddSingleton(_ => _factoryMock.Object) - .AddRestApiClientOptionsApi(options => - { - options.JsonSerializerOptions = new JsonSerializerOptions - { - DictionaryKeyPolicy = JsonNamingPolicy.CamelCase - }; - }) - .BuildServiceProvider(); - var sut = provider.GetRequiredService(); - - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Post && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/dict" && - message.Content!.Headers.ContentType!.ToString() == "application/json; charset=utf-8" && - message.Content!.ReadAsStringAsync().Result == @"{""myProperty"":""MyValue""}"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await sut.PostDictionary(new Dictionary - { - ["MyProperty"] = "MyValue" - }); - - Assert.Equal("Success!", response); - } - - [Fact] - public void GivenNullJsonSerializerOptions_Throws() - { - using var provider = new ServiceCollection() - .AddSingleton(_ => _factoryMock.Object) - .AddRestApiClientOptionsApi(options => options.JsonSerializerOptions = null!) - .BuildServiceProvider(); - - Assert.Throws(provider.GetRequiredService); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/Generated/BasicRequestsTests.cs b/test/Generators/Microsoft.Gen.AutoClient/Generated/BasicRequestsTests.cs deleted file mode 100644 index f5f08c3db2c..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Generated/BasicRequestsTests.cs +++ /dev/null @@ -1,268 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Http.AutoClient; -using Moq; -using Moq.Protected; -using TestClasses; -using Xunit; - -namespace Microsoft.Gen.AutoClient.Test; - -[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "IDisposable inside mock setups")] -public class BasicRequestsTests : IDisposable -{ - private readonly Mock _handlerMock = new(MockBehavior.Strict); - private readonly Mock _factoryMock = new(MockBehavior.Strict); - private readonly IBasicTestClient _sut; - private readonly ServiceProvider _provider; - - public BasicRequestsTests() - { - _factoryMock.Setup(m => m.CreateClient("MyClient")).Returns(new HttpClient(_handlerMock.Object) - { - BaseAddress = new Uri("https://example.com/") - }); - - var services = new ServiceCollection(); - services.AddSingleton(_ => _factoryMock.Object); - services.AddBasicTestClient(); - _provider = services.BuildServiceProvider(); - - _sut = _provider.GetRequiredService(); - } - - [Fact] - public async Task DeleteRequest() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Delete && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success DELETE!") - }); - - var response = await _sut.DeleteUsers(); - - Assert.Equal("Success DELETE!", response); - } - - [Fact] - public async Task GetRequest() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success GET!") - }); - - var response = await _sut.GetUsers(); - - Assert.Equal("Success GET!", response); - } - - [Fact] - public async Task GetRequestCancellationToken() - { - var ct = new CancellationToken(true); - - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success GET!") - }); - -#if NET5_0_OR_GREATER - await Assert.ThrowsAsync(() => _sut.GetUsersWithCancellationToken(ct)); -#else - await Assert.ThrowsAsync(() => _sut.GetUsersWithCancellationToken(ct)); -#endif - } - - [Fact] - public async Task HeadRequest() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Head && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success HEAD!") - }); - - var response = await _sut.HeadUsers(); - - Assert.Equal("Success HEAD!", response); - } - - [Fact] - public async Task OptionsRequest() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Options && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success OPTIONS!") - }); - - var response = await _sut.OptionsUsers(); - - Assert.Equal("Success OPTIONS!", response); - } - - [Fact] - public async Task PatchRequest() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == new HttpMethod("PATCH") && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success PATCH!") - }); - - var response = await _sut.PatchUsers(); - - Assert.Equal("Success PATCH!", response); - } - - [Fact] - public async Task PostRequest() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Post && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success POST!") - }); - - var response = await _sut.PostUsers(); - - Assert.Equal("Success POST!", response); - } - - [Fact] - public async Task PutRequest() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Put && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success PUT!") - }); - - var response = await _sut.PutUsers(); - - Assert.Equal("Success PUT!", response); - } - - [Fact] - public async Task UnsuccessfulResponse() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.Forbidden, - Content = new StringContent("Forbidden") - }); - - var ex = await Assert.ThrowsAsync(() => _sut.GetUsers()); - Assert.Equal(403, ex.StatusCode); - Assert.Equal("Forbidden", ex.HttpError!.RawContent); - Assert.Equal("/api/users", ex.Path); - } - - [Fact] - public async Task FullUrlUsesBaseAddress() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.ToString() == "https://example.com/api/users"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent("Ok") - }); - - var response = await _sut.GetUsers(); - - Assert.Equal("Ok", response); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _provider.Dispose(); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/Generated/BodyTests.cs b/test/Generators/Microsoft.Gen.AutoClient/Generated/BodyTests.cs deleted file mode 100644 index 1170148e6c1..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Generated/BodyTests.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Moq.Protected; -using TestClasses; -using Xunit; - -namespace Microsoft.Gen.AutoClient.Test; - -[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "IDisposable inside mock setups")] -[System.Diagnostics.CodeAnalysis.SuppressMessage("Critical Code Smell", "S1067:Expressions should not be too complex", Justification = "Mock conditions")] -public class BodyTests : IDisposable -{ - private readonly Mock _handlerMock = new(MockBehavior.Strict); - private readonly Mock _factoryMock = new(MockBehavior.Strict); - - private readonly IBodyTestClient _sut; - private readonly ServiceProvider _provider; - - public BodyTests() - { - _factoryMock.Setup(m => m.CreateClient("MyClient")).Returns(new HttpClient(_handlerMock.Object) - { - BaseAddress = new Uri("https://example.com/") - }); - - var services = new ServiceCollection(); - services.AddSingleton(_ => _factoryMock.Object); - services.AddBodyTestClient(); - _provider = services.BuildServiceProvider(); - - _sut = _provider.GetRequiredService(); - } - - [Fact] - public async Task JsonBody() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Post && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users" && - message.Content!.Headers.ContentType!.ToString() == "application/json; charset=utf-8" && - message.Content!.ReadAsStringAsync().Result == @"{""Value"":""MyBodyObjectValue""}"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sut.PostUsers(new IBodyTestClient.BodyObject()); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task TextBody() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Put && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users" && - message.Content!.Headers.ContentType!.ToString() == "text/plain; charset=utf-8" && - message.Content!.ReadAsStringAsync().Result == "MyBodyObjectValue"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sut.PutUsers(new IBodyTestClient.BodyObject()); - - Assert.Equal("Success!", response); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _provider.Dispose(); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/Generated/HeadersTests.cs b/test/Generators/Microsoft.Gen.AutoClient/Generated/HeadersTests.cs deleted file mode 100644 index ae920b11881..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Generated/HeadersTests.cs +++ /dev/null @@ -1,259 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Moq.Protected; -using TestClasses; -using Xunit; - -namespace Microsoft.Gen.AutoClient.Test; - -[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "IDisposable inside mock setups")] -[System.Diagnostics.CodeAnalysis.SuppressMessage("Critical Code Smell", "S1067:Expressions should not be too complex", Justification = "Mock conditions")] -public class HeadersTests : IDisposable -{ - private readonly Mock _handlerMock = new(MockBehavior.Strict); - private readonly Mock _factoryMock = new(MockBehavior.Strict); - - private readonly IStaticHeaderTestClient _sutStatic; - private readonly IParamHeaderTestClient _sutParam; - private readonly ServiceProvider _provider; - - public HeadersTests() - { - _factoryMock.Setup(m => m.CreateClient("MyClient")).Returns(new HttpClient(_handlerMock.Object) - { - BaseAddress = new Uri("https://example.com/") - }); - - var services = new ServiceCollection(); - services.AddSingleton(_ => _factoryMock.Object); - services.AddStaticHeaderTestClient(); - services.AddParamHeaderTestClient(); - _provider = services.BuildServiceProvider(); - - _sutStatic = _provider.GetRequiredService(); - _sutParam = _provider.GetRequiredService(); - } - - [Fact] - public async Task StaticHeader() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users" && - message.Headers.GetValues("X-MyHeader").First() == "MyValue"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sutStatic.GetUsers(); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task StaticHeaderMultipleInMethod() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users" && - message.Headers.GetValues("X-MyHeader").First() == "MyValue" && - message.Headers.GetValues("X-MyHeader1").First() == "MyValue" && - message.Headers.GetValues("X-MyHeader2").First() == "MyValue"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sutStatic.GetUsersHeaders(); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task StaticHeaderWithEscapedQuotes() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users" && - message.Headers.GetValues("X-MyHeader3").First() == "MyValueWith\"Escaped\"Stuff\t\b\u03a0"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sutStatic.GetUsersHeadersEscaped(); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task HeaderFromParameter() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users" && - message.Headers.GetValues("X-MyHeader").First() == "MyParamValue"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sutParam.GetUsers("MyParamValue"); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task HeaderFromValueTypeParameter() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users" && - message.Headers.GetValues("X-MyHeader").First() == 123.ToString()), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sutParam.GetUsersInt32(123); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task HeaderFromNullableParameter() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users" && - message.Headers.GetValues("X-MyHeader").First() == 123.ToString()), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sutParam.GetUsersNullableInt32(123); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task HeaderFromParameterNullIsExcluded() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users" && - !message.Headers.Contains("X-MyHeader")), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sutParam.GetUsers(null); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task HeaderFromNullableParameterNullIsExcluded() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users" && - !message.Headers.Contains("X-MyHeader")), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sutParam.GetUsersNullableInt32(null); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task HeaderFromParameterCustomObject() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users" && - message.Headers.GetValues("X-MyHeader").First() == "CustomObjectToString"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sutParam.GetUsersObject(new IParamHeaderTestClient.CustomObject()); - - Assert.Equal("Success!", response); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _provider.Dispose(); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/Generated/Microsoft.Gen.AutoClient.Generated.Tests.csproj b/test/Generators/Microsoft.Gen.AutoClient/Generated/Microsoft.Gen.AutoClient.Generated.Tests.csproj deleted file mode 100644 index 6d58ce5afbd..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Generated/Microsoft.Gen.AutoClient.Generated.Tests.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - Microsoft.Gen.AutoClient.Test - Tests for code generated by Microsoft.Gen.AutoClient. - - - - $(TestNetCoreTargetFrameworks) - $(TestNetCoreTargetFrameworks)$(ConditionalNet462) - true - true - $(NoWarn);IDE0161;S1144 - - - - - - - - - - - - - - - - - diff --git a/test/Generators/Microsoft.Gen.AutoClient/Generated/PathTests.cs b/test/Generators/Microsoft.Gen.AutoClient/Generated/PathTests.cs deleted file mode 100644 index a076744f9ee..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Generated/PathTests.cs +++ /dev/null @@ -1,187 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Moq.Protected; -using TestClasses; -using Xunit; - -namespace Microsoft.Gen.AutoClient.Test; - -[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "IDisposable inside mock setups")] -public class PathTests : IDisposable -{ - private readonly Mock _handlerMock = new(MockBehavior.Strict); - private readonly Mock _factoryMock = new(MockBehavior.Strict); - - private readonly IPathTestClient _sut; - private readonly ServiceProvider _provider; - - public PathTests() - { - _factoryMock.Setup(m => m.CreateClient("MyClient")).Returns(new HttpClient(_handlerMock.Object) - { - BaseAddress = new Uri("https://example.com/") - }); - - var services = new ServiceCollection(); - services.AddSingleton(_ => _factoryMock.Object); - services.AddPathTestClient(); - _provider = services.BuildServiceProvider(); - - _sut = _provider.GetRequiredService(); - } - - [Fact] - public async Task SimplePath() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users/myUser"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sut.GetUser("myUser"); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task MultiplePathParameters() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users/myTenant/3"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sut.GetUserFromTenant("myTenant", 3); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task EncodedPathParameters() - { - // "+=&" aren't required to be escaped in path segments but there is no harm in doing so - - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users/some%2B%3D%26value/3"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sut.GetUserFromTenant("some+=&value", 3); - - Assert.Equal("Success!", response); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - [InlineData(" ")] - [InlineData("a/b")] - [InlineData("/b")] - [InlineData("/b/")] - [InlineData("b/")] - [InlineData("//")] - [InlineData(".")] - [InlineData("..")] - [InlineData(" .")] - [InlineData(" ..")] - [InlineData(". ")] - [InlineData(".. ")] - [InlineData(" . ")] - [InlineData(" .. ")] - [InlineData("../")] - [InlineData("/..")] - [InlineData("./..")] - [InlineData("\\")] - [InlineData("a\\b")] - [InlineData("a\\")] - [InlineData("\\b")] - [InlineData("\\\\")] - public async Task EncodedPathInvalidParameters(string value) - { - var encodedValue = Uri.EscapeDataString($"{value}"); - - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.IsAny(), - ItExpr.IsAny()); - - var ex = await Assert.ThrowsAsync("tenantId", () => _sut.GetUserFromTenant(value, 3)); - Assert.Contains("The value can't contain '\\', '/', be empty, null or contain only dots (.).", ex.Message); - } - - [Theory] - [InlineData("abc")] - [InlineData("a..b")] - [InlineData("..b")] - [InlineData("a..")] - [InlineData(" ..b")] - [InlineData(" a..")] - [InlineData("..b ")] - [InlineData("a.. ")] - [InlineData(" ..b ")] - [InlineData(" a.. ")] - public async Task EncodedPathValidParameters(string value) - { - var encodedValue = Uri.EscapeDataString($"{value}"); - - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.IsAny(), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sut.GetUserFromTenant(value, 3); - Assert.Equal("Success!", response); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _provider.Dispose(); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/Generated/QueryTests.cs b/test/Generators/Microsoft.Gen.AutoClient/Generated/QueryTests.cs deleted file mode 100644 index 85bde7d4e33..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Generated/QueryTests.cs +++ /dev/null @@ -1,190 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Moq.Protected; -using TestClasses; -using Xunit; - -namespace Microsoft.Gen.AutoClient.Test; - -[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "IDisposable inside mock setups")] -public class QueryTests : IDisposable -{ - private readonly Mock _handlerMock = new(MockBehavior.Strict); - private readonly Mock _factoryMock = new(MockBehavior.Strict); - - private readonly IQueryTestClient _sut; - private readonly ServiceProvider _provider; - - public QueryTests() - { - _factoryMock.Setup(m => m.CreateClient("MyClient")).Returns(new HttpClient(_handlerMock.Object) - { - BaseAddress = new Uri("https://example.com/") - }); - - var services = new ServiceCollection(); - services.AddSingleton(_ => _factoryMock.Object); - services.AddQueryTestClient(); - _provider = services.BuildServiceProvider(); - - _sut = _provider.GetRequiredService(); - } - - [Fact] - public async Task QueryFromParameter() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users?paramQuery=myValue"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sut.GetUsers("myValue"); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task QueryIsEscaped() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users?paramQuery=http%3A%2F%2Fmicrosoft.com"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sut.GetUsers("http://microsoft.com"); - - Assert.Equal("Success!", response); - } - - [Theory] - [InlineData("?")] - [InlineData("=")] - [InlineData("&")] - [InlineData("%")] - [InlineData("+")] - [InlineData("#")] - [InlineData(" ")] - [InlineData("/")] // / isn't required to be escaped in query string values but there is no harm in doing so - public async Task SensitiveCharsAreEscaped(string value) - { - var encodedValue = Uri.EscapeDataString(value); - - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == $"/api/users?paramQuery={encodedValue}"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sut.GetUsers(value); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task QueryFromParameterCustom() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users?paramQueryCustom=myValue"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sut.GetUsersCustom("myValue"); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task MultipleQueryParams() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users?paramQuery1=myValue1¶mQuery2=myValue2"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sut.GetUsers2("myValue1", "myValue2"); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task QueryParamCustomObject() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users?paramQuery=CustomObjectToString"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sut.GetUsersObject(new IQueryTestClient.CustomObject()); - - Assert.Equal("Success!", response); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _provider.Dispose(); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/Generated/SpecialReturnTypeTests.cs b/test/Generators/Microsoft.Gen.AutoClient/Generated/SpecialReturnTypeTests.cs deleted file mode 100644 index 5820122d74d..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Generated/SpecialReturnTypeTests.cs +++ /dev/null @@ -1,169 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Http.AutoClient; -using Moq; -using Moq.Protected; -using TestClasses; -using Xunit; - -namespace Microsoft.Gen.AutoClient.Test; - -[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "IDisposable inside mock setups")] -public class SpecialReturnTypeTests : IDisposable -{ - private readonly Mock _handlerMock = new(MockBehavior.Strict); - private readonly Mock _factoryMock = new(MockBehavior.Strict); - - private readonly IReturnTypesTestClient _sut; - private readonly ServiceProvider _provider; - - public SpecialReturnTypeTests() - { - _factoryMock.Setup(m => m.CreateClient("MyClient")).Returns(new HttpClient(_handlerMock.Object) - { - BaseAddress = new Uri("https://example.com/") - }); - - var services = new ServiceCollection(); - services.AddSingleton(_ => _factoryMock.Object); - services.AddReturnTypesTestClient(); - _provider = services.BuildServiceProvider(); - - _sut = _provider.GetRequiredService(); - } - - [Fact] - public async Task CustomObjectReturnType() - { - var responseObject = new IReturnTypesTestClient.CustomObject - { - CustomProperty = "CustomValue" - }; - var serialized = JsonSerializer.Serialize(responseObject); - - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(serialized, Encoding.UTF8, "application/json") - }); - - var response = await _sut.GetUsers(); - - Assert.Equal("CustomValue", response.CustomProperty); - } - - [Fact] - public async Task RawResponseReturnType() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Some raw string content") - }); - - var response = await _sut.GetUsersRaw(); - var rawResponseString = await response.Content.ReadAsStringAsync(); - - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal("Some raw string content", rawResponseString); - } - - [Fact] - public async Task PlainTextString() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(@"""Some raw string content""") - }); - - var response = await _sut.GetUsersTextPlain(); - - Assert.Equal(@"""Some raw string content""", response); - } - - [Fact] - public async Task JsonTextString() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(@"""Some raw string content""", Encoding.UTF8, "application/json") - }); - - var response = await _sut.GetUsersTextPlain(); - - Assert.Equal(@"""Some raw string content""", response); - } - - [Fact] - public async Task UnsupportedContentType() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("", Encoding.UTF8, "text/xml") - }); - - var ex = await Assert.ThrowsAsync(() => _sut.GetUsers()); - Assert.Equal($"The 'ReturnTypesTest' REST API returned an unsupported content type ('text/xml').", ex.Message); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _provider.Dispose(); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/Generated/TelemetryTests.cs b/test/Generators/Microsoft.Gen.AutoClient/Generated/TelemetryTests.cs deleted file mode 100644 index db1a69b3a24..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Generated/TelemetryTests.cs +++ /dev/null @@ -1,181 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Moq.Protected; -using TestClasses; -using Xunit; - -namespace Microsoft.Gen.AutoClient.Test; - -[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "IDisposable inside mock setups")] -[System.Diagnostics.CodeAnalysis.SuppressMessage("Critical Code Smell", "S1067:Expressions should not be too complex", Justification = "Mock conditions")] -public class TelemetryTests : IDisposable -{ - private readonly Mock _handlerMock = new(MockBehavior.Strict); - private readonly Mock _factoryMock = new(MockBehavior.Strict); - - private readonly IRequestMetadataTestClient _sutClient; - private readonly IRequestMetadataTestApi _sutApi; - private readonly ICustomRequestMetadataTestClient _sutCustom; - private readonly ServiceProvider _provider; - - public TelemetryTests() - { - _factoryMock.Setup(m => m.CreateClient("MyClient")).Returns(new HttpClient(_handlerMock.Object) - { - BaseAddress = new Uri("https://example.com/") - }); - - var services = new ServiceCollection(); - services.AddSingleton(_ => _factoryMock.Object); - services.AddRequestMetadataTestClient(); - services.AddRequestMetadataTestApi(); - services.AddCustomRequestMetadataTestClient(); - _provider = services.BuildServiceProvider(); - - _sutClient = _provider.GetRequiredService(); - _sutApi = _provider.GetRequiredService(); - _sutCustom = _provider.GetRequiredService(); - } - - [Fact] - public async Task ClientDependencyComplexPath() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users/myUser?search=searchParam" && - message.GetRequestMetadata()!.RequestRoute == "/api/users/{userId}?search={search}" && - message.GetRequestMetadata()!.DependencyName == "RequestMetadataTest" && - message.GetRequestMetadata()!.RequestName == "GetUser"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sutClient.GetUser("myUser", "searchParam"); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task ClientDependencyMethodNameAsync() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users" && - message.GetRequestMetadata()!.RequestRoute == "/api/users" && - message.GetRequestMetadata()!.DependencyName == "RequestMetadataTest" && - message.GetRequestMetadata()!.RequestName == "GetUsers"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sutClient.GetUsersAsync(); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task ApiDependencyMethodNameAsync() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users" && - message.GetRequestMetadata()!.RequestRoute == "/api/users" && - message.GetRequestMetadata()!.DependencyName == "RequestMetadataTest" && - message.GetRequestMetadata()!.RequestName == "GetUsers"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sutApi.GetUsersAsync(); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task CustomDependencyName() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/user" && - message.GetRequestMetadata()!.RequestRoute == "/api/user" && - message.GetRequestMetadata()!.DependencyName == "MyDependency" && - message.GetRequestMetadata()!.RequestName == "GetUser"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sutCustom.GetUser(); - - Assert.Equal("Success!", response); - } - - [Fact] - public async Task CustomRequestName() - { - _handlerMock - .Protected() - .Setup>("SendAsync", ItExpr.Is(message => - message.Method == HttpMethod.Get && - message.RequestUri != null && - message.RequestUri.PathAndQuery == "/api/users" && - message.GetRequestMetadata()!.RequestRoute == "/api/users" && - message.GetRequestMetadata()!.DependencyName == "MyDependency" && - message.GetRequestMetadata()!.RequestName == "MyRequestName"), - ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent("Success!") - }); - - var response = await _sutCustom.GetUsers(); - - Assert.Equal("Success!", response); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _provider.Dispose(); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IBasicTestClient.cs b/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IBasicTestClient.cs deleted file mode 100644 index 967d5615715..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IBasicTestClient.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Http.AutoClient; - -namespace TestClasses -{ - [AutoClient("MyClient")] - public interface IBasicTestClient - { - [Get("/api/users")] - public Task GetUsers(CancellationToken cancellationToken = default); - - [Delete("/api/users")] - public Task DeleteUsers(CancellationToken cancellationToken = default); - - [Head("/api/users")] - public Task HeadUsers(CancellationToken cancellationToken = default); - - [Options("/api/users")] - public Task OptionsUsers(CancellationToken cancellationToken = default); - - [Patch("/api/users")] - public Task PatchUsers(CancellationToken cancellationToken = default); - - [Post("/api/users")] - public Task PostUsers(CancellationToken cancellationToken = default); - - [Put("/api/users")] - public Task PutUsers(CancellationToken cancellationToken = default); - - [Get("/api/users")] - public Task GetUsersWithCancellationToken(CancellationToken cancellationToken); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IBodyTestClient.cs b/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IBodyTestClient.cs deleted file mode 100644 index ea130ebbfce..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IBodyTestClient.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Http.AutoClient; - -namespace TestClasses -{ - [AutoClient("MyClient")] - public interface IBodyTestClient - { - [Post("/api/users")] - public Task PostUsers([Body] BodyObject body, CancellationToken cancellationToken = default); - - [Put("/api/users")] - public Task PutUsers([Body(BodyContentType.TextPlain)] BodyObject body, CancellationToken cancellationToken = default); - - public class BodyObject - { - public string Value { get; set; } = "MyBodyObjectValue"; - - public override string? ToString() => Value; - } - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/ICustomRequestMetadataTestClient.cs b/test/Generators/Microsoft.Gen.AutoClient/TestClasses/ICustomRequestMetadataTestClient.cs deleted file mode 100644 index a8b5cf2fa5f..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/ICustomRequestMetadataTestClient.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Http.AutoClient; - -namespace TestClasses -{ - [AutoClient("MyClient", "MyDependency")] - public interface ICustomRequestMetadataTestClient - { - [Get("/api/user")] - public Task GetUser(CancellationToken cancellationToken = default); - - [Get("/api/users", RequestName = "MyRequestName")] - public Task GetUsers(CancellationToken cancellationToken = default); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/INameConflictTestClient.cs b/test/Generators/Microsoft.Gen.AutoClient/TestClasses/INameConflictTestClient.cs deleted file mode 100644 index 58cb099898d..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/INameConflictTestClient.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Http.AutoClient; - -#pragma warning disable IDE1006 - -namespace TestClasses -{ - [AutoClient("MyClient")] - public interface INameConflictTestClient - { - [Get("/api/users")] - public Task Statics(CancellationToken cancellationToken); - - [Get("/api/users")] - public Task _httpClient(CancellationToken cancellationToken); - - [Get("/api/users")] - public Task _autoClientOptions(CancellationToken cancellationToken); - - [Get("/api/users")] - public Task GetUsers1([Header("X-MyHeader")] string? httpRequestMessage, CancellationToken cancellationToken = default); - - [Get("/api/users")] - public Task GetUsers2([Header("X-MyHeader")] string? _autoClientOptions, CancellationToken cancellationToken = default); - - [Get("/api/users")] - public Task GetUsers3([Header("X-MyHeader")] string? _httpClient, CancellationToken cancellationToken = default); - - [Get("/api/users")] - public Task GetUsers4([Header("X-MyHeader")] string? Statics, CancellationToken cancellationToken = default); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IParamHeaderTestClient.cs b/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IParamHeaderTestClient.cs deleted file mode 100644 index 495ae1f31e3..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IParamHeaderTestClient.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Http.AutoClient; - -namespace TestClasses -{ - [AutoClient("MyClient")] - public interface IParamHeaderTestClient - { - [Get("/api/users")] - public Task GetUsers([Header("X-MyHeader")] string? headerValue, CancellationToken cancellationToken = default); - - [Get("/api/users")] - public Task GetUsersObject([Header("X-MyHeader")] CustomObject headerValue, CancellationToken cancellationToken = default); - - [Get("/api/users")] - public Task GetUsersInt32([Header("X-MyHeader")] int headerValue, CancellationToken cancellationToken = default); - - [Get("/api/users")] - public Task GetUsersNullableInt32([Header("X-MyHeader")] int? headerValue, CancellationToken cancellationToken = default); - - public class CustomObject - { - public override string? ToString() => "CustomObjectToString"; - } - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IPathTestClient.cs b/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IPathTestClient.cs deleted file mode 100644 index e96a8e89fb4..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IPathTestClient.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Http.AutoClient; - -namespace TestClasses -{ - [AutoClient("MyClient")] - public interface IPathTestClient - { - [Get("/api/users/{userId}")] - public Task GetUser(string userId, CancellationToken cancellationToken = default); - - [Get("/api/users/{tenantId}/{userId}")] - public Task GetUserFromTenant(string tenantId, int? userId, CancellationToken cancellationToken = default); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IQueryTestClient.cs b/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IQueryTestClient.cs deleted file mode 100644 index cd00df0ae6c..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IQueryTestClient.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Http.AutoClient; - -namespace TestClasses -{ - [AutoClient("MyClient")] - public interface IQueryTestClient - { - [Get("/api/users")] - public Task GetUsers([Query] string paramQuery, CancellationToken cancellationToken = default); - - [Get("/api/users")] - public Task GetUsersCustom([Query("paramQueryCustom")] string paramQuery, CancellationToken cancellationToken = default); - - [Get("/api/users")] - public Task GetUsers2([Query] string paramQuery1, [Query] string paramQuery2, CancellationToken cancellationToken = default); - - [Get("/api/users")] - public Task GetUsersObject([Query] CustomObject paramQuery, CancellationToken cancellationToken = default); - - public class CustomObject - { - public override string? ToString() => "CustomObjectToString"; - } - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IRequestMetadataTestApi.cs b/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IRequestMetadataTestApi.cs deleted file mode 100644 index d1a228d46e7..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IRequestMetadataTestApi.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Http.AutoClient; - -namespace TestClasses -{ - [AutoClient("MyClient")] - public interface IRequestMetadataTestApi - { - [Get("/api/users")] - public Task GetUsersAsync(CancellationToken cancellationToken = default); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IRequestMetadataTestClient.cs b/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IRequestMetadataTestClient.cs deleted file mode 100644 index a9f9af02925..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IRequestMetadataTestClient.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Http.AutoClient; - -namespace TestClasses -{ - [AutoClient("MyClient")] - public interface IRequestMetadataTestClient - { - [Get("/api/users/{userId}")] - public Task GetUser(string userId, [Query] string search, CancellationToken cancellationToken = default); - - [Get("/api/users")] - public Task GetUsersAsync(CancellationToken cancellationToken = default); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IRestApiClientOptionsApi.cs b/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IRestApiClientOptionsApi.cs deleted file mode 100644 index 9aed97957f3..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IRestApiClientOptionsApi.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Http.AutoClient; - -namespace TestClasses -{ - [AutoClient("MyClient")] - public interface IRestApiClientOptionsApi - { - [Post("/api/dict")] - public Task PostDictionary([Body] Dictionary body, CancellationToken cancellationToken = default); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IReturnTypesTestClient.cs b/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IReturnTypesTestClient.cs deleted file mode 100644 index 60d5b49b8ab..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IReturnTypesTestClient.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Http.AutoClient; - -namespace TestClasses -{ - [AutoClient("MyClient")] - public interface IReturnTypesTestClient - { - [Get("/api/users")] - public Task GetUsers(CancellationToken cancellationToken = default); - - [Get("/api/users")] - public Task GetUsersRaw(CancellationToken cancellationToken = default); - - [Get("/api/users")] - public Task GetUsersTextPlain(CancellationToken cancellationToken = default); - - public class CustomObject - { - public string CustomProperty { get; set; } = string.Empty; - } - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IStaticHeaderTestClient.cs b/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IStaticHeaderTestClient.cs deleted file mode 100644 index 37b6b5a8f15..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/TestClasses/IStaticHeaderTestClient.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Http.AutoClient; - -namespace TestClasses -{ - [AutoClient("MyClient")] - [StaticHeader("X-MyHeader", "MyValue")] - public interface IStaticHeaderTestClient - { - [Get("/api/users")] - public Task GetUsers(CancellationToken cancellationToken = default); - - [Get("/api/users")] - [StaticHeader("X-MyHeader1", "MyValue")] - [StaticHeader("X-MyHeader2", "MyValue")] - public Task GetUsersHeaders(CancellationToken cancellationToken = default); - - [Get("/api/users")] - [StaticHeader("X-MyHeader3", "MyValueWith\"Escaped\"Stuff\t\b\u03a0")] - public Task GetUsersHeadersEscaped(CancellationToken cancellationToken = default); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/Unit/BodyContentTypeParamExtensionsTests.cs b/test/Generators/Microsoft.Gen.AutoClient/Unit/BodyContentTypeParamExtensionsTests.cs deleted file mode 100644 index 474c7aa5cb2..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Unit/BodyContentTypeParamExtensionsTests.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Gen.AutoClient.Model; -using Xunit; - -namespace Microsoft.Gen.AutoClient.Test; - -public class BodyContentTypeParamExtensionsTests -{ - [Fact] - public void ConvertToString() - { - Assert.Equal("application/json", BodyContentTypeParamExtensions.ConvertToString(BodyContentTypeParam.ApplicationJson)); - Assert.Equal("text/plain", BodyContentTypeParamExtensions.ConvertToString(BodyContentTypeParam.TextPlain)); - Assert.Equal("", BodyContentTypeParamExtensions.ConvertToString((BodyContentTypeParam)999)); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/Unit/DiagDescriptorsTests.cs b/test/Generators/Microsoft.Gen.AutoClient/Unit/DiagDescriptorsTests.cs deleted file mode 100644 index 8c185584933..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Unit/DiagDescriptorsTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Reflection; -using Microsoft.CodeAnalysis; -using Xunit; - -namespace Microsoft.Gen.AutoClient.Test; - -public class DiagDescriptorsTests -{ - public static IEnumerable DiagDescriptorsData() - { - var type = typeof(DiagDescriptors); - foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.GetProperty)) - { - var value = property.GetValue(type, null); - yield return new[] { value }; - } - } - - [Theory] - [MemberData(nameof(DiagDescriptorsData))] - public void ShouldContainValidLinkAndBeEnabled(DiagnosticDescriptor descriptor) - { - Assert.True(descriptor.IsEnabledByDefault, descriptor.Id + " should be enabled by default"); - Assert.EndsWith("/" + descriptor.Id, descriptor.HelpLinkUri, StringComparison.OrdinalIgnoreCase); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/Unit/EmitterTests.cs b/test/Generators/Microsoft.Gen.AutoClient/Unit/EmitterTests.cs deleted file mode 100644 index d9c57c66257..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Unit/EmitterTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Threading.Tasks; -using Microsoft.Extensions.Http.AutoClient; -using Microsoft.Gen.Shared; -using Xunit; - -namespace Microsoft.Gen.AutoClient.Test; - -public class EmitterTests -{ - [Fact] - public async Task TestEmitter() - { - var sources = new List(); - foreach (var file in Directory.GetFiles("TestClasses")) - { - sources.Add(File.ReadAllText(file)); - } - - var (d, r) = await RoslynTestUtils.RunGenerator( - new AutoClientGenerator(), - new[] - { - Assembly.GetAssembly(typeof(AutoClientAttribute))!, - Assembly.GetAssembly(typeof(GetAttribute))! - }, - sources) - .ConfigureAwait(false); - - Assert.Empty(d); - Assert.Single(r); - - var goldenClient = File.ReadAllText("GoldenFiles/Microsoft.Gen.AutoClient/Microsoft.Gen.AutoClient.AutoClientGenerator/AutoClients.g.cs"); - var result = r[0].SourceText.ToString(); - Assert.Equal(goldenClient, result); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/Unit/Microsoft.Gen.AutoClient.Unit.Tests.csproj b/test/Generators/Microsoft.Gen.AutoClient/Unit/Microsoft.Gen.AutoClient.Unit.Tests.csproj deleted file mode 100644 index 0a63cb7220e..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Unit/Microsoft.Gen.AutoClient.Unit.Tests.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - Microsoft.Gen.AutoClient.Unit.Test - Unit tests for Microsoft.Gen.AutoClient. - - - - true - - - - - - - - - - - - - - - - - diff --git a/test/Generators/Microsoft.Gen.AutoClient/Unit/ParserTests.cs b/test/Generators/Microsoft.Gen.AutoClient/Unit/ParserTests.cs deleted file mode 100644 index a3872033ac2..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Unit/ParserTests.cs +++ /dev/null @@ -1,726 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.Extensions.Http.AutoClient; -using Microsoft.Gen.Shared; -using Xunit; - -namespace Microsoft.Gen.AutoClient.Test; - -public class ParserTests -{ - private static readonly string[] _unsupportedCharactersStrings = new[] { "\\\"", "\\n", "\\r", "\\t" }; - private static readonly string[] _unsupportedCharactersHeaderValues = new[] { "\\n", "\\r" }; - - [Fact] - public void NoSymbols() - { - var comp = CSharpCompilation.Create(null); - var p = new Parser(comp, (_) => { }, default); - Assert.Empty(p.GetRestApiClasses(new List())); - } - - [Fact] - public async Task ApiIsClass() - { - var d = await RunGenerator(@" - [AutoClient(""MyClient"")] - public class C - { - [Get(""/api/users"")] - public Task GetUsers(CancellationToken cancellationToken) { return """"; } - }"); - - Assert.Empty(d); - } - - [Fact] - public async Task NestedNamespace() - { - var d = await RunGenerator(@" - namespace ParentNamespace - { - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public Task GetUsers(CancellationToken cancellationToken); - } - }"); - - Assert.Empty(d); - } - - [Fact] - public async Task NoAttributes() - { - var d = await RunGenerator(@" - public interface IClient - { - public Task GetUsers(CancellationToken cancellationToken); - }"); - - Assert.Empty(d); - } - - [Fact] - public async Task InvalidInterfaceName() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface Client - { - [Get(""/api/users"")] - public Task GetUsers(CancellationToken cancellationToken); - }"); - - var d = Assert.Single(ds); - Assert.Equal(DiagDescriptors.ErrorInterfaceName.Id, d.Id); - } - - [Fact] - public async Task InvalidMethodReturnType() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public string GetUsers(CancellationToken cancellationToken); - }"); - - Assert.Contains(DiagDescriptors.ErrorInvalidReturnType.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task InvalidMethodReturnTypeNullable() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public Task GetUsers(CancellationToken cancellationToken); - }"); - - Assert.Contains(DiagDescriptors.ErrorInvalidReturnType.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task InvalidMethodReturnTypeMultipleTypeArguments() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public Task GetUsers(CancellationToken cancellationToken); - }"); - - Assert.Contains(DiagDescriptors.ErrorInvalidReturnType.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task GenericInterface() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public Task GetUsers(CancellationToken cancellationToken); - }"); - - var d = Assert.Single(ds); - Assert.Equal(DiagDescriptors.ErrorInterfaceIsGeneric.Id, d.Id); - } - - [Fact] - public async Task MissingRestApiAttribute() - { - var d = await RunGenerator(@" - public interface IClient - { - [Get(""/api/users"")] - public Task GetUsers(CancellationToken cancellationToken); - }"); - - Assert.Empty(d); - } - - [Fact] - public async Task NestedClass() - { - var ds = await RunGenerator(@" - public class B - { - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public Task GetUsers(CancellationToken cancellationToken); - } - }"); - - var d = Assert.Single(ds); - Assert.Equal(DiagDescriptors.ErrorClientMustNotBeNested.Id, d.Id); - } - - [Fact] - public async Task MissingRestApiMethods() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - public Task GetUsers(CancellationToken cancellationToken); - }"); - - Assert.Contains(DiagDescriptors.WarningRestClientWithoutRestMethods.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task MultipleApiMethodAttributes() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - [Post(""/api/users"")] - public Task GetUsers(CancellationToken cancellationToken); - }"); - - Assert.Contains(DiagDescriptors.ErrorApiMethodMoreThanOneAttribute.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task WithRequestNameAttributes() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Post(""/api/users"", RequestName = ""MyRequest"")] - public Task GetUsers(CancellationToken cancellationToken); - }"); - - Assert.Empty(ds); - } - - [Fact] - public async Task WithQueryInPath() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Post(""/api/users?query=true"")] - public Task GetUsers(CancellationToken cancellationToken); - }"); - - Assert.Contains(DiagDescriptors.ErrorPathWithQuery.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task GenericMethodUnsupported() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public Task GetUsers(CancellationToken cancellationToken); - }"); - - Assert.Contains(DiagDescriptors.ErrorMethodIsGeneric.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task StaticMethodUnsupported() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public static string GetUsers(CancellationToken cancellationToken); - }"); - - Assert.Contains(DiagDescriptors.ErrorStaticMethod.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task MissingNamespace() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public Task GetUsers(CancellationToken cancellationToken); - }", inNamespace: false); - - Assert.Empty(ds); - } - - [Fact] - public async Task InvalidMethodAttribute() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [AutoClient(""/api/users"")] - public Task GetUsers(CancellationToken cancellationToken); - }"); - - Assert.Contains(DiagDescriptors.ErrorMissingMethodAttribute.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task UnsupportedBodyMethod() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public Task GetUsers([Body] string param, CancellationToken cancellationToken); - }"); - - Assert.Contains(DiagDescriptors.ErrorUnsupportedMethodBody.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task DuplicateBody() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public Task GetUsers([Body] string param, [Body] string param2, CancellationToken cancellationToken); - }"); - - Assert.Contains(DiagDescriptors.ErrorDuplicateBody.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task ParameterMissingUrl() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public Task GetUser(string userId, CancellationToken cancellationToken); - }"); - - Assert.Contains(DiagDescriptors.ErrorMissingParameterUrl.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task SingleCancellationToken() - { - var d = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public Task GetUsers(CancellationToken token); - }"); - - Assert.Empty(d); - } - - [Fact] - public async Task MissingCancellationToken() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public Task GetUsers(); - }"); - - Assert.Contains(DiagDescriptors.ErrorMissingCancellationToken.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task ErrorSymbolNotImported() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public Task GetUsers(SomeSymbol symbol, CancellationToken token); - }"); - - Assert.Contains(DiagDescriptors.WarningRestClientWithoutRestMethods.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task DoubleCancellationToken() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public Task GetUsers(CancellationToken token, CancellationToken token2); - }"); - - Assert.Contains(DiagDescriptors.ErrorDuplicateCancellationToken.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task RequestNameDuplicate() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public Task GetUsers(CancellationToken token); - - [Get(""/api/users"")] - public Task GetUsersAsync(CancellationToken token); - }"); - - Assert.Contains(DiagDescriptors.ErrorDuplicateRequestName.Id, ds.Select(x => x.Id)); - Assert.Contains("GetUsers", ds.First(x => x.Id == DiagDescriptors.ErrorDuplicateRequestName.Id).GetMessage()); - } - - [Fact] - public async Task RequestNameDuplicateAttribute() - { - var ds = await RunGenerator(@" - [AutoClient(""MyClient"")] - public interface IClient - { - [Get(""/api/users"")] - public Task GetUsers(CancellationToken token); - - [Get(""/api/users"", RequestName = ""GetUsers"")] - public Task GetSomeUsers(CancellationToken token); - }"); - - Assert.Contains(DiagDescriptors.ErrorDuplicateRequestName.Id, ds.Select(x => x.Id)); - Assert.Contains("GetUsers", ds.First(x => x.Id == DiagDescriptors.ErrorDuplicateRequestName.Id).GetMessage()); - } - - [Fact] - public async Task InvalidHttpClientName() - { - foreach (var invalidChar in _unsupportedCharactersStrings) - { - var ds = await RunGenerator(@$" - [AutoClient(""MyClient{invalidChar}"")] - public interface IClient - {{ - [Get(""/api/users"")] - public Task GetUsers(CancellationToken token); - }}"); - - Assert.Contains(DiagDescriptors.ErrorInvalidHttpClientName.Id, ds.Select(x => x.Id)); - } - } - - [Fact] - public async Task InvalidDependencyName() - { - foreach (var invalidChar in _unsupportedCharactersStrings) - { - var ds = await RunGenerator(@$" - [AutoClient(""MyClient"", ""DependencyName{invalidChar}"")] - public interface IClient - {{ - [Get(""/api/users"")] - public Task GetUsers(CancellationToken token); - }}"); - - Assert.Contains(DiagDescriptors.ErrorInvalidDependencyName.Id, ds.Select(x => x.Id)); - } - } - - [Fact] - public async Task InvalidHeaderNameType() - { - foreach (var invalidChar in _unsupportedCharactersStrings) - { - var ds = await RunGenerator(@$" - [AutoClient(""MyClient"")] - [StaticHeader(""HeaderName{invalidChar}"", ""HeaderValue"")] - public interface IClient - {{ - [Get(""/api/users"")] - public Task GetUsers(CancellationToken token); - }}"); - - Assert.Contains(DiagDescriptors.ErrorInvalidHeaderName.Id, ds.Select(x => x.Id)); - } - } - - [Fact] - public async Task InvalidHeaderNameMethod() - { - foreach (var invalidChar in _unsupportedCharactersStrings) - { - var ds = await RunGenerator(@$" - [AutoClient(""MyClient"")] - public interface IClient - {{ - [Get(""/api/users"")] - [StaticHeader(""HeaderName{invalidChar}"", ""HeaderValue"")] - public Task GetUsers(CancellationToken token); - }}"); - - Assert.Contains(DiagDescriptors.ErrorInvalidHeaderName.Id, ds.Select(x => x.Id)); - } - } - - [Fact] - public async Task InvalidHeaderValueType() - { - foreach (var invalidChar in _unsupportedCharactersHeaderValues) - { - var ds = await RunGenerator(@$" - [AutoClient(""MyClient"")] - [StaticHeader(""HeaderName"", ""HeaderValue{invalidChar}"")] - public interface IClient - {{ - [Get(""/api/users"")] - public Task GetUsers(CancellationToken token); - }}"); - - Assert.Contains(DiagDescriptors.ErrorInvalidHeaderValue.Id, ds.Select(x => x.Id)); - } - } - - [Fact] - public async Task InvalidHeaderValueMethod() - { - foreach (var invalidChar in _unsupportedCharactersHeaderValues) - { - var ds = await RunGenerator(@$" - [AutoClient(""MyClient"")] - public interface IClient - {{ - [Get(""/api/users"")] - [StaticHeader(""HeaderName"", ""HeaderValue{invalidChar}"")] - public Task GetUsers(CancellationToken token); - }}"); - - Assert.Contains(DiagDescriptors.ErrorInvalidHeaderValue.Id, ds.Select(x => x.Id)); - } - } - - [Fact] - public async Task InvalidPath() - { - foreach (var invalidChar in _unsupportedCharactersHeaderValues) - { - var ds = await RunGenerator(@$" - [AutoClient(""MyClient"")] - public interface IClient - {{ - [Get(""/api/users{invalidChar}"")] - public Task GetUsers(CancellationToken token); - }}"); - - Assert.Contains(DiagDescriptors.ErrorInvalidPath.Id, ds.Select(x => x.Id)); - } - } - - [Fact] - public async Task NullPath() - { - var ds = await RunGenerator(@$" - [AutoClient(""MyClient"")] - public interface IClient - {{ - [Get(null!)] - public Task GetUsers(CancellationToken token); - }}"); - - Assert.Contains(DiagDescriptors.ErrorMissingMethodAttribute.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task EmptyPath() - { - var ds = await RunGenerator(@$" - [AutoClient(""MyClient"")] - public interface IClient - {{ - [Get("""")] - public Task GetUsers(CancellationToken token); - }}"); - - Assert.Contains(DiagDescriptors.ErrorMissingMethodAttribute.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task NullStaticHeaderKey() - { - var ds = await RunGenerator(@$" - [AutoClient(""MyClient"")] - public interface IClient - {{ - [Get(""/api/users"")] - [StaticHeader(null!, ""value"")] - public Task GetUsers(CancellationToken token); - }}"); - - Assert.Contains(DiagDescriptors.ErrorInvalidHeaderName.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task NullStaticHeaderValue() - { - var ds = await RunGenerator(@$" - [AutoClient(""MyClient"")] - public interface IClient - {{ - [Get(""/api/users"")] - [StaticHeader(""key"", null!)] - public Task GetUsers(CancellationToken token); - }}"); - - Assert.Contains(DiagDescriptors.ErrorInvalidHeaderValue.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task EmptyStaticHeaderKey() - { - var ds = await RunGenerator(@$" - [AutoClient(""MyClient"")] - public interface IClient - {{ - [Get(""/api/users"")] - [StaticHeader("""", ""value"")] - public Task GetUsers(CancellationToken token); - }}"); - - Assert.Contains(DiagDescriptors.ErrorInvalidHeaderName.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task EmptyStaticHeaderValue_NoError() - { - var ds = await RunGenerator(@$" - [AutoClient(""MyClient"")] - public interface IClient - {{ - [Get(""/api/users"")] - [StaticHeader(""key"", """")] - public Task GetUsers(CancellationToken token); - }}"); - - Assert.Empty(ds); - } - - [Fact] - public async Task NullRequestName() - { - var ds = await RunGenerator(@$" - [AutoClient(""MyClient"")] - public interface IClient - {{ - [Get(""/api/users"", RequestName = null!)] - public Task GetUsers(CancellationToken token); - }}"); - - Assert.Contains(DiagDescriptors.ErrorInvalidRequestName.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task EmptyRequestName() - { - var ds = await RunGenerator(@$" - [AutoClient(""MyClient"")] - public interface IClient - {{ - [Get(""/api/users"", RequestName = """")] - public Task GetUsers(CancellationToken token); - }}"); - - Assert.Contains(DiagDescriptors.ErrorInvalidRequestName.Id, ds.Select(x => x.Id)); - } - - [Fact] - public async Task InvalidRequestName() - { - foreach (var invalidChar in _unsupportedCharactersHeaderValues) - { - var ds = await RunGenerator(@$" - [AutoClient(""MyClient"")] - public interface IClient - {{ - [Get(""/api/users"", RequestName = ""RequestName{invalidChar}"")] - public Task GetUsers(CancellationToken token); - }}"); - - Assert.Contains(DiagDescriptors.ErrorInvalidRequestName.Id, ds.Select(x => x.Id)); - } - } - - private static async Task> RunGenerator( - string code, - bool wrap = true, - bool inNamespace = true, - bool includeBaseReferences = true, - bool includeRestApi = true, - CancellationToken cancellationToken = default) - { - var text = code; - if (wrap) - { - var nspaceStart = "namespace Test {"; - var nspaceEnd = "}"; - if (!inNamespace) - { - nspaceStart = ""; - nspaceEnd = ""; - } - - text = $@" - {nspaceStart} - using Microsoft.Extensions.Http.AutoClient; - using System.Threading; - using System.Threading.Tasks; - {code} - {nspaceEnd} - "; - } - - Assembly[]? refs = null; - if (includeRestApi) - { - refs = new[] - { - Assembly.GetAssembly(typeof(AutoClientAttribute))! - }; - } - - var (d, _) = await RoslynTestUtils.RunGenerator( - new AutoClientGenerator(), - refs, - new[] { text }, - includeBaseReferences: includeBaseReferences, - cancellationToken: cancellationToken).ConfigureAwait(false); - - return d; - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/Unit/RestApiMethodParameterTests.cs b/test/Generators/Microsoft.Gen.AutoClient/Unit/RestApiMethodParameterTests.cs deleted file mode 100644 index c664470e69a..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Unit/RestApiMethodParameterTests.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Gen.AutoClient.Model; -using Xunit; - -namespace Microsoft.Gen.AutoClient.Test; - -public class RestApiMethodParameterTests -{ - [Fact] - public void Fields_Should_BeInitialized() - { - var instance = new RestApiMethodParameter(); - Assert.Empty(instance.Name); - Assert.Empty(instance.Type); - Assert.Null(instance.HeaderName); - Assert.Null(instance.QueryKey); - Assert.Null(instance.BodyType); - Assert.False(instance.IsHeader); - Assert.False(instance.IsQuery); - Assert.False(instance.IsBody); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/Unit/RestApiMethodTests.cs b/test/Generators/Microsoft.Gen.AutoClient/Unit/RestApiMethodTests.cs deleted file mode 100644 index c89386afd55..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Unit/RestApiMethodTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Gen.AutoClient.Model; -using Xunit; - -namespace Microsoft.Gen.AutoClient.Test; - -public class RestApiMethodTests -{ - [Fact] - public void Fields_Should_BeInitialized() - { - var instance = new RestApiMethod(); - Assert.Empty(instance.AllParameters); - Assert.Empty(instance.FormatParameters); - Assert.Empty(instance.MethodName); - Assert.Empty(instance.HttpMethod!); - Assert.Empty(instance.Path!); - Assert.Empty(instance.ReturnType!); - Assert.Empty(instance.RequestName); - } -} diff --git a/test/Generators/Microsoft.Gen.AutoClient/Unit/SymbolLoaderTests.cs b/test/Generators/Microsoft.Gen.AutoClient/Unit/SymbolLoaderTests.cs deleted file mode 100644 index fd0ad42eeaa..00000000000 --- a/test/Generators/Microsoft.Gen.AutoClient/Unit/SymbolLoaderTests.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.CodeAnalysis.CSharp; -using Xunit; - -namespace Microsoft.Gen.AutoClient.Test; - -public class SymbolLoaderTests -{ - [Fact] - public void RestApiAttributeNotAvailable() - { - var comp = CSharpCompilation.Create(null); - Assert.Null(SymbolLoader.LoadSymbols(comp)); - } -} diff --git a/test/Generators/Microsoft.Gen.ComplianceReports/Unit/Microsoft.Gen.ComplianceReports.Unit.Tests.csproj b/test/Generators/Microsoft.Gen.ComplianceReports/Unit/Microsoft.Gen.ComplianceReports.Unit.Tests.csproj index 14f553ecdd3..f1553d2abaf 100644 --- a/test/Generators/Microsoft.Gen.ComplianceReports/Unit/Microsoft.Gen.ComplianceReports.Unit.Tests.csproj +++ b/test/Generators/Microsoft.Gen.ComplianceReports/Unit/Microsoft.Gen.ComplianceReports.Unit.Tests.csproj @@ -26,7 +26,7 @@ - + diff --git a/test/Generators/Microsoft.Gen.EnumStrings/Generated/EnumStringsTests.cs b/test/Generators/Microsoft.Gen.EnumStrings/Generated/EnumStringsTests.cs deleted file mode 100644 index 3155c53e78a..00000000000 --- a/test/Generators/Microsoft.Gen.EnumStrings/Generated/EnumStringsTests.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using TestClasses; -using Xunit; - -namespace Microsoft.Gen.EnumStrings.Test; - -public static class EnumStringsTests -{ - [Fact] - public static void TestGeneral() - { - Test(i => (Size0)i, v => v.ToInvariantString()); - Test(i => (Size1)i, v => v.ToInvariantString()); - Test(i => (Size2)i, v => v.ToInvariantString()); - Test(i => (Size3)i, v => v.ToInvariantString()); - Test(i => (Size4)i, v => v.ToInvariantString()); - Test(i => (Size5)i, v => v.ToInvariantString()); - Test(i => (Size6)i, v => v.ToInvariantString()); - Test(i => (Size7)i, v => v.ToInvariantString()); - - Test(i => (Flags0)i, v => v.ToInvariantString()); - Test(i => (Flags1)i, v => v.ToInvariantString()); - Test(i => (Flags2)i, v => v.ToInvariantString()); - Test(i => (Flags3)i, v => v.ToInvariantString()); - Test(i => (Flags4)i, v => v.ToInvariantString()); - Test(i => (Flags5)i, v => v.ToInvariantString()); - Test(i => (Flags6)i, v => v.ToInvariantString()); - Test(i => (Flags7)i, v => v.ToInvariantString()); - Test(i => (Flags8)i, v => v.ToInvariantString()); - - Test(i => (SByteEnum1)i, v => v.ToInvariantString()); - Test(i => (SByteEnum2)i, v => v.ToInvariantString()); - Test(i => (SByteEnum3)i, v => v.ToInvariantString()); - - Test(i => (ByteEnum1)i, v => v.ToInvariantString()); - Test(i => (ByteEnum2)i, v => v.ToInvariantString()); - Test(i => (ByteEnum3)i, v => v.ToInvariantString()); - - Test(i => (ShortEnum1)i, v => v.ToInvariantString()); - Test(i => (ShortEnum2)i, v => v.ToInvariantString()); - Test(i => (ShortEnum3)i, v => v.ToInvariantString()); - - Test(i => (UShortEnum1)i, v => v.ToInvariantString()); - Test(i => (UShortEnum2)i, v => v.ToInvariantString()); - Test(i => (UShortEnum3)i, v => v.ToInvariantString()); - - Test(i => (IntEnum1)i, v => v.ToInvariantString()); - Test(i => (IntEnum2)i, v => v.ToInvariantString()); - Test(i => (IntEnum3)i, v => v.ToInvariantString()); - - Test(i => (UIntEnum1)i, v => v.ToInvariantString()); - Test(i => (UIntEnum2)i, v => v.ToInvariantString()); - Test(i => (UIntEnum3)i, v => v.ToInvariantString()); - - Test(i => (LongEnum1)i, v => v.ToInvariantString()); - Test(i => (LongEnum2)i, v => v.ToInvariantString()); - Test(i => (LongEnum3)i, v => v.ToInvariantString()); - - Test(i => (ULongEnum1)i, v => v.ToInvariantString()); - Test(i => (ULongEnum2)i, v => v.ToInvariantString()); - Test(i => (ULongEnum3)i, v => v.ToInvariantString()); - - Test(i => (Options0)i, v => NamespaceX.ClassY.MethodZ(v)); - Test(i => (Options1)i, v => NamespaceA.ClassB.MethodC(v)); - - Test(i => (Overlapping1)i, v => v.ToInvariantString()); - Test(i => (Overlapping2)i, v => v.ToInvariantString()); - - Test(i => (TestClasses.Nested.Fruit)i, v => v.ToInvariantString()); - - Test(i => (Level)i, v => v.ToInvariantString()); - Test(i => (Medal)i, v => v.ToInvariantString()); - - Test(i => (Negative0)i, v => v.ToInvariantString()); - Test(i => (Negative1)i, v => v.ToInvariantString()); - - Test(i => (NegativeLong0)i, v => v.ToInvariantString()); - Test(i => (NegativeLong1)i, v => v.ToInvariantString()); - - static void Test(Func convert, Func extension) - where T : notnull - { - for (int i = -120; i < 120; i++) - { - var v = convert(i); - Assert.Equal(v.ToString(), extension(v)); - } - } - } -} diff --git a/test/Generators/Microsoft.Gen.EnumStrings/Generated/Microsoft.Gen.EnumStrings.Generated.Tests.csproj b/test/Generators/Microsoft.Gen.EnumStrings/Generated/Microsoft.Gen.EnumStrings.Generated.Tests.csproj deleted file mode 100644 index 80febbdb554..00000000000 --- a/test/Generators/Microsoft.Gen.EnumStrings/Generated/Microsoft.Gen.EnumStrings.Generated.Tests.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - Microsoft.Gen.EnumStrings.Test - Tests for code generated by Microsoft.Gen.EnumStrings. - - - - $(TestNetCoreTargetFrameworks) - $(TestNetCoreTargetFrameworks)$(ConditionalNet462) - true - true - true - true - $(NoWarn);IDE0161;S1144 - - - - - - - - - - - - - - - - diff --git a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/AssemblyLevel.cs b/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/AssemblyLevel.cs deleted file mode 100644 index c9c38f97d2d..00000000000 --- a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/AssemblyLevel.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.EnumStrings; - -[assembly: EnumStrings(typeof(TestClasses.Level))] -[assembly: EnumStrings(typeof(TestClasses.Medal))] - -namespace TestClasses -{ - public enum Level - { - One, - Two, - Three, - } - - public enum Medal - { - Bronze, - Silver, - Gold, - } -} diff --git a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Flags.cs b/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Flags.cs deleted file mode 100644 index 9a6cf5bdd41..00000000000 --- a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Flags.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using Microsoft.Extensions.EnumStrings; - -namespace TestClasses -{ - [Flags] - [EnumStrings] - public enum Flags0 - { - } - - [Flags] - [EnumStrings] - public enum Flags1 - { - Zero = 1, - } - - [Flags] - [EnumStrings] - public enum Flags2 - { - Zero = 1, - One = 2, - } - - [Flags] - [EnumStrings] - public enum Flags3 - { - Zero = 1, - One = 2, - Two = 4, - } - - [Flags] - [EnumStrings] - public enum Flags4 - { - Zero = 1, - One = 2, - Two = 4, - Three = 8, - } - - [Flags] - [EnumStrings] - public enum Flags5 - { - Zero = 1, - One = 2, - Two = 4, - Three = 8, - Four = 16, - } - - [Flags] - [EnumStrings] - public enum Flags6 - { - Zero = 1, - One = 2, - Two = 4, - Three = 8, - Four = 16, - Five = 32, - } - - [Flags] - [EnumStrings] - public enum Flags7 - { - Zero = 1, - Two = 4, - Three = 8, - } - - [Flags] - [EnumStrings] - public enum Flags8 - { - Zero = 1, - Two = 4, - Three = 8, - Ten = 1024, - Eleven = 2048, - } - - [Flags] - [EnumStrings] - public enum Flags9 : ulong - { - Zero = 1, - Two = 4, - Three = 8, - Ten = 1024, - Eleven = 2048, - } -} diff --git a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Negative.cs b/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Negative.cs deleted file mode 100644 index 79bec7d5a74..00000000000 --- a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Negative.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.EnumStrings; - -namespace TestClasses -{ - [EnumStrings] - public enum Negative0 - { - MinusOne = -1 - } - - [EnumStrings] - public enum Negative1 - { - MinusOne = -1, - MinusTwo = -2, - MinusThree = -3, - MinusFour = -4, - MinusFive = -5, - MinusSix = -6, - } - - [EnumStrings] - public enum NegativeLong0 : long - { - MinusOne = -1 - } - - [EnumStrings] - public enum NegativeLong1 : long - { - MinusOne = -1, - MinusTwo = -2, - MinusThree = -3, - MinusFour = -4, - MinusFive = -5, - MinusSix = -6, - } -} diff --git a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Nested.cs b/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Nested.cs deleted file mode 100644 index 5630a219717..00000000000 --- a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Nested.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.EnumStrings; - -namespace TestClasses -{ - public static class Nested - { - [EnumStrings] - public enum Fruit - { - Banana, - Apple, - Peach, - } - } -} diff --git a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/NoNamespace.cs b/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/NoNamespace.cs deleted file mode 100644 index 2e17a88c821..00000000000 --- a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/NoNamespace.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.EnumStrings; - -[EnumStrings] -public enum NoNamespace -{ - Value -} diff --git a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Options.cs b/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Options.cs deleted file mode 100644 index 6808d9d01ba..00000000000 --- a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Options.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.EnumStrings; - -[assembly: EnumStrings(typeof(TestClasses.Options1), ExtensionNamespace = "NamespaceA", ExtensionClassName = "ClassB", ExtensionMethodName = "MethodC")] - -namespace TestClasses -{ - [EnumStrings(ExtensionNamespace = "NamespaceX", ExtensionClassName = "ClassY", ExtensionMethodName = "MethodZ", ExtensionClassModifiers = "public static")] - public enum Options0 - { - Option0, - } - - public enum Options1 - { - Option0, - Option1, - } -} diff --git a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Overlapping.cs b/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Overlapping.cs deleted file mode 100644 index 79b7a4e07e6..00000000000 --- a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Overlapping.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using Microsoft.Extensions.EnumStrings; - -#pragma warning disable CA1069 -#pragma warning disable CA1027 - -namespace TestClasses -{ - [EnumStrings] - public enum Overlapping1 - { - Zero, - One, - Un = One, - Two, - Deux = Two, - Three, - Four, - } - - [Flags] - [EnumStrings] - public enum Overlapping2 - { - None = 0, - One = 1, - Two = 2, - Deux = 2, - Four = 4, - Eight = 8, - Twelve = 12, - Douze = 12, - Thirteen = 13, - } -} diff --git a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Sizes.cs b/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Sizes.cs deleted file mode 100644 index 045f72f94a3..00000000000 --- a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/Sizes.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.EnumStrings; - -namespace TestClasses -{ - [EnumStrings] - public enum Size0 - { - } - - [EnumStrings] - public enum Size1 - { - Zero, - } - - [EnumStrings] - public enum Size2 - { - Zero, - One, - } - - [EnumStrings] - public enum Size3 - { - Zero, - One, - Two, - } - - [EnumStrings] - public enum Size4 - { - Zero, - One, - Two, - Three, - } - - [EnumStrings] - public enum Size5 - { - Zero, - One, - Two, - Three, - Four, - } - - [EnumStrings] - public enum Size6 - { - One = 1, - Two = 2, - Three = 3, - Four = 4, - Five = 5, - Six = 6, - } - - [EnumStrings] - public enum Size7 - { - One = 1, - Two = 2, - Three = 3, - Four = 4, - Five = 5, - Six = 6, - - Ten = 10, - Eleven = 11, - Twelve = 12, - } -} diff --git a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/UnderlyingTypes.cs b/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/UnderlyingTypes.cs deleted file mode 100644 index 1d55f4d5f31..00000000000 --- a/test/Generators/Microsoft.Gen.EnumStrings/TestClasses/UnderlyingTypes.cs +++ /dev/null @@ -1,203 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.EnumStrings; - -#pragma warning disable S4022 -#pragma warning disable S2344 -#pragma warning disable S1939 - -namespace TestClasses -{ - [EnumStrings] - public enum SByteEnum1 : sbyte - { - One, - } - - [EnumStrings] - public enum SByteEnum2 : sbyte - { - One, - Two, - Three, - Four - } - - [EnumStrings] - public enum SByteEnum3 : sbyte - { - One, - Two, - Three, - Four = -42 - } - - [EnumStrings] - public enum ByteEnum1 : byte - { - One, - } - - [EnumStrings] - public enum ByteEnum2 : byte - { - One, - Two, - Three, - Four - } - - [EnumStrings] - public enum ByteEnum3 : byte - { - One, - Two, - Three, - Four = 42 - } - - [EnumStrings] - public enum ShortEnum1 : short - { - One, - } - - [EnumStrings] - public enum ShortEnum2 : short - { - One, - Two, - Three, - Four - } - - [EnumStrings] - public enum ShortEnum3 : short - { - One, - Two, - Three, - Four = -42 - } - - [EnumStrings] - public enum UShortEnum1 : ushort - { - One, - } - - [EnumStrings] - public enum UShortEnum2 : ushort - { - One, - Two, - Three, - Four - } - - [EnumStrings] - public enum UShortEnum3 : ushort - { - One, - Two, - Three, - Four = 42 - } - - [EnumStrings] - public enum IntEnum1 : int - { - One, - } - - [EnumStrings] - public enum IntEnum2 : int - { - One, - Two, - Three, - Four - } - - [EnumStrings] - public enum IntEnum3 : int - { - One, - Two, - Three, - Four = -42 - } - - [EnumStrings] - public enum UIntEnum1 : uint - { - One, - } - - [EnumStrings] - public enum UIntEnum2 : uint - { - One, - Two, - Three, - Four - } - - [EnumStrings] - public enum UIntEnum3 : uint - { - One, - Two, - Three, - Four = 42 - } - - [EnumStrings] - public enum LongEnum1 : long - { - One, - } - - [EnumStrings] - public enum LongEnum2 : long - { - One, - Two, - Three, - Four - } - - [EnumStrings] - public enum LongEnum3 : long - { - One, - Two, - Three, - Four = -42 - } - - [EnumStrings] - public enum ULongEnum1 : ulong - { - One, - } - - [EnumStrings] - public enum ULongEnum2 : ulong - { - One, - Two, - Three, - Four - } - - [EnumStrings] - public enum ULongEnum3 : ulong - { - One, - Two, - Three, - Four = 42 - } -} diff --git a/test/Generators/Microsoft.Gen.EnumStrings/Unit/EmitterTests.cs b/test/Generators/Microsoft.Gen.EnumStrings/Unit/EmitterTests.cs deleted file mode 100644 index eed6b2ee609..00000000000 --- a/test/Generators/Microsoft.Gen.EnumStrings/Unit/EmitterTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Frozen; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Threading.Tasks; -using Microsoft.Extensions.EnumStrings; -using Microsoft.Gen.Shared; -using Xunit; - -namespace Microsoft.Gen.EnumStrings.Test; - -public class EmitterTests -{ - [Fact] - public async Task TestEmitter() - { - var sources = new List(); - foreach (var file in Directory.GetFiles("TestClasses")) - { - sources.Add("#define NETCOREAPP3_1_OR_GREATER\n" + File.ReadAllText(file)); - } - - // try it without the frozen collections - var (d, r) = await RoslynTestUtils.RunGenerator( - new EnumStringsGenerator(), - new[] - { - Assembly.GetAssembly(typeof(EnumStringsAttribute))!, - }, - sources).ConfigureAwait(false); - - Assert.Empty(d); - _ = Assert.Single(r); - - // try it again with the frozen collections, this is what we need to compare with the golden files - (d, r) = await RoslynTestUtils.RunGenerator( - new EnumStringsGenerator(), - new[] - { - Assembly.GetAssembly(typeof(EnumStringsAttribute))!, - Assembly.GetAssembly(typeof(FrozenDictionary))!, - }, - sources).ConfigureAwait(false); - - Assert.Empty(d); - _ = Assert.Single(r); - - var golden = File.ReadAllText($"GoldenFiles/Microsoft.Gen.EnumStrings/Microsoft.Gen.EnumStrings.EnumStringsGenerator/EnumStrings.g.cs"); - var result = r[0].SourceText.ToString(); - Assert.Equal(golden, result); - } -} diff --git a/test/Generators/Microsoft.Gen.EnumStrings/Unit/Microsoft.Gen.EnumStrings.Unit.Tests.csproj b/test/Generators/Microsoft.Gen.EnumStrings/Unit/Microsoft.Gen.EnumStrings.Unit.Tests.csproj deleted file mode 100644 index e907d6768f6..00000000000 --- a/test/Generators/Microsoft.Gen.EnumStrings/Unit/Microsoft.Gen.EnumStrings.Unit.Tests.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - Microsoft.Gen.EnumStrings.Test - Unit tests for Microsoft.Gen.EnumStrings. - - - - true - true - - - - - - - - - - - - - - - - - diff --git a/test/Generators/Microsoft.Gen.EnumStrings/Unit/ParserTests.cs b/test/Generators/Microsoft.Gen.EnumStrings/Unit/ParserTests.cs deleted file mode 100644 index 80d005b75ec..00000000000 --- a/test/Generators/Microsoft.Gen.EnumStrings/Unit/ParserTests.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Reflection; -using System.Threading.Tasks; -using Microsoft.Extensions.EnumStrings; -using Microsoft.Gen.Shared; -using Xunit; - -namespace Microsoft.Gen.EnumStrings.Test; - -public static class ParserTests -{ - [Fact] - public static async Task InvalidTypeLevelUses() - { - var args = new[] - { - ( "EnumStrings(typeof(string))", DiagDescriptors.IncorrectOverload ), - ( "EnumStrings(ExtensionClassName = \"A.B\")", DiagDescriptors.InvalidExtensionClassName ), - ( "EnumStrings(ExtensionClassName = \"123\")", DiagDescriptors.InvalidExtensionClassName ), - ( "EnumStrings(ExtensionMethodName = \"A.B\")", DiagDescriptors.InvalidExtensionMethodName ), - ( "EnumStrings(ExtensionMethodName = \"123\")", DiagDescriptors.InvalidExtensionMethodName ), - ( "EnumStrings(ExtensionNamespace = \"A.123\")", DiagDescriptors.InvalidExtensionNamespace ), - ( "EnumStrings(ExtensionNamespace = \"123\")", DiagDescriptors.InvalidExtensionNamespace ), - }; - - foreach (var (attrArg, diag) in args) - { - var source = @$" - using Microsoft.Extensions.EnumStrings; - - namespace Test - {{ - [/*0+*/{attrArg}/*-0*/] - public enum Color - {{ - Red, - Green, - Blue, - }} - }} - "; - - var (d, _) = await RoslynTestUtils.RunGenerator( - new EnumStringsGenerator(), - new[] { Assembly.GetAssembly(typeof(EnumStringsAttribute))! }, - new[] { source }).ConfigureAwait(false); - - Assert.Equal(1, d.Count); - source.AssertDiagnostic(0, diag, d[0]); - - (d, _) = await RoslynTestUtils.RunGenerator( - new EnumStringsGenerator(), - new[] - { - Assembly.GetAssembly(typeof(EnumStringsAttribute))!, - Assembly.GetAssembly(typeof(System.Collections.Frozen.FrozenDictionary))!, - }, - new[] { source }).ConfigureAwait(false); - - Assert.Equal(1, d.Count); - source.AssertDiagnostic(0, diag, d[0]); - } - } - - [Fact] - public static async Task InvalidAssemblyLevelUses() - { - var args = new[] - { - ( "EnumStrings", DiagDescriptors.IncorrectOverload ), - ( "EnumStrings(typeof(MyClass))", DiagDescriptors.InvalidEnumType ), - ( "EnumStrings(typeof(MyJunk))", DiagDescriptors.InvalidEnumType ), - ( "EnumStrings(typeof(Color), ExtensionClassName = \"A.B\")", DiagDescriptors.InvalidExtensionClassName ), - ( "EnumStrings(typeof(Color), ExtensionClassName = \"123\")", DiagDescriptors.InvalidExtensionClassName ), - ( "EnumStrings(typeof(Color), ExtensionMethodName = \"A.B\")", DiagDescriptors.InvalidExtensionMethodName ), - ( "EnumStrings(typeof(Color), ExtensionMethodName = \"123\")", DiagDescriptors.InvalidExtensionMethodName ), - ( "EnumStrings(typeof(Color), ExtensionNamespace = \"A.123\")", DiagDescriptors.InvalidExtensionNamespace ), - ( "EnumStrings(typeof(Color), ExtensionNamespace = \"123\")", DiagDescriptors.InvalidExtensionNamespace ), - }; - - foreach (var (attrArg, diag) in args) - { - var source = @$" - using Microsoft.Extensions.EnumStrings; - - [assembly: /*0+*/{attrArg}/*-0*/] - - public class MyClass - {{ - }} - - public enum Color - {{ - Red, - Green, - Blue, - }} - "; - - var (d, _) = await RoslynTestUtils.RunGenerator( - new EnumStringsGenerator(), - new[] { Assembly.GetAssembly(typeof(EnumStringsAttribute))! }, - new[] { source }).ConfigureAwait(false); - - Assert.Equal(1, d.Count); - source.AssertDiagnostic(0, diag, d[0]); - } - } -} diff --git a/test/Generators/Microsoft.Gen.Logging/Generated/LogMethodTests.cs b/test/Generators/Microsoft.Gen.Logging/Generated/LogMethodTests.cs index 19e9d8e450b..2753fcc9a2b 100644 --- a/test/Generators/Microsoft.Gen.Logging/Generated/LogMethodTests.cs +++ b/test/Generators/Microsoft.Gen.Logging/Generated/LogMethodTests.cs @@ -209,7 +209,7 @@ public void CollectionTest() collector.Clear(); CollectionTestExtensions.M9(logger, LogLevel.Critical, 0, new ArgumentException("Foo"), 1); - TestCollection(3, collector); + AssertLastState(collector, new("p0", "0"), new("p1", "1")); } [Fact] @@ -751,7 +751,7 @@ public void AtSymbolsTest() collector.Clear(); AtSymbolsTestExtensions.M3(logger, LogLevel.Debug, o); - Assert.Equal("42", collector.LatestRecord.StructuredState!.GetValue("event_class")); + Assert.Equal("42", collector.LatestRecord.StructuredState!.GetValue("event.class")); collector.Clear(); AtSymbolsTestExtensions.M5(logger, LogLevel.Debug, o); @@ -892,7 +892,7 @@ public void FormattableTest() collector.Clear(); FormattableTestExtensions.Method2(logger, new FormattableTestExtensions.ComplexObj()); Assert.Equal(1, collector.Count); - Assert.Equal("Formatted!", collector.LatestRecord.StructuredState!.GetValue("p1_P1")); + Assert.Equal("Formatted!", collector.LatestRecord.StructuredState!.GetValue("p1.P1")); collector.Clear(); FormattableTestExtensions.Method3(logger, default); @@ -903,11 +903,10 @@ public void FormattableTest() private static void AssertLastState(FakeLogCollector collector, params KeyValuePair[] expected) { var rol = (IReadOnlyList>)collector.LatestRecord.State!; - int count = 0; + foreach (var kvp in expected) { Assert.Equal(kvp.Value, rol.GetValue(kvp.Key)); - count++; } } diff --git a/test/Generators/Microsoft.Gen.Logging/Generated/LogPropertiesRedactionTests.cs b/test/Generators/Microsoft.Gen.Logging/Generated/LogPropertiesRedactionTests.cs index a8171954bfe..c42b599b31f 100644 --- a/test/Generators/Microsoft.Gen.Logging/Generated/LogPropertiesRedactionTests.cs +++ b/test/Generators/Microsoft.Gen.Logging/Generated/LogPropertiesRedactionTests.cs @@ -33,7 +33,7 @@ public void RedactsWhenRedactorProviderIsAvailableInTheInstance() var expectedState = new Dictionary { ["P0"] = "----", - ["p1_StringPropertyBase"] = new('-', classToRedact.StringPropertyBase.Length), + ["p1.StringPropertyBase"] = new('-', classToRedact.StringPropertyBase.Length), ["{OriginalFormat}"] = "LogProperties with redaction: {P0}" }; @@ -59,7 +59,7 @@ public void RedactsWhenDefaultAttrCtorAndRedactorProviderIsInTheInstance() var expectedState = new Dictionary { ["p0"] = "----", - ["p1_StringPropertyBase"] = new('-', classToRedact.StringPropertyBase.Length), + ["p1.StringPropertyBase"] = new('-', classToRedact.StringPropertyBase.Length), }; collector.LatestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState); @@ -81,14 +81,14 @@ public void RedactsWhenLogMethodIsStaticNoParams() var expectedState = new Dictionary { - ["classToLog_StringProperty"] = new('+', classToRedact.StringProperty.Length), - ["classToLog_StringPropertyBase"] = new('-', classToRedact.StringPropertyBase.Length), - ["classToLog_SimplifiedNullableIntProperty"] = classToRedact.SimplifiedNullableIntProperty.ToString(CultureInfo.InvariantCulture), - ["classToLog_GetOnlyProperty"] = new('-', classToRedact.GetOnlyProperty.Length), - ["classToLog_TransitiveProp_TransitiveNumberProp"] = classToRedact.TransitiveProp.TransitiveNumberProp.ToString(CultureInfo.InvariantCulture), - ["classToLog_TransitiveProp_TransitiveStringProp"] = new('-', classToRedact.TransitiveProp.TransitiveStringProp.Length), - ["classToLog_NoRedactionProp"] = classToRedact.NoRedactionProp, - ["classToLog_NonFormattableProperty"] = new('-', classToRedact.NonFormattableProperty.ToString().Length), + ["classToLog.StringProperty"] = new('+', classToRedact.StringProperty.Length), + ["classToLog.StringPropertyBase"] = new('-', classToRedact.StringPropertyBase.Length), + ["classToLog.SimplifiedNullableIntProperty"] = classToRedact.SimplifiedNullableIntProperty.ToString(CultureInfo.InvariantCulture), + ["classToLog.GetOnlyProperty"] = new('-', classToRedact.GetOnlyProperty.Length), + ["classToLog.TransitiveProp.TransitiveNumberProp"] = classToRedact.TransitiveProp.TransitiveNumberProp.ToString(CultureInfo.InvariantCulture), + ["classToLog.TransitiveProp.TransitiveStringProp"] = new('-', classToRedact.TransitiveProp.TransitiveStringProp.Length), + ["classToLog.NoRedactionProp"] = classToRedact.NoRedactionProp, + ["classToLog.NonFormattableProperty"] = new('-', classToRedact.NonFormattableProperty.ToString().Length), ["{OriginalFormat}"] = "No template params" }; @@ -112,14 +112,14 @@ public void RedactsWhenDefaultAttrCtorAndIsStaticNoParams() var expectedState = new Dictionary { - ["classToLog_StringProperty"] = new('+', classToRedact.StringProperty.Length), - ["classToLog_StringPropertyBase"] = new('-', classToRedact.StringPropertyBase.Length), - ["classToLog_SimplifiedNullableIntProperty"] = classToRedact.SimplifiedNullableIntProperty.ToString(CultureInfo.InvariantCulture), - ["classToLog_GetOnlyProperty"] = new('-', classToRedact.GetOnlyProperty.Length), - ["classToLog_TransitiveProp_TransitiveNumberProp"] = classToRedact.TransitiveProp.TransitiveNumberProp.ToString(CultureInfo.InvariantCulture), - ["classToLog_TransitiveProp_TransitiveStringProp"] = new('-', classToRedact.TransitiveProp.TransitiveStringProp.Length), - ["classToLog_NoRedactionProp"] = classToRedact.NoRedactionProp, - ["classToLog_NonFormattableProperty"] = new('-', classToRedact.NonFormattableProperty.ToString().Length), + ["classToLog.StringProperty"] = new('+', classToRedact.StringProperty.Length), + ["classToLog.StringPropertyBase"] = new('-', classToRedact.StringPropertyBase.Length), + ["classToLog.SimplifiedNullableIntProperty"] = classToRedact.SimplifiedNullableIntProperty.ToString(CultureInfo.InvariantCulture), + ["classToLog.GetOnlyProperty"] = new('-', classToRedact.GetOnlyProperty.Length), + ["classToLog.TransitiveProp.TransitiveNumberProp"] = classToRedact.TransitiveProp.TransitiveNumberProp.ToString(CultureInfo.InvariantCulture), + ["classToLog.TransitiveProp.TransitiveStringProp"] = new('-', classToRedact.TransitiveProp.TransitiveStringProp.Length), + ["classToLog.NoRedactionProp"] = classToRedact.NoRedactionProp, + ["classToLog.NonFormattableProperty"] = new('-', classToRedact.NonFormattableProperty.ToString().Length), }; collector.LatestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState); @@ -142,8 +142,8 @@ public void RedactsWhenLogMethodIsStaticTwoParams() var expectedState = new Dictionary { ["StringProperty"] = "-----------", - ["complexParam_TransitiveNumberProp"] = classToRedact.TransitiveNumberProp.ToString(CultureInfo.InvariantCulture), - ["complexParam_TransitiveStringProp"] = new('-', classToRedact.TransitiveStringProp.Length), + ["complexParam.TransitiveNumberProp"] = classToRedact.TransitiveNumberProp.ToString(CultureInfo.InvariantCulture), + ["complexParam.TransitiveStringProp"] = new('-', classToRedact.TransitiveStringProp.Length), ["{OriginalFormat}"] = "Only {StringProperty} as param" }; @@ -168,8 +168,8 @@ public void RedactsWhenDefaultAttrCtorAndIsStaticTwoParams() var expectedState = new Dictionary { ["stringProperty"] = "-----------", - ["complexParam_TransitiveNumberProp"] = classToRedact.TransitiveNumberProp.ToString(CultureInfo.InvariantCulture), - ["complexParam_TransitiveStringProp"] = new('-', classToRedact.TransitiveStringProp.Length), + ["complexParam.TransitiveNumberProp"] = classToRedact.TransitiveNumberProp.ToString(CultureInfo.InvariantCulture), + ["complexParam.TransitiveStringProp"] = new('-', classToRedact.TransitiveStringProp.Length), }; collector.LatestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState); diff --git a/test/Generators/Microsoft.Gen.Logging/Generated/LogPropertiesTests.cs b/test/Generators/Microsoft.Gen.Logging/Generated/LogPropertiesTests.cs index 5aca5bbe07e..a840082f8e6 100644 --- a/test/Generators/Microsoft.Gen.Logging/Generated/LogPropertiesTests.cs +++ b/test/Generators/Microsoft.Gen.Logging/Generated/LogPropertiesTests.cs @@ -43,9 +43,9 @@ public void LogPropertiesEnumerables() Assert.Equal(1, _logger.Collector.Count); var ss = _logger.LatestRecord.StructuredState!.ToDictionary(x => x.Key, x => x.Value); - Assert.Equal("[\"1\",\"2\",\"3\"]", ss["myProps_P5"]); - Assert.Equal("[\"4\",\"5\",\"6\"]", ss["myProps_P6"]); - Assert.Equal("{\"Seven\"=\"7\",\"Eight\"=\"8\",\"Nine\"=\"9\"}", ss["myProps_P7"]); + Assert.Equal("[\"1\",\"2\",\"3\"]", ss["myProps.P5"]); + Assert.Equal("[\"4\",\"5\",\"6\"]", ss["myProps.P6"]); + Assert.Equal("{\"Seven\"=\"7\",\"Eight\"=\"8\",\"Nine\"=\"9\"}", ss["myProps.P7"]); } [Fact] @@ -122,29 +122,29 @@ public void LogPropertiesSpecialTypes() Assert.Equal(19, state.Count); #endif - Assert.Equal(props.P0.ToString(CultureInfo.InvariantCulture), state!.GetValue("p_P0")); - Assert.Equal(props.P1.ToString(CultureInfo.InvariantCulture), state!.GetValue("p_P1")); - Assert.Equal(props.P2.ToString(null, CultureInfo.InvariantCulture), state!.GetValue("p_P2")); - Assert.Equal(props.P3.ToString(), state!.GetValue("p_P3")); - Assert.Equal(props.P4.ToString(), state!.GetValue("p_P4")); - Assert.Equal(props.P5.ToString(), state!.GetValue("p_P5")); - Assert.Equal(props.P6.ToString(), state!.GetValue("p_P6")); - Assert.Equal(props.P7.ToString(), state!.GetValue("p_P7")); - Assert.Equal(props.P8.ToString(), state!.GetValue("p_P8")); - Assert.Equal(props.P9.ToString(), state!.GetValue("p_P9")); - Assert.Equal(props.P10.ToString(CultureInfo.InvariantCulture), state!.GetValue("p_P10")); - Assert.Equal(props.P11.ToString(CultureInfo.InvariantCulture), state!.GetValue("p_P11")); - Assert.Equal(props.P12.ToString(), state!.GetValue("p_P12")); - Assert.Equal(props.P13.ToString(), state!.GetValue("p_P13")); - Assert.Equal(props.P14.ToString(), state!.GetValue("p_P14")); - Assert.Equal(props.P15.ToString(), state!.GetValue("p_P15")); - Assert.Equal(props.P16.ToString(), state!.GetValue("p_P16")); - Assert.Equal(props.P17.ToString(), state!.GetValue("p_P17")); - Assert.Equal(props.P18.ToString(), state!.GetValue("p_P18")); + Assert.Equal(props.P0.ToString(CultureInfo.InvariantCulture), state!.GetValue("p.P0")); + Assert.Equal(props.P1.ToString(CultureInfo.InvariantCulture), state!.GetValue("p.P1")); + Assert.Equal(props.P2.ToString(null, CultureInfo.InvariantCulture), state!.GetValue("p.P2")); + Assert.Equal(props.P3.ToString(), state!.GetValue("p.P3")); + Assert.Equal(props.P4.ToString(), state!.GetValue("p.P4")); + Assert.Equal(props.P5.ToString(), state!.GetValue("p.P5")); + Assert.Equal(props.P6.ToString(), state!.GetValue("p.P6")); + Assert.Equal(props.P7.ToString(), state!.GetValue("p.P7")); + Assert.Equal(props.P8.ToString(), state!.GetValue("p.P8")); + Assert.Equal(props.P9.ToString(), state!.GetValue("p.P9")); + Assert.Equal(props.P10.ToString(CultureInfo.InvariantCulture), state!.GetValue("p.P10")); + Assert.Equal(props.P11.ToString(CultureInfo.InvariantCulture), state!.GetValue("p.P11")); + Assert.Equal(props.P12.ToString(), state!.GetValue("p.P12")); + Assert.Equal(props.P13.ToString(), state!.GetValue("p.P13")); + Assert.Equal(props.P14.ToString(), state!.GetValue("p.P14")); + Assert.Equal(props.P15.ToString(), state!.GetValue("p.P15")); + Assert.Equal(props.P16.ToString(), state!.GetValue("p.P16")); + Assert.Equal(props.P17.ToString(), state!.GetValue("p.P17")); + Assert.Equal(props.P18.ToString(), state!.GetValue("p.P18")); #if NET6_0_OR_GREATER - Assert.Equal(props.P19.ToString(CultureInfo.InvariantCulture), state!.GetValue("p_P19")); - Assert.Equal(props.P20.ToString(CultureInfo.InvariantCulture), state!.GetValue("p_P20")); + Assert.Equal(props.P19.ToString(CultureInfo.InvariantCulture), state!.GetValue("p.P19")); + Assert.Equal(props.P20.ToString(CultureInfo.InvariantCulture), state!.GetValue("p.P20")); #endif } @@ -175,17 +175,17 @@ public void LogPropertiesNullHandling() var ss = collector.LatestRecord.StructuredState!.ToDictionary(x => x.Key, x => x.Value); Assert.Equal(11, ss.Count); - Assert.Null(ss["p_P0"]); - Assert.Null(ss["p_P1"]); - Assert.Equal(props.P2.ToString(null, CultureInfo.InvariantCulture), ss["p_P2"]); - Assert.Null(ss["p_P3"]); - Assert.Equal("I refuse to be formatted", ss["p_P4"]); - Assert.Null(ss["p_P5"]); - Assert.Null(ss["p_P6"]); - Assert.Equal("-", ss["p_P7"]); - Assert.Null(ss["p_P8"]); - Assert.Equal("------------------------", ss["p_P9"]); - Assert.Equal("null", ss["p_P10"]); + Assert.Null(ss["p.P0"]); + Assert.Null(ss["p.P1"]); + Assert.Equal(props.P2.ToString(null, CultureInfo.InvariantCulture), ss["p.P2"]); + Assert.Null(ss["p.P3"]); + Assert.Equal("I refuse to be formatted", ss["p.P4"]); + Assert.Null(ss["p.P5"]); + Assert.Null(ss["p.P6"]); + Assert.Equal("-", ss["p.P7"]); + Assert.Null(ss["p.P8"]); + Assert.Equal("------------------------", ss["p.P9"]); + Assert.Equal("null", ss["p.P10"]); collector.Clear(); LogPropertiesNullHandlingExtensions.M1(logger, props); @@ -193,10 +193,10 @@ public void LogPropertiesNullHandling() ss = collector.LatestRecord.StructuredState!.ToDictionary(x => x.Key, x => x.Value); Assert.Equal(4, ss.Count); - Assert.Equal(props.P2.ToString(null, CultureInfo.InvariantCulture), ss["p_P2"]); - Assert.Equal("I refuse to be formatted", ss["p_P4"]); - Assert.Equal("-", ss["p_P7"]); - Assert.Equal("------------------------", ss["p_P9"]); + Assert.Equal(props.P2.ToString(null, CultureInfo.InvariantCulture), ss["p.P2"]); + Assert.Equal("I refuse to be formatted", ss["p.P4"]); + Assert.Equal("-", ss["p.P7"]); + Assert.Equal("------------------------", ss["p.P9"]); } [Fact] @@ -231,60 +231,60 @@ public void LogPropertiesTest() var expectedState = new Dictionary { ["classToLog_StringProperty_1"] = "microsoft.com", - ["classToLog_StringProperty"] = classToLog.StringProperty, - ["classToLog_SimplifiedNullableIntProperty"] = null, - ["classToLog_ExplicitNullableIntProperty"] = classToLog.ExplicitNullableIntProperty.ToString(), - ["classToLog_GetOnlyProperty"] = classToLog.GetOnlyProperty.ToString(CultureInfo.InvariantCulture), - ["classToLog_VirtualPropertyBase"] = classToLog.VirtualPropertyBase, - ["classToLog_NonVirtualPropertyBase"] = classToLog.NonVirtualPropertyBase, - ["classToLog_TransitivePropertyArray"] = LoggerMessageHelper.Stringify(classToLog.TransitivePropertyArray), - ["classToLog_TransitiveProperty_TransitiveNumberProp"] + ["classToLog.StringProperty"] = classToLog.StringProperty, + ["classToLog.SimplifiedNullableIntProperty"] = null, + ["classToLog.ExplicitNullableIntProperty"] = classToLog.ExplicitNullableIntProperty.ToString(), + ["classToLog.GetOnlyProperty"] = classToLog.GetOnlyProperty.ToString(CultureInfo.InvariantCulture), + ["classToLog.VirtualPropertyBase"] = classToLog.VirtualPropertyBase, + ["classToLog.NonVirtualPropertyBase"] = classToLog.NonVirtualPropertyBase, + ["classToLog.TransitivePropertyArray"] = LoggerMessageHelper.Stringify(classToLog.TransitivePropertyArray), + ["classToLog.TransitiveProperty.TransitiveNumberProp"] = classToLog.TransitiveProperty.TransitiveNumberProp.ToString(CultureInfo.InvariantCulture), - ["classToLog_TransitiveProperty_TransitiveStringProp"] = classToLog.TransitiveProperty.TransitiveStringProp, - ["classToLog_TransitiveProperty_InnerTransitiveProperty_IntegerProperty"] + ["classToLog.TransitiveProperty.TransitiveStringProp"] = classToLog.TransitiveProperty.TransitiveStringProp, + ["classToLog.TransitiveProperty.InnerTransitiveProperty.IntegerProperty"] = classToLog.TransitiveProperty.InnerTransitiveProperty.IntegerProperty.ToString(CultureInfo.InvariantCulture), - ["classToLog_TransitiveProperty_InnerTransitiveProperty_DateTimeProperty"] + ["classToLog.TransitiveProperty.InnerTransitiveProperty.DateTimeProperty"] = classToLog.TransitiveProperty.InnerTransitiveProperty.DateTimeProperty.ToString(CultureInfo.InvariantCulture), - ["classToLog_AnotherTransitiveProperty_IntegerProperty"] = null, // Since AnotherTransitiveProperty is null - ["classToLog_StringPropertyBase"] = classToLog.StringPropertyBase, - ["classToLog_VirtualInterimProperty"] = classToLog.VirtualInterimProperty.ToInvariantString(), - ["classToLog_InterimProperty"] = classToLog.InterimProperty.ToString(CultureInfo.InvariantCulture), - ["classToLog_TransitiveProperty_TransitiveDerivedProp"] = classToLog.TransitiveProperty.TransitiveDerivedProp.ToInvariantString(), - ["classToLog_TransitiveProperty_TransitiveVirtualProp"] = classToLog.TransitiveProperty.TransitiveVirtualProp.ToInvariantString(), - ["classToLog_TransitiveProperty_TransitiveGenericProp_GenericProp"] + ["classToLog.AnotherTransitiveProperty.IntegerProperty"] = null, // Since AnotherTransitiveProperty is null + ["classToLog.StringPropertyBase"] = classToLog.StringPropertyBase, + ["classToLog.VirtualInterimProperty"] = classToLog.VirtualInterimProperty.ToInvariantString(), + ["classToLog.InterimProperty"] = classToLog.InterimProperty.ToString(CultureInfo.InvariantCulture), + ["classToLog.TransitiveProperty.TransitiveDerivedProp"] = classToLog.TransitiveProperty.TransitiveDerivedProp.ToInvariantString(), + ["classToLog.TransitiveProperty.TransitiveVirtualProp"] = classToLog.TransitiveProperty.TransitiveVirtualProp.ToInvariantString(), + ["classToLog.TransitiveProperty.TransitiveGenericProp.GenericProp"] = classToLog.TransitiveProperty.TransitiveGenericProp.GenericProp, - ["classToLog_PropertyOfGenerics_GenericProp"] = classToLog.PropertyOfGenerics.GenericProp.ToInvariantString(), - ["classToLog_CustomStructProperty_LongProperty"] = classToLog.CustomStructProperty.LongProperty.ToInvariantString(), - ["classToLog_CustomStructProperty_TransitiveStructProperty_DateTimeOffsetProperty"] + ["classToLog.PropertyOfGenerics.GenericProp"] = classToLog.PropertyOfGenerics.GenericProp.ToInvariantString(), + ["classToLog.CustomStructProperty.LongProperty"] = classToLog.CustomStructProperty.LongProperty.ToInvariantString(), + ["classToLog.CustomStructProperty.TransitiveStructProperty.DateTimeOffsetProperty"] = classToLog.CustomStructProperty.TransitiveStructProperty.DateTimeOffsetProperty.ToString(CultureInfo.InvariantCulture), - ["classToLog_CustomStructProperty_NullableTransitiveStructProperty_DateTimeOffsetProperty"] + ["classToLog.CustomStructProperty.NullableTransitiveStructProperty.DateTimeOffsetProperty"] = classToLog.CustomStructProperty.NullableTransitiveStructProperty?.DateTimeOffsetProperty.ToString(CultureInfo.InvariantCulture), - ["classToLog_CustomStructProperty_NullableTransitiveStructProperty2_DateTimeOffsetProperty"] + ["classToLog.CustomStructProperty.NullableTransitiveStructProperty2.DateTimeOffsetProperty"] = classToLog.CustomStructProperty.NullableTransitiveStructProperty2?.DateTimeOffsetProperty.ToString(CultureInfo.InvariantCulture), - ["classToLog_CustomStructNullableProperty_LongProperty"] = classToLog.CustomStructNullableProperty?.LongProperty.ToInvariantString(), - ["classToLog_CustomStructNullableProperty_TransitiveStructProperty_DateTimeOffsetProperty"] + ["classToLog.CustomStructNullableProperty.LongProperty"] = classToLog.CustomStructNullableProperty?.LongProperty.ToInvariantString(), + ["classToLog.CustomStructNullableProperty.TransitiveStructProperty.DateTimeOffsetProperty"] = classToLog.CustomStructNullableProperty?.TransitiveStructProperty.DateTimeOffsetProperty.ToString(CultureInfo.InvariantCulture), - ["classToLog_CustomStructNullableProperty_NullableTransitiveStructProperty_DateTimeOffsetProperty"] + ["classToLog.CustomStructNullableProperty.NullableTransitiveStructProperty.DateTimeOffsetProperty"] = classToLog.CustomStructNullableProperty?.NullableTransitiveStructProperty?.DateTimeOffsetProperty.ToString(CultureInfo.InvariantCulture), - ["classToLog_CustomStructNullableProperty_NullableTransitiveStructProperty2_DateTimeOffsetProperty"] + ["classToLog.CustomStructNullableProperty.NullableTransitiveStructProperty2.DateTimeOffsetProperty"] = classToLog.CustomStructNullableProperty?.NullableTransitiveStructProperty2?.DateTimeOffsetProperty.ToString(CultureInfo.InvariantCulture), - ["classToLog_CustomStructNullableProperty2_LongProperty"] = classToLog.CustomStructNullableProperty2?.LongProperty.ToInvariantString(), - ["classToLog_CustomStructNullableProperty2_TransitiveStructProperty_DateTimeOffsetProperty"] + ["classToLog.CustomStructNullableProperty2.LongProperty"] = classToLog.CustomStructNullableProperty2?.LongProperty.ToInvariantString(), + ["classToLog.CustomStructNullableProperty2.TransitiveStructProperty.DateTimeOffsetProperty"] = classToLog.CustomStructNullableProperty2?.TransitiveStructProperty.DateTimeOffsetProperty.ToString(CultureInfo.InvariantCulture), - ["classToLog_CustomStructNullableProperty2_NullableTransitiveStructProperty_DateTimeOffsetProperty"] + ["classToLog.CustomStructNullableProperty2.NullableTransitiveStructProperty.DateTimeOffsetProperty"] = classToLog.CustomStructNullableProperty2?.NullableTransitiveStructProperty?.DateTimeOffsetProperty.ToString(CultureInfo.InvariantCulture), - ["classToLog_CustomStructNullableProperty2_NullableTransitiveStructProperty2_DateTimeOffsetProperty"] + ["classToLog.CustomStructNullableProperty2.NullableTransitiveStructProperty2.DateTimeOffsetProperty"] = classToLog.CustomStructNullableProperty2?.NullableTransitiveStructProperty2?.DateTimeOffsetProperty.ToString(CultureInfo.InvariantCulture), ["{OriginalFormat}"] = "Only {classToLog_StringProperty_1} as param" @@ -306,7 +306,7 @@ public void LogPropertiesInTemplateTest() { ["StringProperty"] = StringProperty, ["ComplexParam"] = classToLog.ToString(), - ["complexParam_MyProperty"] = classToLog.MyProperty.ToInvariantString(), + ["complexParam.MyProperty"] = classToLog.MyProperty.ToInvariantString(), ["{OriginalFormat}"] = "Both {StringProperty} and {ComplexParam} as params" }; @@ -329,9 +329,9 @@ public void LogPropertiesNonStaticClassTest() var expectedState = new Dictionary { ["P0"] = StringParamValue, - ["p1_MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(), - ["p1_MyStringProperty"] = classToLog.MyStringProperty, - ["p1_AnotherStringProperty"] = classToLog.AnotherStringProperty, + ["p1.MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(), + ["p1.MyStringProperty"] = classToLog.MyStringProperty, + ["p1.AnotherStringProperty"] = classToLog.AnotherStringProperty, ["{OriginalFormat}"] = "LogProperties: {P0}" }; @@ -352,14 +352,14 @@ public void LogPropertyTestCustomStruct() var expectedState = new Dictionary { - ["structParam_LongProperty"] = structToLog.LongProperty.ToInvariantString(), - ["structParam_TransitiveStructProperty_DateTimeOffsetProperty"] + ["structParam.LongProperty"] = structToLog.LongProperty.ToInvariantString(), + ["structParam.TransitiveStructProperty.DateTimeOffsetProperty"] = structToLog.TransitiveStructProperty.DateTimeOffsetProperty.ToString(CultureInfo.InvariantCulture), - ["structParam_NullableTransitiveStructProperty_DateTimeOffsetProperty"] + ["structParam.NullableTransitiveStructProperty.DateTimeOffsetProperty"] = structToLog.NullableTransitiveStructProperty?.DateTimeOffsetProperty.ToString(CultureInfo.InvariantCulture), - ["structParam_NullableTransitiveStructProperty2_DateTimeOffsetProperty"] + ["structParam.NullableTransitiveStructProperty2.DateTimeOffsetProperty"] = structToLog.NullableTransitiveStructProperty2.Value.DateTimeOffsetProperty.ToString(CultureInfo.InvariantCulture), ["{OriginalFormat}"] = "Testing non-nullable struct here..." @@ -382,14 +382,14 @@ public void LogPropertyTestCustomNullableStruct() var expectedState = new Dictionary { - ["structParam_LongProperty"] = structToLog.Value.LongProperty.ToInvariantString(), - ["structParam_TransitiveStructProperty_DateTimeOffsetProperty"] + ["structParam.LongProperty"] = structToLog.Value.LongProperty.ToInvariantString(), + ["structParam.TransitiveStructProperty.DateTimeOffsetProperty"] = structToLog.Value.TransitiveStructProperty.DateTimeOffsetProperty.ToString(CultureInfo.InvariantCulture), - ["structParam_NullableTransitiveStructProperty_DateTimeOffsetProperty"] + ["structParam.NullableTransitiveStructProperty.DateTimeOffsetProperty"] = structToLog.Value.NullableTransitiveStructProperty.Value.DateTimeOffsetProperty.ToString(CultureInfo.InvariantCulture), - ["structParam_NullableTransitiveStructProperty2_DateTimeOffsetProperty"] + ["structParam.NullableTransitiveStructProperty2.DateTimeOffsetProperty"] = structToLog.Value.NullableTransitiveStructProperty2?.DateTimeOffsetProperty.ToString(CultureInfo.InvariantCulture), ["{OriginalFormat}"] = "Testing nullable struct here..." @@ -411,10 +411,10 @@ public void LogPropertyTestCustomExplicitNullableStruct() var expectedState = new Dictionary { - ["structParam_LongProperty"] = null, - ["structParam_TransitiveStructProperty_DateTimeOffsetProperty"] = null, - ["structParam_NullableTransitiveStructProperty_DateTimeOffsetProperty"] = null, - ["structParam_NullableTransitiveStructProperty2_DateTimeOffsetProperty"] = null, + ["structParam.LongProperty"] = null, + ["structParam.TransitiveStructProperty.DateTimeOffsetProperty"] = null, + ["structParam.NullableTransitiveStructProperty.DateTimeOffsetProperty"] = null, + ["structParam.NullableTransitiveStructProperty2.DateTimeOffsetProperty"] = null, ["{OriginalFormat}"] = "Testing explicit nullable struct here..." }; @@ -437,7 +437,7 @@ public void LogPropertiesDefaultAttrCtor() var expectedState = new Dictionary { - ["complexParam_MyProperty"] = classToLog.MyProperty.ToInvariantString() + ["complexParam.MyProperty"] = classToLog.MyProperty.ToInvariantString() }; latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState); @@ -464,8 +464,8 @@ public void LogPropertiesInterfaceArgument() var expectedState = new Dictionary { - ["complexParam_IntProperty"] = classToLog.IntProperty.ToInvariantString(), - ["complexParam_TransitiveProp_IntegerProperty"] = classToLog.TransitiveProp.IntegerProperty.ToInvariantString(), + ["complexParam.IntProperty"] = classToLog.IntProperty.ToInvariantString(), + ["complexParam.TransitiveProp.IntegerProperty"] = classToLog.TransitiveProp.IntegerProperty.ToInvariantString(), ["{OriginalFormat}"] = "Testing interface-typed argument here..." }; @@ -488,10 +488,10 @@ public void LogPropertiesRecordClassArgument() var expectedState = new Dictionary { - ["p0_Value"] = recordToLog.Value.ToInvariantString(), - ["p0_class"] = recordToLog.@class, - ["p0_GetOnlyValue"] = recordToLog.GetOnlyValue.ToInvariantString(), - ["p0_event"] = recordToLog.@event.ToString(CultureInfo.InvariantCulture) + ["p0.Value"] = recordToLog.Value.ToInvariantString(), + ["p0.class"] = recordToLog.@class, + ["p0.GetOnlyValue"] = recordToLog.GetOnlyValue.ToInvariantString(), + ["p0.event"] = recordToLog.@event.ToString(CultureInfo.InvariantCulture) }; latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState); @@ -514,9 +514,9 @@ public void LogPropertiesRecordStructArgument() var expectedState = new Dictionary { ["p0"] = recordToLog.ToString(), - ["p0_IntValue"] = recordToLog.IntValue.ToInvariantString(), - ["p0_StringValue"] = recordToLog.StringValue, - ["p0_GetOnlyValue"] = recordToLog.GetOnlyValue.ToInvariantString(), + ["p0.IntValue"] = recordToLog.IntValue.ToInvariantString(), + ["p0.StringValue"] = recordToLog.StringValue, + ["p0.GetOnlyValue"] = recordToLog.GetOnlyValue.ToInvariantString(), ["{OriginalFormat}"] = "Struct is: {p0}" }; @@ -540,9 +540,9 @@ public void LogPropertiesReadonlyRecordStructArgument() var expectedState = new Dictionary { ["p0"] = recordToLog.ToString(), - ["p0_IntValue"] = recordToLog.IntValue.ToInvariantString(), - ["p0_StringValue"] = recordToLog.StringValue, - ["p0_GetOnlyValue"] = recordToLog.GetOnlyValue.ToInvariantString(), + ["p0.IntValue"] = recordToLog.IntValue.ToInvariantString(), + ["p0.StringValue"] = recordToLog.StringValue, + ["p0.GetOnlyValue"] = recordToLog.GetOnlyValue.ToInvariantString(), ["{OriginalFormat}"] = "Readonly struct is: {p0}" }; diff --git a/test/Generators/Microsoft.Gen.Logging/Generated/Microsoft.Gen.Logging.Generated.Tests.csproj b/test/Generators/Microsoft.Gen.Logging/Generated/Microsoft.Gen.Logging.Generated.Tests.csproj index c4658283a42..84621f147e7 100644 --- a/test/Generators/Microsoft.Gen.Logging/Generated/Microsoft.Gen.Logging.Generated.Tests.csproj +++ b/test/Generators/Microsoft.Gen.Logging/Generated/Microsoft.Gen.Logging.Generated.Tests.csproj @@ -23,6 +23,6 @@ - + diff --git a/test/Generators/Microsoft.Gen.Logging/Generated/SensitiveRecordTests.cs b/test/Generators/Microsoft.Gen.Logging/Generated/SensitiveRecordTests.cs new file mode 100644 index 00000000000..3719271a3ce --- /dev/null +++ b/test/Generators/Microsoft.Gen.Logging/Generated/SensitiveRecordTests.cs @@ -0,0 +1,87 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Microsoft.Extensions.Logging.Testing; +using TestClasses; +using Xunit; + +namespace Microsoft.Gen.Logging.Test; + +public class SensitiveRecordTests +{ + [Fact] + public void TestRecordInTemplate() + { + using var logger = Utils.GetLogger(); + SensitiveRecordExtensions.LogInTemplate(logger, new()); + + var logRecord = Assert.Single(logger.FakeLogCollector.GetSnapshot()); + Assert.DoesNotContain(SensitiveRecordExtensions.Sensitive, logRecord.Message); + Assert.NotNull(logRecord.StructuredState); + Assert.All(logRecord.StructuredState, x => Assert.DoesNotContain(SensitiveRecordExtensions.Sensitive, x.Value)); + } + + [Fact] + public void TestRecordStructured() + { + using var logger = Utils.GetLogger(); + SensitiveRecordExtensions.LogFullyStructured(logger, new()); + + var logRecord = Assert.Single(logger.FakeLogCollector.GetSnapshot()); + Assert.DoesNotContain(SensitiveRecordExtensions.Sensitive, logRecord.Message); + Assert.NotNull(logRecord.StructuredState); + Assert.All(logRecord.StructuredState, x => Assert.DoesNotContain(SensitiveRecordExtensions.Sensitive, x.Value)); + } + + [Fact] + public void TestSensitiveRecordWithLogPropsAndTemplate() + { + using var logger = Utils.GetLogger(); + var dataToLog = new SensitiveRecordExtensions.RecordWithSensitiveMembers(SensitiveRecordExtensions.Sensitive, SensitiveRecordExtensions.Sensitive) + { + PropGetSet = SensitiveRecordExtensions.Sensitive + }; + + SensitiveRecordExtensions.LogPropertiesWithTemplate(logger, dataToLog); + + var logRecord = Assert.Single(logger.FakeLogCollector.GetSnapshot()); + Assert.DoesNotContain(SensitiveRecordExtensions.Sensitive, logRecord.Message); + Assert.NotNull(logRecord.StructuredState); + Assert.All(logRecord.StructuredState, x => Assert.DoesNotContain(SensitiveRecordExtensions.Sensitive, x.Value)); + } + + [Fact] + public void TestSensitiveRecordWithLogPropsNoTemplate() + { + using var logger = Utils.GetLogger(); + var dataToLog = new SensitiveRecordExtensions.RecordWithSensitiveMembers(SensitiveRecordExtensions.Sensitive, SensitiveRecordExtensions.Sensitive) + { + PropGetSet = SensitiveRecordExtensions.Sensitive + }; + + SensitiveRecordExtensions.LogPropertiesFullyStructured(logger, dataToLog); + + var logRecord = Assert.Single(logger.FakeLogCollector.GetSnapshot()); + Assert.DoesNotContain(SensitiveRecordExtensions.Sensitive, logRecord.Message); + Assert.NotNull(logRecord.StructuredState); + Assert.All(logRecord.StructuredState, x => Assert.DoesNotContain(SensitiveRecordExtensions.Sensitive, x.Value)); + } + + [Fact] + public void TestSensitiveRecordWithInlineAnnotation() + { + using var logger = Utils.GetLogger(); + var dataToLog = new SensitiveRecordExtensions.RecordWithSensitiveMembers(SensitiveRecordExtensions.Sensitive, SensitiveRecordExtensions.Sensitive) + { + PropGetSet = SensitiveRecordExtensions.Sensitive + }; + + SensitiveRecordExtensions.LogInTemplateWithAnnotation(logger, dataToLog); + + var logRecord = Assert.Single(logger.FakeLogCollector.GetSnapshot()); + Assert.DoesNotContain(SensitiveRecordExtensions.Sensitive, logRecord.Message); + Assert.NotNull(logRecord.StructuredState); + Assert.All(logRecord.StructuredState, x => Assert.DoesNotContain(SensitiveRecordExtensions.Sensitive, x.Value)); + } +} diff --git a/test/Generators/Microsoft.Gen.Logging/Generated/SimpleRedactorProvider.cs b/test/Generators/Microsoft.Gen.Logging/Generated/SimpleRedactorProvider.cs index f9a0ebbfb29..cff9b90d7c9 100644 --- a/test/Generators/Microsoft.Gen.Logging/Generated/SimpleRedactorProvider.cs +++ b/test/Generators/Microsoft.Gen.Logging/Generated/SimpleRedactorProvider.cs @@ -20,6 +20,6 @@ public SimpleRedactorProvider(char replacement) _replacement = replacement; } - public Redactor GetRedactor(DataClassification dataClass) => new SimpleRedactor(_replacement); + public Redactor GetRedactor(DataClassificationSet classifications) => new SimpleRedactor(_replacement); } } diff --git a/test/Generators/Microsoft.Gen.Logging/Generated/TagProviderTests.cs b/test/Generators/Microsoft.Gen.Logging/Generated/TagProviderTests.cs index 2ecbbfaf193..1ce9ca8564a 100644 --- a/test/Generators/Microsoft.Gen.Logging/Generated/TagProviderTests.cs +++ b/test/Generators/Microsoft.Gen.Logging/Generated/TagProviderTests.cs @@ -37,7 +37,7 @@ public void LogsWithObject() var expectedState = new Dictionary { ["Param"] = obj.ToString(), - ["param_ToString"] = obj + " ProvidePropertiesCall", + ["param.ToString"] = obj + " ProvidePropertiesCall", ["{OriginalFormat}"] = "Custom provided properties for {Param}." }; @@ -61,8 +61,8 @@ public void LogsWhenNonStaticClass() { ["P0"] = StringParamValue, ["P1"] = classToLog.ToString(), - ["p1_MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(), - ["p1_Custom_property_name"] = classToLog.MyStringProperty, + ["p1.MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(), + ["p1.Custom_property_name"] = classToLog.MyStringProperty, ["{OriginalFormat}"] = "LogProperties with provider: {P0}, {P1}" }; @@ -87,8 +87,8 @@ public void LogsWhenDefaultAttrCtorInNonStaticClass() var expectedState = new Dictionary { ["p0"] = StringParamValue, - ["p1_MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(), - ["p1_Custom_property_name"] = classToLog.MyStringProperty + ["p1.MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(), + ["p1.Custom_property_name"] = classToLog.MyStringProperty }; latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState); @@ -109,8 +109,8 @@ public void LogsWhenDefaultAttrCtorInStaticClass() var expectedState = new Dictionary { - ["param_MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(), - ["param_Custom_property_name"] = classToLog.MyStringProperty + ["param.MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(), + ["param.Custom_property_name"] = classToLog.MyStringProperty }; latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState); @@ -141,7 +141,7 @@ public void LogsWithNullable() var expectedState = new Dictionary { - ["param_P1"] = "42", + ["param.P1"] = "42", }; latestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState); @@ -250,8 +250,8 @@ public void LogsWhenNonNullStronglyTypedObject() var expectedState = new Dictionary { ["Param"] = classToLog.ToString(), - ["param_MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(), - ["param_Custom_property_name"] = classToLog.MyStringProperty, + ["param.MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(), + ["param.Custom_property_name"] = classToLog.MyStringProperty, ["{OriginalFormat}"] = "Custom provided properties for {Param}." }; @@ -271,8 +271,8 @@ public void LogsWhenStruct() var expectedState = new Dictionary { - ["param_MyIntProperty"] = structToLog.MyIntProperty.ToInvariantString(), - ["param_Custom_property_name"] = structToLog.MyStringProperty, + ["param.MyIntProperty"] = structToLog.MyIntProperty.ToInvariantString(), + ["param.Custom_property_name"] = structToLog.MyStringProperty, ["{OriginalFormat}"] = "Custom provided properties for struct." }; @@ -292,8 +292,8 @@ public void LogsWhenInterface() var expectedState = new Dictionary { - ["param_MyIntProperty"] = interfaceToLog.MyIntProperty.ToInvariantString(), - ["param_Custom_property_name"] = interfaceToLog.MyStringProperty, + ["param.MyIntProperty"] = interfaceToLog.MyIntProperty.ToInvariantString(), + ["param.Custom_property_name"] = interfaceToLog.MyStringProperty, ["{OriginalFormat}"] = "Custom provided properties for interface." }; @@ -313,11 +313,11 @@ public void LogsWhenProviderCombinedWithLogProperties() var expectedState = new Dictionary { - ["param1_MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(), - ["param1_MyStringProperty"] = classToLog.MyStringProperty, - ["param1_AnotherStringProperty"] = classToLog.AnotherStringProperty, - ["param2_MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(), - ["param2_Custom_property_name"] = classToLog.MyStringProperty, + ["param1.MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(), + ["param1.MyStringProperty"] = classToLog.MyStringProperty, + ["param1.AnotherStringProperty"] = classToLog.AnotherStringProperty, + ["param2.MyIntProperty"] = classToLog.MyIntProperty.ToInvariantString(), + ["param2.Custom_property_name"] = classToLog.MyStringProperty, ["{OriginalFormat}"] = "No params." }; @@ -341,10 +341,10 @@ public void LogsTwoStronglyTypedParams() var expectedState = new Dictionary { ["StringParam"] = StringParamValue, - ["param_MyIntProperty"] = classToLog1.MyIntProperty.ToInvariantString(), - ["param_Custom_property_name"] = classToLog1.MyStringProperty, - ["param2_Another_property_name"] = classToLog2.MyStringProperty.ToUpperInvariant(), - ["param2_MyIntProperty_test"] = classToLog2.MyIntProperty.ToInvariantString(), + ["param.MyIntProperty"] = classToLog1.MyIntProperty.ToInvariantString(), + ["param.Custom_property_name"] = classToLog1.MyStringProperty, + ["param2.Another_property_name"] = classToLog2.MyStringProperty.ToUpperInvariant(), + ["param2.MyIntProperty_test"] = classToLog2.MyIntProperty.ToInvariantString(), ["{OriginalFormat}"] = "Custom provided properties for both complex params and {StringParam}." }; @@ -356,8 +356,8 @@ public void LogsTwoStronglyTypedParams() TagProviderExtensions.LogMethodCustomPropsProviderTwoParams(_logger, StringParamValue, classToLog1, classToLog2); Assert.Equal(2, _logger.Collector.Count); - expectedState["param_MyIntProperty"] = classToLog1.MyIntProperty.ToInvariantString(); - expectedState["param2_MyIntProperty_test"] = classToLog2.MyIntProperty.ToInvariantString(); + expectedState["param.MyIntProperty"] = classToLog1.MyIntProperty.ToInvariantString(); + expectedState["param2.MyIntProperty_test"] = classToLog2.MyIntProperty.ToInvariantString(); _logger.Collector.LatestRecord.StructuredState.Should().NotBeNull().And.Equal(expectedState); } @@ -379,9 +379,9 @@ public void LogsTwoObjectParams() var expectedState = new Dictionary { ["StringParam"] = StringParamValue, - ["param_ToString"] = obj1 + " ProvidePropertiesCall", - ["param2_Type"] = obj2.GetType().ToString(), - ["param2_ToString"] = obj2 + " ProvideOtherPropertiesCall", + ["param.ToString"] = obj1 + " ProvidePropertiesCall", + ["param2.Type"] = obj2.GetType().ToString(), + ["param2.ToString"] = obj2 + " ProvideOtherPropertiesCall", ["{OriginalFormat}"] = "Custom provided properties for both complex params and {StringParam}." }; @@ -404,8 +404,8 @@ public void LogsTwoNullObjectParams() var expectedState = new Dictionary { ["StringParam"] = StringParamValue, - ["param2_Type"] = null, - ["param2_ToString"] = " ProvideOtherPropertiesCall", + ["param2.Type"] = null, + ["param2.ToString"] = " ProvideOtherPropertiesCall", ["{OriginalFormat}"] = "Custom provided properties for both complex params and {StringParam}." }; diff --git a/test/Generators/Microsoft.Gen.Logging/Generated/Utils.cs b/test/Generators/Microsoft.Gen.Logging/Generated/Utils.cs index c992d80362b..abbb3225b8b 100644 --- a/test/Generators/Microsoft.Gen.Logging/Generated/Utils.cs +++ b/test/Generators/Microsoft.Gen.Logging/Generated/Utils.cs @@ -72,14 +72,17 @@ public static TestLogger GetLogger() { builder.SetMinimumLevel(LogLevel.Trace); builder.AddProvider(new Provider(fakeLogger)); - builder.EnableRedaction(); + builder.EnableRedaction(o => + { + o.ApplyDiscriminator = false; + }); }); serviceCollection.AddRedaction(builder => { builder.SetRedactor(new PublicDataAttribute().Classification); builder.SetRedactor(new PrivateDataAttribute().Classification); - builder.SetRedactor(new PrivateDataAttribute().Classification | new PublicDataAttribute().Classification); + builder.SetRedactor(new PrivateDataAttribute().Classification, new PublicDataAttribute().Classification); builder.SetFallbackRedactor(); }); diff --git a/test/Generators/Microsoft.Gen.Logging/TestClasses/SensitiveRecordExtensions.cs b/test/Generators/Microsoft.Gen.Logging/TestClasses/SensitiveRecordExtensions.cs new file mode 100644 index 00000000000..479fd935107 --- /dev/null +++ b/test/Generators/Microsoft.Gen.Logging/TestClasses/SensitiveRecordExtensions.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Compliance.Testing; +using Microsoft.Extensions.Logging; + +namespace TestClasses +{ + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Test code")] + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Test code")] + [SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Test code")] + [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Test code")] + [SuppressMessage("Major Code Smell", "S2376:Write-only properties should not be used", Justification = "Test code")] + internal static partial class SensitiveRecordExtensions + { + internal const string Sensitive = "SENSITIVE"; + + public record class BaseRecord + { + [PrivateData] public const string ConstFieldBase = Sensitive; + [PrivateData] public static string StaticFieldBase = Sensitive; + [PrivateData] public static string StaticPropBase => Sensitive; + +#pragma warning disable CS0414 // The field is assigned but its value is never used + [PrivateData] private readonly string _privateFieldBase = Sensitive; + [PrivateData] private string _privateReadonlyFieldBase = Sensitive; + [PrivateData] internal string InternalFieldBase = Sensitive; +#pragma warning restore CS0414 // The field is assigned but its value is never used + } + + public record class InterimRecord : BaseRecord + { + [PrivateData] public virtual string PropVirtual => Sensitive; + } + + // Even though this record and its base classes contain sensitive members, + // they won't be logged because the default record's ToString() implementation doesn't emit non-public and non-instance members. + internal record class MyRecord : InterimRecord + { + [PrivateData] public const string ConstField = Sensitive; + [PrivateData] public static string StaticField = Sensitive; + [PrivateData] public static string StaticProp => Sensitive; + +#pragma warning disable CS0414 // The field is assigned but its value is never used + [PrivateData] private string PrivateField = Sensitive; + [PrivateData] internal string InternalField = Sensitive; +#pragma warning restore CS0414 // The field is assigned but its value is never used + + [PrivateData] private string PrivatePropGetSet { get; set; } = Sensitive; + [PrivateData] internal string InternalPropGetSet { get; set; } = Sensitive; + [PrivateData] protected string ProtectedPropGetSet { get; set; } = Sensitive; + [PrivateData] private string PrivatePropGet => Sensitive; + [PrivateData] internal string InternalPropGet => Sensitive; + [PrivateData] protected string ProtectedPropGet => Sensitive; + + // This one overrides 'virtual' property declared in 'InterimRecord': + public override string PropVirtual => "Regular"; + } + + internal record RecordWithSensitiveMembers( + [PrivateData] string AnnotatedArgFromPrimaryCtor, + [property: PrivateData] string AnnotatedPropFromPrimaryCtor) + { + [PrivateData] + public string? PropGetSet { get; set; } + } + + [LoggerMessage(LogLevel.Debug, "Param is {p0}")] + public static partial void LogInTemplate(ILogger logger, MyRecord p0); + + [LoggerMessage(LogLevel.Debug)] + public static partial void LogFullyStructured(ILogger logger, MyRecord p0); + + [LoggerMessage(LogLevel.Information, "Data was obtained")] + public static partial void LogPropertiesWithTemplate( + ILogger logger, + [LogProperties] RecordWithSensitiveMembers data); + + [LoggerMessage(LogLevel.Information)] + public static partial void LogPropertiesFullyStructured( + ILogger logger, + [LogProperties] RecordWithSensitiveMembers data); + + [LoggerMessage(LogLevel.Information, "Data is {data}")] + public static partial void LogInTemplateWithAnnotation( + ILogger logger, + [PrivateData] RecordWithSensitiveMembers data); + } +} diff --git a/test/Generators/Microsoft.Gen.Logging/Unit/AttributeParserTests.cs b/test/Generators/Microsoft.Gen.Logging/Unit/AttributeParserTests.cs index 5343fc77740..a2364b2adf2 100644 --- a/test/Generators/Microsoft.Gen.Logging/Unit/AttributeParserTests.cs +++ b/test/Generators/Microsoft.Gen.Logging/Unit/AttributeParserTests.cs @@ -178,18 +178,17 @@ namespace Test {{ using Microsoft.Extensions.Compliance.Testing; using Microsoft.Extensions.Compliance.Redaction; using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Logging; {code} }} "; var loggerAssembly = Assembly.GetAssembly(typeof(ILogger)); - var logMethodAssembly = Assembly.GetAssembly(typeof(LoggerMessageAttribute)); + var loggerMessageAssembly = Assembly.GetAssembly(typeof(LoggerMessageAttribute)); var enrichmentAssembly = Assembly.GetAssembly(typeof(IEnrichmentTagCollector)); var dataClassificationAssembly = Assembly.GetAssembly(typeof(DataClassification)); var simpleDataClassificationAssembly = Assembly.GetAssembly(typeof(PrivateDataAttribute)); var redactorProviderAssembly = Assembly.GetAssembly(typeof(IRedactorProvider)); - var refs = new[] { loggerAssembly!, logMethodAssembly!, enrichmentAssembly!, dataClassificationAssembly!, simpleDataClassificationAssembly!, redactorProviderAssembly! }; + var refs = new[] { loggerAssembly!, loggerMessageAssembly!, enrichmentAssembly!, dataClassificationAssembly!, simpleDataClassificationAssembly!, redactorProviderAssembly! }; var (d, _) = await RoslynTestUtils.RunGenerator( new LoggingGenerator(), diff --git a/test/Generators/Microsoft.Gen.Logging/Unit/LogParserUtilitiesTests.cs b/test/Generators/Microsoft.Gen.Logging/Unit/LogParserUtilitiesTests.cs index c1a6e6cde57..1db966137c3 100644 --- a/test/Generators/Microsoft.Gen.Logging/Unit/LogParserUtilitiesTests.cs +++ b/test/Generators/Microsoft.Gen.Logging/Unit/LogParserUtilitiesTests.cs @@ -1,9 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; using Microsoft.CodeAnalysis; +using Microsoft.Gen.Logging.Model; +using Microsoft.Gen.Logging.Parsing; using Microsoft.Gen.Shared; using Moq; +using Moq.Protected; using Xunit; namespace Microsoft.Gen.Logging.Test; @@ -34,4 +41,154 @@ public void ShouldNotDetectNullableOfT() var result = typeSymbolMock.Object.IsNullableOfT(); Assert.False(result); } + + [Fact] + public void RecordHasSensitivePublicMembers_ShouldReturnFalse_WhenNoDataClasses() + { + var symbolMock = new Mock(); + symbolMock + .Setup(x => x.GetMembers()) + .Returns(ImmutableArray.Empty); + + var symbolHolder = new SymbolHolder( + null!, + null!, + null!, + null!, + null!, + null!, + null!, + null!, + null!, + null!, + null!, + null!, + null!, + null!); + + var diagMock = new Mock>(); + var parser = new Parser(null!, diagMock.Object, CancellationToken.None); + var result = parser.RecordHasSensitivePublicMembers(symbolMock.Object, symbolHolder); + Assert.False(result); + symbolMock.VerifyNoOtherCalls(); + } + + [Theory] + [CombinatorialData] + public void RecordHasSensitivePublicMembers_ShouldNotThrow_WhenNoMembersOnType(bool isNull) + { + var symbolMock = new Mock(); + symbolMock + .Setup(x => x.GetMembers()) + .Returns(isNull + ? default + : ImmutableArray.Empty); + + symbolMock + .Setup(x => x.GetAttributes()) + .Returns(Array.Empty().ToImmutableArray()); + + symbolMock + .SetupGet(x => x.BaseType) + .Returns((INamedTypeSymbol?)null); + + symbolMock + .SetupGet(x => x.SpecialType) + .Returns(SpecialType.None); + + var symbolHolder = new SymbolHolder( + null!, + null!, + null!, + null!, + null!, + null!, + null!, + null!, + null!, + null!, + null!, + null!, + null!, + Mock.Of()); + + var diagMock = new Mock>(); + var parser = new Parser(null!, diagMock.Object, CancellationToken.None); + var result = parser.RecordHasSensitivePublicMembers(symbolMock.Object, symbolHolder); + Assert.False(result); + symbolMock.VerifyAll(); + symbolMock.VerifyNoOtherCalls(); + } + + [Theory] + [CombinatorialData] + public void ProcessLogPropertiesForParameter_ShouldNotThrow_WhenNoMembersOnType(bool isNull) + { + var symbolHolder = new SymbolHolder( + null!, + null!, + null!, + null!, + null!, + null!, + null!, + null!, + null!, + new HashSet(SymbolEqualityComparer.Default), + null!, + null!, + null!, + null!); + + const string ParamType = "param type"; + + var paramTypeMock = new Mock() + .As(); + + paramTypeMock.SetupGet(x => x.Kind).Returns(SymbolKind.NamedType); + paramTypeMock.SetupGet(x => x.TypeKind).Returns(TypeKind.Class); + paramTypeMock.SetupGet(x => x.SpecialType).Returns(SpecialType.None); + paramTypeMock.SetupGet(x => x.OriginalDefinition).Returns(paramTypeMock.Object); + paramTypeMock.Setup(x => x.ToDisplayString(It.IsAny())) + .Returns(ParamType); + + paramTypeMock + .SetupGet(x => x.BaseType) + .Returns((INamedTypeSymbol?)null); + + paramTypeMock + .Setup(x => x.GetMembers()) + .Returns(isNull + ? default + : ImmutableArray.Empty); + + var paramSymbolMock = new Mock(); + paramSymbolMock.SetupGet(x => x.Type) + .Returns(paramTypeMock.Object); + + var logPropertiesAttribute = new Mock(); + logPropertiesAttribute + .Protected() + .SetupGet>>("CommonNamedArguments") + .Returns(ImmutableArray>.Empty); + + logPropertiesAttribute + .Protected() + .SetupGet>("CommonConstructorArguments") + .Returns(ImmutableArray.Empty); + + var diagMock = new Mock>(); + var parser = new Parser(null!, diagMock.Object, CancellationToken.None); + bool unused = false; + var result = parser.ProcessLogPropertiesForParameter( + logPropertiesAttribute.Object, + null!, + new LoggingMethodParameter(), + paramSymbolMock.Object, + symbolHolder, + ref unused); + + diagMock.Verify(x => x.Invoke(It.Is(d => d.Id == DiagDescriptors.LogPropertiesParameterSkipped.Id)), Times.Once); + Assert.True(result); + } } diff --git a/test/Generators/Microsoft.Gen.Logging/Unit/Microsoft.Gen.Logging.Unit.Tests.csproj b/test/Generators/Microsoft.Gen.Logging/Unit/Microsoft.Gen.Logging.Unit.Tests.csproj index 0a3244f7b66..19ad8ffe267 100644 --- a/test/Generators/Microsoft.Gen.Logging/Unit/Microsoft.Gen.Logging.Unit.Tests.csproj +++ b/test/Generators/Microsoft.Gen.Logging/Unit/Microsoft.Gen.Logging.Unit.Tests.csproj @@ -16,7 +16,7 @@ - +