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

news/2025/9/26 11:53:45/文章来源:https://www.cnblogs.com/yxysuanfa/p/19110458

在软件开发过程中,日志记录是一个不可或缺的功能。它不仅能帮助开发者调试程序,还能在出现问题时提供重要的上下文信息。本文将介绍如何使用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.mzph.cn/news/916710.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端笔记:vue中 Map、Set之间的采用和区别

前端笔记:vue中 Map、Set之间的采用和区别2025-09-25 08:01 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: blo…

性能暴涨50%:PD分离KV cache传输的实战干货

微信视频号:sph0RgSyDYV47z6快手号:4874645212抖音号:dy0so323fq2w小红书号:95619019828B站1:UID:3546863642871878B站2:UID: 3546955410049087 最近我们团队[1]在vLLM上开发了一种KV cache传输的connector,实现…

加强对网站建设php做的网站如何运行

在C语言编程中&#xff0c;使用scanf函数输入字符串是一项基本操作。然而&#xff0c;当我们尝试在for循环中使用scanf输入字符串时&#xff0c;可能会遇到意外的问题&#xff0c;导致循环无法正常执行。本文将深入探讨这个问题&#xff0c;并提供解决方案&#xff0c;让你能够…

做网站店铺图片用什么软件株洲网站建设的企业

目录 1.什么是链表&#xff1f; 2.链表的分类 &#xff08;1&#xff09;无头单向非循环链表&#xff1a; &#xff08;2&#xff09;带头双向循环链表&#xff1a; 3.单链表的实现 &#xff08;1&#xff09;单链表的定义 &#xff08;2&#xff09;动态创建节点 &#…

【GitHub每日速递 250925】 一套代码跑遍全平台!Flutter 让你的应用开发提速 10 倍

原文:https://mp.weixin.qq.com/s/diCHi4TGuroj4IuJL1j2zA Mac用户福音!Ice菜单管理神器,功能超多超强大! Ice 是一个功能强大的菜单栏管理工具的 macOS 应用。简单讲,它能帮你自动整理和隐藏菜单栏图标,让顶部状…

网站建设报价模板四川住房和建设厅网站

技术背景 我们在做执法记录仪或指挥系统的时候&#xff0c;会遇到这样的情况&#xff0c;大多场景下&#xff0c;我们是不需要把设备端的数据&#xff0c;实时传给国标平台端的&#xff0c;默认只需要本地录像留底&#xff0c;如果指挥中心需要查看前端设备实时数据的时候&…

会展免费网站模板公司域名不变网站做变动

本文收录于《AI绘画从入门到精通》专栏,订阅后可阅读专栏内所有文章,专栏总目录:点这里。 大家好,我是水滴~~ 本文主要介绍在 Stable Diffusion WebUI 中使用 ControlNet 的 depth_zoe 预处理器时,出现的 RuntimeError: Error(s) in loading state_dict for ZoeDepth 异常…

未来做啥网站能致富推广计划ppt

Re介绍一下CentOS下MySQL数据库的安装与配置方法MySQL数据库配置的具体步骤&#xff1a;1、编辑MySQL的配置文件&#xff0c;使用vi /etc/my.cnf[rootsample ~]# vi /etc/my.cnf  ← 编辑MySQL的配置文件[mysqld]datadir/var/lib/mysqlsocket/var/lib/mysql/mysql.sock# Defau…

本网站服务器多个wordpress 合并

企智汇项目管理软系统是一款支持私有化部署的项目管理系统&#xff0c;适合大型、中型、小型企业的不同需求。企智汇软件是一个专业的项目全周期管理平台&#xff0c;拥有10年的项目管理经验&#xff0c;以项目为中心&#xff0c;通过计划控制进度&#xff0c;预算控制成本&…

上海芯片上市公司市值大揭秘!谁是“芯”界顶流?

微信视频号:sph0RgSyDYV47z6快手号:4874645212抖音号:dy0so323fq2w小红书号:95619019828B站1:UID:3546863642871878B站2:UID: 3546955410049087上海芯片上市公司市值大揭秘!谁是“芯”界顶流?上海作为中国集成…

如何设计一个高端网站简洁大方大气wap啥梗

Primary介绍 Primary 是 Spring 框架中的一个注解&#xff0c;用于在多个相同类型的 bean 中指定一个默认的 bean。当 Spring 容器在自动装配时遇到类型冲突&#xff0c;即存在多个相同类型的 bean 时&#xff0c;如果没有使用 Qualifier 或其他方式指定具体的 bean&#xff0…

百度云分享tp响应式网站开发国家标准下载网免费

首先介绍计算机的二进制码 二进制常用的有原码&#xff0c;反码和补码&#xff0c;他们都是由最左边的一个符号位和右边的数值位构成。在计算机中为了更低成本的计算&#xff0c;数据都是用补码来存储和运算的。 原码 最高位表示符号位&#xff08;0代表正数&#xff0c;1代…

建设工程扣分查询网站wordpress 倒计时

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

湖南太平洋建设集团网站猴王水果竞猜网站建设

关键字static的作用是什么&#xff1f; 这个简单的问题很少有人能回答完全。在C语言中&#xff0c;关键字static有三个明显的作用&#xff1a; 1、在函数体&#xff0c;一个被声明为静态的变量在这一函数被调用过程中维持其值不变。 2、 在模块内&#xff08;但在函数体外&…

网站成本费用网络营销效果评估的作用

1.工具安装 我们使用 ffmpeg 软件来完成转换工作1.1 安装命令 sudo add-apt-repository ppa:jonathonf/ffmpeg-3sudo apt-get updatesudo apt-get install ffmpeg1.2 转换命令 &#xff08;1&#xff09;直接转换命令&#xff1a; ffmpeg -i out.mp4 out.gif(2) 带参数命令&…

如何查看网站备案信息吗陕西建站公司

家里断网了&#xff0c;女票说要看电影。电影之前早已下好&#xff0c;奈何播放器不给力&#xff0c;播放不了rmvb格式&#xff0c;怎么办&#xff1f; 办法很简单&#xff0c;使用浏览器来播放&#xff01;现在的浏览器都内置了视频的解码器&#xff0c;直接将视频拖进浏览器…

做美图 网站网页设计视频

第一步&#xff1a;以管理员身份进行登陆&#xff1a; sqlplus / as sysdba 第二步&#xff1a;打开可插拔数据库 alter pluggable database pdborcl open; 第二步&#xff1a;切换至可插拔数据库 alter session set containerpdborcl;

贵州交通建设集团网站合肥蜀山网站开发

一、提出问题 对于生活生产中的表格数据&#xff0c;至多也就上百维&#xff0c;而且表格数据的行与行之间没有序列和位置上的关系&#xff0c;所以用传统的机器学习算法就可轻松的解决这些问题。但是到了图片数据&#xff0c;传统机器学习就非常吃力了&#xff0c;一个普通的…

做网站要考虑什么网站建设方案及报

作为一名偏后台程序测试的测试工程师&#xff0c;经常会接触运维相关工作&#xff0c;与服务器打交道。 一般公司会搭建内网环境和外网环境&#xff0c;项目开发、SIT测试主要是在内网环境做&#xff0c;然后等到UTA、预投产阶段在外网环境。 如果公司业务量大&#xff0c;多个…