journal/Journal.DevTool/Core/Debug/DiagnosticsBundleService.cs

100 lines
4.0 KiB
C#

using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Sdt.Config;
namespace Sdt.Core.Debug;
public sealed class DiagnosticsBundleService : IDiagnosticsBundleService
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true
};
public async Task<DiagnosticsBundleResult> WriteBundleAsync(
DiagnosticsBundleRequest request,
CancellationToken cancellationToken = default)
{
try
{
var timestamp = DateTimeOffset.Now.ToString("yyyyMMdd-HHmmss");
var root = Path.GetFullPath(Path.Combine(request.ProjectRoot, request.DiagnosticsOptions.OutputDir));
var bundleDir = Path.Combine(root, $"{request.Category}-{timestamp}");
Directory.CreateDirectory(bundleDir);
var stepsPath = Path.Combine(bundleDir, "steps.json");
var toolsPath = Path.Combine(bundleDir, "tools.json");
var envPath = Path.Combine(bundleDir, "env.json");
var outputPath = Path.Combine(bundleDir, "output.log");
var summaryPath = Path.Combine(bundleDir, "summary.json");
await File.WriteAllTextAsync(stepsPath, JsonSerializer.Serialize(request.WorkflowSteps, JsonOptions), cancellationToken);
await File.WriteAllTextAsync(toolsPath, JsonSerializer.Serialize(request.Probes, JsonOptions), cancellationToken);
await File.WriteAllTextAsync(envPath, JsonSerializer.Serialize(CaptureEnvironment(request.DiagnosticsOptions), JsonOptions), cancellationToken);
await File.WriteAllLinesAsync(outputPath, request.OutputLines, cancellationToken);
var summary = new
{
category = request.Category,
stopReason = request.StopReason?.ToString(),
message = request.SummaryMessage,
createdAt = DateTimeOffset.Now,
configHash = HashConfig(request.Config),
debugProfile = request.DebugProfile?.Id,
debugExitCode = request.DebugRun?.ExitCode,
envCapture = BuildEnvCaptureSummary(request.DiagnosticsOptions),
};
await File.WriteAllTextAsync(summaryPath, JsonSerializer.Serialize(summary, JsonOptions), cancellationToken);
return new DiagnosticsBundleResult(
Success: true,
BundleDirectory: bundleDir,
ZipPath: null,
Message: "Diagnostics bundle generated.");
}
catch (Exception ex)
{
return new DiagnosticsBundleResult(
Success: false,
BundleDirectory: string.Empty,
ZipPath: null,
Message: ex.Message);
}
}
private static Dictionary<string, string> CaptureEnvironment(DebugDiagnosticsOptions options)
{
if (options.IncludeAllEnv)
{
return Environment.GetEnvironmentVariables()
.Cast<System.Collections.DictionaryEntry>()
.ToDictionary(e => e.Key?.ToString() ?? "", e => e.Value?.ToString() ?? "", StringComparer.OrdinalIgnoreCase);
}
if (options.CaptureEnvKeys.Count == 0)
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var captured = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var key in options.CaptureEnvKeys)
captured[key] = Environment.GetEnvironmentVariable(key) ?? "";
return captured;
}
private static string BuildEnvCaptureSummary(DebugDiagnosticsOptions options)
{
if (options.IncludeAllEnv)
return "full";
if (options.CaptureEnvKeys.Count == 0)
return "allowlist-empty (intentional minimal capture)";
return $"allowlist ({options.CaptureEnvKeys.Count} keys)";
}
private static string HashConfig(object config)
{
var json = JsonSerializer.Serialize(config);
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(json));
return Convert.ToHexString(bytes);
}
}