同一个数据库上有两个不同实例的 Log4net

Log4net with two different instances on same database

不久前我使用 log4net 设置了日志记录,只是没有想到特定的场景。

目前我有一个全局配置文件。有一个 RollingFileAppender 和一个 AdonetAppender。

当数据库中只有一个程序实例 运行ning 时,一切都很好。 有时,两个略有不同的实例可能在同一个数据库上 运行。附加程序记录来自两个实例的消息,但通常(在公共代码中)无法分辨消息来自哪个实例。当然,我可以将实例名称添加到消息中或其他任何内容,但这似乎是最后的选择。

我的大脑像一团雾。我认为解决方案应该很简单,但我想不出什么是最好的。

我可以创建另一个 appender,并且 "switch" 在 运行 期间根据实例名称使用哪个 appender?

这是配置文件:

<?xml version="1.0" encoding="utf-8" ?>
<log4net debug="true">

  <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
    <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
    <file value="D:\Medupi logs\CalcEngineLog.txt"/>
    <appendToFile value="true"/>
    <rollingStyle value="Size"/>
    <maxSizeRollBackups value="2"/>
    <maximumFileSize value="4MB"/>
    <staticLogFileName value="true"/>
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date [%thread] %level %logger %location - %message%newline%exception"/>
    </layout>
    <filter type="log4net.Filter.LevelRangeFilter">
      <levelMin value="DEBUG"/>
      <levelMax value="FATAL"/>
    </filter>
    <filter type="log4net.Filter.DenyAllFilter" />
  </appender>  

  <appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
   <!-- <threshold value="Warn" /> -->
    <bufferSize value="1" />
    <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    <connectionString value="" />
    <commandText value="INSERT INTO [Calculation Engine Log] ([Date],[Thread],[Level],[Logger],[Location],[Message],[Exception]) VALUES (@log_date, @thread, @log_level, @logger, @location, @message, @exception)" />
    <parameter>
      <parameterName value="@log_date" />
      <dbType value="DateTime" />
      <layout type="log4net.Layout.RawTimeStampLayout" />
    </parameter>
    <parameter>
      <parameterName value="@thread" />
      <dbType value="String" />
      <size value="255" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%thread" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@log_level" />
      <dbType value="String" />
      <size value="50" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%level" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@logger" />
      <dbType value="String" />
      <size value="255" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%logger" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@location" />
      <dbType value="String" />
      <size value="255" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%location" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@message" />
      <dbType value="String" />
      <size value="4000" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%message" />
      </layout>
    </parameter>
    <parameter>
      <parameterName value="@exception" />
      <dbType value="String" />
      <size value="2000" />
      <layout type="log4net.Layout.ExceptionLayout" />
    </parameter>
  </appender>  

  <root>
    <level value="Debug"/>
    <appender-ref ref="AdoNetAppender"/>
    <appender-ref ref="RollingFileAppender"/> 
    <appender-ref ref="DebugAppender"/>
  </root>

</log4net>

在启动项目中是这样配置的:

private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);  


static void Main()
    {
        if (!log4net.LogManager.GetRepository().Configured)
        {
            log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo("C:\Program Files\Symplexity\bin\Log4NetSettingsGlobal.xml"));
        }
        ...
    }

附加信息

在生产中,启动了一个 Windows 服务,这会检查一个配置文件,其实例必须在集群上 运行。

每个实例都有一个单独的进程:

foreach (var cluster in clusters) 
        {
        ProcessStartInfo startInfo = new ProcessStartInfo
            {
                CreateNoWindow = true,
                FileName = processFileName,

                WindowStyle = ProcessWindowStyle.Hidden,
                Arguments = string.Format("\"{0}\" \"{1}\"", cluster.Name, _ConfigPath)
            };

            _ClusterProcesses.Add(Process.Start(startInfo));               
        }

我假设我可以将 appIdentifier(根据评论)添加到 startInfo?

Arguments

首先确定 2 个应用程序实例。
最简单的方法是使用唯一的命令行参数启动每个应用程序实例。
通过例如启动第一个实例。 .cmd 文件为 MyApplication.exe instance1,第二个文件为 MyApplication.exe instance2.
从方法 Main.

