Cause: - Live diagnostics required logging from multiple applications without blocking TBF. - Shared nlog.config files caused configuration conflicts. - The original viewer lacked configurable colors and practical log controls. - Inconsistent diagnostic deployment caused log transfer failures and locked shared DLLs during builds. Solution: 1. Added universal diagnostic transport - Added AppDiagnostic.Logging with an NLog target and named-pipe communication. - Added background buffering and automatic reconnection. - Registered the diagnostic connector programmatically. 2. Separated diagnostic window lifecycle - Run AppDiagnostic as an independent process. - Activate the existing window on repeated launches. - Keep TBF running when the diagnostic window closes. 3. Corrected diagnostic deployment - Copy the complete diagnostic output into a dedicated AppDiagnostic subdirectory during build. - Overwrite existing deployment files to prevent inconsistent versions. - Validate and launch the executable from that subdirectory with an explicit working directory. 4. Improved the log viewer - Added configurable substring colors with a palette and live preview. - Added text, level and application filters. - Added line wrapping, clipboard actions and log export. - Added Pause/Resume, Refresh, Clear, auto-scroll and Always on top. - Added connection and log counters. - Persist color rules between sessions. 5. Removed legacy dependencies - Replaced LiveLogCache with structured diagnostic storage. - Removed AppDiagnostic dependencies on log4net and SharedComponents. Validation: - Confirmed successful log transfer when launching AppDiagnostic from TBF.
320 lines
12 KiB
C#
320 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Windows.Forms;
|
|
|
|
namespace AppDiagnostic
|
|
{
|
|
public sealed class ColorRuleEditorForm : Form
|
|
{
|
|
private readonly List<LogColorRule> rules;
|
|
private readonly ListBox ruleList = new ListBox();
|
|
private readonly TextBox patternTextBox = new TextBox();
|
|
private readonly CheckBox enabledCheckBox = new CheckBox();
|
|
private readonly CheckBox boldCheckBox = new CheckBox();
|
|
private readonly CheckBox backgroundCheckBox = new CheckBox();
|
|
private readonly Button foregroundButton = new Button();
|
|
private readonly Button backgroundButton = new Button();
|
|
private readonly RichTextBox previewBox = new RichTextBox();
|
|
private readonly Font previewBoldFont;
|
|
private bool loadingRule;
|
|
|
|
public ColorRuleEditorForm(IEnumerable<LogColorRule> sourceRules)
|
|
{
|
|
rules = sourceRules.Select(rule => rule.Clone()).ToList();
|
|
InitializeWindow();
|
|
previewBoldFont = new Font(previewBox.Font, FontStyle.Bold);
|
|
RefreshRuleList(0);
|
|
RefreshPreview();
|
|
}
|
|
|
|
public List<LogColorRule> Rules => rules.Select(rule => rule.Clone()).ToList();
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
previewBoldFont.Dispose();
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
private void InitializeWindow()
|
|
{
|
|
Text = "Log color rules";
|
|
Icon = Properties.Resources.TBF_icon;
|
|
StartPosition = FormStartPosition.CenterParent;
|
|
MinimumSize = new Size(760, 500);
|
|
ClientSize = new Size(860, 560);
|
|
|
|
var root = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 1, RowCount = 3, Padding = new Padding(10) };
|
|
root.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
|
root.RowStyles.Add(new RowStyle(SizeType.Absolute, 145F));
|
|
root.RowStyles.Add(new RowStyle(SizeType.Absolute, 42F));
|
|
|
|
var split = new SplitContainer { Dock = DockStyle.Fill, SplitterDistance = 280 };
|
|
split.Panel1.Padding = new Padding(0, 0, 8, 0);
|
|
split.Panel2.Padding = new Padding(8, 0, 0, 0);
|
|
|
|
ruleList.Dock = DockStyle.Fill;
|
|
ruleList.IntegralHeight = false;
|
|
ruleList.SelectedIndexChanged += RuleList_SelectedIndexChanged;
|
|
|
|
var listButtons = new FlowLayoutPanel { Dock = DockStyle.Bottom, Height = 36, FlowDirection = FlowDirection.LeftToRight };
|
|
listButtons.Controls.Add(CreateButton("Add", AddRule_Click));
|
|
listButtons.Controls.Add(CreateButton("Delete", DeleteRule_Click));
|
|
listButtons.Controls.Add(CreateButton("Up", MoveUp_Click));
|
|
listButtons.Controls.Add(CreateButton("Down", MoveDown_Click));
|
|
split.Panel1.Controls.Add(ruleList);
|
|
split.Panel1.Controls.Add(listButtons);
|
|
|
|
var editor = new TableLayoutPanel { Dock = DockStyle.Top, AutoSize = true, ColumnCount = 2, Padding = new Padding(4) };
|
|
editor.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 120F));
|
|
editor.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
|
|
|
enabledCheckBox.Text = "Rule enabled";
|
|
enabledCheckBox.AutoSize = true;
|
|
enabledCheckBox.CheckedChanged += EditorValueChanged;
|
|
AddEditorRow(editor, "State", enabledCheckBox);
|
|
|
|
patternTextBox.Dock = DockStyle.Fill;
|
|
patternTextBox.TextChanged += EditorValueChanged;
|
|
AddEditorRow(editor, "Substring", patternTextBox);
|
|
|
|
foregroundButton.Text = "Choose text color...";
|
|
foregroundButton.AutoSize = true;
|
|
foregroundButton.Click += ForegroundButton_Click;
|
|
AddEditorRow(editor, "Text color", foregroundButton);
|
|
|
|
var backgroundPanel = new FlowLayoutPanel { Dock = DockStyle.Fill, AutoSize = true };
|
|
backgroundCheckBox.Text = "Use background";
|
|
backgroundCheckBox.AutoSize = true;
|
|
backgroundCheckBox.CheckedChanged += EditorValueChanged;
|
|
backgroundButton.Text = "Choose...";
|
|
backgroundButton.AutoSize = true;
|
|
backgroundButton.Click += BackgroundButton_Click;
|
|
backgroundPanel.Controls.Add(backgroundCheckBox);
|
|
backgroundPanel.Controls.Add(backgroundButton);
|
|
AddEditorRow(editor, "Background", backgroundPanel);
|
|
|
|
boldCheckBox.Text = "Bold text";
|
|
boldCheckBox.AutoSize = true;
|
|
boldCheckBox.CheckedChanged += EditorValueChanged;
|
|
AddEditorRow(editor, "Font", boldCheckBox);
|
|
|
|
var resetButton = CreateButton("Restore default rules", RestoreDefaults_Click);
|
|
resetButton.AutoSize = true;
|
|
AddEditorRow(editor, string.Empty, resetButton);
|
|
split.Panel2.Controls.Add(editor);
|
|
|
|
previewBox.Dock = DockStyle.Fill;
|
|
previewBox.ReadOnly = true;
|
|
previewBox.WordWrap = true;
|
|
previewBox.Font = new Font("Consolas", 9F);
|
|
previewBox.Text = "2026-09-10 14:25:31.042|DEBUG|TBF[14280]|TBF.UI.MainWnd|Device configuration loaded.\n2026-09-10 14:25:31.384|INFO |TBF[14280]|TBF.Rig.StateMachine|Test bench started.\n2026-09-10 14:25:32.018|WARN |TBF[14280]|TBF.Device|Pressure is approaching the limit.\n2026-09-10 14:25:32.611|ERROR|TBF[14280]|TBF.Device|Communication failed.";
|
|
|
|
var previewGroup = new GroupBox { Text = "Live preview", Dock = DockStyle.Fill, Padding = new Padding(8) };
|
|
previewGroup.Controls.Add(previewBox);
|
|
|
|
var footer = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.RightToLeft, Padding = new Padding(0, 8, 0, 0) };
|
|
var okButton = new Button { Text = "OK", DialogResult = DialogResult.OK, AutoSize = true };
|
|
var cancelButton = new Button { Text = "Cancel", DialogResult = DialogResult.Cancel, AutoSize = true };
|
|
footer.Controls.Add(okButton);
|
|
footer.Controls.Add(cancelButton);
|
|
|
|
AcceptButton = okButton;
|
|
CancelButton = cancelButton;
|
|
root.Controls.Add(split, 0, 0);
|
|
root.Controls.Add(previewGroup, 0, 1);
|
|
root.Controls.Add(footer, 0, 2);
|
|
Controls.Add(root);
|
|
}
|
|
|
|
private static Button CreateButton(string text, EventHandler clickHandler)
|
|
{
|
|
var button = new Button { Text = text, AutoSize = true };
|
|
button.Click += clickHandler;
|
|
return button;
|
|
}
|
|
|
|
private static void AddEditorRow(TableLayoutPanel panel, string labelText, Control control)
|
|
{
|
|
int row = panel.RowCount++;
|
|
panel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
|
var label = new Label { Text = labelText, AutoSize = true, Anchor = AnchorStyles.Left, Margin = new Padding(3, 8, 3, 3) };
|
|
control.Margin = new Padding(3, 5, 3, 5);
|
|
panel.Controls.Add(label, 0, row);
|
|
panel.Controls.Add(control, 1, row);
|
|
}
|
|
|
|
private void RefreshRuleList(int selectedIndex)
|
|
{
|
|
ruleList.BeginUpdate();
|
|
ruleList.Items.Clear();
|
|
foreach (LogColorRule rule in rules)
|
|
{
|
|
ruleList.Items.Add(rule);
|
|
}
|
|
ruleList.EndUpdate();
|
|
|
|
if (rules.Count > 0)
|
|
{
|
|
ruleList.SelectedIndex = Math.Max(0, Math.Min(selectedIndex, rules.Count - 1));
|
|
}
|
|
}
|
|
|
|
private void RuleList_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
LogColorRule rule = ruleList.SelectedItem as LogColorRule;
|
|
if (rule == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
loadingRule = true;
|
|
enabledCheckBox.Checked = rule.Enabled;
|
|
patternTextBox.Text = rule.Pattern;
|
|
foregroundButton.BackColor = rule.ForeColor;
|
|
backgroundCheckBox.Checked = rule.UseBackColor;
|
|
backgroundButton.Enabled = rule.UseBackColor;
|
|
backgroundButton.BackColor = rule.UseBackColor ? rule.BackColor : SystemColors.Control;
|
|
boldCheckBox.Checked = rule.Bold;
|
|
loadingRule = false;
|
|
}
|
|
|
|
private void EditorValueChanged(object sender, EventArgs e)
|
|
{
|
|
if (loadingRule)
|
|
{
|
|
return;
|
|
}
|
|
|
|
LogColorRule rule = ruleList.SelectedItem as LogColorRule;
|
|
if (rule == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
rule.Enabled = enabledCheckBox.Checked;
|
|
rule.Pattern = patternTextBox.Text;
|
|
rule.UseBackColor = backgroundCheckBox.Checked;
|
|
rule.Bold = boldCheckBox.Checked;
|
|
backgroundButton.Enabled = rule.UseBackColor;
|
|
int index = ruleList.SelectedIndex;
|
|
RefreshRuleList(index);
|
|
RefreshPreview();
|
|
}
|
|
|
|
private void ForegroundButton_Click(object sender, EventArgs e)
|
|
{
|
|
LogColorRule rule = ruleList.SelectedItem as LogColorRule;
|
|
if (rule == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
using (var dialog = new ColorDialog { Color = rule.ForeColor, FullOpen = true })
|
|
{
|
|
if (dialog.ShowDialog(this) != DialogResult.OK)
|
|
{
|
|
return;
|
|
}
|
|
|
|
rule.ForeColorArgb = dialog.Color.ToArgb();
|
|
foregroundButton.BackColor = dialog.Color;
|
|
RefreshPreview();
|
|
}
|
|
}
|
|
|
|
private void BackgroundButton_Click(object sender, EventArgs e)
|
|
{
|
|
LogColorRule rule = ruleList.SelectedItem as LogColorRule;
|
|
if (rule == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Color initialColor = rule.UseBackColor ? rule.BackColor : SystemColors.Window;
|
|
using (var dialog = new ColorDialog { Color = initialColor, FullOpen = true })
|
|
{
|
|
if (dialog.ShowDialog(this) != DialogResult.OK)
|
|
{
|
|
return;
|
|
}
|
|
|
|
rule.BackColorArgb = dialog.Color.ToArgb();
|
|
rule.UseBackColor = true;
|
|
backgroundCheckBox.Checked = true;
|
|
backgroundButton.BackColor = dialog.Color;
|
|
RefreshPreview();
|
|
}
|
|
}
|
|
|
|
private void AddRule_Click(object sender, EventArgs e)
|
|
{
|
|
rules.Add(new LogColorRule { Pattern = "NEW SUBSTRING", ForeColorArgb = Color.MediumPurple.ToArgb() });
|
|
RefreshRuleList(rules.Count - 1);
|
|
patternTextBox.Focus();
|
|
patternTextBox.SelectAll();
|
|
}
|
|
|
|
private void DeleteRule_Click(object sender, EventArgs e)
|
|
{
|
|
int index = ruleList.SelectedIndex;
|
|
if (index < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
rules.RemoveAt(index);
|
|
RefreshRuleList(Math.Min(index, rules.Count - 1));
|
|
RefreshPreview();
|
|
}
|
|
|
|
private void MoveUp_Click(object sender, EventArgs e)
|
|
{
|
|
MoveSelectedRule(-1);
|
|
}
|
|
|
|
private void MoveDown_Click(object sender, EventArgs e)
|
|
{
|
|
MoveSelectedRule(1);
|
|
}
|
|
|
|
private void MoveSelectedRule(int offset)
|
|
{
|
|
int index = ruleList.SelectedIndex;
|
|
int newIndex = index + offset;
|
|
if (index < 0 || newIndex < 0 || newIndex >= rules.Count)
|
|
{
|
|
return;
|
|
}
|
|
|
|
LogColorRule rule = rules[index];
|
|
rules.RemoveAt(index);
|
|
rules.Insert(newIndex, rule);
|
|
RefreshRuleList(newIndex);
|
|
RefreshPreview();
|
|
}
|
|
|
|
private void RestoreDefaults_Click(object sender, EventArgs e)
|
|
{
|
|
rules.Clear();
|
|
rules.AddRange(LogColorRule.CreateDefaults());
|
|
RefreshRuleList(0);
|
|
RefreshPreview();
|
|
}
|
|
|
|
private void RefreshPreview()
|
|
{
|
|
int selectionStart = previewBox.SelectionStart;
|
|
int selectionLength = previewBox.SelectionLength;
|
|
string text = previewBox.Text;
|
|
LogColorizer.Apply(previewBox, 0, text.Length, text, rules, previewBox.Font, previewBoldFont);
|
|
previewBox.Select(Math.Min(selectionStart, previewBox.TextLength), Math.Min(selectionLength, Math.Max(0, previewBox.TextLength - selectionStart)));
|
|
}
|
|
}
|
|
}
|