Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 91 additions & 8 deletions src/NLog.Mongo/MongoTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,32 @@ public string CollectionName
/// </summary>
public bool IncludeEventProperties { get; set; }

/// <summary>
/// Gets or sets the suffix for the collection name in case of multi tenants application.
/// </summary>
/// <value>
/// The name of the property to use as a suffix.
/// </value>
public string CollectionSuffixProperty
{
get => (_collectionSuffixProperty as SimpleLayout)?.Text;
set => _collectionSuffixProperty = value ?? string.Empty;
}
private Layout _collectionSuffixProperty;

/// <summary>
/// Gets or sets the suffix for the database name in case of multi tenants application.
/// </summary>
/// <value>
/// The name of the property to use as a suffix.
/// </value>
public string DatabaseSuffixProperty
{
get => (_databaseSuffixProperty as SimpleLayout)?.Text;
set => _databaseSuffixProperty = value ?? string.Empty;
}
private Layout _databaseSuffixProperty;

/// <summary>
/// Initializes the target. Can be used by inheriting classes
/// to initialize logging.
Expand Down Expand Up @@ -190,14 +216,30 @@ protected override void Write(IList<AsyncLogEventInfo> 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)
{
Expand All @@ -214,6 +256,32 @@ protected override void Write(IList<AsyncLogEventInfo> 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;
}

/// <summary>
/// Writes logging event to the log target.
/// classes.
Expand All @@ -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)
Expand Down Expand Up @@ -393,14 +462,17 @@ private BsonValue GetValue(MongoField field, LogEventInfo logEvent)
return bsonValue ?? new BsonString(value);
}

private IMongoCollection<BsonDocument> GetCollection(DateTime timestamp)
private IMongoCollection<BsonDocument> 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.");
Expand Down Expand Up @@ -451,6 +523,17 @@ private IMongoCollection<BsonDocument> 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)
Expand Down
26 changes: 26 additions & 0 deletions test/NLog.Mongo.Tests/LoggerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 20 additions & 2 deletions test/NLog.Mongo.Tests/NLog.config
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@
</field>
</target>


<target name="rollingFile"
xsi:type="File"
layout="${longdate} ${threadid:padding=4} ${level:uppercase=true:padding=5} ${logger} ${message} ${exception:format=tostring}"
Expand All @@ -88,6 +87,24 @@
name="console"
layout="${time} ${level:uppercase=true:padding=1:fixedLength=true} ${logger:shortName=true} ${message} ${exception:format=tostring}"/>

<target xsi:type="Mongo"
name="mongoCustomDatabaseWithSuffix"
includeEventProperties="false"
connectionString="mongodb://localhost/Logging"
databaseName="CustomLogging"
collectionName="CustomLog"
cappedCollectionSize="26214400"
databaseSuffixProperty="TenantId">
<field name="TenantId" bsonType="String" layout="${scope-property:TenantId}" />
<field name="Properties" bsonType="Object">
<layout type="JsonLayout" includeAllProperties="true" includeMdlc="true" maxRecursionLimit="10">
<attribute name="ThreadID" layout="${threadid}" encode="false" />
<attribute name="ProcessID" layout="${processid}" encode="false" />
<attribute name="ProcessName" layout="${processname:fullName=false}" />
</layout>
</field>
</target>

</targets>

<rules>
Expand All @@ -97,5 +114,6 @@
<logger name="*" minlevel="Trace" writeTo="mongoCustomJsonProperties" />
<logger name="*" minlevel="Debug" writeTo="console" />
<logger name="*" minlevel="Trace" writeTo="rollingFile" />
<logger name="*" minlevel="Trace" writeTo="mongoCustomDatabaseWithSuffix" />
</rules>
</nlog>
</nlog>