| File: src\Shared\Export\ExportArchive.cs | Web Access |
| Project: src\src\Aspire.Cli\Aspire.Cli.csproj (aspire) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.IO.Compression; using System.Text; using System.Text.Encodings.Web; using System.Text.Json; using Aspire.Otlp.Serialization; using Aspire.Shared.Model.Serialization; namespace Aspire.Shared.Export; /// <summary> /// Represents an archive of exported Aspire resource and telemetry data. /// Populate the strongly-typed collections, then call <see cref="WriteToFile"/> or <see cref="WriteToStream"/> /// to produce a zip archive with source-generated JSON serialization (AOT-compatible). /// </summary> internal sealed class ExportArchive { /// <summary> /// Gets the collection of resource details keyed by display name. /// Each entry is serialized to <c>resources/{name}.json</c>. /// </summary> public Dictionary<string, ResourceJson> Resources { get; } = new(); /// <summary> /// Gets the collection of console log lines keyed by resource display name. /// Each entry is written to <c>consolelogs/{name}.txt</c> as plain text. /// </summary> public Dictionary<string, List<string>> ConsoleLogs { get; } = new(); /// <summary> /// Gets the collection of structured logs (OTLP format) keyed by resource or aggregate name. /// Each entry is serialized to <c>structuredlogs/{name}.json</c>. /// </summary> public Dictionary<string, OtlpTelemetryDataJson> StructuredLogs { get; } = new(); /// <summary> /// Gets the collection of traces (OTLP format) keyed by resource or aggregate name. /// Each entry is serialized to <c>traces/{name}.json</c>. /// </summary> public Dictionary<string, OtlpTelemetryDataJson> Traces { get; } = new(); /// <summary> /// Gets the collection of metrics (OTLP format) keyed by resource name. /// Each entry is serialized to <c>metrics/{name}.json</c>. /// </summary> public Dictionary<string, OtlpTelemetryDataJson> Metrics { get; } = new(); /// <summary> /// Writes the archive contents to a file. /// </summary> /// <param name="filePath">The file path to write the zip archive to.</param> public void WriteToFile(string filePath) { using var fileStream = File.Create(filePath); using var archive = new ZipArchive(fileStream, ZipArchiveMode.Create, leaveOpen: false); WriteEntries(archive); } /// <summary> /// Writes the archive contents to a stream. /// </summary> /// <param name="stream">The stream to write the zip archive to. The stream is left open.</param> public void WriteToStream(Stream stream) { using var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: true); WriteEntries(archive); } private static readonly OtlpJsonSerializerContext s_serializerContext = OtlpJsonSerializerContext.Default; private static readonly JsonWriterOptions s_writerOptions = new() { Indented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; private void WriteEntries(ZipArchive archive) { foreach (var (name, resource) in Resources) { var entry = archive.CreateEntry($"resources/{SanitizeFileName(name)}.json"); using var entryStream = entry.Open(); using var writer = new Utf8JsonWriter(entryStream, s_writerOptions); JsonSerializer.Serialize(writer, resource, s_serializerContext.ResourceJson); } foreach (var (name, lines) in ConsoleLogs) { var entry = archive.CreateEntry($"consolelogs/{SanitizeFileName(name)}.txt"); using var entryStream = entry.Open(); using var writer = new StreamWriter(entryStream); foreach (var line in lines) { writer.WriteLine(line); } } foreach (var (name, data) in StructuredLogs) { var entry = archive.CreateEntry($"structuredlogs/{SanitizeFileName(name)}.json"); using var entryStream = entry.Open(); using var writer = new Utf8JsonWriter(entryStream, s_writerOptions); JsonSerializer.Serialize(writer, data, s_serializerContext.OtlpTelemetryDataJson); } foreach (var (name, data) in Traces) { var entry = archive.CreateEntry($"traces/{SanitizeFileName(name)}.json"); using var entryStream = entry.Open(); using var writer = new Utf8JsonWriter(entryStream, s_writerOptions); JsonSerializer.Serialize(writer, data, s_serializerContext.OtlpTelemetryDataJson); } foreach (var (name, data) in Metrics) { var entry = archive.CreateEntry($"metrics/{SanitizeFileName(name)}.json"); using var entryStream = entry.Open(); using var writer = new Utf8JsonWriter(entryStream, s_writerOptions); JsonSerializer.Serialize(writer, data, s_serializerContext.OtlpTelemetryDataJson); } } /// <summary> /// Sanitizes a file name by replacing invalid characters with underscores. /// </summary> internal static string SanitizeFileName(string name) { var invalidChars = Path.GetInvalidFileNameChars(); var sanitized = new StringBuilder(name.Length); foreach (var c in name) { sanitized.Append(invalidChars.Contains(c) ? '_' : c); } return sanitized.ToString(); } }