用NetCore + ReactJS 实现一个前后端分离的网站 (5) 日志 - log4net & AOP切面编程
1. 前言
日志始终是跟踪与调试程序的最佳手段,因为调试难以溯及既往,而日志则能忠实地记录下曾经发生过的事情。
2. log4net
这个工具大家再熟悉不过了,这里简单介绍一下。
2.1 添加以下引用
- log4net
- Microsoft.Extensions.Logging.Log4Net.AspNetCore
2.2. 在根目录添加配置文件log4net.config
配置文件
<?xml version="1.0" encoding="utf-8"?>
<log4net>
<appender name="RollingAppender" type="log4net.Appender.RollingFileAppender">
<!--指定日志文件保存的目录-->
<file value="log/"/>
<!--追加日志内容-->
<appendToFile value="true"/>
<!--可以为:Once|Size|Date|Composite-->
<!--Compoosite为Size和Date的组合-->
<rollingStyle value="Composite"/>
<!--设置为true,当前最新日志文件名永远为file字节中的名字-->
<staticLogFileName value="false"/>
<!--当备份文件时,备份文件的名称及后缀名-->
<datePattern value="yyyyMMdd'.txt'"/>
<!--日志最大个数-->
<!--rollingStyle节点为Size时,只能有value个日志-->
<!--rollingStyle节点为Composie时,每天有value个日志-->
<maxSizeRollBackups value="20"/>
<!--可用的单位:KB|MB|GB-->
<maximumFileSize value="5MB"/>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="ALL"/>
<param name="LevelMax" value="FATAL"/>
</filter>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"/>
</layout>
</appender>
<root>
<priority value="ALL"/>
<level value="ALL"/>
<appender-ref ref="RollingAppender"/>
</root>
</log4net>
2.3. 添加服务
Program.cs
#region Log
builder.WebHost.ConfigureLogging((context, loggingBuilder) =>
{
loggingBuilder.AddFilter("System", LogLevel.Warning);
loggingBuilder.AddFilter("Microsoft", LogLevel.Warning);
var path = Path.Combine(Environment.CurrentDirectory, "log4net.config");
loggingBuilder.AddLog4Net(path);
});
#endregion
2.4. 通过构造函数注入
NovelController.cs
// 通过构造函数注入依赖
public NovelController(INovelService novelService, ILogger<NovelController> logger)
{
_novelService = novelService;
_logger = logger;
}
// 其他代码
[HttpGet("{id}")]
[ApiAuthorize]
public async Task<ResponseModel<Novel>> Get(int id)
{
_logger.LogInformation($"Get novel, id = {id}, current user: {UserID}");
var novel = await _novelService.SelectAsync(x => x.ID == id);
return new ResponseModel<Novel>().Ok(novel);
}
// 其他代码
2.5. 输出到日志文件
AOP切面编程
这个概念其实和中间件很像,只不过中间件
处理的是请求
,而这个是通过过滤器(Filter
)和拦截器(Interceptor
)为服务层服务。
3. 过滤器
3.1. 添加实现
LogFilter.cs
using Microsoft.AspNetCore.Mvc.Filters;
using System.Text.Json;
namespace NovelTogether.Core.API.Filters
{
public class LogFilter : IActionFilter
{
private readonly ILogger<LogFilter> _logger;
public LogFilter(ILogger<LogFilter> logger)
{
_logger = logger;
}
public void OnActionExecuted(ActionExecutedContext context)
{
var result = "void";
if(context.Result != null)
result = JsonSerializer.Serialize(((Microsoft.AspNetCore.Mvc.ObjectResult)context.Result).Value);
_logger.LogInformation($"Action Executed\tID: {context.ActionDescriptor.Id}\tResult: {result} ");
}
public void OnActionExecuting(ActionExecutingContext context)
{
var functionName = ((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor).ControllerTypeInfo.FullName;
_logger.LogInformation($"Action Executing\tID: {context.ActionDescriptor.Id}\tAction: {functionName}\tParameters: {JsonSerializer.Serialize(context.ActionArguments)}");
}
}
}
3.2. 添加服务
服务
builder.Services.AddControllers(option =>
{
option.Filters.Add(typeof(LogFilter));
});
3.3. 输出到日志文件
4. 拦截器
相比过滤器作用在Controller上,拦截器是作用在Service层或者说接口层。
4.1. 添加实现
AsyncInterceptorBase.cs
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Castle.DynamicProxy;
namespace NovelTogether.Core.API.AOP
{
//inspired by : https://stackoverflow.com/a/39784559/7726468
public abstract class AsyncInterceptorBase : IInterceptor
{
public AsyncInterceptorBase()
{
}
public void Intercept(IInvocation invocation)
{
BeforeProceed(invocation);
invocation.Proceed();
if (IsAsyncMethod(invocation.MethodInvocationTarget))
{
// 关键实现语句
invocation.ReturnValue = InterceptAsync((dynamic)invocation.ReturnValue, invocation);
}
else
{
AfterProceedSync(invocation);
}
}
private bool CheckMethodReturnTypeIsTaskType(MethodInfo method)
{
var methodReturnType = method.ReturnType;
if (methodReturnType.IsGenericType)
{
if (methodReturnType.GetGenericTypeDefinition() == typeof(Task<>) ||
methodReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
return true;
}
else
{
if (methodReturnType == typeof(Task) ||
methodReturnType == typeof(ValueTask))
return true;
}
return false;
}
private bool IsAsyncMethod(MethodInfo method)
{
bool isDefAsync = Attribute.IsDefined(method, typeof(AsyncStateMachineAttribute), false);
bool isTaskType = CheckMethodReturnTypeIsTaskType(method);
bool isAsync = isDefAsync && isTaskType;
return isAsync;
}
protected object ProceedAsyncResult { get; set; }
private async Task InterceptAsync(Task task, IInvocation invocation)
{
await task.ConfigureAwait(false);
await AfterProceedAsync(invocation);
}
private async Task<TResult> InterceptAsync<TResult>(Task<TResult> task, IInvocation invocation)
{
ProceedAsyncResult = await task.ConfigureAwait(false);
await AfterProceedAsync(invocation, ProceedAsyncResult);
return (TResult)ProceedAsyncResult;
}
private async ValueTask InterceptAsync(ValueTask task, IInvocation invocation)
{
await task.ConfigureAwait(false);
await AfterProceedAsync(invocation, false);
}
private async ValueTask<TResult> InterceptAsync<TResult>(ValueTask<TResult> task, IInvocation invocation)
{
ProceedAsyncResult = await task.ConfigureAwait(false);
await AfterProceedAsync(invocation, true);
return (TResult)ProceedAsyncResult;
}
protected virtual void BeforeProceed(IInvocation invocation) { }
protected virtual void AfterProceedSync(IInvocation invocation) { }
protected virtual Task AfterProceedAsync(IInvocation invocation)
{
return Task.CompletedTask;
}
protected virtual Task AfterProceedAsync<TResult>(IInvocation invocation, TResult returnValue)
{
return Task.CompletedTask;
}
}
}
LogInterceptor.cs
using Castle.DynamicProxy;
using System.Text.Json;
namespace NovelTogether.Core.API.AOP
{
public class LogInterceptor : AsyncInterceptorBase
{
private readonly ILogger<LogInterceptor> _logger;
public LogInterceptor(ILogger<LogInterceptor> logger)
{
_logger = logger;
}
protected override void BeforeProceed(IInvocation invocation)
{
// 执行方法前
var methodPreExecuting = $"Method Pre Executing\r\nMethod: {invocation.Method.Name}\r\nParameters: {string.Join(", ", invocation.Arguments.Select(x => (x ?? "").ToString()).ToArray())}";
_logger.LogInformation(methodPreExecuting);
}
protected override void AfterProceedSync(IInvocation invocation)
{
// 执行方法后
var methodAfterExecuting = $"Method After Executing\r\nResult:{invocation.ReturnValue}";
_logger.LogInformation(methodAfterExecuting);
}
protected override Task AfterProceedAsync<TResult>(IInvocation invocation, TResult returnValue)
{
// 执行方法后
var methodAfterExecuting = $"Method After Executing\r\nResult:{JsonSerializer.Serialize(returnValue)}";
_logger.LogInformation(methodAfterExecuting);
return Task.CompletedTask;
}
}
}
4.2. 添加服务
Program.cs
// 其他代码
containerBuilder.RegisterType<LogInterceptor>();
// 其他代码
var assembly = Assembly.LoadFile(assemblyPath);
containerBuilder.RegisterAssemblyTypes(assembly)
.AsImplementedInterfaces()
.InstancePerLifetimeScope()
.EnableInterfaceInterceptors().InterceptedBy(typeof(LogInterceptor));
// 其他代码
4.3. 输出到日志文件
从图中可以看到Filter->Controller->Interceptor这样一个顺序。
这里还有个问题没解决,就是因为服务层的参数都是Expression
类型,而不是普通的int或者object类型,所以取出来的参数看着很奇怪。
5. 总结
日志就这样搞定了,同样的,全局的exception也可以用Filter来处理,回头再把这个功能加上去。
标签:log4net,IInvocation,Task,return,NetCore,ReactJS,invocation,logger,public From: https://www.cnblogs.com/wang-yi-yi/p/16951754.html