如何使用 log4net 为每个任务登录不同的文件?

How to log into a different file per Task with log4net?

我开发了一个 运行 并行执行多个任务的应用程序。为了使应用程序的日志文件更易于阅读,我希望这些任务中的每一个都通过 log4net 登录到自己的日志文件中。 此外,我还希望将在任务之外记录的所有内容都记录到“主”日志文件中,这样我每个任务都有一个日志文件,还有一个日志文件包含除任务内部记录的内容之外的所有内容。 这是我当前的 log4net 配置 xml:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <configSections>
      <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
   </configSections>
   <log4net>
      <appender name="MainRollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
         <filter type="log4net.Filter.PropertyFilter">
            <key value="LogicName" />
            <regexToMatch value="^(?!Main$).*$" />
            <acceptOnMatch value="false" />
         </filter>
         <param name="File" value="C:\Temp\Test\Main.log" />
         <appendToFile value="true" />
         <rollingStyle value="Size" />
         <maxSizeRollBackups value="100" />
         <maximumFileSize value="10MB" />
         <staticLogFileName value="true" />
         <datePattern value="yyyyMMdd" />
         <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
         <layout type="log4net.Layout.PatternLayout, log4net">
            <conversionPattern value="%date [%thread] %-5level %logger: %message%newline" />
         </layout>
      </appender>
      <appender name="TaskRollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
         <filter type="log4net.Filter.PropertyFilter">
            <key value="LogicName" />
            <regexToMatch value="^(?!Main$).*$" />
         </filter>
         <filter type="log4net.Filter.DenyAllFilter" />
         <file type="log4net.Util.PatternString" value="C:\Temp\Test\%property{LogicName}.log" />
         <appendToFile value="true" />
         <rollingStyle value="Size" />
         <maxSizeRollBackups value="100" />
         <maximumFileSize value="10MB" />
         <datePattern value="yyyyMMdd" />
         <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
         <layout type="log4net.Layout.PatternLayout, log4net">
            <conversionPattern value="%date [%thread] %-5level %logger: %message%newline" />
         </layout>
      </appender>
      <root>
         <level value="ALL" />
         <appender-ref ref="MainRollingLogFileAppender" />
         <appender-ref ref="LogicRollingLogFileAppender" />
      </root>
   </log4net>
</configuration>

创建单个任务时,我立即运行这行代码为该任务设置当前逻辑名称(logicName包含当前任务中执行的逻辑名称):

log4net.LogicalThreadContext.Properties["LogicName"] = logicName;

每个任务都是这样开始的:

Task.Run(async () =>
{
    await executeLogic(); // The first line in this function sets the logicName in the LogicalThreadContext
}, cancellationToken);

遗憾的是,所有这些所做的只是创建一个 Main.log 文件,该文件在应用程序 运行ning 期间保持为空,以及一个包含所有应用程序日志的 (null).log 文件。 理想情况下,我希望在 LogicalThreadContext 中没有指定 LogicName 的所有内容都自动记录在主日志文件中。 我的 log4net 配置必须如何才能使这项工作正常进行?

由于每个 RollingFileAppender 都有一个文件句柄,因此不可能使用相同的附加程序动态创建新文件。在这种情况下您不能使用配置文件 - 每次创建新的 Thread.

时都必须以编程方式创建新的附加程序

这是一个如何实现该目标的示例。来自 Main 的所有日志都将发送到 Main.log,每个线程都有自己的日志文件。

using log4net;
using log4net.Appender;
using log4net.Config;
using log4net.Filter;
using log4net.Layout;
using System.Threading;
using System.Threading.Tasks;

namespace AppenderTest
{
    static class Program
    {
        static ILog log;

        static void Main(string[] args)
        {
            ConfigureAppender("Main");

            log = LogManager.GetLogger(typeof(Program));

            log.Info("Starting from Main!");

            Task task1 = Task.Run(() => NewThread("LogicName1"));

            log.Info("Hello from Main!");

            Task task2 = Task.Run(() => NewThread("LogicName2"));

            log.Info("Hello again from Main!");

            Task.WaitAll(task1, task2);

            log.Info("Still from Main!");
        }

        public static void NewThread(string name)
        {
            ConfigureAppender(name);

            for (int i = 0; i < 10; i++)
            {
                Thread.Sleep(50);

                log.Info($"Loop index {i}");
            }
        }

        private static void ConfigureAppender(string name)
        {
            RollingFileAppender appender = new RollingFileAppender
            {
                Name = $"{name}Appender",
                File = $"{name}.log"
            };

            PatternLayout layout = new PatternLayout
            {
                ConversionPattern = "%date{hh:mm:ss.fff} %level %thread %logger %property{LogicName} - %message%newline"
            };

            layout.ActivateOptions();

            appender.Layout = layout;

            PropertyFilter filter = new PropertyFilter
            {
                Key = "LogicName",
                StringToMatch = name,
                AcceptOnMatch = true
            };

            filter.ActivateOptions();

            appender.AddFilter(filter);
            appender.AddFilter(new DenyAllFilter());

            appender.ActivateOptions();

            LogicalThreadContext.Properties["LogicName"] = name;

            BasicConfigurator.Configure(appender);
        }
    }
}