rft: 日志汇报打包 debug 截图,支持分卷,修改存储路径

This commit is contained in:
uye
2025-11-12 14:15:15 +08:00
parent 74cad75e74
commit 39a89f6361
2 changed files with 148 additions and 61 deletions

View File

@@ -76,4 +76,14 @@ public static class PathsHelper
/// named "data". This property returns the resolved path, creating the directory if it does not exist is the
/// responsibility of the caller.</remarks>
public static string DataDir => _data ??= Path.Combine(BaseDir, "data");
private static string? _reports;
/// <summary>
/// Gets the full path to the application's reports directory.
/// </summary>
/// <remarks>The reports directory is located within the application's base directory under a folder
/// named "_reports". This property returns the resolved path, creating the directory if it does not exist is the
/// responsibility of the caller.</remarks>
public static string ReportsDir => _reports ??= Path.Combine(BaseDir, "reports");
}

View File

@@ -13,16 +13,16 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using HandyControl.Controls;
using HandyControl.Data;
using JetBrains.Annotations;
using MaaWpfGui.Configuration.Factory;
using MaaWpfGui.Constants;
using MaaWpfGui.Helper;
using MaaWpfGui.Main;
using Serilog;
using Stylet;
@@ -38,110 +38,187 @@ public class IssueReportUserControlModel : PropertyChangedBase
Instance = new();
}
private static readonly string[] _payloadFileNames = [
Bootstrapper.UiLogFile,
Bootstrapper.UiLogBakFile,
Bootstrapper.CoreLogFile,
Bootstrapper.CoreLogBakFile,
ConfigurationHelper.ConfigFile,
ConfigFactory.ConfigFile];
public static IssueReportUserControlModel Instance { get; }
public void OpenDebugFolder()
{
try
{
if (!Directory.Exists(PathsHelper.DebugDir))
if (!Directory.Exists(PathsHelper.ReportsDir))
{
Directory.CreateDirectory(PathsHelper.DebugDir);
Directory.CreateDirectory(PathsHelper.ReportsDir);
}
Process.Start("explorer.exe", PathsHelper.DebugDir);
Process.Start("explorer.exe", PathsHelper.ReportsDir);
}
catch (Exception ex)
{
ToastNotification.ShowDirect($"Failed to open debug folder\n{ex.Message}");
Log.Error(ex, "Failed to open debug folder");
ToastNotification.ShowDirect($"Failed to open reports folder\n{ex.Message}");
Log.Error(ex, "Failed to open reports folder");
}
}
/// <summary>
/// 生成日志压缩包
/// </summary>
public void GenerateSupportPayload()
{
try
{
var reportFileName = $"report_{DateTimeOffset.Now:MM-dd_HH-mm-ss}.zip";
string zipPath = Path.Combine(PathsHelper.DebugDir, reportFileName);
string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
string debugTempPath = Path.Combine(tempPath, "debug");
string resourceTempPath = Path.Combine(tempPath, "resource");
if (File.Exists(zipPath))
{
File.Delete(zipPath);
}
const int PartSize = 10 * 1024 * 1024; // 10 MB
string reportNameBase = $"report_{DateTimeOffset.Now:MM-dd_HH-mm-ss}";
string tempPath = Path.Combine(Path.GetTempPath(), $"maa-report-{Guid.NewGuid()}");
Directory.CreateDirectory(tempPath);
Directory.CreateDirectory(debugTempPath);
Directory.CreateDirectory(resourceTempPath);
// 复制 _payloadFileNames 中的文件到 tempPath/debug
foreach (var file in _payloadFileNames)
if (!Directory.Exists(PathsHelper.ReportsDir))
{
if (!File.Exists(file))
{
continue;
}
string relativePath = Path.GetRelativePath(PathsHelper.BaseDir, file);
string dest = Path.Combine(tempPath, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(dest)!);
File.Copy(file, dest, overwrite: true);
Directory.CreateDirectory(PathsHelper.ReportsDir);
}
// 遍历 resource 文件夹下带 _custom 后缀的文件,复制到 tempPath/resource
const string ResourceDir = "resource";
if (Directory.Exists(ResourceDir))
// 复制文件
CopyDirectoryIfExists(PathsHelper.DebugDir, Path.Combine(tempPath, "debug"),
f => { return !Path.GetFileName(f).StartsWith("report", StringComparison.OrdinalIgnoreCase); });
CopyDirectoryIfExists(PathsHelper.ResourceDir, Path.Combine(tempPath, "resource"),
f => { return Path.GetFileName(f).Contains("_custom.", StringComparison.OrdinalIgnoreCase); });
CopyDirectoryIfExists(PathsHelper.ConfigDir, Path.Combine(tempPath, "config"));
CopyDirectoryIfExists(PathsHelper.CacheDir, Path.Combine(tempPath, "cache"));
string partsFolder = Path.Combine(PathsHelper.ReportsDir, $"{reportNameBase}_parts");
if (!Directory.Exists(partsFolder))
{
foreach (var file in Directory.EnumerateFiles(ResourceDir, "*_custom.*", SearchOption.AllDirectories))
Directory.CreateDirectory(partsFolder);
}
// ====== part01config + resource + cache + debug 根目录文件 ======
List<string> part01Files = [];
string[] categories = ["config", "resource", "cache"];
foreach (string category in categories)
{
string categoryPath = Path.Combine(tempPath, category);
if (Directory.Exists(categoryPath))
{
string relativePath = Path.GetRelativePath(ResourceDir, file);
string dest = Path.Combine(resourceTempPath, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(dest)!);
File.Copy(file, dest, overwrite: true);
part01Files.AddRange(Directory.EnumerateFiles(categoryPath, "*", SearchOption.AllDirectories));
}
}
// 遍历 cache 文件夹下的文件,复制到 tempPath/cache
string cacheResourceDir = PathsHelper.CacheDir;
if (Directory.Exists(cacheResourceDir))
string debugPath = Path.Combine(tempPath, "debug");
if (Directory.Exists(debugPath))
{
foreach (var file in Directory.EnumerateFiles(cacheResourceDir, "*", SearchOption.AllDirectories))
// 只取 debug 根目录文件
var debugRootFiles = Directory.EnumerateFiles(debugPath, "*", SearchOption.TopDirectoryOnly).ToList();
part01Files.AddRange(debugRootFiles);
}
if (part01Files.Count > 0)
{
string part01Path = Path.Combine(partsFolder, "part01.zip");
using var fs = new FileStream(part01Path, FileMode.Create);
using var archive = new ZipArchive(fs, ZipArchiveMode.Create);
foreach (var file in part01Files)
{
string relativePath = Path.GetRelativePath(cacheResourceDir, file);
string dest = Path.Combine(tempPath, "cache", relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(dest)!);
File.Copy(file, dest, overwrite: true);
string entryName = Path.GetRelativePath(tempPath, file).Replace("\\", "/");
var entry = archive.CreateEntry(entryName, CompressionLevel.SmallestSize);
using var entryStream = entry.Open();
using var fileStream = File.OpenRead(file);
fileStream.CopyTo(entryStream);
}
}
using (FileStream zipToOpen = new FileStream(zipPath, FileMode.Create))
// ====== part02+debug 子目录文件按 PartSize 分卷 ======
var debugSubFiles = Directory.EnumerateFiles(debugPath, "*", SearchOption.AllDirectories)
.Where(f => Path.GetDirectoryName(f) != debugPath).ToList();
int partNumber = 2;
while (debugSubFiles.Count > 0)
{
using var archive = new ZipArchive(zipToOpen, ZipArchiveMode.Create);
foreach (var file in Directory.EnumerateFiles(tempPath, "*", SearchOption.AllDirectories))
string partFileName = $"part{partNumber:D2}.zip";
string partPath = Path.Combine(partsFolder, partFileName);
using (var fs = new FileStream(partPath, FileMode.Create))
{
string entryName = Path.GetRelativePath(tempPath, file);
archive.CreateEntryFromFile(file, entryName, CompressionLevel.SmallestSize);
using var archive = new ZipArchive(fs, ZipArchiveMode.Create);
long currentSize = 0;
List<string> processedFiles = [];
foreach (var file in debugSubFiles.ToList())
{
var fileInfo = new FileInfo(file);
if (currentSize + fileInfo.Length > PartSize && currentSize > 0)
{
break;
}
string entryName = Path.GetRelativePath(tempPath, file).Replace("\\", "/");
var entry = archive.CreateEntry(entryName, CompressionLevel.SmallestSize);
using (var entryStream = entry.Open())
{
using var fileStream = File.OpenRead(file);
fileStream.CopyTo(entryStream);
}
currentSize += fileInfo.Length;
processedFiles.Add(file);
}
debugSubFiles.RemoveAll(f => processedFiles.Contains(f));
}
partNumber++;
}
// ====== 生成完整压缩包 ======
string fullZipPath = Path.Combine(PathsHelper.ReportsDir, $"{reportNameBase}.zip");
ZipFile.CreateFromDirectory(tempPath, fullZipPath, CompressionLevel.SmallestSize, includeBaseDirectory: false);
// 清理临时目录
Directory.Delete(tempPath, recursive: true);
ShowGrowl($"{LocalizationHelper.GetString("GenerateSupportPayloadSuccessful")}\n{reportFileName}");
ShowGrowl($"{LocalizationHelper.GetString("GenerateSupportPayloadSuccessful")}\n{fullZipPath}");
OpenDebugFolder();
}
catch (Exception ex)
{
ShowGrowl($"{LocalizationHelper.GetString("GenerateSupportPayloadException")}\n{ex.Message}");
Log.Error(ex, "Failed to create log zip");
Log.Error(ex, "Failed to create support payload");
}
}
/// <summary>
/// 从 sourceDir 复制文件到 targetDir支持过滤。
/// </summary>
private static void CopyDirectoryIfExists(string? sourceDir, string targetDir, Func<string, bool>? filter = null)
{
if (string.IsNullOrWhiteSpace(sourceDir) || !Directory.Exists(sourceDir))
{
return;
}
foreach (var file in Directory.EnumerateFiles(sourceDir, "*", SearchOption.AllDirectories))
{
if (filter != null && !filter(file))
{
continue;
}
string relative = Path.GetRelativePath(sourceDir, file);
string dest = Path.Combine(targetDir, relative);
Directory.CreateDirectory(Path.GetDirectoryName(dest)!);
try
{
File.Copy(file, dest, true);
}
catch (IOException)
{
// 某些文件可能被占用,忽略复制失败
}
catch (UnauthorizedAccessException)
{
// 也忽略权限问题
}
}
}