监控和诊断
本文档介绍源生成器开发中的监控和诊断技巧,包括性能监控、错误处理、诊断报告等。
📋 文档信息
- 难度级别: 高级
- 预计阅读时间: 20 分钟
- 前置知识:
- 诊断 API 基础
- 性能分析基础
🎯 学习目标
完成本文档后,您将能够:
- ✅ 实现性能监控
- ✅ 正确报告诊断信息
- ✅ 实现优雅的错误处理
- ✅ 记录详细的日志
- ✅ 分析生成器性能
错误处理模式
诊断报告最佳实践
csharp
using Microsoft.CodeAnalysis;
/// <summary>
/// 诊断报告器
/// 展示如何正确报告诊断信息
/// </summary>
public class DiagnosticReporter
{
// ✅ 定义诊断描述符
private static readonly DiagnosticDescriptor InvalidAttributeUsage = new(
id: "GEN001",
title: "Invalid attribute usage",
messageFormat: "Attribute '{0}' cannot be applied to '{1}'",
category: "Generator",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "The attribute can only be applied to classes.");
/// <summary>
/// 报告诊断
/// </summary>
public void ReportDiagnostic(
SourceProductionContext context,
Location location,
params object[] messageArgs)
{
var diagnostic = Diagnostic.Create(
InvalidAttributeUsage,
location,
messageArgs);
context.ReportDiagnostic(diagnostic);
}
/// <summary>
/// 安全地报告诊断(处理异常)
/// </summary>
public void SafeReportDiagnostic(
SourceProductionContext context,
Action reportAction)
{
try
{
reportAction();
}
catch (Exception ex)
{
// ✅ 不要让异常传播,而是报告为诊断
var diagnostic = Diagnostic.Create(
new DiagnosticDescriptor(
"GEN999",
"Generator error",
$"An error occurred: {ex.Message}",
"Generator",
DiagnosticSeverity.Error,
true),
Location.None);
context.ReportDiagnostic(diagnostic);
}
}
}优雅的错误恢复
csharp
using Microsoft.CodeAnalysis;
/// <summary>
/// 错误恢复策略
/// </summary>
public class ErrorRecoveryGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var classData = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, ct) =>
{
try
{
return ProcessClass(ctx, ct);
}
catch (Exception ex)
{
// ✅ 记录错误但返回 null,让生成器继续
System.Diagnostics.Debug.WriteLine($"Error: {ex.Message}");
return null;
}
})
.Where(x => x != null); // ✅ 过滤掉错误的结果
context.RegisterSourceOutput(classData, (spc, data) =>
{
if (data == null) return;
try
{
var code = GenerateCode(data);
spc.AddSource($"{data.ClassName}.g.cs", code);
}
catch (Exception ex)
{
// ✅ 报告诊断而不是抛出异常
var diagnostic = Diagnostic.Create(
new DiagnosticDescriptor(
"GEN002",
"Code generation failed",
$"Failed to generate code for {data.ClassName}: {ex.Message}",
"Generator",
DiagnosticSeverity.Warning,
true),
Location.None);
spc.ReportDiagnostic(diagnostic);
}
});
}
private ClassData? ProcessClass(GeneratorSyntaxContext ctx, CancellationToken ct)
{
// 处理类
return null;
}
private string GenerateCode(ClassData data)
{
return $"// Code for {data.ClassName}";
}
}
/// <summary>
/// 类数据
/// </summary>
public record ClassData(string ClassName, string Namespace, List<PropertyData> Properties);
/// <summary>
/// 属性数据
/// </summary>
public record PropertyData(string Name, string Type);测试策略
单元测试最佳实践
csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
/// <summary>
/// 生成器单元测试
/// </summary>
public class GeneratorTests
{
[Fact]
public void Generator_GeneratesExpectedCode()
{
// Arrange
var source = @"
using System;
[Generate]
public class TestClass
{
public string Name { get; set; }
public int Age { get; set; }
}";
var compilation = CreateCompilation(source);
var generator = new MyGenerator();
// Act
var driver = CSharpGeneratorDriver.Create(generator);
driver = driver.RunGenerators(compilation);
// Assert
var result = driver.GetRunResult();
Assert.Single(result.GeneratedTrees);
Assert.Empty(result.Diagnostics);
var generatedCode = result.GeneratedTrees[0].ToString();
Assert.Contains("partial class TestClass", generatedCode);
}
[Theory]
[InlineData("public", true)]
[InlineData("private", false)]
[InlineData("internal", false)]
public void Generator_HandlesAccessibility(string accessibility, bool shouldGenerate)
{
// Arrange
var source = $@"
[Generate]
{accessibility} class TestClass {{ }}";
var compilation = CreateCompilation(source);
var generator = new MyGenerator();
// Act
var driver = CSharpGeneratorDriver.Create(generator);
driver = driver.RunGenerators(compilation);
var result = driver.GetRunResult();
// Assert
if (shouldGenerate)
{
Assert.Single(result.GeneratedTrees);
}
else
{
Assert.Empty(result.GeneratedTrees);
}
}
private static Compilation CreateCompilation(string source)
{
var syntaxTree = CSharpSyntaxTree.ParseText(source);
var references = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location))
.Select(a => MetadataReference.CreateFromFile(a.Location))
.Cast<MetadataReference>();
return CSharpCompilation.Create(
"TestAssembly",
new[] { syntaxTree },
references,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
}
}快照测试
csharp
using Xunit;
using VerifyXunit;
/// <summary>
/// 快照测试示例
/// </summary>
[UsesVerify]
public class SnapshotTests
{
[Fact]
public Task Generator_ProducesExpectedOutput()
{
// Arrange
var source = @"
[Generate]
public class TestClass
{
public string Name { get; set; }
}";
var compilation = CreateCompilation(source);
var generator = new MyGenerator();
// Act
var driver = CSharpGeneratorDriver.Create(generator);
driver = driver.RunGenerators(compilation);
var result = driver.GetRunResult();
var generatedCode = result.GeneratedTrees[0].ToString();
// Assert - 使用 Verify 进行快照测试
return Verifier.Verify(generatedCode);
}
}性能监控
性能监控器实现
csharp
using System.Diagnostics;
/// <summary>
/// 性能监控器
/// 用于监控生成器的性能
/// </summary>
public class PerformanceMonitor
{
private readonly Dictionary<string, Stopwatch> _timers = new();
private readonly Dictionary<string, long> _counters = new();
/// <summary>
/// 开始计时
/// </summary>
public void StartTimer(string name)
{
if (!_timers.ContainsKey(name))
{
_timers[name] = new Stopwatch();
}
_timers[name].Restart();
}
/// <summary>
/// 停止计时
/// </summary>
public void StopTimer(string name)
{
if (_timers.TryGetValue(name, out var timer))
{
timer.Stop();
}
}
/// <summary>
/// 获取计时结果
/// </summary>
public TimeSpan GetElapsed(string name)
{
return _timers.TryGetValue(name, out var timer)
? timer.Elapsed
: TimeSpan.Zero;
}
/// <summary>
/// 增加计数器
/// </summary>
public void IncrementCounter(string name, long value = 1)
{
if (!_counters.ContainsKey(name))
{
_counters[name] = 0;
}
_counters[name] += value;
}
/// <summary>
/// 获取计数器值
/// </summary>
public long GetCounter(string name)
{
return _counters.TryGetValue(name, out var value) ? value : 0;
}
/// <summary>
/// 生成性能报告
/// </summary>
public string GenerateReport()
{
var sb = new StringBuilder();
sb.AppendLine("=== Performance Report ===");
sb.AppendLine();
sb.AppendLine("Timers:");
foreach (var (name, timer) in _timers)
{
sb.AppendLine($" {name}: {timer.Elapsed.TotalMilliseconds:F2}ms");
}
sb.AppendLine();
sb.AppendLine("Counters:");
foreach (var (name, value) in _counters)
{
sb.AppendLine($" {name}: {value}");
}
return sb.ToString();
}
}
/// <summary>
/// 使用性能监控的生成器
/// </summary>
[Generator]
public class MonitoredGenerator : IIncrementalGenerator
{
private readonly PerformanceMonitor _monitor = new();
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var classData = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, ct) =>
{
_monitor.StartTimer("Transform");
_monitor.IncrementCounter("ClassesProcessed");
try
{
return ProcessClass(ctx, ct);
}
finally
{
_monitor.StopTimer("Transform");
}
});
context.RegisterSourceOutput(classData, (spc, data) =>
{
if (data == null) return;
_monitor.StartTimer("CodeGeneration");
_monitor.IncrementCounter("FilesGenerated");
try
{
var code = GenerateCode(data);
spc.AddSource($"{data.ClassName}.g.cs", code);
}
finally
{
_monitor.StopTimer("CodeGeneration");
// 输出性能报告
var report = _monitor.GenerateReport();
System.Diagnostics.Debug.WriteLine(report);
}
});
}
private ClassData? ProcessClass(GeneratorSyntaxContext ctx, CancellationToken ct)
{
// 处理类
return null;
}
private string GenerateCode(ClassData data)
{
return $"// Code for {data.ClassName}";
}
}日志记录
csharp
/// <summary>
/// 日志记录器
/// </summary>
public class GeneratorLogger
{
private readonly string _logFilePath;
private readonly object _lock = new();
public GeneratorLogger(string logFilePath)
{
_logFilePath = logFilePath;
}
/// <summary>
/// 记录信息
/// </summary>
public void LogInfo(string message)
{
Log("INFO", message);
}
/// <summary>
/// 记录警告
/// </summary>
public void LogWarning(string message)
{
Log("WARN", message);
}
/// <summary>
/// 记录错误
/// </summary>
public void LogError(string message, Exception? ex = null)
{
var fullMessage = ex != null
? $"{message}\n{ex}"
: message;
Log("ERROR", fullMessage);
}
/// <summary>
/// 记录日志
/// </summary>
private void Log(string level, string message)
{
lock (_lock)
{
try
{
var logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] {message}\n";
File.AppendAllText(_logFilePath, logEntry);
}
catch
{
// 忽略日志记录错误
}
}
}
}
/// <summary>
/// 使用日志记录的生成器
/// </summary>
[Generator]
public class LoggedGenerator : IIncrementalGenerator
{
private readonly GeneratorLogger _logger = new(@"C:\temp\generator.log");
public void Initialize(IncrementalGeneratorInitializationContext context)
{
_logger.LogInfo("Generator initialized");
var classData = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, ct) =>
{
try
{
_logger.LogInfo($"Processing class: {ctx.Node}");
return ProcessClass(ctx, ct);
}
catch (Exception ex)
{
_logger.LogError("Error processing class", ex);
return null;
}
});
context.RegisterSourceOutput(classData, (spc, data) =>
{
if (data == null) return;
try
{
_logger.LogInfo($"Generating code for: {data.ClassName}");
var code = GenerateCode(data);
spc.AddSource($"{data.ClassName}.g.cs", code);
_logger.LogInfo($"Successfully generated code for: {data.ClassName}");
}
catch (Exception ex)
{
_logger.LogError($"Error generating code for: {data.ClassName}", ex);
}
});
}
private ClassData? ProcessClass(GeneratorSyntaxContext ctx, CancellationToken ct)
{
// 处理类
return null;
}
private string GenerateCode(ClassData data)
{
return $"// Code for {data.ClassName}";
}
}🔑 关键要点
- 诊断报告 - 使用 DiagnosticDescriptor 正确报告错误
- 错误恢复 - 捕获异常并优雅地恢复
- 性能监控 - 使用计时器和计数器监控性能
- 日志记录 - 记录详细的日志信息
- 单元测试 - 编写全面的单元测试
📚 相关资源
📝 下一步
📝 文档质量保证
本文档遵循以下质量标准:
- ✅ 完整的目录结构
- ✅ 所有代码示例包含详细中文注释
- ✅ 包含最佳实践和反模式对比
- ✅ 包含真实使用场景
- ✅ 包含跨文档引用
- ✅ 内容完整,未因任何限制而精简
最后更新: 2025-01-21