Skip to content

About

NlogViewer is a ui control library to visualize NLog logs

Resources

Stars

100 stars

Watchers

3 watching

Forks

Latest commit

 

History

349 Commits

Folders and files

Repository files navigation

Nuget

NuGet NuGetDownloads

A NuGet-package is available here.

Sentinel.LogViewer

LogViewer is a ui control library to visualize NLog logs in your personal application. It is mainly based on Sentinel and its controls.

supported Framework: .NET8

Desktop application (Sentinel.LogViewer)

GitHub Releases ship:

  • MSI installer (Sentinel.LogViewer-<version>-win-x64.msi): installs under Program Files with a license dialog (MIT), Start Menu shortcut, and optional features (desktop shortcut, .log association) selectable in Custom setup. Silent install: msiexec /i Sentinel.LogViewer-<version>-win-x64.msi /qn (optional features with higher install level stay off unless you raise INSTALLLEVEL or use transforms).
  • Portable ZIP (self-contained and framework-dependent): unpack and run Sentinel.LogViewer.exe from any folder; a NLogViewer.portable marker file directs settings to appsettings.json beside the app (see README-Portable.txt in the ZIP). Installed (MSI) copies keep using per-user %LocalAppData%\Sentinel.LogViewer\.

Build the installer locally (Windows): dotnet build installer/Sentinel.LogViewer.Installer.wixproj -c Release — output installer/bin/Release/Sentinel.LogViewer.msi.

Versioning: MSI, portable ZIPs, and NuGet packages always use GitVersion fullSemVer (for example 3.2.0-124-wix-installer-implementation-and-portable-versions.88 on a feature branch, or 3.2.0 on a release tag). The Chocolatey Community package is the only exception; it uses a flattened prerelease such as 3.2.0-dev0000000004001. See chocolatey/README.md.

Text log files: the application detects the separator and the columns of a text file (timestamp, level, logger, message), and File → Open File with Column Mapping lets you correct them. Timestamps are read in the common formats, also when date and time are separate columns or in English: 2025-12-18 15:11:48.385, 18.12.2025 15:11:48, 12/18/2025 3:11:48 PM, 18 Dec 2025 15:11:48, Thu Dec 18 15:11:48 2025 and syslog's Dec 18 15:11:48 (the year is taken from the file date). Whether a date like 01/02/2025 is day first or month first is read from the dates of the file (a day above 12 decides); when the file does not tell, the culture of the computer is used, and the Date order setting of the mapping dialog overrides it. The format of the timestamp column in the list is a setting and can be switched per tab with a right-click on the column.

Restarted applications: an application is told apart by its name and its sender (IP address). When it starts again, its new process opens a new tab by default; the setting After a restart of an application (Settings → Receiving) can also continue in the existing tab, or open a new tab and close the old one. A new tab takes over the filters of the last tab of the application (levels, filter terms, hidden loggers and the format of the timestamp column, not the search box) and says so, with a button to remove them. Both can be set for one application in the menu of its tab, the arrow ▾ in the tab header or a right-click.

Changelog: after an update the application opens the changelog once, with everything that changed since the version you used before; Help → About → What’s new opens it at any time. It lists the release notes of all versions, can be searched, and is kept on disk so it is also available without a connection. Showing it after an update can be switched off under Settings → Updates.

LogViewer

Quick Start

1. Add Required References

Add the following NuGet package reference to your project:

<PackageReference Include="Sentinel.LogViewer.Wpf" Version="[version]" />

2. Configure Application Resources

In your App.xaml, add the LogViewer theme and styles:

<Application x:Class="YourApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:xamlConverter="clr-namespace:Sentinel.LogViewer.Wpf.XamlConverter;assembly=Sentinel.LogViewer.Wpf"
             xmlns:xamlMultiValueConverter="clr-namespace:Sentinel.LogViewer.Wpf.XamlMultiValueConverter;assembly=Sentinel.LogViewer.Wpf"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <!-- LogViewer Theme -->
                <ResourceDictionary Source="pack://application:,,,/Sentinel.LogViewer.Wpf;component/Themes/Generic.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

3. Use the Control

Add the namespace and use the control in your XAML:

<Window x:Class="YourApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:dj="clr-namespace:Sentinel.LogViewer.Wpf;assembly=Sentinel.LogViewer.Wpf">
    <Grid>
        <dj:LogViewer />
    </Grid>
</Window>

NLog Configuration

LogViewer is subscribing to CacheTarget. By default, the LogViewer is automatically creating a CacheTarget with loggingPattern "*" and LogLevel "Trace".

If you want to customize the loggingPattern and LogLevel, add the following to your Nlog.config.

