Files
XplorePlane/XP.Common/Dump/Implementations/DumpService.cs
T

322 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
}