Pergunta

Estou interessado em aprender mais sobre como as pessoas injetam log com plataformas de injeção de dependência.Embora os links abaixo e meus exemplos se refiram ao log4net e ao Unity, não irei necessariamente usar nenhum deles.Para injeção de dependência/IOC, provavelmente usarei MEF, pois esse é o padrão que o resto do projeto (grande) está adotando.

Sou muito novo em injeção de dependência/ioc e muito novo em C# e .NET (escrevi muito pouco código de produção em C#/.NET após os últimos 10 anos de VC6 e VB6).Eu fiz muitas investigações sobre as várias soluções de registro disponíveis, então acho que tenho um controle decente sobre seus conjuntos de recursos.Eu simplesmente não estou familiarizado o suficiente com a mecânica real de injetar uma dependência (ou, talvez mais "corretamente", obter uma versão abstrata de uma dependência injetada).

Já vi outras postagens relacionadas ao registro e/ou injeção de dependência, como:injeção de dependência e interfaces de registro

Práticas recomendadas de registro

Qual seria a aparência de uma classe Log4Net Wrapper?

novamente sobre log4net e configuração do Unity IOC

A minha pergunta não tem especificamente a ver com "Como faço para injectar a plataforma de registo xxx utilizando a ferramenta ioc yyy?" Em vez disso, estou interessado na forma como as pessoas lidaram com o envolvimento da plataforma de registo (como é frequentemente, mas nem sempre recomendado) e configuração (i.e.aplicativo.config).Por exemplo, usando o log4net como exemplo, eu poderia configurar (em app.config) vários registradores e, em seguida, obter esses registradores (sem injeção de dependência) da maneira padrão de usar um código como este:

private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

Alternativamente, se meu logger não tiver o nome de uma classe, mas sim de uma área funcional, eu poderia fazer o seguinte:

private static readonly ILog logger = LogManager.GetLogger("Login");
private static readonly ILog logger = LogManager.GetLogger("Query");
private static readonly ILog logger = LogManager.GetLogger("Report");

Então, acho que meus "requisitos" seriam mais ou menos assim:

  1. Gostaria de isolar a fonte do meu produto de uma dependência direta de uma plataforma de registro.

  2. Eu gostaria de poder resolver uma instância específica do logger nomeado (provavelmente compartilhando a mesma instância entre todos os solicitantes da mesma instância nomeada), direta ou indiretamente, por algum tipo de injeção de dependência, provavelmente MEF.

  3. Não sei se chamaria isso de um requisito difícil, mas gostaria de poder obter um criador de logs nomeado (diferente do registrador de classe) sob demanda.Por exemplo, posso criar um criador de logs para minha classe com base no nome da classe, mas um método precisa de diagnósticos particularmente pesados ​​que gostaria de controlar separadamente.Em outras palavras, talvez eu queira que uma única classe "depende" de duas instâncias separadas do logger.

Vamos começar com o número 1.Eu li vários artigos, principalmente aqui no stackoverflow, sobre se é ou não uma boa ideia agrupar.Veja o link "melhores práticas" acima e vá para Jeffrey Hantinpara uma visão sobre por que é ruim agrupar o log4net.Se você embrulhasse (e pudesse embrulhar de maneira eficaz), você embrulharia estritamente para fins de injeção/remoção de dependência direta?Ou você também tentaria abstrair algumas ou todas as informações do log4net app.config?

Digamos que eu queira usar System.Diagnostics, provavelmente gostaria de implementar um logger baseado em interface (talvez até usando a interface "comum" ILogger/ILog), provavelmente baseado em TraceSource, para que eu pudesse injetá-lo.Você implementaria a interface, digamos, no TraceSource, e apenas usaria as informações do System.Diagnostics app.config como estão?

Algo assim:

public class MyLogger : ILogger
{
  private TraceSource ts;
  public MyLogger(string name)
  {
    ts = new TraceSource(name);
  }

  public void ILogger.Log(string msg)
  {
    ts.TraceEvent(msg);
  }
}

E use assim:

private static readonly ILogger logger = new MyLogger("stackoverflow");
logger.Info("Hello world!")

