Por que os loggers recomendam usar um logger por classe?

87

De acordo com a documentação do NLog:

A maioria dos aplicativos usará um logger por classe, onde o nome do logger é o mesmo que o nome da classe.

É a mesma maneira que o log4net opera. Por que isso é uma boa prática?

Daniel T.
fonte
1
Hmm. parece que há dois problemas aqui - um é ter um objeto de log real por classe e outro é ter o nome do log igual ao da classe.
Peter Recore

Respostas:

59

Com o log4net, o uso de um registrador por classe facilita a captura da origem da mensagem de log (ou seja, a gravação da classe no log). Se você não tem um logger por classe, mas em vez disso tem um logger para todo o aplicativo, você precisa recorrer a mais truques de reflexão para saber de onde vêm as mensagens de log.

Compare o seguinte:

Log por aula

using System.Reflection;
private static readonly ILog _logger = 
    LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);    

public void SomeMethod()
{
    _logger.DebugFormat("File not found: {0}", _filename);
}

Um logger por aplicativo (ou similar)

Logger.DebugFormat("File not found: {0}", _filename); // Logger determines caller

-- or --

Logger.DebugFormat(this, "File not found: {0}", _filename); // Pass in the caller

Usando o segundo exemplo, o Logger precisaria construir um rastreamento de pilha para ver quem o estava chamando ou seu código sempre teria que passar pelo chamador. Com o estilo logger-per-class, você ainda faz isso, mas pode fazê-lo uma vez por classe em vez de uma vez por chamada e eliminar um sério problema de desempenho.

Jeremy Wiebe
fonte
Obrigado, isso ajuda a esclarecer as coisas. Estávamos apenas colocando manualmente o nome da classe e o método na mensagem (ou seja, "ImageCreator.CreateThumbnail () chamado"), mas é melhor se o logger puder lidar com isso.
Daniel T.
1
Apenas para sua informação, tornou-se uma prática "melhor" ter um Logger por instância, em vez de por classe (ou seja, estático), porque isso torna mais fácil capturar informações, como informações de thread. Obviamente é uma questão de gosto, nenhuma "regra dura e rápida", mas eu queria apenas jogar isso fora.
Will Hartung de
7
@will, você pode explicar um pouco mais? Ao registrar usando um Logger por classe, sempre registro o ID do thread para que o logger possa obter as informações do thread atual. Qualquer outra informação de thread estaria disponível para o logger também.
Jeremy Wiebe
@Jeremy Wiebe: Esse é o único motivo? Funcionalmente, não há problemas se eu usar uma única variável global do tipo logger para todo o aplicativo?
giorgim
1
@Giorgi Não, acho que não. Você pode obter muitas dessas informações hoje em dia com os atributos CallerInformation, o que torna um registrador por classe um pouco menos relevante - msdn.microsoft.com/en-us/library/hh534540.aspx
Jeremy Wiebe
15

Vantagem de usar "logger por arquivo" em NLog: você tem a possibilidade de gerenciar / filtrar logs por namespace e nome de classe. Exemplo:

<logger name="A.NameSpace.MyClass"      minlevel="Debug" writeTo="ImportantLogs" /> 
<logger name="A.NameSpace.MyOtherClass" minlevel="Trace" writeTo="ImportantLogs" /> 
<logger name="StupidLibrary.*"          minlevel="Error" writeTo="StupidLibraryLogs" />

<!-- Hide other messages from StupidLibrary -->
<logger name="StupidLibrary.*" final="true" /> 

<!-- Log all but hidden messages -->
<logger name="*" writeTo="AllLogs" /> 

O NLogger tem um trecho de código muito útil para fazer isso. O nloggersnippet criará o seguinte código:

private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

Portanto, apenas alguns toques no teclado e você terá um logger por classe. Ele usará o namespace e o nome da classe como o nome do logger. Para definir um nome diferente para o registrador de sua classe, você pode usar isto:

private static NLog.Logger logger = NLog.LogManager.GetLogger("MyLib.MyName");

