diff --git a/GFramework.Generator.Attributes/GFramework.Generator.Attributes.csproj b/GFramework.Generator.Attributes/GFramework.Generator.Attributes.csproj
index 2b946ab..2bac1cd 100644
--- a/GFramework.Generator.Attributes/GFramework.Generator.Attributes.csproj
+++ b/GFramework.Generator.Attributes/GFramework.Generator.Attributes.csproj
@@ -3,5 +3,9 @@
netstandard2.0
GeWuYou.GFramework.Generator.Attributes
1.0.0
+ 10
+
+
+
diff --git a/GFramework.Generator.Attributes/generator/logging/LogAttribute.cs b/GFramework.Generator.Attributes/generator/logging/LogAttribute.cs
new file mode 100644
index 0000000..2214713
--- /dev/null
+++ b/GFramework.Generator.Attributes/generator/logging/LogAttribute.cs
@@ -0,0 +1,32 @@
+#nullable enable
+using System;
+
+namespace GFramework.Generator.Attributes.generator.logging;
+
+///
+/// 标注在类上,Source Generator 会为该类自动生成一个日志记录器字段。
+///
+[AttributeUsage(AttributeTargets.Class, Inherited = false)]
+public sealed class LogAttribute : Attribute
+{
+ /// 日志分类名(默认使用类名)
+ public string? Category { get; }
+
+ /// 生成字段名
+ public string FieldName { get; set; } = "_log";
+
+ /// 是否生成 static 字段
+ public bool IsStatic { get; set; } = true;
+
+ /// 访问修饰符
+ public string AccessModifier { get; set; } = "private";
+
+ ///
+ /// 初始化 LogAttribute 类的新实例
+ ///
+ /// 日志分类名,默认使用类名
+ public LogAttribute(string? category = null)
+ {
+ Category = category;
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Generator/AnalyzerReleases.Shipped.md b/GFramework.Generator/AnalyzerReleases.Shipped.md
new file mode 100644
index 0000000..60b59dd
--- /dev/null
+++ b/GFramework.Generator/AnalyzerReleases.Shipped.md
@@ -0,0 +1,3 @@
+; Shipped analyzer releases
+; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
+
diff --git a/GFramework.Generator/AnalyzerReleases.Unshipped.md b/GFramework.Generator/AnalyzerReleases.Unshipped.md
new file mode 100644
index 0000000..6e2cad7
--- /dev/null
+++ b/GFramework.Generator/AnalyzerReleases.Unshipped.md
@@ -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
\ No newline at end of file
diff --git a/GFramework.Generator/GFramework.Generator.csproj b/GFramework.Generator/GFramework.Generator.csproj
index 593796c..f3e46ac 100644
--- a/GFramework.Generator/GFramework.Generator.csproj
+++ b/GFramework.Generator/GFramework.Generator.csproj
@@ -16,6 +16,7 @@
true
Generated
+ true
diff --git a/GFramework.Generator/generator/logging/Diagnostic.cs b/GFramework.Generator/generator/logging/Diagnostic.cs
new file mode 100644
index 0000000..6dd2100
--- /dev/null
+++ b/GFramework.Generator/generator/logging/Diagnostic.cs
@@ -0,0 +1,25 @@
+using Microsoft.CodeAnalysis;
+
+namespace GFramework.Generator.generator.logging;
+
+///
+/// 提供诊断描述符的静态类,用于GFramework日志生成器的编译时检查
+///
+internal static class Diagnostics
+{
+ ///
+ /// 定义一个诊断描述符,用于检查使用[Log]特性的类是否声明为partial
+ ///
+ ///
+ /// 当类使用[Log]特性但未声明为partial时,编译器将报告此错误
+ ///
+ 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
+ );
+}
diff --git a/GFramework.Generator/generator/logging/LoggerGenerator.cs b/GFramework.Generator/generator/logging/LoggerGenerator.cs
new file mode 100644
index 0000000..6bed45c
--- /dev/null
+++ b/GFramework.Generator/generator/logging/LoggerGenerator.cs
@@ -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
+{
+ ///
+ /// 日志生成器,用于为标记了LogAttribute的类自动生成日志字段
+ ///
+ [Generator]
+ public sealed class LoggerGenerator : IIncrementalGenerator
+ {
+ private const string AttributeMetadataName =
+ "GFramework.Generator.Attributes.Logging.LogAttribute";
+
+ ///
+ /// 初始化增量生成器
+ ///
+ /// 增量生成器初始化上下文
+ 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));
+ });
+ }
+
+ ///
+ /// 生成日志字段代码
+ ///
+ /// 类符号
+ /// 生成的代码字符串
+ 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("// ");
+ 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();
+ }
+
+ ///
+ /// 获取属性参数的值
+ ///
+ /// 参数类型
+ /// 属性数据
+ /// 参数名称
+ /// 默认值
+ /// 参数值或默认值
+ private static T GetNamedArg(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;
+ }
+ }
+}