C# + SQLite 实现工业物联网中设备日志记录

160 阅读6分钟

前言

在工业自动化、物联网和设备监控领域,记录设备运行日志是保障系统稳定性和后续问题追踪的关键环节。一个完善的日志系统不仅能帮助开发者快速定位异常,还能为运维人员提供数据支持,从而实现对系统的持续优化。

本文将基于 C#SQLite 数据库,构建一个轻量级但功能强大的设备运行日志记录系统,并借助 Spectre.Console 库,为控制台输出添加美观的视觉效果,提升用户体验和可读性。

正文

一、为什么选择 SQLite?

SQLite 是一种嵌入式关系型数据库,具有以下显著优势:

零配置,无需安装独立的服务端;

单文件存储,便于备份、迁移和版本管理;

资源占用低,适合资源受限环境;

支持标准 SQL 查询,具备良好的扩展性;

跨平台兼容性强,适用于 Windows、Linux、macOS 等多种操作系统。

这些特点使其成为嵌入式系统、桌面应用和小型服务的理想选择。

二、系统设计概览

本系统主要由以下几个核心组件构成:

1、日志类型枚举(LogType)

2、设备日志实体类(DeviceLog)

3、日志管理器(DeviceLogManager)

4、控制台展示界面(使用 Spectre.Console)

1、日志类型与实体定义

我们首先定义日志的基本结构:

// 日志类型枚举
public enum LogType
{
    Info,     // 一般信息
    Warning,  // 警告信息
    Error,    // 错误信息
    Critical  // 严重错误
}

// 设备日志实体类
public class DeviceLog
{
    public int Id { get; set; }
    public DateTime Timestamp { get; set; }
    public string DeviceId { get; set; }
    public LogType LogLevel { get; set; }
    public string Message { get; set; }
    public double? Temperature { get; set; }  // 可选字段
    public double? Voltage { get; set; }      // 可选字段
}

2、数据库操作模块(DeviceLogManager)

DeviceLogManager 负责日志的增删查改操作,封装了与 SQLite 的交互逻辑:

public class DeviceLogManager
{
    private string _connectionString;

    public DeviceLogManager(string dbPath)
    {
        _connectionString = $"Data Source={dbPath};Version=3;";
        InitializeDatabase();
    }

    private void InitializeDatabase()
    {
        using var connection = new SQLiteConnection(_connectionString);
        connection.Open();
        using var command = new SQLiteCommand(connection);
        command.CommandText = @"
            CREATE TABLE IF NOT EXISTS DeviceLogs (
                Id INTEGER PRIMARY KEY AUTOINCREMENT,
                Timestamp DATETIME NOT NULL,
                DeviceId TEXT NOT NULL,
                LogLevel INTEGER NOT NULL,
                Message TEXT NOT NULL,
                Temperature REAL,
                Voltage REAL
            )";
        command.ExecuteNonQuery();
    }

    public void LogEvent(DeviceLog log)
    {
        using var connection = new SQLiteConnection(_connectionString);
        connection.Open();
        using var command = new SQLiteCommand(connection);
        command.CommandText = @"
            INSERT INTO DeviceLogs 
            (Timestamp, DeviceId, LogLevel, Message, Temperature, Voltage) 
            VALUES 
            (@Timestamp, @DeviceId, @LogLevel, @Message, @Temperature, @Voltage)";
        command.Parameters.AddWithValue("@Timestamp", log.Timestamp);
        command.Parameters.AddWithValue("@DeviceId", log.DeviceId);
        command.Parameters.AddWithValue("@LogLevel", (int)log.LogLevel);
        command.Parameters.AddWithValue("@Message", log.Message);
        command.Parameters.AddWithValue("@Temperature", log.Temperature ?? DBNull.Value);
        command.Parameters.AddWithValue("@Voltage", log.Voltage ?? DBNull.Value);
        command.ExecuteNonQuery();
    }

