C#系统日志
一、什么是“系统日志 / 日志系统”,以及它的作用
日志(Log):记录程序在运行时,某一时刻发生了什么(事件、数据、错误、性能)。
作用
排障与回溯(哪里报错、什么时候、堆栈)
运行洞察(用户行为、接口耗时、业务关键路径)
合规审计(谁做了什么)
运维监控(通过日志采集/告警)
二、传统日志信息
初学时采用控制台显示程序运行结果是 Console.WriteLine("hello world!");
1.运行代码
static void Main(string[] args){Console.WriteLine("hello world!"); //日志监控1return; //模拟异常退出Console.WriteLine("OK");//日志监控2}
2.运行结果
3.缺点:没有时间、没有存储、没有区分日志信息等级
围绕以上缺点去开可以能满足自己的日志系统。当然也有一些比较好用的第三方日志库,但建议自己开发,比较是学习。
三、自定义日志信息
1.添加日志时间
internal class Program{static void Main(string[] args){Log("hello world!"); //日志监控1return; //模拟异常退出Log("OK");//日志监控2}static void Log(string message) {string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss:fff}]{message}";Console.WriteLine(logEntry);}}
封装一个Log方法,每当日志显示时,自动添加当前的时间。
[2025-08-22 23:29:05:862]hello world!
2.添加本地存储(.txt)
internal class Program{static void Main(string[] args){Log("Debug", "This is a debug message");}static void Log(string level, string message) {string logFilePath = $"d:/log-{DateTime.Now:yyyy-MM-dd}.txt";string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss:fff}] [{level}] {message}";Console.WriteLine(logEntry);using (var writer = new StreamWriter(logFilePath, true, Encoding.UTF8)){writer.AutoFlush = false; writer.WriteLineAsync(logEntry);}}}
[2025-09-02 00:12:58:142] [Debug] This is a debug messageE:\code\c#\TestLog\TestLog\bin\Debug\TestLog.exe (进程 13340)已退出,代码为 0 (0x0)。
按任意键关闭此窗口. . .
3.添加日志类型
定义日志级别,例如 Info、Warning、Error、Debug、Log。 如果日志多的话,在.txt分类基本查询。
internal class Program{static void Main(string[] args){for (int i = 0; i < 1000; i++){if (i < 500){Log("debug", i.ToString());continue;}Log("info", i.ToString());}}static void Log(string level, string message) {string logFilePath = $"d:/log/log-{DateTime.Now:yyyy-MM-dd}.txt";string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss:fff}] [{level}] {message}";Console.WriteLine(logEntry);using (var writer = new StreamWriter(logFilePath, true, Encoding.UTF8)){writer.AutoFlush = false; writer.WriteLineAsync(logEntry);}}}
4.封装成单例者模式
一个类在整个应用程序生命周期里只会有唯一一个实例,并且提供一个全局的访问点。
internal class Program{public sealed class Logger{// 1) 单例(全局唯一)private static readonly Lazy<Logger> _instance = new Lazy<Logger>(() => new Logger());public static Logger Instance => _instance.Value;private readonly string _basePath = "d:/log";// 2) 构造函数私有化private Logger(){if (!Directory.Exists(_basePath)){Directory.CreateDirectory(_basePath);}}// 3) 日志方法public void Log(string level, string message){string logFilePath = Path.Combine(_basePath, $"log-{DateTime.Now:yyyy-MM-dd}.txt");string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss:fff}] [{level}] {message}";Console.WriteLine(logEntry);// 写入文件using (var writer = new StreamWriter(logFilePath, true, Encoding.UTF8)){writer.WriteLine(logEntry);}}// 便捷方法(可选)public void Debug(string msg) => Log("debug", msg);public void Info(string msg) => Log("info", msg);public void Error(string msg) => Log("error", msg);}static void Main(string[] args){//方法一Logger.Instance.Debug("这是一个Debug日志");Logger.Instance.Info("这是一个Info日志");Logger.Instance.Error("程序结束出现了一个错误示例");//方法二Logger.Instance.Log("TCP", "TPC通讯已打开!");Logger.Instance.Log("相机日志", "相机驱动已连接!");}}
[2025-09-02 00:26:59:803] [debug] 这是一个Debug日志
[2025-09-02 00:26:59:803] [info] 这是一个Info日志
[2025-09-02 00:26:59:803] [error] 程序结束出现了一个错误示例
[2025-09-02 00:26:59:804] [TCP] TPC通讯已打开!
[2025-09-02 00:26:59:804] [相机日志] 相机驱动已连接!E:\code\c#\TestLog\TestLog\bin\Debug\TestLog.exe (进程 3344)已退出,代码为 0 (0x0)。
按任意键关闭此窗口. . .
5.异步日志
用 线程安全队列(Channel 或 BlockingCollection) 缓存日志;
启动一个 后台 Task 专门把日志写到文件;
主线程只负责把日志塞进队列,性能更高。
internal class Program{public sealed class Logger : IDisposable{//readonly 只读// 单例private static readonly Lazy<Logger> _instance = new Lazy<Logger>(() => new Logger()); public static Logger Instance => _instance.Value;private readonly string _basePath = @"d:\log";private readonly BlockingCollection<string> _queue;private readonly Thread _worker;private volatile bool _running = true;private Logger(){if (!Directory.Exists(_basePath))Directory.CreateDirectory(_basePath);_queue = new BlockingCollection<string>(new ConcurrentQueue<string>());_worker = new Thread(WorkerLoop){IsBackground = true,Name = "LoggerWorker"};_worker.Start();}public void Log(string level, string message){string logFilePath = Path.Combine(_basePath, $"log-{DateTime.Now:yyyy-MM-dd}.txt");string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss:fff}] [{level}] {message}";Console.WriteLine(logEntry);_queue.Add($"{logFilePath}|{logEntry}");}public void Debug(string msg) => Log("debug", msg);public void Info(string msg) => Log("info", msg);public void Error(string msg) => Log("error", msg);private void WorkerLoop(){Console.WriteLine("OK");while (_running || _queue.Count > 0){try{string item;if (!_queue.TryTake(out item, Timeout.Infinite)) {continue;}var parts = item.Split('|');var logFilePath = parts[0];var logEntry = parts[1];using (var writer = new StreamWriter(logFilePath, true, Encoding.UTF8)){writer.WriteLine(logEntry);}}catch (Exception ex){// 写日志出错时,写到控制台Console.Error.WriteLine("Logger error: " + ex);}}}public void Dispose(){_running = false;_queue.CompleteAdding();if (!_worker.Join(2000)){_worker.Abort(); }}}static void Main(string[] args){//方法一Logger.Instance.Debug("这是一个Debug日志");Logger.Instance.Info("这是一个Info日志");Logger.Instance.Error("程序结束出现了一个错误示例");//方法二Logger.Instance.Log("TCP", "TPC通讯已打开!");Logger.Instance.Log("相机日志", "相机驱动已连接!");}}
1.知识扩张(来自AI总结)
readonly
作用:修饰字段,使字段只能在 定义时 或 构造函数里 赋值,之后不能修改。
好处:避免对象生命周期中被错误更改。
volatile
作用:告诉编译器和 CPU,字段的值可能被多个线程同时修改,不要优化缓存,每次都要去内存里取最新的值。
常用场景:线程通信(标志位)
IDisposable
和 Dispose()
作用:用于 释放资源(文件句柄、数据库连接、线程等)。
模式:实现
IDisposable
接口 → 提供Dispose()
方法 → 在using
里用时自动释放。
BlockingCollection<T>
作用:线程安全的集合,用于 生产者-消费者模式。
特点:如果队列满了,
Add
会阻塞;如果队列空了,Take
会阻塞。应用:日志异步写入、任务队列。
IsBackground = true
作用:设置线程为 后台线程,当主程序退出时,后台线程会自动结束(不会阻止进程退出)。
区别:前台线程必须执行完,程序才会结束。
sealed
关键字
意思:禁止类被继承(final class)。
作用:
设计意图明确:告诉别人这个类就是最终实现,不允许再派生子类。
安全性:防止子类随意更改逻辑(比如日志单例类,如果能被继承,可能会破坏单例模式)。
性能优化:JIT 编译器对
sealed
类的虚方法调用可以优化得更快。
四、打包封装DLL
1.创建类库(.NET Framework)
2.日志程序写入你新建的DLL
3.运行 生产DLL。
五、DLL调用
1.创建程序,调用生产的日志DLL。安装以下步骤添加。
2.记得勾上添加的DLL。
3.检查引用区域是添加。
4.添加自己的namespace 。我的是using myLog;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using myLog;namespace TestLogApp
{public partial class Form1 : Form{public Form1(){InitializeComponent();Logger.Instance.Info("Form1 初始化完成!");}private void button1_Click(object sender, EventArgs e){Logger.Instance.Info("点击了按钮1");}private void button2_Click(object sender, EventArgs e){Logger.Instance.Debug("点击了按钮2");}private void button3_Click(object sender, EventArgs e){Logger.Instance.Error("点击了按钮3");}private void Form1_Load(object sender, EventArgs e){Logger.Instance.Info("Form1 加载完成!");}}
}
5.大概做几个按钮 监控按钮的使用情况。
6.生产了操作日志在本地。