MainForm.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Security.Permissions;
  7. using System.Text.RegularExpressions;
  8. using System.Threading.Tasks;
  9. using System.Windows.Forms;
  10. using VCommon.Logging.Viewer.Utility;
  11. namespace VCommon.Logging.Viewer
  12. {
  13. //TODO: 清除时移除文件监视,仅监视最后拖入对象(目录或文件)
  14. //TODO: 放出JSON可视化选项,作为一行
  15. public partial class MainForm : Form, IMessageFilter
  16. {
  17. private bool _mouseOnLvSummary;
  18. private FileSizeWatcher _fileWatcherSize;
  19. private readonly ColumnSorter _lstColumnSorter = new ColumnSorter();
  20. private readonly List<DetailForm> _detailforms = new List<DetailForm>();
  21. public MainForm()
  22. {
  23. InitializeComponent();
  24. var eIcon = IconExtractor.GetMainIcon();
  25. if (null != eIcon) Icon = eIcon;
  26. Application.AddMessageFilter(this);
  27. WebBrowserUtility.LoadWebBrowserResource(WbJson);
  28. LvSummary.ListViewItemSorter = _lstColumnSorter;
  29. }
  30. #pragma warning disable 4014
  31. public MainForm(string openDirectoryName) : this() => OpenFileTask(openDirectoryName, false);
  32. #pragma warning restore 4014
  33. private async Task OpenFileTask(string fileOrdirectoryName, bool openByClick = true)
  34. {
  35. if (File.Exists(fileOrdirectoryName))
  36. {
  37. var fileInfos = new[] { new FileInfo(fileOrdirectoryName) };
  38. EnableWatcherChanged(fileInfos);
  39. await AnalysisLogFile(fileInfos, watchFile: () => { CreateFileWatcher(fileOrdirectoryName); });
  40. }
  41. else
  42. {
  43. if (!openByClick)
  44. {
  45. await ProcessOpenDirectoryTask(fileOrdirectoryName);
  46. }
  47. else
  48. {
  49. FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog();
  50. if (!string.IsNullOrEmpty(fileOrdirectoryName) && Directory.Exists(fileOrdirectoryName))
  51. folderBrowserDialog.SelectedPath = fileOrdirectoryName;
  52. if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
  53. {
  54. fileOrdirectoryName = folderBrowserDialog.SelectedPath.Trim();
  55. await ProcessOpenDirectoryTask(fileOrdirectoryName);
  56. }
  57. }
  58. }
  59. }
  60. private async Task ProcessOpenDirectoryTask(string directoryName)
  61. {
  62. List<FileInfo> flieInfos = new List<FileInfo>();
  63. OperatingFileAssistant.GetAllFilesByDirectory(directoryName, flieInfos, "*.log");
  64. var fileInfos = flieInfos.ToArray();
  65. EnableWatcherChanged(fileInfos);
  66. await AnalysisLogFile(fileInfos, watchFile: () => { CreateFileWatcher(directoryName); });
  67. }
  68. private void btnSelectFile_Click(object sender, EventArgs e) { }
  69. /// <summary>
  70. /// 分析日志
  71. /// </summary>
  72. /// <param name="fileInfos">文件信息</param>
  73. /// <param name="seekBeginPosition">读取起始位置</param>
  74. /// <param name="clearListView">是否清除ListView</param>
  75. /// <param name="watchFile">监控文件委托</param>
  76. /// <param name="removeContent">是否是删除内容</param>
  77. /// <exception cref="ArgumentException">文件信息不能为空</exception>
  78. /// <returns></returns>
  79. private async Task AnalysisLogFile(FileInfo[] fileInfos, long seekBeginPosition = 0, bool clearListView = true, Action watchFile = null, bool removeContent = false)
  80. {
  81. await Task.Run(async () =>
  82. {
  83. if (fileInfos == null || fileInfos.Length == 0) throw new ArgumentException(nameof(fileInfos));
  84. if (clearListView)
  85. {
  86. LvSummary.Invoke(new Action(() =>
  87. {
  88. LvSummary.Items.Clear();
  89. }));
  90. }
  91. var errorList = new List<string>();
  92. Dictionary<FileInfo, Task<LogEntity[]>> readFileResultTasks = new Dictionary<FileInfo, Task<LogEntity[]>>();
  93. foreach (var file in fileInfos)
  94. {
  95. var line = 0;
  96. try
  97. {
  98. var tasksReadTextAsync = OperatingFileAssistant.ReadTextAsync(file, str =>
  99. {
  100. List<LogEntity> logEntities = new List<LogEntity>();
  101. string[] logContent = str.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
  102. var logEnumerator = logContent.GetEnumerator();
  103. while (logEnumerator.MoveNext())
  104. {
  105. line++;
  106. // ReSharper disable once PossibleNullReferenceException
  107. var currentContent = logEnumerator.Current.ToString();
  108. var matchCollection = Regex.Matches(currentContent, @"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) (.+? )(.+)$");
  109. if (matchCollection.Count > 0)
  110. {
  111. LogEntity logEntity = new LogEntity
  112. {
  113. WriteTime = $"{matchCollection[0].Groups[1].Value}",
  114. Level = matchCollection[0].Groups[2].Value,
  115. ThreadName = matchCollection[0].Groups[3].Value,
  116. FileName = file.Name,
  117. LineNumber = line
  118. };
  119. if (logEnumerator.MoveNext() == false) break;
  120. line++;
  121. logEntity.Summary = logEnumerator.Current.ToString();
  122. if (logEnumerator.MoveNext() == false) break;
  123. line++;
  124. logEntity.Detail = logEnumerator.Current.ToString();
  125. logEntities.Add(logEntity);
  126. }
  127. }
  128. return logEntities.ToArray();
  129. }, seekBeginPosition);
  130. readFileResultTasks.Add(file, tasksReadTextAsync);
  131. }
  132. catch (Exception ex)
  133. {
  134. errorList.Add(string.Format("于文件{1}行{2}解析错误:{0}", ex, file.Name, line));
  135. }
  136. }
  137. readFileResultTasks = readFileResultTasks.OrderByDescending(c => c.Key.LastWriteTime)
  138. .ToDictionary(p => p.Key, o => o.Value);
  139. await Task.WhenAll(readFileResultTasks.Select(c => c.Value).ToArray()).ConfigureAwait(false);
  140. //如果是移除,只需比较ListView数据,删除即可
  141. if (removeContent)
  142. {
  143. LvSummary.Invoke(new Action(() =>
  144. {
  145. var listViewItems = LvSummary.Items.Cast<ListViewItem>().Where(p => p.Text == fileInfos[0].Name).ToArray();
  146. var logEntities = readFileResultTasks.SelectMany(c => c.Value.Result).Select(c => c.WriteTime);
  147. var viewItems = listViewItems.Where(p => !logEntities.Contains(p.SubItems[1].Text)).ToArray();
  148. foreach (var item in viewItems)
  149. {
  150. LvSummary.Items.Remove(item);
  151. }
  152. }));
  153. }
  154. else
  155. {
  156. await Task.Factory.StartNew(() =>
  157. {
  158. List<ListViewItem> listViewItems = new List<ListViewItem>();
  159. foreach (var task in readFileResultTasks)
  160. {
  161. foreach (var logEntity in task.Value.Result.OrderBy(c => c.WriteTime))
  162. {
  163. ListViewItem listViewItem = new ListViewItem
  164. {
  165. Text = task.Key.Name,
  166. Tag = logEntity
  167. };
  168. listViewItem.SubItems.Add(logEntity.WriteTime);
  169. listViewItem.SubItems.Add(logEntity.Level);
  170. listViewItem.SubItems.Add(logEntity.ThreadName);
  171. listViewItem.SubItems.Add(logEntity.Summary);
  172. listViewItem.SubItems.Add(logEntity.Detail);
  173. listViewItems.Add(listViewItem);
  174. }
  175. }
  176. LvSummary.Invoke(new Action<List<ListViewItem>>(viewItems =>
  177. {
  178. LvSummary.Items.AddRange(viewItems.ToArray());
  179. LvSummary_ColumnClick(LvSummary, new ColumnClickEventArgs(1));
  180. LvSummary_ColumnClick(LvSummary, new ColumnClickEventArgs(1));
  181. }), listViewItems);
  182. }).ConfigureAwait(false);
  183. }
  184. watchFile?.Invoke();
  185. if (errorList.Any())
  186. {
  187. Invoke(new Action(() =>
  188. {
  189. new ErrorForm(string.Join(Environment.NewLine, errorList.ToArray())).Show();
  190. }));
  191. }
  192. });
  193. }
  194. private void btnFind_Click(object sender, EventArgs e)
  195. {
  196. var filter = BuildSearchFilter();
  197. if (filter == null) return;
  198. var beginIndex = LvSummary.SelectedItems.Count == 0 ? 0 : LvSummary.SelectedItems[0].Index + 1;
  199. for (int i = beginIndex; i < LvSummary.Items.Count; i++)
  200. {
  201. var item = LvSummary.Items[i];
  202. if (!filter(item.SubItems[4].Text)) continue;
  203. LvSummary.Items[i].Selected = true;
  204. LvSummary.Items[i].EnsureVisible();
  205. return;
  206. }
  207. toolTip.Show("没有数据", (IWin32Window)sender, Point.Empty, 5000);
  208. }
  209. private Func<string, bool> BuildSearchFilter()
  210. {
  211. var search = txtFind.Text;
  212. if (!string.IsNullOrEmpty(search))
  213. {
  214. if (!chkRegex.Checked)
  215. return s => s.Contains(search);
  216. var reg = new Regex(search, RegexOptions.Compiled);
  217. return s => reg.IsMatch(s);
  218. }
  219. if (LvSummary.SelectedItems.Count == 0)
  220. {
  221. balloon.Show("请输入要匹配的文本,或者选中某一个项", txtFind);
  222. balloon.Show("请输入要匹配的文本,或者选中某一个项", txtFind);
  223. return null;
  224. }
  225. search = LvSummary.SelectedItems[0].SubItems[4].Text;
  226. return s => s.Contains(search);
  227. }
  228. private void btnCount_Click(object sender, EventArgs e)
  229. {
  230. var filter = BuildSearchFilter();
  231. if (filter == null) return;
  232. var ret = LvSummary.Items.Cast<ListViewItem>().Count(p => filter(p.SubItems[4].Text)).ToString();
  233. toolTip.Show(ret, (IWin32Window)sender, Point.Empty, 5000);
  234. }
  235. private void btnHightlight_Click(object sender, EventArgs e)
  236. {
  237. var filter = BuildSearchFilter();
  238. if (filter == null) return;
  239. var items = LvSummary.Items.Cast<ListViewItem>().Where(p => filter(p.SubItems[4].Text));
  240. foreach (var item in items)
  241. {
  242. item.Font = new Font(item.Font, FontStyle.Bold);
  243. }
  244. }
  245. private void btnClearHightlight_Click(object sender, EventArgs e)
  246. {
  247. foreach (ListViewItem item in LvSummary.Items)
  248. {
  249. item.Font = new Font(item.Font, FontStyle.Regular);
  250. }
  251. }
  252. private void RemoveByMatchCondition_Click(object sender, EventArgs e)
  253. {
  254. if (LvSummary.SelectedItems.Count == 0)
  255. return;
  256. var remove = sender == btnRemove;
  257. Func<ListViewItem, bool> filter;
  258. if (chkRegex.Checked)
  259. {
  260. var reg = new Regex(txtFind.Text, RegexOptions.Compiled);
  261. filter = p => reg.IsMatch(p.SubItems[4].Text) == !remove;
  262. }
  263. else
  264. {
  265. var sel = LvSummary.SelectedItems[0].SubItems[4].Text;
  266. if (remove)
  267. {
  268. filter = p => p.SubItems[4].Text != sel;
  269. }
  270. else
  271. {
  272. filter = p => p.SubItems[4].Text == sel;
  273. }
  274. }
  275. var nks = LvSummary.Items.Cast<ListViewItem>().Where(filter).ToArray();
  276. LvSummary.Items.Clear();
  277. LvSummary.Items.AddRange(nks);
  278. }
  279. private void btnClear_Click(object sender, EventArgs e) => LvSummary.Items.Clear();
  280. private void btnCopyAll_Click(object sender, EventArgs e)
  281. {
  282. var logArray = LvSummary.Items.Cast<ListViewItem>().Select(p => p.Tag).ToArray();
  283. Clipboard.SetText(string.Join(Environment.NewLine, logArray));
  284. }
  285. private void btnExport_Click(object sender, EventArgs e)
  286. {
  287. var saveFileDialog = new SaveFileDialog
  288. {
  289. Filter = "文本文件|*.log",
  290. FileName = "Log.log"
  291. };
  292. if (saveFileDialog.ShowDialog() != DialogResult.OK) return;
  293. var logArray = LvSummary.Items.Cast<ListViewItem>().Select(p => p.Tag).ToArray();
  294. File.WriteAllText(saveFileDialog.FileName, string.Join(Environment.NewLine, logArray));
  295. }
  296. private void LvSummary_ColumnClick(object sender, ColumnClickEventArgs e)
  297. {
  298. var myListView = (ListView)sender;
  299. // Determine if clicked column is already the column that is being sorted.
  300. if (e.Column == _lstColumnSorter.SortColumn)
  301. {
  302. // Reverse the current sort direction for this column.
  303. _lstColumnSorter.Order =
  304. _lstColumnSorter.Order == SortOrder.Ascending
  305. ? SortOrder.Descending
  306. : SortOrder.Ascending;
  307. }
  308. else
  309. {
  310. // Set the column number that is to be sorted; default to ascending.
  311. _lstColumnSorter.SortColumn = e.Column;
  312. _lstColumnSorter.Order = SortOrder.Ascending;
  313. }
  314. // Perform the sort with these new sort options.
  315. myListView.Sort();
  316. myListView.SetSortIcon(_lstColumnSorter.SortColumn, _lstColumnSorter.Order);
  317. }
  318. private void Form1_Shown(object sender, EventArgs e)
  319. {
  320. LvSummary.Items.Clear();
  321. }
  322. private async void Form1_DragDrop(object sender, DragEventArgs e)
  323. {
  324. Enabled = false;
  325. SuspendLayout();
  326. try
  327. {
  328. var fileStrings = (string[])e.Data.GetData(DataFormats.FileDrop);
  329. if (fileStrings.Length == 0) return;
  330. var rootPath = string.Empty;
  331. var fileInfos = fileStrings.SelectMany(file =>
  332. {
  333. List<FileInfo> flies = new List<FileInfo>();
  334. if (string.IsNullOrEmpty(Path.GetExtension(file)))
  335. {
  336. if (rootPath == string.Empty) rootPath = file;
  337. OperatingFileAssistant.GetAllFilesByDirectory(file, flies, "*.log");
  338. return flies;
  339. }
  340. flies.Add(new FileInfo(file));
  341. if (rootPath == string.Empty) rootPath = Path.GetDirectoryName(file);
  342. return flies;
  343. }).ToArray();
  344. EnableWatcherChanged(fileInfos);
  345. await AnalysisLogFile(fileInfos, watchFile: () => { CreateFileWatcher(rootPath); });
  346. }
  347. finally
  348. {
  349. Invoke(new Action(delegate
  350. {
  351. ResumeLayout();
  352. Enabled = true;
  353. }));
  354. }
  355. }
  356. private void Form1_DragEnter(object sender, DragEventArgs e)
  357. {
  358. e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
  359. ? DragDropEffects.Copy
  360. : DragDropEffects.None;
  361. }
  362. private void switchWindow_Click(object sender, EventArgs e)
  363. {
  364. if (splitContainer.Orientation == Orientation.Horizontal)
  365. {
  366. splitContainer.Orientation = Orientation.Vertical;
  367. splitContainer.SplitterDistance = Width / 2;
  368. }
  369. else
  370. {
  371. splitContainer.Orientation = Orientation.Horizontal;
  372. splitContainer.SplitterDistance = _panel1Height;
  373. }
  374. }
  375. private int _panel1Height;
  376. private void Form1_Load(object sender, EventArgs e)
  377. {
  378. _panel1Height = splitContainer.Panel1.Height;
  379. }
  380. private void LvSummary_Click(object sender, EventArgs e)
  381. {
  382. var listView = (ListView)sender;
  383. var selectedItem = listView.SelectedItems[0];
  384. WbJson.Document?.InvokeScript("GetOutputResult", new object[] { selectedItem.SubItems[5].Text });
  385. }
  386. #region 文件监控
  387. private FileSystemWatcher _watcher;
  388. [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
  389. private void CreateFileWatcher(string path)
  390. {
  391. _watcher = new FileSystemWatcher
  392. {
  393. Path = path,
  394. Filter = "*.log",
  395. IncludeSubdirectories = true,
  396. NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
  397. | NotifyFilters.FileName | NotifyFilters.DirectoryName
  398. | NotifyFilters.CreationTime
  399. };
  400. _watcher.Deleted += _watcher_Deleted;
  401. _watcher.Created += _watcher_Created;
  402. _watcher.EnableRaisingEvents = true;
  403. }
  404. private void _watcher_Deleted(object sender, FileSystemEventArgs e)
  405. {
  406. _fileWatcherSize?.RemoveFileWatch(e.FullPath);
  407. RemoveListItemByFileName(e.Name);
  408. }
  409. private async void _watcher_Created(object sender, FileSystemEventArgs e)
  410. {
  411. _fileWatcherSize?.AddFileWatch(e.FullPath);
  412. await TaskExtend.Delay(500).ContinueWith(async task =>
  413. {
  414. await AnalysisLogFile(new[] { new FileInfo(e.FullPath) }, 0, false);
  415. });
  416. }
  417. public void EnableWatcherChanged(FileInfo[] fileInfos)
  418. {
  419. if (fileInfos == null || fileInfos.Length == 0)
  420. throw new ArgumentException(nameof(fileInfos));
  421. _fileWatcherSize = new FileSizeWatcher(fileInfos);
  422. _fileWatcherSize.Changed += FileWatcherSize_Changed;
  423. }
  424. private async void FileWatcherSize_Changed(FileSizeWatcherEventArgs e)
  425. {
  426. var fileInfo = new FileInfo(e.FullPath);
  427. bool isDeleted = false;
  428. long seekBeginPosition = 0;
  429. if (e.ChangeSizeType == ChangeSizeType.Increase)
  430. {
  431. seekBeginPosition = e.OriginalLength;
  432. }
  433. else
  434. {
  435. isDeleted = true;
  436. }
  437. await AnalysisLogFile(new[] { fileInfo }, seekBeginPosition, false, null, isDeleted);
  438. }
  439. private void RemoveListItemByFileName(string fileName)
  440. {
  441. Invoke(new Action(() =>
  442. {
  443. var logArray = LvSummary.Items.Cast<ListViewItem>().Where(p => ((LogEntity)p.Tag).FileName == fileName).ToArray();
  444. foreach (var removeItem in logArray)
  445. {
  446. LvSummary.Items.Remove(removeItem);
  447. }
  448. }));
  449. }
  450. #endregion 文件监控
  451. bool IMessageFilter.PreFilterMessage(ref Message m)
  452. {
  453. if (m.Msg == 0x20A) // WM_MOUSEWHEEL
  454. {
  455. if (_mouseOnLvSummary)
  456. {
  457. NativeMethods.SendMessage(LvSummary.Handle, 0x020A, m.WParam, m.LParam);
  458. }
  459. else
  460. {
  461. WebBrowserUtility.SendMouseWheel(m, WbJson);
  462. foreach (var form in _detailforms)
  463. {
  464. form.PreFilterMessage(ref m);
  465. }
  466. }
  467. return true;
  468. }
  469. return false;
  470. }
  471. private void LvSummary_MouseEnter(object sender, EventArgs e) => _mouseOnLvSummary = true;
  472. private void LvSummary_MouseLeave(object sender, EventArgs e) => _mouseOnLvSummary = false;
  473. private void LvSummary_MouseDoubleClick(object sender, MouseEventArgs e)
  474. {
  475. var listView = (ListView)sender;
  476. var detailForm = new DetailForm((LogEntity)listView.SelectedItems[0].Tag, form => _detailforms.Remove(form));
  477. _detailforms.Add(detailForm);
  478. if (splitContainer.Orientation == Orientation.Vertical)
  479. {
  480. detailForm.Left += Width / 2;
  481. detailForm.Top += Height - WbJson.Height;
  482. }
  483. detailForm.Show(this);
  484. }
  485. private void LvSummary_SelectedIndexChanged(object sender, EventArgs e)
  486. {
  487. var listView = (ListView)sender;
  488. if (listView.SelectedItems.Count == 0) return;
  489. var selectedItem = listView.SelectedItems[0];
  490. WbJson.Document?.InvokeScript("GetOutputResult", new object[] { selectedItem.SubItems[5].Text });
  491. }
  492. }
  493. }