args 参数中读取此参数值
static void Main(String[] args)
{
    String appIdentifier = args[0];
    // ...
}

现在您有多种选择。

选项 1:两个实例的共享 Log4net 配置

appIdentifier 分配给 Log4net context property
使用的范围取决于您的场景,请参阅 the Log4net documentation.

log4net.GlobalContext.Properties["appIdentifier"] = appIdentifier;

RollingFileAppender

通过 %property{appIdentifier}RollingFileAppenderlayout 中包含此上下文 属性。 每个记录的行现在将包含适当的 appIdentifier 值 'instance1' 或 'instance2'.

<layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="... %property{appIdentifier}  ..."/>
</layout>    

A​​doNetAppender

首先确保您要登录的 table 包含一个用于存储 appIdentifier 值的列, 例如。列 appIdentifier VARCHAR(25).

使用额外参数扩展 AdoNetAppender 的配置以匹配 appIdentifier 属性。

<parameter>
    <parameterName value="@appIdentifier"/>
    <dbType value="String" />
    <size value="25" />
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%property{appIdentifier}" />
    </layout>
</parameter>

用相应的列名和参数扩展 SQL INSERT 语句。

INSERT INTO [Calculation Engine Log] ([Date],[Thread],[Level],[Logger],[Location],[Message],[Exception],[appIdentifier]) 
VALUES (@log_date, @thread, @log_level, @logger, @location, @message, @exception, @appIdentifier)

选项 2:每个实例一个单独的 Log4net 配置文件

这不像选项 1 那样漂亮...但仍然是一个选项。

通过将文件名与 appIdentifier 命令行参数匹配,为每个实例创建一个单独的 xml 配置文件。

log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo("C:\Program Files\Symplexity\bin\" + appIdentifier + '.xml'));

RollingFileAppender

为每个实例配置单独的输出文件路径。

例如1

<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
    ...
    <file value="D:\Medupi logs\Instance1.txt"/>    
    ...
 </appender>

例如2

<file value="D:\Medupi logs\Instance2.txt"/>     

A​​doNetAppender

参见选项 1,了解如何设置列来存储 appIdentifier
SQL INSERT 语句中包含一个常量值作为标识符。

INSERT INTO [Calculation Engine Log] ([Date],[Thread],[Level],[Logger],[Location],[Message],[Exception],[appIdentifier]) 
VALUES (@log_date, @thread, @log_level, @logger, @location, @message, @exception, 'Instance1')

xml 配置文件中为 instance2 执行相同的操作,值为 'Instance2'。


选项 3:单个命名记录器

如果您在整个应用程序中只使用一个 ILog 实例,请使用 appIdentifier.

的值按​​名称检索它

RollingFileAppender%logger 标记和 AdoNetAppender@logger 标记将分别包含值 'Instance1' 或 'Instance2'。

我不喜欢使用单个记录器,但我经常看到这种做法。

private static log4net.ILog log;

static void Main(String args)
{
    String appIdentifier = args[0];

    if (!log4net.LogManager.GetRepository().Configured)
    {
        log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo("C:\Program Files\Symplexity\bin\Log4NetSettingsGlobal.xml"));

        log = log4net.LogManager.GetLogger(appIdentifier); 
    }
    // ...
}

选项 4:RollingFileAppender 的参数化文件名

这只是 RollingFileAppender 的一个替代选项,将其与上述的选项之一结合AdoNetAppender.

按照选项 1 中的说明设置 Log4net context property
可以在 RollingFileAppender.

的输出文件路径中包含这样的 Log4net context properties

这样做将分别生成一个文件 'Instance1.log' 或 'Instance2.log'。
(除了改变文件名,您还可以将其应用于(子)文件夹名称。)

不在多个应用程序实例之间共享相同的输出文件 运行 如果您有或担心锁定 and/or 并发可能是个好主意性能问题。

<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
    ...
    <file type="log4net.Util.PatternString" value="D:\Medupi logs\%property{appIdentifier}.log" /> 
    ...
 </appender>