前言
在工业自动化、物联网和设备监控领域,记录设备运行日志是保障系统稳定性和后续问题追踪的关键环节。一个完善的日志系统不仅能帮助开发者快速定位异常,还能为运维人员提供数据支持,从而实现对系统的持续优化。
本文将基于 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
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!