diff --git a/allstarr/Controllers/JellyfinController.cs b/allstarr/Controllers/JellyfinController.cs index e0a8b23..3179cc1 100644 --- a/allstarr/Controllers/JellyfinController.cs +++ b/allstarr/Controllers/JellyfinController.cs @@ -1671,6 +1671,9 @@ public class JellyfinController : ControllerBase // DEBUG: Log EVERY request to see what's happening _logger.LogWarning("ProxyRequest called with path: {Path}", path); + // Log endpoint usage to file for analysis + await LogEndpointUsageAsync(path, Request.Method); + // Block dangerous admin endpoints var blockedPrefixes = new[] { @@ -1962,6 +1965,37 @@ public class JellyfinController : ControllerBase } } + /// + /// Logs endpoint usage to a file for analysis. + /// Creates a CSV file with timestamp, method, path, and query string. + /// + 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) { if (string.IsNullOrWhiteSpace(includeItemTypes)) @@ -2484,5 +2518,114 @@ public class JellyfinController : ControllerBase } #endregion + + #region Debug & Monitoring + + /// + /// 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 + /// + [HttpGet("debug/endpoint-usage")] + [ServiceFilter(typeof(ApiKeyAuthFilter))] + public async Task 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() + }); + } + + 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 }); + } + } + + /// + /// Clears the endpoint usage log file. + /// DELETE /debug/endpoint-usage?api_key=YOUR_KEY + /// + [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