From e1dc3ba9b62b817849c34c0d112025596dea8c98 Mon Sep 17 00:00:00 2001 From: Gianni Bonicolini Date: Thu, 26 Jun 2025 10:15:11 +0200 Subject: [PATCH] Added supporto for suffix on database and/or collection based on properties --- src/NLog.Mongo/MongoTarget.cs | 99 ++++++++++++++++++++++++++--- test/NLog.Mongo.Tests/LoggerTest.cs | 26 ++++++++ test/NLog.Mongo.Tests/NLog.config | 22 ++++++- 3 files changed, 137 insertions(+), 10 deletions(-) diff --git a/src/NLog.Mongo/MongoTarget.cs b/src/NLog.Mongo/MongoTarget.cs index 452f973..2d1d619 100644 --- a/src/NLog.Mongo/MongoTarget.cs +++ b/src/NLog.Mongo/MongoTarget.cs @@ -159,6 +159,32 @@ public string CollectionName /// public bool IncludeEventProperties { get; set; } + /// + /// Gets or sets the suffix for the collection name in case of multi tenants application. + /// + /// + /// The name of the property to use as a suffix. + /// + public string CollectionSuffixProperty + { + get => (_collectionSuffixProperty as SimpleLayout)?.Text; + set => _collectionSuffixProperty = value ?? string.Empty; + } + private Layout _collectionSuffixProperty; + + /// + /// Gets or sets the suffix for the database name in case of multi tenants application. + /// + /// + /// The name of the property to use as a suffix. + /// + public string DatabaseSuffixProperty + { + get => (_databaseSuffixProperty as SimpleLayout)?.Text; + set => _databaseSuffixProperty = value ?? string.Empty; + } + private Layout _databaseSuffixProperty; + /// /// Initializes the target. Can be used by inheriting classes /// to initialize logging. @@ -190,14 +216,30 @@ protected override void Write(IList logEvents) try { if (_createDocumentDelegate == null) + { _createDocumentDelegate = e => CreateDocument(e.LogEvent); + } var documents = logEvents.Select(_createDocumentDelegate); - var collection = GetCollection(logEvents[logEvents.Count - 1].LogEvent.TimeStamp); - collection.InsertMany(documents); - for (int i = 0; i < logEvents.Count; ++i) - logEvents[i].Continuation(null); + var grouped = logEvents + .Zip(documents, (eventInfo, bsonDocument) => (EventInfo: eventInfo, Document: bsonDocument)) + .GroupBy(z => GetSuffixOrDefault(z.Document)); + + foreach (var group in grouped) + { + var suffix = group.Key; + var items = group.ToArray(); + var timestamp = items.Last().EventInfo.LogEvent.TimeStamp; + + var collection = GetCollection(timestamp, suffix); + collection.InsertMany(items.Select(i => i.Document)); + + foreach (var (eventInfo, document) in items) + { + SetEventConfigurationToNull(eventInfo); + } + } } catch (Exception ex) { @@ -214,6 +256,32 @@ protected override void Write(IList logEvents) } } + private static AsyncLogEventInfo SetEventConfigurationToNull(AsyncLogEventInfo e) + { + e.Continuation(null); + return e; + } + + private (string DatabaseSuffix, string CollectionSuffix) GetSuffixOrDefault(BsonDocument document, string defaultValue = "") + { + string databaseSuffix = GetSuffixValueOrDefault(document, defaultValue, DatabaseSuffixProperty); + string collectionSuffix = GetSuffixValueOrDefault(document, defaultValue, CollectionSuffixProperty); + + return (databaseSuffix, collectionSuffix); + } + + private static string GetSuffixValueOrDefault(BsonDocument document, string defaultValue, string suffixPropertyName) + { + var suffixPresent = !string.IsNullOrWhiteSpace(suffixPropertyName); + if (suffixPresent) + { + document.TryGetValue(suffixPropertyName, out var propertyValue); + return propertyValue?.ToString() ?? defaultValue; + } + + return defaultValue; + } + /// /// Writes logging event to the log target. /// classes. @@ -224,7 +292,8 @@ protected override void Write(LogEventInfo logEvent) try { var document = CreateDocument(logEvent); - var collection = GetCollection(logEvent.TimeStamp); + var suffix = GetSuffixOrDefault(document); + var collection = GetCollection(logEvent.TimeStamp, suffix); collection.InsertOne(document); } catch (Exception ex) @@ -393,14 +462,17 @@ private BsonValue GetValue(MongoField field, LogEventInfo logEvent) return bsonValue ?? new BsonString(value); } - private IMongoCollection GetCollection(DateTime timestamp) + private IMongoCollection GetCollection(DateTime timestamp, (string Database, string Collection) suffix) { if (_defaultLogEvent.TimeStamp < timestamp) _defaultLogEvent.TimeStamp = timestamp; + Layout collectionNameWithSuffix = GetCollectionNameWithSuffix(suffix.Collection); + Layout databaseNameWithSuffix = GetDatabaseNameWithSuffix(suffix.Database); + string connectionString = _connectionString != null ? RenderLogEvent(_connectionString, _defaultLogEvent) : string.Empty; - string collectionName = _collectionName != null ? RenderLogEvent(_collectionName, _defaultLogEvent) : string.Empty; - string databaseName = _databaseName != null ? RenderLogEvent(_databaseName, _defaultLogEvent) : string.Empty; + string collectionName = collectionNameWithSuffix != null ? RenderLogEvent(collectionNameWithSuffix, _defaultLogEvent) : string.Empty; + string databaseName = databaseNameWithSuffix != null ? RenderLogEvent(databaseNameWithSuffix, _defaultLogEvent) : string.Empty; if (string.IsNullOrEmpty(connectionString)) throw new NLogConfigurationException("Can not resolve MongoDB ConnectionString. Please make sure the ConnectionString property is set."); @@ -451,6 +523,17 @@ private IMongoCollection GetCollection(DateTime timestamp) }); } + private Layout GetCollectionNameWithSuffix(string suffix) => GetNameWithSuffix(_collectionName, suffix); + + private Layout GetDatabaseNameWithSuffix(string suffix) => GetNameWithSuffix(_databaseName, suffix); + + private static Layout GetNameWithSuffix(Layout layout, string suffix) + => !string.IsNullOrWhiteSpace(suffix) + ? layout != null + ? new SimpleLayout($"{layout}_{suffix}") + : null + : layout; + private static string GetConnectionString(string connectionName) { if (connectionName == null) diff --git a/test/NLog.Mongo.Tests/LoggerTest.cs b/test/NLog.Mongo.Tests/LoggerTest.cs index 322e933..ee78db7 100644 --- a/test/NLog.Mongo.Tests/LoggerTest.cs +++ b/test/NLog.Mongo.Tests/LoggerTest.cs @@ -23,6 +23,32 @@ public void Write() .Property("Test", "Tesing properties") .Log(); + string t = "tenant1"; + // The property name must match the one written in the NLog.config + using (ScopeContext.PushProperty("TenantId", t)) + { + _logger.Trace("Sample trace message, k={0}, l={1}, t={2}", k, l, t); + _logger.Debug("Sample debug message, k={0}, l={1}, t={2}", k, l, t); + _logger.Info("Sample informational message, k={0}, l={1}, t={2}", k, l, t); + _logger.Warn("Sample warning message, k={0}, l={1}, t={2}", k, l, t); + _logger.Error("Sample error message, k={0}, l={1}, t={2}", k, l, t); + _logger.Fatal("Sample fatal error message, k={0}, l={1}, t={2}", k, l, t); + _logger.Log(LogLevel.Info, "Sample fatal error message, k={0}, l={1}, t={2}", k, l, t); + } + + t = "tenant2"; + // The property name must match the one written in the NLog.config + using (ScopeContext.PushProperty("TenantId", t)) + { + _logger.Trace("Sample trace message, k={0}, l={1}, t={2}", k, l, t); + _logger.Debug("Sample debug message, k={0}, l={1}, t={2}", k, l, t); + _logger.Info("Sample informational message, k={0}, l={1}, t={2}", k, l, t); + _logger.Warn("Sample warning message, k={0}, l={1}, t={2}", k, l, t); + _logger.Error("Sample error message, k={0}, l={1}, t={2}", k, l, t); + _logger.Fatal("Sample fatal error message, k={0}, l={1}, t={2}", k, l, t); + _logger.Log(LogLevel.Info, "Sample fatal error message, k={0}, l={1}, t={2}", k, l, t); + } + string path = "blah.txt"; try diff --git a/test/NLog.Mongo.Tests/NLog.config b/test/NLog.Mongo.Tests/NLog.config index f23105d..1016046 100644 --- a/test/NLog.Mongo.Tests/NLog.config +++ b/test/NLog.Mongo.Tests/NLog.config @@ -71,7 +71,6 @@ - + + + + + + + + + + + @@ -97,5 +114,6 @@ + - \ No newline at end of file +