Skip to content

Add a cross-platform Avalonia log viewer control as the successor to the WPF LogMessageReceiver #136

Description

@karlspace

Problem

The library's live log viewer only works in WPF. BAUERGROUP.Shared.Desktop targets net10.0-windows;net8.0-windows with UseWPF/UseWindowsForms (src/BAUERGROUP.Shared.Desktop/BAUERGROUP.Shared.Desktop.csproj:4-6). An Avalonia application on Linux therefore cannot use WPFToolbox.LogMessageReceiverWindow (src/BAUERGROUP.Shared.Desktop/WPFToolbox.cs:19) and has to build its own viewer.

A straight port would also bring along problems in the current implementation:

  • Two unrelated mechanisms. The window (src/BAUERGROUP.Shared.Desktop/Logging/LogMessageReceiver.xaml:13) hosts LogMessageReceiverControl, which receives log lines over UDP loopback through BGLoggerNetworkListener (Logging/LogMessageReceiverControl.xaml.cs:39). Logging/TextBoxTraceListener.cs is a separate TraceListener that nothing in the library uses.

  • Global side effects of the UDP listener. BGLoggerNetworkListener sets BGLogger.Configuration.Network = true (src/BAUERGROUP.Shared.Core/Logging/BGLoggerNetworkListener.cs:43-44) and never sets it back. From then on, every log line is also sent to udp://127.0.0.1:9898 (the default NetworkPort, BGLoggerConfiguration.cs:44, :533, :560) until the process ends. Dispose only cancels and disposes the CancellationTokenSource (:47-52), and the loop checks the token only between blocking UdpClient.Receive calls (:28-30). The socket is therefore released only after the next datagram arrives.

  • Buffer is either unbounded or loses history. Both viewers append with Text += (Logging/TextBoxTraceListener.cs:44, Logging/LogMessageReceiverControl.xaml.cs:53), which copies the whole text on every line. TextBoxTraceListener never trims. LogMessageReceiverControl clears all text once it passes 1000 lines (Logging/LogMessageReceiverControl.xaml.cs:57-58) instead of dropping only the oldest lines.

  • The producer is blocked. TextBoxTraceListener uses Dispatcher.Invoke (Logging/TextBoxTraceListener.cs:42), so the thread that logs waits for the UI thread.

  • Relies on window placement. The window is Topmost="True" with Left="5" Top="5" (Logging/LogMessageReceiver.xaml:11). Native Wayland does not honour that. In Avalonia.Wayland 12.1.2, Position, Activate() and SetTopmost() do nothing (WindowImplBase.cs#L67-L72), neither does Move (WindowImpl.cs#L310-L311), and Window.Activate() only forwards to the platform (WindowBase.cs#L142-L145).

  • The Trace route is off by default. A viewer that only adds a TraceListener shows no BGLogger output, because the TRACE target is disabled by default (src/BAUERGROUP.Shared.Core/Logging/BGLoggerConfiguration.cs:62, property at :322). I checked this with a small console program against BAUERGROUP.Shared.Core 3.0.8 that registers a TraceListener:

    Configuration.Trace default = False
    lines captured after default: 0
    lines captured after Trace = true: 1
    

These files have not changed between v3.0.8 and origin/main (git diff --stat v3.0.8 origin/main -- src/BAUERGROUP.Shared.Desktop src/BAUERGROUP.Shared.Core/Logging prints nothing).

Proposal

Build an Avalonia log viewer as a control first. It should work the same on Windows, X11 and native Wayland.

  1. Embeddable control (e.g. LogViewer). The host places it in its own window: a panel, tab, splitter or overlay. This form does not depend on window position, stacking order or activation, so it behaves the same on every backend.
  2. Optional window wrapper (e.g. LogViewerWindow) that hosts the same control. It is shown with an owner (Show(owner), which maps to xdg_toplevel.set_parent on Wayland, WindowImpl.cs#L235-L239). Topmost and Position are hints at most. The wrapper must stay usable when they are ignored, and must not rely on Activate() to bring an existing window back.
  3. Log source owned by the control. Attach on AttachedToVisualTree and detach on DetachedFromVisualTree. Both must be safe to call twice, and nothing may be left registered after detach. Two options:
    • (a) A TraceListener that sets BGLogger.Configuration.Trace = true while attached and restores the previous value on detach. This is simple, but Trace is process-wide: all other registered listeners receive the lines too, and several viewers need reference counting.
    • (b) Preferred: a small in-process sink API in Core, e.g. IDisposable BGLogger.Configuration.AddLiveSink(Action<LogLine>). It adds an NLog target while at least one sink is registered, has no Trace side effects, and can pass the level along for colouring or filtering.
    • Not UDP: inside the same process, a fixed port and a global Network switch add nothing.
  4. Bounded buffer. Use a ring buffer of N lines (configurable, default e.g. 5000) that drops the oldest lines. Render only what the buffer holds, e.g. a virtualized list, or a text view rebuilt from the buffer at a throttled rate. Never re-assign the entire accumulated text for each line.
  5. Threading. The logging thread only enqueues (non-blocking, never Dispatcher.Invoke). A UI timer (e.g. every 100-250 ms) empties the queue in batches, so a burst of lines causes one UI update instead of one Post per line.
  6. Autoscroll to the newest line by default, as v1 does (Logging/LogMessageReceiverControl.xaml.cs:60). Pausing it while the user has scrolled up is optional.
  7. Headless tests (Avalonia.Headless) for: nothing left registered after detach, the buffer limit, and batched draining.

The UI-independent parts (the sink API and the ring buffer) belong in BAUERGROUP.Shared.Core. The existing WPF LogMessageReceiverControl can then use them too, replacing the UDP listener and fixing its buffer.

Open decision: packaging

Option Pros Cons
New BAUERGROUP.Shared.Avalonia package (net8.0;net10.0, no -windows) Linux consumers get no WPF/WinForms dependency; WPF consumers get no Avalonia dependency; TFMs stay simple One more package to version and release; CI has to build and test it on a non-Windows TFM
Inside BAUERGROUP.Shared.Desktop No new package Desktop is *-windows with UseWPF/UseWindowsForms (csproj:4-6), so a Linux net10.0 build cannot reference it without multi-targeting and conditional compilation; every WPF consumer would pull in Avalonia

Context

bgIndustrialAutomation Client (bauer-group/OT-AutomationClient) moved from WPF to Avalonia 12 and targets Windows and Linux. It now runs natively on Wayland through Avalonia.Wayland 12.1.2 where the compositor supports it, and falls back to X11 otherwise (bauer-group/OT-AutomationClient@0c8529b). As a workaround the application rebuilt the F12 viewer in src/BAUERGROUP.bgIndustrialAutomation.Client.App/Views/LogWindow.axaml(.cs) (application references below are at its main, commit 3f6fb39). Its remarks say the code should move into a BAUERGROUP.Shared.Avalonia package once it has proven itself (LogWindow.axaml.cs:15-20). The rebuild shows the gaps described above:

  • It is a topmost window at Position="5,5" (LogWindow.axaml:9-11). Pressing F12 again calls _logWindow.Activate() (App.axaml.cs:299-303), which does nothing on Wayland.
  • Its StringBuilder _buffer (LogWindow.axaml.cs:40) grows for as long as the window stays open, e.g. a whole shift. On every line, Text is replaced with the whole buffer (:87-92), so each new line costs more than the last.
  • It adds a TraceListener (:55-58) but never enables BGLogger.Configuration.Trace, so under library defaults no BGLogger lines reach it (see the check above). That is a defect in the application. It is mentioned here because a control that owns its log source rules out this mistake.

The application uses BAUERGROUP.Shared.Core 3.0.8 and does not reference BAUERGROUP.Shared.Desktop. Library references are to origin/main at 02325dd.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions