wanshenmean
8 天以前 fd18eaba5e1c086a588509371f91310e7aafff9c
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/LogHelper/QuartzLogger.cs
@@ -1,85 +1,345 @@
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();
        }
    }
}