Passando para o número 2...Como resolver uma instância específica do logger nomeado?Devo apenas aproveitar as informações app.config da plataforma de log que escolho (ou seja,resolver os registradores com base no esquema de nomenclatura no app.config)?Então, no caso do log4net, posso preferir "injetar" o LogManager (observe que sei que isso não é possível, pois é um objeto estático)?Eu poderia agrupar o LogManager (chame-o de MyLogManager), fornecer uma interface ILogManager e, em seguida, resolver a interface MyLogManager.ILogManager.Meus outros objetos poderiam ter uma dependência (Importar no jargão do MEF) no ILogManager (Exportar do assembly onde está implementado).Agora eu poderia ter objetos como este:

public class MyClass
{
  private ILogger logger;
  public MyClass([Import(typeof(ILogManager))] logManager)
  {
    logger = logManager.GetLogger("MyClass");
  }
}

Sempre que o ILogManager for chamado, ele delegará diretamente ao LogManager do log4net.Como alternativa, o LogManager empacotado poderia pegar as instâncias do ILogger obtidas com base no app.config e adicioná-las ao (um?) contêiner MEF por nome.Posteriormente, quando um criador de logs com o mesmo nome for solicitado, o LogManager encapsulado será consultado para obter esse nome.Se o ILogger estiver lá, será resolvido dessa forma.Se isso for possível com o MEF, há algum benefício em fazê-lo?

Nesse caso, realmente, apenas o ILogManager é "injetado" e pode distribuir instâncias do ILogger da maneira que o log4net normalmente faz.Como esse tipo de injeção (essencialmente de uma fábrica) se compara à injeção das instâncias do logger nomeadas?Isso permite um aproveitamento mais fácil do arquivo app.config do log4net (ou outra plataforma de registro).

Eu sei que posso obter instâncias nomeadas do contêiner MEF assim:

var container = new CompositionContainer(<catalogs and other stuff>);
ILogger logger = container.GetExportedValue<ILogger>("ThisLogger");

Mas como coloco as instâncias nomeadas no contêiner?Eu conheço o modelo baseado em atributos onde eu poderia ter diferentes implementações do ILogger, cada uma delas nomeada (por meio de um atributo MEF), mas isso realmente não me ajuda.Existe uma maneira de criar algo como um app.config (ou uma seção nele) que listaria os criadores de logs (todos da mesma implementação) por nome e que o MEF pudesse ler?Poderia/deveria haver um "gerenciador" central (como MyLogManager) que resolva os criadores de logs nomeados por meio do app.config subjacente e, em seguida, insira o criador de logs resolvido no contêiner MEF?Dessa forma, ele estaria disponível para outra pessoa que tivesse acesso ao mesmo contêiner MEF (embora sem o conhecimento do MyLogManager sobre como usar as informações app.config do log4net, parece que o contêiner não seria capaz de resolver nenhum registrador nomeado diretamente).

Isso já demorou bastante.Espero que seja coerente.Sinta-se à vontade para compartilhar qualquer informação específica sobre como sua dependência injetou uma plataforma de log (provavelmente estamos considerando log4net, NLog ou algo (espero que fino) construído em System.Diagnostics) em seu aplicativo.

Você injetou o "gerente" e fez com que ele retornasse instâncias do logger?

Você adicionou algumas de suas próprias informações de configuração em sua própria seção de configuração ou na seção de configuração de sua plataforma DI para tornar mais fácil/possível injetar instâncias de logger diretamente (ou seja,faça com que suas dependências estejam no ILogger em vez do ILogManager).

Que tal ter um contêiner estático ou global que contenha a interface ILogManager ou o conjunto de instâncias nomeadas do ILogger?Portanto, em vez de injetar no sentido convencional (por meio de dados de construtor, propriedade ou membro), a dependência de registro é explicitamente resolvida sob demanda.Esta é uma maneira boa ou ruim de injetar dependência.

Estou marcando isso como um wiki da comunidade, pois não parece uma pergunta com resposta definitiva.Se alguém achar o contrário, fique à vontade para mudar.

Obrigado por qualquer ajuda!

Foi útil?

Solução 3

