refactor(cqrs): 移除旧日志行为并添加CQRS运行时模块

- 删除 LoggingBehavior 类及其相关实现
- 新增 CqrsRuntimeModule 用于注册CQRS运行时组件
- 添加 ArchitectureComponentRegistryBehaviorTests 测试组件注册行为
- 添加 ArchitectureContextTests 测试架构上下文功能
- 完善CQRS运行时的并发安全性和生命周期管理
This commit is contained in:
GeWuYou 2026-04-15 21:40:58 +08:00
parent e36c80229a
commit 1c7558aeb8
19 changed files with 302 additions and 64 deletions

View File

@ -0,0 +1,250 @@
namespace GFramework.Core.Abstractions.Logging;
/// <summary>
/// 提供全局日志工厂访问入口。
/// </summary>
/// <remarks>
/// 该类型位于抽象层,是为了让上层模块可以在不依赖 <c>GFramework.Core</c> 实现程序集的前提下
/// 获取日志记录器。默认 provider 会优先通过反射解析 <c>GFramework.Core</c> 中的控制台实现,
/// 若宿主未加载该程序集,则退回到静默 provider避免抽象层形成实现层循环依赖。
/// </remarks>
public static class LoggerFactoryResolver
{
private const string DefaultProviderTypeName =
"GFramework.Core.Logging.ConsoleLoggerFactoryProvider, GFramework.Core";
/// <summary>
/// 获取或设置当前日志工厂提供程序。
/// </summary>
/// <exception cref="ArgumentNullException">
/// 当赋值为 <see langword="null" /> 时抛出。
/// </exception>
public static ILoggerFactoryProvider Provider
{
get => field ??= CreateDefaultProvider();
set => field = value ?? throw new ArgumentNullException(nameof(value));
}
/// <summary>
/// 获取或设置新创建日志记录器的最小日志级别。
/// </summary>
/// <remarks>
/// 该属性直接代理到当前 <see cref="Provider" />,确保调用方调整级别后立即影响后续创建的日志器。
/// </remarks>
public static LogLevel MinLevel
{
get => Provider.MinLevel;
set => Provider.MinLevel = value;
}
private static ILoggerFactoryProvider CreateDefaultProvider()
{
if (Type.GetType(DefaultProviderTypeName, throwOnError: false) is { } providerType &&
Activator.CreateInstance(providerType) is ILoggerFactoryProvider provider)
{
provider.MinLevel = LogLevel.Info;
return provider;
}
return new SilentLoggerFactoryProvider();
}
/// <summary>
/// 当宿主未提供默认日志实现时使用的静默 provider。
/// </summary>
private sealed class SilentLoggerFactoryProvider : ILoggerFactoryProvider
{
public LogLevel MinLevel { get; set; } = LogLevel.Info;
public ILogger CreateLogger(string name)
{
return new SilentLogger(name);
}
}
/// <summary>
/// 默认日志实现不可用时的 no-op 日志器。
/// </summary>
private sealed class SilentLogger(string name) : ILogger
{
public string Name()
{
return name;
}
public bool IsTraceEnabled()
{
return false;
}
public bool IsDebugEnabled()
{
return false;
}
public bool IsInfoEnabled()
{
return false;
}
public bool IsWarnEnabled()
{
return false;
}
public bool IsErrorEnabled()
{
return false;
}
public bool IsFatalEnabled()
{
return false;
}
public bool IsEnabledForLevel(LogLevel level)
{
return false;
}
public void Trace(string msg)
{
}
public void Trace(string format, object arg)
{
}
public void Trace(string format, object arg1, object arg2)
{
}
public void Trace(string format, params object[] arguments)
{
}
public void Trace(string msg, Exception t)
{
}
public void Debug(string msg)
{
}
public void Debug(string format, object arg)
{
}
public void Debug(string format, object arg1, object arg2)
{
}
public void Debug(string format, params object[] arguments)
{
}
public void Debug(string msg, Exception t)
{
}
public void Info(string msg)
{
}
public void Info(string format, object arg)
{
}
public void Info(string format, object arg1, object arg2)
{
}
public void Info(string format, params object[] arguments)
{
}
public void Info(string msg, Exception t)
{
}
public void Warn(string msg)
{
}
public void Warn(string format, object arg)
{
}
public void Warn(string format, object arg1, object arg2)
{
}
public void Warn(string format, params object[] arguments)
{
}
public void Warn(string msg, Exception t)
{
}
public void Error(string msg)
{
}
public void Error(string format, object arg)
{
}
public void Error(string format, object arg1, object arg2)
{
}
public void Error(string format, params object[] arguments)
{
}
public void Error(string msg, Exception t)
{
}
public void Fatal(string msg)
{
}
public void Fatal(string format, object arg)
{
}
public void Fatal(string format, object arg1, object arg2)
{
}
public void Fatal(string format, params object[] arguments)
{
}
public void Fatal(string msg, Exception t)
{
}
public void Log(LogLevel level, string message)
{
}
public void Log(LogLevel level, string format, object arg)
{
}
public void Log(LogLevel level, string format, object arg1, object arg2)
{
}
public void Log(LogLevel level, string format, params object[] arguments)
{
}
public void Log(LogLevel level, string message, Exception exception)
{
}
}
}

