Log4Net с замком Виндзор

Я настраиваю ведение журнала для своего приложения и для ведения журнала, который я использую log4net а также castle windsor для DI.

Я хочу, чтобы структура логирования была включена в пользовательскую реализацию, чтобы ее можно было изменить в будущем.

public interface ICustomLogger
{
    void Debug(object message, Exception ex = null);
    void Info(object message, Exception ex = null);
    void Warn(object message, Exception ex = null);
    void Error(object message, Exception ex = null);
    void Fatal(object message, Exception ex = null);
}

public class CustomLogger : ICustomLogger
{
    private readonly log4net.ILog _log;
    private readonly log4net.ILog _log1;

    public CustomLogger()
    {
        //approach1
        var stack = new StackTrace();
        var frame = stack.GetFrame(1);
        var method = frame.GetMethod();
        Type type = method.DeclaringType;
        _log = log4net.LogManager.GetLogger(type);

        //approach2
        var dtype = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType;
        _log1 = log4net.LogManager.GetLogger(dtype);
    }

    public CustomLogger(string name)
    {
        _log = log4net.LogManager.GetLogger(name);
    }

    public CustomLogger(Type type)
    {
        _log = log4net.LogManager.GetLogger(type);
    }

    public void Debug(object message, Exception ex = null)
    {
        if (_log.IsDebugEnabled)
        {
            if (ex == null)
            {
                _log.Debug(message);
            }
            else
            {
                _log.Debug(message, ex);
            }
        }
    }

    public void Info(object message, Exception ex = null)
    {
        if (_log.IsInfoEnabled)
        {
            if (ex == null)
            {
                _log.Info(message);
            }
            else
            {
                _log.Info(message, ex);
            }
        }
    }

    public void Warn(object message, Exception ex = null)
    {
        if (_log.IsWarnEnabled)
        {
            if (ex == null)
            {
                _log.Warn(message);
            }
            else
            {
                _log.Warn(message, ex);
            }
        }
    }

    public void Error(object message, Exception ex = null)
    {
        if (_log.IsErrorEnabled)
        {
            if (ex == null)
            {
                _log.Error(message);
            }
            else
            {
                _log.Error(message, ex);
            }
        }
    }

    public void Fatal(object message, Exception ex = null)
    {
        if (_log.IsFatalEnabled)
        {
            if (ex == null)
            {
                _log.Fatal(message);
            }
            else
            {
                _log.Fatal(message, ex);
            }
        }
    }
}

Чтобы зарегистрировать эту пользовательскую реализацию с DI...

   container.Register(Component.For<ICustomLogger>()
                                   .ImplementedBy<CustomLogger>()
                                   .LifeStyle.Transient);

Проблема возникает, когда я прошу DI разрешить регистратор, тогда он всегда возвращает регистратор для Customlogger введите не тот класс, где я хочу его использовать.

class ABC
{
    ICustomLogger _logger;

    public ABC(ICustomLogger logger)
    {
        _logger = logger; // type of this logger is CustomLogger not ABC
    }
}

Оба подхода не работают, чтобы разрешить регистратор как ABC. Может кто-нибудь помочь мне понять, что здесь не так и как решить проблему.

2 ответа

Решение

Вы можете сделать это с помощью специального средства разрешения зависимостей.

Сначала нужно создать реализацию ISubDependencyResolver который может разрешать зависимости типа ICustomLogger:

public class LoggerResolver : ISubDependencyResolver
{
    public bool CanResolve(
        CreationContext context,
        ISubDependencyResolver contextHandlerResolver,
        ComponentModel model,
        DependencyModel dependency)
    {
        //We can only handle dependencies of type ICustomLogger 
        return dependency.TargetType == typeof (ICustomLogger);
    }

    public object Resolve(
        CreationContext context,
        ISubDependencyResolver contextHandlerResolver,
        ComponentModel model,
        DependencyModel dependency)
    {
        //We pass the requested type, e.g. ABC, to the constructor of CustomLogger
        return new CustomLogger(context.RequestedType);
    }
}

Затем вам нужно зарегистрировать этот преобразователь в контейнере следующим образом:

container.Kernel.Resolver.AddSubResolver(new LoggerResolver());

Для вашего конкретного вопроса - в обоих подходах вы никогда не покидаете "рамки" своего класса. С первой вы создаете новую StackTrace, а с другой декларирующим типом конструктора является сам этот класс.

Но вы реализовали конструктор, который может получить тип, так почему бы не использовать его. В настоящее время ваш CustomLogger зарегистрирован с вашим конструктором по умолчанию:

//There is no place here that you tell castle to resolve using the constructor 
//that receives `ABS` 
container.Register(Component.For<ICustomLogger>()
                            .ImplementedBy<CustomLogger>()
                            .LifeStyle.Transient);

Посмотрите, как Castle Windsor передает параметры конструктора, чтобы понять, как передавать параметры и как вызывать нужный конструктор.


Кроме того - стоит переосмыслить:

Хотя в этом случае хорошей идеей является создание такой абстракции между вашим кодом и внешним источником, я бы этого не делал, и я объясню, почему:

  1. Из моего опыта никто не меняет структуру логирования после того, как код запущен и работает. Тем более, что вы работаете со зрелой и превосходной основой - Log4Net, Он имеет множество встроенных возможностей и очень легко адаптируется для собственных нужд: от различного форматирования сообщений до вывода журналов в разные источники, такие как базы данных, файлы, и, если я не ошибаюсь, есть также приложения для таких вещей, как упругий поиск.
  2. Ты используешь Castle Windsor которая имеет хорошую интеграцию с Log4Net и имеет для вас готовый Logging Facility в Log4Net, Посмотрите этот вопрос, как просто добавить его.
  3. Последний пункт заключается в том, что если вы уже написали хороший код SOLID и передали свой регистратор как ILogger для всех компонентов (а не конкретной реализации) все, что они, вероятно, будут делать, это вызывать разные Debug/Info/Warn/Error/Fatal методы - которые будут иметь любая другая зрелая структура ведения журнала. Таким образом, в тот день, когда вам придется изменить (что, я думаю, не произойдет), вы можете написать интерфейс, который выглядит как Log4Netинтерфейс и реализация, которая адаптирует его к вашей новой структуре ведения журналов.
Другие вопросы по тегам