WorkCamera/client/WorkCameraExport/Services/LogService.cs
2026-01-05 23:58:56 +08:00

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
}
}