GFramework/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs
GeWuYou d94d8deb29 fix(logging): 修复异步日志追加器中的操作取消异常处理
- 添加对 OperationCanceledException 的特殊处理,避免将其报告为后台处理错误
- 在 ReportProcessingError 方法中检查并过滤掉操作取消异常
- 添加单元测试验证当内部追加器抛出 OperationCanceledException 时不报告错误
- 创建 CancellationAppender 测试辅助类来模拟取消异常场景
- 确保取消相关的异常不会触发错误处理逻辑
2026-03-21 21:59:51 +08:00

298 lines
8.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using GFramework.Core.Logging.Appenders;
namespace GFramework.Core.Tests.Logging;
/// <summary>
/// 测试 AsyncLogAppender 的功能和行为
/// </summary>
[TestFixture]
public class AsyncLogAppenderTests
{
[Test]
public void Constructor_WithNullInnerAppender_ShouldThrowArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => new AsyncLogAppender(null!));
}
[Test]
public void Constructor_WithInvalidBufferSize_ShouldThrowArgumentException()
{
var innerAppender = new TestAppender();
Assert.Throws<ArgumentException>(() => new AsyncLogAppender(innerAppender, bufferSize: 0));
Assert.Throws<ArgumentException>(() => new AsyncLogAppender(innerAppender, bufferSize: -1));
}
[Test]
public void Append_ShouldNotBlock()
{
var innerAppender = new SlowAppender(delayMs: 100);
using var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 1000);
var startTime = DateTime.UtcNow;
for (int i = 0; i < 10; i++)
{
var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null);
asyncAppender.Append(entry);
}
var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
// 异步写入应该非常快(< 100ms不应该等待内部 Appender
Assert.That(elapsed, Is.LessThan(100));
}
[Test]
public void Append_ShouldEventuallyWriteToInnerAppender()
{
var innerAppender = new TestAppender();
using (var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 1000))
{
for (int i = 0; i < 10; i++)
{
var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null);
asyncAppender.Append(entry);
}
asyncAppender.Flush();
}
Assert.That(innerAppender.Entries.Count, Is.EqualTo(10));
}
[Test]
public void Flush_ShouldWaitForAllEntriesToBeProcessed()
{
var innerAppender = new TestAppender();
using var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 1000);
for (int i = 0; i < 100; i++)
{
var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null);
asyncAppender.Append(entry);
}
asyncAppender.Flush();
Assert.That(innerAppender.Entries.Count, Is.EqualTo(100));
}
[Test]
public void Dispose_ShouldProcessRemainingEntries()
{
var innerAppender = new TestAppender();
using (var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 1000))
{
for (int i = 0; i < 50; i++)
{
var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null);
asyncAppender.Append(entry);
}
} // Dispose 会等待所有日志处理完成
Assert.That(innerAppender.Entries.Count, Is.EqualTo(50));
}
[Test]
public void Append_AfterDispose_ShouldThrowObjectDisposedException()
{
var innerAppender = new TestAppender();
var asyncAppender = new AsyncLogAppender(innerAppender);
asyncAppender.Dispose();
var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", "Test", null, null);
Assert.Throws<ObjectDisposedException>(() => asyncAppender.Append(entry));
}
[Test]
public void PendingCount_ShouldReflectQueuedEntries()
{
var innerAppender = new SlowAppender(delayMs: 50);
using var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 1000);
for (int i = 0; i < 10; i++)
{
var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null);
asyncAppender.Append(entry);
}
// 应该有一些待处理的条目
Assert.That(asyncAppender.PendingCount, Is.GreaterThanOrEqualTo(0));
}
[Test]
public async Task Append_FromMultipleThreads_ShouldHandleConcurrency()
{
var innerAppender = new TestAppender();
using var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 10000);
var tasks = new Task[10];
for (int t = 0; t < 10; t++)
{
int threadId = t;
tasks[t] = Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger",
$"Thread {threadId} Message {i}", null, null);
asyncAppender.Append(entry);
}
});
}
await Task.WhenAll(tasks);
asyncAppender.Flush();
Assert.That(innerAppender.Entries.Count, Is.EqualTo(1000));
}
[Test]
public void Append_WhenInnerAppenderThrows_ShouldNotCrash()
{
var reportedExceptions = new List<Exception>();
var innerAppender = new ThrowingAppender();
using var asyncAppender = new AsyncLogAppender(
innerAppender,
bufferSize: 1000,
processingErrorHandler: reportedExceptions.Add);
// 即使内部 Appender 抛出异常,也不应该影响调用线程
Assert.DoesNotThrow(() =>
{
for (int i = 0; i < 10; i++)
{
var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null);
asyncAppender.Append(entry);
}
});
asyncAppender.Flush();
Assert.That(reportedExceptions, Has.Count.EqualTo(10));
Assert.That(reportedExceptions, Has.All.TypeOf<InvalidOperationException>());
Assert.That(reportedExceptions.Select(static exception => exception.Message),
Has.All.EqualTo("Test exception"));
}
[Test]
public void Append_WhenProcessingErrorHandlerThrows_ShouldStillNotCrash()
{
var innerAppender = new ThrowingAppender();
using var asyncAppender = new AsyncLogAppender(
innerAppender,
bufferSize: 1000,
processingErrorHandler: static _ => throw new InvalidOperationException("Observer failure"));
Assert.DoesNotThrow(() =>
{
for (int i = 0; i < 10; i++)
{
var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null);
asyncAppender.Append(entry);
}
});
Assert.That(asyncAppender.Flush(), Is.True);
}
[Test]
public void Append_WhenInnerAppenderThrowsOperationCanceledException_ShouldNotReportError()
{
var reportedExceptions = new List<Exception>();
var innerAppender = new CancellationAppender();
using var asyncAppender = new AsyncLogAppender(
innerAppender,
bufferSize: 1000,
processingErrorHandler: reportedExceptions.Add);
Assert.DoesNotThrow(() =>
{
for (int i = 0; i < 10; i++)
{
var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null);
asyncAppender.Append(entry);
}
});
Assert.That(asyncAppender.Flush(), Is.True);
Assert.That(reportedExceptions, Is.Empty);
}
// 辅助测试类
private class TestAppender : ILogAppender
{
public List<LogEntry> Entries { get; } = new();
public void Append(LogEntry entry)
{
lock (Entries)
{
Entries.Add(entry);
}
}
public void Flush()
{
}
public void Dispose()
{
}
}
private class SlowAppender : ILogAppender
{
private readonly int _delayMs;
public SlowAppender(int delayMs)
{
_delayMs = delayMs;
}
public void Append(LogEntry entry)
{
Thread.Sleep(_delayMs);
}
public void Flush()
{
}
public void Dispose()
{
}
}
private class ThrowingAppender : ILogAppender
{
public void Append(LogEntry entry)
{
throw new InvalidOperationException("Test exception");
}
public void Flush()
{
}
public void Dispose()
{
}
}
private class CancellationAppender : ILogAppender
{
public void Append(LogEntry entry)
{
throw new OperationCanceledException("Simulated cancellation");
}
public void Flush()
{
}
public void Dispose()
{
}
}
}