首页 > 数据库 >C#使用log4net和sqlite数据库记录日志

C#使用log4net和sqlite数据库记录日志

时间:2024-12-19 14:09:58浏览次数:3  
标签:log4net sqlite string lvwLog C# private new 日志

1 安装包

两个包:

  • log4net
  • System.Data.SQLite

第二个包也可以使用Microsoft.Data.Sqlite,查到的资料显示如果环境使用的是 .NET Core 或 .NET 5+,则建议使用Microsoft.Data.Sqlite。但是我并没有测试第二个包,可能使用上有区别。

2 下载Sqlite

如果本地没有sqlite环境的话,需要先下载。官网下载链接

进去之后直接找各自环境对应的版本,如果是windows环境的话,直接下载下图中标记的tool,中间那个下载链接是下载sqlite3.dll,不过我并不清楚如何使用。

tool解压之后有如下几个文件,双击打开sqlite3.exe即可。

3 Sqlite常用命令

打开是一个命令行界面,可以使用.help查看常用的命令及解释。

.help

创建数据库文件使用.open xxx,这条语句,如果发现数据库文件存在,就会直接打开,如果不存在,就会先创建再打开。

.open test.db

在目录内可以看到创建的数据库文件。

.databases可以查询所有数据库文件

.tables可以查询所有表(我还未创建,所以目前还没有表)

sql语句的话可以查询相关资料。

查询的数据以标准格式显示。

.header on
.mode column
SELECT * FROM COMPANY;

当然sqlite也有可视化的软件,但是我目前没用到,所以没有下载安装,需要的话可以自行查询。

4 创建日志相关基本表

使用命令创建日志表,包含id(使用自增,当然可以换成uuid或者其它形式)、日期、线程号、级别(info、error这些)、记录者、具体记录的信息、异常信息。具体内容要对应log4net的配置。

CREATE TABLE Log (
  Id INTEGER PRIMARY KEY AUTOINCREMENT,
  Date DATETIME,
  Thread VARCHAR(255),
  Level VARCHAR(50),
  Logger VARCHAR(255),
  Message TEXT,
  Exception TEXT
);

5 log4net配置

更换数据库连接。sql语句的内容对应数据库基本表的字段。

<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
  </configSections>

  <log4net>
    <appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
      <bufferSize value="1" />
      <connectionType value="System.Data.SQLite.SQLiteConnection, System.Data.SQLite" />
      <connectionString value="Data Source=./database/logs.db;Version=3;" />
      <commandText value="INSERT INTO Log (Date, Thread, Level, Logger, Message, Exception) VALUES (@log_date, @thread, @log_level, @logger, @message, @exception)" />
      
      <parameter>
        <parameterName value="@log_date" />
        <dbType value="DateTime" />
        <layout type="log4net.Layout.RawTimeStampLayout" />
      </parameter>
      <parameter>
        <parameterName value="@thread" />
        <dbType value="String" />
        <size value="255" />
        <layout type="log4net.Layout.PatternLayout">
          <conversionPattern value="%thread" />
        </layout>
      </parameter>
      <parameter>
        <parameterName value="@log_level" />
        <dbType value="String" />
        <size value="50" />
        <layout type="log4net.Layout.PatternLayout">
          <conversionPattern value="%level" />
        </layout>
      </parameter>
      <parameter>
        <parameterName value="@logger" />
        <dbType value="String" />
        <size value="255" />
        <layout type="log4net.Layout.PatternLayout">
          <conversionPattern value="%logger" />
        </layout>
      </parameter>
      <parameter>
        <parameterName value="@message" />
        <dbType value="String" />
        <size value="4000" />
        <layout type="log4net.Layout.PatternLayout">
          <conversionPattern value="%message" />
        </layout>
      </parameter>
      <parameter>
        <parameterName value="@exception" />
        <dbType value="String" />
        <size value="2000" />
        <layout type="log4net.Layout.ExceptionLayout" />
      </parameter>
    </appender>

    <root>
      <level value="ALL" />
      <appender-ref ref="AdoNetAppender" />
    </root>
  </log4net>
</configuration>

6 日志记录

把生成的db文件拷到程序里,这个文件就是记录文件的数据库了,其它的都不重要。当然,如果想查看数据库的话,也可以把sqlite3.exe拷过来。

读取log4net的配置建议写在AssemblyInfo.cs,这样程序启动时会默认加载配置文件。

//Log4net配置
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config", Watch = true)]

具体程序如下:

private static readonly ILog log = LogManager.GetLogger(typeof(Form1));

log.Info("这是一条info语句");
log.Warn("这是一条warn语句");
log.Error("这是一条错误语句", new Exception("测试异常"));

查看日志是否正常写入数据库。

7 使用C#程序查询sqlite

程序如下:

// 构建连接字符串
string connectionString = $"Data Source=./database/SoftWareBaseLog.db;Version=3;";

