我有点惊讶之前没有人问过这个问题。我能找到的唯一相关主题是 this一。
我在数据库级别的租户策略是共享数据库/单独模式。我希望日志记录在租户级别,因此每个租户都有自己的日志表(=我正在使用 AdoNetAppender 登录到数据库)。
基本上我需要的是为进入我的 ASP.NET MVC/ASP.NET Web API 应用程序(两者都托管在同一个项目中)的每个 HTTP 请求解析租户,然后命令 log4net 登录到租户的数据库模式。我能够做到这两点,但我开始怀疑 log4net 是否旨在支持这种情况。
这个场景中的关键概念是 HTTP 请求:每次调用 log4net 时,它应该唯一地设置架构,而不影响其他线程上的其他用户。但我不确定 log4net 是否能够处理这个问题。目前,我已经做到了这一点:
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);
}
}
这可行,但我相当确定这会产生一些不良后果。所有请求都通过单例类发送到同一个记录器(请参见下面的代码)。我对 log4net 的内部架构不够熟悉,不知道这是否是确保每个请求同时独立于其他租户服务的可行方法。那么在上面的代码示例中,如果两个用户同时执行 this.CommandText = ... 会发生什么?
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; }
}
ILogger 接口(interface)是 log4net 程序集的自定义包装器:
private readonly ILog Log;
public Logger()
{
this.Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
}
通过这种方法,我认为租户最终会在彼此的模式中写入日志,从而导致租户 A 的日志出现在租户 B 的数据库模式中,等等。我对此的怀疑是否正确?
在我编写自定义日志记录组件之前, Multi-Tenancy 日志记录是否有任何替代方案,或者 log4net 是否提供了一种方法来确保我的要求?
最佳答案
因为 appender 是共享的,所以不要修改 Append 方法。像这样修改内部 AdoNetAppender SendBuffer 方法:
public class MultiTenantAdoNetAppender : AdoNetAppender
{
override protected void SendBuffer(IDbTransaction dbTran, LoggingEvent[] events)
{
// run for all events
foreach (LoggingEvent e in events)
{
// Code to retrieve the tenant from the context omitted
string tenant = "";
using (IDbCommand dbCmd = Connection.CreateCommand())
{
// Set the command string
dbCmd.CommandText = this.CommandText.Replace("[dbo]", string.Format("[{0}]", tenant));
// Set the command type
dbCmd.CommandType = CommandType;
// Send buffer using the prepared command object
if (dbTran != null)
{
dbCmd.Transaction = dbTran;
}
// Set the parameter values
foreach (AdoNetAppenderParameter param in m_parameters)
{
param.Prepare(dbCmd);
param.FormatValue(dbCmd, e);
}
// Execute the query
dbCmd.ExecuteNonQuery();
}
}
}
}
这将为每个 Execute 调用修改 CommandText 属性。为了更快地记录日志,您必须按租户对事件进行分组并为每个租户创建命令并重用准备好的命令精简版:
override protected void SendBuffer(IDbTransaction dbTran, LoggingEvent[] events)
{
// Code to retrieve the tenant from the context omitted
string[] usedTenants = GetTenantsFromEvents(events);
foreach (string tenant in usedTenants)
{
using (IDbCommand dbCmd = Connection.CreateCommand())
{
// Set the command string
dbCmd.CommandText = this.CommandText.Replace("[dbo]", string.Format("[{0}]", tenant));
// Set the command type
dbCmd.CommandType = CommandType;
// Send buffer using the prepared command object
if (dbTran != null)
{
dbCmd.Transaction = dbTran;
}
// prepare the command, which is significantly faster
dbCmd.Prepare();
// run for all events for tenant, code for select omitted
foreach (LoggingEvent e in GetEventsForTenant(events, tenant))
{
// clear parameters that have been set
dbCmd.Parameters.Clear();
// Set the parameter values
foreach (AdoNetAppenderParameter param in m_parameters)
{
param.Prepare(dbCmd);
param.FormatValue(dbCmd, e);
}
// Execute the query
dbCmd.ExecuteNonQuery();
}
}
}
}
关于c# - log4net 中的 Multi-Tenancy 登录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35345978/