mirror of
https://github.com/alexhopeoconnor/bom-local-service.git
synced 2026-10-04 03:28:11 +10:00
- Remove OpenAPI/Swagger support and all related code - Standardize configuration: remove hardcoded defaults, all defaults from appsettings.json - Configuration values throw exceptions if missing (except Debug:Enabled defaults to false) - Refactor CacheManagementService: extract ProcessLocationsAsync helper, remove duplicate code - Combine UpdateStaggerSeconds and LocationStaggerSeconds into single LocationStaggerSeconds config - Move config reads to constructors for better performance - Update README and docker-compose.yml to reflect configuration changes
182 lines
8.2 KiB
C#
182 lines
8.2 KiB
C#
using BomLocalService.Services.Interfaces;
|
|
using BomLocalService.Utilities;
|
|
using Microsoft.Playwright;
|
|
|
|
namespace BomLocalService.Services;
|
|
|
|
public class DebugService : IDebugService
|
|
{
|
|
private readonly ILogger<DebugService> _logger;
|
|
private readonly bool _enabled;
|
|
private readonly string _debugDirectory;
|
|
private readonly int _waitMs;
|
|
|
|
public DebugService(ILogger<DebugService> logger, IConfiguration configuration)
|
|
{
|
|
_logger = logger;
|
|
|
|
var waitMsConfig = configuration.GetValue<int?>("Debug:WaitMs");
|
|
if (!waitMsConfig.HasValue)
|
|
{
|
|
throw new InvalidOperationException("Debug:WaitMs configuration is required. Set it in appsettings.json or via DEBUG__WAITMS environment variable.");
|
|
}
|
|
_waitMs = waitMsConfig.Value;
|
|
|
|
// Get debug enabled from configuration (default from appsettings.json, can be overridden via DEBUG__ENABLED environment variable)
|
|
// Note: bool defaults to false if not found, which is acceptable for Debug:Enabled
|
|
_enabled = configuration.GetValue<bool>("Debug:Enabled", false);
|
|
|
|
var cacheDirectory = FilePathHelper.GetCacheDirectory(configuration);
|
|
_debugDirectory = Path.Combine(cacheDirectory, "debug");
|
|
|
|
if (_enabled)
|
|
{
|
|
Directory.CreateDirectory(_debugDirectory);
|
|
_logger.LogInformation("Debug mode is ENABLED. Debug files will be saved to: {DebugDirectory}", _debugDirectory);
|
|
}
|
|
else
|
|
{
|
|
_logger.LogInformation("Debug mode is DISABLED");
|
|
}
|
|
}
|
|
|
|
public bool IsEnabled => _enabled;
|
|
|
|
public string CreateRequestFolder(string requestId)
|
|
{
|
|
if (!_enabled) return string.Empty;
|
|
|
|
var requestFolder = Path.Combine(_debugDirectory, requestId);
|
|
Directory.CreateDirectory(requestFolder);
|
|
_logger.LogDebug("Created debug folder for request: {RequestId} at {Path}", requestId, requestFolder);
|
|
return requestFolder;
|
|
}
|
|
|
|
public async Task SaveStepDebugAsync(string requestFolder, int stepNumber, string stepName, IPage page, List<(string type, string text, DateTime timestamp)>? consoleMessages = null, List<(string method, string url, int? status, string resourceType, DateTime timestamp)>? networkRequests = null, CancellationToken cancellationToken = default)
|
|
{
|
|
if (!_enabled || string.IsNullOrEmpty(requestFolder)) return;
|
|
|
|
try
|
|
{
|
|
// Wait for page to be fully loaded before taking screenshot
|
|
try
|
|
{
|
|
await page.WaitForLoadStateAsync(LoadState.NetworkIdle, new PageWaitForLoadStateOptions { Timeout = 10000 });
|
|
await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded, new PageWaitForLoadStateOptions { Timeout = 5000 });
|
|
}
|
|
catch
|
|
{
|
|
// If network idle times out, at least wait for DOM
|
|
}
|
|
await page.WaitForTimeoutAsync(_waitMs); // Additional wait for dynamic content and animations
|
|
|
|
var stepFolder = Path.Combine(requestFolder, $"step_{stepNumber:D2}_{stepName}");
|
|
Directory.CreateDirectory(stepFolder);
|
|
|
|
// Save screenshot
|
|
var screenshotPath = Path.Combine(stepFolder, "screenshot.png");
|
|
await page.ScreenshotAsync(new PageScreenshotOptions
|
|
{
|
|
Path = screenshotPath,
|
|
FullPage = true
|
|
});
|
|
|
|
// Save HTML
|
|
var htmlContent = await page.ContentAsync();
|
|
var htmlPath = Path.Combine(stepFolder, "page.html");
|
|
await File.WriteAllTextAsync(htmlPath, htmlContent, cancellationToken);
|
|
|
|
// Save console messages if provided
|
|
// Create a snapshot to avoid collection modification during enumeration
|
|
if (consoleMessages != null && consoleMessages.Count > 0)
|
|
{
|
|
var consolePath = Path.Combine(stepFolder, "console.log");
|
|
var consoleSnapshot = consoleMessages.ToList(); // Create snapshot
|
|
var consoleText = string.Join("\n", consoleSnapshot.Select(msg => $"[{msg.timestamp:HH:mm:ss.fff}] [{msg.type}] {msg.text}"));
|
|
await File.WriteAllTextAsync(consolePath, consoleText, cancellationToken);
|
|
}
|
|
|
|
// Save network requests summary if provided
|
|
// Create a snapshot to avoid collection modification during enumeration
|
|
if (networkRequests != null && networkRequests.Count > 0)
|
|
{
|
|
var networkPath = Path.Combine(stepFolder, "network.log");
|
|
var networkSnapshot = networkRequests.ToList(); // Create snapshot
|
|
var networkText = string.Join("\n", networkSnapshot.Select(req =>
|
|
$"[{req.timestamp:HH:mm:ss.fff}] {req.method} {req.url} -> {req.status?.ToString() ?? "pending"} ({req.resourceType})"
|
|
));
|
|
await File.WriteAllTextAsync(networkPath, networkText, cancellationToken);
|
|
}
|
|
|
|
_logger.LogDebug("Saved debug files for step {StepNumber} ({StepName}) to {Path}", stepNumber, stepName, stepFolder);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to save debug files for step {StepNumber} ({StepName})", stepNumber, stepName);
|
|
}
|
|
}
|
|
|
|
public async Task SaveErrorDebugAsync(string requestFolder, string errorMessage, IPage? page = null,
|
|
List<(string type, string text, DateTime timestamp)>? consoleMessages = null,
|
|
List<(string method, string url, int? status, string resourceType, DateTime timestamp)>? networkRequests = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (!_enabled || string.IsNullOrEmpty(requestFolder)) return;
|
|
|
|
try
|
|
{
|
|
var errorFolder = Path.Combine(requestFolder, "error");
|
|
Directory.CreateDirectory(errorFolder);
|
|
|
|
// Save error message
|
|
var errorPath = Path.Combine(errorFolder, "error.txt");
|
|
await File.WriteAllTextAsync(errorPath, errorMessage, cancellationToken);
|
|
|
|
// Save screenshot if page is available
|
|
if (page != null)
|
|
{
|
|
var screenshotPath = Path.Combine(errorFolder, "screenshot.png");
|
|
await page.ScreenshotAsync(new PageScreenshotOptions
|
|
{
|
|
Path = screenshotPath,
|
|
FullPage = true
|
|
});
|
|
|
|
// Save HTML
|
|
var htmlContent = await page.ContentAsync();
|
|
var htmlPath = Path.Combine(errorFolder, "page.html");
|
|
await File.WriteAllTextAsync(htmlPath, htmlContent, cancellationToken);
|
|
}
|
|
|
|
// Save console messages if provided
|
|
// Create a snapshot to avoid collection modification during enumeration
|
|
if (consoleMessages != null && consoleMessages.Count > 0)
|
|
{
|
|
var consolePath = Path.Combine(errorFolder, "console.log");
|
|
var consoleSnapshot = consoleMessages.ToList(); // Create snapshot
|
|
var consoleText = string.Join("\n", consoleSnapshot.Select(msg => $"[{msg.timestamp:HH:mm:ss.fff}] [{msg.type}] {msg.text}"));
|
|
await File.WriteAllTextAsync(consolePath, consoleText, cancellationToken);
|
|
}
|
|
|
|
// Save network requests summary if provided
|
|
// Create a snapshot to avoid collection modification during enumeration
|
|
if (networkRequests != null && networkRequests.Count > 0)
|
|
{
|
|
var networkPath = Path.Combine(errorFolder, "network.log");
|
|
var networkSnapshot = networkRequests.ToList(); // Create snapshot
|
|
var networkText = string.Join("\n", networkSnapshot.Select(req =>
|
|
$"[{req.timestamp:HH:mm:ss.fff}] {req.method} {req.url} -> {req.status?.ToString() ?? "pending"} ({req.resourceType})"
|
|
));
|
|
await File.WriteAllTextAsync(networkPath, networkText, cancellationToken);
|
|
}
|
|
|
|
_logger.LogDebug("Saved error debug files to {Path}", errorFolder);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to save error debug files");
|
|
}
|
|
}
|
|
}
|
|
|