一、什么是"系统日志 / 日志系统",以及它的作用
-
日志(Log):记录程序在运行时,某一时刻发生了什么(事件、数据、错误、性能)。
-
作用
-
排障与回溯(哪里报错、什么时候、堆栈)
-
运行洞察(用户行为、接口耗时、业务关键路径)
-
合规审计(谁做了什么)
-
运维监控(通过日志采集/告警)
-
二、传统日志信息
初学时采用控制台显示程序运行结果是 Console.WriteLine("hello world!");
1.运行代码
cs
static void Main(string[] args)
{
Console.WriteLine("hello world!"); //日志监控1
return; //模拟异常退出
Console.WriteLine("OK");//日志监控2
}
2.运行结果

3.缺点:没有时间、没有存储、没有区分日志信息等级
围绕以上缺点去开可以能满足自己的日志系统。当然也有一些比较好用的第三方日志库,但建议自己开发,比较是学习。
三、自定义日志信息
1.添加日志时间
cs
internal class Program
{
static void Main(string[] args)
{
Log("hello world!"); //日志监控1
return; //模拟异常退出
Log("OK");//日志监控2
}
static void Log(string message) {
string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss:fff}]{message}";
Console.WriteLine(logEntry);
}
}
封装一个Log方法,每当日志显示时,自动添加当前的时间。
XML
[2025-08-22 23:29:05:862]hello world!
2.添加本地存储(.txt)
cs
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);
}
}
}
XML
[2025-09-02 00:12:58:142] [Debug] This is a debug message
E:\code\c#\TestLog\TestLog\bin\Debug\TestLog.exe (进程 13340)已退出,代码为 0 (0x0)。
按任意键关闭此窗口. . .

3.添加日志类型
定义日志级别,例如 Info、Warning、Error、Debug、Log。 如果日志多的话,在.txt分类基本查询。
cs
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.封装成单例者模式
一个类在整个应用程序生命周期里只会有唯一一个实例,并且提供一个全局的访问点。
cs
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("相机日志", "相机驱动已连接!");
}
}
cs
[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 专门把日志写到文件;
-
主线程只负责把日志塞进队列,性能更高。
cs
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;
cs
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.生产了操作日志在本地。
