2009-06-30 10 views
5

Estoy trabajando en una configuración de Log4Net que registrará todas las excepciones no controladas. Necesito ciertas propiedades, basadas en el usuario, para agregar a cada entrada de registro. Lo configuré con éxito de la siguiente manera en mi evento Application_Error. Aquí está mi global.asax completoLog4Net, ThreadContext y Global.asax

Imports log4net 
Imports log4net.Config 

    Public Class Global_asax 
     Inherits System.Web.HttpApplication 

     'Define a static logger variable 
     Private Shared log As ILog = LogManager.GetLogger(GetType(Global_asax)) 

     Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs) 
      ' Fires when the application is started 
      ConfigureLogging() 
     End Sub 

     Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs) 
      ' Code that runs when an unhandled error occurs 
      Dim ex As Exception = Server.GetLastError() 
      ThreadContext.Properties("user") = User.Identity.Name 
      ThreadContext.Properties("appbrowser") = String.Concat(Request.Browser.Browser, " ", Request.Browser.Version) 
      If TypeOf ex Is HttpUnhandledException AndAlso ex.InnerException IsNot Nothing Then 
       ex = ex.InnerException 
      End If 
      log.Error(ex) 
      ThreadContext.Properties.Clear() 
     End Sub 

     Private Sub ConfigureLogging() 
      Dim logFile As String = Server.MapPath("~/Log4Net.config") 
      log4net.Config.XmlConfigurator.ConfigureAndWatch(New System.IO.FileInfo(logFile)) 
      log4net.GlobalContext.Properties("appname") = System.Reflection.Assembly.GetExecutingAssembly.GetName.Name 
     End Sub 
    End Class 

Esto parece estar funcionando bien. Sin embargo, tengo algunas preguntas que no puedo responder.

¿La forma en que estoy agregando las propiedades específicas del usuario, a través del threadcontext, es correcta? ¿Esto siempre registrará la información correcta, incluso bajo carga? ¿Cuándo usarías threadlogicalcontext? ¿Hay una mejor manera de hacer esto?

Gracias

Respuesta

11

No es seguro para cargar los valores de petición específica en ThreadContext así. La razón es que ASP.NET compartió subprocesos con las solicitudes de servicio. Lo hace con bastante frecuencia, de hecho.

En su lugar, podría utilizar LogicalThreadContext, sin embargo, eso simplemente almacena los valores en Contexto de llamadas, que se utiliza para Remoting.

AFAIK no hay almacenamiento de contexto específico HttpContext, entonces lo que puede hacer es asignar una instancia de "proveedor de valor" como su contexto de subproceso, y en tiempo de ejecución llamará a .ToString() en esta clase para obtener el valor.

public class HttpContextUserProvider 
{ 
    public override string ToString() 
    { 
     return HttpContext.Current.User.Identity.Name; 
    } 
} 

No es ideal, pero funciona.

+1

Solo tenga cuidado si usa codificación paralela ya que HttpContext.Current puede necesitar copiarse en cualquier subproceso de trabajo si se agregan al registro. –

+0

¿En qué parte de global.asax.cs pondría la llamada a HttpContextUserProvider? – Rory

+0

ASP.Net tiene un almacenamiento específico de contexto. Vea aquí: http://msdn.microsoft.com/en-us/library/system.web.httpcontext.items.aspx – MatteoSp

5

La respuesta de Ben es correcta.

Sin embargo, como algunos de los otros usuarios, todavía estaba un poco perdido sobre cómo proceder. Esta publicación log4net Context problems with ASP.Net thread agility y especialmente esta Marek Stój's Blog - log4net Contextual Properties and ASP.NET dan un poco más de contexto para el problema con algunos excelentes ejemplos de código.

recomiendo encarecidamente la aplicación de Marek støj, aunque el ThreadContext.Properties["UserName"] necesitaba ser reemplazado con ThreadContext.Properties["User"] en mi caso.

He añadido un método BeginRequest a mi clase Logger a la que llamo desde Application_AuthenticateRequest que carga todas las propiedades relevantes de log4net.

protected void Application_AuthenticateRequest(object sender, EventArgs e) 
{ 
    Logger.BeginRequest(Request); 
} 

Y el código del método:

public static void BeginRequest(System.Web.HttpRequest request) 
{ 
    if (request == null) return; 

    ThreadContext.Properties["ip_address"] = AdaptivePropertyProvider.Create("ip_address", IPNetworking.GetMachineNameAndIP4Address()); 
    ThreadContext.Properties["rawUrl"] = AdaptivePropertyProvider.Create("rawUrl", request.RawUrl); 

    if (request.Browser != null && request.Browser.Capabilities != null) 
     ThreadContext.Properties["browser"] = AdaptivePropertyProvider.Create("browser", request.Browser.Capabilities[""].ToString()); 

    if (request.IsAuthenticated && HttpContext.Current.User != null) 
     ThreadContext.Properties["User"] = AdaptivePropertyProvider.Create("user", HttpContext.Current.User.Identity.Name); 
} 

me encontré con que tenía que pasar en la solicitud objeto en lugar de utilizar HttpContext.Current.Request dentro del método. De lo contrario, perdería el usuario y la información de autenticación. Tenga en cuenta que la clase IPNetworking es mía, por lo que deberá proporcionar su propio método para obtener la IP del cliente. La clase AdaptivePropertyProvider es directamente de Marek Stój.

+1

Esto asigna el objeto de propiedad adaptativo a la colección de propiedades 'TheadContext', pero ¿no fue el problema inicial que no pudimos contar con que la recopilación persistiera correctamente? Creo que el manejador de propiedades adaptativas debe asignarse como una propiedad en el contexto global ... – starwed

+0

No estoy seguro de qué tipo de objeto global sería aplicable dado que estamos intentando iniciar sesión, en este caso, una sesión web determinada, entonces los valores serían contextualizados por el hilo individual, ¿correcto? – Shane