Platform | Logo | Package |
---|---|---|
MetroLog.Net6 |
||
MetroLog.Maui |
MetroLog.Net6 is a .NET 6 lightweight logging framework designed primarily for mobile platforms like iOS
and Android
.
The intention is that MetroLog is mostly compatible with NLog
. Both the surface area and internal construction
should just about match.
However, to achieve better startup performance, configuration is only possible through code.
Configuration is made through the crossing of a target and log levels.
A target specify how the logs will be stored. For example, you can use the DebugTarget
to show your logs in the debug output.
Or you could use the FileSnapshotTarget
to store your logs in a file.
The log levels describe the level of criticality. You bind each target to a set of log levels.
var config = new LoggingConfiguration();
#if RELEASE
config.AddTarget(
LogLevel.Info,
LogLevel.Fatal,
new StreamingFileTarget(retainDays: 2);
#else
// Will write logs to the Debug output
config.AddTarget(
LogLevel.Trace,
LogLevel.Fatal,
new TraceTarget());
#endif
// will write logs to the console output (Logcat for android)
config.AddTarget(
LogLevel.Info,
LogLevel.Fatal,
new ConsoleTarget();
config.AddTarget(
LogLevel.Info,
LogLevel.Fatal,
new MemoryTarget(2048);
LoggerFactory.Initialize(config);
If we are in release mode, we store our logs in files for 2 days, only if it falls between Info
and Fatal
levels (debug level logs will be discarded).
If we are in debug we just send our logs to the debug output thanks to the trace target.
In both case, we send our logs to a memory buffer so we can display them at any time (through the LogOperator
).
public class DownloadService
{
private static readonly ILogger Log = LoggerFactory.GetLogger(nameof(DownloadService));
public async Task<File> DowloadAsync()
{
Log.Info("DowloadAsync()");
try
{
await _client.GetAsync();
}
catch (Exception exception)
{
Log.Error("Oops something bad happened", exception);
}
}
}
builder.Logging
.SetMinimumLevel(LogLevel.Trace) // IMPORTANT: set your minimum log level, here Trace
.AddTraceLogger(
options =>
{
options.MinLevel = LogLevel.Trace;
options.MaxLevel = LogLevel.Critical;
}) // Will write to the Debug Output
.AddConsoleLogger(
options =>
{
options.MinLevel = LogLevel.Information;
options.MaxLevel = LogLevel.Critical;
}) // Will write to the Console Output (logcat for android)
.AddInMemoryLogger(
options =>
{
options.MaxLines = 1024;
options.MinLevel = LogLevel.Debug;
options.MaxLevel = LogLevel.Critical;
})
.AddStreamingFileLogger(
options =>
{
options.RetainDays = 2;
options.FolderPath = Path.Combine(
FileSystem.CacheDirectory,
"MetroLogs");
});
In this cas we add 4 different targets to our configuration:
- Trace target (
Trace -> Critical
) - Console target (
Information -> Critical
) - In memory target (
Debug -> Critical
) - File target (
Info -> Critical
)
public class DownloadService
{
private readonly ILogger _logger;
public DowloadService(ILogger<DownloadService> logger)
public async Task<File> DowloadAsync()
{
_logger.Info("DowloadAsync()");
try
{
await _client.GetAsync();
}
catch (Exception exception)
{
_logger.Error("Oops something bad happened", exception);
}
}
}
We only specify the levels for the in memory logger, it means the other loggers will have the default min/max levels.
Default min level is Info
, default max level is Fatal
.
PR from @SirJohnK.
Since 2.1, you can specify your own message layout.
Inherit from Layout
and override GetFormattedString
:
using MetroLog;
using Layout = MetroLog.Layouts.Layout;
namespace MetroLogSample.Maui.Layouts
{
public class SimpleLayout : Layout
{
public override string GetFormattedString(LogWriteContext context, LogEventInfo info)
{
return $"{info.TimeStamp:G} - {info.Level}: {info.Message}";
}
}
}
Then set the Layout
property from the LoggerOptions
:
builder.Logging
.AddTraceLogger(
options =>
{
options.MinLevel = LogLevel.Trace;
options.MaxLevel = LogLevel.Critical;
options.Layout = new SimpleLayout();
}) // Will write to the Debug Output
ConsoleTarget
: log to the Console output (Logcat for android) throughConsole.WriteLine
TraceTarget
: log to the Debug output throughTrace.WriteLine
JsonPostTarget
: post your log message to a http endpointMemoryTarget
: log to a in-memory bufferStreamingFileTarget
: log a to file
You can of course provide your own Target
by implementing SyncTarget
or AsyncTarget
.
You can even subclass FileTarget
if you want your own file base target.
Those target can also provide additional features through the subclassing of the ILogOperator
interface.
Some targets provide additional features.
For example the FileStreamingTarget
allows us to get the logs file as a compressed zip.
Accessing those capabilities is made possible by the LogOperator.Instance
singleton.
If your configuration has a file target, you can then retrieve your compressed logs through the log operator:
private void InitializeAppCenter()
{
#if RELEASE
if (!LogOperatorRetriever.Instance.TryGetOperator<ILogCompressor>(out var logCompressor))
{
return;
}
Crashes.GetErrorAttachments = report =>
{
var compressedLogs = logCompressor.GetCompressedLogs();
return new[]
{
ErrorAttachmentLog.AttachmentWithBinary(
compressedLogs.ToArray(),
"logs.zip",
"application/x-zip-compressed"),
};
};
#endif
}
public interface ILogLister : ILogOperator
{
Task<List<string>> GetLogList();
}
public interface ILogCompressor : ILogOperator
{
Task<MemoryStream> GetCompressedLogs();
}
MetroLog provides a MAUI helper package adding features like:
- Share logs as a zip file
- Displaying logs in a page
- Shake the device to display logs
You can find all the information needed in the MetroLogSample.Maui
project.
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>()
.ConfigureFonts(
fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
builder.Logging
.AddTraceLogger(
options =>
{
options.MinLevel = LogLevel.Trace;
options.MaxLevel = LogLevel.Critical;
}) // Will write to the Debug Output
.AddInMemoryLogger(
options =>
{
options.MaxLines = 1024;
options.MinLevel = LogLevel.Debug;
options.MaxLevel = LogLevel.Critical;
})
.AddStreamingFileLogger(
options =>
{
options.RetainDays = 2;
options.FolderPath = Path.Combine(
FileSystem.CacheDirectory,
"MetroLogs");
});
builder.Services.AddSingleton(LogOperatorRetriever.Instance);
builder.Services.AddSingleton<MainPage>();
return builder.Build();
}
}
The package provides a LogController that acts like a view model providing commands and service to a view.
public class LogController : INotifyPropertyChanged
{
public ICommand ToggleShakeCommand { get; }
public ICommand GoToLogsPageCommand { get; }
public ICommand ClosePageCommand { get; }
public bool CanGetCompressedLogs => LogCompressor != null;
public bool CanGetLogsString => LogLister != null;
public bool IsShakeEnabled
{
get => _isShakeEnabled;
set
{
if (_isShakeEnabled != value)
{
ToggleAccelerometer(value);
_isShakeEnabled = value;
}
OnPropertyChanged();
}
}
public static void SuspendShake();
public static void ResumeShakeIfNeeded();
public async Task<MemoryStream?> GetCompressedLogs();
public async Task<List<string>?> GetLogList();
}
By setting IsShakeEnabled
to true, you can display your log page by simply shaking your device.
IMG_4408.mp4
Because there is so several navigation framework out there, you first need to initialize the LogController
with the navigation function, and the pop page function.
using MetroLog.Maui;
namespace MetroLogSample.Maui;
public partial class App : Application
{
public App(MainPage mainPage)
{
InitializeComponent();
MainPage = new NavigationPage(mainPage);
LogController.InitializeNavigation(
page => MainPage!.Navigation.PushModalAsync(page),
() => MainPage!.Navigation.PopModalAsync());
}
}
The third parameter is the page factory returning the log page you want to display when are calling the GoToLogsPageCommand
.
By default, it's the MetroLogPage
.
The package provides a default log page bound to the LogController
.
This page displays the logs, and give the possibility to share your logs as a zip and refresh them.
WARNING
- you need to add the
MemoryTarget
if you want to display the logs. - you need to add the
StreamingFileTarget
if you want to share the logs.
You can customize some page colors by settings those resource key to the desired color:
public static class LogPageResources
{
public const string LogPageBackgroundColor = nameof(LogPageBackgroundColor);
public const string LogPageTextColor = nameof(LogPageTextColor);
public const string LogPageButtonColor = nameof(LogPageButtonColor);
public const string LogPageFontFamily = nameof(LogPageFontFamily);
public const string LogPageLogsBorderColor = nameof(LogPageLogsBorderColor);
public const string LogPageLogsBackgroundColor = nameof(LogPageLogsBackgroundColor);
}
<?xml version="1.0" encoding="UTF-8"?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MetroLogSample.Maui.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<Color x:Key="LogPageBackgroundColor">#262626</Color>
<Color x:Key="LogPageLogsBackgroundColor">#1D1D1D</Color>
<Color x:Key="LogPageLogsBorderColor">#1D1D1D</Color>
<Color x:Key="LogPageTextColor">White</Color>
</ResourceDictionary>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
This is a fork/reboot of the MetroLog library created by Claire Novotny.
This is an open source project that welcomes contributions/suggestions/bug reports from those who use it. If you have any ideas on how to improve the library, please contact @piskariov.
MetroLog is distributed under the MIT license.