diff --git a/docs/project/list-of-obsoletions.md b/docs/project/list-of-diagnostics.md
similarity index 55%
rename from docs/project/list-of-obsoletions.md
rename to docs/project/list-of-diagnostics.md
index cb094673111ec4..df1f73e6d7b068 100644
--- a/docs/project/list-of-obsoletions.md
+++ b/docs/project/list-of-diagnostics.md
@@ -26,4 +26,32 @@ Currently the identifiers `SYSLIB0001` through `SYSLIB0999` are carved out for o
| __`SYSLIB0011`__ | `BinaryFormatter` serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for recommended alternatives. |
| __`SYSLIB0012`__ | Assembly.CodeBase and Assembly.EscapedCodeBase are only included for .NET Framework compatibility. Use Assembly.Location instead. |
| __`SYSLIB0013`__ | Uri.EscapeUriString can corrupt the Uri string in some cases. Consider using Uri.EscapeDataString for query string components instead. |
-| __`SYSLIB0015`__ | DisablePrivateReflectionAttribute has no effect in .NET 6.0+ applications. |
\ No newline at end of file
+| __`SYSLIB0015`__ | DisablePrivateReflectionAttribute has no effect in .NET 6.0+ applications. |
+
+### Analyzer warnings (`SYSLIB1001` - `SYSLIB1999`)
+| Diagnostic ID | Description |
+| :---------------- | :---------- |
+| __`SYSLIB1001`__ | Logging method names cannot start with _ |
+| __`SYSLIB1002`__ | Don't include log level parameters as templates in the logging message |
+| __`SYSLIB1003`__ | InvalidLoggingMethodParameterNameTitle |
+| __`SYSLIB1004`__ | Logging class cannot be in nested types |
+| __`SYSLIB1005`__ | Could not find a required type definition |
+| __`SYSLIB1006`__ | Multiple logging methods cannot use the same event id within a class |
+| __`SYSLIB1007`__ | Logging methods must return void |
+| __`SYSLIB1008`__ | One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface |
+| __`SYSLIB1009`__ | Logging methods must be static |
+| __`SYSLIB1010`__ | Logging methods must be partial |
+| __`SYSLIB1011`__ | Logging methods cannot be generic |
+| __`SYSLIB1012`__ | Redundant qualifier in logging message |
+| __`SYSLIB1013`__ | Don't include exception parameters as templates in the logging message |
+| __`SYSLIB1014`__ | Logging template has no corresponding method argument |
+| __`SYSLIB1015`__ | Argument is not referenced from the logging message |
+| __`SYSLIB1016`__ | Logging methods cannot have a body |
+| __`SYSLIB1017`__ | A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method |
+| __`SYSLIB1018`__ | Don't include logger parameters as templates in the logging message |
+| __`SYSLIB1019`__ | Couldn't find a field of type Microsoft.Extensions.Logging.ILogger |
+| __`SYSLIB1020`__ | Found multiple fields of type Microsoft.Extensions.Logging.ILogger |
+| __`SYSLIB1021`__ | Can't have the same template with different casing |
+| __`SYSLIB1022`__ | Can't have malformed format strings (like dangling {, etc) |
+| __`SYSLIB1023`__ | Generating more than 6 arguments is not supported |
+| __`SYSLIB1029`__ | *_Blocked range `SYSLIB1024`-`SYSLIB1029` for logging._* |
\ No newline at end of file
diff --git a/eng/Versions.props b/eng/Versions.props
index 935f406380f734..e8e4e3f971a3eb 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -44,6 +44,11 @@
+
+
+ 3.8.0
+ 3.8.0
+ 6.0.0-preview3.21168.13.9.0-5.final
@@ -154,6 +159,7 @@
2.4.21.3.012.0.3
+ 2.0.44.12.02.14.3
diff --git a/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs b/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs
new file mode 100644
index 00000000000000..4928270b06f170
--- /dev/null
+++ b/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs
@@ -0,0 +1,293 @@
+// 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.IO;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Text;
+using Xunit;
+
+namespace SourceGenerators.Tests
+{
+ internal static class RoslynTestUtils
+ {
+ ///
+ /// Creates a canonical Roslyn project for testing.
+ ///
+ /// Assembly references to include in the project.
+ /// Whether to include references to the BCL assemblies.
+ public static Project CreateTestProject(IEnumerable? references, bool includeBaseReferences = true)
+ {
+ string corelib = Assembly.GetAssembly(typeof(object))!.Location;
+ string runtimeDir = Path.GetDirectoryName(corelib)!;
+
+ var refs = new List();
+ if (includeBaseReferences)
+ {
+ refs.Add(MetadataReference.CreateFromFile(corelib));
+ refs.Add(MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "netstandard.dll")));
+ refs.Add(MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "System.Runtime.dll")));
+ }
+
+ if (references != null)
+ {
+ foreach (var r in references)
+ {
+ refs.Add(MetadataReference.CreateFromFile(r.Location));
+ }
+ }
+
+ return new AdhocWorkspace()
+ .AddSolution(SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create()))
+ .AddProject("Test", "test.dll", "C#")
+ .WithMetadataReferences(refs)
+ .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithNullableContextOptions(NullableContextOptions.Enable));
+ }
+
+ public static Task CommitChanges(this Project proj, params string[] ignorables)
+ {
+ Assert.True(proj.Solution.Workspace.TryApplyChanges(proj.Solution));
+ return AssertNoDiagnostic(proj, ignorables);
+ }
+
+ public static async Task AssertNoDiagnostic(this Project proj, params string[] ignorables)
+ {
+ foreach (Document doc in proj.Documents)
+ {
+ SemanticModel? sm = await doc.GetSemanticModelAsync(CancellationToken.None).ConfigureAwait(false);
+ Assert.NotNull(sm);
+
+ foreach (Diagnostic d in sm!.GetDiagnostics())
+ {
+ bool ignore = ignorables.Any(ig => d.Id == ig);
+
+ Assert.True(ignore, d.ToString());
+ }
+ }
+ }
+
+ private static Project WithDocuments(this Project project, IEnumerable sources, IEnumerable? sourceNames = null)
+ {
+ int count = 0;
+ Project result = project;
+ if (sourceNames != null)
+ {
+ List names = sourceNames.ToList();
+ foreach (string s in sources)
+ result = result.WithDocument(names[count++], s);
+ }
+ else
+ {
+ foreach (string s in sources)
+ result = result.WithDocument($"src-{count++}.cs", s);
+ }
+
+ return result;
+ }
+
+ public static Project WithDocument(this Project proj, string name, string text)
+ {
+ return proj.AddDocument(name, text).Project;
+ }
+
+ public static Document FindDocument(this Project proj, string name)
+ {
+ foreach (Document doc in proj.Documents)
+ {
+ if (doc.Name == name)
+ {
+ return doc;
+ }
+ }
+
+ throw new FileNotFoundException(name);
+ }
+
+ ///
+ /// Looks for /*N+*/ and /*-N*/ markers in a string and creates a TextSpan containing the enclosed text.
+ ///
+ public static TextSpan MakeSpan(string text, int spanNum)
+ {
+ int start = text.IndexOf($"/*{spanNum}+*/", StringComparison.Ordinal);
+ if (start < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(spanNum));
+ }
+
+ start += 6;
+
+ int end = text.IndexOf($"/*-{spanNum}*/", StringComparison.Ordinal);
+ if (end < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(spanNum));
+ }
+
+ end -= 1;
+
+ return new TextSpan(start, end - start);
+ }
+
+ ///
+ /// Runs a Roslyn generator over a set of source files.
+ ///
+ public static async Task<(ImmutableArray, ImmutableArray)> RunGenerator(
+ ISourceGenerator generator,
+ IEnumerable? references,
+ IEnumerable sources,
+ AnalyzerConfigOptionsProvider? optionsProvider = null,
+ bool includeBaseReferences = true,
+ CancellationToken cancellationToken = default)
+ {
+ Project proj = CreateTestProject(references, includeBaseReferences);
+
+ proj = proj.WithDocuments(sources);
+
+ Assert.True(proj.Solution.Workspace.TryApplyChanges(proj.Solution));
+
+ Compilation? comp = await proj!.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false);
+
+ CSharpGeneratorDriver cgd = CSharpGeneratorDriver.Create(new[] { generator }, optionsProvider: optionsProvider);
+ GeneratorDriver gd = cgd.RunGenerators(comp!, cancellationToken);
+
+ GeneratorDriverRunResult r = gd.GetRunResult();
+ return (r.Results[0].Diagnostics, r.Results[0].GeneratedSources);
+ }
+
+ ///
+ /// Runs a Roslyn analyzer over a set of source files.
+ ///
+ public static async Task> RunAnalyzer(
+ DiagnosticAnalyzer analyzer,
+ IEnumerable references,
+ IEnumerable sources)
+ {
+ Project proj = CreateTestProject(references);
+
+ proj = proj.WithDocuments(sources);
+
+ await proj.CommitChanges().ConfigureAwait(false);
+
+ ImmutableArray analyzers = ImmutableArray.Create(analyzer);
+
+ Compilation? comp = await proj!.GetCompilationAsync().ConfigureAwait(false);
+ return await comp!.WithAnalyzers(analyzers).GetAllDiagnosticsAsync().ConfigureAwait(false);
+ }
+
+ ///
+ /// Runs a Roslyn analyzer and fixer.
+ ///
+ public static async Task> RunAnalyzerAndFixer(
+ DiagnosticAnalyzer analyzer,
+ CodeFixProvider fixer,
+ IEnumerable references,
+ IEnumerable sources,
+ IEnumerable? sourceNames = null,
+ string? defaultNamespace = null,
+ string? extraFile = null)
+ {
+ Project proj = CreateTestProject(references);
+
+ int count = sources.Count();
+ proj = proj.WithDocuments(sources, sourceNames);
+
+ if (defaultNamespace != null)
+ {
+ proj = proj.WithDefaultNamespace(defaultNamespace);
+ }
+
+ await proj.CommitChanges().ConfigureAwait(false);
+
+ ImmutableArray analyzers = ImmutableArray.Create(analyzer);
+
+ while (true)
+ {
+ Compilation? comp = await proj!.GetCompilationAsync().ConfigureAwait(false);
+ ImmutableArray diags = await comp!.WithAnalyzers(analyzers).GetAllDiagnosticsAsync().ConfigureAwait(false);
+ if (diags.IsEmpty)
+ {
+ // no more diagnostics reported by the analyzers
+ break;
+ }
+
+ var actions = new List();
+ foreach (Diagnostic d in diags)
+ {
+ Document? doc = proj.GetDocument(d.Location.SourceTree);
+
+ CodeFixContext context = new CodeFixContext(doc!, d, (action, _) => actions.Add(action), CancellationToken.None);
+ await fixer.RegisterCodeFixesAsync(context).ConfigureAwait(false);
+ }
+
+ if (actions.Count == 0)
+ {
+ // nothing to fix
+ break;
+ }
+
+ ImmutableArray operations = await actions[0].GetOperationsAsync(CancellationToken.None).ConfigureAwait(false);
+ Solution solution = operations.OfType().Single().ChangedSolution;
+ Project? changedProj = solution.GetProject(proj.Id);
+ if (changedProj != proj)
+ {
+ proj = await RecreateProjectDocumentsAsync(changedProj!).ConfigureAwait(false);
+ }
+ }
+
+ var results = new List();
+
+ if (sourceNames != null)
+ {
+ List l = sourceNames.ToList();
+ for (int i = 0; i < count; i++)
+ {
+ SourceText s = await proj.FindDocument(l[i]).GetTextAsync().ConfigureAwait(false);
+ results.Add(s.ToString().Replace("\r\n", "\n", StringComparison.Ordinal));
+ }
+ }
+ else
+ {
+ for (int i = 0; i < count; i++)
+ {
+ SourceText s = await proj.FindDocument($"src-{i}.cs").GetTextAsync().ConfigureAwait(false);
+ results.Add(s.ToString().Replace("\r\n", "\n", StringComparison.Ordinal));
+ }
+ }
+
+ if (extraFile != null)
+ {
+ SourceText s = await proj.FindDocument(extraFile).GetTextAsync().ConfigureAwait(false);
+ results.Add(s.ToString().Replace("\r\n", "\n", StringComparison.Ordinal));
+ }
+
+ return results;
+ }
+
+ private static async Task RecreateProjectDocumentsAsync(Project project)
+ {
+ foreach (DocumentId documentId in project.DocumentIds)
+ {
+ Document? document = project.GetDocument(documentId);
+ document = await RecreateDocumentAsync(document!).ConfigureAwait(false);
+ project = document.Project;
+ }
+
+ return project;
+ }
+
+ private static async Task RecreateDocumentAsync(Document document)
+ {
+ SourceText newText = await document.GetTextAsync().ConfigureAwait(false);
+ return document.WithText(SourceText.From(newText.ToString(), newText.Encoding, newText.ChecksumAlgorithm));
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/Microsoft.Extensions.Logging.sln b/src/libraries/Microsoft.Extensions.Logging/Microsoft.Extensions.Logging.sln
index 7d19ffd425c313..c04786d3166325 100644
--- a/src/libraries/Microsoft.Extensions.Logging/Microsoft.Extensions.Logging.sln
+++ b/src/libraries/Microsoft.Extensions.Logging/Microsoft.Extensions.Logging.sln
@@ -141,6 +141,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{62569F09-F90
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{35D3ECF9-E321-4AA6-BF5B-41E7AC54A620}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{9E96ED55-37A0-4007-854D-F3E3526E3CC0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Logging.Generators", "gen\Microsoft.Extensions.Logging.Generators.csproj", "{56A5DED2-47C2-4938-931E-B896A6BDDA0D}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Encodings.Web", "..\System.Text.Encodings.Web\ref\System.Text.Encodings.Web.csproj", "{58614FD7-05BC-46A8-900D-AC5B8622C724}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json", "..\System.Text.Json\ref\System.Text.Json.csproj", "{F7E444A4-124D-48E2-B311-17A5ED8B0CC2}"
@@ -149,6 +153,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Diagnostics.Diagnost
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Threading.AccessControl", "..\System.Threading.AccessControl\ref\System.Threading.AccessControl.csproj", "{5FF1A443-F491-428F-9121-51523AA65052}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Logging.Generators.Tests", "tests\Microsoft.Extensions.Logging.Generators.Tests\Microsoft.Extensions.Logging.Generators.Tests.csproj", "{8C0151F9-1FC9-4D9B-A5B1-60C7B8E7B761}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -419,6 +425,10 @@ Global
{1E1B25F0-7B14-4798-BBF4-156A52949CBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E1B25F0-7B14-4798-BBF4-156A52949CBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E1B25F0-7B14-4798-BBF4-156A52949CBA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {56A5DED2-47C2-4938-931E-B896A6BDDA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {56A5DED2-47C2-4938-931E-B896A6BDDA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {56A5DED2-47C2-4938-931E-B896A6BDDA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {56A5DED2-47C2-4938-931E-B896A6BDDA0D}.Release|Any CPU.Build.0 = Release|Any CPU
{58614FD7-05BC-46A8-900D-AC5B8622C724}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{58614FD7-05BC-46A8-900D-AC5B8622C724}.Debug|Any CPU.Build.0 = Debug|Any CPU
{58614FD7-05BC-46A8-900D-AC5B8622C724}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -435,6 +445,10 @@ Global
{5FF1A443-F491-428F-9121-51523AA65052}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FF1A443-F491-428F-9121-51523AA65052}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FF1A443-F491-428F-9121-51523AA65052}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8C0151F9-1FC9-4D9B-A5B1-60C7B8E7B761}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8C0151F9-1FC9-4D9B-A5B1-60C7B8E7B761}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8C0151F9-1FC9-4D9B-A5B1-60C7B8E7B761}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8C0151F9-1FC9-4D9B-A5B1-60C7B8E7B761}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -506,10 +520,12 @@ Global
{504464B5-B163-4D6B-A113-52DDC78A401D} = {35D3ECF9-E321-4AA6-BF5B-41E7AC54A620}
{7467E5B1-7454-4277-A84F-E8BE34A80CE4} = {62569F09-F901-4240-B3E1-E2FF90544D74}
{1E1B25F0-7B14-4798-BBF4-156A52949CBA} = {35D3ECF9-E321-4AA6-BF5B-41E7AC54A620}
+ {56A5DED2-47C2-4938-931E-B896A6BDDA0D} = {9E96ED55-37A0-4007-854D-F3E3526E3CC0}
{58614FD7-05BC-46A8-900D-AC5B8622C724} = {62569F09-F901-4240-B3E1-E2FF90544D74}
{F7E444A4-124D-48E2-B311-17A5ED8B0CC2} = {62569F09-F901-4240-B3E1-E2FF90544D74}
{5B5AFA97-C1FC-47B9-AB7A-6A10E0285282} = {62569F09-F901-4240-B3E1-E2FF90544D74}
{5FF1A443-F491-428F-9121-51523AA65052} = {62569F09-F901-4240-B3E1-E2FF90544D74}
+ {8C0151F9-1FC9-4D9B-A5B1-60C7B8E7B761} = {88957302-AFDD-4923-BF5A-336EAB5F28B7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B57B7C13-740F-4482-B7B6-B5E87014ACB1}
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/DiagnosticDescriptors.cs b/src/libraries/Microsoft.Extensions.Logging/gen/DiagnosticDescriptors.cs
new file mode 100644
index 00000000000000..9e51646b52f17e
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/DiagnosticDescriptors.cs
@@ -0,0 +1,195 @@
+// 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.CodeAnalysis;
+
+namespace Microsoft.Extensions.Logging.Generators
+{
+ public static class DiagnosticDescriptors
+ {
+ public static DiagnosticDescriptor InvalidLoggingMethodName { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1001",
+ title: new LocalizableResourceString(nameof(SR.InvalidLoggingMethodNameMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.InvalidLoggingMethodNameMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor ShouldntMentionLogLevelInMessage { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1002",
+ title: new LocalizableResourceString(nameof(SR.ShouldntMentionLogLevelInMessageTitle), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.ShouldntMentionInTemplateMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor InvalidLoggingMethodParameterName { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1003",
+ title: new LocalizableResourceString(nameof(SR.InvalidLoggingMethodParameterNameMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.InvalidLoggingMethodParameterNameMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor LoggingMethodInNestedType { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1004",
+ title: new LocalizableResourceString(nameof(SR.LoggingMethodInNestedTypeMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.LoggingMethodInNestedTypeMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor MissingRequiredType { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1005",
+ title: new LocalizableResourceString(nameof(SR.MissingRequiredTypeTitle), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.MissingRequiredTypeMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor ShouldntReuseEventIds { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1006",
+ title: new LocalizableResourceString(nameof(SR.ShouldntReuseEventIdsTitle), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.ShouldntReuseEventIdsMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor LoggingMethodMustReturnVoid { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1007",
+ title: new LocalizableResourceString(nameof(SR.LoggingMethodMustReturnVoidMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.LoggingMethodMustReturnVoidMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor MissingLoggerArgument { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1008",
+ title: new LocalizableResourceString(nameof(SR.MissingLoggerArgumentMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.MissingLoggerArgumentMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor LoggingMethodShouldBeStatic { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1009",
+ title: new LocalizableResourceString(nameof(SR.LoggingMethodShouldBeStaticMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.LoggingMethodShouldBeStaticMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor LoggingMethodMustBePartial { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1010",
+ title: new LocalizableResourceString(nameof(SR.LoggingMethodMustBePartialMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.LoggingMethodMustBePartialMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor LoggingMethodIsGeneric { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1011",
+ title: new LocalizableResourceString(nameof(SR.LoggingMethodIsGenericMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.LoggingMethodIsGenericMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor RedundantQualifierInMessage { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1012",
+ title: new LocalizableResourceString(nameof(SR.RedundantQualifierInMessageTitle), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.RedundantQualifierInMessageMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor ShouldntMentionExceptionInMessage { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1013",
+ title: new LocalizableResourceString(nameof(SR.ShouldntMentionExceptionInMessageTitle), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.ShouldntMentionInTemplateMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor TemplateHasNoCorrespondingArgument { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1014",
+ title: new LocalizableResourceString(nameof(SR.TemplateHasNoCorrespondingArgumentTitle), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.TemplateHasNoCorrespondingArgumentMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor ArgumentHasNoCorrespondingTemplate { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1015",
+ title: new LocalizableResourceString(nameof(SR.ArgumentHasNoCorrespondingTemplateTitle), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.ArgumentHasNoCorrespondingTemplateMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor LoggingMethodHasBody { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1016",
+ title: new LocalizableResourceString(nameof(SR.LoggingMethodHasBodyMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.LoggingMethodHasBodyMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor MissingLogLevel { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1017",
+ title: new LocalizableResourceString(nameof(SR.MissingLogLevelMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.MissingLogLevelMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor ShouldntMentionLoggerInMessage { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1018",
+ title: new LocalizableResourceString(nameof(SR.ShouldntMentionLoggerInMessageTitle), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.ShouldntMentionInTemplateMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor MissingLoggerField { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1019",
+ title: new LocalizableResourceString(nameof(SR.MissingLoggerFieldTitle), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.MissingLoggerFieldMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor MultipleLoggerFields { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1020",
+ title: new LocalizableResourceString(nameof(SR.MultipleLoggerFieldsTitle), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.MultipleLoggerFieldsMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor InconsistentTemplateCasing { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1021",
+ title: new LocalizableResourceString(nameof(SR.InconsistentTemplateCasingMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.InconsistentTemplateCasingMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor MalformedFormatStrings { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1022",
+ title: new LocalizableResourceString(nameof(SR.MalformedFormatStringsMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.MalformedFormatStringsMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor GeneratingForMax6Arguments { get; } = new DiagnosticDescriptor(
+ id: "SYSLIB1023",
+ title: new LocalizableResourceString(nameof(SR.GeneratingForMax6ArgumentsMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ messageFormat: new LocalizableResourceString(nameof(SR.GeneratingForMax6ArgumentsMessage), SR.ResourceManager, typeof(FxResources.Microsoft.Extensions.Logging.Generators.SR)),
+ category: "LoggingGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/LoggerMessageGenerator.Emitter.cs b/src/libraries/Microsoft.Extensions.Logging/gen/LoggerMessageGenerator.Emitter.cs
new file mode 100644
index 00000000000000..91065fa530fc09
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/LoggerMessageGenerator.Emitter.cs
@@ -0,0 +1,557 @@
+// 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.Text;
+using System.Threading;
+
+namespace Microsoft.Extensions.Logging.Generators
+{
+ public partial class LoggerMessageGenerator
+ {
+ internal class Emitter
+ {
+ // The maximum arity of LoggerMessage.Define.
+ private const int MaxLoggerMessageDefineArguments = 6;
+ private const int DefaultStringBuilderCapacity = 1024;
+
+ private readonly string _generatedCodeAttribute =
+ $"global::System.CodeDom.Compiler.GeneratedCodeAttribute(" +
+ $"\"{typeof(Emitter).Assembly.GetName().Name}\", " +
+ $"\"{typeof(Emitter).Assembly.GetName().Version}\")";
+ private readonly StringBuilder _builder = new StringBuilder(DefaultStringBuilderCapacity);
+
+ public string Emit(IReadOnlyList logClasses, CancellationToken cancellationToken)
+ {
+ _builder.Clear();
+ _builder.AppendLine("// ");
+ _builder.AppendLine("#nullable enable");
+
+ foreach (LoggerClass lc in logClasses)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ GenType(lc);
+ }
+
+ return _builder.ToString();
+ }
+
+ private static bool UseLoggerMessageDefine(LoggerMethod lm)
+ {
+ bool result =
+ (lm.TemplateParameters.Count <= MaxLoggerMessageDefineArguments) && // more args than LoggerMessage.Define can handle
+ (lm.Level != null) && // dynamic log level, which LoggerMessage.Define can't handle
+ (lm.TemplateList.Count == lm.TemplateParameters.Count); // mismatch in template to args, which LoggerMessage.Define can't handle
+
+ if (result)
+ {
+ // make sure the order of the templates matches the order of the logging method parameter
+ int count = 0;
+ foreach (string t in lm.TemplateList)
+ {
+ if (!t.Equals(lm.TemplateParameters[count].Name, StringComparison.OrdinalIgnoreCase))
+ {
+ // order doesn't match, can't use LoggerMessage.Define
+ return false;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private void GenType(LoggerClass lc)
+ {
+ if (!string.IsNullOrWhiteSpace(lc.Namespace))
+ {
+ _builder.Append($@"
+namespace {lc.Namespace}
+{{");
+ }
+
+ _builder.Append($@"
+ partial class {lc.Name} {lc.Constraints}
+ {{");
+
+ foreach (LoggerMethod lm in lc.Methods)
+ {
+ if (!UseLoggerMessageDefine(lm))
+ {
+ GenStruct(lm);
+ }
+
+ GenLogMethod(lm);
+ }
+
+ GenEnumerationHelper(lc);
+
+ _builder.Append($@"
+ }}");
+
+ if (!string.IsNullOrWhiteSpace(lc.Namespace))
+ {
+ _builder.Append($@"
+}}");
+ }
+ }
+
+ private void GenStruct(LoggerMethod lm)
+ {
+ _builder.AppendLine($@"
+ [{_generatedCodeAttribute}]
+ private readonly struct __{lm.Name}Struct : global::System.Collections.Generic.IReadOnlyList>
+ {{");
+ GenFields(lm);
+
+ if (lm.TemplateParameters.Count > 0)
+ {
+ _builder.Append($@"
+ public __{lm.Name}Struct(");
+ GenArguments(lm);
+ _builder.Append($@")
+ {{");
+ _builder.AppendLine();
+ GenFieldAssignments(lm);
+ _builder.Append($@"
+ }}
+");
+ }
+
+ _builder.Append($@"
+ public override string ToString()
+ {{
+");
+ GenVariableAssignments(lm);
+ _builder.Append($@"
+ return $""{lm.Message}"";
+ }}
+");
+ _builder.Append($@"
+ public static string Format(__{lm.Name}Struct state, global::System.Exception? ex) => state.ToString();
+
+ public int Count => {lm.TemplateParameters.Count + 1};
+
+ public global::System.Collections.Generic.KeyValuePair this[int index]
+ {{
+ get => index switch
+ {{
+");
+ GenCases(lm);
+ _builder.Append($@"
+ _ => throw new global::System.IndexOutOfRangeException(nameof(index)), // return the same exception LoggerMessage.Define returns in this case
+ }};
+ }}
+
+ public global::System.Collections.Generic.IEnumerator> GetEnumerator()
+ {{
+ for (int i = 0; i < {lm.TemplateParameters.Count + 1}; i++)
+ {{
+ yield return this[i];
+ }}
+ }}
+
+ global::System.Collections.IEnumerator global::System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
+ }}
+");
+ }
+
+ private void GenFields(LoggerMethod lm)
+ {
+ foreach (LoggerParameter p in lm.TemplateParameters)
+ {
+ _builder.AppendLine($" private readonly {p.Type} _{p.Name};");
+ }
+ }
+
+ private void GenFieldAssignments(LoggerMethod lm)
+ {
+ foreach (LoggerParameter p in lm.TemplateParameters)
+ {
+ _builder.AppendLine($" this._{p.Name} = {p.Name};");
+ }
+ }
+
+ private void GenVariableAssignments(LoggerMethod lm)
+ {
+ foreach (KeyValuePair t in lm.TemplateMap)
+ {
+ int index = 0;
+ foreach (LoggerParameter p in lm.TemplateParameters)
+ {
+ if (t.Key.Equals(p.Name, System.StringComparison.OrdinalIgnoreCase))
+ {
+ break;
+ }
+
+ index++;
+ }
+
+ // check for an index that's too big, this can happen in some cases of malformed input
+ if (index < lm.TemplateParameters.Count)
+ {
+ if (lm.TemplateParameters[index].IsEnumerable)
+ {
+ _builder.AppendLine($" var {t.Key} = "
+ + $"__Enumerate((global::System.Collections.IEnumerable ?)this._{lm.TemplateParameters[index].Name});");
+ }
+ else
+ {
+ _builder.AppendLine($" var {t.Key} = this._{lm.TemplateParameters[index].Name};");
+ }
+ }
+ }
+ }
+
+ private void GenCases(LoggerMethod lm)
+ {
+ int index = 0;
+ foreach (LoggerParameter p in lm.TemplateParameters)
+ {
+ string name = p.Name;
+ if (lm.TemplateMap.ContainsKey(name))
+ {
+ // take the letter casing from the template
+ name = lm.TemplateMap[name];
+ }
+
+ _builder.AppendLine($" {index++} => new global::System.Collections.Generic.KeyValuePair(\"{name}\", this._{p.Name}),");
+ }
+
+ _builder.AppendLine($" {index++} => new global::System.Collections.Generic.KeyValuePair(\"{{OriginalFormat}}\", \"{ConvertEndOfLineAndQuotationCharactersToEscapeForm(lm.Message)}\"),");
+ }
+
+ private void GenCallbackArguments(LoggerMethod lm)
+ {
+ foreach (LoggerParameter p in lm.TemplateParameters)
+ {
+ _builder.Append($"{p.Name}, ");
+ }
+ }
+
+ private void GenDefineTypes(LoggerMethod lm, bool brackets)
+ {
+ if (lm.TemplateParameters.Count == 0)
+ {
+ return;
+ }
+ if (brackets)
+ {
+ _builder.Append("<");
+ }
+
+ bool firstItem = true;
+ foreach (LoggerParameter p in lm.TemplateParameters)
+ {
+ if (firstItem)
+ {
+ firstItem = false;
+ }
+ else
+ {
+ _builder.Append(", ");
+ }
+
+ _builder.Append($"{p.Type}");
+ }
+
+ if (brackets)
+ {
+ _builder.Append(">");
+ }
+ else
+ {
+ _builder.Append(", ");
+ }
+ }
+
+ private void GenParameters(LoggerMethod lm)
+ {
+ bool firstItem = true;
+ foreach (LoggerParameter p in lm.AllParameters)
+ {
+ if (firstItem)
+ {
+ firstItem = false;
+ }
+ else
+ {
+ _builder.Append(", ");
+ }
+
+ _builder.Append($"{p.Type} {p.Name}");
+ }
+ }
+
+ private void GenArguments(LoggerMethod lm)
+ {
+ bool firstItem = true;
+ foreach (LoggerParameter p in lm.TemplateParameters)
+ {
+ if (firstItem)
+ {
+ firstItem = false;
+ }
+ else
+ {
+ _builder.Append(", ");
+ }
+
+ _builder.Append($"{p.Type} {p.Name}");
+ }
+ }
+
+ private void GenHolder(LoggerMethod lm)
+ {
+ string typeName = $"__{lm.Name}Struct";
+
+ _builder.Append($"new {typeName}(");
+ foreach (LoggerParameter p in lm.TemplateParameters)
+ {
+ if (p != lm.TemplateParameters[0])
+ {
+ _builder.Append(", ");
+ }
+
+ _builder.Append(p.Name);
+ }
+
+ _builder.Append(')');
+ }
+
+ private void GenLogMethod(LoggerMethod lm)
+ {
+ string level = GetLogLevel(lm);
+ string extension = (lm.IsExtensionMethod ? "this " : string.Empty);
+ string eventName = string.IsNullOrWhiteSpace(lm.EventName) ? $"nameof({lm.Name})" : $"\"{lm.EventName}\"";
+ string exceptionArg = GetException(lm);
+ string logger = GetLogger(lm);
+
+ if (UseLoggerMessageDefine(lm))
+ {
+ _builder.Append($@"
+ [{_generatedCodeAttribute}]
+ private static readonly global::System.Action __{lm.Name}Callback =
+ global::Microsoft.Extensions.Logging.LoggerMessage.Define");
+
+ GenDefineTypes(lm, brackets: true);
+
+ _builder.Append(@$"({level}, new global::Microsoft.Extensions.Logging.EventId({lm.EventId}, {eventName}), ""{ConvertEndOfLineAndQuotationCharactersToEscapeForm(lm.Message)}"", true);
+");
+ }
+
+ _builder.Append($@"
+ [{_generatedCodeAttribute}]
+ {lm.Modifiers} void {lm.Name}({extension}");
+
+ GenParameters(lm);
+
+ _builder.Append($@")
+ {{
+ if ({logger}.IsEnabled({level}))
+ {{");
+
+ if (UseLoggerMessageDefine(lm))
+ {
+ _builder.Append($@"
+ __{lm.Name}Callback({logger}, ");
+
+ GenCallbackArguments(lm);
+
+ _builder.Append(@$"{exceptionArg});");
+ }
+ else
+ {
+ _builder.Append($@"
+ {logger}.Log(
+ {level},
+ new global::Microsoft.Extensions.Logging.EventId({lm.EventId}, {eventName}),
+ ");
+ GenHolder(lm);
+ _builder.Append($@",
+ {exceptionArg},
+ __{lm.Name}Struct.Format);");
+ }
+
+ _builder.Append($@"
+ }}
+ }}");
+
+ static string GetException(LoggerMethod lm)
+ {
+ string exceptionArg = "null";
+ foreach (LoggerParameter p in lm.AllParameters)
+ {
+ if (p.IsException)
+ {
+ exceptionArg = p.Name;
+ break;
+ }
+ }
+ return exceptionArg;
+ }
+
+ static string GetLogger(LoggerMethod lm)
+ {
+ string logger = lm.LoggerField;
+ foreach (LoggerParameter p in lm.AllParameters)
+ {
+ if (p.IsLogger)
+ {
+ logger = p.Name;
+ break;
+ }
+ }
+ return logger;
+ }
+
+ static string GetLogLevel(LoggerMethod lm)
+ {
+ string level = string.Empty;
+
+ if (lm.Level == null)
+ {
+ foreach (LoggerParameter p in lm.AllParameters)
+ {
+ if (p.IsLogLevel)
+ {
+ level = p.Name;
+ break;
+ }
+ }
+ }
+ else
+ {
+ level = lm.Level switch
+ {
+ 0 => "global::Microsoft.Extensions.Logging.LogLevel.Trace",
+ 1 => "global::Microsoft.Extensions.Logging.LogLevel.Debug",
+ 2 => "global::Microsoft.Extensions.Logging.LogLevel.Information",
+ 3 => "global::Microsoft.Extensions.Logging.LogLevel.Warning",
+ 4 => "global::Microsoft.Extensions.Logging.LogLevel.Error",
+ 5 => "global::Microsoft.Extensions.Logging.LogLevel.Critical",
+ 6 => "global::Microsoft.Extensions.Logging.LogLevel.None",
+ _ => $"(global::Microsoft.Extensions.Logging.LogLevel){lm.Level}",
+ };
+ }
+
+ return level;
+ }
+ }
+
+ private void GenEnumerationHelper(LoggerClass lc)
+ {
+ foreach (LoggerMethod lm in lc.Methods)
+ {
+ if (UseLoggerMessageDefine(lm))
+ {
+ foreach (LoggerParameter p in lm.TemplateParameters)
+ {
+ if (p.IsEnumerable)
+ {
+ _builder.Append($@"
+ [{_generatedCodeAttribute}]
+ private static string __Enumerate(global::System.Collections.IEnumerable? enumerable)
+ {{
+ if (enumerable == null)
+ {{
+ return ""(null)"";
+ }}
+
+ var sb = new global::System.Text.StringBuilder();
+ _ = sb.Append('[');
+
+ bool first = true;
+ foreach (object e in enumerable)
+ {{
+ if (!first)
+ {{
+ _ = sb.Append("", "");
+ }}
+
+ if (e == null)
+ {{
+ _ = sb.Append(""(null)"");
+ }}
+ else
+ {{
+ if (e is global::System.IFormattable fmt)
+ {{
+ _ = sb.Append(fmt.ToString(null, global::System.Globalization.CultureInfo.InvariantCulture));
+ }}
+ else
+ {{
+ _ = sb.Append(e);
+ }}
+ }}
+
+ first = false;
+ }}
+
+ _ = sb.Append(']');
+
+ return sb.ToString();
+ }}
+");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static string ConvertEndOfLineAndQuotationCharactersToEscapeForm(string s)
+ {
+ int index = 0;
+ while (index < s.Length)
+ {
+ if (s[index] == '\n' || s[index] == '\r' || s[index] == '"')
+ {
+ break;
+ }
+ index++;
+ }
+
+ if (index >= s.Length)
+ {
+ return s;
+ }
+
+ StringBuilder sb = new StringBuilder(s.Length);
+ sb.Append(s, 0, index);
+
+ while (index < s.Length)
+ {
+ switch (s[index])
+ {
+ case '\n':
+ sb.Append('\\');
+ sb.Append('n');
+ break;
+
+ case '\r':
+ sb.Append('\\');
+ sb.Append('r');
+ break;
+
+ case '"':
+ sb.Append('\\');
+ sb.Append('"');
+ break;
+
+ default:
+ sb.Append(s[index]);
+ break;
+ }
+
+ index++;
+ }
+
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/LoggerMessageGenerator.Parser.cs b/src/libraries/Microsoft.Extensions.Logging/gen/LoggerMessageGenerator.Parser.cs
new file mode 100644
index 00000000000000..3b909af90cc6a8
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/LoggerMessageGenerator.Parser.cs
@@ -0,0 +1,664 @@
+// 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 System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Microsoft.Extensions.Logging.Generators
+{
+ public partial class LoggerMessageGenerator
+ {
+ internal class Parser
+ {
+ private readonly CancellationToken _cancellationToken;
+ private readonly Compilation _compilation;
+ private readonly Action _reportDiagnostic;
+
+ public Parser(Compilation compilation, Action reportDiagnostic, CancellationToken cancellationToken)
+ {
+ _compilation = compilation;
+ _cancellationToken = cancellationToken;
+ _reportDiagnostic = reportDiagnostic;
+ }
+
+ ///
+ /// Gets the set of logging classes containing methods to output.
+ ///
+ public IReadOnlyList GetLogClasses(IEnumerable classes)
+ {
+ const string LoggerMessageAttribute = "Microsoft.Extensions.Logging.LoggerMessageAttribute";
+
+ INamedTypeSymbol loggerMessageAttribute = _compilation.GetTypeByMetadataName(LoggerMessageAttribute);
+ if (loggerMessageAttribute == null)
+ {
+ // nothing to do if this type isn't available
+ return Array.Empty();
+ }
+
+ INamedTypeSymbol loggerSymbol = _compilation.GetTypeByMetadataName("Microsoft.Extensions.Logging.ILogger");
+ if (loggerSymbol == null)
+ {
+ // nothing to do if this type isn't available
+ return Array.Empty();
+ }
+
+ INamedTypeSymbol logLevelSymbol = _compilation.GetTypeByMetadataName("Microsoft.Extensions.Logging.LogLevel");
+ if (logLevelSymbol == null)
+ {
+ // nothing to do if this type isn't available
+ return Array.Empty();
+ }
+
+ INamedTypeSymbol exceptionSymbol = _compilation.GetTypeByMetadataName("System.Exception");
+ if (exceptionSymbol == null)
+ {
+ Diag(DiagnosticDescriptors.MissingRequiredType, null, "System.Exception");
+ return Array.Empty();
+ }
+
+ INamedTypeSymbol enumerableSymbol = _compilation.GetTypeByMetadataName("System.Collections.IEnumerable");
+ if (enumerableSymbol == null)
+ {
+ Diag(DiagnosticDescriptors.MissingRequiredType, null, "System.Collections.IEnumerable");
+ return Array.Empty();
+ }
+
+ INamedTypeSymbol stringSymbol = _compilation.GetTypeByMetadataName("System.String");
+ if (stringSymbol == null)
+ {
+ Diag(DiagnosticDescriptors.MissingRequiredType, null, "System.String");
+ return Array.Empty();
+ }
+
+ var results = new List();
+ var ids = new HashSet();
+
+ // we enumerate by syntax tree, to minimize the need to instantiate semantic models (since they're expensive)
+ foreach (var group in classes.GroupBy(x => x.SyntaxTree))
+ {
+ SemanticModel? sm = null;
+ foreach (ClassDeclarationSyntax classDec in group)
+ {
+ // stop if we're asked to
+ _cancellationToken.ThrowIfCancellationRequested();
+
+ LoggerClass? lc = null;
+ string nspace = string.Empty;
+ string? loggerField = null;
+ bool multipleLoggerFields = false;
+
+ ids.Clear();
+ foreach (var member in classDec.Members)
+ {
+ var method = member as MethodDeclarationSyntax;
+ if (method == null)
+ {
+ // we only care about methods
+ continue;
+ }
+
+ foreach (AttributeListSyntax mal in method.AttributeLists)
+ {
+ foreach (AttributeSyntax ma in mal.Attributes)
+ {
+ sm ??= _compilation.GetSemanticModel(classDec.SyntaxTree);
+
+ IMethodSymbol attrCtorSymbol = sm.GetSymbolInfo(ma, _cancellationToken).Symbol as IMethodSymbol;
+ if (attrCtorSymbol == null || !loggerMessageAttribute.Equals(attrCtorSymbol.ContainingType, SymbolEqualityComparer.Default))
+ {
+ // badly formed attribute definition, or not the right attribute
+ continue;
+ }
+
+ (int eventId, int? level, string? message, string? eventName) = ExtractAttributeValues(ma.ArgumentList!, sm);
+
+ IMethodSymbol? methodSymbol = sm.GetDeclaredSymbol(method, _cancellationToken);
+ if (methodSymbol != null)
+ {
+ var lm = new LoggerMethod
+ {
+ Name = method.Identifier.ToString(),
+ Level = level,
+ Message = message ?? string.Empty,
+ EventId = eventId,
+ EventName = eventName,
+ IsExtensionMethod = methodSymbol.IsExtensionMethod,
+ Modifiers = method.Modifiers.ToString(),
+ };
+
+ ExtractTemplates(message, lm.TemplateMap, lm.TemplateList);
+
+ bool keepMethod = true; // whether or not we want to keep the method definition or if it's got errors making it so we should discard it instead
+ if (lm.Name[0] == '_')
+ {
+ // can't have logging method names that start with _ since that can lead to conflicting symbol names
+ // because the generated symbols start with _
+ Diag(DiagnosticDescriptors.InvalidLoggingMethodName, method.Identifier.GetLocation());
+ keepMethod = false;
+ }
+
+ if (sm.GetTypeInfo(method.ReturnType!).Type!.SpecialType != SpecialType.System_Void)
+ {
+ // logging methods must return void
+ Diag(DiagnosticDescriptors.LoggingMethodMustReturnVoid, method.ReturnType.GetLocation());
+ keepMethod = false;
+ }
+
+ if (method.Arity > 0)
+ {
+ // we don't currently support generic methods
+ Diag(DiagnosticDescriptors.LoggingMethodIsGeneric, method.Identifier.GetLocation());
+ keepMethod = false;
+ }
+
+ bool isStatic = false;
+ bool isPartial = false;
+ foreach (SyntaxToken mod in method.Modifiers)
+ {
+ switch (mod.Text)
+ {
+ case "partial":
+ isPartial = true;
+ break;
+
+ case "static":
+ isStatic = true;
+ break;
+ }
+ }
+
+ if (!isPartial)
+ {
+ Diag(DiagnosticDescriptors.LoggingMethodMustBePartial, method.GetLocation());
+ keepMethod = false;
+ }
+
+ if (method.Body != null)
+ {
+ Diag(DiagnosticDescriptors.LoggingMethodHasBody, method.Body.GetLocation());
+ keepMethod = false;
+ }
+
+ // ensure there are no duplicate ids.
+ if (ids.Contains(lm.EventId))
+ {
+ Diag(DiagnosticDescriptors.ShouldntReuseEventIds, ma.GetLocation(), lm.EventId, classDec.Identifier.Text);
+ }
+ else
+ {
+ _ = ids.Add(lm.EventId);
+ }
+
+ string msg = lm.Message;
+ if (msg.StartsWith("INFORMATION:", StringComparison.OrdinalIgnoreCase)
+ || msg.StartsWith("INFO:", StringComparison.OrdinalIgnoreCase)
+ || msg.StartsWith("WARNING:", StringComparison.OrdinalIgnoreCase)
+ || msg.StartsWith("WARN:", StringComparison.OrdinalIgnoreCase)
+ || msg.StartsWith("ERROR:", StringComparison.OrdinalIgnoreCase)
+ || msg.StartsWith("ERR:", StringComparison.OrdinalIgnoreCase))
+ {
+ Diag(DiagnosticDescriptors.RedundantQualifierInMessage, ma.GetLocation(), method.Identifier.ToString());
+ }
+
+ bool foundLogger = false;
+ bool foundException = false;
+ bool foundLogLevel = level != null;
+ foreach (ParameterSyntax p in method.ParameterList.Parameters)
+ {
+ string paramName = p.Identifier.ToString();
+ if (string.IsNullOrWhiteSpace(paramName))
+ {
+ // semantic problem, just bail quietly
+ keepMethod = false;
+ break;
+ }
+
+ IParameterSymbol? declSymbol = sm.GetDeclaredSymbol(p);
+ ITypeSymbol paramSymbol = declSymbol!.Type;
+ if (paramSymbol is IErrorTypeSymbol)
+ {
+ // semantic problem, just bail quietly
+ keepMethod = false;
+ break;
+ }
+
+ IParameterSymbol declaredType = sm.GetDeclaredSymbol(p);
+ string typeName = declaredType!.Type.ToDisplayString(
+ SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(
+ SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier));
+
+ var lp = new LoggerParameter
+ {
+ Name = paramName,
+ Type = typeName,
+ IsLogger = !foundLogger && IsBaseOrIdentity(paramSymbol!, loggerSymbol),
+ IsException = !foundException && IsBaseOrIdentity(paramSymbol!, exceptionSymbol),
+ IsLogLevel = !foundLogLevel && IsBaseOrIdentity(paramSymbol!, logLevelSymbol),
+ IsEnumerable = IsBaseOrIdentity(paramSymbol!, enumerableSymbol) && !IsBaseOrIdentity(paramSymbol!, stringSymbol),
+ };
+
+ foundLogger |= lp.IsLogger;
+ foundException |= lp.IsException;
+ foundLogLevel |= lp.IsLogLevel;
+
+ if (lp.IsLogger && lm.TemplateMap.ContainsKey(paramName))
+ {
+ Diag(DiagnosticDescriptors.ShouldntMentionLoggerInMessage, p.Identifier.GetLocation(), paramName);
+ }
+ else if (lp.IsException && lm.TemplateMap.ContainsKey(paramName))
+ {
+ Diag(DiagnosticDescriptors.ShouldntMentionExceptionInMessage, p.Identifier.GetLocation(), paramName);
+ }
+ else if (lp.IsLogLevel && lm.TemplateMap.ContainsKey(paramName))
+ {
+ Diag(DiagnosticDescriptors.ShouldntMentionLogLevelInMessage, p.Identifier.GetLocation(), paramName);
+ }
+ else if (lp.IsLogLevel && level != null && !lm.TemplateMap.ContainsKey(paramName))
+ {
+ Diag(DiagnosticDescriptors.ArgumentHasNoCorrespondingTemplate, p.Identifier.GetLocation(), paramName);
+ }
+ else if (lp.IsTemplateParameter && !lm.TemplateMap.ContainsKey(paramName))
+ {
+ Diag(DiagnosticDescriptors.ArgumentHasNoCorrespondingTemplate, p.Identifier.GetLocation(), paramName);
+ }
+
+ if (paramName[0] == '_')
+ {
+ // can't have logging method parameter names that start with _ since that can lead to conflicting symbol names
+ // because all generated symbols start with _
+ Diag(DiagnosticDescriptors.InvalidLoggingMethodParameterName, p.Identifier.GetLocation());
+ }
+
+ lm.AllParameters.Add(lp);
+ if (lp.IsTemplateParameter)
+ {
+ lm.TemplateParameters.Add(lp);
+ }
+ }
+
+ if (keepMethod)
+ {
+ if (isStatic && !foundLogger)
+ {
+ Diag(DiagnosticDescriptors.MissingLoggerArgument, method.GetLocation());
+ keepMethod = false;
+ }
+ else if (!isStatic && foundLogger)
+ {
+ Diag(DiagnosticDescriptors.LoggingMethodShouldBeStatic, method.GetLocation());
+ }
+ else if (!isStatic && !foundLogger)
+ {
+ if (loggerField == null)
+ {
+ (loggerField, multipleLoggerFields) = FindLoggerField(sm, classDec, loggerSymbol);
+ }
+
+ if (multipleLoggerFields)
+ {
+ Diag(DiagnosticDescriptors.MultipleLoggerFields, method.GetLocation(), classDec.Identifier.Text);
+ keepMethod = false;
+ }
+ else if (loggerField == null)
+ {
+ Diag(DiagnosticDescriptors.MissingLoggerField, method.GetLocation(), classDec.Identifier.Text);
+ keepMethod = false;
+ }
+ else
+ {
+ lm.LoggerField = loggerField;
+ }
+ }
+
+ if (level == null && !foundLogLevel)
+ {
+ Diag(DiagnosticDescriptors.MissingLogLevel, method.GetLocation());
+ keepMethod = false;
+ }
+
+ foreach (KeyValuePair t in lm.TemplateMap)
+ {
+ bool found = false;
+ foreach (LoggerParameter p in lm.AllParameters)
+ {
+ if (t.Key.Equals(p.Name, StringComparison.OrdinalIgnoreCase))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ Diag(DiagnosticDescriptors.TemplateHasNoCorrespondingArgument, ma.GetLocation(), t);
+ }
+ }
+ }
+
+ if (lc == null)
+ {
+ // determine the namespace the class is declared in, if any
+ var ns = classDec.Parent as NamespaceDeclarationSyntax;
+ if (ns == null)
+ {
+ if (classDec.Parent is not CompilationUnitSyntax)
+ {
+ // since this generator doesn't know how to generate a nested type...
+ Diag(DiagnosticDescriptors.LoggingMethodInNestedType, classDec.Identifier.GetLocation());
+ keepMethod = false;
+ }
+ }
+ else
+ {
+ nspace = ns.Name.ToString();
+ while (true)
+ {
+ ns = ns.Parent as NamespaceDeclarationSyntax;
+ if (ns == null)
+ {
+ break;
+ }
+
+ nspace = $"{ns.Name}.{nspace}";
+ }
+ }
+ }
+
+ if (keepMethod)
+ {
+ lc ??= new LoggerClass
+ {
+ Namespace = nspace,
+ Name = classDec.Identifier.ToString() + classDec.TypeParameterList,
+ Constraints = classDec.ConstraintClauses.ToString(),
+ };
+
+ lc.Methods.Add(lm);
+ }
+ }
+ }
+ }
+ }
+
+ if (lc != null)
+ {
+ results.Add(lc);
+ }
+ }
+ }
+
+ return results;
+ }
+
+ private (string? loggerField, bool multipleLoggerFields) FindLoggerField(SemanticModel sm, TypeDeclarationSyntax classDec, ITypeSymbol loggerSymbol)
+ {
+ string? loggerField = null;
+
+ foreach (MemberDeclarationSyntax m in classDec.Members)
+ {
+ if (m is FieldDeclarationSyntax fds)
+ {
+ foreach (VariableDeclaratorSyntax v in fds.Declaration.Variables)
+ {
+ var fs = sm.GetDeclaredSymbol(v, _cancellationToken) as IFieldSymbol;
+ if (fs != null)
+ {
+ if (IsBaseOrIdentity(fs.Type, loggerSymbol))
+ {
+ if (loggerField == null)
+ {
+ loggerField = v.Identifier.Text;
+ }
+ else
+ {
+ return (null, true);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return (loggerField, false);
+ }
+
+ private (int eventId, int? level, string? message, string? eventName) ExtractAttributeValues(AttributeArgumentListSyntax args, SemanticModel sm)
+ {
+ // two constructor arg shapes:
+ //
+ // (eventId, level, message)
+ // (eventId, message)
+
+ int eventId = 0;
+ int? level = null;
+ string? eventName = null;
+ string? message = null;
+ int numPositional = 0;
+ foreach (AttributeArgumentSyntax a in args.Arguments)
+ {
+ if (a.NameEquals != null)
+ {
+ switch (a.NameEquals.Name.ToString())
+ {
+ case "EventId":
+ eventId = (int)sm.GetConstantValue(a.Expression, _cancellationToken).Value!;
+ break;
+ case "EventName":
+ eventName = sm.GetConstantValue(a.Expression, _cancellationToken).ToString();
+ break;
+ case "Level":
+ level = (int)sm.GetConstantValue(a.Expression, _cancellationToken).Value!;
+ break;
+ case "Message":
+ message = sm.GetConstantValue(a.Expression, _cancellationToken).ToString();
+ break;
+ }
+ }
+ else if (a.NameColon != null)
+ {
+ switch (a.NameColon.Name.ToString())
+ {
+ case "eventId":
+ eventId = (int)sm.GetConstantValue(a.Expression, _cancellationToken).Value!;
+ break;
+
+ case "level":
+ level = (int)sm.GetConstantValue(a.Expression, _cancellationToken).Value!;
+ break;
+
+ case "message":
+ message = sm.GetConstantValue(a.Expression, _cancellationToken).ToString();
+ break;
+ }
+ }
+ else
+ {
+ switch (numPositional)
+ {
+ // event id
+ case 0:
+ eventId = (int)sm.GetConstantValue(a.Expression, _cancellationToken).Value!;
+ break;
+
+ // log level or message
+ case 1:
+ object o = sm.GetConstantValue(a.Expression, _cancellationToken).Value!;
+ if (o is int l)
+ {
+ level = l;
+ }
+ else
+ {
+ message = sm.GetConstantValue(a.Expression, _cancellationToken).ToString();
+ }
+
+ break;
+
+ // message
+ case 2:
+ message = sm.GetConstantValue(a.Expression, _cancellationToken).ToString();
+ break;
+ }
+
+ numPositional++;
+ }
+ }
+
+ return (eventId, level, message, eventName);
+ }
+
+ private void Diag(DiagnosticDescriptor desc, Location? location, params object?[]? messageArgs)
+ {
+ _reportDiagnostic(Diagnostic.Create(desc, location, messageArgs));
+ }
+
+ private bool IsBaseOrIdentity(ITypeSymbol source, ITypeSymbol dest)
+ {
+ Conversion conversion = _compilation.ClassifyConversion(source, dest);
+ return conversion.IsIdentity || (conversion.IsReference && conversion.IsImplicit);
+ }
+
+ private static readonly char[] _formatDelimiters = { ',', ':' };
+
+ ///
+ /// Finds the template arguments contained in the message string.
+ ///
+ private static void ExtractTemplates(string? message, IDictionary templateMap, IList templateList)
+ {
+ if (string.IsNullOrEmpty(message))
+ {
+ return;
+ }
+
+ int scanIndex = 0;
+ int endIndex = message!.Length;
+
+ while (scanIndex < endIndex)
+ {
+ int openBraceIndex = FindBraceIndex(message, '{', scanIndex, endIndex);
+ int closeBraceIndex = FindBraceIndex(message, '}', openBraceIndex, endIndex);
+
+ if (closeBraceIndex == endIndex)
+ {
+ scanIndex = endIndex;
+ }
+ else
+ {
+ // Format item syntax : { index[,alignment][ :formatString] }.
+ int formatDelimiterIndex = FindIndexOfAny(message, _formatDelimiters, openBraceIndex, closeBraceIndex);
+
+ string templateName = message.Substring(openBraceIndex + 1, formatDelimiterIndex - openBraceIndex - 1);
+ templateMap[templateName] = templateName;
+ templateList.Add(templateName);
+ scanIndex = closeBraceIndex + 1;
+ }
+ }
+ }
+
+ private static int FindBraceIndex(string message, char brace, int startIndex, int endIndex)
+ {
+ // Example: {{prefix{{{Argument}}}suffix}}.
+ int braceIndex = endIndex;
+ int scanIndex = startIndex;
+ int braceOccurrenceCount = 0;
+
+ while (scanIndex < endIndex)
+ {
+ if (braceOccurrenceCount > 0 && message[scanIndex] != brace)
+ {
+ if (braceOccurrenceCount % 2 == 0)
+ {
+ // Even number of '{' or '}' found. Proceed search with next occurrence of '{' or '}'.
+ braceOccurrenceCount = 0;
+ braceIndex = endIndex;
+ }
+ else
+ {
+ // An unescaped '{' or '}' found.
+ break;
+ }
+ }
+ else if (message[scanIndex] == brace)
+ {
+ if (brace == '}')
+ {
+ if (braceOccurrenceCount == 0)
+ {
+ // For '}' pick the first occurrence.
+ braceIndex = scanIndex;
+ }
+ }
+ else
+ {
+ // For '{' pick the last occurrence.
+ braceIndex = scanIndex;
+ }
+
+ braceOccurrenceCount++;
+ }
+
+ scanIndex++;
+ }
+
+ return braceIndex;
+ }
+
+ private static int FindIndexOfAny(string message, char[] chars, int startIndex, int endIndex)
+ {
+ int findIndex = message.IndexOfAny(chars, startIndex, endIndex - startIndex);
+ return findIndex == -1 ? endIndex : findIndex;
+ }
+ }
+
+ ///
+ /// A logger class holding a bunch of logger methods.
+ ///
+ internal class LoggerClass
+ {
+ public readonly List Methods = new ();
+ public string Namespace = string.Empty;
+ public string Name = string.Empty;
+ public string Constraints = string.Empty;
+ }
+
+ ///
+ /// A logger method in a logger class.
+ ///
+ internal class LoggerMethod
+ {
+ public readonly List AllParameters = new ();
+ public readonly List TemplateParameters = new ();
+ public readonly Dictionary TemplateMap = new (StringComparer.OrdinalIgnoreCase);
+ public readonly List TemplateList = new ();
+ public string Name = string.Empty;
+ public string Message = string.Empty;
+ public int? Level;
+ public int EventId;
+ public string? EventName;
+ public bool IsExtensionMethod;
+ public string Modifiers = string.Empty;
+ public string LoggerField;
+ }
+
+ ///
+ /// A single parameter to a logger method.
+ ///
+ internal class LoggerParameter
+ {
+ public string Name = string.Empty;
+ public string Type = string.Empty;
+ public bool IsLogger;
+ public bool IsException;
+ public bool IsLogLevel;
+ public bool IsEnumerable;
+ // A parameter flagged as IsTemplateParameter is not going to be taken care of specially as an argument to ILogger.Log
+ // but instead is supposed to be taken as a parameter for the template.
+ public bool IsTemplateParameter => !IsLogger && !IsException && !IsLogLevel;
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/LoggerMessageGenerator.cs b/src/libraries/Microsoft.Extensions.Logging/gen/LoggerMessageGenerator.cs
new file mode 100644
index 00000000000000..9c1d2cbcfeb91b
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/LoggerMessageGenerator.cs
@@ -0,0 +1,65 @@
+// 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.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+[assembly: System.Resources.NeutralResourcesLanguage("en-us")]
+
+namespace Microsoft.Extensions.Logging.Generators
+{
+ [Generator]
+ public partial class LoggerMessageGenerator : ISourceGenerator
+ {
+ [ExcludeFromCodeCoverage]
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ context.RegisterForSyntaxNotifications(SyntaxReceiver.Create);
+ }
+
+ [ExcludeFromCodeCoverage]
+ public void Execute(GeneratorExecutionContext context)
+ {
+ if (context.SyntaxReceiver is not SyntaxReceiver receiver || receiver.ClassDeclarations.Count == 0)
+ {
+ // nothing to do yet
+ return;
+ }
+
+ var p = new Parser(context.Compilation, context.ReportDiagnostic, context.CancellationToken);
+ IReadOnlyList logClasses = p.GetLogClasses(receiver.ClassDeclarations);
+ if (logClasses.Count > 0)
+ {
+ var e = new Emitter();
+ string result = e.Emit(logClasses, context.CancellationToken);
+
+ context.AddSource(nameof(LoggerMessageGenerator), SourceText.From(result, Encoding.UTF8));
+ }
+ }
+
+ [ExcludeFromCodeCoverage]
+ private sealed class SyntaxReceiver : ISyntaxReceiver
+ {
+ internal static ISyntaxReceiver Create()
+ {
+ return new SyntaxReceiver();
+ }
+
+ public List ClassDeclarations { get; } = new ();
+
+ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
+ {
+ if (syntaxNode is ClassDeclarationSyntax classSyntax)
+ {
+ ClassDeclarations.Add(classSyntax);
+ }
+ }
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/Microsoft.Extensions.Logging.Generators.csproj b/src/libraries/Microsoft.Extensions.Logging/gen/Microsoft.Extensions.Logging.Generators.csproj
new file mode 100644
index 00000000000000..719c9011b391e5
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/Microsoft.Extensions.Logging.Generators.csproj
@@ -0,0 +1,27 @@
+
+
+
+ netstandard2.0
+ enable
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/Resources/Strings.resx b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/Strings.resx
new file mode 100644
index 00000000000000..f0d6b21bf7ae87
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/Strings.resx
@@ -0,0 +1,218 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+ Logging method names cannot start with _
+
+
+ Logging method parameter names cannot start with _
+
+
+ Logging class cannot be in nested types
+
+
+ Could not find a required type definition
+
+
+ Could not find definition for type {0}
+
+
+ Multiple logging methods cannot use the same event id within a class
+
+
+ Multiple logging methods are using event id {0} in class {1}
+
+
+ Logging methods must return void
+
+
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Logging methods must be static
+
+
+ Logging methods must be partial
+
+
+ Logging methods cannot be generic
+
+
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+
+
+ Don't include exception parameters as templates in the logging message
+
+
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+
+
+ Redundant qualifier in logging message
+
+
+ Argument {0} is not referenced from the logging message
+
+
+ Argument is not referenced from the logging message
+
+
+ Template {0} is not provided as argument to the logging method
+
+
+ Logging template has no corresponding method argument
+
+
+ Logging methods cannot have a body
+
+
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+
+
+ Don't include log level parameters as templates in the logging message
+
+
+ Don't include logger parameters as templates in the logging message
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Can't have the same template with different casing
+
+
+ Can't have malformed format strings (like dangling {, etc)
+
+
+ Generating more than 6 arguments is not supported
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.cs.xlf b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.cs.xlf
new file mode 100644
index 00000000000000..044e1bd67633f0
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.cs.xlf
@@ -0,0 +1,162 @@
+
+
+
+
+
+ Argument {0} is not referenced from the logging message
+ Argument {0} is not referenced from the logging message
+
+
+
+ Argument is not referenced from the logging message
+ Argument is not referenced from the logging message
+
+
+
+ Generating more than 6 arguments is not supported
+ Generating more than 6 arguments is not supported
+
+
+
+ Can't have the same template with different casing
+ Can't have the same template with different casing
+
+
+
+ Logging method names cannot start with _
+ Logging method names cannot start with _
+
+
+
+ Logging method parameter names cannot start with _
+ Logging method parameter names cannot start with _
+
+
+
+ Logging methods cannot have a body
+ Logging methods cannot have a body
+
+
+
+ Logging class cannot be in nested types
+ Logging class cannot be in nested types
+
+
+
+ Logging methods cannot be generic
+ Logging methods cannot be generic
+
+
+
+ Logging methods must be partial
+ Logging methods must be partial
+
+
+
+ Logging methods must return void
+ Logging methods must return void
+
+
+
+ Logging methods must be static
+ Logging methods must be static
+
+
+
+ Can't have malformed format strings (like dangling {, etc)
+ Can't have malformed format strings (like dangling {, etc)
+
+
+
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+
+
+
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Could not find definition for type {0}
+ Could not find definition for type {0}
+
+
+
+ Could not find a required type definition
+ Could not find a required type definition
+
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+
+
+
+ Redundant qualifier in logging message
+ Redundant qualifier in logging message
+
+
+
+ Don't include exception parameters as templates in the logging message
+ Don't include exception parameters as templates in the logging message
+
+
+
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+
+
+
+ Don't include log level parameters as templates in the logging message
+ Don't include log level parameters as templates in the logging message
+
+
+
+ Don't include logger parameters as templates in the logging message
+ Don't include logger parameters as templates in the logging message
+
+
+
+ Multiple logging methods are using event id {0} in class {1}
+ Multiple logging methods are using event id {0} in class {1}
+
+
+
+ Multiple logging methods cannot use the same event id within a class
+ Multiple logging methods cannot use the same event id within a class
+
+
+
+ Template {0} is not provided as argument to the logging method
+ Template {0} is not provided as argument to the logging method
+
+
+
+ Logging template has no corresponding method argument
+ Logging template has no corresponding method argument
+
+
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.de.xlf b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.de.xlf
new file mode 100644
index 00000000000000..258f697c492e26
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.de.xlf
@@ -0,0 +1,162 @@
+
+
+
+
+
+ Argument {0} is not referenced from the logging message
+ Argument {0} is not referenced from the logging message
+
+
+
+ Argument is not referenced from the logging message
+ Argument is not referenced from the logging message
+
+
+
+ Generating more than 6 arguments is not supported
+ Generating more than 6 arguments is not supported
+
+
+
+ Can't have the same template with different casing
+ Can't have the same template with different casing
+
+
+
+ Logging method names cannot start with _
+ Logging method names cannot start with _
+
+
+
+ Logging method parameter names cannot start with _
+ Logging method parameter names cannot start with _
+
+
+
+ Logging methods cannot have a body
+ Logging methods cannot have a body
+
+
+
+ Logging class cannot be in nested types
+ Logging class cannot be in nested types
+
+
+
+ Logging methods cannot be generic
+ Logging methods cannot be generic
+
+
+
+ Logging methods must be partial
+ Logging methods must be partial
+
+
+
+ Logging methods must return void
+ Logging methods must return void
+
+
+
+ Logging methods must be static
+ Logging methods must be static
+
+
+
+ Can't have malformed format strings (like dangling {, etc)
+ Can't have malformed format strings (like dangling {, etc)
+
+
+
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+
+
+
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Could not find definition for type {0}
+ Could not find definition for type {0}
+
+
+
+ Could not find a required type definition
+ Could not find a required type definition
+
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+
+
+
+ Redundant qualifier in logging message
+ Redundant qualifier in logging message
+
+
+
+ Don't include exception parameters as templates in the logging message
+ Don't include exception parameters as templates in the logging message
+
+
+
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+
+
+
+ Don't include log level parameters as templates in the logging message
+ Don't include log level parameters as templates in the logging message
+
+
+
+ Don't include logger parameters as templates in the logging message
+ Don't include logger parameters as templates in the logging message
+
+
+
+ Multiple logging methods are using event id {0} in class {1}
+ Multiple logging methods are using event id {0} in class {1}
+
+
+
+ Multiple logging methods cannot use the same event id within a class
+ Multiple logging methods cannot use the same event id within a class
+
+
+
+ Template {0} is not provided as argument to the logging method
+ Template {0} is not provided as argument to the logging method
+
+
+
+ Logging template has no corresponding method argument
+ Logging template has no corresponding method argument
+
+
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.es.xlf b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.es.xlf
new file mode 100644
index 00000000000000..0424989915dd77
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.es.xlf
@@ -0,0 +1,162 @@
+
+
+
+
+
+ Argument {0} is not referenced from the logging message
+ Argument {0} is not referenced from the logging message
+
+
+
+ Argument is not referenced from the logging message
+ Argument is not referenced from the logging message
+
+
+
+ Generating more than 6 arguments is not supported
+ Generating more than 6 arguments is not supported
+
+
+
+ Can't have the same template with different casing
+ Can't have the same template with different casing
+
+
+
+ Logging method names cannot start with _
+ Logging method names cannot start with _
+
+
+
+ Logging method parameter names cannot start with _
+ Logging method parameter names cannot start with _
+
+
+
+ Logging methods cannot have a body
+ Logging methods cannot have a body
+
+
+
+ Logging class cannot be in nested types
+ Logging class cannot be in nested types
+
+
+
+ Logging methods cannot be generic
+ Logging methods cannot be generic
+
+
+
+ Logging methods must be partial
+ Logging methods must be partial
+
+
+
+ Logging methods must return void
+ Logging methods must return void
+
+
+
+ Logging methods must be static
+ Logging methods must be static
+
+
+
+ Can't have malformed format strings (like dangling {, etc)
+ Can't have malformed format strings (like dangling {, etc)
+
+
+
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+
+
+
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Could not find definition for type {0}
+ Could not find definition for type {0}
+
+
+
+ Could not find a required type definition
+ Could not find a required type definition
+
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+
+
+
+ Redundant qualifier in logging message
+ Redundant qualifier in logging message
+
+
+
+ Don't include exception parameters as templates in the logging message
+ Don't include exception parameters as templates in the logging message
+
+
+
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+
+
+
+ Don't include log level parameters as templates in the logging message
+ Don't include log level parameters as templates in the logging message
+
+
+
+ Don't include logger parameters as templates in the logging message
+ Don't include logger parameters as templates in the logging message
+
+
+
+ Multiple logging methods are using event id {0} in class {1}
+ Multiple logging methods are using event id {0} in class {1}
+
+
+
+ Multiple logging methods cannot use the same event id within a class
+ Multiple logging methods cannot use the same event id within a class
+
+
+
+ Template {0} is not provided as argument to the logging method
+ Template {0} is not provided as argument to the logging method
+
+
+
+ Logging template has no corresponding method argument
+ Logging template has no corresponding method argument
+
+
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.fr.xlf b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.fr.xlf
new file mode 100644
index 00000000000000..a482372783f57c
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.fr.xlf
@@ -0,0 +1,162 @@
+
+
+
+
+
+ Argument {0} is not referenced from the logging message
+ Argument {0} is not referenced from the logging message
+
+
+
+ Argument is not referenced from the logging message
+ Argument is not referenced from the logging message
+
+
+
+ Generating more than 6 arguments is not supported
+ Generating more than 6 arguments is not supported
+
+
+
+ Can't have the same template with different casing
+ Can't have the same template with different casing
+
+
+
+ Logging method names cannot start with _
+ Logging method names cannot start with _
+
+
+
+ Logging method parameter names cannot start with _
+ Logging method parameter names cannot start with _
+
+
+
+ Logging methods cannot have a body
+ Logging methods cannot have a body
+
+
+
+ Logging class cannot be in nested types
+ Logging class cannot be in nested types
+
+
+
+ Logging methods cannot be generic
+ Logging methods cannot be generic
+
+
+
+ Logging methods must be partial
+ Logging methods must be partial
+
+
+
+ Logging methods must return void
+ Logging methods must return void
+
+
+
+ Logging methods must be static
+ Logging methods must be static
+
+
+
+ Can't have malformed format strings (like dangling {, etc)
+ Can't have malformed format strings (like dangling {, etc)
+
+
+
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+
+
+
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Could not find definition for type {0}
+ Could not find definition for type {0}
+
+
+
+ Could not find a required type definition
+ Could not find a required type definition
+
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+
+
+
+ Redundant qualifier in logging message
+ Redundant qualifier in logging message
+
+
+
+ Don't include exception parameters as templates in the logging message
+ Don't include exception parameters as templates in the logging message
+
+
+
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+
+
+
+ Don't include log level parameters as templates in the logging message
+ Don't include log level parameters as templates in the logging message
+
+
+
+ Don't include logger parameters as templates in the logging message
+ Don't include logger parameters as templates in the logging message
+
+
+
+ Multiple logging methods are using event id {0} in class {1}
+ Multiple logging methods are using event id {0} in class {1}
+
+
+
+ Multiple logging methods cannot use the same event id within a class
+ Multiple logging methods cannot use the same event id within a class
+
+
+
+ Template {0} is not provided as argument to the logging method
+ Template {0} is not provided as argument to the logging method
+
+
+
+ Logging template has no corresponding method argument
+ Logging template has no corresponding method argument
+
+
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.it.xlf b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.it.xlf
new file mode 100644
index 00000000000000..304056d1bd69fd
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.it.xlf
@@ -0,0 +1,162 @@
+
+
+
+
+
+ Argument {0} is not referenced from the logging message
+ Argument {0} is not referenced from the logging message
+
+
+
+ Argument is not referenced from the logging message
+ Argument is not referenced from the logging message
+
+
+
+ Generating more than 6 arguments is not supported
+ Generating more than 6 arguments is not supported
+
+
+
+ Can't have the same template with different casing
+ Can't have the same template with different casing
+
+
+
+ Logging method names cannot start with _
+ Logging method names cannot start with _
+
+
+
+ Logging method parameter names cannot start with _
+ Logging method parameter names cannot start with _
+
+
+
+ Logging methods cannot have a body
+ Logging methods cannot have a body
+
+
+
+ Logging class cannot be in nested types
+ Logging class cannot be in nested types
+
+
+
+ Logging methods cannot be generic
+ Logging methods cannot be generic
+
+
+
+ Logging methods must be partial
+ Logging methods must be partial
+
+
+
+ Logging methods must return void
+ Logging methods must return void
+
+
+
+ Logging methods must be static
+ Logging methods must be static
+
+
+
+ Can't have malformed format strings (like dangling {, etc)
+ Can't have malformed format strings (like dangling {, etc)
+
+
+
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+
+
+
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Could not find definition for type {0}
+ Could not find definition for type {0}
+
+
+
+ Could not find a required type definition
+ Could not find a required type definition
+
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+
+
+
+ Redundant qualifier in logging message
+ Redundant qualifier in logging message
+
+
+
+ Don't include exception parameters as templates in the logging message
+ Don't include exception parameters as templates in the logging message
+
+
+
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+
+
+
+ Don't include log level parameters as templates in the logging message
+ Don't include log level parameters as templates in the logging message
+
+
+
+ Don't include logger parameters as templates in the logging message
+ Don't include logger parameters as templates in the logging message
+
+
+
+ Multiple logging methods are using event id {0} in class {1}
+ Multiple logging methods are using event id {0} in class {1}
+
+
+
+ Multiple logging methods cannot use the same event id within a class
+ Multiple logging methods cannot use the same event id within a class
+
+
+
+ Template {0} is not provided as argument to the logging method
+ Template {0} is not provided as argument to the logging method
+
+
+
+ Logging template has no corresponding method argument
+ Logging template has no corresponding method argument
+
+
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.ja.xlf b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.ja.xlf
new file mode 100644
index 00000000000000..c8b172fd7630b6
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.ja.xlf
@@ -0,0 +1,162 @@
+
+
+
+
+
+ Argument {0} is not referenced from the logging message
+ Argument {0} is not referenced from the logging message
+
+
+
+ Argument is not referenced from the logging message
+ Argument is not referenced from the logging message
+
+
+
+ Generating more than 6 arguments is not supported
+ Generating more than 6 arguments is not supported
+
+
+
+ Can't have the same template with different casing
+ Can't have the same template with different casing
+
+
+
+ Logging method names cannot start with _
+ Logging method names cannot start with _
+
+
+
+ Logging method parameter names cannot start with _
+ Logging method parameter names cannot start with _
+
+
+
+ Logging methods cannot have a body
+ Logging methods cannot have a body
+
+
+
+ Logging class cannot be in nested types
+ Logging class cannot be in nested types
+
+
+
+ Logging methods cannot be generic
+ Logging methods cannot be generic
+
+
+
+ Logging methods must be partial
+ Logging methods must be partial
+
+
+
+ Logging methods must return void
+ Logging methods must return void
+
+
+
+ Logging methods must be static
+ Logging methods must be static
+
+
+
+ Can't have malformed format strings (like dangling {, etc)
+ Can't have malformed format strings (like dangling {, etc)
+
+
+
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+
+
+
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Could not find definition for type {0}
+ Could not find definition for type {0}
+
+
+
+ Could not find a required type definition
+ Could not find a required type definition
+
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+
+
+
+ Redundant qualifier in logging message
+ Redundant qualifier in logging message
+
+
+
+ Don't include exception parameters as templates in the logging message
+ Don't include exception parameters as templates in the logging message
+
+
+
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+
+
+
+ Don't include log level parameters as templates in the logging message
+ Don't include log level parameters as templates in the logging message
+
+
+
+ Don't include logger parameters as templates in the logging message
+ Don't include logger parameters as templates in the logging message
+
+
+
+ Multiple logging methods are using event id {0} in class {1}
+ Multiple logging methods are using event id {0} in class {1}
+
+
+
+ Multiple logging methods cannot use the same event id within a class
+ Multiple logging methods cannot use the same event id within a class
+
+
+
+ Template {0} is not provided as argument to the logging method
+ Template {0} is not provided as argument to the logging method
+
+
+
+ Logging template has no corresponding method argument
+ Logging template has no corresponding method argument
+
+
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.ko.xlf b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.ko.xlf
new file mode 100644
index 00000000000000..fdb53576c829ad
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.ko.xlf
@@ -0,0 +1,162 @@
+
+
+
+
+
+ Argument {0} is not referenced from the logging message
+ Argument {0} is not referenced from the logging message
+
+
+
+ Argument is not referenced from the logging message
+ Argument is not referenced from the logging message
+
+
+
+ Generating more than 6 arguments is not supported
+ Generating more than 6 arguments is not supported
+
+
+
+ Can't have the same template with different casing
+ Can't have the same template with different casing
+
+
+
+ Logging method names cannot start with _
+ Logging method names cannot start with _
+
+
+
+ Logging method parameter names cannot start with _
+ Logging method parameter names cannot start with _
+
+
+
+ Logging methods cannot have a body
+ Logging methods cannot have a body
+
+
+
+ Logging class cannot be in nested types
+ Logging class cannot be in nested types
+
+
+
+ Logging methods cannot be generic
+ Logging methods cannot be generic
+
+
+
+ Logging methods must be partial
+ Logging methods must be partial
+
+
+
+ Logging methods must return void
+ Logging methods must return void
+
+
+
+ Logging methods must be static
+ Logging methods must be static
+
+
+
+ Can't have malformed format strings (like dangling {, etc)
+ Can't have malformed format strings (like dangling {, etc)
+
+
+
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+
+
+
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Could not find definition for type {0}
+ Could not find definition for type {0}
+
+
+
+ Could not find a required type definition
+ Could not find a required type definition
+
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+
+
+
+ Redundant qualifier in logging message
+ Redundant qualifier in logging message
+
+
+
+ Don't include exception parameters as templates in the logging message
+ Don't include exception parameters as templates in the logging message
+
+
+
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+
+
+
+ Don't include log level parameters as templates in the logging message
+ Don't include log level parameters as templates in the logging message
+
+
+
+ Don't include logger parameters as templates in the logging message
+ Don't include logger parameters as templates in the logging message
+
+
+
+ Multiple logging methods are using event id {0} in class {1}
+ Multiple logging methods are using event id {0} in class {1}
+
+
+
+ Multiple logging methods cannot use the same event id within a class
+ Multiple logging methods cannot use the same event id within a class
+
+
+
+ Template {0} is not provided as argument to the logging method
+ Template {0} is not provided as argument to the logging method
+
+
+
+ Logging template has no corresponding method argument
+ Logging template has no corresponding method argument
+
+
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.pl.xlf b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.pl.xlf
new file mode 100644
index 00000000000000..790fec15f6a669
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.pl.xlf
@@ -0,0 +1,162 @@
+
+
+
+
+
+ Argument {0} is not referenced from the logging message
+ Argument {0} is not referenced from the logging message
+
+
+
+ Argument is not referenced from the logging message
+ Argument is not referenced from the logging message
+
+
+
+ Generating more than 6 arguments is not supported
+ Generating more than 6 arguments is not supported
+
+
+
+ Can't have the same template with different casing
+ Can't have the same template with different casing
+
+
+
+ Logging method names cannot start with _
+ Logging method names cannot start with _
+
+
+
+ Logging method parameter names cannot start with _
+ Logging method parameter names cannot start with _
+
+
+
+ Logging methods cannot have a body
+ Logging methods cannot have a body
+
+
+
+ Logging class cannot be in nested types
+ Logging class cannot be in nested types
+
+
+
+ Logging methods cannot be generic
+ Logging methods cannot be generic
+
+
+
+ Logging methods must be partial
+ Logging methods must be partial
+
+
+
+ Logging methods must return void
+ Logging methods must return void
+
+
+
+ Logging methods must be static
+ Logging methods must be static
+
+
+
+ Can't have malformed format strings (like dangling {, etc)
+ Can't have malformed format strings (like dangling {, etc)
+
+
+
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+
+
+
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Could not find definition for type {0}
+ Could not find definition for type {0}
+
+
+
+ Could not find a required type definition
+ Could not find a required type definition
+
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+
+
+
+ Redundant qualifier in logging message
+ Redundant qualifier in logging message
+
+
+
+ Don't include exception parameters as templates in the logging message
+ Don't include exception parameters as templates in the logging message
+
+
+
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+
+
+
+ Don't include log level parameters as templates in the logging message
+ Don't include log level parameters as templates in the logging message
+
+
+
+ Don't include logger parameters as templates in the logging message
+ Don't include logger parameters as templates in the logging message
+
+
+
+ Multiple logging methods are using event id {0} in class {1}
+ Multiple logging methods are using event id {0} in class {1}
+
+
+
+ Multiple logging methods cannot use the same event id within a class
+ Multiple logging methods cannot use the same event id within a class
+
+
+
+ Template {0} is not provided as argument to the logging method
+ Template {0} is not provided as argument to the logging method
+
+
+
+ Logging template has no corresponding method argument
+ Logging template has no corresponding method argument
+
+
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.pt-BR.xlf
new file mode 100644
index 00000000000000..1451df01554bbe
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.pt-BR.xlf
@@ -0,0 +1,162 @@
+
+
+
+
+
+ Argument {0} is not referenced from the logging message
+ Argument {0} is not referenced from the logging message
+
+
+
+ Argument is not referenced from the logging message
+ Argument is not referenced from the logging message
+
+
+
+ Generating more than 6 arguments is not supported
+ Generating more than 6 arguments is not supported
+
+
+
+ Can't have the same template with different casing
+ Can't have the same template with different casing
+
+
+
+ Logging method names cannot start with _
+ Logging method names cannot start with _
+
+
+
+ Logging method parameter names cannot start with _
+ Logging method parameter names cannot start with _
+
+
+
+ Logging methods cannot have a body
+ Logging methods cannot have a body
+
+
+
+ Logging class cannot be in nested types
+ Logging class cannot be in nested types
+
+
+
+ Logging methods cannot be generic
+ Logging methods cannot be generic
+
+
+
+ Logging methods must be partial
+ Logging methods must be partial
+
+
+
+ Logging methods must return void
+ Logging methods must return void
+
+
+
+ Logging methods must be static
+ Logging methods must be static
+
+
+
+ Can't have malformed format strings (like dangling {, etc)
+ Can't have malformed format strings (like dangling {, etc)
+
+
+
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+
+
+
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Could not find definition for type {0}
+ Could not find definition for type {0}
+
+
+
+ Could not find a required type definition
+ Could not find a required type definition
+
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+
+
+
+ Redundant qualifier in logging message
+ Redundant qualifier in logging message
+
+
+
+ Don't include exception parameters as templates in the logging message
+ Don't include exception parameters as templates in the logging message
+
+
+
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+
+
+
+ Don't include log level parameters as templates in the logging message
+ Don't include log level parameters as templates in the logging message
+
+
+
+ Don't include logger parameters as templates in the logging message
+ Don't include logger parameters as templates in the logging message
+
+
+
+ Multiple logging methods are using event id {0} in class {1}
+ Multiple logging methods are using event id {0} in class {1}
+
+
+
+ Multiple logging methods cannot use the same event id within a class
+ Multiple logging methods cannot use the same event id within a class
+
+
+
+ Template {0} is not provided as argument to the logging method
+ Template {0} is not provided as argument to the logging method
+
+
+
+ Logging template has no corresponding method argument
+ Logging template has no corresponding method argument
+
+
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.ru.xlf b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.ru.xlf
new file mode 100644
index 00000000000000..66fbc83a6e6a94
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.ru.xlf
@@ -0,0 +1,162 @@
+
+
+
+
+
+ Argument {0} is not referenced from the logging message
+ Argument {0} is not referenced from the logging message
+
+
+
+ Argument is not referenced from the logging message
+ Argument is not referenced from the logging message
+
+
+
+ Generating more than 6 arguments is not supported
+ Generating more than 6 arguments is not supported
+
+
+
+ Can't have the same template with different casing
+ Can't have the same template with different casing
+
+
+
+ Logging method names cannot start with _
+ Logging method names cannot start with _
+
+
+
+ Logging method parameter names cannot start with _
+ Logging method parameter names cannot start with _
+
+
+
+ Logging methods cannot have a body
+ Logging methods cannot have a body
+
+
+
+ Logging class cannot be in nested types
+ Logging class cannot be in nested types
+
+
+
+ Logging methods cannot be generic
+ Logging methods cannot be generic
+
+
+
+ Logging methods must be partial
+ Logging methods must be partial
+
+
+
+ Logging methods must return void
+ Logging methods must return void
+
+
+
+ Logging methods must be static
+ Logging methods must be static
+
+
+
+ Can't have malformed format strings (like dangling {, etc)
+ Can't have malformed format strings (like dangling {, etc)
+
+
+
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+
+
+
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Could not find definition for type {0}
+ Could not find definition for type {0}
+
+
+
+ Could not find a required type definition
+ Could not find a required type definition
+
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+
+
+
+ Redundant qualifier in logging message
+ Redundant qualifier in logging message
+
+
+
+ Don't include exception parameters as templates in the logging message
+ Don't include exception parameters as templates in the logging message
+
+
+
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+
+
+
+ Don't include log level parameters as templates in the logging message
+ Don't include log level parameters as templates in the logging message
+
+
+
+ Don't include logger parameters as templates in the logging message
+ Don't include logger parameters as templates in the logging message
+
+
+
+ Multiple logging methods are using event id {0} in class {1}
+ Multiple logging methods are using event id {0} in class {1}
+
+
+
+ Multiple logging methods cannot use the same event id within a class
+ Multiple logging methods cannot use the same event id within a class
+
+
+
+ Template {0} is not provided as argument to the logging method
+ Template {0} is not provided as argument to the logging method
+
+
+
+ Logging template has no corresponding method argument
+ Logging template has no corresponding method argument
+
+
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.tr.xlf b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.tr.xlf
new file mode 100644
index 00000000000000..fc70ba858ab992
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.tr.xlf
@@ -0,0 +1,162 @@
+
+
+
+
+
+ Argument {0} is not referenced from the logging message
+ Argument {0} is not referenced from the logging message
+
+
+
+ Argument is not referenced from the logging message
+ Argument is not referenced from the logging message
+
+
+
+ Generating more than 6 arguments is not supported
+ Generating more than 6 arguments is not supported
+
+
+
+ Can't have the same template with different casing
+ Can't have the same template with different casing
+
+
+
+ Logging method names cannot start with _
+ Logging method names cannot start with _
+
+
+
+ Logging method parameter names cannot start with _
+ Logging method parameter names cannot start with _
+
+
+
+ Logging methods cannot have a body
+ Logging methods cannot have a body
+
+
+
+ Logging class cannot be in nested types
+ Logging class cannot be in nested types
+
+
+
+ Logging methods cannot be generic
+ Logging methods cannot be generic
+
+
+
+ Logging methods must be partial
+ Logging methods must be partial
+
+
+
+ Logging methods must return void
+ Logging methods must return void
+
+
+
+ Logging methods must be static
+ Logging methods must be static
+
+
+
+ Can't have malformed format strings (like dangling {, etc)
+ Can't have malformed format strings (like dangling {, etc)
+
+
+
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+
+
+
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Could not find definition for type {0}
+ Could not find definition for type {0}
+
+
+
+ Could not find a required type definition
+ Could not find a required type definition
+
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+
+
+
+ Redundant qualifier in logging message
+ Redundant qualifier in logging message
+
+
+
+ Don't include exception parameters as templates in the logging message
+ Don't include exception parameters as templates in the logging message
+
+
+
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+
+
+
+ Don't include log level parameters as templates in the logging message
+ Don't include log level parameters as templates in the logging message
+
+
+
+ Don't include logger parameters as templates in the logging message
+ Don't include logger parameters as templates in the logging message
+
+
+
+ Multiple logging methods are using event id {0} in class {1}
+ Multiple logging methods are using event id {0} in class {1}
+
+
+
+ Multiple logging methods cannot use the same event id within a class
+ Multiple logging methods cannot use the same event id within a class
+
+
+
+ Template {0} is not provided as argument to the logging method
+ Template {0} is not provided as argument to the logging method
+
+
+
+ Logging template has no corresponding method argument
+ Logging template has no corresponding method argument
+
+
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.zh-Hans.xlf
new file mode 100644
index 00000000000000..2cdeaa1133a21c
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.zh-Hans.xlf
@@ -0,0 +1,162 @@
+
+
+
+
+
+ Argument {0} is not referenced from the logging message
+ Argument {0} is not referenced from the logging message
+
+
+
+ Argument is not referenced from the logging message
+ Argument is not referenced from the logging message
+
+
+
+ Generating more than 6 arguments is not supported
+ Generating more than 6 arguments is not supported
+
+
+
+ Can't have the same template with different casing
+ Can't have the same template with different casing
+
+
+
+ Logging method names cannot start with _
+ Logging method names cannot start with _
+
+
+
+ Logging method parameter names cannot start with _
+ Logging method parameter names cannot start with _
+
+
+
+ Logging methods cannot have a body
+ Logging methods cannot have a body
+
+
+
+ Logging class cannot be in nested types
+ Logging class cannot be in nested types
+
+
+
+ Logging methods cannot be generic
+ Logging methods cannot be generic
+
+
+
+ Logging methods must be partial
+ Logging methods must be partial
+
+
+
+ Logging methods must return void
+ Logging methods must return void
+
+
+
+ Logging methods must be static
+ Logging methods must be static
+
+
+
+ Can't have malformed format strings (like dangling {, etc)
+ Can't have malformed format strings (like dangling {, etc)
+
+
+
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+
+
+
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Could not find definition for type {0}
+ Could not find definition for type {0}
+
+
+
+ Could not find a required type definition
+ Could not find a required type definition
+
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+
+
+
+ Redundant qualifier in logging message
+ Redundant qualifier in logging message
+
+
+
+ Don't include exception parameters as templates in the logging message
+ Don't include exception parameters as templates in the logging message
+
+
+
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+
+
+
+ Don't include log level parameters as templates in the logging message
+ Don't include log level parameters as templates in the logging message
+
+
+
+ Don't include logger parameters as templates in the logging message
+ Don't include logger parameters as templates in the logging message
+
+
+
+ Multiple logging methods are using event id {0} in class {1}
+ Multiple logging methods are using event id {0} in class {1}
+
+
+
+ Multiple logging methods cannot use the same event id within a class
+ Multiple logging methods cannot use the same event id within a class
+
+
+
+ Template {0} is not provided as argument to the logging method
+ Template {0} is not provided as argument to the logging method
+
+
+
+ Logging template has no corresponding method argument
+ Logging template has no corresponding method argument
+
+
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.zh-Hant.xlf
new file mode 100644
index 00000000000000..52f65573cda1e2
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/gen/Resources/xlf/Strings.zh-Hant.xlf
@@ -0,0 +1,162 @@
+
+
+
+
+
+ Argument {0} is not referenced from the logging message
+ Argument {0} is not referenced from the logging message
+
+
+
+ Argument is not referenced from the logging message
+ Argument is not referenced from the logging message
+
+
+
+ Generating more than 6 arguments is not supported
+ Generating more than 6 arguments is not supported
+
+
+
+ Can't have the same template with different casing
+ Can't have the same template with different casing
+
+
+
+ Logging method names cannot start with _
+ Logging method names cannot start with _
+
+
+
+ Logging method parameter names cannot start with _
+ Logging method parameter names cannot start with _
+
+
+
+ Logging methods cannot have a body
+ Logging methods cannot have a body
+
+
+
+ Logging class cannot be in nested types
+ Logging class cannot be in nested types
+
+
+
+ Logging methods cannot be generic
+ Logging methods cannot be generic
+
+
+
+ Logging methods must be partial
+ Logging methods must be partial
+
+
+
+ Logging methods must return void
+ Logging methods must return void
+
+
+
+ Logging methods must be static
+ Logging methods must be static
+
+
+
+ Can't have malformed format strings (like dangling {, etc)
+ Can't have malformed format strings (like dangling {, etc)
+
+
+
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+ A LogLevel value must be supplied in the LoggerMessage attribute or as a parameter to the logging method
+
+
+
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ One of the arguments to a logging method must implement the Microsoft.Extensions.Logging.ILogger interface
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ Couldn't find a field of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Could not find definition for type {0}
+ Could not find definition for type {0}
+
+
+
+ Could not find a required type definition
+ Could not find a required type definition
+
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger in class {0}
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ Found multiple fields of type Microsoft.Extensions.Logging.ILogger
+ {Locked="Microsoft.Extensions.Logging.ILogger"}
+
+
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+ Remove redundant qualifier (Info:, Warning:, Error:, etc) from the logging message since it is implicit in the specified log level.
+
+
+
+ Redundant qualifier in logging message
+ Redundant qualifier in logging message
+
+
+
+ Don't include exception parameters as templates in the logging message
+ Don't include exception parameters as templates in the logging message
+
+
+
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+ Don't include a template for {0} in the logging message since it is implicitly taken care of
+
+
+
+ Don't include log level parameters as templates in the logging message
+ Don't include log level parameters as templates in the logging message
+
+
+
+ Don't include logger parameters as templates in the logging message
+ Don't include logger parameters as templates in the logging message
+
+
+
+ Multiple logging methods are using event id {0} in class {1}
+ Multiple logging methods are using event id {0} in class {1}
+
+
+
+ Multiple logging methods cannot use the same event id within a class
+ Multiple logging methods cannot use the same event id within a class
+
+
+
+ Template {0} is not provided as argument to the logging method
+ Template {0} is not provided as argument to the logging method
+
+
+
+ Logging template has no corresponding method argument
+ Logging template has no corresponding method argument
+
+
+
+
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/pkg/Microsoft.Extensions.Logging.pkgproj b/src/libraries/Microsoft.Extensions.Logging/pkg/Microsoft.Extensions.Logging.pkgproj
index b1aeb27796364a..5180cb122482f8 100644
--- a/src/libraries/Microsoft.Extensions.Logging/pkg/Microsoft.Extensions.Logging.pkgproj
+++ b/src/libraries/Microsoft.Extensions.Logging/pkg/Microsoft.Extensions.Logging.pkgproj
@@ -4,6 +4,7 @@
net461;netcoreapp2.0;uap10.0.16299;$(AllXamarinFrameworks)
+
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.cs b/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.cs
index bb2d201cca3207..a81c21d01f68b8 100644
--- a/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.cs
+++ b/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.cs
@@ -24,7 +24,7 @@ public enum ActivityTrackingOptions
TraceState = 8,
TraceFlags = 16,
Tags = 32,
- Baggage = 64
+ Baggage = 64,
}
public static partial class FilterLoggingBuilderExtensions
{
@@ -85,6 +85,15 @@ public LoggerFilterRule(string providerName, string categoryName, Microsoft.Exte
public string ProviderName { get { throw null; } }
public override string ToString() { throw null; }
}
+ [System.AttributeUsageAttribute(System.AttributeTargets.Method)]
+ public sealed partial class LoggerMessageAttribute : System.Attribute
+ {
+ public LoggerMessageAttribute() { }
+ public int EventId { get { throw null; } set { } }
+ public string? EventName { get { throw null; } set { } }
+ public Microsoft.Extensions.Logging.LogLevel Level { get { throw null; } set { } }
+ public string Message { get { throw null; } set { } }
+ }
public static partial class LoggingBuilderExtensions
{
public static Microsoft.Extensions.Logging.ILoggingBuilder AddProvider(this Microsoft.Extensions.Logging.ILoggingBuilder builder, Microsoft.Extensions.Logging.ILoggerProvider provider) { throw null; }
diff --git a/src/libraries/Microsoft.Extensions.Logging/src/LoggerMessageAttribute.cs b/src/libraries/Microsoft.Extensions.Logging/src/LoggerMessageAttribute.cs
new file mode 100644
index 00000000000000..b103ef31a4e162
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/src/LoggerMessageAttribute.cs
@@ -0,0 +1,63 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Extensions.Logging
+{
+ ///
+ /// Provides information to guide the production of a strongly-typed logging method.
+ ///
+ ///
+ /// The method this attribute is applied to:
+ /// - Must be a partial method.
+ /// - Must return void.
+ /// - Must not be generic.
+ /// - Must have an as one of its parameters.
+ /// - Must have a as one of its parameters.
+ /// - None of the parameters can be generic.
+ ///
+ ///
+ ///
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class LoggerMessageAttribute : Attribute
+ {
+ ///
+ /// Initializes a new instance of the class
+ /// which is used to guide the production of a strongly-typed logging method.
+ ///
+ public LoggerMessageAttribute() { }
+
+ ///
+ /// Gets the logging event id for the logging method.
+ ///
+ public int EventId { get; set; } = -1;
+
+ ///
+ /// Gets or sets the logging event name for the logging method.
+ ///
+ ///
+ /// This will equal the method name if not specified.
+ ///
+ public string? EventName { get; set; }
+
+ ///
+ /// Gets the logging level for the logging method.
+ ///
+ public LogLevel Level { get; set; } = LogLevel.None;
+
+ ///
+ /// Gets the message text for the logging method.
+ ///
+ public string Message { get; set; } = "";
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithDynamicLogLevel.generated.txt b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithDynamicLogLevel.generated.txt
new file mode 100644
index 00000000000000..7a2026361b561b
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithDynamicLogLevel.generated.txt
@@ -0,0 +1,57 @@
+//
+#nullable enable
+
+namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
+{
+ partial class TestWithDynamicLogLevel
+ {
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "6.0.0.0")]
+ private readonly struct __M9Struct : global::System.Collections.Generic.IReadOnlyList>
+ {
+
+ public override string ToString()
+ {
+
+ return $"M9";
+ }
+
+ public static string Format(__M9Struct state, global::System.Exception? ex) => state.ToString();
+
+ public int Count => 1;
+
+ public global::System.Collections.Generic.KeyValuePair this[int index]
+ {
+ get => index switch
+ {
+ 0 => new global::System.Collections.Generic.KeyValuePair("{OriginalFormat}", "M9"),
+
+ _ => throw new global::System.IndexOutOfRangeException(nameof(index)), // return the same exception LoggerMessage.Define returns in this case
+ };
+ }
+
+ public global::System.Collections.Generic.IEnumerator> GetEnumerator()
+ {
+ for (int i = 0; i < 1; i++)
+ {
+ yield return this[i];
+ }
+ }
+
+ global::System.Collections.IEnumerator global::System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
+ }
+
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "6.0.0.0")]
+ public static partial void M9(global::Microsoft.Extensions.Logging.LogLevel level, global::Microsoft.Extensions.Logging.ILogger logger)
+ {
+ if (logger.IsEnabled(level))
+ {
+ logger.Log(
+ level,
+ new global::Microsoft.Extensions.Logging.EventId(9, nameof(M9)),
+ new __M9Struct(),
+ null,
+ __M9Struct.Format);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithMoreThan6Params.generated.txt b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithMoreThan6Params.generated.txt
new file mode 100644
index 00000000000000..32b7629942140a
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithMoreThan6Params.generated.txt
@@ -0,0 +1,90 @@
+//
+#nullable enable
+
+namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
+{
+ partial class TestWithMoreThan6Params
+ {
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "6.0.0.0")]
+ private readonly struct __Method9Struct : global::System.Collections.Generic.IReadOnlyList>
+ {
+ private readonly global::System.Int32 _p1;
+ private readonly global::System.Int32 _p2;
+ private readonly global::System.Int32 _p3;
+ private readonly global::System.Int32 _p4;
+ private readonly global::System.Int32 _p5;
+ private readonly global::System.Int32 _p6;
+ private readonly global::System.Int32 _p7;
+
+ public __Method9Struct(global::System.Int32 p1, global::System.Int32 p2, global::System.Int32 p3, global::System.Int32 p4, global::System.Int32 p5, global::System.Int32 p6, global::System.Int32 p7)
+ {
+ this._p1 = p1;
+ this._p2 = p2;
+ this._p3 = p3;
+ this._p4 = p4;
+ this._p5 = p5;
+ this._p6 = p6;
+ this._p7 = p7;
+
+ }
+
+ public override string ToString()
+ {
+ var p1 = this._p1;
+ var p2 = this._p2;
+ var p3 = this._p3;
+ var p4 = this._p4;
+ var p5 = this._p5;
+ var p6 = this._p6;
+ var p7 = this._p7;
+
+ return $"M9 {p1} {p2} {p3} {p4} {p5} {p6} {p7}";
+ }
+
+ public static string Format(__Method9Struct state, global::System.Exception? ex) => state.ToString();
+
+ public int Count => 8;
+
+ public global::System.Collections.Generic.KeyValuePair this[int index]
+ {
+ get => index switch
+ {
+ 0 => new global::System.Collections.Generic.KeyValuePair("p1", this._p1),
+ 1 => new global::System.Collections.Generic.KeyValuePair("p2", this._p2),
+ 2 => new global::System.Collections.Generic.KeyValuePair("p3", this._p3),
+ 3 => new global::System.Collections.Generic.KeyValuePair("p4", this._p4),
+ 4 => new global::System.Collections.Generic.KeyValuePair("p5", this._p5),
+ 5 => new global::System.Collections.Generic.KeyValuePair("p6", this._p6),
+ 6 => new global::System.Collections.Generic.KeyValuePair("p7", this._p7),
+ 7 => new global::System.Collections.Generic.KeyValuePair("{OriginalFormat}", "M9 {p1} {p2} {p3} {p4} {p5} {p6} {p7}"),
+
+ _ => throw new global::System.IndexOutOfRangeException(nameof(index)), // return the same exception LoggerMessage.Define returns in this case
+ };
+ }
+
+ public global::System.Collections.Generic.IEnumerator> GetEnumerator()
+ {
+ for (int i = 0; i < 8; i++)
+ {
+ yield return this[i];
+ }
+ }
+
+ global::System.Collections.IEnumerator global::System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
+ }
+
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "6.0.0.0")]
+ public static partial void Method9(global::Microsoft.Extensions.Logging.ILogger logger, global::System.Int32 p1, global::System.Int32 p2, global::System.Int32 p3, global::System.Int32 p4, global::System.Int32 p5, global::System.Int32 p6, global::System.Int32 p7)
+ {
+ if (logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Error))
+ {
+ logger.Log(
+ global::Microsoft.Extensions.Logging.LogLevel.Error,
+ new global::Microsoft.Extensions.Logging.EventId(8, nameof(Method9)),
+ new __Method9Struct(p1, p2, p3, p4, p5, p6, p7),
+ null,
+ __Method9Struct.Format);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithOneParam.generated.txt b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithOneParam.generated.txt
new file mode 100644
index 00000000000000..866b0469124d8c
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/Baselines/TestWithOneParam.generated.txt
@@ -0,0 +1,21 @@
+//
+#nullable enable
+
+namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
+{
+ partial class TestWithOneParam
+ {
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "6.0.0.0")]
+ private static readonly global::System.Action __M0Callback =
+ global::Microsoft.Extensions.Logging.LoggerMessage.Define(global::Microsoft.Extensions.Logging.LogLevel.Error, new global::Microsoft.Extensions.Logging.EventId(0, nameof(M0)), "M0 {A1}", true);
+
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "6.0.0.0")]
+ public static partial void M0(global::Microsoft.Extensions.Logging.ILogger logger, global::System.Int32 a1)
+ {
+ if (logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Error))
+ {
+ __M0Callback(logger, a1, null);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratedCodeTests.cs b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratedCodeTests.cs
new file mode 100644
index 00000000000000..7e0661d77a6993
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratedCodeTests.cs
@@ -0,0 +1,437 @@
+// 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 Microsoft.Extensions.Logging.Generators.Tests.TestClasses;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.Generators.Tests
+{
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/32743", TestRuntimes.Mono)]
+ public class LoggerMessageGeneratedCodeTests
+ {
+ [Fact]
+ public void BasicTests()
+ {
+ var logger = new MockLogger();
+
+ logger.Reset();
+ NoNamespace.CouldNotOpenSocket(logger, "microsoft.com");
+ Assert.Equal(LogLevel.Critical, logger.LastLogLevel);
+ Assert.Null(logger.LastException);
+ Assert.Equal("Could not open socket to `microsoft.com`", logger.LastFormattedString);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ Level1.OneLevelNamespace.CouldNotOpenSocket(logger, "microsoft.com");
+ Assert.Equal(LogLevel.Critical, logger.LastLogLevel);
+ Assert.Null(logger.LastException);
+ Assert.Equal("Could not open socket to `microsoft.com`", logger.LastFormattedString);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ Level1.Level2.TwoLevelNamespace.CouldNotOpenSocket(logger, "microsoft.com");
+ Assert.Equal(LogLevel.Critical, logger.LastLogLevel);
+ Assert.Null(logger.LastException);
+ Assert.Equal("Could not open socket to `microsoft.com`", logger.LastFormattedString);
+ Assert.Equal(1, logger.CallCount);
+ }
+
+ [Fact]
+ public void EnableTest()
+ {
+ var logger = new MockLogger();
+
+ logger.Reset();
+ logger.Enabled = false;
+ NoNamespace.CouldNotOpenSocket(logger, "microsoft.com");
+ Assert.Equal(0, logger.CallCount); // ensure the logger doesn't get called when it is disabled
+ }
+
+ [Fact]
+ public void ArgTest()
+ {
+ var logger = new MockLogger();
+
+ logger.Reset();
+ ArgTestExtensions.Method1(logger);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M1", logger.LastFormattedString);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ ArgTestExtensions.Method2(logger, "arg1");
+ Assert.Null(logger.LastException);
+ Assert.Equal("M2 arg1", logger.LastFormattedString);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ ArgTestExtensions.Method3(logger, "arg1", 2);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M3 arg1 2", logger.LastFormattedString);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ ArgTestExtensions.Method4(logger, new InvalidOperationException("A"));
+ Assert.Equal("A", logger.LastException!.Message);
+ Assert.Equal("M4", logger.LastFormattedString);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ ArgTestExtensions.Method5(logger, new InvalidOperationException("A"), new InvalidOperationException("B"));
+ Assert.Equal("A", logger.LastException!.Message);
+ Assert.Equal("M5 System.InvalidOperationException: B", logger.LastFormattedString);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ ArgTestExtensions.Method6(logger, new InvalidOperationException("A"), 2);
+ Assert.Equal("A", logger.LastException!.Message);
+ Assert.Equal("M6 2", logger.LastFormattedString);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ ArgTestExtensions.Method7(logger, 1, new InvalidOperationException("B"));
+ Assert.Equal("B", logger.LastException!.Message);
+ Assert.Equal("M7 1", logger.LastFormattedString);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ ArgTestExtensions.Method8(logger, 1, 2, 3, 4, 5, 6, 7);
+ Assert.Equal("M81234567", logger.LastFormattedString);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ ArgTestExtensions.Method9(logger, 1, 2, 3, 4, 5, 6, 7);
+ Assert.Equal("M9 1 2 3 4 5 6 7", logger.LastFormattedString);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ ArgTestExtensions.Method10(logger, 1);
+ Assert.Equal("M101", logger.LastFormattedString);
+ Assert.Equal(1, logger.CallCount);
+ }
+
+ [Fact]
+ public void CollectionTest()
+ {
+ var logger = new MockLogger();
+
+ logger.Reset();
+ CollectionTestExtensions.M0(logger);
+ TestCollection(1, logger);
+
+ logger.Reset();
+ CollectionTestExtensions.M1(logger, 0);
+ TestCollection(2, logger);
+
+ logger.Reset();
+ CollectionTestExtensions.M2(logger, 0, 1);
+ TestCollection(3, logger);
+
+ logger.Reset();
+ CollectionTestExtensions.M3(logger, 0, 1, 2);
+ TestCollection(4, logger);
+
+ logger.Reset();
+ CollectionTestExtensions.M4(logger, 0, 1, 2, 3);
+ TestCollection(5, logger);
+
+ logger.Reset();
+ CollectionTestExtensions.M5(logger, 0, 1, 2, 3, 4);
+ TestCollection(6, logger);
+
+ logger.Reset();
+ CollectionTestExtensions.M6(logger, 0, 1, 2, 3, 4, 5);
+ TestCollection(7, logger);
+
+ logger.Reset();
+ CollectionTestExtensions.M7(logger, 0, 1, 2, 3, 4, 5, 6);
+ TestCollection(8, logger);
+
+ logger.Reset();
+ CollectionTestExtensions.M8(logger, 0, 1, 2, 3, 4, 5, 6, 7);
+ TestCollection(9, logger);
+
+ logger.Reset();
+ CollectionTestExtensions.M9(logger, LogLevel.Critical, 0, new ArgumentException("Foo"), 1);
+ TestCollection(3, logger);
+
+ Assert.True(true);
+ }
+
+ [Fact]
+ public void MessageTests()
+ {
+ var logger = new MockLogger();
+
+ logger.Reset();
+ MessageTestExtensions.M0(logger);
+ Assert.Null(logger.LastException);
+ Assert.Equal(string.Empty, logger.LastFormattedString);
+ Assert.Equal(LogLevel.Trace, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ MessageTestExtensions.M1(logger);
+ Assert.Null(logger.LastException);
+ Assert.Equal(string.Empty, logger.LastFormattedString);
+ Assert.Equal(LogLevel.Debug, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+ }
+
+ [Fact]
+ [ActiveIssue("https://github.com/dotnet/roslyn/issues/52527")]
+ public void MessageTests_SuppressWarning_WarnAsError_NoError()
+ {
+ // Diagnostics produced by source generators do not respect the /warnAsError or /noWarn compiler flags.
+ // These are handled fine by the logger generator and generate warnings. Unfortunately, the warning suppression is
+ // not being observed by the C# compiler at the moment, so having these here causes build warnings.
+#if false
+ var logger = new MockLogger();
+
+ logger.Reset();
+ MessageTestExtensions.M2(logger, "Foo", "Bar");
+ Assert.Null(logger.LastException);
+ Assert.Equal(string.Empty, logger.LastFormattedString);
+ AssertLastState(logger,
+ new KeyValuePair("p1", "Foo"),
+ new KeyValuePair("p2", "Bar"),
+ new KeyValuePair("{OriginalFormat}", string.Empty));
+ Assert.Equal(LogLevel.Trace, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ MessageTestExtensions.M3(logger, "Foo", 42);
+ Assert.Null(logger.LastException);
+ Assert.Equal(string.Empty, logger.LastFormattedString);
+ AssertLastState(logger,
+ new KeyValuePair("p1", "Foo"),
+ new KeyValuePair("p2", 42),
+ new KeyValuePair("{OriginalFormat}", string.Empty));
+ Assert.Equal(LogLevel.Debug, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+#endif
+ }
+
+ [Fact]
+ public void InstanceTests()
+ {
+ var logger = new MockLogger();
+ var o = new TestInstances(logger);
+
+ logger.Reset();
+ o.M0();
+ Assert.Null(logger.LastException);
+ Assert.Equal("M0", logger.LastFormattedString);
+ Assert.Equal(LogLevel.Error, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ o.M1("Foo");
+ Assert.Null(logger.LastException);
+ Assert.Equal("M1 Foo", logger.LastFormattedString);
+ Assert.Equal(LogLevel.Trace, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+ }
+
+ [Fact]
+ public void LevelTests()
+ {
+ var logger = new MockLogger();
+
+ logger.Reset();
+ LevelTestExtensions.M0(logger);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M0", logger.LastFormattedString);
+ Assert.Equal(LogLevel.Trace, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ LevelTestExtensions.M1(logger);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M1", logger.LastFormattedString);
+ Assert.Equal(LogLevel.Debug, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ LevelTestExtensions.M2(logger);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M2", logger.LastFormattedString);
+ Assert.Equal(LogLevel.Information, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ LevelTestExtensions.M3(logger);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M3", logger.LastFormattedString);
+ Assert.Equal(LogLevel.Warning, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ LevelTestExtensions.M4(logger);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M4", logger.LastFormattedString);
+ Assert.Equal(LogLevel.Error, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ LevelTestExtensions.M5(logger);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M5", logger.LastFormattedString);
+ Assert.Equal(LogLevel.Critical, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ LevelTestExtensions.M6(logger);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M6", logger.LastFormattedString);
+ Assert.Equal(LogLevel.None, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ LevelTestExtensions.M7(logger);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M7", logger.LastFormattedString);
+ Assert.Equal((LogLevel)42, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ LevelTestExtensions.M8(logger, LogLevel.Critical);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M8", logger.LastFormattedString);
+ Assert.Equal(LogLevel.Critical, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ LevelTestExtensions.M9(LogLevel.Trace, logger);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M9", logger.LastFormattedString);
+ Assert.Equal(LogLevel.Trace, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+ }
+
+ [Fact]
+ public void ExceptionTests()
+ {
+ var logger = new MockLogger();
+
+ logger.Reset();
+ ExceptionTestExtensions.M0(logger, new ArgumentException("Foo"), new ArgumentException("Bar"));
+ Assert.Equal("Foo", logger.LastException!.Message);
+ Assert.Equal("M0 System.ArgumentException: Bar", logger.LastFormattedString);
+ Assert.Equal(LogLevel.Trace, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+
+ logger.Reset();
+ ExceptionTestExtensions.M1(new ArgumentException("Foo"), logger, new ArgumentException("Bar"));
+ Assert.Equal("Foo", logger.LastException!.Message);
+ Assert.Equal("M1 System.ArgumentException: Bar", logger.LastFormattedString);
+ Assert.Equal(LogLevel.Debug, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+ }
+
+ [Fact]
+ public void EventNameTests()
+ {
+ var logger = new MockLogger();
+
+ logger.Reset();
+ EventNameTestExtensions.M0(logger);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M0", logger.LastFormattedString);
+ Assert.Equal(LogLevel.Trace, logger.LastLogLevel);
+ Assert.Equal(1, logger.CallCount);
+ Assert.Equal("CustomEventName", logger.LastEventId.Name);
+ }
+
+ [Fact]
+ public void TemplateTests()
+ {
+ var logger = new MockLogger();
+
+ logger.Reset();
+ TemplateTestExtensions.M0(logger, 0);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M0 0", logger.LastFormattedString);
+ AssertLastState(logger,
+ new KeyValuePair("A1", 0),
+ new KeyValuePair("{OriginalFormat}", "M0 {A1}"));
+
+ logger.Reset();
+ TemplateTestExtensions.M1(logger, 42);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M1 42 42", logger.LastFormattedString);
+ AssertLastState(logger,
+ new KeyValuePair("A1", 42),
+ new KeyValuePair("{OriginalFormat}", "M1 {A1} {A1}"));
+
+ logger.Reset();
+ TemplateTestExtensions.M2(logger, 42, 43, 44, 45, 46, 47, 48);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M2 42 43 44 45 46 47 48", logger.LastFormattedString);
+ AssertLastState(logger,
+ new KeyValuePair("A1", 42),
+ new KeyValuePair("a2", 43),
+ new KeyValuePair("A3", 44),
+ new KeyValuePair("a4", 45),
+ new KeyValuePair("A5", 46),
+ new KeyValuePair("a6", 47),
+ new KeyValuePair("A7", 48),
+ new KeyValuePair("{OriginalFormat}", "M2 {A1} {a2} {A3} {a4} {A5} {a6} {A7}"));
+
+ logger.Reset();
+ TemplateTestExtensions.M3(logger, 42, 43);
+ Assert.Null(logger.LastException);
+ Assert.Equal("M3 43 42", logger.LastFormattedString);
+ AssertLastState(logger,
+ new KeyValuePair("A1", 42),
+ new KeyValuePair("a2", 43),
+ new KeyValuePair("{OriginalFormat}", "M3 {a2} {A1}"));
+
+ }
+
+ private static void AssertLastState(MockLogger logger, params KeyValuePair[] expected)
+ {
+ var rol = (IReadOnlyList>)logger.LastState!;
+ int count = 0;
+ foreach (var kvp in expected)
+ {
+ Assert.Equal(kvp.Key, rol[count].Key);
+ Assert.Equal(kvp.Value, rol[count].Value);
+ count++;
+ }
+ }
+
+ private static void TestCollection(int expected, MockLogger logger)
+ {
+ var rol = (logger.LastState as IReadOnlyList>)!;
+ Assert.NotNull(rol);
+
+ Assert.Equal(expected, rol.Count);
+ for (int i = 0; i < expected; i++)
+ {
+ if (i != expected - 1)
+ {
+ var kvp = new KeyValuePair($"p{i}", i);
+ Assert.Equal(kvp, rol[i]);
+ }
+ }
+
+ int count = 0;
+ foreach (var actual in rol)
+ {
+ if (count != expected - 1)
+ {
+ var kvp = new KeyValuePair($"p{count}", count);
+ Assert.Equal(kvp, actual);
+ }
+
+ count++;
+ }
+
+ Assert.Equal(expected, count);
+ Assert.Throws(() => rol[expected]);
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorEmitterTests.cs b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorEmitterTests.cs
new file mode 100644
index 00000000000000..c66a267f7c32aa
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorEmitterTests.cs
@@ -0,0 +1,123 @@
+// 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.IO;
+using System.Reflection;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+using SourceGenerators.Tests;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.Generators.Tests
+{
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/32743", TestRuntimes.Mono)]
+ public class LoggerMessageGeneratorEmitterTests
+ {
+ [Fact]
+ public async Task TestEmitter()
+ {
+ // The functionality of the resulting code is tested via LoggerMessageGeneratedCodeTests.cs
+ string[] sources = Directory.GetFiles("TestClasses");
+ foreach (var src in sources)
+ {
+ var testSourceCode = await File.ReadAllTextAsync(src).ConfigureAwait(false);
+
+ var (d, r) = await RoslynTestUtils.RunGenerator(
+ new LoggerMessageGenerator(),
+ new[] { typeof(ILogger).Assembly, typeof(LoggerMessageAttribute).Assembly },
+ new[] { testSourceCode }).ConfigureAwait(false);
+
+ Assert.Empty(d);
+ Assert.Single(r);
+ }
+ }
+
+ [Fact]
+ public async Task TestBaseline_TestWithOneParam_Success()
+ {
+ string testSourceCode = @"
+namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
+{
+ internal static partial class TestWithOneParam
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Error, Message = ""M0 {A1}"")]
+ public static partial void M0(ILogger logger, int a1);
+ }
+}";
+ await VerifyAgainstBaselineUsingFile("TestWithOneParam.generated.txt", testSourceCode);
+ }
+
+ [Fact]
+ public async Task TestBaseline_TestWithMoreThan6Params_Success()
+ {
+ // TODO: Remove support for more than 6 arguments
+ string testSourceCode = @"
+namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
+{
+ internal static partial class TestWithMoreThan6Params
+ {
+ [LoggerMessage(EventId = 8, Level = LogLevel.Error, Message = ""M9 {p1} {p2} {p3} {p4} {p5} {p6} {p7}"")]
+ public static partial void Method9(ILogger logger, int p1, int p2, int p3, int p4, int p5, int p6, int p7);
+ }
+}";
+ await VerifyAgainstBaselineUsingFile("TestWithMoreThan6Params.generated.txt", testSourceCode);
+ }
+
+ [Fact]
+ public async Task TestBaseline_TestWithDynamicLogLevel_Success()
+ {
+ string testSourceCode = @"
+namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
+{
+ internal static partial class TestWithDynamicLogLevel
+ {
+ [LoggerMessage(EventId = 9, Message = ""M9"")]
+ public static partial void M9(LogLevel level, ILogger logger);
+ }
+}";
+ await VerifyAgainstBaselineUsingFile("TestWithDynamicLogLevel.generated.txt", testSourceCode);
+ }
+
+ private async Task VerifyAgainstBaselineUsingFile(string filename, string testSourceCode)
+ {
+ string[] expectedLines = await File.ReadAllLinesAsync(Path.Combine("Baselines", filename)).ConfigureAwait(false);
+
+ var (d, r) = await RoslynTestUtils.RunGenerator(
+ new LoggerMessageGenerator(),
+ new[] { typeof(ILogger).Assembly, typeof(LoggerMessageAttribute).Assembly },
+ new[] { testSourceCode }).ConfigureAwait(false);
+
+ Assert.Empty(d);
+ Assert.Single(r);
+
+ Assert.True(CompareLines(expectedLines, r[0].SourceText,
+ out string errorMessage), errorMessage);
+ }
+
+ private bool CompareLines(string[] expectedLines, SourceText sourceText, out string message)
+ {
+ if (expectedLines.Length != sourceText.Lines.Count)
+ {
+ message = string.Format("Line numbers do not match. Expected: {0} lines, but generated {1}",
+ expectedLines.Length, sourceText.Lines.Count);
+ return false;
+ }
+ int index = 0;
+ foreach (TextLine textLine in sourceText.Lines)
+ {
+ string expectedLine = expectedLines[index];
+ if (!expectedLine.Equals(textLine.ToString(), StringComparison.Ordinal))
+ {
+ message = string.Format("Line {0} does not match.{1}Expected Line:{1}{2}{1}Actual Line:{1}{3}",
+ textLine.LineNumber, Environment.NewLine, expectedLine, textLine);
+ return false;
+ }
+ index++;
+ }
+ message = string.Empty;
+ return true;
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs
new file mode 100644
index 00000000000000..1aabf53997b2aa
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs
@@ -0,0 +1,657 @@
+// 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 System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using SourceGenerators.Tests;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.Generators.Tests
+{
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/32743", TestRuntimes.Mono)]
+ public class LoggerMessageGeneratorParserTests
+ {
+ [Fact]
+ public async Task InvalidMethodName()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1"")]
+ static partial void __M1(ILogger logger);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.InvalidLoggingMethodName.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task MissingLogLevel()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Message = ""M1"")]
+ static partial void M1(ILogger logger);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.MissingLogLevel.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task InvalidMethodBody()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ static partial void M1(ILogger logger);
+
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1"")]
+ static partial void M1(ILogger logger)
+ {
+ }
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.LoggingMethodHasBody.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task MissingTemplate()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""This is a message without foo"")]
+ static partial void M1(ILogger logger, string foo);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.ArgumentHasNoCorrespondingTemplate.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task MissingArgument()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""{foo}"")]
+ static partial void M1(ILogger logger);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.TemplateHasNoCorrespondingArgument.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task NeedlessQualifierInMessage()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = ""INFO: this is an informative message"")]
+ static partial void M1(ILogger logger);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.RedundantQualifierInMessage.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task NeedlessExceptionInMessage()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1 {ex} {ex2}"")]
+ static partial void M1(ILogger logger, System.Exception ex, System.Exception ex2);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.ShouldntMentionExceptionInMessage.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task NeedlessLogLevelInMessage()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Message = ""M1 {l1} {l2}"")]
+ static partial void M1(ILogger logger, LogLevel l1, LogLevel l2);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.ShouldntMentionLogLevelInMessage.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task NeedlessLoggerInMessage()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1 {logger}"")]
+ static partial void M1(ILogger logger);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.ShouldntMentionLoggerInMessage.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task DoubleLogLevel_InAttributeAndAsParameterButMissingInTemplate_ProducesDiagnostic()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1"")]
+ static partial void M1(ILogger logger, LogLevel levelParam);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.ArgumentHasNoCorrespondingTemplate.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task LogLevelDoublySet_AndInMessageTemplate_ProducesDiagnostic()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1 {level2}"")]
+ static partial void M1(ILogger logger, LogLevel level1, LogLevel level2);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.ArgumentHasNoCorrespondingTemplate.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task DoubleLogLevel_FirstOneSetAsMethodParameter_SecondOneInMessageTemplate_Supported()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Message = ""M1 {level2}"")]
+ static partial void M1(ILogger logger, LogLevel level1, LogLevel level2);
+ }
+ ");
+
+ Assert.Empty(diagnostics);
+ }
+
+#if false
+ // TODO: can't have the same template with different casing
+ [Fact]
+ public async Task InconsistentTemplateCasing()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1 {p1} {P1}"")]
+ static partial void M1(ILogger logger, int p1, int P1);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.InconsistentTemplateCasing.Id, diagnostics[0].Id);
+ }
+
+ // TODO: can't have malformed format strings (like dangling {, etc)
+ [Fact]
+ public async Task MalformedFormatString()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1 {p1} {P1}"")]
+ static partial void M1(ILogger logger, int p1, int P1);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.MalformedFormatStrings.Id, diagnostics[0].Id);
+ }
+#endif
+
+ [Fact]
+ public async Task InvalidParameterName()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1 {__foo}"")]
+ static partial void M1(ILogger logger, string __foo);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.InvalidLoggingMethodParameterName.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task NestedType()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ public partial class Nested
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1"")]
+ static partial void M1(ILogger logger);
+ }
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.LoggingMethodInNestedType.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task MissingExceptionType()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ namespace System
+ {
+ public class Object {}
+ public class Void {}
+ public class String {}
+ public struct DateTime {}
+ }
+ namespace System.Collections
+ {
+ public interface IEnumerable {}
+ }
+ namespace Microsoft.Extensions.Logging
+ {
+ public enum LogLevel {}
+ public interface ILogger {}
+ }
+ namespace Microsoft.Extensions.Logging
+ {
+ public class LoggerMessageAttribute {}
+ }
+ partial class C
+ {
+ }
+ ", false, includeBaseReferences: false, includeLoggingReferences: false);
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.MissingRequiredType.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task MissingStringType()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ namespace System
+ {
+ public class Object {}
+ public class Void {}
+ public class Exception {}
+ public struct DateTime {}
+ }
+ namespace System.Collections
+ {
+ public interface IEnumerable {}
+ }
+ namespace Microsoft.Extensions.Logging
+ {
+ public enum LogLevel {}
+ public interface ILogger {}
+ }
+ namespace Microsoft.Extensions.Logging
+ {
+ public class LoggerMessageAttribute {}
+ }
+ partial class C
+ {
+ }
+ ", false, includeBaseReferences: false, includeLoggingReferences: false);
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.MissingRequiredType.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task MissingEnumerableType()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ namespace System
+ {
+ public class Object {}
+ public class Void {}
+ public class Exception {}
+ public struct DateTime {}
+ public class String {}
+ }
+ namespace Microsoft.Extensions.Logging
+ {
+ public enum LogLevel {}
+ public interface ILogger {}
+ }
+ namespace Microsoft.Extensions.Logging
+ {
+ public class LoggerMessageAttribute {}
+ }
+ partial class C
+ {
+ }
+ ", false, includeBaseReferences: false, includeLoggingReferences: false);
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.MissingRequiredType.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task MissingLoggerMessageAttributeType()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ }
+ ", false, includeLoggingReferences: false);
+
+ Assert.Empty(diagnostics);
+ }
+
+ [Fact]
+ public async Task MissingILoggerType()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ namespace Microsoft.Extensions.Logging
+ {
+ public sealed class LoggerMessageAttribute : System.Attribute {}
+ }
+ partial class C
+ {
+ }
+ ", false, includeLoggingReferences: false);
+
+ Assert.Empty(diagnostics);
+ }
+
+ [Fact]
+ public async Task MissingLogLevelType()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ namespace Microsoft.Extensions.Logging
+ {
+ public sealed class LoggerMessageAttribute : System.Attribute {}
+ }
+ namespace Microsoft.Extensions.Logging
+ {
+ public interface ILogger {}
+ }
+ partial class C
+ {
+ }
+ ", false, includeLoggingReferences: false);
+
+ Assert.Empty(diagnostics);
+ }
+
+ [Fact]
+ public async Task EventIdReuse()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class MyClass
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1"")]
+ static partial void M1(ILogger logger);
+
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1"")]
+ static partial void M2(ILogger logger);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.ShouldntReuseEventIds.Id, diagnostics[0].Id);
+ Assert.Contains("in class MyClass", diagnostics[0].GetMessage(), StringComparison.InvariantCulture);
+ }
+
+ [Fact]
+ public async Task MethodReturnType()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1"")]
+ public static partial int M1(ILogger logger);
+
+ public static partial int M1(ILogger logger) { return 0; }
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.LoggingMethodMustReturnVoid.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task MissingILogger()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1 {p1}"")]
+ static partial void M1(int p1);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.MissingLoggerArgument.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task NotStatic()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1"")]
+ partial void M1(ILogger logger);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.LoggingMethodShouldBeStatic.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task NoILoggerField()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1"")]
+ public partial void M1();
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.MissingLoggerField.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task MultipleILoggerFields()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ public ILogger _logger1;
+ public ILogger _logger2;
+
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1"")]
+ public partial void M1();
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.MultipleLoggerFields.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task NotPartial()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1"")]
+ static void M1(ILogger logger) {}
+ }
+ ");
+
+ Assert.Equal(2, diagnostics.Count);
+ Assert.Equal(DiagnosticDescriptors.LoggingMethodMustBePartial.Id, diagnostics[0].Id);
+ Assert.Equal(DiagnosticDescriptors.LoggingMethodHasBody.Id, diagnostics[1].Id);
+ }
+
+ [Fact]
+ public async Task MethodGeneric()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1"")]
+ static partial void M1(ILogger logger);
+ }
+ ");
+
+ Assert.Single(diagnostics);
+ Assert.Equal(DiagnosticDescriptors.LoggingMethodIsGeneric.Id, diagnostics[0].Id);
+ }
+
+ [Fact]
+ public async Task Templates()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = ""M1"")]
+ static partial void M1(ILogger logger);
+
+ [LoggerMessage(EventId = 2, Level = LogLevel.Debug, Message = ""M2 {arg1} {arg2}"")]
+ static partial void M2(ILogger logger, string arg1, string arg2);
+
+ [LoggerMessage(EventId = 3, Level = LogLevel.Debug, Message = ""M3 {arg1"")]
+ static partial void M3(ILogger logger);
+
+ [LoggerMessage(EventId = 4, Level = LogLevel.Debug, Message = ""M4 arg1}"")]
+ static partial void M4(ILogger logger);
+
+ [LoggerMessage(EventId = 5, Level = LogLevel.Debug, Message = ""M5 {"")]
+ static partial void M5(ILogger logger);
+
+ [LoggerMessage(EventId = 6, Level = LogLevel.Debug, Message = ""}M6 "")]
+ static partial void M6(ILogger logger);
+
+ [LoggerMessage(EventId = 7, Level = LogLevel.Debug, Message = ""M7 {{arg1}}"")]
+ static partial void M7(ILogger logger);
+ }
+ ");
+
+ Assert.Empty(diagnostics);
+ }
+
+ [Fact]
+ public async Task Cancellation()
+ {
+ await Assert.ThrowsAsync(async () =>
+ await RunGenerator(@"
+ partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Debug, Message = ""M1"")]
+ static partial void M1(ILogger logger);
+ }
+ ", cancellationToken: new CancellationToken(true)));
+ }
+
+ [Fact]
+ public async Task SourceErrors()
+ {
+ IReadOnlyList diagnostics = await RunGenerator(@"
+ static partial class C
+ {
+ // bogus argument type
+ [LoggerMessage(EventId = 0, Level = "", Message = ""Hello"")]
+ static partial void M1(ILogger logger);
+
+ // missing parameter name
+ [LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = ""Hello"")]
+ static partial void M2(ILogger);
+
+ // bogus parameter type
+ [LoggerMessage(EventId = 2, Level = LogLevel.Debug, Message = ""Hello"")]
+ static partial void M3(XILogger logger);
+
+ // attribute applied to something other than a method
+ [LoggerMessage(EventId = 4, Message = ""Hello"")]
+ int M5;
+ }
+ ");
+
+ Assert.Empty(diagnostics); // should fail quietly on broken code
+ }
+
+ private static async Task> RunGenerator(
+ string code,
+ bool wrap = true,
+ bool inNamespace = true,
+ bool includeBaseReferences = true,
+ bool includeLoggingReferences = true,
+ CancellationToken cancellationToken = default)
+ {
+ var text = code;
+ if (wrap)
+ {
+ var nspaceStart = "namespace Test {";
+ var nspaceEnd = "}";
+ if (!inNamespace)
+ {
+ nspaceStart = "";
+ nspaceEnd = "";
+ }
+
+ text = $@"
+ {nspaceStart}
+ using Microsoft.Extensions.Logging;
+ {code}
+ {nspaceEnd}
+ ";
+ }
+
+ Assembly[]? refs = null;
+ if (includeLoggingReferences)
+ {
+ refs = new[] { typeof(ILogger).Assembly, typeof(LoggerMessageAttribute).Assembly };
+ }
+
+ var (d, r) = await RoslynTestUtils.RunGenerator(
+ new LoggerMessageGenerator(),
+ refs,
+ new[] { text },
+ includeBaseReferences: includeBaseReferences,
+ cancellationToken: cancellationToken).ConfigureAwait(false);
+
+ return d;
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/Microsoft.Extensions.Logging.Generators.Tests.csproj b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/Microsoft.Extensions.Logging.Generators.Tests.csproj
new file mode 100644
index 00000000000000..329b67c2a16a4c
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/Microsoft.Extensions.Logging.Generators.Tests.csproj
@@ -0,0 +1,31 @@
+
+
+
+ $(NetCoreAppCurrent)
+ true
+ true
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/MockLogger.cs b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/MockLogger.cs
new file mode 100644
index 00000000000000..3fa88999b3ab55
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/MockLogger.cs
@@ -0,0 +1,69 @@
+// 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.Logging;
+
+namespace Microsoft.Extensions.Logging.Generators.Tests
+{
+ ///
+ /// A logger which captures the last log state logged to it.
+ ///
+ internal class MockLogger : ILogger
+ {
+ public LogLevel LastLogLevel { get; private set; }
+ public EventId LastEventId { get; private set; }
+ public object? LastState { get; private set; }
+ public Exception? LastException { get; private set; }
+ public string? LastFormattedString { get; private set; }
+ public bool Enabled { get; set; }
+ public int CallCount { get; private set; }
+
+ ///
+ /// Dummy disposable type, for use with BeginScope.
+ ///
+ private class Disposable : IDisposable
+ {
+ public void Dispose()
+ {
+ // nothing
+ }
+ }
+
+ public MockLogger()
+ {
+ Reset();
+ }
+
+ public IDisposable BeginScope(TState state)
+ {
+ return new Disposable();
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ return Enabled;
+ }
+
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
+ {
+ LastLogLevel = logLevel;
+ LastEventId = eventId;
+ LastState = state;
+ LastException = exception;
+ LastFormattedString = formatter(state, exception);
+ CallCount++;
+ }
+
+ public void Reset()
+ {
+ LastLogLevel = (LogLevel)(-1);
+ LastEventId = default;
+ LastState = null;
+ LastException = null;
+ LastFormattedString = null;
+ CallCount = 0;
+ Enabled = true;
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/ArgTestExtensions.cs b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/ArgTestExtensions.cs
new file mode 100644
index 00000000000000..b0f1cb39eaef20
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/ArgTestExtensions.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
+{
+ internal static partial class ArgTestExtensions
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Error, Message = "M1")]
+ public static partial void Method1(ILogger logger);
+
+ [LoggerMessage(EventId = 1, Level = LogLevel.Error, Message = "M2 {p1}")]
+ public static partial void Method2(ILogger logger, string p1);
+
+ [LoggerMessage(EventId = 2, Level = LogLevel.Error, Message = "M3 {p1} {p2}")]
+ public static partial void Method3(ILogger logger, string p1, int p2);
+
+ [LoggerMessage(EventId = 3, Level = LogLevel.Error, Message = "M4")]
+ public static partial void Method4(ILogger logger, InvalidOperationException p1);
+
+ [LoggerMessage(EventId = 4, Level = LogLevel.Error, Message = "M5 {p2}")]
+ public static partial void Method5(ILogger logger, System.InvalidOperationException p1, System.InvalidOperationException p2);
+
+ [LoggerMessage(EventId = 5, Level = LogLevel.Error, Message = "M6 {p2}")]
+ public static partial void Method6(ILogger logger, System.InvalidOperationException p1, int p2);
+
+ [LoggerMessage(EventId = 6, Level = LogLevel.Error, Message = "M7 {p1}")]
+ public static partial void Method7(ILogger logger, int p1, System.InvalidOperationException p2);
+
+ [LoggerMessage(EventId = 7, Level = LogLevel.Error, Message = "M8{p1}{p2}{p3}{p4}{p5}{p6}{p7}")]
+ public static partial void Method8(ILogger logger, int p1, int p2, int p3, int p4, int p5, int p6, int p7);
+
+ [LoggerMessage(EventId = 8, Level = LogLevel.Error, Message = "M9 {p1} {p2} {p3} {p4} {p5} {p6} {p7}")]
+ public static partial void Method9(ILogger logger, int p1, int p2, int p3, int p4, int p5, int p6, int p7);
+
+ [LoggerMessage(EventId = 9, Level = LogLevel.Error, Message = "M10{p1}")]
+ public static partial void Method10(ILogger logger, int p1);
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/CollectionTestExtensions.cs b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/CollectionTestExtensions.cs
new file mode 100644
index 00000000000000..74d4b996ee7098
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/CollectionTestExtensions.cs
@@ -0,0 +1,38 @@
+// 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.Generators.Tests.TestClasses
+{
+ internal static partial class CollectionTestExtensions
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Error, Message = "M0")]
+ public static partial void M0(ILogger logger);
+
+ [LoggerMessage(EventId = 1, Level = LogLevel.Error, Message = "M1{p0}")]
+ public static partial void M1(ILogger logger, int p0);
+
+ [LoggerMessage(EventId = 2, Level = LogLevel.Error, Message = "M2{p0}{p1}")]
+ public static partial void M2(ILogger logger, int p0, int p1);
+
+ [LoggerMessage(EventId = 3, Level = LogLevel.Error, Message = "M3{p0}{p1}{p2}")]
+ public static partial void M3(ILogger logger, int p0, int p1, int p2);
+
+ [LoggerMessage(EventId = 4, Level = LogLevel.Error, Message = "M4{p0}{p1}{p2}{p3}")]
+ public static partial void M4(ILogger logger, int p0, int p1, int p2, int p3);
+
+ [LoggerMessage(EventId = 5, Level = LogLevel.Error, Message = "M5{p0}{p1}{p2}{p3}{p4}")]
+ public static partial void M5(ILogger logger, int p0, int p1, int p2, int p3, int p4);
+
+ [LoggerMessage(EventId = 6, Level = LogLevel.Error, Message = "M6{p0}{p1}{p2}{p3}{p4}{p5}")]
+ public static partial void M6(ILogger logger, int p0, int p1, int p2, int p3, int p4, int p5);
+
+ [LoggerMessage(EventId = 7, Level = LogLevel.Error, Message = "M7{p0}{p1}{p2}{p3}{p4}{p5}{p6}")]
+ public static partial void M7(ILogger logger, int p0, int p1, int p2, int p3, int p4, int p5, int p6);
+
+ [LoggerMessage(EventId = 8, Level = LogLevel.Error, Message = "M8{p0}{p1}{p2}{p3}{p4}{p5}{p6}{p7}")]
+ public static partial void M8(ILogger logger, int p0, int p1, int p2, int p3, int p4, int p5, int p6, int p7);
+
+ [LoggerMessage(EventId = 9, Message = "M8{p0}{p1}")]
+ public static partial void M9(ILogger logger, LogLevel level, int p0, System.Exception ex, int p1);
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/EnumerableTestExtensions.cs b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/EnumerableTestExtensions.cs
new file mode 100644
index 00000000000000..65cfd41a1347c5
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/EnumerableTestExtensions.cs
@@ -0,0 +1,41 @@
+// 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;
+using System.Collections.Generic;
+
+namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
+{
+ internal static partial class EnumerableTestExtensions
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Error, Message = "M0")]
+ public static partial void M0(ILogger logger);
+
+ [LoggerMessage(EventId = 1, Level = LogLevel.Error, Message = "M1{p0}")]
+ public static partial void M1(ILogger logger, IEnumerable p0);
+
+ [LoggerMessage(EventId = 2, Level = LogLevel.Error, Message = "M2{p0}{p1}")]
+ public static partial void M2(ILogger logger, int p0, IEnumerable p1);
+
+ [LoggerMessage(EventId = 3, Level = LogLevel.Error, Message = "M3{p0}{p1}{p2}")]
+ public static partial void M3(ILogger logger, int p0, IEnumerable p1, int p2);
+
+ [LoggerMessage(EventId = 4, Level = LogLevel.Error, Message = "M4{p0}{p1}{p2}{p3}")]
+ public static partial void M4(ILogger logger, int p0, IEnumerable p1, int p2, int p3);
+
+ [LoggerMessage(EventId = 5, Level = LogLevel.Error, Message = "M5{p0}{p1}{p2}{p3}{p4}")]
+ public static partial void M5(ILogger logger, int p0, IEnumerable p1, int p2, int p3, int p4);
+
+ [LoggerMessage(EventId = 6, Level = LogLevel.Error, Message = "M6{p0}{p1}{p2}{p3}{p4}{p5}")]
+ public static partial void M6(ILogger logger, int p0, IEnumerable p1, int p2, int p3, int p4, int p5);
+
+ [LoggerMessage(EventId = 7, Level = LogLevel.Error, Message = "M7{p0}{p1}{p2}{p3}{p4}{p5}{p6}")]
+ public static partial void M7(ILogger logger, int p0, IEnumerable p1, int p2, int p3, int p4, int p5, int p6);
+
+ [LoggerMessage(EventId = 8, Level = LogLevel.Error, Message = "M8{p0}{p1}{p2}{p3}{p4}{p5}{p6}{p7}")]
+ public static partial void M8(ILogger logger, int p0, IEnumerable p1, int p2, int p3, int p4, int p5, int p6, int p7);
+
+ [LoggerMessage(EventId = 9, Level = LogLevel.Error, Message = "M9{p0}{p1}{p2}{p3}{p4}{p5}{p6}{p7}{p8}")]
+ public static partial void M9(ILogger logger, int p0, IEnumerable p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8);
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/EventNameTestExtensions.cs b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/EventNameTestExtensions.cs
new file mode 100644
index 00000000000000..4c0ddf320aabf4
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/EventNameTestExtensions.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.
+
+namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
+{
+ internal static partial class EventNameTestExtensions
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Trace, Message = "M0", EventName = "CustomEventName")]
+ public static partial void M0(ILogger logger);
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/ExceptionTestExtensions.cs b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/ExceptionTestExtensions.cs
new file mode 100644
index 00000000000000..45b01e56b2978f
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/ExceptionTestExtensions.cs
@@ -0,0 +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;
+
+namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
+{
+ internal static partial class ExceptionTestExtensions
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Trace, Message = "M0 {ex2}")]
+ public static partial void M0(ILogger logger, Exception ex1, Exception ex2);
+
+ [LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = "M1 {ex2}")]
+ public static partial void M1(Exception ex1, ILogger logger, Exception ex2);
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/LevelTestExtensions.cs b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/LevelTestExtensions.cs
new file mode 100644
index 00000000000000..5726aa02a4c3fe
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/LevelTestExtensions.cs
@@ -0,0 +1,38 @@
+// 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.Generators.Tests.TestClasses
+{
+ internal static partial class LevelTestExtensions
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Trace, Message = "M0")]
+ public static partial void M0(ILogger logger);
+
+ [LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = "M1")]
+ public static partial void M1(ILogger logger);
+
+ [LoggerMessage(EventId = 2, Level = LogLevel.Information, Message = "M2")]
+ public static partial void M2(ILogger logger);
+
+ [LoggerMessage(EventId = 3, Level = LogLevel.Warning, Message = "M3")]
+ public static partial void M3(ILogger logger);
+
+ [LoggerMessage(EventId = 4, Level = LogLevel.Error, Message = "M4")]
+ public static partial void M4(ILogger logger);
+
+ [LoggerMessage(EventId = 5, Level = LogLevel.Critical, Message = "M5")]
+ public static partial void M5(ILogger logger);
+
+ [LoggerMessage(EventId = 6, Level = LogLevel.None, Message = "M6")]
+ public static partial void M6(ILogger logger);
+
+ [LoggerMessage(EventId = 7, Level = (LogLevel)42, Message = "M7")]
+ public static partial void M7(ILogger logger);
+
+ [LoggerMessage(EventId = 8, Message = "M8")]
+ public static partial void M8(ILogger logger, LogLevel level);
+
+ [LoggerMessage(EventId = 9, Message = "M9")]
+ public static partial void M9(LogLevel level, ILogger logger);
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/MessageTestExtensions.cs b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/MessageTestExtensions.cs
new file mode 100644
index 00000000000000..a30849288b53b4
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/MessageTestExtensions.cs
@@ -0,0 +1,33 @@
+// 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 SYSLIB0027
+
+namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
+{
+ internal static partial class MessageTestExtensions
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Trace)]
+ public static partial void M0(ILogger logger);
+
+ [LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = "")]
+ public static partial void M1(ILogger logger);
+
+#if false
+ // Diagnostics produced by source generators do not respect the /warnAsError or /noWarn compiler flags.
+ // Disabled due to https://github.com/dotnet/roslyn/issues/52527
+ //
+ // These are handled fine by the logger generator and generate warnings. Unfortunately, the above warning suppression is
+ // not being observed by the C# compiler at the moment, so having these here causes build warnings.
+
+ [LoggerMessage(EventId = 2, Level = LogLevel.Trace)]
+ public static partial void M2(ILogger logger, string p1, string p2);
+
+ [LoggerMessage(EventId = 3, Level = LogLevel.Debug, Message = "")]
+ public static partial void M3(ILogger logger, string p1, int p2);
+
+ [LoggerMessage(EventId = 4, Level = LogLevel.Debug, Message = "{p1}")]
+ public static partial void M4(ILogger logger, string p1, int p2, int p3);
+#endif
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/MiscTestExtensions.cs b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/MiscTestExtensions.cs
new file mode 100644
index 00000000000000..5abecef3899069
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/MiscTestExtensions.cs
@@ -0,0 +1,34 @@
+// 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.Logging;
+
+// Used to test use outside of a namespace
+internal static partial class NoNamespace
+{
+ [LoggerMessage(EventId = 0, Level = LogLevel.Critical, Message = "Could not open socket to `{hostName}`")]
+ public static partial void CouldNotOpenSocket(ILogger logger, string hostName);
+}
+
+namespace Level1
+{
+ // used to test use inside a one-level namespace
+ internal static partial class OneLevelNamespace
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Critical, Message = "Could not open socket to `{hostName}`")]
+ public static partial void CouldNotOpenSocket(ILogger logger, string hostName);
+ }
+}
+
+namespace Level1
+{
+ namespace Level2
+ {
+ // used to test use inside a two-level namespace
+ internal static partial class TwoLevelNamespace
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Critical, Message = "Could not open socket to `{hostName}`")]
+ public static partial void CouldNotOpenSocket(ILogger logger, string hostName);
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/README.md b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/README.md
new file mode 100644
index 00000000000000..f6cf4fd21bc25d
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/README.md
@@ -0,0 +1,7 @@
+The source files in this directory serve two purposes:
+
+1. They are used to trigger the source generator during compilation of the test suite itself. The resulting generated code
+is then tested by LoggerMessageGeneratedCodeTests.cs. This ensures the generated code works reliably.
+
+2.They are loaded as a file from `LoggerMessageGeneratorEmitterTests.cs`, and then fed manually to the parser and then the generator
+This is used strictly to calculate code coverage attained by the first case above.
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/SignatureTestExtensions.cs b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/SignatureTestExtensions.cs
new file mode 100644
index 00000000000000..044aa1402ae465
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/SignatureTestExtensions.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.
+
+namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
+{
+ // test particular method signature variations are generated correctly
+ internal static partial class SignatureTestExtensions
+ {
+ // extension method
+ [LoggerMessage(EventId = 10, Level = LogLevel.Critical, Message = "Message11")]
+ internal static partial void M11(this ILogger logger);
+
+ public static void Combo(ILogger logger)
+ {
+ logger.M11();
+ }
+ }
+
+ // test particular method signature variations are generated correctly
+ internal partial class SignatureTestExtensions
+ where T : class
+ {
+ public static void Combo(ILogger logger, ILogger logger2)
+ {
+ M1(logger);
+ M2(logger);
+ M3(logger);
+ M4(logger2);
+ M5(logger, new[] { "A" });
+ M6(logger);
+ M8(logger);
+ M9(logger);
+ M10(logger, null);
+ M11(logger, "A", LogLevel.Debug, "B");
+ }
+
+ // normal public method
+ [LoggerMessage(EventId = 0, Level = LogLevel.Critical, Message = "Message1")]
+ public static partial void M1(ILogger logger);
+
+ // internal method
+ [LoggerMessage(EventId = 1, Level = LogLevel.Critical, Message = "Message2")]
+ internal static partial void M2(ILogger logger);
+
+ // private method
+ [LoggerMessage(EventId = 2, Level = LogLevel.Critical, Message = "Message3")]
+ private static partial void M3(ILogger logger);
+
+ // generic ILogger
+ [LoggerMessage(EventId = 3, Level = LogLevel.Critical, Message = "Message4")]
+ private static partial void M4(ILogger logger);
+
+ // random type method parameter
+ [LoggerMessage(EventId = 4, Level = LogLevel.Critical, Message = "Message5 {items}")]
+ private static partial void M5(ILogger logger, System.Collections.IEnumerable items);
+
+ // line feeds and quotes in the message string
+ [LoggerMessage(EventId = 5, Level = LogLevel.Critical, Message = "Message6\n\"\r")]
+ private static partial void M6(ILogger logger);
+
+ // generic parameter
+ [LoggerMessage(EventId = 6, Level = LogLevel.Critical, Message = "Message7 {p1}\n\"\r")]
+ private static partial void M7(ILogger logger, T p1);
+
+ // normal public method
+ [LoggerMessage(EventId = 7, Level = LogLevel.Critical, Message = "Message8")]
+ private protected static partial void M8(ILogger logger);
+
+ // internal method
+ [LoggerMessage(EventId = 8, Level = LogLevel.Critical, Message = "Message9")]
+ protected internal static partial void M9(ILogger logger);
+
+ // nullable parameter
+ [LoggerMessage(EventId = 9, Level = LogLevel.Critical, Message = "Message10 {optional}")]
+ internal static partial void M10(ILogger logger, string? optional);
+
+ // dynamic log level
+ [LoggerMessage(EventId = 10, Message = "Message11 {p1} {p2}")]
+ internal static partial void M11(ILogger logger, string p1, LogLevel level, string p2);
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/TemplateTestExtensions.cs b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/TemplateTestExtensions.cs
new file mode 100644
index 00000000000000..840e5bce5cadbb
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/TemplateTestExtensions.cs
@@ -0,0 +1,20 @@
+// 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.Generators.Tests.TestClasses
+{
+ internal static partial class TemplateTestExtensions
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Error, Message = "M0 {A1}")]
+ public static partial void M0(ILogger logger, int a1);
+
+ [LoggerMessage(EventId = 1, Level = LogLevel.Error, Message = "M1 {A1} {A1}")]
+ public static partial void M1(ILogger logger, int a1);
+
+ [LoggerMessage(EventId = 2, Level = LogLevel.Error, Message = "M2 {A1} {a2} {A3} {a4} {A5} {a6} {A7}")]
+ public static partial void M2(ILogger logger, int a1, int a2, int a3, int a4, int a5, int a6, int a7);
+
+ [LoggerMessage(EventId = 3, Level = LogLevel.Error, Message = "M3 {a2} {A1}")]
+ public static partial void M3(ILogger logger, int a1, int a2);
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/TestInstances.cs b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/TestInstances.cs
new file mode 100644
index 00000000000000..13ed72bbc82761
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging/tests/Microsoft.Extensions.Logging.Generators.Tests/TestClasses/TestInstances.cs
@@ -0,0 +1,21 @@
+// 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.Generators.Tests.TestClasses
+{
+ public partial class TestInstances
+ {
+ private readonly ILogger _myLogger;
+
+ public TestInstances(ILogger logger)
+ {
+ _myLogger = logger;
+ }
+
+ [LoggerMessage(EventId = 0, Level = LogLevel.Error, Message = "M0")]
+ public partial void M0();
+
+ [LoggerMessage(EventId = 1, Level = LogLevel.Trace, Message = "M1 {p1}")]
+ public partial void M1(string p1);
+ }
+}
diff --git a/src/libraries/src.proj b/src/libraries/src.proj
index 2a2f28c8567d00..c0738c440374f3 100644
--- a/src/libraries/src.proj
+++ b/src/libraries/src.proj
@@ -9,6 +9,8 @@
Exclude="@(ProjectExclusions)" />
+ false
+
+
+
+
+