Files
FATrace/FATrace.WPLApp/ViewModels/LogViewModel.cs
2025-11-26 16:46:48 +08:00

179 lines
6.1 KiB
C#

using FATrace.WPLApp.Core;
using Prism.Commands;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace FATrace.WPLApp.ViewModels
{
public class LogViewModel : NavigationViewModel
{
private readonly DispatcherTimer _timer;
public LogViewModel()
{
Levels = new ObservableCollection<string>(new[] { "ALL", "DEBUG", "INFO", "WARN", "ERROR", "FATAL" });
_selectedDate = DateTime.Today;
_levelFilter = Levels.First();
_maxLines = 2000;
RefreshCommand = new DelegateCommand(async () => await LoadAsync(), () => !IsBusy).ObservesProperty(() => IsBusy);
OpenFolderCommand = new DelegateCommand(OpenFolder);
_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
_timer.Tick += async (_, __) =>
{
if (AutoRefresh && !IsBusy)
await LoadAsync();
};
_timer.Start();
// 初始加载当日日志
_ = LoadAsync();
}
public ObservableCollection<string> Levels { get; }
private ObservableCollection<LogEntry> _items = new();
public ObservableCollection<LogEntry> Items { get => _items; set { _items = value; RaisePropertyChanged(); } }
private DateTime? _selectedDate;
public DateTime? SelectedDate { get => _selectedDate; set { _selectedDate = value; RaisePropertyChanged(); _ = LoadAsync(); } }
private string _levelFilter;
public string LevelFilter { get => _levelFilter; set { _levelFilter = value; RaisePropertyChanged(); _ = LoadAsync(); } }
private string _keyword;
public string Keyword { get => _keyword; set { _keyword = value; RaisePropertyChanged(); _ = LoadAsync(); } }
private bool _autoRefresh;
public bool AutoRefresh { get => _autoRefresh; set { _autoRefresh = value; RaisePropertyChanged(); } }
private int _maxLines;
public int MaxLines { get => _maxLines; set { _maxLines = value; RaisePropertyChanged(); } }
private bool _isBusy;
public bool IsBusy { get => _isBusy; set { _isBusy = value; RaisePropertyChanged(); } }
public DelegateCommand RefreshCommand { get; }
public DelegateCommand OpenFolderCommand { get; }
private string GetLogDirectory()
{
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs");
}
private string GetLogFilePath(DateTime? date)
{
var dir = GetLogDirectory();
var dt = date ?? DateTime.Today;
var file = dt.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) + ".log";
return Path.Combine(dir, file);
}
private static bool PassLevel(string levelFilter, string level)
{
if (string.Equals(levelFilter, "ALL", StringComparison.OrdinalIgnoreCase)) return true;
return string.Equals(level, levelFilter, StringComparison.OrdinalIgnoreCase);
}
private async Task LoadAsync()
{
if (IsBusy) return;
IsBusy = true;
try
{
var path = GetLogFilePath(SelectedDate);
var list = await Task.Run(() => ReadLogFile(path, MaxLines));
var filtered = list.Where(x => PassLevel(LevelFilter, x.Level)
&& (string.IsNullOrWhiteSpace(Keyword) || (x.Message?.IndexOf(Keyword, StringComparison.OrdinalIgnoreCase) >= 0)
|| (x.Logger?.IndexOf(Keyword, StringComparison.OrdinalIgnoreCase) >= 0)));
Items = new ObservableCollection<LogEntry>(filtered);
}
catch
{
Items = new ObservableCollection<LogEntry>();
}
finally
{
IsBusy = false;
}
}
private static IReadOnlyList<LogEntry> ReadLogFile(string path, int maxLines)
{
var result = new LinkedList<LogEntry>();
if (!File.Exists(path)) return result.ToList();
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var sr = new StreamReader(fs, Encoding.UTF8);
string? line;
while ((line = sr.ReadLine()) != null)
{
var entry = ParseLine(line);
result.AddLast(entry);
if (result.Count > maxLines) result.RemoveFirst();
}
return result.ToList();
}
private static LogEntry ParseLine(string line)
{
// layout: longdate|LEVEL|logger|threadid|message[|exception]
var parts = line.Split('|');
var entry = new LogEntry();
if (parts.Length >= 4)
{
entry.Time = parts[0];
entry.Level = parts[1];
entry.Logger = parts[2];
entry.ThreadId = parts[3];
if (parts.Length >= 5)
{
entry.Message = string.Join("|", parts.Skip(4));
}
}
else
{
entry.Message = line;
}
return entry;
}
private void OpenFolder()
{
var dir = GetLogDirectory();
Directory.CreateDirectory(dir);
try
{
Process.Start(new ProcessStartInfo
{
FileName = dir,
UseShellExecute = true
});
}
catch { }
}
}
public class LogEntry
{
public string Time { get; set; }
public string Level { get; set; }
public string Logger { get; set; }
public string ThreadId { get; set; }
public string Message { get; set; }
}
}