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(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 Levels { get; } private ObservableCollection _items = new(); public ObservableCollection 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(filtered); } catch { Items = new ObservableCollection(); } finally { IsBusy = false; } } private static IReadOnlyList ReadLogFile(string path, int maxLines) { var result = new LinkedList(); 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; } } }