I am a bit surprised this question hasn't been asked more before. The only relevant topic that I could find is this one.
My tenant strategy on database level is shared database / separate schemas. I want logging to be on tenant level, so each tenant will have its own log table (= I am using a AdoNetAppender to log to the database).
Basically what I need is to resolve the tenant for every HTTP Request that comes into my ASP.NET MVC / ASP.NET Web API application (both are hosted in the same project) and then order log4net to log to the tenant's database schema. I'm able to do both but I am starting to wonder if log4net was designed to support this scenario.
The key concept in this scenario is the HTTP request: every time log4net is called, it should uniquely set the schema without affecting the other users on other threads. But I'm not sure log4net is able to handle this. For the moment, I've gotten this far to achieve this:
public class MultiTenantAdoNetAppender : AdoNetAppender
{
protected override void Append(LoggingEvent loggingEvent)
{
// Code to retrieve the tenant from the context omitted
string tenant = "";
this.CommandText = this.CommandText.Replace("[dbo]", string.Format("[{0}]", tenant));
base.Append(loggingEvent);
}
}
This works but I am fairly sure this will have some unwanted consequences. All requests go to the same logger via a singleton class (see code below). I am not familiar enough with the log4net's internal architecture to know if this is a feasible approach to ensure each request gets served independently from other tenants at the same. So in the code sample above, what happens if two users execute the this.CommandText = ... at the same time?
public class Singleton
{
private Singleton()
{
if (this.Logger == null)
{
this.Logger = new Logger("Trace Logger");
}
}
public static Singleton Instance()
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
public ILogger Logger { get; set; }
}
The ILogger interface is a custom wrapper around the log4net assembly:
private readonly ILog Log;
public Logger()
{
this.Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
}
With this approach I think the tenants will end up writing logs in each others schemas, resulting in logs from tenant A in the database schema of tenant B, etc. Are my suspicions correct about this?
Before I go write a custom logging component, are there any alternatives for multi tenant logging or does log4net provide a way to ensure my requirement?