View File

@ -1,11 +1,11 @@
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Enums;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility;
using GFramework.Core.Architectures;
using GFramework.Core.Logging;
using Microsoft.Extensions.DependencyInjection;
namespace GFramework.Core.Tests.Architectures;
@ -714,4 +714,4 @@ public class ArchitectureComponentRegistryBehaviorTests
return _context;
}
}
}
}

View File

@ -5,6 +5,7 @@ using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Enums;
using GFramework.Core.Abstractions.Environment;
using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Query;
using GFramework.Core.Abstractions.Systems;

View File

@ -1,9 +1,9 @@
using GFramework.Core.Abstractions.Enums;
using GFramework.Core.Abstractions.Events;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Architectures;
using GFramework.Core.Environment;
using GFramework.Core.Logging;
using Microsoft.Extensions.DependencyInjection;
namespace GFramework.Core.Tests.Architectures;
@ -185,4 +185,4 @@ public class ArchitectureInitializationPipelineTests
private sealed class BootstrapMarker
{
}
}
}

View File

@ -2,12 +2,12 @@ using System.Reflection;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Enums;
using GFramework.Core.Abstractions.Lifecycle;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility;
using GFramework.Core.Architectures;
using GFramework.Core.Logging;
using Microsoft.Extensions.DependencyInjection;
namespace GFramework.Core.Tests.Architectures;
@ -460,4 +460,4 @@ public class ArchitectureLifecycleBehaviorTests
return _context;
}
}
}
}

View File

@ -1,4 +1,5 @@
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Abstractions.Utility;
using GFramework.Core.Architectures;
using GFramework.Core.Logging;

View File

@ -1,5 +1,6 @@
using System.Reflection;
using GFramework.Core.Abstractions.Bases;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility;
@ -244,4 +245,4 @@ public class PriorityTestUtilityC : IPriorityTestUtility, IPrioritized
public int Priority => 30;
}
#endregion
#endregion

View File

@ -1,6 +1,7 @@
using System.Reflection;
using GFramework.Core.Abstractions.Bases;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using GFramework.Core.Tests.Cqrs;

View File

@ -1,5 +1,6 @@
using System.Reflection;
using GFramework.Core.Abstractions.Enums;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Abstractions.State;
using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Architectures;
@ -373,4 +374,4 @@ public class TestStateV5_2 : IState
}
}
#endregion
#endregion

View File

@ -1,26 +0,0 @@
using GFramework.Core.Abstractions.Logging;
namespace GFramework.Core.Logging;
/// <summary>
/// 日志工厂提供程序解析器,用于管理和提供日志工厂提供程序实例
/// </summary>
public static class LoggerFactoryResolver
{
/// <summary>
/// 获取或设置当前的日志工厂提供程序
/// </summary>
/// <value>
/// 日志工厂提供程序实例,默认为控制台日志工厂提供程序
/// </value>
public static ILoggerFactoryProvider Provider { get; set; }
= new ConsoleLoggerFactoryProvider();
/// <summary>
/// 获取或设置日志记录的最小级别
/// </summary>
/// <value>
/// 日志级别枚举值默认为Info级别
/// </value>
public static LogLevel MinLevel { get; set; } = LogLevel.Info;
}

View File

@ -0,0 +1,4 @@
using System.Runtime.CompilerServices;
using GFramework.Core.Abstractions.Logging;
[assembly: TypeForwardedTo(typeof(LoggerFactoryResolver))]

View File

@ -1,7 +1,7 @@
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Logging;
using GFramework.Core.Abstractions.Logging;
using GFramework.Cqrs;
using GFramework.Cqrs.Abstractions.Cqrs;

View File

@ -20,6 +20,7 @@ global using System.Reflection;
global using System.Runtime.CompilerServices;
global using System.Threading;
global using System.Threading.Tasks;
global using System.Diagnostics;
global using GFramework.Tests.Common;
global using Microsoft.Extensions.DependencyInjection;
global using Moq;

View File

@ -1,3 +1,4 @@
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Architectures;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;

View File

@ -1,4 +1,5 @@
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Architectures;
using GFramework.Core.Command;
using GFramework.Core.Ioc;