<?xml version="1.0" encoding="utf-8" ?>
<nlog 
  xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
  autoReload="true">

  <extensions> 
    <add assembly="Sentinel.LogViewer.Wpf" /> 
  </extensions> 

  <targets async="true">
    <target
      xsi:type="CacheTarget"
      name="cache"/>
  </targets>

  <rules>
    <logger name="*" writeTo="cache" minlevel="Debug"/> 
  </rules>
</nlog>

Customize

Control Buttons

The LogViewer includes control buttons that allow you to manage log viewing behavior. The control buttons are organized in a GroupBox and can be controlled programmatically.

Control Buttons - All Visible

Control Buttons - Hidden

Control Properties:

  • AutoScroll - Automatically scroll to the newest log entry when new entries are added
  • ClearCommand - Command to clear all log entries from the viewer
  • Pause - Pause or resume logging to improve performance when not actively monitoring
  • ShowControlButtons - Controls the visibility of the entire control button group

Usage:

// Control auto-scroll behavior
logViewer.AutoScroll = true;  // Enable auto-scroll
logViewer.AutoScroll = false; // Disable auto-scroll

// Pause/resume logging
logViewer.Pause = true;  // Pause logging
logViewer.Pause = false; // Resume logging

// Hide the entire control button group
logViewer.ShowControlButtons = false;

XAML Binding:

<dj:LogViewer 
    AutoScroll="{Binding IsAutoScrollEnabled}" 
    Pause="{Binding IsLoggingPaused}"
    ShowControlButtons="{Binding ShowControls}" />

Control Logic:

  • AutoScroll automatically scrolls to the bottom when new log entries are added
  • ClearCommand removes all log entries from the viewer
  • Pause stops listening for new log events to improve performance
  • The entire control group can be hidden using ShowControlButtons = false

Colors

Customize foreground or background of every logLevel

LogViewer

Multi targeting

Use more than one instance of LogViewer to match different rules.

Create 2 targets with their own rules.

<targets async="true">
  <target xsi:type="CacheTarget" name="target1"/>
  <target xsi:type="CacheTarget" name="target2"/>
</targets>

<rules>
  <logger name="*" writeTo="target1" maxlevel="Info"/> 
  <logger name="*" writeTo="target2" minlevel="Warn"/> 
</rules>

Set TargetName property to link them.

<dj:LogViewer TargetName="target1"/>
<dj:LogViewer TargetName="target2"/>

Column Visibility

Control which columns are visible in the LogViewer. You can dynamically show or hide individual columns using the provided Dependency Properties.

Column Visibility Properties

Available column visibility properties:

  • ShowIdColumn - Controls ID column visibility
  • ShowLevelColumn - Controls Level column visibility
  • ShowTimeStampColumn - Controls TimeStamp column visibility
  • ShowLoggerNameColumn - Controls LoggerName column visibility
  • Message column is always visible (no control available)

Usage:

// Hide specific columns
logViewer.ShowIdColumn = false;
logViewer.ShowLevelColumn = false;

// Show columns again
logViewer.ShowIdColumn = true;
logViewer.ShowLevelColumn = true;

XAML Binding:

<dj:LogViewer ShowIdColumn="{Binding IsIdColumnVisible}" ShowLevelColumn="{Binding IsLevelColumnVisible}" />

Filter Buttons

The LogViewer includes filter buttons that allow you to hide/show specific log levels. The filter buttons are organized in a GroupBox and can be controlled programmatically.

Filter Buttons - All Visible

Filter Buttons - Some Hidden

Filter Properties:

  • TraceFilter - Hide/show Trace level log entries
  • DebugFilter - Hide/show Debug level log entries
  • InfoFilter - Hide/show Info level log entries
  • WarnFilter - Hide/show Warn level log entries
  • ErrorFilter - Hide/show Error level log entries
  • FatalFilter - Hide/show Fatal level log entries
  • ShowFilterButtons - Controls the visibility of the entire filter button group

Usage:

// Hide specific log levels
logViewer.TraceFilter = true;  // Hide Trace entries
logViewer.DebugFilter = true;  // Hide Debug entries
logViewer.InfoFilter = true;   // Hide Info entries

// Show log levels again
logViewer.TraceFilter = false; // Show Trace entries
logViewer.DebugFilter = false; // Show Debug entries

// Hide the entire filter button group
logViewer.ShowFilterButtons = false;

XAML Binding:

<dj:LogViewer 
    TraceFilter="{Binding HideTraceLogs}" 
    DebugFilter="{Binding HideDebugLogs}"
    ShowFilterButtons="{Binding ShowFilters}" />

Filter Logic:

  • When a filter property is set to true, entries of that log level are hidden
  • When a filter property is set to false, entries of that log level are shown
  • The filter buttons are ToggleButtons that automatically bind to these properties
  • The entire filter group can be hidden using ShowFilterButtons = false

Search Functionality

