diff --git a/AppDiagnostic.Logging/AppDiagnostic.Logging.csproj b/AppDiagnostic.Logging/AppDiagnostic.Logging.csproj
new file mode 100644
index 000000000..ae4c61c62
--- /dev/null
+++ b/AppDiagnostic.Logging/AppDiagnostic.Logging.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net472
+ 7.3
+ AppDiagnostic.Logging
+ AppDiagnostic.Logging
+ true
+ {A7AF8ED2-362E-4DBB-B607-EBA2820719EC}
+
+
+
+
+
+
+
+
diff --git a/AppDiagnostic.Logging/AppDiagnosticConnector.cs b/AppDiagnostic.Logging/AppDiagnosticConnector.cs
new file mode 100644
index 000000000..0af3396cd
--- /dev/null
+++ b/AppDiagnostic.Logging/AppDiagnosticConnector.cs
@@ -0,0 +1,41 @@
+using NLog;
+using NLog.Config;
+using NLog.Layouts;
+
+namespace AppDiagnostic.Logging
+{
+ public static class AppDiagnosticConnector
+ {
+ private static readonly object syncRoot = new object();
+
+ public static void Register()
+ {
+ lock (syncRoot)
+ {
+ LoggingConfiguration configuration = LogManager.Configuration ?? new LoggingConfiguration();
+ NLog.Targets.Target existingTarget = configuration.FindTargetByName("appDiagnostic");
+
+ if (existingTarget != null)
+ {
+ return;
+ }
+
+ var target = new AppDiagnosticTarget
+ {
+ Name = "appDiagnostic",
+ PipeName = DiagnosticProtocol.DefaultPipeName,
+ QueueLimit = DiagnosticProtocol.DefaultQueueLimit,
+ ReconnectDelayMilliseconds = DiagnosticProtocol.DefaultReconnectDelayMilliseconds,
+ ConnectTimeoutMilliseconds = DiagnosticProtocol.DefaultConnectTimeoutMilliseconds,
+ Layout = new SimpleLayout("${message}")
+ };
+
+ configuration.AddTarget(target.Name, target);
+ configuration.AddRule(LogLevel.Trace, LogLevel.Fatal, target, "*");
+
+ LogManager.Configuration = configuration;
+ LogManager.ReconfigExistingLoggers();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/AppDiagnostic.Logging/AppDiagnosticTarget.cs b/AppDiagnostic.Logging/AppDiagnosticTarget.cs
new file mode 100644
index 000000000..9136a09f0
--- /dev/null
+++ b/AppDiagnostic.Logging/AppDiagnosticTarget.cs
@@ -0,0 +1,233 @@
+using NLog;
+using NLog.Targets;
+using System;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.IO;
+using System.IO.Pipes;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace AppDiagnostic.Logging
+{
+ [Target("AppDiagnostic")]
+ public sealed class AppDiagnosticTarget : TargetWithLayout
+ {
+ private readonly object connectionLock = new object();
+ private readonly string applicationName;
+ private readonly int processId;
+
+ private BlockingCollection queue;
+ private CancellationTokenSource cancellation;
+ private Task senderTask;
+ private NamedPipeClientStream pipeClient;
+
+ public AppDiagnosticTarget()
+ {
+ using (Process process = Process.GetCurrentProcess())
+ {
+ applicationName = process.ProcessName;
+ processId = process.Id;
+ }
+
+ PipeName = DiagnosticProtocol.DefaultPipeName;
+ QueueLimit = DiagnosticProtocol.DefaultQueueLimit;
+ ReconnectDelayMilliseconds =
+ DiagnosticProtocol.DefaultReconnectDelayMilliseconds;
+ ConnectTimeoutMilliseconds =
+ DiagnosticProtocol.DefaultConnectTimeoutMilliseconds;
+ }
+
+ public string PipeName { get; set; }
+
+ public int QueueLimit { get; set; }
+
+ public int ReconnectDelayMilliseconds { get; set; }
+
+ public int ConnectTimeoutMilliseconds { get; set; }
+
+ protected override void InitializeTarget()
+ {
+ base.InitializeTarget();
+
+ int queueLimit = QueueLimit > 0
+ ? QueueLimit
+ : DiagnosticProtocol.DefaultQueueLimit;
+
+ queue = new BlockingCollection(queueLimit);
+ cancellation = new CancellationTokenSource();
+
+ senderTask = Task.Factory.StartNew(
+ ProcessQueue,
+ cancellation.Token,
+ TaskCreationOptions.LongRunning,
+ TaskScheduler.Default);
+ }
+
+ protected override void Write(LogEventInfo logEvent)
+ {
+ if (logEvent == null || queue == null || queue.IsAddingCompleted)
+ {
+ return;
+ }
+
+ var entry = new DiagnosticLogEntry
+ {
+ TimestampUtc = logEvent.TimeStamp.ToUniversalTime(),
+ MachineName = Environment.MachineName,
+ ApplicationName = applicationName,
+ ProcessId = processId,
+ LoggerName = logEvent.LoggerName ?? string.Empty,
+ Level = logEvent.Level?.Name ?? string.Empty,
+ Message = Layout != null
+ ? Layout.Render(logEvent)
+ : logEvent.FormattedMessage,
+ Exception = logEvent.Exception?.ToString() ?? string.Empty
+ };
+
+ try
+ {
+ queue.TryAdd(entry);
+ }
+ catch (ObjectDisposedException)
+ {
+ // A concurrent NLog shutdown may dispose the queue.
+ }
+ catch (InvalidOperationException)
+ {
+ // A concurrent NLog shutdown may complete the queue.
+ }
+ }
+
+ protected override void CloseTarget()
+ {
+ BlockingCollection currentQueue = queue;
+ CancellationTokenSource currentCancellation = cancellation;
+
+ if (currentQueue != null)
+ {
+ currentQueue.CompleteAdding();
+ }
+
+ if (currentCancellation != null)
+ {
+ currentCancellation.Cancel();
+ }
+
+ CloseConnection();
+
+ try
+ {
+ senderTask?.Wait(2000);
+ }
+ catch (AggregateException)
+ {
+ // Cancellation during application shutdown is expected.
+ }
+
+ currentQueue?.Dispose();
+ currentCancellation?.Dispose();
+
+ queue = null;
+ cancellation = null;
+ senderTask = null;
+
+ base.CloseTarget();
+ }
+
+ private void ProcessQueue()
+ {
+ CancellationToken token = cancellation.Token;
+
+ try
+ {
+ foreach (DiagnosticLogEntry entry in
+ queue.GetConsumingEnumerable(token))
+ {
+ SendWhenConnected(entry, token);
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ // Target shutdown is expected to cancel the sender.
+ }
+ }
+
+ private void SendWhenConnected(
+ DiagnosticLogEntry entry,
+ CancellationToken token)
+ {
+ while (!token.IsCancellationRequested)
+ {
+ try
+ {
+ EnsureConnected();
+ DiagnosticMessageSerializer.WriteFramed(pipeClient, entry);
+ return;
+ }
+ catch (Exception exception) when (
+ exception is IOException ||
+ exception is TimeoutException ||
+ exception is InvalidOperationException)
+ {
+ CloseConnection();
+
+ int delay = ReconnectDelayMilliseconds > 0
+ ? ReconnectDelayMilliseconds
+ : DiagnosticProtocol.DefaultReconnectDelayMilliseconds;
+
+ if (token.WaitHandle.WaitOne(delay))
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ private void EnsureConnected()
+ {
+ lock (connectionLock)
+ {
+ if (pipeClient != null && pipeClient.IsConnected)
+ {
+ return;
+ }
+
+ CloseConnectionWithoutLock();
+
+ pipeClient = new NamedPipeClientStream(
+ ".",
+ string.IsNullOrWhiteSpace(PipeName)
+ ? DiagnosticProtocol.DefaultPipeName
+ : PipeName,
+ PipeDirection.Out,
+ PipeOptions.Asynchronous);
+
+ int timeout = ConnectTimeoutMilliseconds > 0
+ ? ConnectTimeoutMilliseconds
+ : DiagnosticProtocol.DefaultConnectTimeoutMilliseconds;
+
+ pipeClient.Connect(timeout);
+ }
+ }
+
+ private void CloseConnection()
+ {
+ lock (connectionLock)
+ {
+ CloseConnectionWithoutLock();
+ }
+ }
+
+ private void CloseConnectionWithoutLock()
+ {
+ if (pipeClient == null)
+ {
+ return;
+ }
+
+ pipeClient.Dispose();
+ pipeClient = null;
+ }
+ }
+}
diff --git a/AppDiagnostic.Logging/DiagnosticLogEntry.cs b/AppDiagnostic.Logging/DiagnosticLogEntry.cs
new file mode 100644
index 000000000..517670cb1
--- /dev/null
+++ b/AppDiagnostic.Logging/DiagnosticLogEntry.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace AppDiagnostic.Logging
+{
+ [DataContract]
+ public sealed class DiagnosticLogEntry
+ {
+ [DataMember(Order = 1)]
+ public DateTime TimestampUtc { get; set; }
+
+ [DataMember(Order = 2)]
+ public string MachineName { get; set; }
+
+ [DataMember(Order = 3)]
+ public string ApplicationName { get; set; }
+
+ [DataMember(Order = 4)]
+ public int ProcessId { get; set; }
+
+ [DataMember(Order = 5)]
+ public string LoggerName { get; set; }
+
+ [DataMember(Order = 6)]
+ public string Level { get; set; }
+
+ [DataMember(Order = 7)]
+ public string Message { get; set; }
+
+ [DataMember(Order = 8)]
+ public string Exception { get; set; }
+ }
+}
diff --git a/AppDiagnostic.Logging/DiagnosticMessageSerializer.cs b/AppDiagnostic.Logging/DiagnosticMessageSerializer.cs
new file mode 100644
index 000000000..a4fb35595
--- /dev/null
+++ b/AppDiagnostic.Logging/DiagnosticMessageSerializer.cs
@@ -0,0 +1,113 @@
+using System;
+using System.IO;
+using System.Runtime.Serialization.Json;
+
+namespace AppDiagnostic.Logging
+{
+ public static class DiagnosticMessageSerializer
+ {
+ private static readonly DataContractJsonSerializer Serializer =
+ new DataContractJsonSerializer(typeof(DiagnosticLogEntry));
+
+ private static readonly object SerializerLock = new object();
+
+ public static byte[] Serialize(DiagnosticLogEntry entry)
+ {
+ if (entry == null)
+ {
+ throw new ArgumentNullException(nameof(entry));
+ }
+
+ using (var stream = new MemoryStream())
+ {
+ lock (SerializerLock)
+ {
+ Serializer.WriteObject(stream, entry);
+ }
+
+ return stream.ToArray();
+ }
+ }
+
+ public static DiagnosticLogEntry Deserialize(byte[] payload)
+ {
+ if (payload == null)
+ {
+ throw new ArgumentNullException(nameof(payload));
+ }
+
+ if (payload.Length == 0 ||
+ payload.Length > DiagnosticProtocol.MaximumMessageSize)
+ {
+ throw new InvalidDataException("Invalid diagnostic message size.");
+ }
+
+ using (var stream = new MemoryStream(payload, false))
+ {
+ lock (SerializerLock)
+ {
+ return (DiagnosticLogEntry)Serializer.ReadObject(stream);
+ }
+ }
+ }
+
+ public static void WriteFramed(Stream stream, DiagnosticLogEntry entry)
+ {
+ if (stream == null)
+ {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ byte[] payload = Serialize(entry);
+
+ if (payload.Length > DiagnosticProtocol.MaximumMessageSize)
+ {
+ throw new InvalidDataException("Diagnostic message is too large.");
+ }
+
+ byte[] length = BitConverter.GetBytes(payload.Length);
+ stream.Write(length, 0, length.Length);
+ stream.Write(payload, 0, payload.Length);
+ stream.Flush();
+ }
+
+ public static DiagnosticLogEntry ReadFramed(Stream stream)
+ {
+ if (stream == null)
+ {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ byte[] lengthBuffer = ReadExactly(stream, sizeof(int));
+ int payloadLength = BitConverter.ToInt32(lengthBuffer, 0);
+
+ if (payloadLength <= 0 ||
+ payloadLength > DiagnosticProtocol.MaximumMessageSize)
+ {
+ throw new InvalidDataException("Invalid diagnostic message size.");
+ }
+
+ return Deserialize(ReadExactly(stream, payloadLength));
+ }
+
+ private static byte[] ReadExactly(Stream stream, int count)
+ {
+ byte[] buffer = new byte[count];
+ int offset = 0;
+
+ while (offset < count)
+ {
+ int bytesRead = stream.Read(buffer, offset, count - offset);
+
+ if (bytesRead == 0)
+ {
+ throw new EndOfStreamException();
+ }
+
+ offset += bytesRead;
+ }
+
+ return buffer;
+ }
+ }
+}
diff --git a/AppDiagnostic.Logging/DiagnosticProtocol.cs b/AppDiagnostic.Logging/DiagnosticProtocol.cs
new file mode 100644
index 000000000..fc4be8e62
--- /dev/null
+++ b/AppDiagnostic.Logging/DiagnosticProtocol.cs
@@ -0,0 +1,11 @@
+namespace AppDiagnostic.Logging
+{
+ public static class DiagnosticProtocol
+ {
+ public const string DefaultPipeName = "Sensus.AppDiagnostic.LogPipe";
+ public const int MaximumMessageSize = 1024 * 1024;
+ public const int DefaultQueueLimit = 10000;
+ public const int DefaultReconnectDelayMilliseconds = 500;
+ public const int DefaultConnectTimeoutMilliseconds = 250;
+ }
+}
diff --git a/AppDiagnostic.Logging/README.md b/AppDiagnostic.Logging/README.md
new file mode 100644
index 000000000..4c381ac38
--- /dev/null
+++ b/AppDiagnostic.Logging/README.md
@@ -0,0 +1,37 @@
+# AppDiagnostic.Logging
+
+Reusable NLog target for sending structured diagnostic events from multiple
+.NET Framework processes to `AppDiagnostic.exe` through a local named pipe.
+
+## Producer integration
+
+Reference this project from the producer application and add the extension and
+target to its NLog configuration:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+```
+
+The target writes to a bounded background queue. Producer threads are not
+blocked while the diagnostic viewer is unavailable. Queued messages are sent
+after the viewer becomes available; new messages are discarded when the queue
+limit is reached.
+
+The receiver can read messages with:
+
+```csharp
+DiagnosticLogEntry entry =
+ DiagnosticMessageSerializer.ReadFramed(namedPipeServerStream);
+```
diff --git a/AppDiagnostic.Logging/bin/Debug/net472/AppDiagnostic.Logging.dll b/AppDiagnostic.Logging/bin/Debug/net472/AppDiagnostic.Logging.dll
new file mode 100644
index 000000000..fa3b3ef1c
Binary files /dev/null and b/AppDiagnostic.Logging/bin/Debug/net472/AppDiagnostic.Logging.dll differ
diff --git a/AppDiagnostic.Logging/bin/Debug/net472/AppDiagnostic.Logging.pdb b/AppDiagnostic.Logging/bin/Debug/net472/AppDiagnostic.Logging.pdb
new file mode 100644
index 000000000..c8da8e1d1
Binary files /dev/null and b/AppDiagnostic.Logging/bin/Debug/net472/AppDiagnostic.Logging.pdb differ
diff --git a/AppDiagnostic.Logging/bin/Debug/net472/NLog.dll b/AppDiagnostic.Logging/bin/Debug/net472/NLog.dll
new file mode 100644
index 000000000..d519ffc52
Binary files /dev/null and b/AppDiagnostic.Logging/bin/Debug/net472/NLog.dll differ
diff --git a/AppDiagnostic.Logging/bin/Release/net472/AppDiagnostic.Logging.dll b/AppDiagnostic.Logging/bin/Release/net472/AppDiagnostic.Logging.dll
new file mode 100644
index 000000000..92ba99691
Binary files /dev/null and b/AppDiagnostic.Logging/bin/Release/net472/AppDiagnostic.Logging.dll differ
diff --git a/AppDiagnostic.Logging/bin/Release/net472/AppDiagnostic.Logging.pdb b/AppDiagnostic.Logging/bin/Release/net472/AppDiagnostic.Logging.pdb
new file mode 100644
index 000000000..20f7774dc
Binary files /dev/null and b/AppDiagnostic.Logging/bin/Release/net472/AppDiagnostic.Logging.pdb differ
diff --git a/AppDiagnostic.Logging/bin/Release/net472/NLog.dll b/AppDiagnostic.Logging/bin/Release/net472/NLog.dll
new file mode 100644
index 000000000..d519ffc52
Binary files /dev/null and b/AppDiagnostic.Logging/bin/Release/net472/NLog.dll differ
diff --git a/AppDiagnostic.Logging/obj/AppDiagnostic.Logging.csproj.nuget.dgspec.json b/AppDiagnostic.Logging/obj/AppDiagnostic.Logging.csproj.nuget.dgspec.json
new file mode 100644
index 000000000..1cec92f33
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/AppDiagnostic.Logging.csproj.nuget.dgspec.json
@@ -0,0 +1,65 @@
+{
+ "format": 1,
+ "restore": {
+ "C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\AppDiagnostic.Logging.csproj": {}
+ },
+ "projects": {
+ "C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\AppDiagnostic.Logging.csproj": {
+ "version": "1.0.0",
+ "restore": {
+ "projectUniqueName": "C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\AppDiagnostic.Logging.csproj",
+ "projectName": "AppDiagnostic.Logging",
+ "projectPath": "C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\AppDiagnostic.Logging.csproj",
+ "packagesPath": "C:\\Users\\MFRNIAK\\.nuget\\packages\\",
+ "outputPath": "C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\obj\\",
+ "projectStyle": "PackageReference",
+ "fallbackFolders": [
+ "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
+ ],
+ "configFilePaths": [
+ "C:\\Users\\MFRNIAK\\AppData\\Roaming\\NuGet\\NuGet.Config",
+ "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
+ "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
+ ],
+ "originalTargetFrameworks": [
+ "net472"
+ ],
+ "sources": {
+ "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
+ "https://api.nuget.org/v3/index.json": {}
+ },
+ "frameworks": {
+ "net472": {
+ "framework": "net472",
+ "targetAlias": "net472",
+ "projectReferences": {}
+ }
+ },
+ "warningProperties": {
+ "warnAsError": [
+ "NU1605"
+ ]
+ },
+ "restoreAuditProperties": {
+ "enableAudit": "true",
+ "auditLevel": "low",
+ "auditMode": "direct"
+ },
+ "SdkAnalysisLevel": "10.0.300"
+ },
+ "frameworks": {
+ "net472": {
+ "framework": "net472",
+ "targetAlias": "net472",
+ "dependencies": {
+ "NLog": {
+ "target": "Package",
+ "version": "[5.2.2, )"
+ }
+ },
+ "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.303\\RuntimeIdentifierGraph.json"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/AppDiagnostic.Logging/obj/AppDiagnostic.Logging.csproj.nuget.g.props b/AppDiagnostic.Logging/obj/AppDiagnostic.Logging.csproj.nuget.g.props
new file mode 100644
index 000000000..4d5b22437
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/AppDiagnostic.Logging.csproj.nuget.g.props
@@ -0,0 +1,16 @@
+
+
+
+ True
+ NuGet
+ $(MSBuildThisFileDirectory)project.assets.json
+ $(UserProfile)\.nuget\packages\
+ C:\Users\MFRNIAK\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages
+ PackageReference
+ 7.0.0
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AppDiagnostic.Logging/obj/AppDiagnostic.Logging.csproj.nuget.g.targets b/AppDiagnostic.Logging/obj/AppDiagnostic.Logging.csproj.nuget.g.targets
new file mode 100644
index 000000000..3dc06ef3c
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/AppDiagnostic.Logging.csproj.nuget.g.targets
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/AppDiagnostic.Logging/obj/Debug/net472/.NETFramework,Version=v4.7.2.AssemblyAttributes.cs b/AppDiagnostic.Logging/obj/Debug/net472/.NETFramework,Version=v4.7.2.AssemblyAttributes.cs
new file mode 100644
index 000000000..3871b184d
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/Debug/net472/.NETFramework,Version=v4.7.2.AssemblyAttributes.cs
@@ -0,0 +1,4 @@
+//
+using System;
+using System.Reflection;
+[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
diff --git a/AppDiagnostic.Logging/obj/Debug/net472/AppDiagn.CFEA7AC0.Up2Date b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagn.CFEA7AC0.Up2Date
new file mode 100644
index 000000000..e69de29bb
diff --git a/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.AssemblyInfo.cs b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.AssemblyInfo.cs
new file mode 100644
index 000000000..a84dbdace
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.AssemblyInfo.cs
@@ -0,0 +1,23 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+using System;
+using System.Reflection;
+
+[assembly: System.Reflection.AssemblyCompanyAttribute("AppDiagnostic.Logging")]
+[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
+[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
+[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1c6f9af2bb6c7dd588e6f0dab0a2836e409c1f93")]
+[assembly: System.Reflection.AssemblyProductAttribute("AppDiagnostic.Logging")]
+[assembly: System.Reflection.AssemblyTitleAttribute("AppDiagnostic.Logging")]
+[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
+
+// Generated by the MSBuild WriteCodeFragment class.
+
diff --git a/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.AssemblyInfoInputs.cache b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.AssemblyInfoInputs.cache
new file mode 100644
index 000000000..af44c1565
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.AssemblyInfoInputs.cache
@@ -0,0 +1 @@
+328a35425ad6c5261737a98c855c6d7f7d872650a791c2ad21ac4020a96108d0
diff --git a/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.GeneratedMSBuildEditorConfig.editorconfig b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.GeneratedMSBuildEditorConfig.editorconfig
new file mode 100644
index 000000000..9cac7ed56
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.GeneratedMSBuildEditorConfig.editorconfig
@@ -0,0 +1,8 @@
+is_global = true
+build_property.RootNamespace = AppDiagnostic.Logging
+build_property.ProjectDir = C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\
+build_property.EnableComHosting =
+build_property.EnableGeneratedComInterfaceComImportInterop =
+build_property.CsWinRTUseWindowsUIXamlProjections = false
+build_property.EffectiveAnalysisLevelStyle =
+build_property.EnableCodeStyleSeverity =
diff --git a/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.assets.cache b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.assets.cache
new file mode 100644
index 000000000..7bccc9da2
Binary files /dev/null and b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.assets.cache differ
diff --git a/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.csproj.AssemblyReference.cache b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.csproj.AssemblyReference.cache
new file mode 100644
index 000000000..6aed440a2
Binary files /dev/null and b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.csproj.AssemblyReference.cache differ
diff --git a/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.csproj.BuildWithSkipAnalyzers b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.csproj.BuildWithSkipAnalyzers
new file mode 100644
index 000000000..e69de29bb
diff --git a/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.csproj.CoreCompileInputs.cache b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.csproj.CoreCompileInputs.cache
new file mode 100644
index 000000000..eb05a3936
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.csproj.CoreCompileInputs.cache
@@ -0,0 +1 @@
+551ba94934db30a277a1a9cec2dacbe03f8393107e5d0b2436695f9bd62bd569
diff --git a/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.csproj.FileListAbsolute.txt b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.csproj.FileListAbsolute.txt
new file mode 100644
index 000000000..2d4eec639
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.csproj.FileListAbsolute.txt
@@ -0,0 +1,11 @@
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\bin\Debug\net472\AppDiagnostic.Logging.dll
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\bin\Debug\net472\AppDiagnostic.Logging.pdb
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\bin\Debug\net472\NLog.dll
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Debug\net472\AppDiagnostic.Logging.csproj.AssemblyReference.cache
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Debug\net472\AppDiagnostic.Logging.GeneratedMSBuildEditorConfig.editorconfig
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Debug\net472\AppDiagnostic.Logging.AssemblyInfoInputs.cache
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Debug\net472\AppDiagnostic.Logging.AssemblyInfo.cs
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Debug\net472\AppDiagnostic.Logging.csproj.CoreCompileInputs.cache
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Debug\net472\AppDiagn.CFEA7AC0.Up2Date
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Debug\net472\AppDiagnostic.Logging.dll
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Debug\net472\AppDiagnostic.Logging.pdb
diff --git a/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.dll b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.dll
new file mode 100644
index 000000000..fa3b3ef1c
Binary files /dev/null and b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.dll differ
diff --git a/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.pdb b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.pdb
new file mode 100644
index 000000000..c8da8e1d1
Binary files /dev/null and b/AppDiagnostic.Logging/obj/Debug/net472/AppDiagnostic.Logging.pdb differ
diff --git a/AppDiagnostic.Logging/obj/Release/net472/.NETFramework,Version=v4.7.2.AssemblyAttributes.cs b/AppDiagnostic.Logging/obj/Release/net472/.NETFramework,Version=v4.7.2.AssemblyAttributes.cs
new file mode 100644
index 000000000..3871b184d
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/Release/net472/.NETFramework,Version=v4.7.2.AssemblyAttributes.cs
@@ -0,0 +1,4 @@
+//
+using System;
+using System.Reflection;
+[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
diff --git a/AppDiagnostic.Logging/obj/Release/net472/AppDiagn.CFEA7AC0.Up2Date b/AppDiagnostic.Logging/obj/Release/net472/AppDiagn.CFEA7AC0.Up2Date
new file mode 100644
index 000000000..e69de29bb
diff --git a/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.AssemblyInfo.cs b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.AssemblyInfo.cs
new file mode 100644
index 000000000..a53fb10a6
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.AssemblyInfo.cs
@@ -0,0 +1,23 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+using System;
+using System.Reflection;
+
+[assembly: System.Reflection.AssemblyCompanyAttribute("AppDiagnostic.Logging")]
+[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
+[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
+[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+086bb55595ffea5be7adb163ca4c6489903ed67c")]
+[assembly: System.Reflection.AssemblyProductAttribute("AppDiagnostic.Logging")]
+[assembly: System.Reflection.AssemblyTitleAttribute("AppDiagnostic.Logging")]
+[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
+
+// Generated by the MSBuild WriteCodeFragment class.
+
diff --git a/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.AssemblyInfoInputs.cache b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.AssemblyInfoInputs.cache
new file mode 100644
index 000000000..baac57ee0
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.AssemblyInfoInputs.cache
@@ -0,0 +1 @@
+aad509aee7434678bd40a99ea120dafb7aa8b4f050f239aae2fbef41167a7201
diff --git a/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.GeneratedMSBuildEditorConfig.editorconfig b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.GeneratedMSBuildEditorConfig.editorconfig
new file mode 100644
index 000000000..9cac7ed56
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.GeneratedMSBuildEditorConfig.editorconfig
@@ -0,0 +1,8 @@
+is_global = true
+build_property.RootNamespace = AppDiagnostic.Logging
+build_property.ProjectDir = C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\
+build_property.EnableComHosting =
+build_property.EnableGeneratedComInterfaceComImportInterop =
+build_property.CsWinRTUseWindowsUIXamlProjections = false
+build_property.EffectiveAnalysisLevelStyle =
+build_property.EnableCodeStyleSeverity =
diff --git a/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.assets.cache b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.assets.cache
new file mode 100644
index 000000000..3e5407a6d
Binary files /dev/null and b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.assets.cache differ
diff --git a/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.csproj.AssemblyReference.cache b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.csproj.AssemblyReference.cache
new file mode 100644
index 000000000..883e6d9b0
Binary files /dev/null and b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.csproj.AssemblyReference.cache differ
diff --git a/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.csproj.BuildWithSkipAnalyzers b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.csproj.BuildWithSkipAnalyzers
new file mode 100644
index 000000000..e69de29bb
diff --git a/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.csproj.CoreCompileInputs.cache b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.csproj.CoreCompileInputs.cache
new file mode 100644
index 000000000..c31c2aa80
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.csproj.CoreCompileInputs.cache
@@ -0,0 +1 @@
+53dfa6175dfb762da95c667a45dfe918166697a8741b3f7c14601d1f62b1886d
diff --git a/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.csproj.FileListAbsolute.txt b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.csproj.FileListAbsolute.txt
new file mode 100644
index 000000000..82c760b66
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.csproj.FileListAbsolute.txt
@@ -0,0 +1,11 @@
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\bin\Release\net472\AppDiagnostic.Logging.dll
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\bin\Release\net472\AppDiagnostic.Logging.pdb
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\bin\Release\net472\NLog.dll
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Release\net472\AppDiagnostic.Logging.csproj.AssemblyReference.cache
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Release\net472\AppDiagnostic.Logging.GeneratedMSBuildEditorConfig.editorconfig
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Release\net472\AppDiagnostic.Logging.AssemblyInfoInputs.cache
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Release\net472\AppDiagnostic.Logging.AssemblyInfo.cs
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Release\net472\AppDiagnostic.Logging.csproj.CoreCompileInputs.cache
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Release\net472\AppDiagn.CFEA7AC0.Up2Date
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Release\net472\AppDiagnostic.Logging.dll
+C:\Sensus_projects\New\LocalBranch_start_at_6.3.2026\tbf-local\tbf\AppDiagnostic.Logging\obj\Release\net472\AppDiagnostic.Logging.pdb
diff --git a/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.dll b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.dll
new file mode 100644
index 000000000..92ba99691
Binary files /dev/null and b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.dll differ
diff --git a/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.pdb b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.pdb
new file mode 100644
index 000000000..20f7774dc
Binary files /dev/null and b/AppDiagnostic.Logging/obj/Release/net472/AppDiagnostic.Logging.pdb differ
diff --git a/AppDiagnostic.Logging/obj/project.assets.json b/AppDiagnostic.Logging/obj/project.assets.json
new file mode 100644
index 000000000..820b925a7
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/project.assets.json
@@ -0,0 +1,119 @@
+{
+ "version": 4,
+ "targets": {
+ "net472": {
+ "NLog/5.2.2": {
+ "type": "package",
+ "frameworkAssemblies": [
+ "System",
+ "System.Configuration",
+ "System.Core",
+ "System.IO.Compression",
+ "System.Xml"
+ ],
+ "compile": {
+ "lib/net46/NLog.dll": {
+ "related": ".xml"
+ }
+ },
+ "runtime": {
+ "lib/net46/NLog.dll": {
+ "related": ".xml"
+ }
+ }
+ }
+ }
+ },
+ "libraries": {
+ "NLog/5.2.2": {
+ "sha512": "r6Q9740g29gTwmTWlsgdIFm0mhNsfNZmbvWKX/Fxmi8X89ZrpUowHM2T2X1lP7RVpND+ef+XnfKL5g6Q1iNGXA==",
+ "type": "package",
+ "path": "nlog/5.2.2",
+ "files": [
+ ".nupkg.metadata",
+ ".signature.p7s",
+ "N.png",
+ "lib/net35/NLog.dll",
+ "lib/net35/NLog.xml",
+ "lib/net45/NLog.dll",
+ "lib/net45/NLog.xml",
+ "lib/net46/NLog.dll",
+ "lib/net46/NLog.xml",
+ "lib/netstandard1.3/NLog.dll",
+ "lib/netstandard1.3/NLog.xml",
+ "lib/netstandard1.5/NLog.dll",
+ "lib/netstandard1.5/NLog.xml",
+ "lib/netstandard2.0/NLog.dll",
+ "lib/netstandard2.0/NLog.xml",
+ "nlog.5.2.2.nupkg.sha512",
+ "nlog.nuspec"
+ ]
+ }
+ },
+ "projectFileDependencyGroups": {
+ "net472": [
+ "NLog >= 5.2.2"
+ ]
+ },
+ "packageFolders": {
+ "C:\\Users\\MFRNIAK\\.nuget\\packages\\": {},
+ "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {}
+ },
+ "project": {
+ "version": "1.0.0",
+ "restore": {
+ "projectUniqueName": "C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\AppDiagnostic.Logging.csproj",
+ "projectName": "AppDiagnostic.Logging",
+ "projectPath": "C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\AppDiagnostic.Logging.csproj",
+ "packagesPath": "C:\\Users\\MFRNIAK\\.nuget\\packages\\",
+ "outputPath": "C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\obj\\",
+ "projectStyle": "PackageReference",
+ "fallbackFolders": [
+ "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
+ ],
+ "configFilePaths": [
+ "C:\\Users\\MFRNIAK\\AppData\\Roaming\\NuGet\\NuGet.Config",
+ "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
+ "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
+ ],
+ "originalTargetFrameworks": [
+ "net472"
+ ],
+ "sources": {
+ "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
+ "https://api.nuget.org/v3/index.json": {}
+ },
+ "frameworks": {
+ "net472": {
+ "framework": "net472",
+ "targetAlias": "net472",
+ "projectReferences": {}
+ }
+ },
+ "warningProperties": {
+ "warnAsError": [
+ "NU1605"
+ ]
+ },
+ "restoreAuditProperties": {
+ "enableAudit": "true",
+ "auditLevel": "low",
+ "auditMode": "direct"
+ },
+ "SdkAnalysisLevel": "10.0.300"
+ },
+ "frameworks": {
+ "net472": {
+ "framework": "net472",
+ "targetAlias": "net472",
+ "dependencies": {
+ "NLog": {
+ "target": "Package",
+ "version": "[5.2.2, )"
+ }
+ },
+ "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.303\\RuntimeIdentifierGraph.json"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/AppDiagnostic.Logging/obj/project.nuget.cache b/AppDiagnostic.Logging/obj/project.nuget.cache
new file mode 100644
index 000000000..ef0bb2527
--- /dev/null
+++ b/AppDiagnostic.Logging/obj/project.nuget.cache
@@ -0,0 +1,10 @@
+{
+ "version": 2,
+ "dgSpecHash": "ezwKOPZAAXU=",
+ "success": true,
+ "projectFilePath": "C:\\Sensus_projects\\New\\LocalBranch_start_at_6.3.2026\\tbf-local\\tbf\\AppDiagnostic.Logging\\AppDiagnostic.Logging.csproj",
+ "expectedPackageFiles": [
+ "C:\\Users\\MFRNIAK\\.nuget\\packages\\nlog\\5.2.2\\nlog.5.2.2.nupkg.sha512"
+ ],
+ "logs": []
+}
\ No newline at end of file
diff --git a/AppDiagnostic/App.config b/AppDiagnostic/App.config
index 7f5eaea7b..aad6a5e83 100644
--- a/AppDiagnostic/App.config
+++ b/AppDiagnostic/App.config
@@ -3,17 +3,4 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
diff --git a/AppDiagnostic/AppDiagnostic.csproj b/AppDiagnostic/AppDiagnostic.csproj
index cef866153..d2ea61617 100644
--- a/AppDiagnostic/AppDiagnostic.csproj
+++ b/AppDiagnostic/AppDiagnostic.csproj
@@ -33,9 +33,6 @@
4
-
- ..\packages\log4net.3.0.3\lib\net462\log4net.dll
-
@@ -51,12 +48,15 @@
-
- Component
+
+ Form
-
-
-
+
+
+
+
+
+
Form
@@ -78,7 +78,6 @@
Resources.resx
True
-
SettingsSingleFileGenerator
Settings.Designer.cs
@@ -93,9 +92,9 @@
-
- {8f942729-f454-4c99-ba6c-746962065ae3}
- SharedComponents
+
+ {A7AF8ED2-362E-4DBB-B607-EBA2820719EC}
+ AppDiagnostic.Logging
diff --git a/AppDiagnostic/ColorRuleEditorForm.cs b/AppDiagnostic/ColorRuleEditorForm.cs
new file mode 100644
index 000000000..a944d7fef
--- /dev/null
+++ b/AppDiagnostic/ColorRuleEditorForm.cs
@@ -0,0 +1,319 @@
+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 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 sourceRules)
+ {
+ rules = sourceRules.Select(rule => rule.Clone()).ToList();
+ InitializeWindow();
+ previewBoldFont = new Font(previewBox.Font, FontStyle.Bold);
+ RefreshRuleList(0);
+ RefreshPreview();
+ }
+
+ public List 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)));
+ }
+ }
+}
diff --git a/AppDiagnostic/DiagnosticLogFormatter.cs b/AppDiagnostic/DiagnosticLogFormatter.cs
new file mode 100644
index 000000000..c8d598cce
--- /dev/null
+++ b/AppDiagnostic/DiagnosticLogFormatter.cs
@@ -0,0 +1,24 @@
+using AppDiagnostic.Logging;
+using System;
+
+namespace AppDiagnostic
+{
+ public static class DiagnosticLogFormatter
+ {
+ public static string Format(DiagnosticLogEntry entry)
+ {
+ if (entry == null)
+ {
+ return string.Empty;
+ }
+
+ string level = (entry.Level ?? string.Empty).ToUpperInvariant().PadRight(5);
+ string application = string.IsNullOrWhiteSpace(entry.ApplicationName) ? "Unknown" : entry.ApplicationName;
+ string logger = entry.LoggerName ?? string.Empty;
+ string message = entry.Message ?? string.Empty;
+ string exception = string.IsNullOrWhiteSpace(entry.Exception) ? string.Empty : Environment.NewLine + entry.Exception;
+
+ return $"{entry.TimestampUtc.ToLocalTime():yyyy-MM-dd HH:mm:ss.fff}|{level}|{application}[{entry.ProcessId}]|{logger}|{message}{exception}";
+ }
+ }
+}
diff --git a/AppDiagnostic/DiagnosticLogStore.cs b/AppDiagnostic/DiagnosticLogStore.cs
new file mode 100644
index 000000000..67ddb3437
--- /dev/null
+++ b/AppDiagnostic/DiagnosticLogStore.cs
@@ -0,0 +1,82 @@
+using AppDiagnostic.Logging;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace AppDiagnostic
+{
+ public sealed class DiagnosticLogStore
+ {
+ private const int MaximumLogCount = 10000;
+ private static readonly DiagnosticLogStore instance = new DiagnosticLogStore();
+ private readonly object syncRoot = new object();
+ private readonly List entries = new List();
+ private long totalReceived;
+ private long version;
+
+ private DiagnosticLogStore()
+ {
+ }
+
+ public static DiagnosticLogStore Instance => instance;
+
+ public long TotalReceived
+ {
+ get
+ {
+ lock (syncRoot)
+ {
+ return totalReceived;
+ }
+ }
+ }
+
+ public long Version
+ {
+ get
+ {
+ lock (syncRoot)
+ {
+ return version;
+ }
+ }
+ }
+
+ public void Add(DiagnosticLogEntry entry)
+ {
+ if (entry == null)
+ {
+ return;
+ }
+
+ lock (syncRoot)
+ {
+ entries.Add(entry);
+ totalReceived++;
+ version++;
+
+ int excessCount = entries.Count - MaximumLogCount;
+ if (excessCount > 0)
+ {
+ entries.RemoveRange(0, excessCount);
+ }
+ }
+ }
+
+ public List GetSnapshot()
+ {
+ lock (syncRoot)
+ {
+ return entries.ToList();
+ }
+ }
+
+ public void Clear()
+ {
+ lock (syncRoot)
+ {
+ entries.Clear();
+ version++;
+ }
+ }
+ }
+}
diff --git a/AppDiagnostic/DiagnosticPipeServer.cs b/AppDiagnostic/DiagnosticPipeServer.cs
new file mode 100644
index 000000000..4a51550f4
--- /dev/null
+++ b/AppDiagnostic/DiagnosticPipeServer.cs
@@ -0,0 +1,164 @@
+using AppDiagnostic.Logging;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Pipes;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace AppDiagnostic
+{
+ public sealed class DiagnosticPipeServer : IDisposable
+ {
+ private readonly Action messageReceived;
+ private readonly CancellationTokenSource cancellation = new CancellationTokenSource();
+ private readonly object syncRoot = new object();
+ private readonly List connectedClients = new List();
+ private NamedPipeServerStream waitingServer;
+ private Task serverTask;
+
+ public DiagnosticPipeServer(Action messageReceived)
+ {
+ this.messageReceived = messageReceived ?? throw new ArgumentNullException(nameof(messageReceived));
+ }
+
+ public int ConnectedClientCount
+ {
+ get
+ {
+ lock (syncRoot)
+ {
+ return connectedClients.Count;
+ }
+ }
+ }
+
+ public void Start()
+ {
+ if (serverTask != null)
+ {
+ return;
+ }
+
+ serverTask = Task.Run((Action)AcceptConnections);
+ }
+
+ private void AcceptConnections()
+ {
+ while (!cancellation.IsCancellationRequested)
+ {
+ NamedPipeServerStream server = null;
+
+ try
+ {
+ server = new NamedPipeServerStream(DiagnosticProtocol.DefaultPipeName, PipeDirection.In, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
+
+ lock (syncRoot)
+ {
+ waitingServer = server;
+ }
+
+ server.WaitForConnection();
+
+ lock (syncRoot)
+ {
+ waitingServer = null;
+ connectedClients.Add(server);
+ }
+
+ NamedPipeServerStream connectedServer = server;
+ server = null;
+ Task.Run(() => ReadClient(connectedServer));
+ }
+ catch (ObjectDisposedException)
+ {
+ if (!cancellation.IsCancellationRequested)
+ {
+ throw;
+ }
+ }
+ catch (IOException)
+ {
+ if (!cancellation.IsCancellationRequested)
+ {
+ Thread.Sleep(250);
+ }
+ }
+ finally
+ {
+ lock (syncRoot)
+ {
+ if (ReferenceEquals(waitingServer, server))
+ {
+ waitingServer = null;
+ }
+ }
+
+ server?.Dispose();
+ }
+ }
+ }
+
+ private void ReadClient(NamedPipeServerStream client)
+ {
+ try
+ {
+ while (client.IsConnected && !cancellation.IsCancellationRequested)
+ {
+ DiagnosticLogEntry entry = DiagnosticMessageSerializer.ReadFramed(client);
+ messageReceived(entry);
+ }
+ }
+ catch (EndOfStreamException)
+ {
+ // The producer application was closed.
+ }
+ catch (IOException)
+ {
+ // The producer application disconnected.
+ }
+ catch (ObjectDisposedException)
+ {
+ // The diagnostic application is shutting down.
+ }
+ finally
+ {
+ lock (syncRoot)
+ {
+ connectedClients.Remove(client);
+ }
+
+ client.Dispose();
+ }
+ }
+
+ public void Dispose()
+ {
+ cancellation.Cancel();
+
+ lock (syncRoot)
+ {
+ waitingServer?.Dispose();
+ waitingServer = null;
+
+ foreach (NamedPipeServerStream client in connectedClients.ToArray())
+ {
+ client.Dispose();
+ }
+
+ connectedClients.Clear();
+ }
+
+ try
+ {
+ serverTask?.Wait(2000);
+ }
+ catch (AggregateException)
+ {
+ // Cancellation during shutdown is expected.
+ }
+
+ cancellation.Dispose();
+ }
+ }
+}
diff --git a/AppDiagnostic/LogColorRule.cs b/AppDiagnostic/LogColorRule.cs
new file mode 100644
index 000000000..e801bc4c9
--- /dev/null
+++ b/AppDiagnostic/LogColorRule.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+
+namespace AppDiagnostic
+{
+ [Serializable]
+ public sealed class LogColorRule
+ {
+ public bool Enabled { get; set; } = true;
+ public string Pattern { get; set; } = string.Empty;
+ public int ForeColorArgb { get; set; } = Color.Black.ToArgb();
+ public int BackColorArgb { get; set; }
+ public bool UseBackColor { get; set; }
+ public bool Bold { get; set; }
+
+ public Color ForeColor => Color.FromArgb(ForeColorArgb);
+ public Color BackColor => Color.FromArgb(BackColorArgb);
+
+ public LogColorRule Clone()
+ {
+ return new LogColorRule
+ {
+ Enabled = Enabled,
+ Pattern = Pattern,
+ ForeColorArgb = ForeColorArgb,
+ BackColorArgb = BackColorArgb,
+ UseBackColor = UseBackColor,
+ Bold = Bold
+ };
+ }
+
+ public override string ToString()
+ {
+ string state = Enabled ? string.Empty : "[disabled] ";
+ return state + Pattern;
+ }
+
+ public static List CreateDefaults()
+ {
+ return new List
+ {
+ Create("|TRACE|", Color.DimGray, false),
+ Create("|DEBUG|", Color.RoyalBlue, false),
+ Create("|INFO |", Color.ForestGreen, false),
+ Create("|WARN |", Color.DarkOrange, true),
+ Create("|ERROR|", Color.Crimson, true),
+ Create("|FATAL|", Color.White, true, Color.DarkRed)
+ };
+ }
+
+ private static LogColorRule Create(string pattern, Color foreground, bool bold, Color? background = null)
+ {
+ return new LogColorRule
+ {
+ Enabled = true,
+ Pattern = pattern,
+ ForeColorArgb = foreground.ToArgb(),
+ BackColorArgb = background?.ToArgb() ?? Color.Empty.ToArgb(),
+ UseBackColor = background.HasValue,
+ Bold = bold
+ };
+ }
+ }
+}
diff --git a/AppDiagnostic/LogColorRuleRepository.cs b/AppDiagnostic/LogColorRuleRepository.cs
new file mode 100644
index 000000000..7cd86d4f9
--- /dev/null
+++ b/AppDiagnostic/LogColorRuleRepository.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml.Serialization;
+
+namespace AppDiagnostic
+{
+ public static class LogColorRuleRepository
+ {
+ private static readonly string settingsFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Sensus", "AppDiagnostic");
+ private static readonly string settingsPath = Path.Combine(settingsFolder, "color-rules.xml");
+
+ public static List Load()
+ {
+ try
+ {
+ if (!File.Exists(settingsPath))
+ {
+ return LogColorRule.CreateDefaults();
+ }
+
+ var serializer = new XmlSerializer(typeof(List));
+ using (FileStream stream = File.OpenRead(settingsPath))
+ {
+ var rules = serializer.Deserialize(stream) as List;
+ return rules == null || rules.Count == 0 ? LogColorRule.CreateDefaults() : rules;
+ }
+ }
+ catch
+ {
+ return LogColorRule.CreateDefaults();
+ }
+ }
+
+ public static void Save(IEnumerable rules)
+ {
+ Directory.CreateDirectory(settingsFolder);
+
+ var serializer = new XmlSerializer(typeof(List));
+ using (FileStream stream = File.Create(settingsPath))
+ {
+ serializer.Serialize(stream, new List(rules));
+ }
+ }
+ }
+}
diff --git a/AppDiagnostic/LogColorizer.cs b/AppDiagnostic/LogColorizer.cs
new file mode 100644
index 000000000..35891ea5f
--- /dev/null
+++ b/AppDiagnostic/LogColorizer.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace AppDiagnostic
+{
+ public static class LogColorizer
+ {
+ public static void Apply(RichTextBox target, int startIndex, int length, string text, IEnumerable rules, Font regularFont, Font boldFont)
+ {
+ if (target == null || length <= 0)
+ {
+ return;
+ }
+
+ target.Select(startIndex, length);
+ target.SelectionColor = target.ForeColor;
+ target.SelectionBackColor = target.BackColor;
+ target.SelectionFont = regularFont;
+
+ foreach (LogColorRule rule in rules)
+ {
+ if (rule == null || !rule.Enabled || string.IsNullOrEmpty(rule.Pattern))
+ {
+ continue;
+ }
+
+ int searchIndex = 0;
+ while (searchIndex < text.Length)
+ {
+ int matchIndex = text.IndexOf(rule.Pattern, searchIndex, StringComparison.OrdinalIgnoreCase);
+ if (matchIndex < 0)
+ {
+ break;
+ }
+
+ target.Select(startIndex + matchIndex, rule.Pattern.Length);
+ target.SelectionColor = rule.ForeColor;
+ target.SelectionBackColor = rule.UseBackColor ? rule.BackColor : target.BackColor;
+ target.SelectionFont = rule.Bold ? boldFont : regularFont;
+ searchIndex = matchIndex + rule.Pattern.Length;
+ }
+ }
+ }
+ }
+}
diff --git a/AppDiagnostic/MainForm.Designer.cs b/AppDiagnostic/MainForm.Designer.cs
index 1eef3958d..7ea3f99c9 100644
--- a/AppDiagnostic/MainForm.Designer.cs
+++ b/AppDiagnostic/MainForm.Designer.cs
@@ -1,278 +1,309 @@
-using System.Windows.Forms;
-
namespace AppDiagnostic
{
partial class MainForm
{
- ///
- /// Required designer variable.
- ///
private System.ComponentModel.IContainer components = null;
+ private System.Windows.Forms.ToolStrip mainToolStrip;
+ private System.Windows.Forms.ToolStripButton pauseButton;
+ private System.Windows.Forms.ToolStripButton refreshButton;
+ private System.Windows.Forms.ToolStripButton clearButton;
+ private System.Windows.Forms.ToolStripButton saveButton;
+ private System.Windows.Forms.ToolStripButton copyButton;
+ private System.Windows.Forms.ToolStripButton copyAllButton;
+ private System.Windows.Forms.ToolStripButton wrapButton;
+ private System.Windows.Forms.ToolStripButton autoScrollButton;
+ private System.Windows.Forms.ToolStripButton alwaysOnTopButton;
+ private System.Windows.Forms.ToolStripButton colorRulesButton;
+ private System.Windows.Forms.ToolStripButton closeButton;
+ private System.Windows.Forms.TableLayoutPanel filterPanel;
+ private System.Windows.Forms.Label filterLabel;
+ private System.Windows.Forms.TextBox filterTextBox;
+ private System.Windows.Forms.Label levelLabel;
+ private System.Windows.Forms.ComboBox levelComboBox;
+ private System.Windows.Forms.Label applicationLabel;
+ private System.Windows.Forms.ComboBox applicationComboBox;
+ private System.Windows.Forms.Button applyFilterButton;
+ private System.Windows.Forms.Button clearFilterButton;
+ private System.Windows.Forms.RichTextBox logView;
+ private System.Windows.Forms.StatusStrip statusStrip;
+ private System.Windows.Forms.ToolStripStatusLabel receiverStatusLabel;
+ private System.Windows.Forms.ToolStripStatusLabel statusSpacer;
+ private System.Windows.Forms.ToolStripStatusLabel countStatusLabel;
+ private System.Windows.Forms.ContextMenuStrip logContextMenu;
+ private System.Windows.Forms.ToolStripMenuItem copySelectionMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem copyLineMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem copyAllMenuItem;
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
protected override void Dispose(bool disposing)
{
- if (disposing && (components != null))
+ if (disposing && components != null)
{
components.Dispose();
}
+
base.Dispose(disposing);
}
- #region Windows Form Designer generated code
- //private BufferedListView memoListView;
- private System.Windows.Forms.ColumnHeader columnHeaderTime;
- private System.Windows.Forms.ColumnHeader columnHeaderMessage;
-
- ///
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- ///
private void InitializeComponent()
{
- this.columnHeaderTime = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
- this.columnHeaderMessage = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
- this.panel1 = new System.Windows.Forms.Panel();
- this.refreshMemoButton = new System.Windows.Forms.Button();
- this.button6 = new System.Windows.Forms.Button();
- this.button7 = new System.Windows.Forms.Button();
- this.filterManagementPanel = new System.Windows.Forms.Panel();
- this.button5 = new System.Windows.Forms.Button();
- this.filterButton = new System.Windows.Forms.Button();
+ this.components = new System.ComponentModel.Container();
+ this.mainToolStrip = new System.Windows.Forms.ToolStrip();
+ this.pauseButton = new System.Windows.Forms.ToolStripButton();
+ this.refreshButton = new System.Windows.Forms.ToolStripButton();
+ this.clearButton = new System.Windows.Forms.ToolStripButton();
+ this.saveButton = new System.Windows.Forms.ToolStripButton();
+ this.copyButton = new System.Windows.Forms.ToolStripButton();
+ this.copyAllButton = new System.Windows.Forms.ToolStripButton();
+ this.wrapButton = new System.Windows.Forms.ToolStripButton();
+ this.autoScrollButton = new System.Windows.Forms.ToolStripButton();
+ this.alwaysOnTopButton = new System.Windows.Forms.ToolStripButton();
+ this.colorRulesButton = new System.Windows.Forms.ToolStripButton();
+ this.closeButton = new System.Windows.Forms.ToolStripButton();
+ this.filterPanel = new System.Windows.Forms.TableLayoutPanel();
+ this.filterLabel = new System.Windows.Forms.Label();
this.filterTextBox = new System.Windows.Forms.TextBox();
- this.label2 = new System.Windows.Forms.Label();
- this.wndControlPanel = new System.Windows.Forms.Panel();
- this.button2 = new System.Windows.Forms.Button();
- this.flowControlsPanel = new System.Windows.Forms.Panel();
- this.runButton = new System.Windows.Forms.Button();
- this.stopButton = new System.Windows.Forms.Button();
- this.label1 = new System.Windows.Forms.Label();
- this.memoListView = new BufferedListView();
- this.panel1.SuspendLayout();
- this.filterManagementPanel.SuspendLayout();
- this.wndControlPanel.SuspendLayout();
- this.flowControlsPanel.SuspendLayout();
+ this.levelLabel = new System.Windows.Forms.Label();
+ this.levelComboBox = new System.Windows.Forms.ComboBox();
+ this.applicationLabel = new System.Windows.Forms.Label();
+ this.applicationComboBox = new System.Windows.Forms.ComboBox();
+ this.applyFilterButton = new System.Windows.Forms.Button();
+ this.clearFilterButton = new System.Windows.Forms.Button();
+ this.logContextMenu = new System.Windows.Forms.ContextMenuStrip(this.components);
+ this.copySelectionMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.copyLineMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.copyAllMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.logView = new System.Windows.Forms.RichTextBox();
+ this.statusStrip = new System.Windows.Forms.StatusStrip();
+ this.receiverStatusLabel = new System.Windows.Forms.ToolStripStatusLabel();
+ this.statusSpacer = new System.Windows.Forms.ToolStripStatusLabel();
+ this.countStatusLabel = new System.Windows.Forms.ToolStripStatusLabel();
+ this.mainToolStrip.SuspendLayout();
+ this.filterPanel.SuspendLayout();
+ this.logContextMenu.SuspendLayout();
+ this.statusStrip.SuspendLayout();
this.SuspendLayout();
- //
- // columnHeaderTime
- //
- this.columnHeaderTime.Text = "Time";
- this.columnHeaderTime.Width = 150;
- //
- // columnHeaderMessage
- //
- this.columnHeaderMessage.Text = "Message";
- this.columnHeaderMessage.Width = 350;
- //
- // panel1
- //
- this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
- this.panel1.Controls.Add(this.refreshMemoButton);
- this.panel1.Controls.Add(this.button6);
- this.panel1.Controls.Add(this.button7);
- this.panel1.Location = new System.Drawing.Point(189, 356);
- this.panel1.Name = "panel1";
- this.panel1.Size = new System.Drawing.Size(283, 26);
- this.panel1.TabIndex = 16;
- //
- // refreshMemoButton
- //
- this.refreshMemoButton.Location = new System.Drawing.Point(108, 0);
- this.refreshMemoButton.Name = "refreshMemoButton";
- this.refreshMemoButton.Size = new System.Drawing.Size(84, 26);
- this.refreshMemoButton.TabIndex = 17;
- this.refreshMemoButton.Text = "Refresh memo";
- this.refreshMemoButton.UseVisualStyleBackColor = true;
- this.refreshMemoButton.Click += new System.EventHandler(this.refreshMemoButton_Click);
- //
- // button6
- //
- this.button6.Location = new System.Drawing.Point(0, 0);
- this.button6.Name = "button6";
- this.button6.Size = new System.Drawing.Size(102, 26);
- this.button6.TabIndex = 4;
- this.button6.Text = "Clean diag cache";
- this.button6.UseVisualStyleBackColor = true;
- this.button6.Click += new System.EventHandler(this.button6_Click);
- //
- // button7
- //
- this.button7.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
- this.button7.Location = new System.Drawing.Point(198, 0);
- this.button7.Name = "button7";
- this.button7.Size = new System.Drawing.Size(72, 26);
- this.button7.TabIndex = 5;
- this.button7.Text = "Save logs";
- this.button7.UseVisualStyleBackColor = true;
- this.button7.Click += new System.EventHandler(this.button7_Click);
- //
- // filterManagementPanel
- //
- this.filterManagementPanel.Controls.Add(this.button5);
- this.filterManagementPanel.Controls.Add(this.filterButton);
- this.filterManagementPanel.Controls.Add(this.filterTextBox);
- this.filterManagementPanel.Controls.Add(this.label2);
- this.filterManagementPanel.Location = new System.Drawing.Point(127, 6);
- this.filterManagementPanel.Name = "filterManagementPanel";
- this.filterManagementPanel.Size = new System.Drawing.Size(393, 26);
- this.filterManagementPanel.TabIndex = 15;
- //
- // button5
- //
- this.button5.Enabled = false;
- this.button5.Location = new System.Drawing.Point(294, 4);
- this.button5.Name = "button5";
- this.button5.Size = new System.Drawing.Size(81, 20);
- this.button5.TabIndex = 11;
- this.button5.Text = "Add new filter";
- this.button5.UseVisualStyleBackColor = true;
- //
- // filterButton
- //
- this.filterButton.Location = new System.Drawing.Point(225, 4);
- this.filterButton.Name = "filterButton";
- this.filterButton.Size = new System.Drawing.Size(64, 20);
- this.filterButton.TabIndex = 10;
- this.filterButton.Text = "Set filter";
- this.filterButton.UseVisualStyleBackColor = true;
- this.filterButton.Click += new System.EventHandler(this.filterButton_Click_1);
- //
- // filterTextBox
- //
- this.filterTextBox.Location = new System.Drawing.Point(33, 4);
- this.filterTextBox.Name = "filterTextBox";
- this.filterTextBox.Size = new System.Drawing.Size(187, 20);
- this.filterTextBox.TabIndex = 7;
- //
- // label2
- //
- this.label2.AutoSize = true;
- this.label2.Location = new System.Drawing.Point(3, 8);
- this.label2.Name = "label2";
- this.label2.Size = new System.Drawing.Size(29, 13);
- this.label2.TabIndex = 6;
- this.label2.Text = "Filter";
- //
- // wndControlPanel
- //
- this.wndControlPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
- this.wndControlPanel.Controls.Add(this.button2);
- this.wndControlPanel.Location = new System.Drawing.Point(831, 356);
- this.wndControlPanel.Name = "wndControlPanel";
- this.wndControlPanel.Size = new System.Drawing.Size(70, 26);
- this.wndControlPanel.TabIndex = 14;
- //
- // button2
- //
- this.button2.Location = new System.Drawing.Point(3, 0);
- this.button2.Name = "button2";
- this.button2.Size = new System.Drawing.Size(64, 26);
- this.button2.TabIndex = 2;
- this.button2.Text = "Close";
- this.button2.UseVisualStyleBackColor = true;
- this.button2.Click += new System.EventHandler(this.button2_Click);
- //
- // flowControlsPanel
- //
- this.flowControlsPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
- this.flowControlsPanel.Controls.Add(this.runButton);
- this.flowControlsPanel.Controls.Add(this.stopButton);
- this.flowControlsPanel.Location = new System.Drawing.Point(12, 356);
- this.flowControlsPanel.Name = "flowControlsPanel";
- this.flowControlsPanel.Size = new System.Drawing.Size(133, 26);
- this.flowControlsPanel.TabIndex = 13;
- //
- // runButton
- //
- this.runButton.Enabled = false;
- this.runButton.Location = new System.Drawing.Point(0, 0);
- this.runButton.Name = "runButton";
- this.runButton.Size = new System.Drawing.Size(64, 26);
- this.runButton.TabIndex = 4;
- this.runButton.Text = "Run";
- this.runButton.UseVisualStyleBackColor = true;
- this.runButton.Click += new System.EventHandler(this.runButton_Click);
- //
- // stopButton
- //
- this.stopButton.Location = new System.Drawing.Point(69, 0);
- this.stopButton.Name = "stopButton";
- this.stopButton.Size = new System.Drawing.Size(64, 26);
- this.stopButton.TabIndex = 5;
- this.stopButton.Text = "Stop";
- this.stopButton.UseVisualStyleBackColor = true;
- this.stopButton.Click += new System.EventHandler(this.stopButton_Click);
- //
- // label1
- //
- this.label1.AutoSize = true;
- this.label1.Location = new System.Drawing.Point(12, 19);
- this.label1.Name = "label1";
- this.label1.Size = new System.Drawing.Size(76, 13);
- this.label1.TabIndex = 12;
- this.label1.Text = "Flow of events";
- //
- // memoListView
- //
- this.memoListView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
- | System.Windows.Forms.AnchorStyles.Left)
- | System.Windows.Forms.AnchorStyles.Right)));
- this.memoListView.FullRowSelect = true;
- this.memoListView.GridLines = true;
- this.memoListView.HideSelection = false;
- this.memoListView.Location = new System.Drawing.Point(12, 38);
- this.memoListView.Name = "memoListView";
- this.memoListView.Size = new System.Drawing.Size(889, 312);
- this.memoListView.TabIndex = 0;
- this.memoListView.UseCompatibleStateImageBehavior = false;
- this.memoListView.View = System.Windows.Forms.View.Details;
-
- this.memoListView.DoubleClick += new System.EventHandler(this.MemoListView_DoubleClick);
- //
+ //
+ // mainToolStrip
+ //
+ this.mainToolStrip.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden;
+ this.mainToolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.pauseButton,
+ this.refreshButton,
+ this.clearButton,
+ this.saveButton,
+ new System.Windows.Forms.ToolStripSeparator(),
+ this.copyButton,
+ this.copyAllButton,
+ new System.Windows.Forms.ToolStripSeparator(),
+ this.wrapButton,
+ this.autoScrollButton,
+ this.alwaysOnTopButton,
+ this.colorRulesButton,
+ this.closeButton});
+ this.mainToolStrip.Location = new System.Drawing.Point(0, 0);
+ this.mainToolStrip.Name = "mainToolStrip";
+ this.mainToolStrip.Padding = new System.Windows.Forms.Padding(6, 3, 6, 3);
+ this.mainToolStrip.Size = new System.Drawing.Size(1184, 31);
+ this.mainToolStrip.TabIndex = 0;
+ //
+ // pauseButton
+ //
+ this.pauseButton.CheckOnClick = true;
+ this.pauseButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+ this.pauseButton.Text = "Pause";
+ this.pauseButton.ToolTipText = "Pause screen updates while continuing to receive logs";
+ this.pauseButton.Click += new System.EventHandler(this.pauseButton_Click);
+ //
+ // refreshButton
+ //
+ this.refreshButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+ this.refreshButton.Text = "Refresh";
+ this.refreshButton.Click += new System.EventHandler(this.refreshButton_Click);
+ //
+ // clearButton
+ //
+ this.clearButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+ this.clearButton.Text = "Clear";
+ this.clearButton.Click += new System.EventHandler(this.clearButton_Click);
+ //
+ // saveButton
+ //
+ this.saveButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+ this.saveButton.Text = "Save...";
+ this.saveButton.Click += new System.EventHandler(this.saveButton_Click);
+ //
+ // copyButton
+ //
+ this.copyButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+ this.copyButton.Text = "Copy";
+ this.copyButton.ToolTipText = "Copy selected text or the current log line";
+ this.copyButton.Click += new System.EventHandler(this.copyButton_Click);
+ //
+ // copyAllButton
+ //
+ this.copyAllButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+ this.copyAllButton.Text = "Copy all";
+ this.copyAllButton.Click += new System.EventHandler(this.copyAllButton_Click);
+ //
+ // wrapButton
+ //
+ this.wrapButton.Checked = true;
+ this.wrapButton.CheckOnClick = true;
+ this.wrapButton.CheckState = System.Windows.Forms.CheckState.Checked;
+ this.wrapButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+ this.wrapButton.Text = "Wrap lines";
+ this.wrapButton.Click += new System.EventHandler(this.wrapButton_Click);
+ //
+ // autoScrollButton
+ //
+ this.autoScrollButton.Checked = true;
+ this.autoScrollButton.CheckOnClick = true;
+ this.autoScrollButton.CheckState = System.Windows.Forms.CheckState.Checked;
+ this.autoScrollButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+ this.autoScrollButton.Text = "Auto-scroll";
+ //
+ // alwaysOnTopButton
+ //
+ this.alwaysOnTopButton.CheckOnClick = true;
+ this.alwaysOnTopButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+ this.alwaysOnTopButton.Text = "Always on top";
+ this.alwaysOnTopButton.Click += new System.EventHandler(this.alwaysOnTopButton_Click);
+ //
+ // colorRulesButton
+ //
+ this.colorRulesButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+ this.colorRulesButton.Text = "Color rules...";
+ this.colorRulesButton.Click += new System.EventHandler(this.colorRulesButton_Click);
+ //
+ // closeButton
+ //
+ this.closeButton.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right;
+ this.closeButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+ this.closeButton.Text = "Close";
+ this.closeButton.Click += new System.EventHandler(this.closeButton_Click);
+ //
+ // filterPanel
+ //
+ this.filterPanel.ColumnCount = 8;
+ this.filterPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 42F));
+ this.filterPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
+ this.filterPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 42F));
+ this.filterPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 105F));
+ this.filterPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 72F));
+ this.filterPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 180F));
+ this.filterPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 66F));
+ this.filterPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 66F));
+ this.filterPanel.Controls.Add(this.filterLabel, 0, 0);
+ this.filterPanel.Controls.Add(this.filterTextBox, 1, 0);
+ this.filterPanel.Controls.Add(this.levelLabel, 2, 0);
+ this.filterPanel.Controls.Add(this.levelComboBox, 3, 0);
+ this.filterPanel.Controls.Add(this.applicationLabel, 4, 0);
+ this.filterPanel.Controls.Add(this.applicationComboBox, 5, 0);
+ this.filterPanel.Controls.Add(this.applyFilterButton, 6, 0);
+ this.filterPanel.Controls.Add(this.clearFilterButton, 7, 0);
+ this.filterPanel.Dock = System.Windows.Forms.DockStyle.Top;
+ this.filterPanel.Location = new System.Drawing.Point(0, 31);
+ this.filterPanel.Name = "filterPanel";
+ this.filterPanel.Padding = new System.Windows.Forms.Padding(6, 5, 6, 5);
+ this.filterPanel.RowCount = 1;
+ this.filterPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
+ this.filterPanel.Size = new System.Drawing.Size(1184, 38);
+ this.filterPanel.TabIndex = 1;
+ //
+ // filter controls
+ //
+ this.filterLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
+ this.filterLabel.AutoSize = true;
+ this.filterLabel.Text = "Filter";
+ this.filterTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.filterTextBox.KeyDown += new System.Windows.Forms.KeyEventHandler(this.filterTextBox_KeyDown);
+ this.levelLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
+ this.levelLabel.AutoSize = true;
+ this.levelLabel.Text = "Level";
+ this.levelComboBox.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.levelComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.levelComboBox.SelectedIndexChanged += new System.EventHandler(this.filterValueChanged);
+ this.applicationLabel.Anchor = System.Windows.Forms.AnchorStyles.Left;
+ this.applicationLabel.AutoSize = true;
+ this.applicationLabel.Text = "Application";
+ this.applicationComboBox.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.applicationComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.applicationComboBox.SelectedIndexChanged += new System.EventHandler(this.filterValueChanged);
+ this.applyFilterButton.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.applyFilterButton.Text = "Apply";
+ this.applyFilterButton.Click += new System.EventHandler(this.applyFilterButton_Click);
+ this.clearFilterButton.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.clearFilterButton.Text = "Reset";
+ this.clearFilterButton.Click += new System.EventHandler(this.clearFilterButton_Click);
+ //
+ // logContextMenu
+ //
+ this.logContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.copySelectionMenuItem,
+ this.copyLineMenuItem,
+ this.copyAllMenuItem});
+ this.copySelectionMenuItem.Text = "Copy selection";
+ this.copySelectionMenuItem.Click += new System.EventHandler(this.copySelectionMenuItem_Click);
+ this.copyLineMenuItem.Text = "Copy current line";
+ this.copyLineMenuItem.Click += new System.EventHandler(this.copyLineMenuItem_Click);
+ this.copyAllMenuItem.Text = "Copy all visible logs";
+ this.copyAllMenuItem.Click += new System.EventHandler(this.copyAllButton_Click);
+ //
+ // logView
+ //
+ this.logView.BackColor = System.Drawing.SystemColors.Window;
+ this.logView.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+ this.logView.ContextMenuStrip = this.logContextMenu;
+ this.logView.DetectUrls = false;
+ this.logView.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.logView.Font = new System.Drawing.Font("Consolas", 9F);
+ this.logView.HideSelection = false;
+ this.logView.Location = new System.Drawing.Point(0, 69);
+ this.logView.Name = "logView";
+ this.logView.ReadOnly = true;
+ this.logView.Size = new System.Drawing.Size(1184, 554);
+ this.logView.TabIndex = 2;
+ this.logView.Text = "";
+ this.logView.WordWrap = true;
+ //
+ // statusStrip
+ //
+ this.statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.receiverStatusLabel,
+ this.statusSpacer,
+ this.countStatusLabel});
+ this.statusStrip.Location = new System.Drawing.Point(0, 623);
+ this.statusStrip.Name = "statusStrip";
+ this.statusStrip.Size = new System.Drawing.Size(1184, 22);
+ this.statusStrip.TabIndex = 3;
+ this.receiverStatusLabel.Text = "Listening";
+ this.statusSpacer.Spring = true;
+ this.countStatusLabel.Text = "Received: 0 | Visible: 0";
+ //
// MainForm
- //
+ //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(913, 388);
- this.Controls.Add(this.memoListView);
- this.Controls.Add(this.panel1);
- this.Controls.Add(this.filterManagementPanel);
- this.Controls.Add(this.wndControlPanel);
- this.Controls.Add(this.flowControlsPanel);
- this.Controls.Add(this.label1);
+ this.ClientSize = new System.Drawing.Size(1184, 645);
+ this.Controls.Add(this.logView);
+ this.Controls.Add(this.filterPanel);
+ this.Controls.Add(this.mainToolStrip);
+ this.Controls.Add(this.statusStrip);
+ this.KeyPreview = true;
+ this.MinimumSize = new System.Drawing.Size(860, 480);
this.Name = "MainForm";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Application live diagnostic";
- this.panel1.ResumeLayout(false);
- this.filterManagementPanel.ResumeLayout(false);
- this.filterManagementPanel.PerformLayout();
- this.wndControlPanel.ResumeLayout(false);
- this.flowControlsPanel.ResumeLayout(false);
+ this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.MainForm_KeyDown);
+ this.mainToolStrip.ResumeLayout(false);
+ this.mainToolStrip.PerformLayout();
+ this.filterPanel.ResumeLayout(false);
+ this.filterPanel.PerformLayout();
+ this.logContextMenu.ResumeLayout(false);
+ this.statusStrip.ResumeLayout(false);
+ this.statusStrip.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
-
}
-
- #endregion
-
- private System.Windows.Forms.Panel panel1;
- private System.Windows.Forms.Button button6;
- private System.Windows.Forms.Button button7;
- private System.Windows.Forms.Panel filterManagementPanel;
- private System.Windows.Forms.Button button5;
- private System.Windows.Forms.Button filterButton;
- private System.Windows.Forms.TextBox filterTextBox;
- private System.Windows.Forms.Label label2;
- private System.Windows.Forms.Panel wndControlPanel;
- private System.Windows.Forms.Button button2;
- private System.Windows.Forms.Panel flowControlsPanel;
- private System.Windows.Forms.Button runButton;
- private System.Windows.Forms.Button stopButton;
- private System.Windows.Forms.Label label1;
- private System.Windows.Forms.Button refreshMemoButton;
- private BufferedListView memoListView;
}
}
-
diff --git a/AppDiagnostic/MainForm.cs b/AppDiagnostic/MainForm.cs
index f7c0d093d..15bdaff1e 100644
--- a/AppDiagnostic/MainForm.cs
+++ b/AppDiagnostic/MainForm.cs
@@ -1,267 +1,445 @@
-using log4net.Config;
-using log4net;
+using AppDiagnostic.Logging;
using System;
using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data;
using System.Drawing;
+using System.IO;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using System.Windows.Forms;
-using log4net.Appender;
-using SharedComponents;
-using static System.Windows.Forms.VisualStyles.VisualStyleElement;
-using System.IO; // Pre File.WriteAllLines a prácu so súbormi
-using System.Linq;
namespace AppDiagnostic
{
public partial class MainForm : Form
{
- private bool stopUpdate = false; // Premenná na zastavenie aktualizácie
- private int lastDisplayedLogIndex = 0;
- private bool autoScrollEnabled = true; // Automatický posun, predvolene povolený
- private MainForm appDiagnosticForm;
- private Timer refreshTimer;
- private LogAdapter _logAdapter; // Pre LogAdapter
- private LogFilter _logFilter; // LogFilter objekt pre filtráciu
+ private readonly Func connectedClientCountProvider;
+ private readonly Func activationRequestProvider;
+ private readonly Timer refreshTimer;
+ private readonly Font regularLogFont;
+ private readonly Font boldLogFont;
+ private List colorRules;
+ private long lastRenderedVersion = -1;
+ private int observedEntryCount;
+ private int visibleEntryCount;
+ private bool loadingFilters;
- public MainForm()
+ public MainForm() : this(null, null)
{
- this.Icon = Properties.Resources.TBF_icon;
+ }
+ public MainForm(Func connectedClientCountProvider, Func activationRequestProvider)
+ {
+ this.connectedClientCountProvider = connectedClientCountProvider;
+ this.activationRequestProvider = activationRequestProvider;
InitializeComponent();
+ Icon = Properties.Resources.TBF_icon;
- // Inicializujeme LogAdapter a priradíme ho k memoListView
- _logAdapter = new LogAdapter(memoListView);
+ regularLogFont = new Font(logView.Font, FontStyle.Regular);
+ boldLogFont = new Font(logView.Font, FontStyle.Bold);
+ colorRules = LogColorRuleRepository.Load();
- RefreshLogView();
+ InitializeFilters();
- // Pripojenie na udalosť pri pridaní nového logu
- // nakoľko sa memo refreshuje cyklicky, tak refresh na udalosť nepotrebujem
- //LiveLogCache.Instance.LogAdded += OnLogAdded;
+ refreshTimer = new Timer { Interval = 250 };
+ refreshTimer.Tick += refreshTimer_Tick;
+ refreshTimer.Start();
- // Vytvoríme LogFilter objekt
- //_logFilter = new LogFilter(memoListView);
- }
-
- private void OnLogAdded(string log)
- {
- try
- {
- RefreshLogView();
- }
- catch (Exception ex)
- {
- MessageBox.Show($"Error in OnLogAdded: {ex.Message}");
- throw;
- }
- }
-
- private void RefreshLogView()
- {
- // Skontrolujte, či voláme z iného vlákna
- if (InvokeRequired)
- {
- Invoke((Action)RefreshLogView);
- return;
- }
-
- memoListView.BeginUpdate(); // Zabraňuje vizuálnym aktualizáciám počas vykresľovania
-
- try
- {
- // Skontrolujte, či je užívateľ na poslednom viditeľnom zázname
- if (memoListView.Items.Count > 0)
- {
- var lastVisibleIndex = memoListView.TopItem.Index + memoListView.ClientRectangle.Height / memoListView.Items[0].Bounds.Height;
- autoScrollEnabled = lastVisibleIndex >= memoListView.Items.Count - 1;
- }
-
- // Získajte nové logy od posledného indexu
- var logs = LiveLogCache.Instance.GetLogs(lastDisplayedLogIndex, LiveLogCache.Instance.Logs.Count - lastDisplayedLogIndex);
-
- // Pridajte len nové logy
- foreach (var log in logs)
- {
- memoListView.Items.Add(new ListViewItem(log));
- }
-
- // Aktualizujte posledný zobrazený index
- lastDisplayedLogIndex = LiveLogCache.Instance.Logs.Count;
-
- // Ak je autoScrollEnabled, posuňte sa na posledný záznam
- if (autoScrollEnabled && memoListView.Items.Count > 0)
- {
- memoListView.EnsureVisible(memoListView.Items.Count - 1);
- }
- }
- finally
- {
- memoListView.EndUpdate(); // Umožní vizuálne aktualizácie
- }
- }
-
- // Táto metóda sa spustí pri scrollovaní v memoListView (tzn. ak sa používateľ dostane na spodok listu)
- private void memoListView_Scroll(object sender, EventArgs e)
- {
- // Skontrolujte, či používateľ dosiahol spodok ListView
- if (memoListView.Items.Count > 0 &&
- memoListView.Items[memoListView.Items.Count - 1].Bounds.Bottom <= memoListView.ClientSize.Height)
- {
- // Ak áno, načítame ďalšie logy
- RefreshLogView();
- }
- }
-
- private void button6_Click(object sender, EventArgs e)
- {
- //LiveLogCache.Instance.AddLog("Nový log z hlavnej aplikácie");
-
- LiveLogCache.Instance.ClearLogs();
- ClearListView();
- }
-
- private void runButton_Click(object sender, EventArgs e)
- {
- if (stopUpdate) // Ak je časovač zastavený, spustíme ho
- {
- stopUpdate = false; // Zastav aktualizáciu
- runButton.Enabled = false; // Zakážeme tlačidlo Start počas behu
- stopButton.Enabled = true; // Povolenie tlačidla Stop
- refreshTimer.Start();
- }
- }
-
- private void stopButton_Click(object sender, EventArgs e)
- {
- if (!stopUpdate) // Ak časovač beží, môžeme ho zastaviť
- {
- stopUpdate = true; // Spusti aktualizáciu
- runButton.Enabled = true; // Povolenie tlačidla Start
- stopButton.Enabled = false; // Zakážeme tlačidlo Stop
- refreshTimer.Stop();
- }
- }
-
- private void ClearListView()
- {
- if (InvokeRequired)
- {
- Invoke(new Action(ClearListView));
- return;
- }
-
- memoListView.Items.Clear();
- }
-
- private void refreshMemoButton_Click(object sender, EventArgs e)
- {
-
- }
-
- private void button7_Click(object sender, EventArgs e)
- {
- SaveLogsToFile();
- //LiveLogCache.Instance.AddLog("Toto je nový log.");
+ RefreshView(true);
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
- // Odpojenie udalosti, keď sa okno zatvára, inak môže po opätovnom spustení okna a pridaní nového itemu do listu zhhodiť program
- // nakoľko sa memo refreshuje cyklicky, tak refresh na udalosť nepotrebujem
- //LiveLogCache.Instance.LogAdded -= OnLogAdded;
-
- _logAdapter = null;
-
+ refreshTimer.Stop();
+ refreshTimer.Dispose();
+ regularLogFont.Dispose();
+ boldLogFont.Dispose();
base.OnFormClosing(e);
}
- private void filterButton_Click_1(object sender, EventArgs e)
+ private void InitializeFilters()
{
- // Aplikujeme filter na základe textu z filterTextBox
- string filterText = filterTextBox.Text;
- //_logFilter.ApplyFilter(filterText); // Aplikovanie filtra
- _logAdapter.ApplyFilter(filterText);
+ loadingFilters = true;
+ levelComboBox.Items.AddRange(new object[] { "All levels", "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL" });
+ levelComboBox.SelectedIndex = 0;
+ applicationComboBox.Items.Add("All applications");
+ applicationComboBox.SelectedIndex = 0;
+ loadingFilters = false;
}
- private void SubForm_FormClosing(object sender, FormClosingEventArgs e)
+
+ private void refreshTimer_Tick(object sender, EventArgs e)
{
+ ProcessActivationRequest();
+ RefreshView(false);
+ }
+
+ private void ProcessActivationRequest()
+ {
+ if (activationRequestProvider == null || !activationRequestProvider())
+ {
+ return;
+ }
+
+ if (WindowState == FormWindowState.Minimized)
+ {
+ WindowState = FormWindowState.Normal;
+ }
+
+ Show();
+ BringToFront();
+ Activate();
+ }
+
+ private void RefreshView(bool forceFullRefresh)
+ {
+ List snapshot = DiagnosticLogStore.Instance.GetSnapshot();
+ UpdateApplicationFilter(snapshot);
+
+ if (pauseButton.Checked)
+ {
+ UpdateStatus(snapshot.Count);
+ return;
+ }
+
+ long currentVersion = DiagnosticLogStore.Instance.Version;
+ if (!forceFullRefresh && currentVersion == lastRenderedVersion)
+ {
+ UpdateStatus(snapshot.Count);
+ return;
+ }
+
+ bool canAppend = !forceFullRefresh && snapshot.Count > observedEntryCount;
+ if (canAppend)
+ {
+ AppendEntries(snapshot.Skip(observedEntryCount));
+ }
+ else
+ {
+ RebuildView(snapshot);
+ }
+
+ observedEntryCount = snapshot.Count;
+ lastRenderedVersion = currentVersion;
+ UpdateStatus(snapshot.Count);
+ }
+
+ private void RebuildView(IEnumerable entries)
+ {
+ logView.SuspendLayout();
+ logView.Clear();
+ visibleEntryCount = 0;
+ AppendEntries(entries, false);
+ logView.ResumeLayout();
+
+ if (autoScrollButton.Checked)
+ {
+ ScrollToEnd();
+ }
+ }
+
+ private void AppendEntries(IEnumerable entries, bool scrollAfterAppend = true)
+ {
+ int originalSelectionStart = logView.SelectionStart;
+ int originalSelectionLength = logView.SelectionLength;
+
+ foreach (DiagnosticLogEntry entry in entries)
+ {
+ if (!MatchesFilter(entry))
+ {
+ continue;
+ }
+
+ AppendEntry(entry);
+ visibleEntryCount++;
+ }
+
+ if (scrollAfterAppend && autoScrollButton.Checked)
+ {
+ ScrollToEnd();
+ }
+ else if (!autoScrollButton.Checked)
+ {
+ int safeStart = Math.Min(originalSelectionStart, logView.TextLength);
+ int safeLength = Math.Min(originalSelectionLength, logView.TextLength - safeStart);
+ logView.Select(safeStart, safeLength);
+ }
+ }
+
+ private void AppendEntry(DiagnosticLogEntry entry)
+ {
+ string text = DiagnosticLogFormatter.Format(entry);
+
+ if (logView.TextLength > 0)
+ {
+ logView.AppendText(Environment.NewLine);
+ }
+
+ int startIndex = logView.TextLength;
+ logView.AppendText(text);
+ LogColorizer.Apply(logView, startIndex, text.Length, text, colorRules, regularLogFont, boldLogFont);
+ logView.Select(logView.TextLength, 0);
+ }
+
+ private bool MatchesFilter(DiagnosticLogEntry entry)
+ {
+ string selectedLevel = levelComboBox.SelectedItem as string;
+ if (!string.IsNullOrEmpty(selectedLevel) && selectedLevel != "All levels" && !string.Equals(entry.Level, selectedLevel, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ string selectedApplication = applicationComboBox.SelectedItem as string;
+ if (!string.IsNullOrEmpty(selectedApplication) && selectedApplication != "All applications" && !string.Equals(entry.ApplicationName, selectedApplication, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ string filter = filterTextBox.Text.Trim();
+ return filter.Length == 0 || DiagnosticLogFormatter.Format(entry).IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0;
+ }
+
+ private void UpdateApplicationFilter(IEnumerable entries)
+ {
+ string selectedApplication = applicationComboBox.SelectedItem as string ?? "All applications";
+ List applications = entries.Select(entry => entry.ApplicationName).Where(name => !string.IsNullOrWhiteSpace(name)).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(name => name).ToList();
+ List currentApplications = applicationComboBox.Items.Cast().Skip(1).ToList();
+
+ if (applications.SequenceEqual(currentApplications, StringComparer.OrdinalIgnoreCase))
+ {
+ return;
+ }
+
+ loadingFilters = true;
+ applicationComboBox.BeginUpdate();
+ applicationComboBox.Items.Clear();
+ applicationComboBox.Items.Add("All applications");
+ applicationComboBox.Items.AddRange(applications.Cast