// 创建 SQLite 连接
using (var connection = new SQLiteConnection(connectionString))
{
    connection.Open();

    // 创建 SQL 命令
    string sql = "SELECT Message FROM Log where Level='ERROR'";
    using (var command = new SQLiteCommand(sql, connection))
    {
        // 执行命令并读取数据
        using (var reader = command.ExecuteReader())
        {
            while (reader.Read())
            {
                string msg = reader.GetString(0);
                Console.WriteLine(msg);
            }
        }
    }
}

8 实时显示日志

现在所有日志都写到数据库里了,那要是还想实时显示到界面上,当然也有很多方式实现,不过我这里建议实时显示可以使用log4net的自有功能。

比如我想使用winform中的listbox来实时显示日志,可以建立一个Appender(附加),继承于log4net的AppenderSkeleton,这是一个抽象类,有一个抽象方法。

具体的,可以参考以下程序,这里会显示所有的日志,如果需要过滤的话,可以在这个基础上改。另外,一定一定一定要给this.Layout赋值,这是日志在界面上的显示方式,如果没有写的话,就会收获一个报错:“A layout must be set”,去网上搜这条内容,不一定能找到解决方案。

public class ListBoxAppender : AppenderSkeleton
{
    private ListBox _ListBox;

    public ListBoxAppender(ListBox box)
    {
        _ListBox = box;
        this.Layout = new PatternLayout("%date [%thread] %-5level %logger - %message%newline");
    }

    protected override void Append(LoggingEvent loggingEvent)
    {
        // 获取日志信息
        string logMessage = RenderLoggingEvent(loggingEvent);

        // 更新 UI 控件
        if (_ListBox.InvokeRequired)
        {
            _ListBox.Invoke(new Action(() => AppendText(logMessage)));
        }
        else
        {
            AppendText(logMessage);
        }
    }

    private void AppendText(string text)
    {
        _ListBox.Items.Add(text);
    }
}

只需要给log4net配置一次就可以使用,这样,每次调用日志记录,界面上的lbxLog控件就可以一直显示最新的日志信息。

// 添加自定义 Appender
var listBoxAppender = new ListBoxAppender(lbxLog);
log4net.Config.BasicConfigurator.Configure(listBoxAppender);

但是,测试出现了问题,在多线程、短时间添加多条日志的情况下会导致界面卡死,猜测是多条日志抢一个listbox的索引出现的问题,更好的方案是添加缓存机制,生产者-消费者模式。

生产者只记录到队列里即可,这里为了避免队列堆积,使用了定长的一个概念,太多的日志堆积会丢掉一部份数据。

public class LogQueueAppender : AppenderSkeleton
{
    /// <summary>
    /// 锁对象
    /// </summary>
    private static readonly object _lock = new object();

    private static LogQueueAppender Instance { get; set; }

    /// <summary>
    /// 日志队列
    /// 不再直接更新控件,在日志频繁时会出现控件卡死的情况
    /// </summary>
    private ConcurrentQueue<string> LogQueue { get; } = new ConcurrentQueue<string>();

    private LogQueueAppender()
    {
        //Layout必须赋值,否则会报错
        this.Layout = new PatternLayout("%date [%thread] %-5level %logger - %message%newline");
    }

    /// <summary>
    /// 获取实体对象
    /// </summary>
    /// <returns></returns>
    public static LogQueueAppender GetInstance()
    {
        if (Instance == null)
        {
            lock (_lock)
            {
                if (Instance == null)
                {
                    Instance = new LogQueueAppender();
                }
            }
        }

        return Instance;
    }

    /// <summary>
    /// 获取日志队列
    /// </summary>
    /// <returns></returns>
    public ConcurrentQueue<string> GetLogQueue()
    {
        return LogQueue;
    }

    /// <summary>
    /// 覆写Append方法
    /// </summary>
    /// <param name="loggingEvent"></param>
    protected override void Append(LoggingEvent loggingEvent)
    {
        // 获取日志信息
        string logMessage = RenderLoggingEvent(loggingEvent);
        AppendText(logMessage);
    }

    /// <summary>
    /// 添加日志信息
    /// </summary>
    /// <param name="text"></param>
    private void AppendText(string text)
    {
        //最多存储200条日志,多余的会直接丢弃
        if (LogQueue.Count >= 200)
        {
            LogQueue.TryDequeue(out string message);                
        }
        LogQueue.Enqueue(text);
    }

而消费者负责将日志信息更新到界面上,这里每次只更新一条日志信息,我认为足够了,当然这可以改成一次更新多条信息,避免日志堆积。

/// <summary>
/// 读取日志信息
/// </summary>
private void LoadLog()
{
    LogThreadRunFlag = true;
    Task.Factory.StartNew(() => {
        while (LogThreadRunFlag == true)
        {
            bool flag = LogQueueAppender.GetInstance().GetLogQueue().TryDequeue(out string log);
            if (flag)
            {
                lvwLog.Invoke(new Action(() => AppendText(log)));
            }
            Thread.Sleep(50);
        }
    }, TaskCreationOptions.LongRunning);           
}

/// <summary>
/// 添加到界面中
/// </summary>
/// <param name="text">日志信息</param>
private void AppendText(string text)
{
    if (lvwLog.Items.Count == 100)
    {
        lvwLog.Items.Clear();
    }
    lvwLog.Items.Add(text);
    if (text.Contains("ERROR"))
    {
        lvwLog.Items[lvwLog.Items.Count - 1].ForeColor = Color.Red;
    }
}

另外,控件换成了listview,因为这个可以很方便地“高亮”错误日志。相关listview的设置如下:

lvwLog.View = View.Details; // 设置为竖向排列
lvwLog.Scrollable = true; // 确保滚动条可用
lvwLog.Columns.Add("日志信息", -2);   //添加一列数据,并占满全行
lvwLog.DoubleBuffered(true);    //使用双缓冲,避免闪烁

效果如下:

标签:log4net,sqlite,string,lvwLog,C#,private,new,日志
From: https://www.cnblogs.com/cunzai/p/18617127

相关文章

