Merge pull request #1727 from rmcrackan/rmcrackan/log-init-db

Log initial db state, esp to capture books in trash
This commit is contained in:
rmcrackan
2026-04-09 11:05:36 -04:00
committed by GitHub
4 changed files with 83 additions and 0 deletions

View File

@@ -225,6 +225,7 @@ public static class LibationScaffolding
private static void configureLogging(Configuration config)
{
config.ConfigureLogging();
DbContexts.TryEmitPendingInitialDatabaseStatistics();
// capture most Console.WriteLine() and write to serilog. See below tests for details.
// Some dependencies print helpful info via Console.WriteLine. We'd like to log it.

View File

@@ -1,6 +1,9 @@
using DataLayer;
using LibationFileManager;
using Serilog;
using System;
using System.Collections.Generic;
using System.Threading;
namespace ApplicationServices;
@@ -8,6 +11,24 @@ public static class DbContexts
{
private static bool _sqliteDbValidated;
private static readonly object _initialDatabaseStatisticsCaptureLock = new();
/// <summary>
/// True after initial DB statistics were read and either written to Serilog or stored for <see cref="TryEmitPendingInitialDatabaseStatistics"/>.
/// False if capture has not run yet or the last attempt threw (a later <see cref="GetContext"/> may retry).
/// </summary>
private static bool _initialDatabaseStatisticsCaptured;
/// <summary>Shape of the initial DB statistics log event; edit here only when changing what is logged.</summary>
private sealed class InitialDatabaseStatistics
{
public required int LibraryBooksNotInTrash { get; init; }
public required int LibraryBooksInTrash { get; init; }
public required int BookRecords { get; init; }
}
private static InitialDatabaseStatistics? _pendingInitialDbStats;
/// <summary>Use for fully functional context, incl. SaveChanges(). For query-only, use the other method</summary>
public static LibationContext GetContext()
{
@@ -25,9 +46,59 @@ public static class DbContexts
_sqliteDbValidated = true;
}
TryCaptureInitialDatabaseStatistics(context);
return context;
}
private static void TryCaptureInitialDatabaseStatistics(LibationContext context)
{
lock (_initialDatabaseStatisticsCaptureLock)
{
if (_initialDatabaseStatisticsCaptured)
return;
try
{
var (notInTrash, inTrash) = context.GetLibraryBookCountsByTrashFlag();
var bookRecords = context.GetBookCount();
var stats = new InitialDatabaseStatistics
{
LibraryBooksNotInTrash = notInTrash,
LibraryBooksInTrash = inTrash,
BookRecords = bookRecords,
};
if (Configuration.Instance.SerilogInitialized)
LogInitialDatabaseStatistics(stats);
else
_pendingInitialDbStats = stats;
_initialDatabaseStatisticsCaptured = true;
}
catch (Exception ex)
{
if (Configuration.Instance.SerilogInitialized)
Log.Warning(ex, "Could not capture initial database statistics");
}
}
}
/// <summary>
/// Writes initial DB statistics that were captured before Serilog was configured (e.g. WinForms early library load).
/// Call once after <see cref="Configuration.ConfigureLogging"/>.
/// </summary>
public static void TryEmitPendingInitialDatabaseStatistics()
{
var pending = Interlocked.Exchange(ref _pendingInitialDbStats, null);
if (pending is not null)
LogInitialDatabaseStatistics(pending);
}
private static void LogInitialDatabaseStatistics(InitialDatabaseStatistics stats) =>
Log.Logger.Information("Initial database statistics. {@DbStats}", stats);
/// <summary>Use for full library querying. No lazy loading</summary>
public static List<LibraryBook> GetLibrary_Flat_NoTracking(bool includeParents = false)
{

View File

@@ -16,6 +16,10 @@ public static class BookQueries
.AsNoTrackingWithIdentityResolution()
.GetBook(productId);
/// <summary>Total rows in <see cref="LibationContext.Books"/> (no related entities loaded).</summary>
public static int GetBookCount(this LibationContext context)
=> context.Books.AsNoTracking().Count();
public static Book? GetBook(this IQueryable<Book> books, string productId)
=> books
.GetBooks()

View File

@@ -62,6 +62,13 @@ public static class LibraryBookQueries
.Where(lb => lb.IsDeleted || lb.Book.ContentType == ContentType.Parent)
.getLibrary()
.ToList();
/// <summary>Counts <see cref="LibraryBook"/> rows by <see cref="LibraryBook.IsDeleted"/> (no related entities loaded).</summary>
public (int NotInTrash, int InTrash) GetLibraryBookCountsByTrashFlag()
{
var q = context.LibraryBooks.AsNoTracking();
return (q.Count(lb => !lb.IsDeleted), q.Count(lb => lb.IsDeleted));
}
}
extension(IQueryable<LibraryBook> library)