From a8296063f54e4cda758f16b20fff0d3b25c289d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90=C4=83ng=20Nguy=E1=BB=85n?= Date: Mon, 22 Dec 2025 09:43:00 +0700 Subject: [PATCH] update logs --- RobotApp.Client/MainLayout.razor | 5 +- RobotApp.Client/Models/LoggerModel.cs | 40 ++++ RobotApp.Client/Pages/Logs.razor | 176 ++++++++++++++++++ RobotApp.Client/Pages/Logs.razor.css | 38 ++++ RobotApp.Client/RobotApp.Client.csproj | 4 - RobotApp/Controllers/LogsManagerController.cs | 48 +++++ RobotApp/Properties/launchSettings.json | 2 +- RobotApp/Services/MQTTClient.cs | 4 +- RobotApp/Services/Robot/RobotConnection.cs | 6 +- 9 files changed, 311 insertions(+), 12 deletions(-) create mode 100644 RobotApp.Client/Models/LoggerModel.cs create mode 100644 RobotApp.Client/Pages/Logs.razor create mode 100644 RobotApp.Client/Pages/Logs.razor.css create mode 100644 RobotApp/Controllers/LogsManagerController.cs diff --git a/RobotApp.Client/MainLayout.razor b/RobotApp.Client/MainLayout.razor index 5aa7765..2823d0a 100644 --- a/RobotApp.Client/MainLayout.razor +++ b/RobotApp.Client/MainLayout.razor @@ -59,7 +59,7 @@ -@code{ +@code { public class NavModel { public string Icon { get; set; } = ""; @@ -72,7 +72,8 @@ new(){Icon = "mdi-view-dashboard", Path="/", Label = "Dashboard", Match = NavLinkMatch.All}, // new(){Icon = "mdi-map-legend", Path="/maps-manager", Label = "Mapping", Match = NavLinkMatch.All}, new(){Icon = "mdi-monitor", Path="/robot-monitor", Label = "Robot Monitor", Match = NavLinkMatch.All}, - new(){Icon = "mdi-application-cog", Path="/robot-config", Label = "Config", Match = NavLinkMatch.All}, + new(){Icon = "mdi-application-cog", Path="/robot-config", Label = "Config", Match = NavLinkMatch.All }, + new(){Icon = "mdi-math-log", Path="/logs", Label = "Logs", Match = NavLinkMatch.All} ]; private bool collapseNavMenu = true; diff --git a/RobotApp.Client/Models/LoggerModel.cs b/RobotApp.Client/Models/LoggerModel.cs new file mode 100644 index 0000000..c8707c0 --- /dev/null +++ b/RobotApp.Client/Models/LoggerModel.cs @@ -0,0 +1,40 @@ +using System.Text.Json.Serialization; + +namespace RobotApp.Client.Models; + +public class LoggerModel +{ + [JsonPropertyName("time")] + public string? Time { get; set; } + + [JsonPropertyName("level")] + public string? Level { get; set; } + + [JsonPropertyName("message")] + public string? Message { get; set; } + + [JsonPropertyName("exception")] + public string? Exception { get; set; } + + public string ColorClass => Level switch + { + "WARN" => "text-warning", + "INFO" => "text-info", + "DEBUG" => "text-success", + "ERROR" => "text-danger", + "FATAL" => "text-secondary", + _ => "text-muted", + }; + + public string BackgroundClass => Level switch + { + "WARN" => "bg-warning text-dark", + "INFO" => "bg-info text-dark", + "DEBUG" => "bg-success text-white", + "ERROR" => "bg-danger text-white", + "FATAL" => "bg-secondary text-white", + _ => "bg-dark text-white", + }; + + public bool HasException => !string.IsNullOrEmpty(Exception); +} diff --git a/RobotApp.Client/Pages/Logs.razor b/RobotApp.Client/Pages/Logs.razor new file mode 100644 index 0000000..1e18167 --- /dev/null +++ b/RobotApp.Client/Pages/Logs.razor @@ -0,0 +1,176 @@ +@page "/logs" +@rendermode InteractiveWebAssemblyNoPrerender +@attribute [Authorize] + +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +@using RobotApp.Client.Models + +@inject IJSRuntime JSRuntime +@inject HttpClient Http +@inject IConfiguration Configuration +@inject ISnackbar Snackbar + +Logs + +
+
+ + +
+ + + + + + + + +
+
+
+ + + +
+
+ @if (ShowRawLog) + { +
+
Normal
+
+ + @foreach (var log in ShowLogs) + { + @log
+ } +
+ } + else + { + @if (SearchLogs.Count < ShowLogs.Count) + { +
+
Raw log
+
+ } + + @foreach (var log in SearchLogs) + { +
+ + @log.Time @log.Level + + @log.Message + @if (log.HasException) + { +
+
+                                    @log.Exception
+                                                                    
+ } +
+ } + } +
+
+
+
+ + +@code { + private DateTime DateLog = DateTime.Today; + private bool IsLoading; + private readonly List ShowLogs = new(); + private readonly List SearchLogs = new(); + private ElementReference LogContainerRef { get; set; } + private bool ShowRawLog { get; set; } + private string? FilterLog { get; set; } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + if (!firstRender) return; + + await LoadLogs(); + } + + private async Task LoadLogs() + { + try + { + IsLoading = true; + ShowLogs.Clear(); + StateHasChanged(); + + var logs = await Http.GetFromJsonAsync>($"api/LogsManager?date={DateLog}"); + ShowLogs.AddRange(logs ?? []); + + IsLoading = false; + StateHasChanged(); + + await ReloadLogs(); + } + catch (AccessTokenNotAvailableException ex) + { + ex.Redirect(); + return; + } + } + + private async Task ReloadLogs() + { + IsLoading = true; + SearchLogs.Clear(); + StateHasChanged(); + + foreach (var line in ShowLogs.Where(log => string.IsNullOrEmpty(FilterLog) || log.Contains(FilterLog)).TakeLast(2000)) + { + try + { + var log = System.Text.Json.JsonSerializer.Deserialize(line); + if (log is not null) SearchLogs.Add(log); + } + catch (System.Text.Json.JsonException) + { + continue; + } + } + + IsLoading = false; + StateHasChanged(); + await JSRuntime.InvokeVoidAsync("ScrollToBottom", LogContainerRef); + } + + private async Task OnSearch(string text) + { + FilterLog = text; + await ReloadLogs(); + } + + private async Task OnDateChanged(DateTime? date) + { + if (date is not null && date.HasValue) + { + DateLog = date.Value; + await LoadLogs(); + } + } + + private async Task ExportLogs() + { + try + { + var fileContent = await Http.GetFromJsonAsync>($"api/LogsManager?date={DateLog}"); + var formattedContent = string.Join("\n", fileContent ?? []); + var fileName = $"LogsManager_{DateLog.ToShortDateString()}.txt"; + await JSRuntime.InvokeVoidAsync("downloadFile", fileName, formattedContent, "text/plain"); + } + catch (Exception ex) + { + Snackbar.Add($"Lỗi khi tải file: {ex.Message}", Severity.Warning); + } + } +} + diff --git a/RobotApp.Client/Pages/Logs.razor.css b/RobotApp.Client/Pages/Logs.razor.css new file mode 100644 index 0000000..2a3ae7a --- /dev/null +++ b/RobotApp.Client/Pages/Logs.razor.css @@ -0,0 +1,38 @@ +.log-container { + height: 100%; + width: 100%; + overflow-x: hidden; + overflow-y: auto; + position: absolute; + top: 0px; + left: 0px; + display: flex; + flex-direction: column; +} + +.log { + word-wrap: break-word; + line-height: 18px; + margin-bottom: 12px; +} + +.log-logger { + color: rgba(0, 0, 0, 0.3); + font-size: 12px; +} + +.log-level { + display: inline-block; + width: 46px; +} + +.log-head { + border-radius: 3px; + padding: 2px 5px; +} + +.log-exception { + line-height: 16px; + margin-left: 30px; + color: crimson; +} diff --git a/RobotApp.Client/RobotApp.Client.csproj b/RobotApp.Client/RobotApp.Client.csproj index 977a576..481cb7b 100644 --- a/RobotApp.Client/RobotApp.Client.csproj +++ b/RobotApp.Client/RobotApp.Client.csproj @@ -20,8 +20,4 @@ - - - - diff --git a/RobotApp/Controllers/LogsManagerController.cs b/RobotApp/Controllers/LogsManagerController.cs new file mode 100644 index 0000000..ca9741c --- /dev/null +++ b/RobotApp/Controllers/LogsManagerController.cs @@ -0,0 +1,48 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace RobotApp.Controllers; + +[Route("api/[controller]")] +[ApiController] +[Authorize] +public class LogsManagerController(Services.Logger Logger) : ControllerBase +{ + private readonly string LoggerDirectory = "logs"; + + [HttpGet] + public async Task> GetLogs([FromQuery(Name = "date")] DateTime date) + { + string temp = ""; + try + { + string fileName = $"{date:yyyy-MM-dd}.log"; + string path = Path.Combine(LoggerDirectory, fileName); + if (!Path.GetFullPath(path).StartsWith(Path.GetFullPath(LoggerDirectory))) + { + Logger.Warning($"GetLogs: phát hiện đường dẫn không hợp lệ."); + return []; + } + + if (!System.IO.File.Exists(path)) + { + Logger.Warning($"GetLogs: không tìm thấy file log của ngày {date.ToShortDateString()} - {path}."); + return []; + } + + temp = Path.Combine(LoggerDirectory, $"{Guid.NewGuid()}.log"); + System.IO.File.Copy(path, temp); + + return await System.IO.File.ReadAllLinesAsync(temp); + } + catch (Exception ex) + { + Logger.Warning($"GetLogs: Hệ thống có lỗi xảy ra - {ex.Message}"); + return []; + } + finally + { + if (System.IO.File.Exists(temp)) System.IO.File.Delete(temp); + } + } +} diff --git a/RobotApp/Properties/launchSettings.json b/RobotApp/Properties/launchSettings.json index 21714dc..3f43f90 100644 --- a/RobotApp/Properties/launchSettings.json +++ b/RobotApp/Properties/launchSettings.json @@ -17,7 +17,7 @@ "dotnetRunMessages": true, "launchBrowser": true, "workingDirectory": "$(TargetDir)", - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + //"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", "applicationUrl": "https://0.0.0.0:7150;http://localhost:5229", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/RobotApp/Services/MQTTClient.cs b/RobotApp/Services/MQTTClient.cs index e21daa6..1ab8acd 100644 --- a/RobotApp/Services/MQTTClient.cs +++ b/RobotApp/Services/MQTTClient.cs @@ -24,8 +24,8 @@ public class MQTTClient : IAsyncDisposable public event Action? InstanceActionsChanged; public bool IsConnected => !IsDisposed && MqttClient is not null && MqttClient.IsConnected; - private string OrderTopic => $"{VDA5050Setting.TopicPrefix}/{ClientId}/{VDA5050Topic.ORDER.ToTopicString()}"; - private string InstanceActionsTopic => $"{VDA5050Setting.TopicPrefix}/{ClientId}/{VDA5050Topic.INSTANTACTIONS.ToTopicString()}"; + private string OrderTopic => $"{VDA5050Setting.TopicPrefix}/{VDA5050Setting.Manufacturer}/{ClientId}/{VDA5050Topic.ORDER.ToTopicString()}"; + private string InstanceActionsTopic => $"{VDA5050Setting.TopicPrefix}/{VDA5050Setting.Manufacturer}/{ClientId}/{VDA5050Topic.INSTANTACTIONS.ToTopicString()}"; public MQTTClient(string clientId, VDA5050Setting setting, Logger logger) { diff --git a/RobotApp/Services/Robot/RobotConnection.cs b/RobotApp/Services/Robot/RobotConnection.cs index dc35a47..cf3cbed 100644 --- a/RobotApp/Services/Robot/RobotConnection.cs +++ b/RobotApp/Services/Robot/RobotConnection.cs @@ -22,7 +22,7 @@ public class RobotConnection(RobotConfiguration RobotConfiguration, { try { - Logger.Debug($"Nhận Order: {data}"); + //Logger.Debug($"Nhận Order: {data}"); var msg = JsonSerializer.Deserialize(data, JsonOptionExtends.Read); if (msg is null || string.IsNullOrEmpty(msg.SerialNumber) || msg.SerialNumber != RobotConfiguration.SerialNumber) return; OrderUpdated?.Invoke(msg); @@ -37,7 +37,7 @@ public class RobotConnection(RobotConfiguration RobotConfiguration, { try { - Logger.Debug($"Nhận InstanceActions: {data}"); + //Logger.Debug($"Nhận InstanceActions: {data}"); var msg = JsonSerializer.Deserialize(data, JsonOptionExtends.Read); if (msg is null || string.IsNullOrEmpty(msg.SerialNumber) || msg.SerialNumber != RobotConfiguration.SerialNumber) return; ActionUpdated?.Invoke(msg); @@ -50,7 +50,7 @@ public class RobotConnection(RobotConfiguration RobotConfiguration, public async Task Publish(string topic, string data) { - if (MqttClient is not null && MqttClient.IsConnected) return await MqttClient.PublishAsync($"{VDA5050Setting.TopicPrefix}/{RobotConfiguration.SerialNumber}/{topic}", data); + if (MqttClient is not null && MqttClient.IsConnected) return await MqttClient.PublishAsync($"{VDA5050Setting.TopicPrefix}/{VDA5050Setting.Manufacturer}/{RobotConfiguration.SerialNumber}/{topic}", data); return new(false, "Chưa có kết nối tới broker"); }