    public List<DeviceLog> GetLogs(
        string deviceId = null,
        LogType? logLevel = null,
        DateTime? startTime = null,
        DateTime? endTime = null)
    {
        var logs = new List<DeviceLog>();
        using var connection = new SQLiteConnection(_connectionString);
        connection.Open();
        using var command = new SQLiteCommand(connection);

        var whereConditions = new List<string>();
        if (!string.IsNullOrEmpty(deviceId)) whereConditions.Add("DeviceId = @DeviceId");
        if (logLevel.HasValue) whereConditions.Add("LogLevel = @LogLevel");
        if (startTime.HasValue) whereConditions.Add("Timestamp >= @StartTime");
        if (endTime.HasValue) whereConditions.Add("Timestamp <= @EndTime");

        string whereClause = whereConditions.Any() ? "WHERE " + string.Join(" AND ", whereConditions) : "";

        command.CommandText = $@"
            SELECT * FROM DeviceLogs 
            {whereClause} 
            ORDER BY Timestamp DESC";

        if (!string.IsNullOrEmpty(deviceId)) command.Parameters.AddWithValue("@DeviceId", deviceId);
        if (logLevel.HasValue) command.Parameters.AddWithValue("@LogLevel", (int)logLevel.Value);
        if (startTime.HasValue) command.Parameters.AddWithValue("@StartTime", startTime.Value);
        if (endTime.HasValue) command.Parameters.AddWithValue("@EndTime", endTime.Value);

        using var reader = command.ExecuteReader();
        while (reader.Read())
        {
            logs.Add(new DeviceLog
            {
                Id = Convert.ToInt32(reader["Id"]),
                Timestamp = Convert.ToDateTime(reader["Timestamp"]),
                DeviceId = reader["DeviceId"].ToString(),
                LogLevel = (LogType)Convert.ToInt32(reader["LogLevel"]),
                Message = reader["Message"].ToString(),
                Temperature = reader["Temperature"] == DBNull.Value ? null : Convert.ToDouble(reader["Temperature"]),
                Voltage = reader["Voltage"] == DBNull.Value ? null : Convert.ToDouble(reader["Voltage"])
            });
        }

        return logs;
    }

    public void DeleteOldLogs(int daysToKeep)
    {
        using var connection = new SQLiteConnection(_connectionString);
        connection.Open();
        using var command = new SQLiteCommand(connection);
        command.CommandText = @"
            DELETE FROM DeviceLogs 
            WHERE Timestamp < @OldDate";
        command.Parameters.AddWithValue("@OldDate", DateTime.Now.AddDays(-daysToKeep));
        command.ExecuteNonQuery();
    }
}

3、使用 Spectre.Console 美化控制台输出

Spectre.Console 是一款用于 .NET 控制台应用的现代化 UI 框架,支持表格、进度条、颜色输出等功能。

以下是使用 Spectre.Console 展示日志查询结果的示例代码:

using Spectre.Console;
using System;
using System.Data.SQLite;
using System.Linq;

namespace DeviceLoggerSystem
{
    class Program
    {
        static void Main(string[] args)
        {
            // 创建标题
            AnsiConsole.Write(
                new FigletText("设备日志系统")
                    .LeftJustified()
                    .Color(Color.Green));

            // 创建日志管理器
            var dbPath = "device_logs.db";
            var logManager = new DeviceLogManager(dbPath);

            // 使用进度条显示系统初始化
            AnsiConsole.Progress()
                .Start(ctx =>
                {
                    var task = ctx.AddTask("[green]初始化系统[/]");

                    for (int i = 0; i < 100; i++)
                    {
                        task.Increment(1);
                        System.Threading.Thread.Sleep(20);
                    }
                });

            // 记录一些示例日志
            AddSampleLogs(logManager);

            // 主循环
            bool running = true;
            while (running)
            {
                var choice = AnsiConsole.Prompt(
                    new SelectionPrompt<string>()
                        .Title("[yellow]请选择操作:[/]")
                        .PageSize(10)
                        .AddChoices(new[]
                        {
                            "添加新日志", 
                            "查询设备日志",
                            "查看统计信息",
                            "清理旧日志",
                            "退出"
                        }));

                switch (choice)
                {
                    case"添加新日志":
                        AddNewLog(logManager);
                        break;
                    case"查询设备日志":
                        QueryLogs(logManager);
                        break;
                    case"查看统计信息":
                        ShowStatistics(logManager);
                        break;
                    case"清理旧日志":
                        CleanupOldLogs(logManager);
                        break;
                    case"退出":
                        running = false;
                        break;
                }
            }
        }

