using Microsoft.Extensions.Logging; using Serilog; using Serilog.Core; using Serilog.Events; using Serilog.Formatting; using Serilog.Formatting.Display; namespace NexusReader.Maui.Infrastructure.Logging; public static class SerilogConfiguration { private const string OutputTemplate = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [ThreadId: {ThreadId}] [{SourceContext}] {Message:lj}{NewLine}{Exception}"; public static MauiAppBuilder RegisterLogging(this MauiAppBuilder builder) { // 1. Ensure logs directory exists in secure sandbox var logDir = Path.Combine(Microsoft.Maui.Storage.FileSystem.AppDataDirectory, "logs"); if (!Directory.Exists(logDir)) { Directory.CreateDirectory(logDir); } var logPath = Path.Combine(logDir, "log-.txt"); // 2. Inject sandboxed log path dynamically into configuration provider builder.Configuration["Serilog:WriteTo:0:Args:configure:0:Args:path"] = logPath; // 3. Configure Serilog Logger Configuration using App Configuration settings var loggerConfig = new LoggerConfiguration() .ReadFrom.Configuration(builder.Configuration) .Enrich.With(new ThreadIdEnricher()); // 4. Platform-specific and environment-specific sinks #if ANDROID // Direct Native Android Logcat Sink (JNI bindings for native diagnostics) loggerConfig.WriteTo.Sink( new AndroidLogcatSink(new MessageTemplateTextFormatter(OutputTemplate, null)), restrictedToMinimumLevel: LogEventLevel.Debug); #endif // 5. Initialize the static Serilog Log Log.Logger = loggerConfig.CreateLogger(); // 6. Connect Serilog to Microsoft.Extensions.Logging builder.Logging.ClearProviders(); builder.Logging.AddSerilog(dispose: true); return builder; } } /// /// A custom self-contained thread enricher to avoid unnecessary NuGet packages. /// internal sealed class ThreadIdEnricher : ILogEventEnricher { public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ThreadId", Environment.CurrentManagedThreadId)); } } #if ANDROID /// /// A high-performance, direct Android Logcat Sink utilizing native Android APIs. /// internal sealed class AndroidLogcatSink : ILogEventSink { private readonly ITextFormatter _formatter; private const string Tag = "NexusReader"; public AndroidLogcatSink(ITextFormatter formatter) { _formatter = formatter ?? throw new ArgumentNullException(nameof(formatter)); } public void Emit(LogEvent logEvent) { using var writer = new StringWriter(); _formatter.Format(logEvent, writer); var message = writer.ToString().Trim(); switch (logEvent.Level) { case LogEventLevel.Verbose: Android.Util.Log.Verbose(Tag, message); break; case LogEventLevel.Debug: Android.Util.Log.Debug(Tag, message); break; case LogEventLevel.Information: Android.Util.Log.Info(Tag, message); break; case LogEventLevel.Warning: Android.Util.Log.Warn(Tag, message); break; case LogEventLevel.Error: Android.Util.Log.Error(Tag, message); break; case LogEventLevel.Fatal: Android.Util.Log.Wtf(Tag, message); break; } } } #endif