Появилась необходимость вести несколько параллельных логов приложения.
Внутри приложения выполняются отдельные не связанные друг с другом задачи, генерирующие большой объём логов. Было решено разделить логи, и вести отдельныйлог для каждой из задач.
Для этого нужно программно создавать логгеры log4net, не полагаясь на конфигурацию, в которой сконфигурирован только один глобальный лог, не привязанный к задаче.
Настройки логирования для каждого нового лога было решено скопировать из глобального лога, изменив только имя файла. Класс TaskLogger инкапсулирует описанную функцинальность:
Внутри приложения выполняются отдельные не связанные друг с другом задачи, генерирующие большой объём логов. Было решено разделить логи, и вести отдельныйлог для каждой из задач.
Для этого нужно программно создавать логгеры log4net, не полагаясь на конфигурацию, в которой сконфигурирован только один глобальный лог, не привязанный к задаче.
Настройки логирования для каждого нового лога было решено скопировать из глобального лога, изменив только имя файла. Класс TaskLogger инкапсулирует описанную функцинальность:
/// <summary> /// Class incapsulating a per-task logging functionality. /// </summary> internal static class TaskLogger { private static readonly IDictionary<int, ILog> loggers = new Dictionary<int, ILog>(); private static Hierarchy hier; private static readonly RollingFileAppender mainAppender; static TaskLogger() { hier = (Hierarchy)LogManager.GetRepository(); mainAppender = hier.GetAppenders()[0] as RollingFileAppender; } /// <summary> /// Creates a logger if there in no logger for a task yet, /// and puts it to cache. /// </summary> /// <param name="taskId"></param> /// <param name="message"></param> public static void Log(int taskId, string message) { if (!loggers.ContainsKey(taskId)) { lock (loggers) { if (!loggers.ContainsKey(taskId)) { loggers.Add(taskId, CreatePerTaskLogger(taskId)); } } } loggers[taskId].Info(message); } /// <summary> /// Removes a logger from cache when it's not required. /// </summary> /// <param name="taskId"></param> public static void RemoveLogger(int taskId) { if (loggers.ContainsKey(taskId)) { lock (loggers) { if (loggers.ContainsKey(taskId)) { loggers.Remove(taskId); } } } } /// <summary> /// Creates an additional logger to log task events in a separate file. /// </summary> /// <param name="taskId">Task ID.</param> /// <returns>New Logger.</returns> private static ILog CreatePerTaskLogger(int taskId) { FileAppender appender = CreateAppender(mainAppender, taskId); Logger logger = new OurLogger(String.Format("Logger_{0}", taskId)); logger.Level = Level.All; //logger.RemoveAllAppenders(); logger.AddAppender(appender); logger.Hierarchy = hier; return new LogImpl(logger); } /// <summary> /// Creates a new appender for a new logger. /// </summary> /// <param name="source">Source appender to copy settings from.</param> /// <param name="taskId">Id of the task being logged.</param> /// <returns></returns> private static RollingFileAppender CreateAppender(RollingFileAppender source, int taskId) { RollingFileAppender result = new RollingFileAppender(); result.AppendToFile = source.AppendToFile; result.Encoding = source.Encoding; result.ErrorHandler = source.ErrorHandler; string logsFolder = Path.GetDirectoryName(source.File); result.File = Path.Combine(logsFolder, String.Format("Task_{0}.log", taskId)); result.ImmediateFlush = source.ImmediateFlush; result.Layout = source.Layout; result.LockingModel = new FileAppender.MinimalLock(); result.Name = String.Format("Appender_{0}", taskId); result.SecurityContext = source.SecurityContext; result.CountDirection = source.CountDirection; result.DatePattern = source.DatePattern; result.DateTimeStrategy = source.DateTimeStrategy; result.MaxFileSize = source.MaxFileSize; result.MaxSizeRollBackups = source.MaxSizeRollBackups; result.MaximumFileSize = source.MaximumFileSize; result.PreserveLogFileNameExtension = source.PreserveLogFileNameExtension; result.RollingStyle = source.RollingStyle; result.StaticLogFileName = source.StaticLogFileName; return result; } }
Изменения кэша аппендеров защищены оператором lock. После завершения (удаления) задачи необходимо удалить аппендер из кэша самостоятельно. Следующий тестовый код использует класс, запуская новые потоки в качестве задач:
public static class Program { public static void Main(string[] args) { GlobalLogger.Log("Starting tasks."); new Thread(Run).Start(); new Thread(Run).Start(); Run(); GlobalLogger.Log("Finished logging messages from tasks."); GlobalLogger.Log("A last message for task!"); } private static void Run() { int threadId = Thread.CurrentThread.ManagedThreadId; log4net.GlobalContext.Properties["ThreadId"] = threadId; Thread.Sleep(threadId * threadId); TaskLogger.Log(threadId, String.Format("Hello from Task {0}!", threadId)); GlobalLogger.Log(String.Format("Global message from task {0}", threadId)); TaskLogger.RemoveLogger(threadId); } }Пара вспомогательных классов, используемых в коде: глобальный логгер для событий, не привязанных к конкретной задачеinternal static class GlobalLogger { private static readonly ILog CommonLogger; static GlobalLogger() { XmlConfigurator.Configure(); CommonLogger = LogManager.GetLogger("TestLogger"); } public static void Log(string message) { CommonLogger.Info(message); } }...и наследник логфонетовского логгера:internal class OurLogger : Logger { public OurLogger(string name) : base(name) { } }
No comments:
Post a Comment