Added more security and metrics
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
using EonaCat.DoxaApi.Models;
|
||||
|
||||
namespace EonaCat.DoxaApi.Generation
|
||||
{
|
||||
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
||||
// See the LICENSE file or go to https://EonaCat.com/license for full license details.
|
||||
|
||||
/// <summary>
|
||||
/// Collects and manages API metrics and analytics.
|
||||
/// </summary>
|
||||
public class MetricsCollector
|
||||
{
|
||||
private readonly Dictionary<string, ApiMetrics> _metrics = new();
|
||||
private readonly List<RequestMetadata> _requestHistory = new();
|
||||
private readonly object _lock = new();
|
||||
private readonly DateTime _startTime = DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// Records a request to an endpoint.
|
||||
/// </summary>
|
||||
public void RecordRequest(string method, string path, int statusCode, long responseTimeMs, long responseSize, string? userAgent = null, string? ipAddress = null)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var key = $"{method}:{path}";
|
||||
|
||||
if (!_metrics.ContainsKey(key))
|
||||
{
|
||||
_metrics[key] = new ApiMetrics
|
||||
{
|
||||
EndpointId = key,
|
||||
Method = method,
|
||||
Path = path,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
var metric = _metrics[key];
|
||||
metric.TotalRequests++;
|
||||
metric.LastRequestAt = DateTime.UtcNow;
|
||||
|
||||
if (statusCode >= 200 && statusCode < 300)
|
||||
{
|
||||
metric.SuccessfulRequests++;
|
||||
}
|
||||
else if (statusCode >= 400)
|
||||
{
|
||||
metric.FailedRequests++;
|
||||
}
|
||||
|
||||
// Track status codes
|
||||
if (!metric.StatusCodes.ContainsKey(statusCode))
|
||||
{
|
||||
metric.StatusCodes[statusCode] = 0;
|
||||
}
|
||||
metric.StatusCodes[statusCode]++;
|
||||
|
||||
// Calculate running average response time
|
||||
metric.AverageResponseTimeMs = (metric.AverageResponseTimeMs * (metric.TotalRequests - 1) + responseTimeMs) / metric.TotalRequests;
|
||||
|
||||
// Calculate running average response size
|
||||
metric.AverageResponseSize = (metric.AverageResponseSize * (metric.TotalRequests - 1) + responseSize) / metric.TotalRequests;
|
||||
|
||||
// Add to request history (keep last 1000)
|
||||
_requestHistory.Add(new RequestMetadata
|
||||
{
|
||||
Method = method,
|
||||
Path = path,
|
||||
StatusCode = statusCode,
|
||||
ResponseTimeMs = responseTimeMs,
|
||||
ResponseSize = responseSize,
|
||||
Timestamp = DateTime.UtcNow,
|
||||
UserAgent = userAgent,
|
||||
IpAddress = ipAddress
|
||||
});
|
||||
|
||||
if (_requestHistory.Count > 1000)
|
||||
{
|
||||
_requestHistory.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records an error for an endpoint.
|
||||
/// </summary>
|
||||
public void RecordError(string method, string path, string errorMessage, string? userAgent = null, string? ipAddress = null)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var key = $"{method}:{path}";
|
||||
|
||||
if (!_metrics.ContainsKey(key))
|
||||
{
|
||||
_metrics[key] = new ApiMetrics
|
||||
{
|
||||
EndpointId = key,
|
||||
Method = method,
|
||||
Path = path,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
_metrics[key].FailedRequests++;
|
||||
|
||||
_requestHistory.Add(new RequestMetadata
|
||||
{
|
||||
Method = method,
|
||||
Path = path,
|
||||
StatusCode = 500,
|
||||
ResponseTimeMs = 0,
|
||||
Timestamp = DateTime.UtcNow,
|
||||
UserAgent = userAgent,
|
||||
IpAddress = ipAddress,
|
||||
Error = errorMessage
|
||||
});
|
||||
|
||||
if (_requestHistory.Count > 1000)
|
||||
{
|
||||
_requestHistory.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets metrics for a specific endpoint.
|
||||
/// </summary>
|
||||
public ApiMetrics? GetMetrics(string method, string path)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var key = $"{method}:{path}";
|
||||
return _metrics.ContainsKey(key) ? _metrics[key] : null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all collected metrics.
|
||||
/// </summary>
|
||||
public IEnumerable<ApiMetrics> GetAllMetrics()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _metrics.Values.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the analytics dashboard data.
|
||||
/// </summary>
|
||||
public AnalyticsDashboard GetDashboard()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var allMetrics = _metrics.Values.ToList();
|
||||
var totalRequests = allMetrics.Sum(m => m.TotalRequests);
|
||||
var totalErrors = allMetrics.Sum(m => m.FailedRequests);
|
||||
|
||||
var topEndpoints = allMetrics
|
||||
.OrderByDescending(m => m.TotalRequests)
|
||||
.Take(10)
|
||||
.Select(m => new EndpointUsageSummary
|
||||
{
|
||||
Method = m.Method,
|
||||
Path = m.Path,
|
||||
RequestCount = m.TotalRequests,
|
||||
ErrorCount = m.FailedRequests,
|
||||
AverageResponseTimeMs = m.AverageResponseTimeMs,
|
||||
LastCalled = m.LastRequestAt
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var recentRequests = _requestHistory
|
||||
.OrderByDescending(r => r.Timestamp)
|
||||
.Take(20)
|
||||
.ToList();
|
||||
|
||||
var uptime = DateTime.UtcNow - _startTime;
|
||||
var requestsPerSecond = totalRequests > 0 ? totalRequests / uptime.TotalSeconds : 0;
|
||||
|
||||
return new AnalyticsDashboard
|
||||
{
|
||||
TotalEndpoints = _metrics.Count,
|
||||
TotalRequests = totalRequests,
|
||||
TotalErrors = totalErrors,
|
||||
AverageResponseTime = allMetrics.Any() ? allMetrics.Average(m => m.AverageResponseTimeMs) : 0,
|
||||
PeakRequestsPerSecond = requestsPerSecond,
|
||||
Uptime = uptime,
|
||||
TopEndpoints = topEndpoints,
|
||||
RecentRequests = recentRequests,
|
||||
ErrorRate = totalRequests > 0 ? (double)totalErrors / totalRequests : 0,
|
||||
GeneratedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all collected metrics and history.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_metrics.Clear();
|
||||
_requestHistory.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets recent requests (last N).
|
||||
/// </summary>
|
||||
public IEnumerable<RequestMetadata> GetRecentRequests(int count = 50)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _requestHistory
|
||||
.OrderByDescending(r => r.Timestamp)
|
||||
.Take(count)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user