Skip to content

Display NLog LogEvents in .NET MAUI View

Rolf Kristensen edited this page Aug 23, 2026 · 18 revisions

This guide shows a simple way to display NLog log events directly inside a .NET MAUI application.

The approach consists of three steps:

  • Define a LogRecord
  • Add NLog events to the collection ObservableCollection<LogRecord>
  • Display the collection using a MAUI CollectionView

The result is a simple in-app log viewer without introducing a dedicated logging UI component.

1. Define LogRecord

First, define the information you want to display.

public sealed record LogRecord(
    DateTime Timestamp,
    NLog.LogLevel Level,
    string Logger,
    string Message,
    Exception? Exception);

2. Add NLog events to the collection

Use an NLog MethodCallTarget for converting LogEventInfo and adding to the LogRecords-collection.

The method can look like this:

public static class MauiProgram
{
    public static ObservableCollection<LogRecord> LogRecords { get; } = new();

    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();

        builder.Logging.ClearProviders();

        var logViewerTarget = new MethodCallTarget("MauiLogViewer", (logEvent, args) => AddLogEvent(logEvent));

        NLog.LogManager.Setup()
            .RegisterMauiLog()
            .LoadConfiguration(cfg => cfg
                .ForLogger()
                .FilterMinLevel(NLog.LogLevel.Info)
                .WriteToMauiLog()
                .WriteTo(logViewerTarget));

        builder.Logging.AddNLog();

        builder.UseMauiApp<App>();

        return builder.Build();
    }

    private static void AddLogEvent(LogEventInfo logEvent)
    {
        var record = new LogRecord(
            logEvent.TimeStamp,
            logEvent.Level,
            logEvent.LoggerName,
            logEvent.FormattedMessage,
            logEvent.Exception);

        // MainThread ensure log events coming from background threads are added using the UI-thread.
        MainThread.BeginInvokeOnMainThread(() =>
        {
            LogRecords.Add(record);

            const int maxRecords = 500;

            // Avoid growing the collection indefinitely.
            while (LogRecords.Count > maxRecords)
                LogRecords.RemoveAt(0);
        });
    }
}

The target is simply another destination for NLog events; your existing logging calls do not need to change.

The maximum number of records is controlled by the code adding records to the collection, keeping the in-memory log bounded.

3. Display the records

MAUI's CollectionView can display the ObservableCollection directly.

The local XAML namespace must reference the namespace containing MauiProgram. For example, if MauiProgram is in the MauiApp2 namespace, add the following to your ContentPage:

<ContentPage
    ...
    xmlns:local="clr-namespace:MauiApp2">

You can then bind the CollectionView directly to the static LogRecords collection:

<CollectionView
    ItemsSource="{x:Static local:MauiProgram.LogRecords}"
    ItemsUpdatingScrollMode="KeepLastItemInView">

    <CollectionView.ItemTemplate>
        <DataTemplate>
            <Grid
                Padding="8,4"
                ColumnDefinitions="Auto,Auto,Auto,*"
                ColumnSpacing="12">

                <Label Grid.Column="0"
                    Text="{Binding Timestamp, StringFormat='{0:HH:mm:ss.fff}'}" />

                <Label Grid.Column="1"
                    Text="{Binding Level}" />

                <Label Grid.Column="2"
                    Text="{Binding Logger}" />

                <Label Grid.Column="3"
                    Text="{Binding Message}" />
            </Grid>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

You now have a simple log view showing:

Timestamp Level Logger Message
12:31:02.123 Info MyApp.Api Request started
12:31:03.002 Warn MyApp.Database Query took 842 ms
12:31:03.114 Error MyApp.Api Request failed

The basic LogRecord can be extended to also include structured information like IReadOnlyDictionary<string, object?> Properties if needed.

You can also add filtering, searching, level-specific colors, a clear button, or control automatic scrolling without changing the basic NLog integration.

Summary

The complete setup is intentionally simple:

LogEventInfo
    ↓
MethodCallTarget
    ↓
LogRecord
    ↓
ObservableCollection<LogRecord>
    ↓
CollectionView

This approach provides a lightweight in-app log viewer without changing existing logging calls, while leaving the UI and functionality entirely under your control. It can be further extended to meet your application's requirements, or serve as an example of using MethodCallTarget as a bridge to an existing LogViewer control.

Clone this wiki locally