mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +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>
|
||||
<PackageId>GeWuYou.GFramework.Generator.Attributes</PackageId>
|
||||
<Version>1.0.0</Version>
|
||||
<LangVersion>10</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
|
||||
</ItemGroup>
|
||||
</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>
|
||||
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<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