using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Security.Permissions; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows.Forms; using VCommon.Logging.Viewer.Utility; namespace VCommon.Logging.Viewer { //TODO: 清除时移除文件监视,仅监视最后拖入对象(目录或文件) //TODO: 放出JSON可视化选项,作为一行 public partial class MainForm : Form, IMessageFilter { private bool _mouseOnLvSummary; private FileSizeWatcher _fileWatcherSize; private readonly ColumnSorter _lstColumnSorter = new ColumnSorter(); private readonly List _detailforms = new List(); public MainForm() { InitializeComponent(); var eIcon = IconExtractor.GetMainIcon(); if (null != eIcon) Icon = eIcon; Application.AddMessageFilter(this); WebBrowserUtility.LoadWebBrowserResource(WbJson); LvSummary.ListViewItemSorter = _lstColumnSorter; } #pragma warning disable 4014 public MainForm(string openDirectoryName) : this() => OpenFileTask(openDirectoryName, false); #pragma warning restore 4014 private async Task OpenFileTask(string fileOrdirectoryName, bool openByClick = true) { if (File.Exists(fileOrdirectoryName)) { var fileInfos = new[] { new FileInfo(fileOrdirectoryName) }; EnableWatcherChanged(fileInfos); await AnalysisLogFile(fileInfos, watchFile: () => { CreateFileWatcher(fileOrdirectoryName); }); } else { if (!openByClick) { await ProcessOpenDirectoryTask(fileOrdirectoryName); } else { FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog(); if (!string.IsNullOrEmpty(fileOrdirectoryName) && Directory.Exists(fileOrdirectoryName)) folderBrowserDialog.SelectedPath = fileOrdirectoryName; if (folderBrowserDialog.ShowDialog() == DialogResult.OK) { fileOrdirectoryName = folderBrowserDialog.SelectedPath.Trim(); await ProcessOpenDirectoryTask(fileOrdirectoryName); } } } } private async Task ProcessOpenDirectoryTask(string directoryName) { List flieInfos = new List(); OperatingFileAssistant.GetAllFilesByDirectory(directoryName, flieInfos, "*.log"); var fileInfos = flieInfos.ToArray(); EnableWatcherChanged(fileInfos); await AnalysisLogFile(fileInfos, watchFile: () => { CreateFileWatcher(directoryName); }); } private void btnSelectFile_Click(object sender, EventArgs e) { } /// /// 分析日志 /// /// 文件信息 /// 读取起始位置 /// 是否清除ListView /// 监控文件委托 /// 是否是删除内容 /// 文件信息不能为空 /// private async Task AnalysisLogFile(FileInfo[] fileInfos, long seekBeginPosition = 0, bool clearListView = true, Action watchFile = null, bool removeContent = false) { await Task.Run(async () => { if (fileInfos == null || fileInfos.Length == 0) throw new ArgumentException(nameof(fileInfos)); if (clearListView) { LvSummary.Invoke(new Action(() => { LvSummary.Items.Clear(); })); } var errorList = new List(); Dictionary> readFileResultTasks = new Dictionary>(); foreach (var file in fileInfos) { var line = 0; try { var tasksReadTextAsync = OperatingFileAssistant.ReadTextAsync(file, str => { List logEntities = new List(); string[] logContent = str.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); var logEnumerator = logContent.GetEnumerator(); while (logEnumerator.MoveNext()) { line++; // ReSharper disable once PossibleNullReferenceException var currentContent = logEnumerator.Current.ToString(); var matchCollection = Regex.Matches(currentContent, @"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) (.+? )(.+)$"); if (matchCollection.Count > 0) { LogEntity logEntity = new LogEntity { WriteTime = $"{matchCollection[0].Groups[1].Value}", Level = matchCollection[0].Groups[2].Value, ThreadName = matchCollection[0].Groups[3].Value, FileName = file.Name, LineNumber = line }; if (logEnumerator.MoveNext() == false) break; line++; logEntity.Summary = logEnumerator.Current.ToString(); if (logEnumerator.MoveNext() == false) break; line++; logEntity.Detail = logEnumerator.Current.ToString(); logEntities.Add(logEntity); } } return logEntities.ToArray(); }, seekBeginPosition); readFileResultTasks.Add(file, tasksReadTextAsync); } catch (Exception ex) { errorList.Add(string.Format("于文件{1}行{2}解析错误:{0}", ex, file.Name, line)); } } readFileResultTasks = readFileResultTasks.OrderByDescending(c => c.Key.LastWriteTime) .ToDictionary(p => p.Key, o => o.Value); await Task.WhenAll(readFileResultTasks.Select(c => c.Value).ToArray()).ConfigureAwait(false); //如果是移除,只需比较ListView数据,删除即可 if (removeContent) { LvSummary.Invoke(new Action(() => { var listViewItems = LvSummary.Items.Cast().Where(p => p.Text == fileInfos[0].Name).ToArray(); var logEntities = readFileResultTasks.SelectMany(c => c.Value.Result).Select(c => c.WriteTime); var viewItems = listViewItems.Where(p => !logEntities.Contains(p.SubItems[1].Text)).ToArray(); foreach (var item in viewItems) { LvSummary.Items.Remove(item); } })); } else { await Task.Factory.StartNew(() => { List listViewItems = new List(); foreach (var task in readFileResultTasks) { foreach (var logEntity in task.Value.Result.OrderBy(c => c.WriteTime)) { ListViewItem listViewItem = new ListViewItem { Text = task.Key.Name, Tag = logEntity }; listViewItem.SubItems.Add(logEntity.WriteTime); listViewItem.SubItems.Add(logEntity.Level); listViewItem.SubItems.Add(logEntity.ThreadName); listViewItem.SubItems.Add(logEntity.Summary); listViewItem.SubItems.Add(logEntity.Detail); listViewItems.Add(listViewItem); } } LvSummary.Invoke(new Action>(viewItems => { LvSummary.Items.AddRange(viewItems.ToArray()); LvSummary_ColumnClick(LvSummary, new ColumnClickEventArgs(1)); LvSummary_ColumnClick(LvSummary, new ColumnClickEventArgs(1)); }), listViewItems); }).ConfigureAwait(false); } watchFile?.Invoke(); if (errorList.Any()) { Invoke(new Action(() => { new ErrorForm(string.Join(Environment.NewLine, errorList.ToArray())).Show(); })); } }); } private void btnFind_Click(object sender, EventArgs e) { var filter = BuildSearchFilter(); if (filter == null) return; var beginIndex = LvSummary.SelectedItems.Count == 0 ? 0 : LvSummary.SelectedItems[0].Index + 1; for (int i = beginIndex; i < LvSummary.Items.Count; i++) { var item = LvSummary.Items[i]; if (!filter(item.SubItems[4].Text)) continue; LvSummary.Items[i].Selected = true; LvSummary.Items[i].EnsureVisible(); return; } toolTip.Show("没有数据", (IWin32Window)sender, Point.Empty, 5000); } private Func BuildSearchFilter() { var search = txtFind.Text; if (!string.IsNullOrEmpty(search)) { if (!chkRegex.Checked) return s => s.Contains(search); var reg = new Regex(search, RegexOptions.Compiled); return s => reg.IsMatch(s); } if (LvSummary.SelectedItems.Count == 0) { balloon.Show("请输入要匹配的文本,或者选中某一个项", txtFind); balloon.Show("请输入要匹配的文本,或者选中某一个项", txtFind); return null; } search = LvSummary.SelectedItems[0].SubItems[4].Text; return s => s.Contains(search); } private void btnCount_Click(object sender, EventArgs e) { var filter = BuildSearchFilter(); if (filter == null) return; var ret = LvSummary.Items.Cast().Count(p => filter(p.SubItems[4].Text)).ToString(); toolTip.Show(ret, (IWin32Window)sender, Point.Empty, 5000); } private void btnHightlight_Click(object sender, EventArgs e) { var filter = BuildSearchFilter(); if (filter == null) return; var items = LvSummary.Items.Cast().Where(p => filter(p.SubItems[4].Text)); foreach (var item in items) { item.Font = new Font(item.Font, FontStyle.Bold); } } private void btnClearHightlight_Click(object sender, EventArgs e) { foreach (ListViewItem item in LvSummary.Items) { item.Font = new Font(item.Font, FontStyle.Regular); } } private void RemoveByMatchCondition_Click(object sender, EventArgs e) { if (LvSummary.SelectedItems.Count == 0) return; var remove = sender == btnRemove; Func filter; if (chkRegex.Checked) { var reg = new Regex(txtFind.Text, RegexOptions.Compiled); filter = p => reg.IsMatch(p.SubItems[4].Text) == !remove; } else { var sel = LvSummary.SelectedItems[0].SubItems[4].Text; if (remove) { filter = p => p.SubItems[4].Text != sel; } else { filter = p => p.SubItems[4].Text == sel; } } var nks = LvSummary.Items.Cast().Where(filter).ToArray(); LvSummary.Items.Clear(); LvSummary.Items.AddRange(nks); } private void btnClear_Click(object sender, EventArgs e) => LvSummary.Items.Clear(); private void btnCopyAll_Click(object sender, EventArgs e) { var logArray = LvSummary.Items.Cast().Select(p => p.Tag).ToArray(); Clipboard.SetText(string.Join(Environment.NewLine, logArray)); } private void btnExport_Click(object sender, EventArgs e) { var saveFileDialog = new SaveFileDialog { Filter = "文本文件|*.log", FileName = "Log.log" }; if (saveFileDialog.ShowDialog() != DialogResult.OK) return; var logArray = LvSummary.Items.Cast().Select(p => p.Tag).ToArray(); File.WriteAllText(saveFileDialog.FileName, string.Join(Environment.NewLine, logArray)); } private void LvSummary_ColumnClick(object sender, ColumnClickEventArgs e) { var myListView = (ListView)sender; // Determine if clicked column is already the column that is being sorted. if (e.Column == _lstColumnSorter.SortColumn) { // Reverse the current sort direction for this column. _lstColumnSorter.Order = _lstColumnSorter.Order == SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending; } else { // Set the column number that is to be sorted; default to ascending. _lstColumnSorter.SortColumn = e.Column; _lstColumnSorter.Order = SortOrder.Ascending; } // Perform the sort with these new sort options. myListView.Sort(); myListView.SetSortIcon(_lstColumnSorter.SortColumn, _lstColumnSorter.Order); } private void Form1_Shown(object sender, EventArgs e) { LvSummary.Items.Clear(); } private async void Form1_DragDrop(object sender, DragEventArgs e) { Enabled = false; SuspendLayout(); try { var fileStrings = (string[])e.Data.GetData(DataFormats.FileDrop); if (fileStrings.Length == 0) return; var rootPath = string.Empty; var fileInfos = fileStrings.SelectMany(file => { List flies = new List(); if (string.IsNullOrEmpty(Path.GetExtension(file))) { if (rootPath == string.Empty) rootPath = file; OperatingFileAssistant.GetAllFilesByDirectory(file, flies, "*.log"); return flies; } flies.Add(new FileInfo(file)); if (rootPath == string.Empty) rootPath = Path.GetDirectoryName(file); return flies; }).ToArray(); EnableWatcherChanged(fileInfos); await AnalysisLogFile(fileInfos, watchFile: () => { CreateFileWatcher(rootPath); }); } finally { Invoke(new Action(delegate { ResumeLayout(); Enabled = true; })); } } private void Form1_DragEnter(object sender, DragEventArgs e) { e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None; } private void switchWindow_Click(object sender, EventArgs e) { if (splitContainer.Orientation == Orientation.Horizontal) { splitContainer.Orientation = Orientation.Vertical; splitContainer.SplitterDistance = Width / 2; } else { splitContainer.Orientation = Orientation.Horizontal; splitContainer.SplitterDistance = _panel1Height; } } private int _panel1Height; private void Form1_Load(object sender, EventArgs e) { _panel1Height = splitContainer.Panel1.Height; } private void LvSummary_Click(object sender, EventArgs e) { var listView = (ListView)sender; var selectedItem = listView.SelectedItems[0]; WbJson.Document?.InvokeScript("GetOutputResult", new object[] { selectedItem.SubItems[5].Text }); } #region 文件监控 private FileSystemWatcher _watcher; [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] private void CreateFileWatcher(string path) { _watcher = new FileSystemWatcher { Path = path, Filter = "*.log", IncludeSubdirectories = true, NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.CreationTime }; _watcher.Deleted += _watcher_Deleted; _watcher.Created += _watcher_Created; _watcher.EnableRaisingEvents = true; } private void _watcher_Deleted(object sender, FileSystemEventArgs e) { _fileWatcherSize?.RemoveFileWatch(e.FullPath); RemoveListItemByFileName(e.Name); } private async void _watcher_Created(object sender, FileSystemEventArgs e) { _fileWatcherSize?.AddFileWatch(e.FullPath); await TaskExtend.Delay(500).ContinueWith(async task => { await AnalysisLogFile(new[] { new FileInfo(e.FullPath) }, 0, false); }); } public void EnableWatcherChanged(FileInfo[] fileInfos) { if (fileInfos == null || fileInfos.Length == 0) throw new ArgumentException(nameof(fileInfos)); _fileWatcherSize = new FileSizeWatcher(fileInfos); _fileWatcherSize.Changed += FileWatcherSize_Changed; } private async void FileWatcherSize_Changed(FileSizeWatcherEventArgs e) { var fileInfo = new FileInfo(e.FullPath); bool isDeleted = false; long seekBeginPosition = 0; if (e.ChangeSizeType == ChangeSizeType.Increase) { seekBeginPosition = e.OriginalLength; } else { isDeleted = true; } await AnalysisLogFile(new[] { fileInfo }, seekBeginPosition, false, null, isDeleted); } private void RemoveListItemByFileName(string fileName) { Invoke(new Action(() => { var logArray = LvSummary.Items.Cast().Where(p => ((LogEntity)p.Tag).FileName == fileName).ToArray(); foreach (var removeItem in logArray) { LvSummary.Items.Remove(removeItem); } })); } #endregion 文件监控 bool IMessageFilter.PreFilterMessage(ref Message m) { if (m.Msg == 0x20A) // WM_MOUSEWHEEL { if (_mouseOnLvSummary) { NativeMethods.SendMessage(LvSummary.Handle, 0x020A, m.WParam, m.LParam); } else { WebBrowserUtility.SendMouseWheel(m, WbJson); foreach (var form in _detailforms) { form.PreFilterMessage(ref m); } } return true; } return false; } private void LvSummary_MouseEnter(object sender, EventArgs e) => _mouseOnLvSummary = true; private void LvSummary_MouseLeave(object sender, EventArgs e) => _mouseOnLvSummary = false; private void LvSummary_MouseDoubleClick(object sender, MouseEventArgs e) { var listView = (ListView)sender; var detailForm = new DetailForm((LogEntity)listView.SelectedItems[0].Tag, form => _detailforms.Remove(form)); _detailforms.Add(detailForm); if (splitContainer.Orientation == Orientation.Vertical) { detailForm.Left += Width / 2; detailForm.Top += Height - WbJson.Height; } detailForm.Show(this); } private void LvSummary_SelectedIndexChanged(object sender, EventArgs e) { var listView = (ListView)sender; if (listView.SelectedItems.Count == 0) return; var selectedItem = listView.SelectedItems[0]; WbJson.Document?.InvokeScript("GetOutputResult", new object[] { selectedItem.SubItems[5].Text }); } } }