简单的线程安全日志记录器

csharp 复制代码
    /// <summary>
    /// 简单的线程安全日志记录器
    /// 支持多线程同时写入,自动按天分割文件
    /// </summary>
    public sealed class LogHelper : IDisposable
    {
        // 单例实例
        private static readonly Lazy<LogHelper> _instance = new Lazy<LogHelper>(() => new LogHelper());
        public static LogHelper Instance => _instance.Value;


        // 线程安全的日志队列
        private readonly BlockingCollection<LogEntry> _logQueue = new BlockingCollection<LogEntry>();

        // 写入器任务
        private readonly Task _writerTask;
        private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();

        // 当前日志文件路径
        private string _currentLogFilePath = string.Empty;
        private StreamWriter _currentWriter;
        private readonly object _writerLock = new object();

        // 配置
        public string LogDirectory { get; set; } = AppDomain.CurrentDomain.BaseDirectory + "Logs";
        public bool WriteToConsole { get; set; } = true;
        public bool AutoFlush { get; set; } = true;

        private LogHelper()
        {
            // 确保日志目录存在
            EnsureLogDirectory();

            // 启动后台写入线程
            _writerTask = Task.Run(() => ProcessLogQueueAsync(_cancellationTokenSource.Token));
        }

        /// <summary>
        /// 写日志
        /// </summary>
        /// <param name="message">日志消息</param>
        public void Log(string message)
        {
            if (string.IsNullOrWhiteSpace(message)) return;

            var logEntry = new LogEntry
            {
                Timestamp = DateTime.Now,
                Message = message,
                ThreadId = Thread.CurrentThread.ManagedThreadId
            };

            // 将日志加入队列(如果队列已满,会阻塞直到有空间)
            _logQueue.Add(logEntry);
        }

        /// <summary>
        /// 格式化日志(可选自定义格式)
        /// </summary>
        public string FormatLog(LogEntry entry)
        {
            return $"[{entry.Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [Thread:{entry.ThreadId:D4}] {entry.Message}";
        }

        /// <summary>
        /// 后台处理日志队列
        /// </summary>
        private async Task ProcessLogQueueAsync(CancellationToken cancellationToken)
        {
            foreach (var logEntry in _logQueue.GetConsumingEnumerable(cancellationToken))
            {
                try
                {
                    // 检查是否需要切换日志文件(按天)
                    CheckAndSwitchLogFile();

                    // 格式化日志
                    var formattedLog = FormatLog(logEntry);

                    // 写入控制台
                    if (WriteToConsole)
                    {
                        Console.WriteLine(formattedLog);
                    }

                    // 写入文件
                    lock (_writerLock)
                    {
                        if (_currentWriter != null && !_currentWriter.BaseStream.CanWrite)
                        {
                            // 如果流已关闭,重新创建
                            _currentWriter.Dispose();
                            _currentWriter = null;
                        }

                        if (_currentWriter == null)
                        {
                            _currentWriter = new StreamWriter(_currentLogFilePath, true, System.Text.Encoding.UTF8)
                            {
                                AutoFlush = AutoFlush
                            };
                        }

                        _currentWriter.WriteLine(formattedLog);
                    }
                }
                catch (Exception ex)
                {
                    // 写入失败时输出到控制台
                    Console.WriteLine($"[Logger Error] Failed to write log: {ex.Message}");
                }
            }
        }

        /// <summary>
        /// 检查并切换日志文件(按天)
        /// </summary>
        private void CheckAndSwitchLogFile()
        {
            string today = DateTime.Today.ToString("yyyy-MM-dd");
            string newLogFilePath = Path.Combine(LogDirectory, $"log_{today}.txt");

            if (newLogFilePath != _currentLogFilePath)
            {
                lock (_writerLock)
                {
                    if (newLogFilePath != _currentLogFilePath)
                    {
                        // 关闭旧的文件写入器
                        if (_currentWriter != null)
                        {
                            try
                            {
                                _currentWriter.Flush();
                                _currentWriter.Dispose();
                            }
                            catch
                            {
                                // 忽略关闭异常
                            }
                            _currentWriter = null;
                        }

                        // 更新当前日志文件路径
                        _currentLogFilePath = newLogFilePath;

                        // 创建新的文件写入器
                        _currentWriter = new StreamWriter(_currentLogFilePath, true, System.Text.Encoding.UTF8)
                        {
                            AutoFlush = AutoFlush
                        };
                    }
                }
            }
        }

        /// <summary>
        /// 确保日志目录存在
        /// </summary>
        private void EnsureLogDirectory()
        {
            if (!Directory.Exists(LogDirectory))
            {
                Directory.CreateDirectory(LogDirectory);
            }
        }

        /// <summary>
        /// 刷新缓冲区
        /// </summary>
        public void Flush()
        {
            lock (_writerLock)
            {
                _currentWriter?.Flush();
            }
        }

        /// <summary>
        /// 立即写入一条日志(同步,用于紧急情况)
        /// </summary>
        public void LogImmediate(string message)
        {
            if (string.IsNullOrWhiteSpace(message)) return;

            var logEntry = new LogEntry
            {
                Timestamp = DateTime.Now,
                Message = message,
                ThreadId = Thread.CurrentThread.ManagedThreadId
            };

            var formattedLog = FormatLog(logEntry);

            // 写入控制台
            if (WriteToConsole)
            {
                Console.WriteLine(formattedLog);
            }

            // 确保文件准备好
            CheckAndSwitchLogFile();

            // 直接写入文件
            lock (_writerLock)
            {
                try
                {
                    if (_currentWriter == null)
                    {
                        _currentWriter = new StreamWriter(_currentLogFilePath, true, System.Text.Encoding.UTF8)
                        {
                            AutoFlush = AutoFlush
                        };
                    }

                    _currentWriter.WriteLine(formattedLog);
                    _currentWriter.Flush();
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"[Logger Error] Immediate write failed: {ex.Message}");
                }
            }
        }

        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            try
            {
                // 停止队列
                _logQueue.CompleteAdding();
                _cancellationTokenSource.Cancel();

                // 等待写入线程完成
                _writerTask?.Wait(TimeSpan.FromSeconds(5));

                // 关闭写入器
                lock (_writerLock)
                {
                    if (_currentWriter != null)
                    {
                        _currentWriter.Flush();
                        _currentWriter.Dispose();
                        _currentWriter = null;
                    }
                }

                _cancellationTokenSource.Dispose();
                _logQueue.Dispose();
            }
            catch
            {
                // 释放时忽略异常
            }
        }

        /// <summary>
        /// 日志条目结构
        /// </summary>
        public class LogEntry
        {
            public DateTime Timestamp { get; set; }
            public string Message { get; set; } = string.Empty;
            public int ThreadId { get; set; }
        }
    }
相关推荐
Code知行合壹2 小时前
JDK10新特性
开发语言·jdk
T1an-12 小时前
C++11智能指针shared_ptr的控制块内都有什么?
开发语言·c++
迈巴赫车主2 小时前
天梯赛 L2-004 这是二叉搜索树吗?java
java·开发语言·数据结构·算法·天梯赛
青柠代码录2 小时前
【MySQL】DISTINCT 详解
数据库·mysql
数据知道2 小时前
MongoDB查询执行计划解读:executionStats详细分析与性能诊断
数据库·mongodb
小鸡吃米…2 小时前
基准测试与性能分析
开发语言·python
神仙别闹2 小时前
基于MATLAB实现(GUI)汽车出入库识别系统
开发语言·matlab·汽车
筵陌2 小时前
MySQL Connector/C API的使用
数据库·mysql
今儿敲了吗2 小时前
python基础学习笔记第一章
开发语言·python