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

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; }
        }
    }
相关推荐
麦聪聊数据1 分钟前
企业数据市场建设(三):API 化服务封装,让数据开箱即用、避免重复开发
数据库
麦聪聊数据13 分钟前
企业数据市场建设(四):流程闭环与价值运营,让数据市场真正转起来
运维·数据库
正儿八经的少年20 分钟前
redis 的大 key 和热 key 详解
数据库·redis·缓存
AI砖家23 分钟前
多智能体系统实战:架构设计、数据库表设计与 Skill 体系
数据库·多智能体·skill·agent架构设计·agengt
暗暗别做白日梦34 分钟前
Pulsar 消息同步机制
c#·linq
大不点wow41 分钟前
Java序列化与反序列化:让对象走出JVM
java·开发语言·jvm
阿里嘎多学长41 分钟前
2026-07-22 GitHub 热点项目精选
开发语言·程序员·github·代码托管
海盗123443 分钟前
微软技术周报——2026-07-22
microsoft·c#·.net
噢,我明白了1 小时前
Java中日期和字符串的处理
java·开发语言·日期
爱刷碗的苏泓舒1 小时前
C 语言 if-else 与 switch-case 分支语句对比
c语言·开发语言