View File

@ -1,5 +1,6 @@
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Events;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Architectures;
using GFramework.Core.Command;
using GFramework.Core.Environment;

View File

@ -11,19 +11,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using System.Diagnostics;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Logging;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Core.Cqrs.Behaviors;
namespace GFramework.Cqrs.Cqrs.Behaviors;
/// <summary>
/// 日志记录行为类用于在CQRS管道中记录请求处理的日志信息
/// 实现IPipelineBehavior接口为请求处理提供日志记录功能
/// 在 CQRS 请求管道中记录请求开始、完成、取消与失败日志。
/// </summary>
/// <typeparam name="TRequest">请求类型必须实现IRequest接口</typeparam>
/// <typeparam name="TResponse">响应类型</typeparam>
/// <typeparam name="TRequest">请求类型。</typeparam>
/// <typeparam name="TResponse">响应类型。</typeparam>
/// <remarks>
/// 该行为保留在 <c>GFramework.Core.Cqrs.Behaviors</c> 命名空间以兼容现有调用点,
/// 但实现已迁入 <c>GFramework.Cqrs</c> 程序集,避免继续由 <c>GFramework.Core</c> 承载 CQRS runtime 细节。
/// </remarks>
public sealed class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
@ -31,13 +32,12 @@ public sealed class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRe
LoggerFactoryResolver.Provider.CreateLogger(nameof(LoggingBehavior<TRequest, TResponse>));
/// <summary>
/// 处理请求并记录日志
/// 在请求处理前后记录调试信息,处理异常时记录错误日志
/// 执行日志包装后的下一段请求处理逻辑。
/// </summary>
/// <param name="message">要处理的请求消息</param>
/// <param name="next">下一个处理委托,用于继续管道执行</param>
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
/// <returns>处理结果的ValueTask</returns>
/// <param name="message">当前请求消息。</param>
/// <param name="next">后续处理委托。</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>请求处理结果。</returns>
public async ValueTask<TResponse> Handle(
TRequest message,
MessageHandlerDelegate<TRequest, TResponse> next,

View File

@ -11,33 +11,34 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using System.Diagnostics;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Logging;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Core.Cqrs.Behaviors;
namespace GFramework.Cqrs.Cqrs.Behaviors;
/// <summary>
/// 性能监控行为类用于监控CQRS请求的执行时间
/// 实现IPipelineBehavior接口检测并记录执行时间过长的请求
/// 在 CQRS 请求管道中监控处理耗时,并对长耗时请求发出告警。
/// </summary>
/// <typeparam name="TRequest">请求类型必须实现IRequest接口</typeparam>
/// <typeparam name="TResponse">响应类型</typeparam>
/// <typeparam name="TRequest">请求类型。</typeparam>
/// <typeparam name="TResponse">响应类型。</typeparam>
/// <remarks>
/// 该行为保留现有公开命名空间以维持消费端兼容性,但实现已迁入 <c>GFramework.Cqrs</c> 程序集。
/// </remarks>
public sealed class PerformanceBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private const double SlowRequestThresholdMilliseconds = 500;
private readonly ILogger _logger =
LoggerFactoryResolver.Provider.CreateLogger(nameof(PerformanceBehavior<TRequest, TResponse>));
/// <summary>
/// 处理请求并监控执行时间
/// 使用Stopwatch测量请求处理耗时超过500ms时记录警告日志
/// 统计当前请求处理耗时,并在超阈值时记录警告日志。
/// </summary>
/// <param name="message">要处理的请求消息</param>
/// <param name="next">下一个处理委托,用于继续管道执行</param>
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
/// <returns>处理结果的ValueTask</returns>
/// <param name="message">当前请求消息。</param>
/// <param name="next">后续处理委托。</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>请求处理结果。</returns>
public async ValueTask<TResponse> Handle(
TRequest message,
MessageHandlerDelegate<TRequest, TResponse> next,
@ -53,11 +54,10 @@ public sealed class PerformanceBehavior<TRequest, TResponse> : IPipelineBehavior
{
var elapsed = Stopwatch.GetElapsedTime(start);
if (elapsed.TotalMilliseconds > 500)
if (elapsed.TotalMilliseconds > SlowRequestThresholdMilliseconds)
{
var requestName = typeof(TRequest).Name;
_logger.Warn(
$"Long Running Request: {requestName} ({elapsed.TotalMilliseconds:F2} ms)");
_logger.Warn($"Long Running Request: {requestName} ({elapsed.TotalMilliseconds:F2} ms)");
}
}
}

View File

@ -4,3 +4,4 @@ global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;
global using Microsoft.Extensions.DependencyInjection;
global using System.Diagnostics;