| | |
| | | using System; |
| | | using OfficeOpenXml.FormulaParsing.Excel.Functions.Numeric; |
| | | using System; |
| | | using System.Collections.Concurrent; |
| | | using System.Collections.Generic; |
| | | using System.IO; |
| | | using System.Linq; |
| | | using System.Text; |
| | | using System.Threading; |
| | | using System.Threading.Tasks; |
| | | using WIDESEAWCS_Core.Helper; |
| | | |
| | | namespace WIDESEAWCS_Core.LogHelper |
| | | { |
| | | public class QuartzLogger |
| | | /// <summary> |
| | | /// 日志等级枚举 |
| | | /// </summary> |
| | | public enum LogLevel |
| | | { |
| | | static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim(); |
| | | static string folderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"Log\\{DateTime.Now.ToString("yyyy-MM-dd")}"); |
| | | DEBUG = 0, |
| | | INFO = 1, |
| | | WARN = 2, |
| | | ERROR = 3, |
| | | FATAL = 4 |
| | | } |
| | | |
| | | public static void WriteLogToFile(string fileName, string log) |
| | | /// <summary> |
| | | /// 日志条目:封装单条日志的完整信息 |
| | | /// </summary> |
| | | public class LogEntry |
| | | { |
| | | /// <summary> |
| | | /// 日志时间 |
| | | /// </summary> |
| | | public DateTime Time { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 日志等级 |
| | | /// </summary> |
| | | public LogLevel Level { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 日志消息内容 |
| | | /// </summary> |
| | | public string Message { get; set; } = string.Empty; |
| | | |
| | | /// <summary> |
| | | /// 日志来源/文件名 |
| | | /// </summary> |
| | | public string? Source { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 异常信息(可选) |
| | | /// </summary> |
| | | public string? Exception { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 格式化为标准日志字符串 |
| | | /// </summary> |
| | | public string ToFormattedString() |
| | | { |
| | | var sb = new StringBuilder(); |
| | | sb.Append($"【{Time:HH:mm:ss}】【{Level}】:【{Message}】"); |
| | | if (!string.IsNullOrEmpty(Exception)) |
| | | { |
| | | sb.AppendLine(); |
| | | sb.Append($"【异常】:{Exception}"); |
| | | } |
| | | return sb.ToString(); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 队列化集中日志写入器 |
| | | /// 使用生产者-消费者模式,后台线程批量写入文件,提高性能 |
| | | /// </summary> |
| | | public class QueuedLogWriter : IDisposable |
| | | { |
| | | private static readonly Lazy<QueuedLogWriter> _instance = new Lazy<QueuedLogWriter>(() => new QueuedLogWriter()); |
| | | public static QueuedLogWriter Instance => _instance.Value; |
| | | |
| | | private readonly BlockingCollection<LogEntry> _logQueue; |
| | | private readonly CancellationTokenSource _cts; |
| | | private readonly Task _writeTask; |
| | | private readonly ReaderWriterLockSlim _fileLock = new ReaderWriterLockSlim(); |
| | | private readonly string _logFolder; |
| | | private readonly int _maxFileSize; |
| | | private readonly string _fileExt; |
| | | private bool _disposed; |
| | | |
| | | /// <summary> |
| | | /// 日志队列写入器单例 |
| | | /// </summary> |
| | | private QueuedLogWriter() |
| | | { |
| | | _logFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"Log{Path.DirectorySeparatorChar}{DateTime.Now:yyyy-MM-dd}"); |
| | | _maxFileSize = 10 * 1024 * 1024; // 10MB |
| | | _fileExt = ".log"; |
| | | _logQueue = new BlockingCollection<LogEntry>(new ConcurrentQueue<LogEntry>()); |
| | | _cts = new CancellationTokenSource(); |
| | | |
| | | // 确保日志目录存在 |
| | | if (!Directory.Exists(_logFolder)) |
| | | { |
| | | Directory.CreateDirectory(_logFolder); |
| | | } |
| | | |
| | | // 启动后台写入线程 |
| | | _writeTask = Task.Run(WriteLoop, _cts.Token); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 后台写入循环 |
| | | /// </summary> |
| | | private void WriteLoop() |
| | | { |
| | | var batch = new List<LogEntry>(); |
| | | while (!_cts.Token.IsCancellationRequested) |
| | | { |
| | | try |
| | | { |
| | | // 批量取出日志,最多100条或等待500ms |
| | | batch.Clear(); |
| | | if (_logQueue.TryTake(out var entry, 500)) |
| | | { |
| | | batch.Add(entry); |
| | | while (batch.Count < 100 && _logQueue.TryTake(out entry, 50)) |
| | | { |
| | | batch.Add(entry); |
| | | } |
| | | WriteBatch(batch); |
| | | } |
| | | } |
| | | catch (OperationCanceledException) |
| | | { |
| | | break; |
| | | } |
| | | catch (Exception) |
| | | { |
| | | // 忽略写入异常,防止后台线程崩溃 |
| | | } |
| | | } |
| | | |
| | | // 取消前将剩余日志写出 |
| | | while (_logQueue.TryTake(out var remainingEntry, 100)) |
| | | { |
| | | batch.Add(remainingEntry); |
| | | } |
| | | if (batch.Count > 0) |
| | | { |
| | | WriteBatch(batch); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 批量写入日志到文件 |
| | | /// </summary> |
| | | private void WriteBatch(List<LogEntry> entries) |
| | | { |
| | | if (entries.Count == 0) return; |
| | | |
| | | try |
| | | { |
| | | // 进入写锁 |
| | | LogWriteLock.EnterWriteLock(); |
| | | |
| | | // 如果文件夹不存在,则创建文件夹 |
| | | if (!Directory.Exists(folderPath)) |
| | | _fileLock.EnterWriteLock(); |
| | | foreach (var entry in entries) |
| | | { |
| | | Directory.CreateDirectory(folderPath); |
| | | string fileName = GetLogFileName(entry.Source); |
| | | string filePath = Path.Combine(_logFolder, fileName); |
| | | string content = entry.ToFormattedString() + Environment.NewLine; |
| | | ConsoleHelper.WriteInfoLine(content); |
| | | File.AppendAllText(filePath, content); |
| | | } |
| | | // 获取日志文件路径 |
| | | string logFilePath = Path.Combine(folderPath, GetLastAccessFileName(fileName)); |
| | | // 获取当前时间 |
| | | DateTime now = DateTime.Now; |
| | | // 构造日志内容 |
| | | string logContent = $"【{now}】{Environment.NewLine}{log}"; |
| | | |
| | | // 将日志内容追加到日志文件中 |
| | | File.AppendAllText(logFilePath, logContent); |
| | | } |
| | | catch { } |
| | | catch (Exception) |
| | | { |
| | | // 静默处理,防止文件IO异常影响主线程 |
| | | } |
| | | finally |
| | | { |
| | | // 退出写锁 |
| | | LogWriteLock.ExitWriteLock(); |
| | | _fileLock.ExitWriteLock(); |
| | | } |
| | | } |
| | | static int size = 10 * 1024 * 1024; |
| | | static string ext = ".log"; |
| | | private static string GetLogFilePath(string folderPath, string fileName) |
| | | { |
| | | // 获取指定文件夹下的所有文件 |
| | | var allFiles = new DirectoryInfo(folderPath); |
| | | // 获取符合条件的文件,按文件名降序排列 |
| | | var selectFiles = allFiles.GetFiles().Where(fi => fi.Name.ToLower().Contains(fileName.ToLower()) && fi.Extension.ToLower() == ext.ToLower() && fi.Length < size).OrderByDescending(d => d.Name).ToList(); |
| | | |
| | | FileInfo? file = selectFiles.FirstOrDefault(); |
| | | // 如果有符合条件的文件,返回第一个文件的完整路径 |
| | | if (file != null) |
| | | /// <summary> |
| | | /// 获取或创建一个日志文件路径(按大小分文件) |
| | | /// </summary> |
| | | private string GetLogFileName(string? source) |
| | | { |
| | | string prefix = string.IsNullOrEmpty(source) ? "WCS" : source; |
| | | string searchPattern = $"{prefix}*.log"; |
| | | |
| | | if (!Directory.Exists(_logFolder)) |
| | | { |
| | | return file.FullName; |
| | | Directory.CreateDirectory(_logFolder); |
| | | } |
| | | |
| | | // 如果没有符合条件的文件,返回一个新的文件路径,文件名为原文件名加上当前时间 |
| | | return Path.Combine(folderPath, $@"{fileName}_{DateTime.Now.ToString("HH-mm-ss")}.log"); |
| | | } |
| | | var files = Directory.GetFiles(_logFolder, searchPattern) |
| | | .Select(f => new FileInfo(f)) |
| | | .Where(f => f.Extension.Equals(_fileExt, StringComparison.OrdinalIgnoreCase)) |
| | | .OrderByDescending(f => f.Name) |
| | | .ToList(); |
| | | |
| | | private static string GetLastAccessFileName(string fileName) |
| | | { |
| | | foreach (var m in GetExistLogFileNames(fileName)) |
| | | // 查找有空间的现有文件 |
| | | foreach (var file in files) |
| | | { |
| | | FileInfo fileInfo = new FileInfo(m); |
| | | if (fileInfo.Length < size) |
| | | if (file.Length < _maxFileSize) |
| | | { |
| | | return m; |
| | | return file.Name; |
| | | } |
| | | } |
| | | |
| | | // 返回一个新的默认当前时间的日志名称 |
| | | return $@"{fileName}_{DateTime.Now.ToString("HH-mm-ss")}.log"; |
| | | // 创建新文件 |
| | | return $"{prefix}_{DateTime.Now:HH-mm-ss}{_fileExt}"; |
| | | } |
| | | |
| | | public static string[] GetExistLogFileNames(string fileName) |
| | | /// <summary> |
| | | /// 写入日志(生产端) |
| | | /// </summary> |
| | | public void Enqueue(LogEntry entry) |
| | | { |
| | | string[] fileNames = Directory.GetFiles(folderPath, fileName + "*.log"); |
| | | return fileNames; |
| | | if (_disposed) return; |
| | | _logQueue.Add(entry); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 写入日志的便捷方法 |
| | | /// </summary> |
| | | public void Log(LogLevel level, string message, string? source = null, string? exception = null) |
| | | { |
| | | if (_disposed) return; |
| | | var entry = new LogEntry |
| | | { |
| | | Time = DateTime.Now, |
| | | Level = level, |
| | | Message = message, |
| | | Source = source, |
| | | Exception = exception |
| | | }; |
| | | _logQueue.Add(entry); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 关闭日志写入器 |
| | | /// </summary> |
| | | public void Dispose() |
| | | { |
| | | if (_disposed) return; |
| | | _disposed = true; |
| | | _cts.Cancel(); |
| | | try |
| | | { |
| | | _writeTask.Wait(3000); |
| | | } |
| | | catch (AggregateException) { } |
| | | _cts.Dispose(); |
| | | _logQueue.Dispose(); |
| | | _fileLock.Dispose(); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 静态日志工具类(兼容原有接口) |
| | | /// 提供多种格式化方法和队列化写入 |
| | | /// </summary> |
| | | public class QuartzLogger |
| | | { |
| | | private static readonly QueuedLogWriter _writer = QueuedLogWriter.Instance; |
| | | |
| | | /// <summary> |
| | | /// 兼容旧接口:将日志写入文件 |
| | | /// </summary> |
| | | /// <param name="fileName">日志文件名(作为Source)</param> |
| | | /// <param name="log">日志内容</param> |
| | | public static void WriteLogToFile(string fileName, string log) |
| | | { |
| | | _writer.Log(LogLevel.INFO, log, fileName); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 写入调试日志 |
| | | /// </summary> |
| | | public static void Debug(string message, string? source = null) |
| | | { |
| | | _writer.Log(LogLevel.DEBUG, message, source); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 写入信息日志 |
| | | /// </summary> |
| | | public static void Info(string message, string? source = null) |
| | | { |
| | | _writer.Log(LogLevel.INFO, message, source); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 写入警告日志 |
| | | /// </summary> |
| | | public static void Warn(string message, string? source = null) |
| | | { |
| | | _writer.Log(LogLevel.WARN, message, source); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 写入错误日志 |
| | | /// </summary> |
| | | public static void Error(string message, string? source = null, Exception? exception = null) |
| | | { |
| | | _writer.Log(LogLevel.ERROR, message, source, exception?.ToString()); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 写入致命错误日志 |
| | | /// </summary> |
| | | public static void Fatal(string message, string? source = null, Exception? exception = null) |
| | | { |
| | | _writer.Log(LogLevel.FATAL, message, source, exception?.ToString()); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 使用指定格式写入日志 |
| | | /// </summary> |
| | | /// <param name="format">格式化字符串</param> |
| | | /// <param name="args">格式化参数</param> |
| | | public static void WriteFormatted(string format, params object[] args) |
| | | { |
| | | string message = string.Format(format, args); |
| | | _writer.Log(LogLevel.INFO, message); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 直接写入一个日志条目(最灵活) |
| | | /// </summary> |
| | | public static void WriteLogEntry(LogEntry entry) |
| | | { |
| | | _writer.Enqueue(entry); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 释放资源(应用关闭时调用) |
| | | /// </summary> |
| | | public static void Shutdown() |
| | | { |
| | | _writer.Dispose(); |
| | | } |
| | | } |
| | | } |