        // 添加示例日志数据
        static void AddSampleLogs(DeviceLogManager logManager)
        {
            var deviceIds = new[] { "DEVICE_001", "DEVICE_002", "DEVICE_003" };
            var random = new Random();

            foreach (var deviceId in deviceIds)
            {
                // 添加一些随机的历史数据
                for (int i = 0; i < 5; i++)
                {
                    var timestamp = DateTime.Now.AddHours(-random.Next(1, 48));
                    var logType = (LogType)random.Next(0, 4);
                    var temp = 20.0 + random.NextDouble() * 30.0;
                    var voltage = 200.0 + random.NextDouble() * 40.0;

                    string message = logType switch
                    {
                        LogType.Info => "定期检查正常",
                        LogType.Warning => "温度偏高,请注意",
                        LogType.Error => "电压异常,需要检查",
                        LogType.Critical => "设备紧急停机",
                        _ => "系统记录"
                    };

                    logManager.LogEvent(new DeviceLog
                    {
                        Timestamp = timestamp,
                        DeviceId = deviceId,
                        LogLevel = logType,
                        Message = message,
                        Temperature = temp,
                        Voltage = voltage
                    });
                }
            }

            AnsiConsole.MarkupLine("[green]示例数据已创建![/]");
        }

        // 添加新日志
        static void AddNewLog(DeviceLogManager logManager)
        {
            var deviceId = AnsiConsole.Ask<string>("输入设备ID:");

            var logLevel = AnsiConsole.Prompt(
                new SelectionPrompt<LogType>()
                    .Title("选择日志级别:")
                    .AddChoices(Enum.GetValues(typeof(LogType)).Cast<LogType>()));

            var message = AnsiConsole.Ask<string>("输入日志消息:");

            // 可选参数
            double? temperature = null;
            double? voltage = null;

            if (AnsiConsole.Confirm("是否记录温度?"))
            {
                temperature = AnsiConsole.Ask<double>("输入温度值:");
            }

            if (AnsiConsole.Confirm("是否记录电压?"))
            {
                voltage = AnsiConsole.Ask<double>("输入电压值:");
            }

            logManager.LogEvent(new DeviceLog
            {
                Timestamp = DateTime.Now,
                DeviceId = deviceId,
                LogLevel = logLevel,
                Message = message,
                Temperature = temperature,
                Voltage = voltage
            });

            AnsiConsole.MarkupLine("[green]日志已添加成功![/]");
        }

        // 查询日志
        static void QueryLogs(DeviceLogManager logManager)
        {
            // 构建查询条件
            string deviceId = null;
            if (AnsiConsole.Confirm("是否按设备ID筛选?"))
            {
                deviceId = AnsiConsole.Ask<string>("请输入设备ID:");
            }

            LogType? logLevel = null;
            if (AnsiConsole.Confirm("是否按日志级别筛选?"))
            {
                logLevel = AnsiConsole.Prompt(
                    new SelectionPrompt<LogType>()
                        .Title("选择日志级别:")
                        .AddChoices(Enum.GetValues(typeof(LogType)).Cast<LogType>()));
            }

            DateTime? startTime = null;
            if (AnsiConsole.Confirm("是否设置开始时间?"))
            {
                string dateStr = AnsiConsole.Ask<string>("请输入开始时间 (yyyy-MM-dd HH:mm:ss):");
                if (DateTime.TryParse(dateStr, out var dt))
                    startTime = dt;
            }

            DateTime? endTime = null;
            if (AnsiConsole.Confirm("是否设置结束时间?"))
            {
                string dateStr = AnsiConsole.Ask<string>("请输入结束时间 (yyyy-MM-dd HH:mm:ss):");
                if (DateTime.TryParse(dateStr, out var dt))
                    endTime = dt;
            }

            // 执行查询
            var logs = logManager.GetLogs(deviceId, logLevel, startTime, endTime);

            if (logs.Count == 0)
            {
                AnsiConsole.MarkupLine("[yellow]未找到符合条件的日志记录[/]");
                return;
            }

            // 创建表格显示结果
            var table = new Table();
            table.Border(TableBorder.Rounded);
            table.Expand();

            // 添加列
            table.AddColumn("ID");
            table.AddColumn("时间");
            table.AddColumn("设备ID");
            table.AddColumn("级别");
            table.AddColumn("消息");
            table.AddColumn("温度");
            table.AddColumn("电压");

            // 添加数据行
            foreach (var log in logs)
            {
                string tempStr = log.Temperature.HasValue ? $"{log.Temperature:F1}°C" : "-";
                string voltStr = log.Voltage.HasValue ? $"{log.Voltage:F1}V" : "-";

                // 根据日志级别设置行颜色
                var style = log.LogLevel switch
                {
                    LogType.Info => "green",
                    LogType.Warning => "yellow",
                    LogType.Error => "red",
                    LogType.Critical => "red bold",
                    _ => "white"
                };

                table.AddRow(
                    $"[{style}]{log.Id}[/]",
                    $"[{style}]{log.Timestamp}[/]",
                    $"[{style}]{log.DeviceId}[/]",
                    $"[{style}]{log.LogLevel}[/]",
                    $"[{style}]{log.Message}[/]",
                    $"[{style}]{tempStr}[/]",
                    $"[{style}]{voltStr}[/]"
                );
            }

            AnsiConsole.Write(table);
            AnsiConsole.MarkupLine($"[blue]共找到 {logs.Count} 条记录[/]");
        }

