【问题标题】:log4net: different logs on different file appenders at runtimelog4net:运行时不同文件附加器上的不同日志
【发布时间】:2023-03-07 18:25:01
【问题描述】:

大家早上好。

我编写了一个单一的 C# 2.0 应用程序(称为 myapp)。
Myapp 被多次调用,每次调用都会生成一种“任务”,将在单独的线程中执行。
如果短时间内多次调用myapp,任务是并行执行的。

通常我使用 log4net 进行日志记录;我配置它在启动时通过XmlConfigurator.Configure(<config>.xml) 加载一个xml 文件,然后我在每个需要记录器的类中使用静态LogManager.GetLogger(name),非常简单。

相反,这种情况具有挑战性。 我需要做的是:根据每次调用时收到的一个参数(称为 arg),我需要获取一个不同的 RollingFileAppender 来登录不同的文件, 即G。 .log。

举个例子:

第一个电话:myapp.exe -arg:01
- myapp 创建 thread1
- 将新的 RollingFileAppender 设置为 01.log 文件,如果不存在
- 此线程中使用的对象必须登录 01.log 文件

第二次通话:myapp.exe -arg:02
- 创建线程2
- 将新的 RollingFileAppender 设置为 02.log 文件,如果不存在
- 此线程中使用的对象必须登录 02.log 文件,而不是 log.01

