Code · 64 lines · 2702 bytes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
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>Gets or sets the request path (e.g. <c>"/api/users"</c>), or <see langword="null"/> outside an HTTP context.</summary>
	public string? Path { get; set; }

	/// <summary>Gets or sets the HTTP method (e.g. <c>"GET"</c>), or <see langword="null"/> outside an HTTP context.</summary>
	public string? Method { get; set; }

	/// <summary>Gets or sets the client IP address, or <see langword="null"/> outside an HTTP context.</summary>
	public string? ClientIp { get; set; }

	/// <summary>Gets or sets the Referer header value, or <see langword="null"/> outside an HTTP context.</summary>
	public string? Referer { get; set; }

	/// <summary>Gets or sets the User-Agent header value, or <see langword="null"/> outside an HTTP context.</summary>
	public string? UserAgent { 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;
}