  • 关于CH32系列MCU GPIO使用
    1、关于CH32V103PD0/PD1引脚使用PD0、PD1引脚为外部HSE晶振引脚,作为普通GPIO使用的时候注意:需要关闭外部晶振,开启复用时钟,使用HSI配置系统主频,否则无法正常运行。  2、关于CH32V003PA1/PA2引脚时用PA1、PA2引脚可以作为外部晶振引脚使用,注意若要作为普通GPIO使用时,需要......
  • JackJson的@JsonAutoDetect注解
    1、@JsonAutoDetect(作用在类上):自动检测fieldVisibility:字段的可见级别;getterVisibility:getter方法的可见级别;setterVisibility:setter方法的可见级别。ANY:任何级别的字段都可以自动识别NONE:任何字段都不可以自动识别NON_PRIVATE:非private修饰的字段可以自动识别PROTECTED_OR......
  • 产品经理-竞争分析报告(CAR) - AxureMost
    竞争分析报告(CAR)-AxureMost竞争分析报告(CompetitiveAnalysisReport)是一种系统性的评估工具旨在帮助企业了解和分析市场上的竞争对手。通过全面和深入地研究竞争对手,企业可以制定有效的市场策略和商业决策,从而在激烈的市场竞争中保持竞争力。竞争分析报告的定义竞争......
  • JUC视频学习
    第一节锁相关知识线程的并发问题的由来?比如这里的要执行i++操作;我们一般时在主存存储i的值,然后再cpu进行运算的;所以这里的步骤分为三部:1.cpu从内存获取i的值;2.cpu执行运算+1操作;3.cpu将计算结果进行返回;假设我们这里有两台cpu,一个cpu1一个cpu2;如果cpu1和cpu2同时获取到了......
  • 找不到SmartXpButton.ocx文件或SmartXpButton.ocx文件丢失该怎么办?
    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题,如果是新手第一时间会认为是软件或游戏出错了,其实并不是这样,其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库,这时你可以下载这个SmartXpButton.ocx文件(挑选合适的版本文件)......
  • 找不到shlobj71.ocx文件或shlobj71.ocx文件丢失该怎么办?
    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题,如果是新手第一时间会认为是软件或游戏出错了,其实并不是这样,其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库,这时你可以下载这个shlobj71.ocx文件(挑选合适的版本文件)把它......
  • 容器化技术全面解析:Docker 与 Containerd 的深入解读
    目录Docker简介1.什么是Docker?2.Docker的核心组件3.Docker的主要功能4.Docker的优点5.Docker的使用场景Containerd简介1.什么是Containerd?2.Containerd的核心特性3.Containerd的架构4.Containerd与Docker的关系5.Containerd的优点6.Con......
  • 使用dockerfile构建python3的镜像并启动容器另一种方式
    使用dockerfile构建python3的镜像并启动容器另一种方式1.新建目录DockerfilesmkdirDockerfilescdDockerfiles2.新建文件DockerfiletouchDockerfile写入以下内容:FROMcentos:7.6.1810MAINTAINERtest#指定作者信息RUNset-ex\#预安装所需组件&......
  • 产品经理-竞品分析报告(CAR) - AxureMost
    竞品分析报告(CAR)-AxureMost竞品分析报告定义竞品分析报告(CompetitiveAnalysisReport)是一种系统性的分析文档,旨在对市场上具有竞争关系的产品或服务进行深入研究,以了解它们的特性、优势、劣势,以及它们在市场中的表现和地位。通过这种分析,企业或个人可以更好地理解市场现......
  • Mybatis 升级 Mybatis Plus 重写 Mybatis Plus selectList,如果将参数传到 Mapp.xml
    目录Mybatis写法EntityMapperServiceMapper.xmlTestMybatisPlusEntityMapperServiceMapper.xmlTestMybatis升级MybatisPlus将实体做为条件参数带到Mapp.xml中的自定义SQLMybatis写法通过pagehelper进行分页EntitypublicclassActivityTrackingimplementsSeri......