mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
Add endpoint usage logging for analysis
- Log all proxied endpoints to /app/cache/endpoint-usage/endpoints.csv - CSV format: timestamp, method, path, query string - Add GET /debug/endpoint-usage?api_key=KEY to view statistics - Shows top N endpoints by usage count - Filter by date with since parameter - Returns total requests, unique endpoints, first/last seen - Add DELETE /debug/endpoint-usage?api_key=KEY to clear logs - Thread-safe file appending - Helps identify which endpoints clients actually use - Can inform future blocklist/allowlist decisions
This commit is contained in:
@@ -1671,6 +1671,9 @@ public class JellyfinController : ControllerBase
|
|||||||
// DEBUG: Log EVERY request to see what's happening
|
// DEBUG: Log EVERY request to see what's happening
|
||||||
_logger.LogWarning("ProxyRequest called with path: {Path}", path);
|
_logger.LogWarning("ProxyRequest called with path: {Path}", path);
|
||||||
|
|
||||||
|
// Log endpoint usage to file for analysis
|
||||||
|
await LogEndpointUsageAsync(path, Request.Method);
|
||||||
|
|
||||||
// Block dangerous admin endpoints
|
// Block dangerous admin endpoints
|
||||||
var blockedPrefixes = new[]
|
var blockedPrefixes = new[]
|
||||||
{
|
{
|
||||||
@@ -1962,6 +1965,37 @@ public class JellyfinController : ControllerBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logs endpoint usage to a file for analysis.
|
||||||
|
/// Creates a CSV file with timestamp, method, path, and query string.
|
||||||
|
/// </summary>
|
||||||
|
private async Task LogEndpointUsageAsync(string path, string method)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var logDir = "/app/cache/endpoint-usage";
|
||||||
|
Directory.CreateDirectory(logDir);
|
||||||
|
|
||||||
|
var logFile = Path.Combine(logDir, "endpoints.csv");
|
||||||
|
var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss");
|
||||||
|
var queryString = Request.QueryString.HasValue ? Request.QueryString.Value : "";
|
||||||
|
|
||||||
|
// Sanitize path and query for CSV (remove commas, quotes, newlines)
|
||||||
|
var sanitizedPath = path.Replace(",", ";").Replace("\"", "'").Replace("\n", " ").Replace("\r", " ");
|
||||||
|
var sanitizedQuery = queryString.Replace(",", ";").Replace("\"", "'").Replace("\n", " ").Replace("\r", " ");
|
||||||
|
|
||||||
|
var logLine = $"{timestamp},{method},{sanitizedPath},{sanitizedQuery}\n";
|
||||||
|
|
||||||
|
// Append to file (thread-safe)
|
||||||
|
await System.IO.File.AppendAllTextAsync(logFile, logLine);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Don't let logging failures break the request
|
||||||
|
_logger.LogDebug(ex, "Failed to log endpoint usage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static string[]? ParseItemTypes(string? includeItemTypes)
|
private static string[]? ParseItemTypes(string? includeItemTypes)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(includeItemTypes))
|
if (string.IsNullOrWhiteSpace(includeItemTypes))
|
||||||
@@ -2484,5 +2518,114 @@ public class JellyfinController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Debug & Monitoring
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets endpoint usage statistics from the log file.
|
||||||
|
/// GET /debug/endpoint-usage?api_key=YOUR_KEY
|
||||||
|
/// Optional query params: top=50 (default 100), since=2024-01-01
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("debug/endpoint-usage")]
|
||||||
|
[ServiceFilter(typeof(ApiKeyAuthFilter))]
|
||||||
|
public async Task<IActionResult> GetEndpointUsage(
|
||||||
|
[FromQuery] int top = 100,
|
||||||
|
[FromQuery] string? since = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var logFile = "/app/cache/endpoint-usage/endpoints.csv";
|
||||||
|
|
||||||
|
if (!System.IO.File.Exists(logFile))
|
||||||
|
{
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
message = "No endpoint usage data collected yet",
|
||||||
|
endpoints = Array.Empty<object>()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines = await System.IO.File.ReadAllLinesAsync(logFile);
|
||||||
|
|
||||||
|
// Parse CSV and filter by date if provided
|
||||||
|
DateTime? sinceDate = null;
|
||||||
|
if (!string.IsNullOrEmpty(since) && DateTime.TryParse(since, out var parsedDate))
|
||||||
|
{
|
||||||
|
sinceDate = parsedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entries = lines
|
||||||
|
.Select(line => line.Split(','))
|
||||||
|
.Where(parts => parts.Length >= 3)
|
||||||
|
.Where(parts => !sinceDate.HasValue ||
|
||||||
|
(DateTime.TryParse(parts[0], out var entryDate) && entryDate >= sinceDate.Value))
|
||||||
|
.Select(parts => new
|
||||||
|
{
|
||||||
|
Timestamp = parts[0],
|
||||||
|
Method = parts.Length > 1 ? parts[1] : "",
|
||||||
|
Path = parts.Length > 2 ? parts[2] : "",
|
||||||
|
Query = parts.Length > 3 ? parts[3] : ""
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Group by path and count
|
||||||
|
var pathCounts = entries
|
||||||
|
.GroupBy(e => new { e.Method, e.Path })
|
||||||
|
.Select(g => new
|
||||||
|
{
|
||||||
|
Method = g.Key.Method,
|
||||||
|
Path = g.Key.Path,
|
||||||
|
Count = g.Count(),
|
||||||
|
FirstSeen = g.Min(e => e.Timestamp),
|
||||||
|
LastSeen = g.Max(e => e.Timestamp)
|
||||||
|
})
|
||||||
|
.OrderByDescending(x => x.Count)
|
||||||
|
.Take(top)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
totalRequests = entries.Count,
|
||||||
|
uniqueEndpoints = pathCounts.Count,
|
||||||
|
topEndpoints = pathCounts,
|
||||||
|
logFile = logFile,
|
||||||
|
logSize = new FileInfo(logFile).Length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to get endpoint usage");
|
||||||
|
return StatusCode(500, new { error = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the endpoint usage log file.
|
||||||
|
/// DELETE /debug/endpoint-usage?api_key=YOUR_KEY
|
||||||
|
/// </summary>
|
||||||
|
[HttpDelete("debug/endpoint-usage")]
|
||||||
|
[ServiceFilter(typeof(ApiKeyAuthFilter))]
|
||||||
|
public IActionResult ClearEndpointUsage()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var logFile = "/app/cache/endpoint-usage/endpoints.csv";
|
||||||
|
|
||||||
|
if (System.IO.File.Exists(logFile))
|
||||||
|
{
|
||||||
|
System.IO.File.Delete(logFile);
|
||||||
|
return Ok(new { status = "success", message = "Endpoint usage log cleared" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(new { status = "success", message = "No log file to clear" });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to clear endpoint usage log");
|
||||||
|
return StatusCode(500, new { error = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
// force rebuild Sun Jan 25 13:22:47 EST 2026
|
// force rebuild Sun Jan 25 13:22:47 EST 2026
|
||||||
|
|||||||
Reference in New Issue
Block a user