第三次通话:myapp.exe -arg:01
- 创建线程03
- 获取 RollingFileAppender 到 01.log 文件(它已经存在!
- 此线程中使用的对象必须登录 01.log 文件,而不是 log.02

等等。 我不需要将 RollingAppender 的配置留在 xml 文件中,我可以通过编程方式创建它;我的想法是使用一个静态包装类,称之为 LogHelper,如果它们不存在,则基于 arg 创建附加程序,并在对象需要时调度正确的 ILog 实例(在类中我会使用一些东西像ILog log = LogHelper.GetLogger(name, arg) 来使用记录器来代替默认的log4net 方法LogManager.GetLogger(name))。 因此,如果我在 2 个不同的线程中有 2 个同一类的实例,当我记录消息时,每个文件都会发送一个消息,具体取决于 arg(我将在每个对象中注入 arg,如果需要)。

我在 StackOverflow 中浏览了许多线程,但我找不到解决方案。

有人能指出正确的方向吗?

提前致谢。

【问题讨论】:

    标签: c# multithreading logging log4net


    【解决方案1】:

    我最终得到了一个稍微不同的解决方案。
    我创建了一个 LogMaster 静态类(对不起,名字不好),其工作方式类似于默认 log4net LogManager 类。
    不同之处在于您可以根据arg 获得不同的ILog 实例:LogMaster 将为您将使用的每个不同的arg 创建一个新的ILoggerRepository

    代码如下:

    #region Usings
    using System;
    using System.IO;
    
    using log4net;
    using log4net.Appender;
    using log4net.Config;
    using log4net.Core;
    using log4net.Filter;
    using log4net.Layout;
    using log4net.Repository;
    using log4net.Repository.Hierarchy;
    
    
    #endregion
    
    
    namespace Common.Voyager
    {
        /// <summary>
        /// A static class that emulates defualt log4net LogManager static class.
        /// The difference is that you can get various loggers istances based from an args.
        /// LogMaster will create a different logger repository for each new arg it will receive.
        /// </summary>
        public static class LogMaster
        {
            #region Const
            private const string RollingFileAppenderNameDefault = "Rolling";
            private const string MemoryAppenderNameDefault = "Memory";
            #endregion
    
    
            #region Constructors
            static LogMaster()
            {
            }
            #endregion
    
    
            #region Public Methods
            public static ILog GetLogger(string arg, string name)
            {
                //It will create a repository for each different arg it will receive
                var repositoryName = arg;
    
                ILoggerRepository repository = null;
    
                var repositories = LogManager.GetAllRepositories();
                foreach (var loggerRepository in repositories)
                {
                    if (loggerRepository.Name.Equals(repositoryName))
                    {
                        repository = loggerRepository;
                        break;
                    }
                }
    
                Hierarchy hierarchy = null;
                if (repository == null)
                {
                    //Create a new repository
                    repository = LogManager.CreateRepository(repositoryName);
    
                    hierarchy = (Hierarchy)repository;
                    hierarchy.Root.Additivity = false;
    
                    //Add appenders you need: here I need a rolling file and a memoryappender
                    var rollingAppender = GetRollingAppender(repositoryName);
                    hierarchy.Root.AddAppender(rollingAppender);
    
                    var memoryAppender = GetMemoryAppender(repositoryName);
                    hierarchy.Root.AddAppender(memoryAppender);
    
                    BasicConfigurator.Configure(repository);
                }
    
                //Returns a logger from a particular repository;
                //Logger with same name but different repository will log using different appenders
                return LogManager.GetLogger(repositoryName, name);
            }
            #endregion
    
    
            #region Private Methods
            private static IAppender GetRollingAppender(string arg)
            {
                var level = Level.All;
    
                var rollingFileAppenderLayout = new PatternLayout("%date{HH:mm:ss,fff}|T%2thread|%25.25logger|%5.5level| %message%newline");
                rollingFileAppenderLayout.ActivateOptions();
    
                var rollingFileAppenderName = string.Format("{0}{1}", RollingFileAppenderNameDefault, arg);
    
                var rollingFileAppender = new RollingFileAppender();
                rollingFileAppender.Name = rollingFileAppenderName;
                rollingFileAppender.Threshold = level;
                rollingFileAppender.CountDirection = 0;
                rollingFileAppender.AppendToFile = true;
                rollingFileAppender.LockingModel = new FileAppender.MinimalLock();
                rollingFileAppender.StaticLogFileName = true;
                rollingFileAppender.RollingStyle = RollingFileAppender.RollingMode.Date;
                rollingFileAppender.DatePattern = ".yyyy-MM-dd'.log'";
                rollingFileAppender.Layout = rollingFileAppenderLayout;
                rollingFileAppender.File = string.Format("{0}.{1}", "log", arg);
                rollingFileAppender.ActivateOptions();
    
                return rollingFileAppender;
            }
    
            private static IAppender GetMemoryAppender(string station)
            {
                //MemoryAppender
                var memoryAppenderLayout = new PatternLayout("%date{HH:MM:ss} | %message%newline");
                memoryAppenderLayout.ActivateOptions();
    
                var memoryAppenderWithEventsName = string.Format("{0}{1}", MemoryAppenderNameDefault, station);
                var levelRangeFilter = new LevelRangeFilter();
                levelRangeFilter.LevelMax = Level.Fatal;
                levelRangeFilter.LevelMin = Level.Info;
    
                var memoryAppenderWithEvents = new MemoryAppenderWithEvents();
                memoryAppenderWithEvents.Name = memoryAppenderWithEventsName;
                memoryAppenderWithEvents.AddFilter(levelRangeFilter);
                memoryAppenderWithEvents.Layout = memoryAppenderLayout;
                memoryAppenderWithEvents.ActivateOptions();
    
                return memoryAppenderWithEvents;
            }
            #endregion
        }
    }
    

    用法:

    var arg = "myArg";
    var loggerName = "MyLogger";
    var log = LogMaster.GetLogger(arg, loggerName);
    

    使用此解决方案,您可以受益于默认 LogManager 行为检索 ILog 记录器:如果存储库中已存在同名记录器,您将恢复该状态(回收行为)。

    感谢@making3 的建议!

    【讨论】:

    • 您好 Ferdinando Santacroce,首先,很好的研究,恭喜。如果您使用控制台应用程序中的代码,日志输出也会发送到控制台,您知道吗?
    • 谢谢,希望对您有用。是的,我知道了,谢谢:)
    【解决方案2】:

    我做过类似的事情,我需要为类的每个实例使用不同的日志。您可以通过几个步骤动态创建日志。

    看起来default Logger (Line 97) 已经定义了,但它在它们的程序集内部,因此需要继承(据我所知)。

    public sealed class DynamicLogger : Logger 
    { 
        internal DynamicLogger(string name) : base(name) 
        { 
            base.Hierarchy = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
        } 
    }
    

    检索 ILog 的示例方法:

    public static ILog GetSample(string arg)
    {
        var logger = new DynamicLogger(arg);
        logger.Level = Level.All;
    
        var consoleAppender = new ConsoleAppender();
        consoleAppender.Name = arg;
        consoleAppender.Layout = new PatternLayout(arg + ": %m%newline");
        logger.AddAppender(consoleAppender);
    
        var newLog = new LogImpl(logger);
        if (_logs.Any(log => log.Logger.Name == newLog.Logger.Name) == false)
            _logs.Add(newLog);
    
        return newLog;
    }
    

    基本用法:

    var foo = DynamicLog.GetSample("foo");
    var bar = DynamicLog.GetSample("bar");
    foo.Error("Test");
    bar.Error("Test");
    

    对于您的场景,请查看创建RollingFileAppender,并查看对象的可用属性,因为这只是一个示例。

    编辑:添加了以下用于存储ILog的内容,并修改了上面原来的GetSample方法。

    添加一个 ILog 数组和一个 GetLogger 方法:

    private static List<ILog> _logs = new List<ILog>();
    
    public static ILog GetLogger(string name)
    {
        return _logs.SingleOrDefault(a => a.Logger.Name == name);
    }
    

    示例用法:

    DynamicLog.GetSample("foo");
    var foo = DynamicLog.GetLogger("foo");
    

    【讨论】:

    • 非常感谢您的建议。我正在使用 RollingFileAppender 在一个简单的控制台应用程序中成功尝试它。我现在将尝试使用并行线程,我正在交叉手指:)
    • 有一种方法可以在使用 DynamicLog.GetSample() 构建特定记录器后以编程方式检索它?我没有添加存储库记录器;如果我调用 BasicConfigurator.Configure(),log4net 会使用 ConsoleAppender 构建默认存储库。
    • 您始终可以创建自己的LogManager.GetLogger 方法。所需要做的就是创建一个 ILog 列表,存储由GetSample 创建的日志,然后使用另一种方法(例如您自己的GetLogger)使用log.Logger.Name 在所有 ILog 中搜索特定名称。如果您需要示例,请告诉我。
    • 我不能在 GetSample() 方法中返回之前将我创建的记录器存储在主存储库中吗?是的,一个例子将不胜感激。 :)
    • @FerdinandoSantacroce,我添加了一个示例。我相信有一种方法可以做到这一点,但是我不得不在存储库中弄乱一些来举例说明。我想说我的示例和存储它之间的主要(负面)区别是,LogManager.GetLogger 将返回一个 ILog,无论它是否存在。
    猜你喜欢
    • 1970-01-01
    • 2015-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多