235 lines
7.5 KiB
C#
235 lines
7.5 KiB
C#
using System.IO.Compression;
|
|
using System.Text;
|
|
using WorkCameraExport.Services.Interfaces;
|
|
|
|
namespace WorkCameraExport.Services
|
|
{
|
|
/// <summary>
|
|
/// 日志服务实现 - 负责日志记录和导出
|
|
/// </summary>
|
|
public class LogService : ILogService
|
|
{
|
|
private readonly string _logPath;
|
|
private readonly object _lockObject = new();
|
|
private readonly string _appLogPrefix = "app_";
|
|
private readonly string _errorLogPrefix = "error_";
|
|
|
|
public LogService(string logPath)
|
|
{
|
|
_logPath = logPath;
|
|
EnsureLogDirectoryExists();
|
|
}
|
|
|
|
public LogService(IConfigService configService)
|
|
{
|
|
_logPath = configService.LogPath;
|
|
EnsureLogDirectoryExists();
|
|
}
|
|
|
|
#region ILogService 实现
|
|
|
|
public void Info(string message)
|
|
{
|
|
WriteLog("INFO", message, _appLogPrefix);
|
|
}
|
|
|
|
public void Error(string message, Exception? ex = null)
|
|
{
|
|
var fullMessage = ex != null
|
|
? $"{message}\nException: {ex.GetType().Name}: {ex.Message}\nStackTrace: {ex.StackTrace}"
|
|
: message;
|
|
|
|
WriteLog("ERROR", fullMessage, _errorLogPrefix);
|
|
// 同时写入应用日志
|
|
WriteLog("ERROR", fullMessage, _appLogPrefix);
|
|
}
|
|
|
|
public void Warn(string message)
|
|
{
|
|
WriteLog("WARN", message, _appLogPrefix);
|
|
}
|
|
|
|
public void Debug(string message)
|
|
{
|
|
#if DEBUG
|
|
WriteLog("DEBUG", message, _appLogPrefix);
|
|
#endif
|
|
}
|
|
|
|
public async Task ExportLogsAsync(string outputPath, DateTime? startDate = null, DateTime? endDate = null)
|
|
{
|
|
var logFiles = GetLogFilesInRange(startDate, endDate);
|
|
|
|
if (logFiles.Count == 0)
|
|
{
|
|
throw new InvalidOperationException("没有找到符合条件的日志文件");
|
|
}
|
|
|
|
// 如果输出路径是 .zip 文件,则打包
|
|
if (outputPath.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
await Task.Run(() =>
|
|
{
|
|
using var zipStream = new FileStream(outputPath, FileMode.Create);
|
|
using var archive = new ZipArchive(zipStream, ZipArchiveMode.Create);
|
|
|
|
foreach (var logFile in logFiles)
|
|
{
|
|
var entryName = Path.GetFileName(logFile);
|
|
archive.CreateEntryFromFile(logFile, entryName);
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// 否则合并到单个文件
|
|
var sb = new StringBuilder();
|
|
foreach (var logFile in logFiles.OrderBy(f => f))
|
|
{
|
|
sb.AppendLine($"=== {Path.GetFileName(logFile)} ===");
|
|
sb.AppendLine(await File.ReadAllTextAsync(logFile));
|
|
sb.AppendLine();
|
|
}
|
|
await File.WriteAllTextAsync(outputPath, sb.ToString());
|
|
}
|
|
}
|
|
|
|
public List<string> GetLogFiles()
|
|
{
|
|
if (!Directory.Exists(_logPath))
|
|
return new List<string>();
|
|
|
|
return Directory.GetFiles(_logPath, "*.log")
|
|
.OrderByDescending(f => f)
|
|
.ToList();
|
|
}
|
|
|
|
public void CleanOldLogs(int retentionDays = 30)
|
|
{
|
|
if (!Directory.Exists(_logPath))
|
|
return;
|
|
|
|
var cutoffDate = DateTime.Now.AddDays(-retentionDays);
|
|
var logFiles = Directory.GetFiles(_logPath, "*.log");
|
|
|
|
foreach (var file in logFiles)
|
|
{
|
|
try
|
|
{
|
|
var fileInfo = new FileInfo(file);
|
|
if (fileInfo.LastWriteTime < cutoffDate)
|
|
{
|
|
fileInfo.Delete();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// 忽略删除错误
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 私有方法
|
|
|
|
private void EnsureLogDirectoryExists()
|
|
{
|
|
if (!Directory.Exists(_logPath))
|
|
{
|
|
Directory.CreateDirectory(_logPath);
|
|
}
|
|
}
|
|
|
|
private void WriteLog(string level, string message, string filePrefix)
|
|
{
|
|
var timestamp = DateTime.Now;
|
|
var logFileName = $"{filePrefix}{timestamp:yyyy-MM-dd}.log";
|
|
var logFilePath = Path.Combine(_logPath, logFileName);
|
|
var logEntry = FormatLogEntry(timestamp, level, message);
|
|
|
|
lock (_lockObject)
|
|
{
|
|
try
|
|
{
|
|
File.AppendAllText(logFilePath, logEntry + Environment.NewLine);
|
|
}
|
|
catch
|
|
{
|
|
// 忽略写入错误
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 格式化日志条目
|
|
/// </summary>
|
|
public static string FormatLogEntry(DateTime timestamp, string level, string message)
|
|
{
|
|
return $"[{timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{level}] {message}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// 解析日志条目
|
|
/// </summary>
|
|
public static (DateTime? Timestamp, string? Level, string? Message) ParseLogEntry(string logEntry)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(logEntry))
|
|
return (null, null, null);
|
|
|
|
// 格式: [yyyy-MM-dd HH:mm:ss.fff] [LEVEL] message
|
|
var match = System.Text.RegularExpressions.Regex.Match(
|
|
logEntry,
|
|
@"^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\] \[(\w+)\] (.*)$");
|
|
|
|
if (match.Success)
|
|
{
|
|
if (DateTime.TryParseExact(match.Groups[1].Value, "yyyy-MM-dd HH:mm:ss.fff",
|
|
System.Globalization.CultureInfo.InvariantCulture,
|
|
System.Globalization.DateTimeStyles.None, out var timestamp))
|
|
{
|
|
return (timestamp, match.Groups[2].Value, match.Groups[3].Value);
|
|
}
|
|
}
|
|
|
|
return (null, null, null);
|
|
}
|
|
|
|
private List<string> GetLogFilesInRange(DateTime? startDate, DateTime? endDate)
|
|
{
|
|
var allFiles = GetLogFiles();
|
|
|
|
if (startDate == null && endDate == null)
|
|
return allFiles;
|
|
|
|
var result = new List<string>();
|
|
foreach (var file in allFiles)
|
|
{
|
|
var fileName = Path.GetFileNameWithoutExtension(file);
|
|
// 提取日期部分 (app_2025-01-05 或 error_2025-01-05)
|
|
var datePart = fileName.Contains('_')
|
|
? fileName.Substring(fileName.IndexOf('_') + 1)
|
|
: fileName;
|
|
|
|
if (DateTime.TryParseExact(datePart, "yyyy-MM-dd",
|
|
System.Globalization.CultureInfo.InvariantCulture,
|
|
System.Globalization.DateTimeStyles.None, out var fileDate))
|
|
{
|
|
var inRange = true;
|
|
if (startDate.HasValue && fileDate < startDate.Value.Date)
|
|
inRange = false;
|
|
if (endDate.HasValue && fileDate > endDate.Value.Date)
|
|
inRange = false;
|
|
|
|
if (inRange)
|
|
result.Add(file);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|