C#系统日志

一、什么是"系统日志 / 日志系统",以及它的作用

  • 日志(Log):记录程序在运行时,某一时刻发生了什么(事件、数据、错误、性能)。

  • 作用

    1. 排障与回溯(哪里报错、什么时候、堆栈)

    2. 运行洞察(用户行为、接口耗时、业务关键路径)

    3. 合规审计(谁做了什么)

    4. 运维监控(通过日志采集/告警)

二、传统日志信息

初学时采用控制台显示程序运行结果是 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,字段的值可能被多个线程同时修改,不要优化缓存,每次都要去内存里取最新的值。

  • 常用场景:线程通信(标志位)

IDisposableDispose()

  • 作用 :用于 释放资源(文件句柄、数据库连接、线程等)。

  • 模式 :实现 IDisposable 接口 → 提供 Dispose() 方法 → 在 using 里用时自动释放。

BlockingCollection<T>

  • 作用 :线程安全的集合,用于 生产者-消费者模式

  • 特点 :如果队列满了,Add 会阻塞;如果队列空了,Take 会阻塞。

  • 应用:日志异步写入、任务队列。

IsBackground = true

  • 作用 :设置线程为 后台线程,当主程序退出时,后台线程会自动结束(不会阻止进程退出)。

  • 区别:前台线程必须执行完,程序才会结束。

sealed 关键字

  • 意思:禁止类被继承(final class)。

  • 作用

    1. 设计意图明确:告诉别人这个类就是最终实现,不允许再派生子类。

    2. 安全性:防止子类随意更改逻辑(比如日志单例类,如果能被继承,可能会破坏单例模式)。

    3. 性能优化 :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.生产了操作日志在本地。

相关推荐
一只雄牧慕2 小时前
【C++】哈希表
开发语言·数据结构·c++·散列表
cici158743 小时前
在Ubuntu18.04安装兼容JDK 8的Eclipse集成开发环境
java·开发语言·eclipse
不枯石3 小时前
Matlab通过GUI实现点云的统计滤波(附最简版)
开发语言·图像处理·算法·计算机视觉·matlab
代码村新手3 小时前
C语言-操作符
开发语言·c++
天天进步20153 小时前
Python项目--交互式VR教育应用开发
开发语言·python·vr
啃啃大瓜4 小时前
字典 dictionary
开发语言·python
无所事事的海绵宝宝4 小时前
使用python+flask设置挡板
开发语言·python·flask
A.A呐4 小时前
【QT第一章】QT基础知识
开发语言·c++·qt
eqwaak04 小时前
Flask与Django:Python Web框架的哲学对决
开发语言·python·科技·django·flask