Add request tracker. it also track users activity.

This commit is contained in:
Фролов 2021-11-10 14:23:53 +05:00
parent 170693f445
commit bfb76b9dc0
8 changed files with 284 additions and 2 deletions

View File

@ -0,0 +1,19 @@
using System;
namespace AsbCloudApp.Data
{
public class RequestLogDto
{
public string UserLogin { get; set; }
public int UserId { get; set; }
public string UserIp { get; set; }
public string RequestMethod { get; set; }
public string RequestPath { get; set; }
public string Referer { get; set; }
public long ElapsedMilliseconds { get; set; }
public int Status { get; set; }
public string ExceptionMessage { get; set; }
public DateTime Date { get; set; }
public string ExceptionStack { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AsbCloudApp.Data
{
public class RequestLogUserDto
{
public int Id { get; set; }
public string Login { get; set; }
public string Ip { get; set; }
public long ElapsedMs { get; set; }
public DateTime LastDate { get; set; }
public long Requests { get; set; }
public long Errors { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using AsbCloudApp.Data;
using System;
using System.Collections.Generic;
namespace AsbCloudApp.Services
{
public interface IRequerstTrackerService
{
void RegisterRequest(RequestLogDto requestLog);
void RegisterRequestError(RequestLogDto requestLog, Exception ex);
IEnumerable<RequestLogDto> GetAll(int take = -1);
IEnumerable<RequestLogDto> GetFast(int take = -1);
IEnumerable<RequestLogDto> GetSlow(int take = -1);
IEnumerable<RequestLogDto> GetError(int take = -1);
IEnumerable<RequestLogUserDto> GetUsersStat(int take = -1);
}
}

View File

@ -19,6 +19,7 @@ namespace AsbCloudInfrastructure
options.UseNpgsql(configuration.GetConnectionString("DefaultConnection")), ServiceLifetime.Scoped); options.UseNpgsql(configuration.GetConnectionString("DefaultConnection")), ServiceLifetime.Scoped);
services.AddScoped<IAsbCloudDbContext>(provider => provider.GetService<AsbCloudDbContext>()); services.AddScoped<IAsbCloudDbContext>(provider => provider.GetService<AsbCloudDbContext>());
services.AddScoped<IFileShareService, GoogleDriveService>();
services.AddHostedService<ReportsBackgroundService>(); services.AddHostedService<ReportsBackgroundService>();
services.AddHostedService<TelemetryAnalyticsBackgroundService>(); services.AddHostedService<TelemetryAnalyticsBackgroundService>();
@ -26,6 +27,7 @@ namespace AsbCloudInfrastructure
services.AddSingleton(new CacheDb()); services.AddSingleton(new CacheDb());
services.AddSingleton<ITelemetryTracker, TelemetryTracker>(); services.AddSingleton<ITelemetryTracker, TelemetryTracker>();
services.AddSingleton<IReportsBackgroundQueue, ReportsBackgroundQueue>(); services.AddSingleton<IReportsBackgroundQueue, ReportsBackgroundQueue>();
services.AddSingleton<IRequerstTrackerService, RequerstTrackerService>();
services.AddTransient<IAuthService, AuthService>(); services.AddTransient<IAuthService, AuthService>();
services.AddTransient<IWellService, WellService>(); services.AddTransient<IWellService, WellService>();
@ -45,7 +47,6 @@ namespace AsbCloudInfrastructure
services.AddTransient<IDrillingProgramService, DrillingProgramService>(); services.AddTransient<IDrillingProgramService, DrillingProgramService>();
services.AddTransient<IDrillParamsService, DrillParamsService>(); services.AddTransient<IDrillParamsService, DrillParamsService>();
services.AddTransient<IDrillFlowChartService, DrillFlowChartService>(); services.AddTransient<IDrillFlowChartService, DrillFlowChartService>();
services.AddTransient<IFileShareService, GoogleDriveService>();
// admin crud services: // admin crud services:
services.AddTransient<ICrudService<DepositDto>, CrudServiceBase<DepositDto, Deposit>>(); services.AddTransient<ICrudService<DepositDto>, CrudServiceBase<DepositDto, Deposit>>();

View File

@ -0,0 +1,133 @@
using AsbCloudApp.Data;
using AsbCloudApp.Services;
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services
{
public class RequerstTrackerService : IRequerstTrackerService
{
const int fastRequestsCount = 1000;
const int slowRequestsCount = 1000;
const int errorRequestsCount = 1000;
const int span = 100;
const int fastLimitMs = 500;
static readonly char[] stackTraceSeparators = "\r\n".ToCharArray();
ConcurrentQueue<RequestLogDto> fastRequests = new ConcurrentQueue<RequestLogDto>();
ConcurrentQueue<RequestLogDto> slowRequests = new ConcurrentQueue<RequestLogDto>();
ConcurrentQueue<RequestLogDto> errorRequests = new ConcurrentQueue<RequestLogDto>();
ConcurrentDictionary<string, RequestLogUserDto> users = new ConcurrentDictionary<string, RequestLogUserDto>();
private IEnumerable<RequestLogDto> Get(IEnumerable<RequestLogDto> list, int take = -1)
{
IEnumerable<RequestLogDto> orderedlist = list.OrderByDescending(r => r.Date);
if (take > 0)
orderedlist = orderedlist.Take(take);
return orderedlist;
}
public IEnumerable<RequestLogUserDto> GetUsersStat(int take = -1)
{
IEnumerable<RequestLogUserDto> result = users.Values.OrderByDescending(u => u.LastDate);
if (take > 0)
result = result.Take(take);
return result;
}
public IEnumerable<RequestLogDto> GetAll(int take = -1)
{
var result = fastRequests
.Union(slowRequests)
.Union(errorRequests);
return Get(result, take);
}
public IEnumerable<RequestLogDto> GetFast(int take = -1)
=> Get(fastRequests, take);
public IEnumerable<RequestLogDto> GetSlow(int take = -1)
=> Get(slowRequests, take);
public IEnumerable<RequestLogDto> GetError(int take = -1)
=> Get(errorRequests, take);
public void RegisterRequest(RequestLogDto requestLog)
{
if (requestLog.Status < 200)
return;
requestLog.Date = DateTime.Now;
if (requestLog.ElapsedMilliseconds > fastLimitMs)
RegisterSlowRequest(requestLog);
else
RegisterFastRequest(requestLog);
UpdateUserStat(requestLog);
}
private void RegisterFastRequest(RequestLogDto requestLog)
{
fastRequests.Enqueue(requestLog);
if (fastRequests.Count > fastRequestsCount + span)
while (fastRequests.Count > fastRequestsCount)
fastRequests.TryDequeue(out _);
}
private void RegisterSlowRequest(RequestLogDto requestLog)
{
slowRequests.Enqueue(requestLog);
if (slowRequests.Count > slowRequestsCount + span)
while (slowRequests.Count > slowRequestsCount)
slowRequests.TryDequeue(out _);
}
public void RegisterRequestError(RequestLogDto requestLog, Exception ex)
{
requestLog.Date = DateTime.Now;
requestLog.ExceptionMessage = ex.InnerException?.InnerException?.Message
?? ex.InnerException?.Message
?? ex.Message;
requestLog.ExceptionStack = ex.StackTrace?.Split(stackTraceSeparators)[0];
errorRequests.Enqueue(requestLog);
if (errorRequests.Count > errorRequestsCount + span)
while (errorRequests.Count > errorRequestsCount)
errorRequests.TryDequeue(out _);
UpdateUserStat(requestLog);
}
private void UpdateUserStat(RequestLogDto requestLog)
{
if (!string.IsNullOrEmpty(requestLog.UserLogin))
{
var key = $"{requestLog?.UserId}>{requestLog?.UserIp}";
if (!users.ContainsKey(key))
users[key] = new RequestLogUserDto
{
Id = requestLog.UserId,
Ip = requestLog.UserIp,
Login = requestLog.UserLogin,
};
users[key].ElapsedMs += requestLog.ElapsedMilliseconds;
users[key].LastDate = requestLog.Date;
users[key].Requests++;
if(!string.IsNullOrEmpty(requestLog.ExceptionMessage))
users[key].Errors++;
if(users.Count > 1000)
{
var count = 900 - users.Count;
var toRemove = users.OrderBy(kv => kv.Value.LastDate).Take(count);
foreach (var kv in toRemove)
users.TryRemove(kv);
}
}
}
}
}

View File

@ -0,0 +1,54 @@
using AsbCloudApp.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace AsbCloudWebApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class RequerstTrackerController : ControllerBase
{
private readonly IRequerstTrackerService service;
public RequerstTrackerController(IRequerstTrackerService service)
{
this.service = service;
}
[HttpGet]
public IActionResult GetAll(int take = 512)
{
var result = service.GetAll(take);
return Ok(result);
}
[HttpGet("fast")]
public IActionResult GetFast(int take = 512)
{
var result = service.GetFast(take);
return Ok(result);
}
[HttpGet("slow")]
public IActionResult GetSlow(int take = 512)
{
var result = service.GetSlow(take);
return Ok(result);
}
[HttpGet("error")]
public IActionResult GetError(int take = 512)
{
var result = service.GetError(take);
return Ok(result);
}
[HttpGet("users")]
public IActionResult GetUsersStat(int take = 512)
{
var result = service.GetUsersStat(take);
return Ok(result);
}
}
}

View File

@ -73,9 +73,48 @@ namespace AsbCloudWebApi
app.UseStaticFiles(); app.UseStaticFiles();
app.UseCors("ClientPermission"); app.UseCors("ClientPermission");
app.UseRouting(); app.UseRouting();
app.UseResponseCaching();
//app.UseResponseCompression();
app.UseAuthentication(); app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.Use(async (context, next) => {
var service = context.RequestServices.GetRequiredService<AsbCloudApp.Services.IRequerstTrackerService>();
var requestLog = new AsbCloudApp.Data.RequestLogDto
{
UserLogin = context.User?.Identity.Name,
UserIp = context.Connection.RemoteIpAddress.ToString(),
RequestMethod = context.Request.Method,
RequestPath = context.Request.Path.Value,
Referer = context.Request.Headers["Referer"].ToString(),
};
{
var userIdString = context.User?.FindFirst("id")?.Value;
if (!string.IsNullOrEmpty(userIdString) && int.TryParse(userIdString, out int userId))
requestLog.UserId = userId;
}
var sw = System.Diagnostics.Stopwatch.StartNew();
try
{
await next?.Invoke();
sw.Stop();
requestLog.ElapsedMilliseconds = sw.ElapsedMilliseconds;
requestLog.Status = context.Response.StatusCode;
service.RegisterRequest(requestLog);
}
catch(System.Exception ex)
{
sw.Stop();
requestLog.ElapsedMilliseconds = sw.ElapsedMilliseconds;
requestLog.Status = context.Response.StatusCode;
service.RegisterRequestError(requestLog, ex);
throw;
}
});
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
endpoints.MapControllers(); endpoints.MapControllers();