Files
tbf/AppDiagnostic.Logging/DiagnosticMessageSerializer.cs
T
marekf 8c77df207a <Feat>: Add universal NLog diagnostics and improve AppDiagnostic UI
Cause:

- Live diagnostics required logging from multiple applications without blocking TBF.
- Shared nlog.config files caused configuration conflicts.
- The original viewer lacked configurable colors and practical log controls.
- Inconsistent diagnostic deployment caused log transfer failures and locked shared DLLs during builds.

Solution:

1. Added universal diagnostic transport
   - Added AppDiagnostic.Logging with an NLog target and named-pipe communication.
   - Added background buffering and automatic reconnection.
   - Registered the diagnostic connector programmatically.

2. Separated diagnostic window lifecycle
   - Run AppDiagnostic as an independent process.
   - Activate the existing window on repeated launches.
   - Keep TBF running when the diagnostic window closes.

3. Corrected diagnostic deployment
   - Copy the complete diagnostic output into a dedicated AppDiagnostic subdirectory during build.
   - Overwrite existing deployment files to prevent inconsistent versions.
   - Validate and launch the executable from that subdirectory with an explicit working directory.

4. Improved the log viewer
   - Added configurable substring colors with a palette and live preview.
   - Added text, level and application filters.
   - Added line wrapping, clipboard actions and log export.
   - Added Pause/Resume, Refresh, Clear, auto-scroll and Always on top.
   - Added connection and log counters.
   - Persist color rules between sessions.

5. Removed legacy dependencies
   - Replaced LiveLogCache with structured diagnostic storage.
   - Removed AppDiagnostic dependencies on log4net and SharedComponents.

Validation:

- Confirmed successful log transfer when launching AppDiagnostic from TBF.
2026-09-10 17:45:29 +02:00

114 lines
3.3 KiB
C#

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;
}
}
}