Cómo usar log4net con inyección de dependencia

78

Estoy tratando de averiguar cuál es el patrón y el uso correctos de log4net con un marco de inyección de dependencia.

Log4Net usa la interfaz ILog pero requiere que llame

LogManager.GetLogger(Reflection.MethodBase.GetCurrentMethod().DeclaringType)

en cada clase o método donde necesito registrar información. Esto parece ir en contra de los principios de IoC y me une al uso de Log4Net.

¿Debería de alguna manera poner otra capa de abstracción en alguna parte?

Además, necesito registrar propiedades personalizadas como el nombre de usuario actual como este:

log4net.ThreadContext.Properties["userName"] = ApplicationCache.CurrentUserName;

¿Cómo puedo encapsular esto para que no tenga que recordar hacerlo cada vez y aún así mantener el método actual que está registrando? ¿Debo hacer algo como esto o me estoy equivocando totalmente?

public static class Logger
{
    public static void LogException(Type declaringType, string message, Exception ex)
    {
        log4net.ThreadContext.Properties["userName"] = ApplicationCache.CurrentUserName;
        ILog log = LogManager.GetLogger(declaringType);
        log.Error(message, ex);
    }
}
Miqueas
fuente
1
La pregunta más interesante es ¿qué contenedor DI estaba usando?
CodeMonkeyKing

Respuestas:

61

Creo que aquí no estás viendo el bosque por los árboles. ILog y LogManager son una fachada liviana equivalente a casi 1: 1 de Apache commons-logging, y en realidad no acoplan su código al resto de log4net.

<rant>
También descubrí que casi siempre cuando alguien crea un contenedor MyCompanyLogger alrededor de log4net, pierde el punto y pierde capacidades importantes y útiles del marco, desecha información útil, pierde las ganancias de rendimiento posibles usando incluso la interfaz ILog simplificada, O todo lo anterior. En otras palabras, envolver log4net para evitar el acoplamiento es un anti-patrón .
</rant>

Si siente la necesidad de inyectarlo, haga que su instancia de registrador sea accesible a través de una propiedad para habilitar la inyección, pero cree una instancia predeterminada a la antigua.

En cuanto a incluir el estado contextual en cada mensaje de registro, debe agregar una propiedad global cuya ToString()resolución sea lo que está buscando. Como ejemplo, para el tamaño de pila actual:

public class TotalMemoryProperty
{
    public override string ToString()
    {
        return GC.GetTotalMemory(false).ToString();
    }
}

Luego, para enchufarlo durante el inicio:

GlobalContext.Properties["TotalMemory"] = new TotalMemoryProperty();
Jeffrey Hantin
fuente
7
Tu perorata está fuera de lugar aquí. Este tipo preguntaba cómo inyectar una instancia de ILog a sus objetos que están dentro de un contenedor. Hay muchas razones para hacerlo.
CodeMonkeyKing
1
@CodeMonkeyKing: La perorata se dirigió a su enfoque sugerido de crear una capa de abstracción entre log4net y su código, y especialmente en el bloque de código de muestra al final de la pregunta.
Jeffrey Hantin
4
@JeffreyHantin Puede ser un anti-patrón en su opinión, pero he descubierto que hay tantas opiniones como ingenieros escribiendo código. Mi opinión, por supuesto :)
CodeMonkeyKing
1
Me doy cuenta de que esta es una publicación antigua, pero ¿podría explicar por qué un contenedor de este tipo es "malo"? ILog y LogManager residen en el espacio de nombres log4net, y al usar estos tipos en todo mi código, ¿no me está acoplando esto a log4net? ¿Que me estoy perdiendo aqui?
Andrew Stephens
1
Envolver log4net es una buena práctica. El problema no es envolver la biblioteca, lo está haciendo tan mal.
Dan
2

Lo que hice fue crear mi propia interfaz e hice que una clase implementara esa interfaz que usaba Log4Net e inyectara esa clase. De esa manera, no está atado a Log4Net ... puede simplemente crear otra clase para un nuevo registrador e inyectar esa clase.

CSharpAtl
fuente
2

Si está realmente preocupado por tener varios registradores, siempre puede declarar un registrador global y llamarlo para todas sus necesidades de registro. La desventaja de esto es que pierde la capacidad incorporada para identificar la clase desde la que se emitió el registro, pero también puede inyectar su propio mensaje personalizado en el registro para identificar la clase.

Depende de lo robusto que desee que sea el registro. También puede simplemente poner el registrador en la clase principal y dejar que todas las excepciones no manejadas aparezcan para el registro.

Dillie-O
fuente
¿Pero seguramente desea registrar más que solo excepciones?
UpTheCreek
¿Estás sugiriendo el patrón Singleton aquí? Espero que no.
Nate Zaugg
2

Otra forma de hacerlo sería conectar la secuencia de registro del contenedor de la interfaz log4net ILog como esta: (usando Castle en un contexto ASP.NET a continuación como ejemplo)

container.Register(Component.For<ILog>().LifeStyle
    .PerWebRequest.UsingFactoryMethod(() => LogManager.GetLogger(
    MethodBase.GetCurrentMethod().DeclaringType)));

y simplemente inyecte el constructor ILog siempre que lo necesite.

santos
fuente
Esto funciona mejor, creo, ya que obtiene el registrador correcto para la clase de implementación que se está construyendo:Component.For<ILog>().UsingFactoryMethod((kernel, model, cc) => LogManager.GetLogger(cc.Handler.ComponentModel.Implementation))
Mark