making (xml) Comments
6569055a0a0427de5593fe0ef22ceffa93945815
11 files changed
src/VanDerHeijden.Logging.File/FileLogWriter.cssrc/VanDerHeijden.Logging.File/FileLoggingBuilderExtensions.cssrc/VanDerHeijden.Logging.MongoDb/MongoDbLogWriter.cssrc/VanDerHeijden.Logging.MongoDb/MongoDbLoggingBuilderExtensions.cssrc/VanDerHeijden.Logging.Redis/RedisLogEntry.cssrc/VanDerHeijden.Logging.Redis/RedisLogWriter.cssrc/VanDerHeijden.Logging.Redis/RedisLoggingBuilderExtensions.cssrc/VanDerHeijden.Logging.Sql/SqlLogEntry.cssrc/VanDerHeijden.Logging.Sql/SqlLogWriter.cssrc/VanDerHeijden.Logging.Sql/SqlLoggingBuilderExtensions.cssrc/VanDerHeijden.Logging/BatchedLogger.cs
diff --git a/src/VanDerHeijden.Logging.File/FileLogWriter.cs b/src/VanDerHeijden.Logging.File/FileLogWriter.cs
index fe32379..ea8b964 100644
--- a/src/VanDerHeijden.Logging.File/FileLogWriter.cs
+++ b/src/VanDerHeijden.Logging.File/FileLogWriter.cs
@@ -1,10 +1,25 @@
namespace VanDerHeijden.Logging.File;
+/// <summary>
+/// Writes batches of log messages to a daily rotating text file.
+/// A new file is opened automatically whenever the calendar date changes.
+/// </summary>
+/// <param name="logDirectory">
+/// Directory in which log files are created. Defaults to <c>"Logs"</c>.
+/// Files follow the naming pattern <c>log-yyyyMMdd.txt</c>.
+/// </param>
public sealed class FileLogWriter(string logDirectory = "Logs") : IBatchedLogWriter<string>
{
private StreamWriter? writer;
private DateTime currentDate = DateTime.MinValue;
+ /// <summary>
+ /// Appends all messages in the batch to the current day's log file.
+ /// If the date has changed since the last write, the previous file is closed
+ /// and a new one is opened.
+ /// </summary>
+ /// <param name="messages">The pre-formatted log lines to write.</param>
+ /// <param name="ct">A token that can cancel the operation.</param>
public async Task WriteBatchAsync(List<string> messages, CancellationToken ct)
{
var today = DateTime.Today;
@@ -25,6 +40,9 @@ public sealed class FileLogWriter(string logDirectory = "Logs") : IBatchedLogWri
await writer.FlushAsync(ct);
}
+ /// <summary>
+ /// Flushes and closes the current log file, releasing all file handles.
+ /// </summary>
public async ValueTask DisposeAsync()
{
if (writer == null) return;
diff --git a/src/VanDerHeijden.Logging.File/FileLoggingBuilderExtensions.cs b/src/VanDerHeijden.Logging.File/FileLoggingBuilderExtensions.cs
index cf7a71c..26bf378 100644
--- a/src/VanDerHeijden.Logging.File/FileLoggingBuilderExtensions.cs
+++ b/src/VanDerHeijden.Logging.File/FileLoggingBuilderExtensions.cs
@@ -4,8 +4,22 @@ using System.Threading.Channels;
namespace VanDerHeijden.Logging.File;
+/// <summary>
+/// Extension methods for registering file-based logging via <see cref="ILoggingBuilder"/>.
+/// </summary>
public static class FileLoggingBuilderExtensions
{
+ /// <summary>
+ /// Adds a file logger that writes log messages to daily rotating text files inside
+ /// <paramref name="logDirectory"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="ILoggingBuilder"/> to configure.</param>
+ /// <param name="logDirectory">
+ /// Path to the directory where log files are written.
+ /// The directory is created automatically if it does not exist.
+ /// Defaults to <c>"Logs"</c>.
+ /// </param>
+ /// <returns>The <paramref name="builder"/> so that additional calls can be chained.</returns>
public static ILoggingBuilder AddFileLogger(this ILoggingBuilder builder, string logDirectory = "Logs")
{
builder.Services.AddSingleton<ILoggerProvider>(_ =>
diff --git a/src/VanDerHeijden.Logging.MongoDb/MongoDbLogWriter.cs b/src/VanDerHeijden.Logging.MongoDb/MongoDbLogWriter.cs
index 0f6bab7..8305486 100644
--- a/src/VanDerHeijden.Logging.MongoDb/MongoDbLogWriter.cs
+++ b/src/VanDerHeijden.Logging.MongoDb/MongoDbLogWriter.cs
@@ -4,22 +4,46 @@ using MongoDB.Driver;
namespace VanDerHeijden.Logging.MongoDb;
+/// <summary>
+/// Represents a single log entry stored in MongoDB.
+/// </summary>
public class LogEntry
{
+ /// <summary>Gets or sets the MongoDB document identifier.</summary>
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; } = ObjectId.GenerateNewId().ToString();
+
+ /// <summary>Gets or sets the UTC timestamp when the log entry was created.</summary>
public DateTime Timestamp { get; set; }
+
+ /// <summary>Gets or sets the log level (e.g. <c>"Information"</c>, <c>"Error"</c>).</summary>
public string Level { get; set; } = string.Empty;
+
+ /// <summary>Gets or sets the logger category name.</summary>
public string Category { get; set; } = string.Empty;
+
+ /// <summary>Gets or sets the formatted log message.</summary>
public string Message { get; set; } = string.Empty;
+
+ /// <summary>Gets or sets the string representation of an associated exception, or <see langword="null"/> if none.</summary>
public string? Exception { get; set; }
}
+/// <summary>
+/// Writes batches of <see cref="LogEntry"/> documents to a MongoDB collection using <c>InsertManyAsync</c>.
+/// </summary>
+/// <param name="collection">The MongoDB collection that receives log entries.</param>
public sealed class MongoDbLogWriter(IMongoCollection<LogEntry> collection) : IBatchedLogWriter<LogEntry>
{
+ /// <summary>
+ /// Inserts all entries in the batch into the MongoDB collection.
+ /// </summary>
+ /// <param name="entries">The log entries to insert.</param>
+ /// <param name="ct">A token that can cancel the operation.</param>
public async Task WriteBatchAsync(List<LogEntry> entries, CancellationToken ct) =>
await collection.InsertManyAsync(entries, cancellationToken: ct);
+ /// <inheritdoc/>
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}
diff --git a/src/VanDerHeijden.Logging.MongoDb/MongoDbLoggingBuilderExtensions.cs b/src/VanDerHeijden.Logging.MongoDb/MongoDbLoggingBuilderExtensions.cs
index 8d127d1..bccb7f0 100644
--- a/src/VanDerHeijden.Logging.MongoDb/MongoDbLoggingBuilderExtensions.cs
+++ b/src/VanDerHeijden.Logging.MongoDb/MongoDbLoggingBuilderExtensions.cs
@@ -5,8 +5,17 @@ using System.Threading.Channels;
namespace VanDerHeijden.Logging.MongoDb;
+/// <summary>
+/// Extension methods for registering MongoDB-based logging via <see cref="ILoggingBuilder"/>.
+/// </summary>
public static class MongoDbLoggingBuilderExtensions
{
+ /// <summary>
+ /// Adds a MongoDB logger that inserts log entries into the specified collection in batches.
+ /// </summary>
+ /// <param name="builder">The <see cref="ILoggingBuilder"/> to configure.</param>
+ /// <param name="collection">The MongoDB collection that will receive <see cref="LogEntry"/> documents.</param>
+ /// <returns>The <paramref name="builder"/> so that additional calls can be chained.</returns>
public static ILoggingBuilder AddMongoDbLogger(this ILoggingBuilder builder, IMongoCollection<LogEntry> collection)
{
builder.Services.AddSingleton<ILoggerProvider>(_ =>
diff --git a/src/VanDerHeijden.Logging.Redis/RedisLogEntry.cs b/src/VanDerHeijden.Logging.Redis/RedisLogEntry.cs
index d6b6ef9..63729b3 100644
--- a/src/VanDerHeijden.Logging.Redis/RedisLogEntry.cs
+++ b/src/VanDerHeijden.Logging.Redis/RedisLogEntry.cs
@@ -1,10 +1,22 @@
namespace VanDerHeijden.Logging.Redis;
+/// <summary>
+/// Represents a single log entry serialized as JSON and pushed to a Redis list.
+/// </summary>
public class RedisLogEntry
{
+ /// <summary>Gets or sets the UTC timestamp when the log entry was created.</summary>
public DateTime Timestamp { get; set; }
+
+ /// <summary>Gets or sets the log level (e.g. <c>"Information"</c>, <c>"Error"</c>).</summary>
public string Level { get; set; } = string.Empty;
+
+ /// <summary>Gets or sets the logger category name.</summary>
public string Category { get; set; } = string.Empty;
+
+ /// <summary>Gets or sets the formatted log message.</summary>
public string Message { get; set; } = string.Empty;
+
+ /// <summary>Gets or sets the string representation of an associated exception, or <see langword="null"/> if none.</summary>
public string? Exception { get; set; }
}
diff --git a/src/VanDerHeijden.Logging.Redis/RedisLogWriter.cs b/src/VanDerHeijden.Logging.Redis/RedisLogWriter.cs
index af8cf6f..cb23b5a 100644
--- a/src/VanDerHeijden.Logging.Redis/RedisLogWriter.cs
+++ b/src/VanDerHeijden.Logging.Redis/RedisLogWriter.cs
@@ -19,6 +19,12 @@ public sealed class RedisLogWriter(
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
+ /// <summary>
+ /// Serializes all entries as JSON and appends them to the Redis list in a single <c>RPUSH</c> command.
+ /// If a TTL is configured, <c>EXPIREAT</c> is applied to the key after each write.
+ /// </summary>
+ /// <param name="entries">The log entries to push.</param>
+ /// <param name="ct">A token that can cancel the operation.</param>
public async Task WriteBatchAsync(List<RedisLogEntry> entries, CancellationToken ct)
{
var values = entries
@@ -31,5 +37,6 @@ public sealed class RedisLogWriter(
await database.KeyExpireAsync(listKey, ttl.Value);
}
+ /// <inheritdoc/>
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}
diff --git a/src/VanDerHeijden.Logging.Redis/RedisLoggingBuilderExtensions.cs b/src/VanDerHeijden.Logging.Redis/RedisLoggingBuilderExtensions.cs
index 896c5a7..d01744f 100644
--- a/src/VanDerHeijden.Logging.Redis/RedisLoggingBuilderExtensions.cs
+++ b/src/VanDerHeijden.Logging.Redis/RedisLoggingBuilderExtensions.cs
@@ -5,8 +5,22 @@ using System.Threading.Channels;
namespace VanDerHeijden.Logging.Redis;
+/// <summary>
+/// Extension methods for registering Redis-based logging via <see cref="ILoggingBuilder"/>.
+/// </summary>
public static class RedisLoggingBuilderExtensions
{
+ /// <summary>
+ /// Adds a Redis logger that pushes log entries as JSON to a Redis list using <c>RPUSH</c>.
+ /// </summary>
+ /// <param name="builder">The <see cref="ILoggingBuilder"/> to configure.</param>
+ /// <param name="database">The Redis database instance used for all write operations.</param>
+ /// <param name="listKey">The Redis key of the list that receives log entries. Defaults to <c>"logs"</c>.</param>
+ /// <param name="ttl">
+ /// Optional time-to-live applied to <paramref name="listKey"/> after each batch write.
+ /// When <see langword="null"/> (the default) the key never expires.
+ /// </param>
+ /// <returns>The <paramref name="builder"/> so that additional calls can be chained.</returns>
public static ILoggingBuilder AddRedisLogger(
this ILoggingBuilder builder,
IDatabase database,
diff --git a/src/VanDerHeijden.Logging.Sql/SqlLogEntry.cs b/src/VanDerHeijden.Logging.Sql/SqlLogEntry.cs
index 2f45d65..b9bb126 100644
--- a/src/VanDerHeijden.Logging.Sql/SqlLogEntry.cs
+++ b/src/VanDerHeijden.Logging.Sql/SqlLogEntry.cs
@@ -1,10 +1,22 @@
namespace VanDerHeijden.Logging.Sql;
+/// <summary>
+/// Represents a single log entry written to a SQL Server table via bulk copy.
+/// </summary>
public class SqlLogEntry
{
+ /// <summary>Gets or sets the UTC timestamp when the log entry was created.</summary>
public DateTime Timestamp { get; set; }
+
+ /// <summary>Gets or sets the log level (e.g. <c>"Information"</c>, <c>"Error"</c>).</summary>
public string Level { get; set; } = string.Empty;
+
+ /// <summary>Gets or sets the logger category name.</summary>
public string Category { get; set; } = string.Empty;
+
+ /// <summary>Gets or sets the formatted log message.</summary>
public string Message { get; set; } = string.Empty;
+
+ /// <summary>Gets or sets the string representation of an associated exception, or <see langword="null"/> if none.</summary>
public string? Exception { get; set; }
}
diff --git a/src/VanDerHeijden.Logging.Sql/SqlLogWriter.cs b/src/VanDerHeijden.Logging.Sql/SqlLogWriter.cs
index 0e5a7e9..0144143 100644
--- a/src/VanDerHeijden.Logging.Sql/SqlLogWriter.cs
+++ b/src/VanDerHeijden.Logging.Sql/SqlLogWriter.cs
@@ -16,6 +16,11 @@ namespace VanDerHeijden.Logging.Sql;
/// </summary>
public sealed class SqlLogWriter(string connectionString, string tableName = "Logs") : IBatchedLogWriter<SqlLogEntry>
{
+ /// <summary>
+ /// Bulk-inserts all entries into the configured SQL Server table using <see cref="SqlBulkCopy"/>.
+ /// </summary>
+ /// <param name="entries">The log entries to insert.</param>
+ /// <param name="ct">A token that can cancel the operation.</param>
public async Task WriteBatchAsync(List<SqlLogEntry> entries, CancellationToken ct)
{
await using var connection = new SqlConnection(connectionString);
@@ -37,6 +42,7 @@ public sealed class SqlLogWriter(string connectionString, string tableName = "Lo
await bulkCopy.WriteToServerAsync(table, ct);
}
+ /// <inheritdoc/>
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
private static System.Data.DataTable ToDataTable(List<SqlLogEntry> entries)
diff --git a/src/VanDerHeijden.Logging.Sql/SqlLoggingBuilderExtensions.cs b/src/VanDerHeijden.Logging.Sql/SqlLoggingBuilderExtensions.cs
index 8c2966f..d3e6889 100644
--- a/src/VanDerHeijden.Logging.Sql/SqlLoggingBuilderExtensions.cs
+++ b/src/VanDerHeijden.Logging.Sql/SqlLoggingBuilderExtensions.cs
@@ -4,8 +4,21 @@ using System.Threading.Channels;
namespace VanDerHeijden.Logging.Sql;
+/// <summary>
+/// Extension methods for registering SQL Server-based logging via <see cref="ILoggingBuilder"/>.
+/// </summary>
public static class SqlLoggingBuilderExtensions
{
+ /// <summary>
+ /// Adds a SQL Server logger that bulk-inserts log entries into the specified table using <c>SqlBulkCopy</c>.
+ /// </summary>
+ /// <param name="builder">The <see cref="ILoggingBuilder"/> to configure.</param>
+ /// <param name="connectionString">The SQL Server connection string.</param>
+ /// <param name="tableName">
+ /// The destination table name. Defaults to <c>"Logs"</c>.
+ /// See <see cref="SqlLogWriter"/> for the expected schema.
+ /// </param>
+ /// <returns>The <paramref name="builder"/> so that additional calls can be chained.</returns>
public static ILoggingBuilder AddSqlLogger(
this ILoggingBuilder builder,
string connectionString,
diff --git a/src/VanDerHeijden.Logging/BatchedLogger.cs b/src/VanDerHeijden.Logging/BatchedLogger.cs
index c521dd1..c4362d3 100644
--- a/src/VanDerHeijden.Logging/BatchedLogger.cs
+++ b/src/VanDerHeijden.Logging/BatchedLogger.cs
@@ -1,13 +1,28 @@
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging;
using System.Threading.Channels;
namespace VanDerHeijden.Logging;
+/// <summary>
+/// Defines a writer that receives a batch of log entries and persists them to a backing store.
+/// </summary>
+/// <typeparam name="T">The type of log entry.</typeparam>
public interface IBatchedLogWriter<T> : IAsyncDisposable
{
+ /// <summary>
+ /// Writes a batch of log entries to the backing store.
+ /// </summary>
+ /// <param name="entries">The entries to write.</param>
+ /// <param name="ct">A token that can cancel the operation.</param>
Task WriteBatchAsync(List<T> entries, CancellationToken ct);
}
+/// <summary>
+/// Buffers log entries in a bounded channel and flushes them in batches via an <see cref="IBatchedLogWriter{T}"/>.
+/// Entries are flushed when the batch reaches the configured batch size or after
+/// the configured idle timeout, whichever comes first.
+/// </summary>
+/// <typeparam name="T">The type of log entry.</typeparam>
public sealed class BatchedLogger<T> : IDisposable
{
private readonly Channel<T> channel;
@@ -17,10 +32,17 @@ public sealed class BatchedLogger<T> : IDisposable
private readonly int batchSize;
private readonly int maxIdleMs;
+ /// <summary>
+ /// Initializes a new <see cref="BatchedLogger{T}"/>.
+ /// </summary>
+ /// <param name="writer">The writer that persists batches.</param>
+ /// <param name="batchSize">Maximum number of entries per batch before an immediate flush is triggered.</param>
+ /// <param name="maxIdleMs">Maximum time in milliseconds to wait before flushing a non-full batch.</param>
+ /// <param name="fullMode">Behaviour when the internal channel is full.</param>
public BatchedLogger(
- IBatchedLogWriter<T> writer,
- int batchSize = 200,
- int maxIdleMs = 4000,
+ IBatchedLogWriter<T> writer,
+ int batchSize = 200,
+ int maxIdleMs = 4000,
BoundedChannelFullMode fullMode = BoundedChannelFullMode.Wait)
{
this.writer = writer;
@@ -37,6 +59,11 @@ public sealed class BatchedLogger<T> : IDisposable
consumerTask = Task.Run(() => ConsumeAsync(cts.Token));
}
+ /// <summary>
+ /// Enqueues a log entry. If the channel is full and the <c>fullMode</c> is
+ /// <see cref="BoundedChannelFullMode.Wait"/>, the call blocks until space is available.
+ /// </summary>
+ /// <param name="entry">The entry to enqueue.</param>
public void Write(T entry) => channel.Writer.TryWrite(entry);
private async Task ConsumeAsync(CancellationToken ct)
@@ -101,6 +128,10 @@ public sealed class BatchedLogger<T> : IDisposable
}
}
+ /// <summary>
+ /// Signals the channel as complete, waits up to 10 seconds for the consumer to flush remaining
+ /// entries, then disposes resources.
+ /// </summary>
public void Dispose()
{
channel.Writer.Complete();
@@ -110,12 +141,28 @@ public sealed class BatchedLogger<T> : IDisposable
}
}
-// Generic provider + category logger, reusable for any entry type T
+/// <summary>
+/// An <see cref="ILoggerProvider"/> that creates <see cref="ILogger"/> instances backed by a
+/// shared <see cref="BatchedLogger{T}"/>.
+/// </summary>
+/// <typeparam name="T">The type of log entry produced by <paramref name="entryFactory"/>.</typeparam>
+/// <param name="batchedLogger">The shared batched logger used by all created loggers.</param>
+/// <param name="entryFactory">
+/// A factory that converts a formatted message string and <see cref="LogLevel"/> into a <typeparamref name="T"/> entry.
+/// </param>
public sealed class BatchedLoggerProvider<T>(BatchedLogger<T> batchedLogger, Func<string, LogLevel, T> entryFactory) : ILoggerProvider
{
+ /// <summary>
+ /// Creates an <see cref="ILogger"/> for the given category name.
+ /// </summary>
+ /// <param name="categoryName">The category name for messages produced by the logger.</param>
+ /// <returns>An <see cref="ILogger"/> instance.</returns>
public ILogger CreateLogger(string categoryName) =>
new BatchedCategoryLogger<T>(batchedLogger, categoryName, entryFactory);
+ /// <summary>
+ /// Disposes the underlying <see cref="BatchedLogger{T}"/>, flushing any remaining entries.
+ /// </summary>
public void Dispose() => batchedLogger.Dispose();
}
@@ -129,4 +176,4 @@ internal sealed class BatchedCategoryLogger<T>(BatchedLogger<T> batchedLogger, s
if (!IsEnabled(logLevel)) return;
batchedLogger.Write(entryFactory($"{categoryName}: {formatter(state, exception)}{(exception != null ? $"{Environment.NewLine}{exception}" : "")}", logLevel));
}
-}
\ No newline at end of file
+}