当前位置: 首页 > news >正文

【代码】关于C#支持文件和文本框的简单日志实现

在软件开发过程中,日志记录是一个不可或缺的功能。它不仅能帮助开发者调试程序,还能在出现问题时提供重要的上下文信息。本文将介绍如何使用C#实现一个简单但功能完备的日志系统,支持同时输出到文件和文本框,并具备异步处理、级别过滤和颜色区分等实用特性。

一、功能概述

我们实现的SLog类具有以下特点:

  1. 支持同时输出到文件和界面文本框
  2. 异步处理日志,几乎不影响程序性能
  3. 支持不同日志级别(Trace, Debug, Info, Warn, Error)
  4. 文件日志支持按日期自动分割
  5. 界面日志支持不同级别显示不同颜色
  6. 提供事件机制支持自定义日志处理
  7. 线程安全,支持多线程环境
  8. 无第三方依赖

二、 核心实现

1. 日志级别定义

public enum LogLevel
{Trace,Debug,Info,Warn,Error
}

我们定义了五种日志级别,从最详细的Trace到最严重的Error,满足不同场景的需求。

2. 异步处理机制

为了确保日志记录不会阻塞主线程,我们采用了生产者-消费者模式:

private readonly ConcurrentQueue<LogItem> _logQueue = new ConcurrentQueue<LogItem>();
private readonly ManualResetEventSlim _logEvent = new ManualResetEventSlim(false);
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private Task _processingTask;public SLog()
{_processingTask = Task.Run(ProcessLogQueue, _cancellationTokenSource.Token);
}

当应用程序调用日志方法时,日志项被放入并发队列,然后通过ManualResetEventSlim通知后台处理线程。后台线程从队列中取出日志项并处理,这样主线程可以继续执行而不被阻塞。

3. 文件日志实现

文件日志支持按日期自动分割,通过文件路径中的日期占位符实现:

private string GetFilePath(DateTime timestamp)
{if (string.IsNullOrEmpty(File))return null;return File.Replace("yyyy", timestamp.Year.ToString("D4")).Replace("MM", timestamp.Month.ToString("D2")).Replace("dd", timestamp.Day.ToString("D2"));
}

当日志处理跨越午夜时,系统会自动检测日期变化并切换到新的日志文件:

// 检查是否需要创建新的日志文件
if (_fileWriter == null || _currentFileDate != timestamp.Date)
{_currentFileDate = timestamp.Date;_currentFilePath = GetFilePath(timestamp);// ...创建新文件
}

4. 界面日志与颜色支持

对于界面日志,我们特别添加了颜色区分功能,使不同级别的日志更加醒目:

private void WriteToTextBox(string message, LogLevel level)
{if (TextBox.InvokeRequired){TextBox.Invoke(new Action<string, LogLevel>(WriteToTextBox), message, level);return;}try{// 根据日志级别选择颜色Color color = GetColorForLevel(level);// 处理不同类型的文本框if (TextBox is RichTextBox richTextBox){// 对于RichTextBox,可以设置颜色richTextBox.SelectionStart = richTextBox.TextLength;richTextBox.SelectionLength = 0;richTextBox.SelectionColor = color;richTextBox.AppendText(message + Environment.NewLine);richTextBox.SelectionColor = richTextBox.ForeColor;}else{// 对于普通TextBox,只能使用默认颜色TextBox.AppendText(message + Environment.NewLine);}TextBox.ScrollToCaret();}catch (Exception ex){// 错误处理}
}

默认颜色设置为:

  • Trace: 灰色
  • Debug: 蓝色
  • Info: 黑色
  • Warn: 橙色
  • Error: 红色

5.6 事件机制

SLog类提供了事件机制,允许外部订阅日志事件:

public event EventHandler<LogEventArgs> LogEvent;// 触发日志事件
LogEvent?.Invoke(this, new LogEventArgs(logItem.Timestamp,logItem.Level,logItem.Message,logItem.ProcessId,logItem.ThreadId));

这样,开发者可以轻松地将日志重定向到其他系统,如数据库、网络或其他自定义存储。

三、使用示例

1. 基本用法

// 创建日志实例
var log = new SLog
{File = "logs/app_yyyyMMdd.log", // 支持日期格式TextBox = richTextBox1, // 使用RichTextBox以支持颜色FileLogLevel = LogLevel.Info,   // 文件记录级别TextBoxLogLevel = LogLevel.Debug // 界面记录级别
};// 记录不同级别的日志
log.Trace("这是一条跟踪日志");
log.Debug("这是一条调试日志");
log.Info("这是一条信息日志");
log.Warn("这是一条警告日志");
log.Error("这是一条错误日志");// 使用完成后释放资源
log.Dispose();

2. 自定义颜色

// 自定义颜色
log.TraceTextBoxColor = Color.DarkGray;
log.DebugTextBoxColor = Color.DarkBlue;
log.InfoTextBoxColor = Color.DarkGreen;
log.WarnTextBoxColor = Color.DarkOrange;
log.ErrorTextBoxColor = Color.DarkRed;

3. 事件订阅

// 订阅日志事件
log.LogEvent += (sender, e) =>
{// 可以在这里处理自定义日志记录Console.WriteLine($"Custom log: {e.Message} at {e.Timestamp}");
};

四、实现要点

  1. 线程安全:使用ConcurrentQueue确保多线程环境下的安全性。
  2. 资源管理:实现IDisposable接口,确保资源正确释放。
  3. 异常处理:对文件和界面操作进行异常捕获,避免因日志错误影响主程序。
  4. 性能优化:异步处理和批量写入减少I/O操作次数。
  5. 灵活性:支持多种配置选项,满足不同场景需求。

五、完整源码

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;namespace Tools
{public class SLog : IDisposable{#region 外部定义public enum LogLevel{Trace,Debug,Info,Warn,Error}public class LogEventArgs : EventArgs{public DateTime Timestamp { get; set; }public LogLevel Level { get; set; }public string Message { get; set; }public int ProcessId { get; set; }public int ThreadId { get; set; }public LogEventArgs(DateTime timestamp, LogLevel level, string message, int processId, int threadId){Timestamp = timestamp;Level = level;Message = message;ProcessId = processId;ThreadId = threadId;}}#endregion#region 内部对象private readonly ConcurrentQueue<LogItem> _logQueue = new ConcurrentQueue<LogItem>();private readonly ManualResetEventSlim _logEvent = new ManualResetEventSlim(false);private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();private Task _processingTask;private StreamWriter _fileWriter;private DateTime _currentFileDate;private string _currentFilePath;private bool _isDisposed;private readonly object _fileLock = new object();// 默认颜色设置private static readonly Color TraceColor = Color.Gray;private static readonly Color DebugColor = Color.Blue;private static readonly Color InfoColor = Color.Black;private static readonly Color WarnColor = Color.Orange;private static readonly Color ErrorColor = Color.Red;#endregion#region 外部对象public string File { get; set; }public TextBoxBase TextBox { get; set; }public LogLevel FileLogLevel { get; set; } = LogLevel.Trace;public LogLevel TextBoxLogLevel { get; set; } = LogLevel.Trace;public event EventHandler<LogEventArgs> LogEvent;#endregion#region 外部函数public SLog(){_processingTask = Task.Run(ProcessLogQueue, _cancellationTokenSource.Token);}public void Trace(string message) => Log(LogLevel.Trace, message);public void Debug(string message) => Log(LogLevel.Debug, message);public void Info(string message) => Log(LogLevel.Info, message);public void Warn(string message) => Log(LogLevel.Warn, message);public void Error(string message) => Log(LogLevel.Error, message);#endregion#region 内部函数private void Log(LogLevel level, string message){var logItem = new LogItem{Timestamp = DateTime.Now,Level = level,Message = message,ProcessId = Process.GetCurrentProcess().Id,ThreadId = Thread.CurrentThread.ManagedThreadId};_logQueue.Enqueue(logItem);_logEvent.Set();}private void ProcessLogQueue(){// 写日志Action processLogItem = () =>{while (_logQueue.TryDequeue(out var logItem)){ProcessLogItem(logItem);}};while (!_cancellationTokenSource.Token.IsCancellationRequested){_logEvent.Wait(_cancellationTokenSource.Token);_logEvent.Reset();processLogItem();while (_logQueue.TryDequeue(out var logItem)){ProcessLogItem(logItem);}}// 处理剩余日志项processLogItem();}private void ProcessLogItem(LogItem logItem){// 触发日志事件LogEvent?.Invoke(this, new LogEventArgs(logItem.Timestamp,logItem.Level,logItem.Message,logItem.ProcessId,logItem.ThreadId));// 格式化日志消息var logMessage = FormatLogMessage(logItem);// 写入文件if (!string.IsNullOrEmpty(File) && logItem.Level >= FileLogLevel){lock (_fileLock){WriteToFile(logMessage, logItem.Timestamp);}}// 写入文本框if (TextBox != null && logItem.Level >= TextBoxLogLevel){WriteToTextBox(logMessage, logItem.Level);}}private string FormatLogMessage(LogItem logItem){return $"[{logItem.Timestamp:HH:mm:ss.ff}] [{logItem.Level}] [{logItem.ProcessId},{logItem.ThreadId}] {logItem.Message}";}private void WriteToFile(string message, DateTime timestamp){try{// 检查是否需要创建新的日志文件if (_fileWriter == null || _currentFileDate != timestamp.Date){_currentFileDate = timestamp.Date;_currentFilePath = GetFilePath(timestamp);// 确保目录存在var directory = System.IO.Path.GetDirectoryName(_currentFilePath);if (!string.IsNullOrEmpty(directory) && !System.IO.Directory.Exists(directory)){System.IO.Directory.CreateDirectory(directory);}_fileWriter?.Close();_fileWriter = new StreamWriter(_currentFilePath, true, Encoding.UTF8){AutoFlush = true};}_fileWriter.WriteLine(message);}catch (Exception ex){// 日志写入失败时,尝试输出到调试器Console.WriteLine($"Failed to write log to file: {ex.Message}");}}private string GetFilePath(DateTime timestamp){if (string.IsNullOrEmpty(File))return null;return File.Replace("yyyy", timestamp.Year.ToString("D4")).Replace("MM", timestamp.Month.ToString("D2")).Replace("dd", timestamp.Day.ToString("D2"));}private void WriteToTextBox(string message, LogLevel level){if (TextBox.InvokeRequired){TextBox.Invoke(new Action<string, LogLevel>(WriteToTextBox), message, level);return;}try{// 根据日志级别选择颜色Color color = GetColorForLevel(level);// 处理不同类型的文本框if (TextBox is RichTextBox richTextBox){// 对于RichTextBox,可以设置颜色richTextBox.SelectionStart = richTextBox.TextLength;richTextBox.SelectionLength = 0;richTextBox.SelectionColor = color;richTextBox.AppendText(message + Environment.NewLine);richTextBox.SelectionColor = richTextBox.ForeColor;}else{// 对于普通TextBox,只能使用默认颜色TextBox.AppendText(message + Environment.NewLine);}TextBox.ScrollToCaret();}catch (Exception ex){// 文本框写入失败时,尝试输出到调试器Console.WriteLine($"Failed to write log to textbox: {ex.Message}");}}private Color GetColorForLevel(LogLevel level){switch (level){case LogLevel.Trace: return TraceColor;case LogLevel.Debug: return DebugColor;case LogLevel.Info: return InfoColor;case LogLevel.Warn: return WarnColor;case LogLevel.Error: return ErrorColor;default: return TextBox.ForeColor;}}public void Dispose(){if (_isDisposed) return;_isDisposed = true;_cancellationTokenSource.Cancel();_logEvent.Set();try{_processingTask?.Wait(1000);}catch (AggregateException){// 任务取消时可能抛出异常,可以忽略}_cancellationTokenSource.Dispose();_logEvent.Dispose();lock (_fileLock){_fileWriter?.Close();_fileWriter?.Dispose();}}private struct LogItem{public DateTime Timestamp { get; set; }public LogLevel Level { get; set; }public string Message { get; set; }public int ProcessId { get; set; }public int ThreadId { get; set; }}#endregion}
}

六、总结

本文介绍的SLog类是一个简单但功能完备的日志系统,它结合了文件记录和界面显示的优势,并提供了丰富的自定义选项。通过异步处理和级别过滤,它在提供详细日志信息的同时,几乎不影响应用程序的性能。颜色区分功能使得在界面中查看日志更加直观,而事件机制则为扩展功能提供了可能。

这个实现不依赖任何第三方库,可以直接集成到任何C#项目中,特别是Windows Forms应用程序。开发者可以根据实际需求进一步扩展功能,如添加日志文件大小限制、网络日志传输等。

希望这个简单的日志实现能为您的项目开发提供便利,同时也欢迎根据实际需求进行修改和扩展。

http://www.dtcms.com/a/395203.html

相关文章:

  • atcoder经典好题
  • 【Linux】Linux文件系统详解:从磁盘到文件的奥秘
  • 【Android Keystore】Android 密钥库系统使用指南
  • RBAC权限模型实战图解:绘制企业权限矩阵,告别混乱授权
  • 【ROS2】通讯协议接口 Interface
  • Spring —— 事务控制
  • 基于vue开发的背单词网站
  • javascript 角色跟踪实践
  • 第九周作业
  • 【ThinkPHP项目添加新页面完整解决方案】
  • Thinkphp框架相关漏洞扫描器(一)
  • 【网络通讯】Qt中使用Modbus Tcp协议(附Demo)
  • 在 macOS 上使用 Windows 快捷键指南
  • pd26 虚拟机 (Mac中文)
  • 本周的股指
  • (论文速读)生成式摄影:让AI理解相机的物理世界
  • ELK 企业级日志分析系统
  • 项目日记 -日志系统 -功能完善
  • install_docker.sh
  • opencv的DNN模块里
  • FPGA学习笔记——图像处理之对比度调节(线性调节)
  • SkyWalking 核心概念与智能探针工作原理深度揭秘(上)
  • leetcode hot100 简单难度 day02-刷题
  • ARP报文格式
  • 【论文速递】2025年第26周(Jun-22-28)(Robotics/Embodied AI/LLM)
  • 用【PinMe】轻松实现前端部署(文章附有演示案例)
  • 巨坑Spring ai 之spring-ai-starter-vector-store-elasticsearch
  • 【LeetCode 每日一题】2349. 设计数字容器系统
  • i.MX6ULL移植内核6.6(一)修改网络驱动和LCD驱动
  • vue-router(vue 路由)基本使用指南(一)