前言:这个功能主要是用来记录WebApi的所有调用记录,会把所有的传入参数以及得到的结果保存下来,方便问题跟踪(跟前面写的Winform操作日志记录遥相呼应,对系统端到端实现完整记录)。
为了简单易用,这里主要采用中间件的方式来实现。同时考虑了某些情况下并不需要记录,所以加入了忽略某些Action的方法(比如一些需要不间断循环调用的接口),不过我是直接写到中间件里面了。可以考虑其他方式实现高可用性。.
-
首先创建一个中间件,并定义以下字段。其中
ignoreActions
就是不需要记录的一些函数名;_serviceScopeFactory
是为了拿到依赖注入的对象实现数据存储的。
public class WebApiLog
{
readonly RequestDelegate _next;
readonly IServiceScopeFactory _serviceScopeFactory;
readonly List<string> ignoreActions = new List<string>{
"Index1","Default/Index2"
};
public WebApiLog(RequestDelegate next, IServiceScopeFactory serviceScopeFactory)
{
_next = next;
_serviceScopeFactory = serviceScopeFactory;
}
-
传入参数的记录,由于WebApi的传入类型可以是多样性的,如:FromForm、FromBody等,所以对
context.Request
进行了判断,并采用了不同的数据组合方式
public async Task InvokeAsync(HttpContext context)
{
if (!ignoreActions.Exists(s=>context.Request.Path.ToString().Contains(s)))
{
//首先记录一些基本的参数,IP,Action,Time等
TApilog Apilog = new TApilog();
Apilog.Ip = Convert.ToString(context.Connection.RemoteIpAddress);
Apilog.Action = context.Request.Path;
Apilog.Intime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
using var scope = _serviceScopeFactory.CreateScope();
string token = context.Request.Headers["token"];
if (!string.IsNullOrEmpty(token))
{
var tokenService = scope.ServiceProvider.GetRequiredService<ITokenService>();
Apilog.Useraccount = tokenService.ParseToken(context)?.UserAccount;
}
#region 传入参数解析
StringBuilder inarg = new StringBuilder();
if (context.Request.HasFormContentType)
{
foreach (var item in context.Request.Form)
{
inarg.AppendLine(item.Key + ":" + item.Value);
}
}
else if (context.Request.Query.Count > 0)
{
foreach (var item in context.Request.Query)
{
inarg.AppendLine(item.Key + ":" + item.Value);
}
}
else
{
context.Request.EnableBuffering();
StreamReader streamReader = new StreamReader(context.Request.Body);
inarg.AppendLine(await streamReader.ReadToEndAsync());
context.Request.Body.Seek(0, SeekOrigin.Begin);
}
Apilog.Input = inarg.ToString();
#endregion
-
#region 传出参数解析
var originalBodyStream = context.Response.Body;
using (var responseBody = new MemoryStream())
{
context.Response.Body = responseBody;
await _next(context);
Apilog.Output = await GetResponse(context.Response);
await responseBody.CopyToAsync(originalBodyStream);
}
#endregion
Apilog.Outtime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
var _tApilogServices = scope.ServiceProvider.GetRequiredService<ITApilogServices>();
try {await _tApilogServices.InsertAsync(Apilog); } catch { } }
else
{
await _next(context);
}
}
public async Task<string> GetResponse(HttpResponse response)
{
response.Body.Seek(0, SeekOrigin.Begin);
var text = await new StreamReader(response.Body).ReadToEndAsync();
response.Body.Seek(0, SeekOrigin.Begin);
return text;
}
-
//日志记录
app.UseMiddleware<WebApiLog>();
-
结束。最后补充下,因为这里是记录到数据库的,所以字段长度在设计的时候要足够,同时因为这个表查询频率不高,可以不建任何索引(这个表空间的增长速度会非常快,所以个人认为没必要增加开销)