Как настроить и включить log4net для автономной сборки библиотеки классов?
Фон
Я пишу сборку библиотеки классов на C# .NET 3.5, которая используется для интеграции с другими приложениями, включая сторонние инструменты для коммерческого использования (COTS). Поэтому иногда эта библиотека классов будет вызываться приложениями (EXE), которые я контролирую, в то время как в других случаях она будет вызываться другими DLL или приложениями, которые я не контролирую.
Предположения
- Я использую C# 3.0, .NET 3.5 SP1 и Visual Studio 2008 SP1
- Я использую log4net 1.2.10.0 или выше
Ограничения
Любое решение должно:
- Разрешите библиотеке классов включать и настраивать ведение журнала через собственный файл конфигурации, если вызывающее приложение не настраивает log4net.
- Разрешить библиотеке классов включать и настраивать ведение журнала через конфигурацию вызывающих приложений, если в ней указана информация log4net
ИЛИ ЖЕ
- Разрешить библиотеке классов всегда включать и настраивать ведение журнала, используя собственный файл конфигурации.
проблема
Когда моя отдельная библиотека классов вызывается из библиотеки DLL или приложения, которое я не контролирую (например, стороннего инструмента COTS), и которое не указывает информацию о конфигурации log4net, моя библиотека классов не может выполнить какую-либо ее регистрацию,
Вопрос
Как настроить и включить log4net для автономной сборки библиотеки классов, чтобы она регистрировалась независимо от того, предоставляет ли вызывающее приложение конфигурацию log4net?
5 ответов
Решение 1
Решение для первого набора ограничений состоит в том, чтобы в основном заключить log4net.LogManager в свой собственный класс LogManager, как предложили Jacob, Jeroen и McWafflestix (см. Код ниже).
К сожалению, класс log4net.LogManager является статическим, и C# не поддерживает статическое наследование, поэтому вы не можете просто наследовать его и переопределить метод GetLogger. Однако в классе log4net.LogManager не так много методов, так что это, безусловно, возможно.
Другим недостатком этого решения является то, что если у вас есть существующая кодовая база (что я и делаю в моем случае), вам придется заменить все существующие вызовы log4net.LogManager вашим классом-оберткой. Тем не менее, нет ничего страшного в современных инструментах рефакторинга.
Для моего проекта эти недостатки перевесили преимущества использования конфигурации журналирования, предоставляемой вызывающим приложением, поэтому я перешел к решению 2.
Код
Во-первых, вам нужен класс-оболочка LogManager:
using System;
using System.IO;
using log4net;
using log4net.Config;
namespace MyApplication.Logging
{
//// TODO: Implement the additional GetLogger method signatures and log4net.LogManager methods that are not seen below.
public static class LogManagerWrapper
{
private static readonly string LOG_CONFIG_FILE= @"path\to\log4net.config";
public static ILog GetLogger(Type type)
{
// If no loggers have been created, load our own.
if(LogManager.GetCurrentLoggers().Length == 0)
{
LoadConfig();
}
return LogManager.GetLogger(type);
}
private void LoadConfig()
{
//// TODO: Do exception handling for File access issues and supply sane defaults if it's unavailable.
XmlConfigurator.ConfigureAndWatch(new FileInfo(LOG_CONFIG_FILE));
}
}
Тогда в ваших классах вместо:
private static readonly ILog log = LogManager.GetLogger(typeof(MyApp));
Использование:
private static readonly ILog log = LogManagerWrapper.GetLogger(typeof(MyApp));
Решение 2
Для моих целей я решил остановиться на решении, которое соответствует второму набору ограничений. Посмотрите код ниже для моего решения.
"Сборка может выбрать использование именованного хранилища журналов, а не хранилища по умолчанию. Это полностью отделяет ведение журнала для сборки от остальной части приложения. Это может быть очень полезно для разработчиков компонентов, которые хотят использовать log4net для своих компонентов, но не не нужно требовать, чтобы все приложения, использующие их компонент, знали о log4net. Это также означает, что их конфигурация отладки отделена от конфигурации приложений. Сборка должна указывать RepositoryAttribute для установки своего репозитория журналирования."
Код
Я поместил следующие строки в файл AssemblyInfo.cs моей библиотеки классов:
// Log4Net configuration file location [assembly: log4net.Config.Repository("CompanyName.IntegrationLibName")] [assembly: log4net.Config.XmlConfigurator(ConfigFile = "CompanyName.IntegrationLibName.config", Watch = true)]
Рекомендации
Вероятно, вы можете написать что-нибудь вокруг класса XmlConfigurator:
public static class MyLogManager
{
// for illustration, you should configure this somewhere else...
private static string configFile = @"path\to\log4net.config";
public static ILog GetLogger(Type type)
{
if(log4net.LogManager.GetCurrentLoggers().Length == 0)
{
// load logger config with XmlConfigurator
log4net.Config.XmlConfigurator.Configure(configFile);
}
return LogManager.GetLogger(type);
}
}
Тогда в ваших классах вместо:
private static readonly ILog log = LogManager.GetLogger(typeof(MyApp));
Использование:
private static readonly ILog log = MyLogManager.GetLogger(typeof(MyApp));
Конечно, было бы предпочтительнее сделать этот класс сервисом и динамически настроить его с использованием контейнера IoC по вашему выбору, но вы поняли?
РЕДАКТИРОВАТЬ: Исправлена проблема Count(), указанная в комментариях.
В своем коде вы можете проверить, есть ли какие-либо регистраторы через
log4net.LogManager.GetCurrentLoggers().Count()
Затем вы можете, например, использовать XmlConfigurator для загрузки конфигурации по умолчанию из файла:
log4net.Config.XmlConfigurator.Configure(configFile)
Вы можете выполнить инициализацию в статическом или обычном конструкторе.
class Sample
{
private static readonly log4net.ILog LOG;
static Sample()
{
if (log4net.LogManager.GetCurrentLoggers().Count() == 0)
{
loadConfig();
}
LOG = log4net.LogManager.GetLogger(typeof(Sample));
}
private static void loadConfig()
{
/* Load your config file here */
}
public void YourMethod()
{
LOG.Info("Your messages");
}
}
В вашей автономной библиотеке классов есть синглтон, который загружает log4net
файл конфигурации с использованием log4net.Config.XmlConfigurator
,
В частности, вы можете определить весь свой код, чтобы использовать свой собственный класс регистрации; этот класс может быть просто оболочкой для вызовов журнала log4net, с одним дополнением; создайте статический член, содержащий информацию журнала, в которую вы хотите войти; инициализируйте это с помощью вызова XmlConfigurator в статическом конструкторе для этого класса. Это все, что вам нужно сделать.
Вы можете найти хорошее описание здесь: log4net: краткое руководство
Как описано в статье, чтобы настроить его для каждой сборки отдельно, создайте файл XML для вашей сборки с именем AssemblyName.dll.log4net
и поместите в него следующий XML-код:
<?xml version="1.0" encoding="utf-8"?>
<log4net debug="false">
<appender name="XmlSchemaFileAppender" type="log4net.Appender.FileAppender">
<file value="AppLog.xml" />
<appendToFile value="true" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.XmlLayout" />
</appender>
<root>
<level value="WARN" />
<appender-ref ref="XmlSchemaFileAppender" />
</root>
</log4net>
Далее он описывает, чтобы создать новый регистратор, просто объявить его как переменную для всего класса следующим образом:
public class LogSample
{
private static readonly log4net.ILog Log
= log4net.LogManager.GetLogger(typeof(LogSample));
// Class Methods Go Here...
}
Затем вы можете использовать приватную переменную Log
внутри вашего класса, как:
Log.Info("Sample message");
Точно так же вы можете использовать Log.Error("Error occurred while running myMethod", ex)
регистрировать ошибки вместе с деталями исключения.
Я обнаружил следующее:
Не забудьте позвонить
log4net.Config.XmlConfigurator.Configure();
активировать вашу конфигурациюЕсли вам нужно знать путь к написанному файлу (файлам), вот некоторый код, как получить его из Log4Net
Надеюсь, это поможет.
Это работает для меня для общей библиотеки
protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.Assembly.GetExecutingAssembly().GetType());