MAUI Blazor学习21-使用NLog记录安卓APP运行日志
MAUI Blazor学习21-使用NLog记录安卓APP运行日志
MAUI Blazor系列目录
- MAUI Blazor学习1-移动客户端Shell布局 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习2-创建移动客户端Razor页面 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习3-绘制ECharts图表 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习4-绘制BootstrapBlazor.Chart图表 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习5-BLE低功耗蓝牙 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习6-扫描二维码 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习7-实现登录跳转页面 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习8-支持多语言 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习9-VS Code开发调试MAUI入门 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习10-BarcodeScanner扫描二维码 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习11-百度地图定位 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习12-文件另存为 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习13-打开文件 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习14-选择目录 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习15-采用html2pdf.js生成pdf - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习16-连续按BACK退出APP - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习17-NavigationLock阻止页面回退 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习18-自动升级 - SunnyTrudeau - 博客园
- MAUI Blazor学习19-角标(右上角红点) - SunnyTrudeau - 博客园
- MAUI Blazor学习20-升级到Net8 - SunnyTrudeau - 博客园
对于.Net Core框架的应用软件而言,记录日志是非常基本的功能,也有很多成熟的第三方日志组件。但是在MAUI安卓APP中记录日志还是有一些值得注意的地方,总结一下。
引用NLog日志组件
Web项目可以引用NLog.Web.AspNetCore,但是MAUI项目不可以,否则编译报错:Microsoft.AspNetCore.App 没有运行时包可用于指定的 RuntimeIdentifier“android-arm64”。MAUI项目引用的组件为NLog.Extensions.Logging
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.14" />
编写NLog.config
根据项目需要编写NLog.config,需要注意手机APP运行的环境跟Web项目差别很大,建议只记录必要的日志,以免影响性能。
<?xml version="1.0" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true"> <targets> <!--write logs to Console--> <target name="console" xsi:type="ColoredConsole" layout="${longdate}, ${level:uppercase=true:padding=-5}, ${callsite}, ${message}, ${exception}" /> <!--write logs to Visual Studio Output--> <target name="debugger" xsi:type="Debugger" layout="${longdate}, ${level:uppercase=true:padding=-5}, ${callsite}, ${message}, ${exception}" /> <!--write logs to file--> <target name="file" xsi:type="File" layout="${longdate}, ${level:uppercase=true:padding=-5}, ${callsite}, ${message}, ${exception}" fileName="${basedir}/app.log" archiveFileName="${basedir}/app.{#}.log" encoding="utf-8" archiveAboveSize="102400" maxArchiveFiles="10" archiveNumbering="Rolling" concurrentWrites="true" keepFileOpen="false" /> </targets> <rules> <!--TRACE,DEBUG,INFO,WARN,ERROR,FATAL--> <logger name="Microsoft.*" maxlevel="Info" writeTo="" final="true" /> <logger name="*" minlevel="Debug" writeTo="console" /> <logger name="*" minlevel="Debug" writeTo="debugger" /> <logger name="*" minlevel="Info" writeTo="file" /> </rules> </nlog>
注册NLog日志服务
NLog组件有注册服务的接口,使用非常简单。如果是Web项目,可以把NLog.config文件属性设置为总是复制到输出目录,软件启动时就可以加载配置文件。但是MAUI项目行不通,必须编写额外的代码,把NLog.config文件从资源文件目录,复制到APP数据目录。为此编写一个文件帮助类,可以从资源目录加载文件,读写APP数据目录的文件。
D:\Software\gitee\mauiblazorapp\MaBlaApp\Data\FileHelper.cs
/// <summary> /// 文件帮助类 /// </summary> public static class FileHelper { /// <summary> /// 加载Raw目录下的文本文件 /// </summary> /// <param name="filename"></param> /// <returns></returns> public static async Task<string> LoadTxtFromMauiAssetAsync(string filename) { using var stream = await FileSystem.OpenAppPackageFileAsync(filename); using var reader = new StreamReader(stream); var contents = reader.ReadToEnd(); Debug.WriteLine($"加载{filename}, 长度={contents.Length:N0}"); return contents; } /// <summary> /// 保存文本文件到APP数据目录 /// </summary> /// <param name="filename"></param> /// <param name="contents"></param> /// <returns></returns> public static async Task<bool> SaveTxtToAppDataAsync(string filename, string contents) { string filePath = Path.Combine(FileSystem.Current.AppDataDirectory, filename); try { if (File.Exists(filePath)) { // 如果文件内容没有变化,则不需要保存 string oldContents = await File.ReadAllTextAsync(filePath); if (oldContents == contents) { Debug.WriteLine($"文件{filePath}内容没有变化,不需要保存"); return true; } } await File.WriteAllTextAsync(filePath, contents); Debug.WriteLine($"保存{filePath}, 长度={contents.Length:N0}"); return true; } catch (Exception ex) { Debug.WriteLine(ex); return false; } } /// <summary> /// 加载APP数据目录下的文本文件 /// </summary> /// <param name="filename"></param> /// <returns></returns> public static async Task<string> LoadTxtFromAppDataAsync(string filename) { string filePath = Path.Combine(FileSystem.Current.AppDataDirectory, filename); try { if (!File.Exists(filePath)) return ""; string contents = await File.ReadAllTextAsync(filePath); Debug.WriteLine($"加载{filePath}, 长度={contents.Length:N0}"); return contents; } catch (Exception ex) { Debug.WriteLine(ex); return ""; } } }
编写APP日志帮助类,封装注册NLog服务。安装APP后首次运行没有NLog.config文件,无法注册NLog日志服务;第二次以后运行就可以了。
D:\Software\gitee\mauiblazorapp\MaBlaApp\Data\AppLogHelper.cs
/// <summary> /// APP日志帮助类 /// </summary> public static class AppLogHelper { const string NLogConfigFileName = "NLog.config"; /// <summary> /// 注册NLog日志服务 /// </summary> /// <param name="services"></param> public static void AddLogServices(this IServiceCollection services) { //复制NLog配置文件到AppData目录 InitNLogConfigAsync().ConfigureAwait(false); services.AddLogging(builder => { // 移除已经注册的其他日志处理程序 builder.ClearProviders(); builder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Debug); string nlogConfigFile = Path.Combine(AppContext.BaseDirectory, NLogConfigFileName); if (File.Exists(nlogConfigFile)) { LogManager.Configuration = new XmlLoggingConfiguration(nlogConfigFile); builder.AddNLog(LogManager.Configuration); Debug.WriteLine($"{nlogConfigFile}文件存在,NLog日志服务注册成功"); } else { Debug.WriteLine($"{nlogConfigFile}文件不存在,NLog日志服务未注册"); } }); } /// <summary> /// 复制NLog配置文件到AppData目录 /// </summary> /// <returns></returns> public static async Task<bool> InitNLogConfigAsync() { //加载Raw目录下的NLog配置文件 string contents = await FileHelper.LoadTxtFromMauiAssetAsync(NLogConfigFileName); //保存NLog配置文件到APP数据目录 bool success = await FileHelper.SaveTxtToAppDataAsync(NLogConfigFileName, contents); return success; } }
编写测试日志的服务和页面模块
编写一个测试日志的服务类,生成各种级别的日志消息,把日志文件复制到安卓手机的download目录。
D:\Software\gitee\mauiblazorapp\MaBlaApp\Data\TestLogService.cs
/// <summary> /// 测试日志服务 /// </summary> public class TestLogService { const string AppLogFileName = "app.log"; private readonly ILogger<TestLogService> _logger; public TestLogService(ILogger<TestLogService> logger) { _logger = logger; } /// <summary> /// 创建日志消息 /// </summary> public void MakeLogMessage() { _logger.LogTrace("测试Trace日志"); _logger.LogDebug("测试Debug日志"); _logger.LogInformation("测试Info日志"); _logger.LogWarning("测试Warn日志"); _logger.LogError("测试Error日志"); _logger.LogCritical("测试Critical日志"); } /// <summary> /// 导出APP日志文件 /// </summary> /// <returns></returns> public async Task<int> ExportAppLogAsync() { #if ANDROID //加载APP日志文件内容 //[0:] 加载/data/user/0/com.companyname.mablaapp/files/app.log, 长度=xxx string contents = await FileHelper.LoadTxtFromAppDataAsync(AppLogFileName); // 获取download目录 var downloadFile = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads); string downloadPath = downloadFile.AbsolutePath; #else //D:\Software\gitee\mauiblazorapp\MaBlaApp\bin\Debug\net8.0-windows10.0.19041.0\win10-x64\app.log string contents = await File.ReadAllTextAsync(Path.Combine(AppContext.BaseDirectory, AppLogFileName)); string downloadPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); #endif string downloadFilePath = Path.Combine(downloadPath, AppLogFileName); await File.WriteAllTextAsync(downloadFilePath, contents); //[0:] 导出APP日志文件到/storage/emulated/0/Download/app.log, 长度=xxx Debug.WriteLine($"导出APP日志文件到{downloadFilePath}, 长度={contents.Length:N0}"); return contents.Length; } }
注册日志服务
D:\Software\gitee\mauiblazorapp\MaBlaApp\MauiProgram.cs
public static MauiApp CreateMauiApp()
//注册NLog日志服务
AppLogHelper.AddLogServices(builder.Services);
//注册日志服务
builder.Services.AddScoped<TestLogService>();
创建测试导出日志的页面
D:\Software\gitee\mauiblazorapp\MaBlaApp\Pages\ExportAppLog.razor
@page "/exportapplog" @using System.Diagnostics @inject TestLogService LogService <h3>导出APP日志文件</h3> <button class="btn btn-primary mx-2" @onclick=ExportAppLogFileAsync>导出</button> <p class="m-4" role="status">@Msg</p> @code { private string Msg = ""; protected override async Task OnInitializedAsync() { //创建日志消息 LogService.MakeLogMessage(); } private async Task ExportAppLogFileAsync() { try { //导出APP日志文件 int fileLen = await LogService.ExportAppLogAsync(); Msg = $"{DateTimeOffset.Now}, 导出日志文件大小{fileLen:N0}"; } catch (Exception ex) { // 处理异常 Debug.WriteLine($"导出日志文件失败: {ex.Message}"); } } }
可以运行项目,测试导出APP日志。然后在手机的download目录查看导出的日志文件。

首次运行没有日志服务
[0:] /data/user/0/com.companyname.mablaapp/files/NLog.config文件不存在,NLog日志服务未注册
第二次以后运行有日志服务了
[0:] /data/user/0/com.companyname.mablaapp/files/NLog.config文件存在,NLog日志服务注册成功
查看导出的日志文件,跟NLog.config配置是符合的。
2025-08-24 15:18:35.3640, INFO , MaBlaApp.Data.TestLogService.MakeLogMessage, 测试Info日志,
2025-08-24 15:18:35.3838, WARN , MaBlaApp.Data.TestLogService.MakeLogMessage, 测试Warn日志,
2025-08-24 15:18:35.3908, ERROR, MaBlaApp.Data.TestLogService.MakeLogMessage, 测试Error日志,
2025-08-24 15:18:35.3957, FATAL, MaBlaApp.Data.TestLogService.MakeLogMessage, 测试Critical日志,
遗留问题
安装APP后首次运行没有NLog.config文件,无法注册NLog日志服务;第二次以后运行就可以了。因为从资源目录复制文件到APP数据目录,采用了异步函数。如果想要解决这个问题,也是可以的,比如采用读写文件的同步函数。不建议调用异步函数的GetAwaiter().GetResult(),有死锁风险。
DEMO代码地址:https://gitee.com/woodsun/mauiblazorapp
浙公网安备 33010602011771号