The LogViewer includes search capabilities that filter log entries based on text patterns or regular expressions. Search terms are displayed as chips/tags and can be managed through the UI or programmatically.

Search Features:

  • Text Search - Case-insensitive substring matching
  • Regex Search - Full regular expression pattern matching
  • AND Logic - All search terms must match for an entry to be displayed
  • Search Highlighting - Matched text is highlighted in both message and logger name columns
  • Context Menu - Right-click search terms for edit/remove options

Search Properties:

  • CurrentSearchText - The text currently being typed in the search box
  • UseRegexSearch - Toggle between text search and regex search modes
  • ActiveSearchTerms - Collection of active search terms (read-only)
  • SearchHighlightBackground - Brush used to highlight matched text

Usage:

// Programmatically add search terms
logViewer.CurrentSearchText = "error";
logViewer.AddSearchTerm(); // Adds as text search

// Enable regex mode and add regex pattern
logViewer.UseRegexSearch = true;
logViewer.CurrentSearchText = @"\d{4}-\d{2}-\d{2}";
logViewer.AddSearchTerm(); // Adds as regex search

// Remove specific search term
var searchTerm = logViewer.ActiveSearchTerms.First();
logViewer.RemoveSearchTerm(searchTerm);

// Clear all search terms
logViewer.ClearAllSearchTerms();

// Customize search highlight color
logViewer.SearchHighlightBackground = Brushes.Yellow;

XAML Binding:

<dj:LogViewer 
    CurrentSearchText="{Binding SearchText}"
    UseRegexSearch="{Binding IsRegexMode}"
    SearchHighlightBackground="{Binding HighlightBrush}" />

Search Logic:

  • Search terms are applied to both the message and logger name columns
  • AND logic: ALL active search terms must match for a log entry to be displayed
  • Text search is case-insensitive and performs substring matching
  • Regex search uses full .NET regular expression patterns
  • Invalid regex patterns show a warning dialog and are not added
  • Search highlighting works in real-time as you add/remove terms

Logger Tree

The loggers of the entries are shown as a tree on the right of the list, split at the dots of their names (Company → Product → Service). Open it with the Loggers button on the right edge of the list.

Features:

  • A check box per logger shows or hides that logger and everything below it; a partly checked box means some of the loggers below are hidden
  • Per logger: number of entries, plus counters for warnings and for errors and fatal errors; they update while entries arrive
  • Right-click a logger: Show only this logger, Show all loggers, Expand all, Collapse all
  • Right-click a logger name in the list: Show only this logger, Hide this logger (the same rules; ShowOnlyLoggerCommand and HideLoggerCommand take the logger name)
  • A search box above the tree lists the loggers that contain the text
  • Drag the splitter between the list and the tree to change its width; the arrow button in the header moves the tree to the other side of the list
  • Hidden loggers work together with the level buttons and the filter terms; an entry is shown only if all of them allow it

Logger Tree Properties:

  • ShowLoggerTree - Whether the tree can be opened (default true)
  • IsLoggerTreeExpanded - Whether the tree is open (default false)
  • LoggerTreeWidth - Width of the open tree (default 260, at least 140); it also follows the splitter
  • IsLoggerTreeOnLeft - Whether the tree is on the left of the list instead of the right (default false)
  • LoggerFilterRules - The hidden and shown loggers as text, to save and restore them
  • LoggerTree - The tree itself with its nodes, counters and commands

LoggerFilterRules holds one rule per entry: -Company.Product hides that logger and everything below it, +Company.Product.Service shows one of them again; the most specific rule wins. - alone stands for all loggers. Rules also work for loggers that did not log anything yet.

logViewer.IsLoggerTreeExpanded = true;

// Hide a logger and everything below it, except one logger
logViewer.LoggerFilterRules = new[] { "-Company.Product", "+Company.Product.Service" };

// Save and restore the state
var saved = logViewer.LoggerFilterRules;

Timestamp Format

The timestamp column shows the time with date and milliseconds in one of four formats of a fixed width:

TimeStampFormat Example
System (default) the short date and long time of the culture of the computer, e.g. 18.12.2025 15:11:48.385
Iso 2025-12-18 15:11:48.385
German 18.12.2025 15:11:48.385
English 12/18/2025 03:11:48.385 PM

Right-click the header or a cell of the timestamp column to switch the format of that viewer; Default view goes back to the default.

  • DefaultTimeStampFormat - The format of all viewers that have no choice of their own
  • CustomTimeStampFormat - The format of this viewer, or null to follow the default
  • EffectiveTimeStampFormat - The format that is shown (read-only)
  • CanChangeTimeStampFormat - false while you use a TimeStampResolver of your own; the menu is then disabled and your resolver is left alone

Control Architecture

LogViewer is built as a CustomControl with theming support:

