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 cb094673111ec..df1f73e6d7b06 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 935f406380f73..e8e4e3f971a3e 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -44,6 +44,11 @@ + + + 3.8.0 + 3.8.0 + 6.0.0-preview3.21168.1 3.9.0-5.final @@ -154,6 +159,7 @@ 2.4.2 1.3.0 12.0.3 + 2.0.4 4.12.0 2.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 0000000000000..4928270b06f17 --- /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 7d19ffd425c31..c04786d316632 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 0000000000000..9e51646b52f17 --- /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 0000000000000..91065fa530fc0 --- /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 0000000000000..3b909af90cc6a --- /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 0000000000000..9c1d2cbcfeb91 --- /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 0000000000000..719c9011b391e --- /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 0000000000000..f0d6b21bf7ae8 --- /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 0000000000000..044e1bd67633f --- /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 0000000000000..258f697c492e2 --- /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 0000000000000..0424989915dd7 --- /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 0000000000000..a482372783f57 --- /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 0000000000000..304056d1bd69f --- /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 0000000000000..c8b172fd7630b --- /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 0000000000000..fdb53576c829a --- /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 0000000000000..790fec15f6a66 --- /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 0000000000000..1451df01554bb --- /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 0000000000000..66fbc83a6e6a9 --- /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 0000000000000..fc70ba858ab99 --- /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 0000000000000..2cdeaa1133a21 --- /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 0000000000000..52f65573cda1e --- /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 b1aeb27796364..5180cb122482f 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 bb2d201cca320..a81c21d01f68b 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 0000000000000..b103ef31a4e16 --- /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 0000000000000..7a2026361b561 --- /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 0000000000000..32b7629942140 --- /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 0000000000000..866b0469124d8 --- /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 0000000000000..7e0661d77a699 --- /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 0000000000000..c66a267f7c32a --- /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 0000000000000..1aabf53997b2a --- /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 0000000000000..329b67c2a16a4 --- /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 0000000000000..3fa88999b3ab5 --- /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 0000000000000..b0f1cb39eaef2 --- /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 0000000000000..74d4b996ee709 --- /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 0000000000000..65cfd41a1347c --- /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 0000000000000..4c0ddf320aabf --- /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 0000000000000..45b01e56b2978 --- /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 0000000000000..5726aa02a4c3f --- /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 0000000000000..a30849288b53b --- /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 0000000000000..5abecef389906 --- /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 0000000000000..f6cf4fd21bc25 --- /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 0000000000000..044aa1402ae46 --- /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 0000000000000..840e5bce5cadb --- /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 0000000000000..13ed72bbc8276 --- /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 2a2f28c8567d0..c0738c440374f 100644 --- a/src/libraries/src.proj +++ b/src/libraries/src.proj @@ -9,6 +9,8 @@ Exclude="@(ProjectExclusions)" /> + false + + + + +