100 lines
4.0 KiB
C#
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);
|
|
}
|
|
}
|