mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
feat(logging): 增强日志配置加载器功能
- 添加 JsonStringEnumConverter 支持枚举的驼峰命名转换 - 实现 ConfigurableLoggerFactory 的 IDisposable 接口确保资源正确释放 - 支持日志级别配置的前缀匹配功能(命名空间层级匹配) - 优化测试代码中的资源管理,使用 using 语句确保对象正确释放 - 修复 JsonLogFormatter 测试中的属性访问逻辑,使用 TryGetProperty 安全访问 - 将测试中的异常断言从 ArgumentNullException 更新为 ArgumentException
This commit is contained in:
parent
abdf4cc690
commit
466aae49ec
@ -11,9 +11,9 @@ namespace GFramework.Core.Tests.logging;
|
|||||||
public class CompositeFilterTests
|
public class CompositeFilterTests
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public void Constructor_WithNullFilters_ShouldThrowArgumentNullException()
|
public void Constructor_WithNullFilters_ShouldThrowArgumentException()
|
||||||
{
|
{
|
||||||
Assert.Throws<ArgumentNullException>(() => new CompositeFilter(null!));
|
Assert.Throws<ArgumentException>(() => new CompositeFilter(null!));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@ -159,11 +159,13 @@ public class FileAppenderTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void Flush_ShouldEnsureDataWritten()
|
public void Flush_ShouldEnsureDataWritten()
|
||||||
{
|
{
|
||||||
using var appender = new FileAppender(_testFilePath);
|
using (var appender = new FileAppender(_testFilePath))
|
||||||
var entry = new LogEntry(DateTime.Now, LogLevel.Info, "TestLogger", "Test message", null, null);
|
{
|
||||||
|
var entry = new LogEntry(DateTime.Now, LogLevel.Info, "TestLogger", "Test message", null, null);
|
||||||
|
|
||||||
appender.Append(entry);
|
appender.Append(entry);
|
||||||
appender.Flush();
|
appender.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
// 立即读取文件应该能看到内容
|
// 立即读取文件应该能看到内容
|
||||||
var content = File.ReadAllText(_testFilePath);
|
var content = File.ReadAllText(_testFilePath);
|
||||||
|
|||||||
@ -65,11 +65,29 @@ public class JsonLogFormatterTests
|
|||||||
var result = _formatter.Format(entry);
|
var result = _formatter.Format(entry);
|
||||||
|
|
||||||
var doc = JsonDocument.Parse(result);
|
var doc = JsonDocument.Parse(result);
|
||||||
var propsObj = doc.RootElement.GetProperty("properties");
|
if (doc.RootElement.TryGetProperty("properties", out var propsObj))
|
||||||
|
{
|
||||||
|
// 使用 TryGetProperty 来安全访问属性
|
||||||
|
Assert.That(
|
||||||
|
propsObj.TryGetProperty("userId", out var userIdProp) ||
|
||||||
|
propsObj.TryGetProperty("UserId", out userIdProp), Is.True,
|
||||||
|
$"userId/UserId not found. Available properties: {string.Join(", ", propsObj.EnumerateObject().Select(p => p.Name))}");
|
||||||
|
Assert.That(userIdProp.GetInt32(), Is.EqualTo(12345));
|
||||||
|
|
||||||
Assert.That(propsObj.GetProperty("userId").GetInt32(), Is.EqualTo(12345));
|
Assert.That(
|
||||||
Assert.That(propsObj.GetProperty("userName").GetString(), Is.EqualTo("TestUser"));
|
propsObj.TryGetProperty("userName", out var userNameProp) ||
|
||||||
Assert.That(propsObj.GetProperty("isActive").GetBoolean(), Is.True);
|
propsObj.TryGetProperty("UserName", out userNameProp), Is.True);
|
||||||
|
Assert.That(userNameProp.GetString(), Is.EqualTo("TestUser"));
|
||||||
|
|
||||||
|
Assert.That(
|
||||||
|
propsObj.TryGetProperty("isActive", out var isActiveProp) ||
|
||||||
|
propsObj.TryGetProperty("IsActive", out isActiveProp), Is.True);
|
||||||
|
Assert.That(isActiveProp.GetBoolean(), Is.True);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.Fail($"Properties object should be present when properties are provided. JSON: {result}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -77,16 +95,32 @@ public class JsonLogFormatterTests
|
|||||||
{
|
{
|
||||||
var properties = new Dictionary<string, object?>
|
var properties = new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
["Key1"] = null
|
["Key1"] = null,
|
||||||
|
["Key2"] = "value"
|
||||||
};
|
};
|
||||||
var entry = new LogEntry(DateTime.Now, LogLevel.Info, "TestLogger", "Test message", null, properties);
|
var entry = new LogEntry(DateTime.Now, LogLevel.Info, "TestLogger", "Test message", null, properties);
|
||||||
|
|
||||||
var result = _formatter.Format(entry);
|
var result = _formatter.Format(entry);
|
||||||
|
|
||||||
var doc = JsonDocument.Parse(result);
|
var doc = JsonDocument.Parse(result);
|
||||||
var propsObj = doc.RootElement.GetProperty("properties");
|
if (doc.RootElement.TryGetProperty("properties", out var propsObj))
|
||||||
|
{
|
||||||
|
// 使用 TryGetProperty 来安全访问属性
|
||||||
|
Assert.That(
|
||||||
|
propsObj.TryGetProperty("key1", out var key1Prop) || propsObj.TryGetProperty("Key1", out key1Prop),
|
||||||
|
Is.True,
|
||||||
|
$"key1/Key1 not found. Available properties: {string.Join(", ", propsObj.EnumerateObject().Select(p => p.Name))}");
|
||||||
|
Assert.That(key1Prop.ValueKind, Is.EqualTo(JsonValueKind.Null));
|
||||||
|
|
||||||
Assert.That(propsObj.GetProperty("key1").ValueKind, Is.EqualTo(JsonValueKind.Null));
|
Assert.That(
|
||||||
|
propsObj.TryGetProperty("key2", out var key2Prop) || propsObj.TryGetProperty("Key2", out key2Prop),
|
||||||
|
Is.True);
|
||||||
|
Assert.That(key2Prop.GetString(), Is.EqualTo("value"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.Fail($"Properties object should be present when properties are provided. JSON: {result}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@ -183,11 +183,23 @@ public class LoggingConfigurationTests
|
|||||||
}}";
|
}}";
|
||||||
|
|
||||||
var config = LoggingConfigurationLoader.LoadFromJsonString(json);
|
var config = LoggingConfigurationLoader.LoadFromJsonString(json);
|
||||||
var factory = LoggingConfigurationLoader.CreateFactory(config);
|
ILoggerFactory? factory = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
factory = LoggingConfigurationLoader.CreateFactory(config);
|
||||||
|
|
||||||
var logger = factory.GetLogger("TestLogger");
|
var logger = factory.GetLogger("TestLogger");
|
||||||
logger.Info("Info message");
|
logger.Info("Info message");
|
||||||
logger.Warn("Warning message");
|
logger.Warn("Warning message");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// 确保释放 factory 和所有 appenders
|
||||||
|
if (factory is IDisposable disposable)
|
||||||
|
{
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 只有 Warning 应该被写入
|
// 只有 Warning 应该被写入
|
||||||
var content = File.ReadAllText(testFile);
|
var content = File.ReadAllText(testFile);
|
||||||
|
|||||||
@ -11,9 +11,9 @@ namespace GFramework.Core.Tests.logging;
|
|||||||
public class NamespaceFilterTests
|
public class NamespaceFilterTests
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public void Constructor_WithNullNamespaces_ShouldThrowArgumentNullException()
|
public void Constructor_WithNullNamespaces_ShouldThrowArgumentException()
|
||||||
{
|
{
|
||||||
Assert.Throws<ArgumentNullException>(() => new NamespaceFilter(null!));
|
Assert.Throws<ArgumentException>(() => new NamespaceFilter(null!));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@ -151,11 +151,13 @@ public class RollingFileAppenderTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void Flush_ShouldEnsureDataWritten()
|
public void Flush_ShouldEnsureDataWritten()
|
||||||
{
|
{
|
||||||
using var appender = new RollingFileAppender(_testFilePath);
|
using (var appender = new RollingFileAppender(_testFilePath))
|
||||||
var entry = new LogEntry(DateTime.Now, LogLevel.Info, "TestLogger", "Test message", null, null);
|
{
|
||||||
|
var entry = new LogEntry(DateTime.Now, LogLevel.Info, "TestLogger", "Test message", null, null);
|
||||||
|
|
||||||
appender.Append(entry);
|
appender.Append(entry);
|
||||||
appender.Flush();
|
appender.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
Assert.That(File.Exists(_testFilePath), Is.True);
|
Assert.That(File.Exists(_testFilePath), Is.True);
|
||||||
var content = File.ReadAllText(_testFilePath);
|
var content = File.ReadAllText(_testFilePath);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using GFramework.Core.Abstractions.logging;
|
using GFramework.Core.Abstractions.logging;
|
||||||
using GFramework.Core.logging.appenders;
|
using GFramework.Core.logging.appenders;
|
||||||
using GFramework.Core.logging.filters;
|
using GFramework.Core.logging.filters;
|
||||||
@ -16,7 +17,8 @@ public static class LoggingConfigurationLoader
|
|||||||
{
|
{
|
||||||
PropertyNameCaseInsensitive = true,
|
PropertyNameCaseInsensitive = true,
|
||||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||||
AllowTrailingCommas = true
|
AllowTrailingCommas = true,
|
||||||
|
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, allowIntegerValues: true) }
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -129,10 +131,11 @@ public static class LoggingConfigurationLoader
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 可配置的 Logger 工厂
|
/// 可配置的 Logger 工厂
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class ConfigurableLoggerFactory : ILoggerFactory
|
internal sealed class ConfigurableLoggerFactory : ILoggerFactory, IDisposable
|
||||||
{
|
{
|
||||||
private readonly ILogAppender[] _appenders;
|
private readonly ILogAppender[] _appenders;
|
||||||
private readonly LoggingConfiguration _config;
|
private readonly LoggingConfiguration _config;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
public ConfigurableLoggerFactory(LoggingConfiguration config)
|
public ConfigurableLoggerFactory(LoggingConfiguration config)
|
||||||
{
|
{
|
||||||
@ -140,12 +143,36 @@ internal sealed class ConfigurableLoggerFactory : ILoggerFactory
|
|||||||
_appenders = config.Appenders.Select(LoggingConfigurationLoader.CreateAppender).ToArray();
|
_appenders = config.Appenders.Select(LoggingConfigurationLoader.CreateAppender).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var appender in _appenders)
|
||||||
|
{
|
||||||
|
if (appender is IDisposable disposable)
|
||||||
|
{
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
public ILogger GetLogger(string name, LogLevel minLevel = LogLevel.Info)
|
public ILogger GetLogger(string name, LogLevel minLevel = LogLevel.Info)
|
||||||
{
|
{
|
||||||
// 检查是否有特定 Logger 的级别配置
|
// 检查是否有特定 Logger 的级别配置(支持前缀匹配)
|
||||||
var effectiveLevel = _config.LoggerLevels.TryGetValue(name, out var level)
|
var effectiveLevel = _config.MinLevel;
|
||||||
? level
|
|
||||||
: _config.MinLevel;
|
foreach (var kvp in _config.LoggerLevels)
|
||||||
|
{
|
||||||
|
// 精确匹配或前缀匹配(命名空间层级)
|
||||||
|
if (name == kvp.Key || name.StartsWith(kvp.Key + ".", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
effectiveLevel = kvp.Value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 如果没有 Appender,返回简单的 ConsoleLogger
|
// 如果没有 Appender,返回简单的 ConsoleLogger
|
||||||
if (_appenders.Length == 0)
|
if (_appenders.Length == 0)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user