Architecture:

  • CustomControl Base Class - Enables proper theming and styling
  • RelayCommand System - MVVM-compatible command system
  • Theme Support - Full support for custom themes through resource dictionaries
  • Code Organization - Separation of concerns between UI and logic

Format output (ILogEventInfoResolver)

To format the output of a LogEventInfo, implement a new instance of ILogEventInfoResolver and bind it to the Resolver you want to customize:

/// <summary>
/// Reformat the DateTime
/// </summary>
public class FooTimeStampResolver : ILogEventInfoResolver
{
    public string Resolve(LogEventInfo logEventInfo)
    {
        return logEventInfo.TimeStamp.ToUniversalTime().ToString();
    }
}
LogViewer1.TimeStampResolver = new FooTimeStampResolver();

Subscription Management (StartListen/StopListen)

The LogViewer provides manual control over log event subscriptions through the StartListen() and StopListen() methods. These methods are particularly useful in docking systems or scenarios where the control's lifecycle needs to be managed manually.

Purpose: These methods were implemented to address Issue #90 - subscription disposal when undocking the viewer parent container. The issue occurred when LogViewer controls were used in docking systems where the control would be moved between different parent windows, causing subscription leaks and improper disposal.

Root Cause: When undocking a control from a docking system, the Unloaded event is triggered, which automatically disposes the subscription. This means that after undocking, the control is no longer listening for log events. Therefore, StartListen() must always be called in the DockChanged event handler to restore the subscription after undocking.

Method Details:

StartListen()

  • Purpose: Starts listening for log events by subscribing to the cache target
  • When to use:
    • When undocking from a docking system
    • When the window loads again after being hidden
    • When resuming log monitoring after pausing
  • Behavior:
    • Subscribes to the CacheTarget's observable stream
    • Buffers log events for 100ms intervals for better performance
    • Automatically manages parent window references for proper disposal
    • Prevents duplicate subscriptions if already listening

StopListen()

  • Purpose: Stops listening for log events by disposing the subscription
  • When to use:
    • When docking in a docking system
    • When the window is being unloaded
    • When pausing log monitoring temporarily
  • Behavior:
    • Disposes the current subscription
    • Clears the listening state
    • Prevents memory leaks from orphaned subscriptions

Usage Example:

// Manual control in docking scenarios
private void OnDockChanged(object sender, DockChangedEventArgs e)
{
    // Control is being undocked - MUST call StartListen() because 
    // the Unloaded event was triggered and disposed the subscription
    logViewer.StartListen();
}

// Pause/Resume functionality
private void ToggleLogging()
{
    if (logViewer.IsListening)
    {
        logViewer.StopListen();
    }
    else
    {
        logViewer.StartListen();
    }
}

Automatic Management: The control automatically calls StartListen() when loaded and StopListen() when properly disposed. The methods are designed to be safe to call multiple times and handle edge cases like design-time mode and missing parent windows.

Samples

open on a new window

LogViewer

Create a new Window and add a default LogViewer

<dj:LogViewer TargetName="target1"/>

Open the new Window

TestPopup popup = new TestPopup();
popup.Show();

seperate logger for a task

LogViewer

Below is a sample how you could create a LogViewer for a task

// create unique target name
var taskNumber = _RandomTaskCounter++;
string targetName = $"task{taskNumber}";
// create a unique logger
var loggerName = $"MyFoo.Logger.{taskNumber}";
var logger = LogManager.GetLogger(loggerName);

// create new CacheTarget
CacheTarget target = new CacheTarget
{
    Name = targetName
};

// get config // https://stackoverflow.com/a/3603571/6229375
var config = LogManager.Configuration;

// add target
config.AddTarget(targetName, target);

// create a logging rule for the new logger
LoggingRule loggingRule = new LoggingRule(loggerName, LogLevel.Trace, target);

// add the logger to the existing configuration
config.LoggingRules.Add(loggingRule);

// reassign config back to NLog
LogManager.Configuration = config;

// create a new LogViewer Control with the unique logger target name
LogViewer logViewer = new LogViewer
{
    TargetName = targetName,
};

// add it to the tab control
var tabItem = new TabItem { Header = $"Task {taskNumber}", Content = logViewer };
TabControl1.Items.Add(tabItem);
TabControl1.SelectedItem = tabItem;

// create task which produces some output
var task = new Task(async () =>
{
    while (true)
    {
        logger.Info($"Hello from task nr. {taskNumber}. It's {DateTime.Now.ToLongTimeString()}");
        await Task.Delay(1000);
    }
});

Why CacheTarget?

There is already a LogViewerTarget, which is used for Sentinel. See here

<target 
    xsi:type="LogViewer"
    name="sentinel"
    address="udp://127.0.0.1:9999"/>

Contributors

Feel free to make a PullRequest or open an Issue to extend this library!

About

NlogViewer is a ui control library to visualize NLog logs

Resources

Stars

100 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages