Logs tool
Viewing Avalonia logs in the tool
By default Avalonia warnings and errors are automatically recorded by Developer Tools.
Main features include:
- Combined message in the data table.
- Filtering by verbosity, message and parameters.
- Display of each argument independently.
- If log entry
Sourceis a visual element attached to the elements tree, it can be clicked to navigate to this element inside of the Developer Tools - Integration with third-party loggers.

Enabling Microsoft.Extensions.Logging integration
By default, only Avalonia logs are redirected to the Developer Tools process. The DiagnosticsSupport library includes built-in integration with Microsoft logging abstractions, which you can enable if needed.
To do so, LoggerFactory needs to be created as normally. Returned object can be passed to the DeveloperToolsOptions.AddMicrosoftLoggerObservable(ILoggerFactory, LogLevel) method.
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
var loggerFactory = LoggerFactory.Create(b => b
.SetMinimumLevel(LogLevel.Information)
.AddConsole());
this.AttachDeveloperTools(o =>
{
o.AddMicrosoftLoggerObservable(loggerFactory);
});
Logger = loggerFactory.CreateLogger<Application>();
}
For MS Dependency Injection solutions, ILoggerFactory interfaces can be stored and retrieved from the ServiceCollection.
You can find more details about DeveloperToolsOptions on Reference to DeveloperToolsOptions page.
Attaching custom log source

This example creates a Serilog sink that is configured to redirect logs into the Developer Tools.
According to Serilog Developing a sink documentation it's necessary to implement a simple ILogEventSink interface. Together with ILoggerObservable, which is necessary to connect it with the Developer Tools:
public class DevToolsSerilogSink(string logArea = "Serilog") : ILogEventSink, ILoggerObservable
{
}
Start with implementing ILoggerObservable.Subscribe by recording a list of observers. ILoggerObserver has only two methods: IsEnabled and Log, both of which are going to be used in this sample. Return value is a disposable that will get called once DevTools is disconnecting.
private readonly LinkedList<ILoggerObserver> _observers = [];
public IDisposable Subscribe(ILoggerObserver observer)
{
_observers.AddLast(observer);
return Disposable.Create(() => _observers.Remove(observer));
}
ILogEventSink.Emit implementation has to convert Serilog log event into parameters compatible with ILoggerObserver:
public void Emit(LogEvent logEvent)
{
var logLevel = logEvent.Level switch
{
LogEventLevel.Verbose => LogEntryVerbosity.Verbose,
LogEventLevel.Debug => LogEntryVerbosity.Debug,
LogEventLevel.Information => LogEntryVerbosity.Information,
LogEventLevel.Warning => LogEntryVerbosity.Warning,
LogEventLevel.Error => LogEntryVerbosity.Error,
LogEventLevel.Fatal => LogEntryVerbosity.Fatal,
_ => throw new ArgumentOutOfRangeException()
};
// Map each parameter into a strings array:
var parameters = new string[logEvent.Properties.Count];
var paramIndex = 0;
foreach (var value in logEvent.Properties.Values)
{
parameters[paramIndex++] = value.ToString(null, formatProvider);
}
foreach (var observer in _observers)
{
// `Developer Tools` might disable specific logging areas, so we need to check them first.
if (observer.IsEnabled(logLevel, logArea))
{
// Queue log entry with our parameters.
observer.Log(logLevel, logArea, null, logEvent.MessageTemplate.Text, logEvent.Exception, parameters);
}
}
}
With both interfaces it's now possible to configure both Serilog and the Developer Tools together in Application.Initialize method:
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
var sink = new SerilogSink();
Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.Sink(sink)
.CreateLogger();
this.AttachDeveloperTools(o =>
{
o.AddLoggerObservable(sink);
});
}
Then, use it somewhere in the code:
private int _clickTimes = 0;
private void Button_OnClick(object? sender, RoutedEventArgs e)
{
_clickTimes++;
App.Logger!.Information("Button was clicked {Times} times", _clickTimes);
}
Full listing of DevToolsSerilogSink class
public class DevToolsSerilogSink(string logArea = "Serilog", IFormatProvider? formatProvider = null)
: ILogEventSink, ILoggerObservable
{
private readonly LinkedList<ILoggerObserver> _observers = [];
public IDisposable Subscribe(ILoggerObserver observer)
{
_observers.AddLast(observer);
return Disposable.Create(() => _observers.Remove(observer));
}
public void Emit(LogEvent logEvent)
{
var logLevel = logEvent.Level switch
{
LogEventLevel.Verbose => LogEntryVerbosity.Verbose,
LogEventLevel.Debug => LogEntryVerbosity.Debug,
LogEventLevel.Information => LogEntryVerbosity.Information,
LogEventLevel.Warning => LogEntryVerbosity.Warning,
LogEventLevel.Error => LogEntryVerbosity.Error,
LogEventLevel.Fatal => LogEntryVerbosity.Fatal,
_ => throw new ArgumentOutOfRangeException()
};
var parameters = new string[logEvent.Properties.Count];
var paramIndex = 0;
foreach (var value in logEvent.Properties.Values)
{
parameters[paramIndex++] = value.ToString(null, formatProvider);
}
foreach (var observer in _observers)
{
if (observer.IsEnabled(logLevel, logArea))
{
observer.Log(logLevel, logArea, null, logEvent.MessageTemplate.Text, logEvent.Exception, parameters);
}
}
}
}