LiteOrm 基于 Microsoft.Extensions.Logging 输出运行日志。只要宿主应用已经配置了日志提供程序(如 Console、Debug、Serilog),LiteOrm 的服务调用日志、异常日志和慢查询日志就会进入同一套日志管道。
默认日志能力主要集中在 Service 层拦截器:
其中,IEntityService / IEntityViewService 这些通用接口已经默认标记了:
[ServiceLog(LogLevel = ServiceLogLevel.Debug)]
public interface IEntityServiceAsync<T> : IEntityServiceAsync
{
}
这意味着你在业务里直接复用 EntityService / EntityViewService 时,通常已经具备基础服务日志。
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Host.RegisterLiteOrm();
var host = Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Information);
})
.RegisterLiteOrm()
.Build();
RegisterLiteOrm(options => ...) 中的 LoggerFactorybuilder.Host.RegisterLiteOrm(options =>
{
options.LoggerFactory = LoggerFactory.Create(logging =>
{
logging.AddConsole();
logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Information);
});
});
这里的 options.LoggerFactory 主要用于 框架注册与程序集扫描阶段 的日志输出;真正的服务调用日志仍然走宿主 DI 中的 ILoggerFactory。
ServiceLog 的用法[ServiceLog] 用来控制服务方法调用日志的级别和输出内容,可标记在:
它有两个关键参数:
LogLevel:对应 ServiceLogLevel.Trace / Debug / Information / Warning / Error / Critical / NoneLogFormat:对应 LogFormat.None / Args / ReturnValue / Full[ServiceLog]
public interface IUserService
{
Task<User?> GetByIdAsync(int id);
}
默认等价于:
[ServiceLog(LogLevel = ServiceLogLevel.Information, LogFormat = LogFormat.Full)]
[ServiceLog(LogLevel = ServiceLogLevel.Debug, LogFormat = LogFormat.Args)]
public interface IOrderService
{
Task<Order?> GetAsync(int id);
Task<IReadOnlyList<Order>> SearchAsync(string keyword);
}
这个写法适合开发期排查“调用了什么、参数是什么”,但又不想输出完整返回值。
public interface IAccountService
{
[ServiceLog(LogLevel = ServiceLogLevel.Warning, LogFormat = LogFormat.Full)]
Task<bool> TransferAsync(long fromId, long toId, decimal amount);
[ServiceLog(LogLevel = ServiceLogLevel.None)]
Task<string> GetHealthAsync();
}
TransferAsync 会以更高等级输出,便于关键业务审计。GetHealthAsync 可直接关闭服务日志,避免高频噪声。LogFormat 选择建议| 配置 | 适用场景 |
|---|---|
None |
彻底关闭该方法的调用前后日志 |
Args |
只关心入参,不关心返回体 |
ReturnValue |
只关心执行结果 |
Full |
同时看参数和返回值,适合联调和问题定位 |
Log 特性的两种常见应用[Log] / [Log(false)] 主要用来控制哪些数据可以进入日志。
Service 拦截器会读取方法参数上的 LogAttribute。如果显式写成 [Log(false)],日志里该参数会被替换为 *。
public interface IAuthService
{
Task<LoginResult> LoginAsync(string userName, [Log(false)] string password);
}
这样记录服务调用时,不会把明文密码直接写进日志。
CancellationToken在框架中默认不会展开记录,即使不写[Log(false)]也会被排除。
ObjectBase 实现了 ILogable。当服务日志记录实体对象时,会优先调用 ToLog();而 ObjectBase.ToLog() 会读取属性上的 LogAttribute。
[Table("Users")]
public class User : ObjectBase
{
[Column("Id", IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
[Column("UserName")]
public string UserName { get; set; } = string.Empty;
[Column("PasswordHash")]
[Log(false)]
public string PasswordHash { get; set; } = string.Empty;
[Column("PasswordSalt")]
[Log(false)]
public string PasswordSalt { get; set; } = string.Empty;
}
这样在记录 User 对象时,PasswordHash、PasswordSalt 不会出现在日志文本里。
如果默认的 ObjectBase.ToLog() 还不够,可以让类型自己实现 ILogable:
public class PaymentRequest : ILogable
{
public string CardNo { get; set; } = string.Empty;
public decimal Amount { get; set; }
public string ToLog()
{
var tail = CardNo.Length >= 4 ? CardNo[^4..] : CardNo;
return $"CardNo:****{tail}, Amount:{Amount}";
}
}
这类对象被写入服务日志时,会优先使用 ToLog() 的结果。
ExceptionHook:在服务异常时追加处理逻辑[ExceptionHook] 适合放在服务方法、类或接口上,为 Service 拦截器补充“异常发生时还要做什么”的逻辑,例如:
它对应的 hook 类型必须实现 IServiceExceptionHook:
public class OrderExceptionHook : IServiceExceptionHook
{
public void OnException(ServiceExceptionContext context)
{
// 可读取异常、方法名、参数、SQL 栈等上下文
}
}
然后在服务上声明:
[ExceptionHook(typeof(OrderExceptionHook), Mode = ServiceExceptionHookMode.Notify)]
public interface IOrderService
{
Task SubmitAsync(long id);
}
当服务方法抛出异常时,ServiceInvokeInterceptor 会按下面顺序处理:
ServiceExceptionContextExceptionHookServiceInvokeInterceptor.ExceptionHandling 事件这意味着 ExceptionHook 更适合做局部、贴近业务方法的异常扩展;而 ExceptionHandling 事件更适合做全局统一兜底。
Notify 和 Handle 的区别ExceptionHookAttribute.Mode 有两种模式:
| 模式 | 含义 |
|---|---|
Notify |
只通知,不允许把异常标记为已处理 |
Handle |
允许调用 context.Handle(...),把异常转成正常返回结果 |
Notify:只观察,不吞异常[ExceptionHook(typeof(NotifyOnlyHook), Mode = ServiceExceptionHookMode.Notify)]
public void ThrowWithNotifyHook()
{
throw new InvalidOperationException("notify");
}
这种模式适合做告警、埋点、补充日志。异常仍会继续抛出。
如果
Notify模式下仍调用了context.Handle(...),框架会抛出InvalidOperationException,防止“配置成只通知,实际却吞掉异常”的歧义行为。
Handle:显式把异常转成结果[ExceptionHook(typeof(HandleHook), Mode = ServiceExceptionHookMode.Handle)]
public int GetStatus()
{
throw new InvalidOperationException("handle");
}
public class HandleHook : IServiceExceptionHook
{
public void OnException(ServiceExceptionContext context)
{
context.Handle(123);
}
}
当 hook 调用 context.Handle(123) 后,拦截器会把这次调用视为“已处理”,并直接返回 123。
异步方法同样适用;对 Task<T>,返回值会按 T 的类型构造。
ServiceExceptionContext 里能拿到什么OnException(ServiceExceptionContext context) 中通常会用到这些信息:
context.Exception:原始异常context.ServiceName / context.MethodName:当前服务与方法context.Arguments / context.LogArguments:原始参数与日志参数context.SessionID:当前会话 IDcontext.SqlStack:当前 SQL 栈因此它既能做日志增强,也能做“按异常类型决定是否降级返回”的判断。
ServiceInvokeInterceptor 通过 DI 解析 hook 类型,因此 ExceptionHook 对应的实现类应注册到容器中。仓库里的常见写法是:
[AutoRegister(Lifetime.Scoped, typeof(IServiceExceptionHook))]
public class OrderExceptionHook : IServiceExceptionHook
{
public void OnException(ServiceExceptionContext context)
{
}
}
如果 hook 没有注册,框架会尝试按类型创建实例;但只要 hook 依赖其他服务,仍建议显式走 DI 注册。
ServiceInvokeInterceptor 还提供了两个常用静态参数:
ServiceInvokeInterceptor.SlowQueryThreshold = TimeSpan.FromSeconds(1);
ServiceInvokeInterceptor.MaxExpandedLogLength = 20;
SlowQueryThreshold:超过阈值会额外输出 <Slow> 和 <SlowSQL> 日志。MaxExpandedLogLength:控制集合参数/结果的展开上限,避免日志过大。ServiceLog,把日志策略放在 Service 边界,而不是控制器里零散打印。[Log(false)] 或自定义 ILogable 脱敏。Debug + Full,生产环境更建议按业务重要性收敛到 Information 或 Warning。ExceptionHook 适合做方法级告警、补偿和异常转结果;跨服务统一策略更适合放在全局 ExceptionHandling 事件。ServiceLogLevel.None 降噪。