322 lines
13 KiB
C#
322 lines
13 KiB
C#
using System;
|
||
using System.Diagnostics;
|
||
using System.IO;
|
||
using System.Runtime.InteropServices;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using XP.Common.Dump.Configs;
|
||
using XP.Common.Dump.Interfaces;
|
||
using XP.Common.Dump.Native;
|
||
using XP.Common.Logging.Interfaces;
|
||
|
||
namespace XP.Common.Dump.Implementations
|
||
{
|
||
/// <summary>
|
||
/// Dump 文件管理服务实现,负责 Dump 生成、崩溃事件订阅、定时调度和清理
|
||
/// Dump file management service implementation, handles dump generation, crash event subscription, scheduled tasks and cleanup
|
||
/// </summary>
|
||
public class DumpService : IDumpService
|
||
{
|
||
private readonly DumpConfig _config;
|
||
private readonly ILoggerService _logger;
|
||
private readonly DumpCleaner _cleaner;
|
||
private Timer? _scheduledTimer;
|
||
private Timer? _cleanupTimer;
|
||
private bool _isStarted;
|
||
private bool _disposed;
|
||
|
||
/// <summary>
|
||
/// 默认存储路径 | Default storage path
|
||
/// </summary>
|
||
private const string DefaultStoragePath = @"D:\XplorePlane\Dump";
|
||
|
||
public DumpService(DumpConfig config, ILoggerService logger)
|
||
{
|
||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||
_logger = logger?.ForModule<DumpService>() ?? throw new ArgumentNullException(nameof(logger));
|
||
_cleaner = new DumpCleaner(config, logger);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成 Dump 文件名(纯函数)| Generate dump file name (pure function)
|
||
/// 格式:XplorePlane_{yyyyMMdd_HHmm}_{TriggerType}.dmp
|
||
/// </summary>
|
||
/// <param name="triggerType">触发类型 | Trigger type</param>
|
||
/// <param name="timestamp">时间戳 | Timestamp</param>
|
||
/// <returns>文件名 | File name</returns>
|
||
internal static string GenerateFileName(DumpTriggerType triggerType, DateTime timestamp)
|
||
{
|
||
return $"XplorePlane_{timestamp:yyyyMMdd_HHmm}_{triggerType}.dmp";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证 Mini Dump 文件大小是否在限制范围内 | Validate Mini Dump file size is within limit
|
||
/// </summary>
|
||
/// <param name="fileSizeBytes">文件大小(字节)| File size in bytes</param>
|
||
/// <param name="sizeLimitMB">大小限制(MB)| Size limit in MB</param>
|
||
/// <returns>true 表示在限制内 | true if within limit</returns>
|
||
internal static bool ValidateFileSize(long fileSizeBytes, long sizeLimitMB)
|
||
{
|
||
return fileSizeBytes <= sizeLimitMB * 1024 * 1024;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 写入 Dump 文件的核心方法 | Core method to write dump file
|
||
/// </summary>
|
||
/// <param name="triggerType">触发类型 | Trigger type</param>
|
||
/// <param name="isMiniDump">是否为 Mini Dump | Whether it is a Mini Dump</param>
|
||
/// <returns>生成的文件完整路径,失败返回 null | Full path of generated file, null on failure</returns>
|
||
private string? WriteDump(DumpTriggerType triggerType, bool isMiniDump)
|
||
{
|
||
try
|
||
{
|
||
// 非手动触发请求 Full Dump 时,降级为 Mini Dump | Downgrade to Mini Dump for non-manual Full Dump requests
|
||
if (!isMiniDump && triggerType != DumpTriggerType.Manual)
|
||
{
|
||
_logger.Warn("非手动触发不允许生成 Full Dump,已降级为 Mini Dump:{TriggerType} | Non-manual trigger cannot generate Full Dump, downgraded to Mini Dump: {TriggerType}", triggerType);
|
||
isMiniDump = true;
|
||
}
|
||
|
||
var fileName = GenerateFileName(triggerType, DateTime.Now);
|
||
var filePath = Path.Combine(_config.StoragePath, fileName);
|
||
|
||
// 确定 Dump 类型标志 | Determine dump type flags
|
||
uint dumpType = isMiniDump
|
||
? (uint)(MiniDumpType.MiniDumpWithDataSegs | MiniDumpType.MiniDumpWithHandleData | MiniDumpType.MiniDumpWithThreadInfo)
|
||
: (uint)MiniDumpType.MiniDumpWithFullMemory;
|
||
|
||
var process = Process.GetCurrentProcess();
|
||
|
||
using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
|
||
{
|
||
bool success = NativeMethods.MiniDumpWriteDump(
|
||
process.Handle,
|
||
(uint)process.Id,
|
||
fileStream.SafeFileHandle,
|
||
dumpType,
|
||
IntPtr.Zero,
|
||
IntPtr.Zero,
|
||
IntPtr.Zero);
|
||
|
||
if (!success)
|
||
{
|
||
var errorCode = Marshal.GetLastWin32Error();
|
||
_logger.Error(null!, "MiniDumpWriteDump 调用失败,错误码:{ErrorCode} | MiniDumpWriteDump failed, error code: {ErrorCode}", errorCode);
|
||
// 清理失败的文件 | Clean up failed file
|
||
try { File.Delete(filePath); } catch { }
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// Mini Dump 文件大小检查 | Mini Dump file size check
|
||
if (isMiniDump)
|
||
{
|
||
var fileInfo = new FileInfo(filePath);
|
||
if (!ValidateFileSize(fileInfo.Length, _config.MiniDumpSizeLimitMB))
|
||
{
|
||
_logger.Warn("Mini Dump 文件超过大小限制({SizeMB}MB),已删除:{FilePath} | Mini Dump file exceeds size limit ({SizeMB}MB), deleted: {FilePath}",
|
||
_config.MiniDumpSizeLimitMB, filePath);
|
||
File.Delete(filePath);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
var dumpTypeStr = isMiniDump ? "Mini" : "Full";
|
||
_logger.Info("Dump 文件已生成:{FilePath},触发类型:{TriggerType},Dump 类型:{DumpType} | Dump file generated: {FilePath}, trigger: {TriggerType}, type: {DumpType}",
|
||
filePath, triggerType, dumpTypeStr);
|
||
|
||
return filePath;
|
||
}
|
||
catch (IOException ex)
|
||
{
|
||
_logger.Error(ex, "Dump 文件写入失败:{Message} | Dump file write failed: {Message}", ex.Message);
|
||
return null;
|
||
}
|
||
catch (UnauthorizedAccessException ex)
|
||
{
|
||
_logger.Error(ex, "Dump 文件写入权限不足:{Message} | Dump file write permission denied: {Message}", ex.Message);
|
||
return null;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Error(ex, "Dump 文件生成异常:{Message} | Dump file generation error: {Message}", ex.Message);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
#region 手动触发方法 | Manual trigger methods
|
||
|
||
/// <inheritdoc />
|
||
public string? CreateMiniDump()
|
||
{
|
||
_logger.Info("手动触发 Mini Dump 生成 | Manually triggering Mini Dump generation");
|
||
var filePath = WriteDump(DumpTriggerType.Manual, isMiniDump: true);
|
||
if (filePath != null)
|
||
{
|
||
_logger.Info("手动 Mini Dump 已生成:{FilePath} | Manual Mini Dump generated: {FilePath}", filePath);
|
||
}
|
||
return filePath;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public string? CreateFullDump()
|
||
{
|
||
_logger.Info("手动触发 Full Dump 生成 | Manually triggering Full Dump generation");
|
||
var filePath = WriteDump(DumpTriggerType.Manual, isMiniDump: false);
|
||
if (filePath != null)
|
||
{
|
||
_logger.Info("手动 Full Dump 已生成:{FilePath} | Manual Full Dump generated: {FilePath}", filePath);
|
||
}
|
||
return filePath;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 生命周期管理 | Lifecycle management
|
||
|
||
/// <inheritdoc />
|
||
public void Start()
|
||
{
|
||
if (_isStarted) return;
|
||
|
||
_logger.Info("Dump 服务启动中 | Dump service starting");
|
||
|
||
// 确保存储目录存在 | Ensure storage directory exists
|
||
EnsureStorageDirectory();
|
||
|
||
// 订阅崩溃事件 | Subscribe to crash events
|
||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
||
|
||
// 启动定时 Dump 计时器 | Start scheduled dump timer
|
||
if (_config.EnableScheduledDump)
|
||
{
|
||
var intervalMs = _config.ScheduledIntervalMinutes * 60 * 1000;
|
||
_scheduledTimer = new Timer(OnScheduledDumpCallback, null, intervalMs, intervalMs);
|
||
_logger.Info("定时 Dump 已启用,间隔:{Interval} 分钟 | Scheduled dump enabled, interval: {Interval} minutes", _config.ScheduledIntervalMinutes);
|
||
}
|
||
|
||
// 启动每日清理计时器(每24小时执行一次)| Start daily cleanup timer (every 24 hours)
|
||
var dailyMs = 24 * 60 * 60 * 1000;
|
||
_cleanupTimer = new Timer(OnCleanupCallback, null, dailyMs, dailyMs);
|
||
|
||
// 立即执行一次清理 | Execute cleanup immediately
|
||
_cleaner.CleanExpiredFiles();
|
||
|
||
_isStarted = true;
|
||
_logger.Info("Dump 服务已启动 | Dump service started");
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void Stop()
|
||
{
|
||
if (!_isStarted) return;
|
||
|
||
_logger.Info("Dump 服务停止中 | Dump service stopping");
|
||
|
||
// 取消事件订阅 | Unsubscribe from events
|
||
AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException;
|
||
TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
|
||
|
||
// 停止所有计时器 | Stop all timers
|
||
_scheduledTimer?.Change(Timeout.Infinite, Timeout.Infinite);
|
||
_cleanupTimer?.Change(Timeout.Infinite, Timeout.Infinite);
|
||
|
||
_isStarted = false;
|
||
_logger.Info("Dump 服务已停止 | Dump service stopped");
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public void Dispose()
|
||
{
|
||
if (_disposed) return;
|
||
|
||
Stop();
|
||
|
||
_scheduledTimer?.Dispose();
|
||
_scheduledTimer = null;
|
||
|
||
_cleanupTimer?.Dispose();
|
||
_cleanupTimer = null;
|
||
|
||
_disposed = true;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 内部方法 | Internal methods
|
||
|
||
/// <summary>
|
||
/// 确保存储目录存在,不存在则创建,创建失败回退默认路径
|
||
/// Ensure storage directory exists, create if not, fallback to default on failure
|
||
/// </summary>
|
||
private void EnsureStorageDirectory()
|
||
{
|
||
try
|
||
{
|
||
if (!Directory.Exists(_config.StoragePath))
|
||
{
|
||
Directory.CreateDirectory(_config.StoragePath);
|
||
_logger.Info("已创建存储目录:{Path} | Created storage directory: {Path}", _config.StoragePath);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Error(ex, "存储目录创建失败:{Path},回退到默认路径 | Storage directory creation failed: {Path}, falling back to default", _config.StoragePath);
|
||
_config.StoragePath = DefaultStoragePath;
|
||
|
||
try
|
||
{
|
||
if (!Directory.Exists(DefaultStoragePath))
|
||
{
|
||
Directory.CreateDirectory(DefaultStoragePath);
|
||
_logger.Info("已创建默认存储目录:{Path} | Created default storage directory: {Path}", DefaultStoragePath);
|
||
}
|
||
}
|
||
catch (Exception fallbackEx)
|
||
{
|
||
_logger.Fatal(fallbackEx, "默认存储目录创建也失败:{Path} | Default storage directory creation also failed: {Path}", DefaultStoragePath);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 未处理异常回调 | Unhandled exception callback
|
||
/// </summary>
|
||
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||
{
|
||
_logger.Info("检测到未处理异常,正在生成崩溃 Dump | Unhandled exception detected, generating crash dump");
|
||
WriteDump(DumpTriggerType.Crash, isMiniDump: true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 未观察到的 Task 异常回调 | Unobserved task exception callback
|
||
/// </summary>
|
||
private void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
|
||
{
|
||
_logger.Info("检测到未观察的 Task 异常,正在生成崩溃 Dump | Unobserved task exception detected, generating crash dump");
|
||
WriteDump(DumpTriggerType.Crash, isMiniDump: true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 定时 Dump 回调 | Scheduled dump callback
|
||
/// </summary>
|
||
private void OnScheduledDumpCallback(object? state)
|
||
{
|
||
_logger.Debug("定时 Dump 触发 | Scheduled dump triggered");
|
||
WriteDump(DumpTriggerType.Scheduled, isMiniDump: true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理回调 | Cleanup callback
|
||
/// </summary>
|
||
private void OnCleanupCallback(object? state)
|
||
{
|
||
_logger.Debug("定时清理触发 | Scheduled cleanup triggered");
|
||
_cleaner.CleanExpiredFiles();
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|