mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-25 04:59:01 +08:00
feat(logging): 添加日志生成器功能
- 实现了 LoggerGenerator 源代码生成器,为标记 LogAttribute 的类自动生成日志字段 - 添加了 LogAttribute 特性,支持配置日志分类、字段名、访问修饰符和静态属性 - 创建了 Diagnostics 静态类,定义 GFLOG001 诊断规则检查 partial 类声明 - 集成 Microsoft.CodeAnalysis 包,启用增量生成器和扩展分析器规则 - 生成的代码包含命名空间、类名和日志字段的完整实现
This commit is contained in:
parent
5087db9f21
commit
ab5ea42350
@ -3,5 +3,9 @@
|
|||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<PackageId>GeWuYou.GFramework.Generator.Attributes</PackageId>
|
<PackageId>GeWuYou.GFramework.Generator.Attributes</PackageId>
|
||||||
<Version>1.0.0</Version>
|
<Version>1.0.0</Version>
|
||||||
|
<LangVersion>10</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GFramework.Generator.Attributes.generator.logging;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 标注在类上,Source Generator 会为该类自动生成一个日志记录器字段。
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||||
|
public sealed class LogAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>日志分类名(默认使用类名)</summary>
|
||||||
|
public string? Category { get; }
|
||||||
|
|
||||||
|
/// <summary>生成字段名</summary>
|
||||||
|
public string FieldName { get; set; } = "_log";
|
||||||
|
|
||||||
|
/// <summary>是否生成 static 字段</summary>
|
||||||
|
public bool IsStatic { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>访问修饰符</summary>
|
||||||
|
public string AccessModifier { get; set; } = "private";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化 LogAttribute 类的新实例
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="category">日志分类名,默认使用类名</param>
|
||||||
|
public LogAttribute(string? category = null)
|
||||||
|
{
|
||||||
|
Category = category;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
GFramework.Generator/AnalyzerReleases.Shipped.md
Normal file
3
GFramework.Generator/AnalyzerReleases.Shipped.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
; Shipped analyzer releases
|
||||||
|
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
|
||||||
|
|
||||||
8
GFramework.Generator/AnalyzerReleases.Unshipped.md
Normal file
8
GFramework.Generator/AnalyzerReleases.Unshipped.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
; Unshipped analyzer release
|
||||||
|
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
|
||||||
|
|
||||||
|
### New Rules
|
||||||
|
|
||||||
|
Rule ID | Category | Severity | Notes
|
||||||
|
--------|----------|----------|-------
|
||||||
|
GFLOG001 | GFramework.Logging | Error | Diagnostics
|
||||||
@ -16,6 +16,7 @@
|
|||||||
<!-- 有助于调试生成的代码(可选) -->
|
<!-- 有助于调试生成的代码(可选) -->
|
||||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||||
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
|
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
|
||||||
|
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all"/>
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all"/>
|
||||||
|
|||||||
25
GFramework.Generator/generator/logging/Diagnostic.cs
Normal file
25
GFramework.Generator/generator/logging/Diagnostic.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace GFramework.Generator.generator.logging;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 提供诊断描述符的静态类,用于GFramework日志生成器的编译时检查
|
||||||
|
/// </summary>
|
||||||
|
internal static class Diagnostics
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 定义一个诊断描述符,用于检查使用[Log]特性的类是否声明为partial
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 当类使用[Log]特性但未声明为partial时,编译器将报告此错误
|
||||||
|
/// </remarks>
|
||||||
|
public static readonly DiagnosticDescriptor MustBePartial =
|
||||||
|
new(
|
||||||
|
id: "GFLOG001",
|
||||||
|
title: "Class must be partial",
|
||||||
|
messageFormat: "Class '{0}' must be declared partial to use [Log]",
|
||||||
|
category: "GFramework.Logging",
|
||||||
|
DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true
|
||||||
|
);
|
||||||
|
}
|
||||||
149
GFramework.Generator/generator/logging/LoggerGenerator.cs
Normal file
149
GFramework.Generator/generator/logging/LoggerGenerator.cs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
|
namespace GFramework.Generator.generator.logging
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 日志生成器,用于为标记了LogAttribute的类自动生成日志字段
|
||||||
|
/// </summary>
|
||||||
|
[Generator]
|
||||||
|
public sealed class LoggerGenerator : IIncrementalGenerator
|
||||||
|
{
|
||||||
|
private const string AttributeMetadataName =
|
||||||
|
"GFramework.Generator.Attributes.Logging.LogAttribute";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化增量生成器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">增量生成器初始化上下文</param>
|
||||||
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||||
|
{
|
||||||
|
// 1. 拿到 LogAttribute Symbol
|
||||||
|
var logAttributeSymbol =
|
||||||
|
context.CompilationProvider.Select((compilation, _) =>
|
||||||
|
compilation.GetTypeByMetadataName(AttributeMetadataName));
|
||||||
|
|
||||||
|
// 2. 在 SyntaxProvider 阶段就拿到 SemanticModel
|
||||||
|
var candidates =
|
||||||
|
context.SyntaxProvider.CreateSyntaxProvider(
|
||||||
|
static (node, _) => node is ClassDeclarationSyntax,
|
||||||
|
static (ctx, _) =>
|
||||||
|
{
|
||||||
|
var classDecl = (ClassDeclarationSyntax)ctx.Node;
|
||||||
|
var symbol = ctx.SemanticModel.GetDeclaredSymbol(classDecl);
|
||||||
|
return (ClassDecl: classDecl, Symbol: symbol);
|
||||||
|
})
|
||||||
|
.Where(x => x.Symbol is not null);
|
||||||
|
|
||||||
|
// 3. 合并 Attribute Symbol 并筛选
|
||||||
|
var targets =
|
||||||
|
candidates.Combine(logAttributeSymbol)
|
||||||
|
.Where(pair =>
|
||||||
|
{
|
||||||
|
var symbol = pair.Left.Symbol!;
|
||||||
|
var attrSymbol = pair.Right;
|
||||||
|
if (attrSymbol is null) return false;
|
||||||
|
|
||||||
|
return symbol.GetAttributes().Any(a =>
|
||||||
|
SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. 输出代码
|
||||||
|
context.RegisterSourceOutput(targets, (spc, pair) =>
|
||||||
|
{
|
||||||
|
var classDecl = pair.Left.ClassDecl;
|
||||||
|
var classSymbol = pair.Left.Symbol!;
|
||||||
|
|
||||||
|
// 必须是 partial
|
||||||
|
if (!classDecl.Modifiers.Any(SyntaxKind.PartialKeyword))
|
||||||
|
{
|
||||||
|
spc.ReportDiagnostic(
|
||||||
|
Diagnostic.Create(
|
||||||
|
Diagnostics.MustBePartial,
|
||||||
|
classDecl.Identifier.GetLocation(),
|
||||||
|
classSymbol.Name
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var source = Generate(classSymbol);
|
||||||
|
spc.AddSource(
|
||||||
|
$"{classSymbol.Name}.Logger.g.cs",
|
||||||
|
SourceText.From(source, Encoding.UTF8));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成日志字段代码
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="classSymbol">类符号</param>
|
||||||
|
/// <returns>生成的代码字符串</returns>
|
||||||
|
private static string Generate(INamedTypeSymbol classSymbol)
|
||||||
|
{
|
||||||
|
var ns = classSymbol.ContainingNamespace.IsGlobalNamespace
|
||||||
|
? null
|
||||||
|
: classSymbol.ContainingNamespace.ToDisplayString();
|
||||||
|
|
||||||
|
var className = classSymbol.Name;
|
||||||
|
|
||||||
|
var attr = classSymbol.GetAttributes()
|
||||||
|
.First(a => a.AttributeClass!.ToDisplayString() == AttributeMetadataName);
|
||||||
|
|
||||||
|
var category =
|
||||||
|
attr.ConstructorArguments.Length > 0 &&
|
||||||
|
attr.ConstructorArguments[0].Value is string s
|
||||||
|
? s
|
||||||
|
: className;
|
||||||
|
|
||||||
|
var fieldName = GetNamedArg(attr, "FieldName", "_log");
|
||||||
|
var access = GetNamedArg(attr, "AccessModifier", "private");
|
||||||
|
var isStatic = GetNamedArg(attr, "IsStatic", true);
|
||||||
|
|
||||||
|
var staticKeyword = isStatic ? "static " : "";
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("// <auto-generated />");
|
||||||
|
sb.AppendLine("using GFramework.Core.logging;");
|
||||||
|
|
||||||
|
if (ns is not null)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"namespace {ns}");
|
||||||
|
sb.AppendLine("{");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine($" public partial class {className}");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(
|
||||||
|
$" {access} {staticKeyword}readonly ILog {fieldName} =" +
|
||||||
|
$" Log.CreateLogger(\"{category}\");");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
|
||||||
|
if (ns is not null)
|
||||||
|
sb.AppendLine("}");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取属性参数的值
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">参数类型</typeparam>
|
||||||
|
/// <param name="attr">属性数据</param>
|
||||||
|
/// <param name="name">参数名称</param>
|
||||||
|
/// <param name="defaultValue">默认值</param>
|
||||||
|
/// <returns>参数值或默认值</returns>
|
||||||
|
private static T GetNamedArg<T>(AttributeData attr, string name, T defaultValue)
|
||||||
|
{
|
||||||
|
foreach (var kv in attr.NamedArguments)
|
||||||
|
{
|
||||||
|
if (kv.Key == name && kv.Value.Value is T v)
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user