Isso é para o benefício de qualquer pessoa que esteja tentando descobrir como injetar uma dependência de logger quando o logger que você deseja injetar recebe uma plataforma de log como log4net ou NLog.Meu problema era que eu não conseguia entender como poderia fazer uma aula (por exemplo,MyClass) dependente de uma interface do tipo ILogger quando eu sabia que a resolução do ILogger específico dependeria do conhecimento do tipo da classe que depende do ILogger (por exemploMinha classe).Como a plataforma/contêiner DI/IoC obtém o ILogger correto?

Bem, eu olhei a fonte do Castle e do NInject e vi como eles funcionam.Também procurei AutoFac e StructureMap.

Castle e NInject fornecem uma implementação de registro.Ambos suportam log4net e NLog.Castle também oferece suporte a System.Diagnostics.Em ambos os casos, quando a plataforma resolve as dependências de um determinado objeto (por exemplo,quando a plataforma está criando MyClass e MyClass depende do ILogger), ela delega a criação da dependência (ILogger) ao "provedor" do ILogger (resolvedor pode ser um termo mais comum).A implementação do provedor ILogger é então responsável por instanciar uma instância do ILogger e devolvê-la, para então ser injetada na classe dependente (por exemplo,Minha classe).Em ambos os casos, o provedor/resolvedor conhece o tipo da classe dependente (por exemploMinha classe).Portanto, quando MyClass for criada e suas dependências estiverem sendo resolvidas, o "resolvedor" do ILogger saberá que a classe é MyClass.No caso de usar as soluções de log fornecidas pelo Castle ou NInject, isso significa que a solução de log (implementada como um wrapper sobre log4net ou NLog) obtém o tipo (MyClass), para que possa delegar para log4net.LogManager.GetLogger() ou NLog.LogManager.GetLogger().(Não tenho 100% de certeza da sintaxe para log4net e NLog, mas você entendeu).

Embora o AutoFac e o StructureMap não forneçam um recurso de registro (pelo menos isso eu poderia dizer olhando), eles parecem fornecer a capacidade de implementar resolvedores personalizados.Portanto, se você quiser escrever sua própria camada de abstração de log, também poderá escrever um resolvedor personalizado correspondente.Dessa forma, quando o contêiner quiser resolver o ILogger, seu resolvedor será usado para obter o ILogger correto E terá acesso ao contexto atual (ou seja,quais dependências do objeto estão sendo satisfeitas no momento - qual objeto depende do ILogger).Obtenha o tipo do objeto e você estará pronto para delegar a criação do ILogger à plataforma de registro atualmente configurada (que você provavelmente abstraiu por trás de uma interface e para a qual escreveu um resolvedor).

Portanto, alguns pontos-chave que eu suspeitava serem necessários, mas que não entendi totalmente antes, são:

  1. Em última análise, o recipiente DI deve ser consciente, de alguma forma, do que o registo plataforma a usar-se.Normalmente isto é feito especificando que o "ILogger" é a ser resolvida por um "resolver" que é específico para uma plataforma de registo (Portanto, o Castle tem log4net, NLog, e System.Diagnostics "resolvedores" (entre outros)).A especificação do qual o resolvedor a usar-se pode ser feito via ficheiro de configuração ou programaticamente.

  2. O resolvedor precisa de saber o contexto para o qual a dependência (ILogger) está a ser resolvido.Que é, se a MyClass tiver sido criada e depende do ILogger, então quando o resolvedor está a tentar criar o ILogger correcto, ele (o resolvedor) deve saber o tipo actual (MyClass).Dessa forma, o resolvedor pode utilizar o registo subjacente implementação (log4net, NLog, etc) para obter o logger correcto.

Esses pontos podem ser óbvios para os usuários de DI/IoC por aí, mas só agora estou entrando no assunto, então demorei um pouco para entender isso.

Uma coisa que ainda não descobri é como ou se algo assim é possível com o MEF.Posso ter um objeto dependente de uma interface e executar meu código depois que o MEF tiver criado o objeto e enquanto a interface/dependência estiver sendo resolvida?Então, suponha que eu tenha uma classe como esta:

public class MyClass
{
  [Import(ILogger)]
  public ILogger logger;

  public MyClass()
  {
  }

  public void DoSomething()
  {
    logger.Info("Hello World!");
  }
}

