在工业自动化、物联网和设备监控领域,记录设备运行日志是保障系统稳定和问题追踪的关键环节。本文将详细介绍如何使用C#和SQLite数据库构建一个轻量级但功能强大的设备日志记录系统,并使用流行的Spectre.Console库为控制台输出添加美观的视觉效果。
为什么选择SQLite? SQLite作为一个嵌入式数据库,具有以下优势:
零配置,无需安装独立数据库服务 单文件存储,便于备份和迁移 跨平台兼容性强 资源占用少,适合嵌入式设备 支持SQL标准查询 系统设计 我们的设备日志系统包含以下核心组件:
日志类型枚举(LogType) 设备日志实体类(DeviceLog) 日志管理器(DeviceLogManager) 控制台展示界面(使用Spectre.Console) 基础结构定义 首先,让我们定义日志类型枚举和设备日志实体类:
// 日志类型枚举 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 ; } // 电压(可选) }
数据库管理 日志管理器负责与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); // 对可能为空的数值使用DBNull.Value command.Parameters.AddWithValue( "@Temperature" , log .Temperature ?? (object)DBNull.Value); command.Parameters.AddWithValue( "@Voltage" , log .Voltage ?? (object)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)) { // 构建WHERE子句 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)}" : "" ; // 构建SQL查询 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(); } } } }
使用Spectre.Console美化控制台输出 Spectre.Console是一个现代化的.NET库,能够为控制台应用程序创建美观、交互式的用户界面。下面,我们将使用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管理界面等。
阅读原文:原文链接
该文章在 2025/5/26 11:08:33 编辑过