Logging
Para nuestras aplicaciones vamos a usar log4net para el logueo de mensajes/excepciones.
Para usarlo se debe agregar la referencia a la libreria log4net.dll al proyecto y en cada clase donde queramos usarlo lo definimos de la siguiente manera:
using System;
using log4net;
public class SampleClass
{
private int _variable1;
private int _variable2;
private static ILog _log = LogManager.GetLogger(typeof(SampleClass));
public void Method1(int x)
{
...
if(_log.IsDebugEnabled) _log.Debug(string.Format("Mehod1 Parameter value:{0}", x));
...
}
public void Method2(int y)
{
try
{
...
_log.Debug("Pass here");
...
}
catch(Exception ex)
{
_log.Error("Method2", ex);
throw; //Si necesitamos subir la excepcion debemos usarla de esta manera para que no se pierda la pila. No usar "throw ex;"
}
finally
{
...
}
}
}
La diferencia esta en que cuando necesitamos formatear una salida del debug debemos usar la condicion IsDebugEnabled.
Como vemos cada clase tiene su propio logger.
Existen varios métodos para loguear, en orden de prioridad son:
Debug(..)
Info(..)
Warn(..)
Error(..) [Usado para las excepciones]
Fatal(..)
Las excepciones debieran loguear siempre con el metodo Error, los demás metodos son opcionales. Lo interesante de este logger es que podemos dejar en nuestro código las instrucciones de logueo sin que afecten al sistema en producción ya que el logging se define por configuración, de esta manera si necesitamos correr el sistema en desarrollo, cambiamo la configuración y comenzamos a loguear los mensajes que solo nos sirven para estas instancias (Debug, Info, Warn).
Además el logging se puede definir para que lo haga por consola, archivo, db, event, etc.
Para las aplicaciones basadas en MVC vamos a tener en cuenta ciertas consideraciones especiales:
Logging:
Usar Debug en los lugares que creamos conveniente, sin temor al abuso. El mensaje de logueo debe ser entendible y con significado.
Las aplicaciones en produccion y QA deben enviaran el log de error a la DB correspondiente. Esto se configura en el web.config [actualmente no se está usando log por DB, sino por RollingFileAppender].
Excepciones:
Todas las clases del tipo System.Web.Mvc.Controller deben tener el atributo [NoanetHandleError]
Se va a usar el metodo estático NoanetException ExceptionManager.Handle(string message, Exception ex) del namespace Noanet.MVC.Common.Exceptions.
Cuando la acción en que se genera el error a manejar recibe datos sensibles de seguridad, utilizar el método HandleConfidential en lugar de Handle y agregar en el diccionario Data de la excepción la información que se quiere persistir en el log del error (por ejemplo ex.Data["username"] = model.UserName). Obviamente, el llenado del diccionario Data debe hacerse antes de llamar al método HandleConfidential.
En caso de controlar nosotros la excepcion debemos lanzar el resultado del metodo anterior hacia arriba. La clase NoanetException posee 4 propiedades que podemos llenar WhatHappened (descripcion del error para mostrar en la UI, obligatorio), WhatHasBeenAffected (que se vio afectado por el error),WhatActionsCanUserDo (lo que el usuario puede hacer a continuacion), SupportInformation (informacion sobre el contacto para el soporte).
En caso de que controlemos la excepcion y la disparemos hacia arriba podemos usar el diccionario de Data la clase NoanetException para poder almacenar información que pueda ayudarnos en caso de que lo necesitemos.
Cuando una excepcion llegue a la UI se direcciona a la vista Views/Shared/Error.aspx
La vista Error.aspx deriva de System.Web.Mvc.ViewPage<System.Web.Mvc.HandleErrorInfo> por lo tanto en Model tenemos el objeto del tipo NoanetException(debemos hacer un cast) y podemos acceder a las propiedades antes mencionadas.
Las excepciones no controladas llegan a la vista de Error de la misma manera, con la propiedada WhatHappened vacia.
Las excepciones del tipo SecurityException no siguen el mismo camino, son controladas en el metodo Application_Error de la clase Global.asax.cs
Usar el bloque finally para limpiar recursos.
Ejemplos:
Controlamos una excepcion y seguimos:
public bool IsDatabaseOnline(string connectionstring)
{
SqlConnection connection = null;
try
{
SqlConnection connection = new SqlConection(connectionstring)
connection.Open();
return true;
}
catch(Exception ex)
{
ExceptionManager.Handle("IsDatabaseOnline", ex);
return false;
}
finally
{
if(connection!=null) connection.Dispose();
}
}
Controlamos una excepcion y la lanzamos hacia arriba:
public ActionResult Index()
{
IList<string> name;
try
{
names = repository.GetAllNames();
}
catch(Exception ex)
{
NoanetException newEx = ExceptionManager.Handle("Error Obteniendo los clientes", ex);
newEx.WhatHasBeenAffected = "No se puede mostrar el listado de clientes";
newEx.WhatActionsCanUserDo = "Reintentar presionando F5";
throw newEx;
}
return View(names)
}
Global.asax.cs (SecurityException)
protected void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
if (ex.GetBaseException() is SecurityException)
{
if (!User.Identity.IsAuthenticated)
Response.Redirect("~/Login");
}
}
web.config
<configuration>
...
<configSections>
...
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" requirePermission="false" />
...
</configSections>
...
<log4net>
<appender name="TraceAppender" type="log4net.Appender.TraceAppender, log4net">
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern" value="%d{ABSOLUTE} %-5p %c{1}:%L - %m%n" />
</layout>
</appender>
<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
<bufferSize value="0" />
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionString value="Data Source=SERVER;Database=Noanet.License;Integrated Security=SSPI;" />
<commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],[Message],[Exception], [Application]) VALUES (@log_date, @thread, @log_level, @logger, @message, @exception, 'Cenit')" />
<parameter>
<parameterName value="@log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="@thread" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread" />
</layout>
</parameter>
<parameter>
<parameterName value="@log_level" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</layout>
</parameter>
<parameter>
<parameterName value="@logger" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<parameter>
<parameterName value="@message" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message" />
</layout>
</parameter>
<parameter>
<parameterName value="@exception" />
<dbType value="String" />
<size value="2000" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
</appender>
<root>
<priority value="ERROR"/>
<appender-ref ref="AdoNetAppender"/>
</root>
<logger name="NHibernate.SQL">
<level value="DEBUG" />
<appender-ref ref="TraceAppender" />
</logger>
</log4net>
...
<system.web>
...
<customErrors mode="On" defaultRedirect="error.htm"/>
...
</system.web>
...
</configuration>