Quando o MEF está resolvendo as importações para MyClass, posso fazer com que parte do meu próprio código (por meio de um atributo, por meio de uma interface extra na implementação do ILogger, em outro lugar???) execute e resolva a importação do ILogger com base no fato de que é MyClass que está atualmente no contexto e retorna uma instância ILogger (potencialmente) diferente da que seria recuperada para YourClass?Devo implementar algum tipo de provedor MEF?

Neste ponto, ainda não sei sobre o MEF.

Outras dicas

Estou usando o Ninject para resolver o nome da classe atual para a instância do logger assim:

kernel.Bind<ILogger>().To<NLogLogger>()
  .WithConstructorArgument("currentClassName", x => x.Request.ParentContext.Request.Service.FullName);

O construtor de uma implementação NLog poderia ser assim:

public NLogLogger(string currentClassName)
{
  _logger = LogManager.GetLogger(currentClassName);
}

Essa abordagem também deve funcionar com outros contêineres IOC, eu acho.

Também se pode usar Registro comum fachada ou o Fachada de registro simples.

Ambos empregam um padrão de estilo de localizador de serviço para recuperar um ILogger.

Francamente, o log é uma daquelas dependências que vejo pouco ou nenhum valor na injeção automática.

A maioria das minhas aulas que requerem serviços de registro são assim:

public class MyClassThatLogs {
    private readonly ILogger log = Slf.LoggerService.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName);

}

Ao utilizar o Simple Logging Façade, mudei um projeto de log4net para NLog e adicionei o log de uma biblioteca de terceiros que usava o log4net, além do log do meu aplicativo usando o NLog.Ou seja, a fachada nos serviu bem.

Uma ressalva que é difícil de evitar é a perda de recursos específicos de uma estrutura de log ou de outra, talvez o exemplo mais frequente disso sejam os níveis de log personalizados.

Vejo que você descobriu sua própria resposta :) Mas, para pessoas no futuro que tenham essa dúvida sobre como NÃO se vincular a uma estrutura de registro específica, esta biblioteca: Registro comum ajuda exatamente nesse cenário.

Eu fiz meu costume Provedor de exportação de serviço, pelo provedor eu registro o logger Log4Net para injeção de dependência pelo MEF.Como resultado, você pode usar o logger para diferentes tipos de injeções.

Exemplo de injeções:

[Export]
public class Part
{
    [ImportingConstructor]
    public Part(ILog log)
    {
        Log = log;
    }

    public ILog Log { get; }
}

[Export(typeof(AnotherPart))]
public class AnotherPart
{
    [Import]
    public ILog Log { get; set; }
}

Exemplo de uso:

class Program
{
    static CompositionContainer CreateContainer()
    {
        var logFactoryProvider = new ServiceExportProvider<ILog>(LogManager.GetLogger);
        var catalog = new AssemblyCatalog(typeof(Program).Assembly);
        return new CompositionContainer(catalog, logFactoryProvider);
    }

    static void Main(string[] args)
    {
        log4net.Config.XmlConfigurator.Configure();
        var container = CreateContainer();
        var part = container.GetExport<Part>().Value;
        part.Log.Info("Hello, world! - 1");
        var anotherPart = container.GetExport<AnotherPart>().Value;
        anotherPart.Log.Fatal("Hello, world! - 2");
    }
}

Resultado no console:

2016-11-21 13:55:16,152 INFO  Log4Mef.Part - Hello, world! - 1
2016-11-21 13:55:16,572 FATAL Log4Mef.AnotherPart - Hello, world! - 2

Provedor de exportação de serviço implementação:

public class ServiceExportProvider<TContract> : ExportProvider
{
    private readonly Func<string, TContract> _factoryMethod;

    public ServiceExportProvider(Func<string, TContract> factoryMethod)
    {
        _factoryMethod = factoryMethod;
    }

    protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)
    {
        var cb = definition as ContractBasedImportDefinition;
        if (cb?.RequiredTypeIdentity == typeof(TContract).FullName)
        {
            var ce = definition as ICompositionElement;
            var displayName = ce?.Origin?.DisplayName;
            yield return new Export(definition.ContractName, () => _factoryMethod(displayName));
        }
    }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top