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(){//加载APP日志文件内容//[0:] 加载/data/user/0/com.companyname.mablaapp/files/app.log, 长度=xxxstring contents = await FileHelper.LoadTxtFromAppDataAsync(AppLogFileName);// 获取download目录 #if ANDROIDvar downloadFile = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads);string downloadPath = downloadFile.AbsolutePath; #elsestring downloadPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); #endifstring downloadFilePath = Path.Combine(downloadPath, AppLogFileName);await File.WriteAllTextAsync(downloadFilePath, contents);//[0:] 导出APP日志文件到/storage/emulated/0/Download/app.log, 长度=xxxDebug.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