E, como @JeremyWiebe disse, você não precisa usar truques para obter o nome da classe que está tentando registrar uma mensagem: O nome do logger (que geralmente é o nome da classe) pode ser facilmente registrado no arquivo (ou outro alvo) usando ${logger} em layout.

CoperNick
fonte
5

Posso ver algumas razões para essa escolha.

  • Você sempre saberá de onde veio uma instrução de log específica, se incluir o nome do logger em seu formato de saída de log.
  • Você pode controlar quais declarações de log você vê em um nível refinado, ligando ou desligando certos registradores, ou definindo seu nível.
Peter Recore
fonte
4

Também há um benefício de desempenho no caso do NLog. A maioria dos usuários usará

Logger logger = LogManager.GetCurrentClassLogger()

Pesquisar a classe atual a partir do rastreamento de pilha leva algum (mas não muito) desempenho.

Julian
fonte
3

Na maioria dos casos, o nome da classe fornece um bom nome para o logger. Ao verificar os arquivos de log, você pode ver a mensagem de log e associá-la diretamente a uma linha de código.

Um bom exemplo onde essa não é a melhor abordagem são os logs SQL do Hibernate. Há um logger compartilhado denominado "Hibernate.SQL" ou algo parecido, onde várias classes diferentes gravam SQL bruto em uma única categoria de logger.

Eric Hauser
fonte
2

Do ponto de vista do desenvolvimento, é mais fácil se você não precisar criar um objeto logger todas as vezes. Por outro lado, se você não fizer isso, mas sim criá-lo dinamicamente usando reflexão, isso tornará o desempenho mais lento. Para resolver isso, você pode usar o código a seguir, que cria o registrador de forma dinâmica e assíncrona:

using NLog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WinForms
{
    class log
    {

        public static async void Log(int severity, string message)
        {
            await Task.Run(() => LogIt(severity, message));
        }

        private static void LogIt(int severity, string message)
        {
            StackTrace st = new StackTrace();
            StackFrame x = st.GetFrame(2);     //the third one goes back to the original caller
            Type t = x.GetMethod().DeclaringType;
            Logger theLogger = LogManager.GetLogger(t.FullName);

            //https://github.com/NLog/NLog/wiki/Log-levels
            string[] levels = { "Off", "Trace", "Debug", "Info", "Warn", "Error", "Fatal" };
            int level = Math.Min(levels.Length, severity);
            theLogger.Log(LogLevel.FromOrdinal(level), message);

        }
    }
}
as9876
fonte
1

Duas razões vêm imediatamente à mente:

  1. Ter um log separado para cada classe torna mais fácil agrupar todas as mensagens / erros de log pertencentes a uma determinada classe.
  2. Ter um log dentro de uma classe permite que você registre detalhes internos que podem não estar acessíveis fora da classe (por exemplo, estado privado, informações que lidam com a implementação de uma classe, etc.).
Dan Tao
fonte
2
Você tem um registrador na classe, independentemente de ele ser definido no nível da classe ou globalmente. Loggers globais não estão "fora" da classe de uma perspectiva de visibilidade. Você ainda está fazendo referência ao logger global de dentro da classe em questão, para que tenha visibilidade total.
Robert
0

Provavelmente porque você deseja poder registrar métodos que são visíveis apenas para a classe sem quebrar o encapsulamento, isso também torna mais fácil usar a classe em outro aplicativo sem quebrar a funcionalidade de registro.

Rodrick Chapman
fonte
1
Isso torna difícil usar essa classe em outro aplicativo. Você tem que fazer referência à biblioteca de registro, quer goste ou não.
Piotr Perak
0

Facilita a configuração de appenders por namespace ou classe.

dotjoe
fonte
0

Se você estiver usando NLOG, você pode especificar o site de chamada na configuração, isso gravará o nome da classe e o método onde a instrução de registro foi localizada.

<property name="CallSite" value="${callsite}" />

Você pode então usar uma constante para o nome do registrador ou o nome do conjunto.

Isenção de responsabilidade: Não sei como o NLOG coleta essas informações, meu palpite seria uma reflexão, portanto, talvez você precise considerar o desempenho. Existem alguns problemas com os métodos Async se você não estiver usando o NLOG v4.4 ou posterior.

user6271979
fonte