Add date filtering to client logs diagnostics
TaxBaik CI/CD / build-and-deploy (push) Has been cancelled
TaxBaik CI/CD / build-and-deploy (push) Has been cancelled
This commit is contained in:
@@ -23,24 +23,6 @@
|
|||||||
<div><strong>Info</strong> @_summary.InfoCount</div>
|
<div><strong>Info</strong> @_summary.InfoCount</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (_groups.Count > 0)
|
|
||||||
{
|
|
||||||
<div class="admin-log-group-list">
|
|
||||||
@foreach (var group in _groups)
|
|
||||||
{
|
|
||||||
<article class="admin-log-group-item">
|
|
||||||
<div class="admin-log-item-header">
|
|
||||||
<strong>@group.Count</strong>
|
|
||||||
<span>@group.Level</span>
|
|
||||||
<span>@group.Source</span>
|
|
||||||
<span>@group.Route</span>
|
|
||||||
</div>
|
|
||||||
<p>@group.Message</p>
|
|
||||||
<small>@group.LastSeen</small>
|
|
||||||
</article>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div class="admin-log-filter-row">
|
<div class="admin-log-filter-row">
|
||||||
<input class="mud-input-slot" placeholder="검색어" @bind="_query" @bind:event="oninput" />
|
<input class="mud-input-slot" placeholder="검색어" @bind="_query" @bind:event="oninput" />
|
||||||
<select class="mud-input-slot" @bind="_level">
|
<select class="mud-input-slot" @bind="_level">
|
||||||
@@ -49,6 +31,8 @@
|
|||||||
<option value="warning">warning</option>
|
<option value="warning">warning</option>
|
||||||
<option value="info">info</option>
|
<option value="info">info</option>
|
||||||
</select>
|
</select>
|
||||||
|
<input class="mud-input-slot" placeholder="시작 ISO 시각" @bind="_start" @bind:event="oninput" />
|
||||||
|
<input class="mud-input-slot" placeholder="종료 ISO 시각" @bind="_end" @bind:event="oninput" />
|
||||||
<button type="button" class="mud-button-root" @onclick="LoadAsync">새로고침</button>
|
<button type="button" class="mud-button-root" @onclick="LoadAsync">새로고침</button>
|
||||||
</div>
|
</div>
|
||||||
@if (_loading)
|
@if (_loading)
|
||||||
@@ -76,6 +60,27 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@if (_groups.Count > 0)
|
||||||
|
{
|
||||||
|
<details class="admin-log-group-details">
|
||||||
|
<summary>반복 오류 그룹 보기</summary>
|
||||||
|
<div class="admin-log-group-list">
|
||||||
|
@foreach (var group in _groups)
|
||||||
|
{
|
||||||
|
<article class="admin-log-group-item">
|
||||||
|
<div class="admin-log-item-header">
|
||||||
|
<strong>@group.Count</strong>
|
||||||
|
<span>@group.Level</span>
|
||||||
|
<span>@group.Source</span>
|
||||||
|
<span>@group.Route</span>
|
||||||
|
</div>
|
||||||
|
<p>@group.Message</p>
|
||||||
|
<small>@group.LastSeen</small>
|
||||||
|
</article>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -85,6 +90,8 @@
|
|||||||
private bool _loading = true;
|
private bool _loading = true;
|
||||||
private string _query = "";
|
private string _query = "";
|
||||||
private string _level = "";
|
private string _level = "";
|
||||||
|
private string _start = "";
|
||||||
|
private string _end = "";
|
||||||
private ClientLogSummaryDto? _summary;
|
private ClientLogSummaryDto? _summary;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
@@ -101,7 +108,7 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_summary = await Http.GetFromJsonAsync<ClientLogSummaryDto>("/taxbaik/api/client-logs/summary");
|
_summary = await Http.GetFromJsonAsync<ClientLogSummaryDto>("/taxbaik/api/client-logs/summary");
|
||||||
var url = $"/taxbaik/api/client-logs/recent?q={Uri.EscapeDataString(_query ?? string.Empty)}&level={Uri.EscapeDataString(_level ?? string.Empty)}";
|
var url = $"/taxbaik/api/client-logs/recent?q={Uri.EscapeDataString(_query ?? string.Empty)}&level={Uri.EscapeDataString(_level ?? string.Empty)}&start={Uri.EscapeDataString(_start ?? string.Empty)}&end={Uri.EscapeDataString(_end ?? string.Empty)}";
|
||||||
var result = await Http.GetFromJsonAsync<List<ClientLogDto>>(url);
|
var result = await Http.GetFromJsonAsync<List<ClientLogDto>>(url);
|
||||||
var grouped = await Http.GetFromJsonAsync<List<ClientLogGroupDto>>("/taxbaik/api/client-logs/groups");
|
var grouped = await Http.GetFromJsonAsync<List<ClientLogGroupDto>>("/taxbaik/api/client-logs/groups");
|
||||||
if (result is not null)
|
if (result is not null)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace TaxBaik.Web.Endpoints.ClientLogs;
|
|||||||
public sealed class RecentClientLogEntry
|
public sealed class RecentClientLogEntry
|
||||||
{
|
{
|
||||||
public string? Timestamp { get; set; }
|
public string? Timestamp { get; set; }
|
||||||
|
public DateTimeOffset? OccurredAt { get; set; }
|
||||||
public string? Level { get; set; }
|
public string? Level { get; set; }
|
||||||
public string? Source { get; set; }
|
public string? Source { get; set; }
|
||||||
public string? Message { get; set; }
|
public string? Message { get; set; }
|
||||||
@@ -37,6 +38,8 @@ public sealed class GetRecentLogsEndpoint : EndpointWithoutRequest<IReadOnlyList
|
|||||||
var source = Query<string>("source");
|
var source = Query<string>("source");
|
||||||
var route = Query<string>("route");
|
var route = Query<string>("route");
|
||||||
var term = Query<string>("q");
|
var term = Query<string>("q");
|
||||||
|
var start = ParseDate(Query<string>("start"));
|
||||||
|
var end = ParseDate(Query<string>("end"));
|
||||||
|
|
||||||
var logDir = Path.Combine(_environment.ContentRootPath, "logs");
|
var logDir = Path.Combine(_environment.ContentRootPath, "logs");
|
||||||
if (!Directory.Exists(logDir))
|
if (!Directory.Exists(logDir))
|
||||||
@@ -60,7 +63,7 @@ public sealed class GetRecentLogsEndpoint : EndpointWithoutRequest<IReadOnlyList
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
var entry = Parse(line);
|
var entry = Parse(line);
|
||||||
if (Matches(entry, level, source, route, term))
|
if (Matches(entry, level, source, route, term, start, end))
|
||||||
entries.Add(entry);
|
entries.Add(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,18 +73,21 @@ public sealed class GetRecentLogsEndpoint : EndpointWithoutRequest<IReadOnlyList
|
|||||||
|
|
||||||
private static RecentClientLogEntry Parse(string line)
|
private static RecentClientLogEntry Parse(string line)
|
||||||
{
|
{
|
||||||
var entry = new RecentClientLogEntry();
|
var timestamp = ExtractTimestamp(line);
|
||||||
entry.Timestamp = ExtractTimestamp(line);
|
return new RecentClientLogEntry
|
||||||
entry.Level = ExtractBetween(line, "] [", "]");
|
{
|
||||||
entry.Source = ExtractTokenAfter(line, "ClientLog ", 1);
|
Timestamp = timestamp,
|
||||||
entry.Route = ExtractNamedValue(line, "Route=");
|
OccurredAt = ParseDate(timestamp),
|
||||||
entry.Screen = ExtractNamedValue(line, "Screen=");
|
Level = ExtractBetween(line, "] [", "]"),
|
||||||
entry.Feature = ExtractNamedValue(line, "Feature=");
|
Source = ExtractTokenAfter(line, "ClientLog ", 1),
|
||||||
entry.Action = ExtractNamedValue(line, "Action=");
|
Route = ExtractNamedValue(line, "Route="),
|
||||||
entry.Step = ExtractNamedValue(line, "Step=");
|
Screen = ExtractNamedValue(line, "Screen="),
|
||||||
entry.BuildVersion = ExtractNamedValue(line, "BuildVersion=");
|
Feature = ExtractNamedValue(line, "Feature="),
|
||||||
entry.Message = ExtractMessage(line);
|
Action = ExtractNamedValue(line, "Action="),
|
||||||
return entry;
|
Step = ExtractNamedValue(line, "Step="),
|
||||||
|
BuildVersion = ExtractNamedValue(line, "BuildVersion="),
|
||||||
|
Message = ExtractMessage(line)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? ExtractTimestamp(string line)
|
private static string? ExtractTimestamp(string line)
|
||||||
@@ -120,7 +126,7 @@ public sealed class GetRecentLogsEndpoint : EndpointWithoutRequest<IReadOnlyList
|
|||||||
return clientIndex >= 0 ? line[(clientIndex + "ClientLog ".Length)..] : line;
|
return clientIndex >= 0 ? line[(clientIndex + "ClientLog ".Length)..] : line;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool Matches(RecentClientLogEntry entry, string? level, string? source, string? route, string? term)
|
private static bool Matches(RecentClientLogEntry entry, string? level, string? source, string? route, string? term, DateTimeOffset? start, DateTimeOffset? end)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(level) &&
|
if (!string.IsNullOrWhiteSpace(level) &&
|
||||||
!string.Equals(entry.Level, level, StringComparison.OrdinalIgnoreCase))
|
!string.Equals(entry.Level, level, StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -147,9 +153,29 @@ public sealed class GetRecentLogsEndpoint : EndpointWithoutRequest<IReadOnlyList
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (start is not null && (entry.OccurredAt is null || entry.OccurredAt < start))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end is not null && (entry.OccurredAt is null || entry.OccurredAt > end))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static DateTimeOffset? ParseDate(string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateTimeOffset.TryParse(value, out var parsed) ? parsed : null;
|
||||||
|
}
|
||||||
|
|
||||||
private static string? ExtractNamedValue(string text, string prefix)
|
private static string? ExtractNamedValue(string text, string prefix)
|
||||||
{
|
{
|
||||||
var index = text.IndexOf(prefix, StringComparison.OrdinalIgnoreCase);
|
var index = text.IndexOf(prefix, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|||||||
Reference in New Issue
Block a user