        // 显示统计信息
        static void ShowStatistics(DeviceLogManager logManager)
        {
            // 获取所有日志
            var allLogs = logManager.GetLogs();

            // 按设备统计
            var deviceCounts = allLogs
                .GroupBy(l => l.DeviceId)
                .Select(g => (DeviceId: g.Key, Count: g.Count()))
                .ToList();

            // 按日志级别统计
            var levelCounts = allLogs
                .GroupBy(l => l.LogLevel)
                .Select(g => (Level: g.Key, Count: g.Count()))
                .ToList();

            // 创建统计图表
            AnsiConsole.Write(new Rule("[yellow]设备日志统计[/]"));

            // 设备柱状图
            var deviceChart = new BarChart()
                .Width(60)
                .Label("[green bold]按设备统计[/]")
                .CenterLabel();

            foreach (var item in deviceCounts)
            {
                deviceChart.AddItem(item.DeviceId, item.Count, Color.Blue);
            }

            AnsiConsole.Write(deviceChart);

            // 日志级别饼图
            var levelChart = new BarChart()
                .Width(60)
                .Label("[red bold]按日志级别统计[/]")
                .CenterLabel();

            foreach (var item in levelCounts)
            {
                var color = item.Level switch
                {
                    LogType.Info => Color.Green,
                    LogType.Warning => Color.Yellow,
                    LogType.Error => Color.Red,
                    LogType.Critical => Color.Purple,
                    _ => Color.White
                };

                levelChart.AddItem(item.Level.ToString(), item.Count, color);
            }

            AnsiConsole.Write(levelChart);

            // 时间分布
            if (allLogs.Any())
            {
                AnsiConsole.MarkupLine($"[blue]最早记录时间: {allLogs.Min(l => l.Timestamp)}[/]");
                AnsiConsole.MarkupLine($"[blue]最新记录时间: {allLogs.Max(l => l.Timestamp)}[/]");
                AnsiConsole.MarkupLine($"[blue]总记录数: {allLogs.Count}[/]");
            }
        }

        // 清理旧日志
        static void CleanupOldLogs(DeviceLogManager logManager)
        {
            var days = AnsiConsole.Ask("保留最近几天的日志?", 30);

            if (AnsiConsole.Confirm($"确定要删除 {days} 天前的所有日志记录?"))
            {
                AnsiConsole.Status()
                    .Start("正在清理旧日志...", ctx => 
                    {
                        logManager.DeleteOldLogs(days);
                        System.Threading.Thread.Sleep(1000); // 模拟操作
                    });

                AnsiConsole.MarkupLine("[green]旧日志清理完成![/]");
            }
        }
    }
}

还可以配合进度条、提示框等控件:

总结

本文介绍一种基于 C# 和 SQLite 实现的设备日志记录系统建设方案,并通过Spectre.Console提升控制台应用的可视化体验。

该系统具备以下特点:

✅轻量级:无依赖数据库服务,单文件存储,易于部署;

✅高性能:SQLite 快速读写,支持灵活查询;

✅可扩展性强:支持自定义字段扩展、日志分级及清理策略;

✅用户友好:结合 Spectre.Console,实现美观的控制台界面;

✅实用性强:涵盖日志的增删查改、过滤、导出等完整功能。

不管是在工业控制系统调试、边缘设备监控还是物联网项目中,这套系统都能有效支撑设备数据的采集与分析需求。

大家可以进一步拓展 Web 界面、远程访问、数据导出为 CSV 或 JSON 文件等功能。

希望本文能为大家的项目开发带来灵感和实用参考!

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

作者:技术老小子

出处:mp.weixin.qq.com/s/-goYKnKxSXk_EGDfpBu9dQ

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!