Log4Net, ThreadContext и Global.asax
Я работаю над конфигурацией Log4Net, которая будет регистрировать все необработанные исключения. Мне нужны определенные свойства, основанные на пользователе, которые будут добавлены к каждой записи журнала. Я успешно установил это следующим образом в моем событии Application_Error. Вот мой полный 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
Похоже, это работает нормально. Однако у меня есть несколько вопросов, на которые я не могу ответить.
Является ли способ, которым я добавляю специфичные для пользователя свойства через threadcontext, правильно? Будет ли это всегда записывать правильную информацию, даже под нагрузкой? Когда бы вы использовали threadlogicalcontext? Есть лучший способ сделать это?
Спасибо
2 ответа
Не безопасно загружать специфичные для запроса значения в ThreadContext
как это. Причина в том, что ASP.NET разделяет потоки для обслуживания запросов. На самом деле это происходит довольно часто.
Вы могли бы вместо этого использовать LogicalThreadContext
Однако это просто сохраняет значения в Call Context, который используется для удаленного взаимодействия.
AFAIK: нет никакого специфичного для HttpContext хранилища контекста, поэтому вы можете вместо этого назначить экземпляр "провайдера значений" в качестве контекста потока, и во время выполнения он вызовет.ToString() для этого класса, чтобы получить значение.
public class HttpContextUserProvider
{
public override string ToString()
{
return HttpContext.Current.User.Identity.Name;
}
}
Это не идеально, но это работает.
Ответ Бена правильный.
Однако, как и некоторые другие пользователи, я все еще был немного растерян, как действовать. Эта проблема в контексте контекста log4net с постом о гибкости потоков ASP.Net и особенно в этом блоге Марека Стоя - в разделе "Контекстные свойства log4net" и "ASP.NET" дается еще один контекст для решения проблемы с некоторыми прекрасными примерами кода.
Я очень рекомендую реализацию Марека Стоя, хотя ThreadContext.Properties["UserName"]
необходимо заменить на ThreadContext.Properties["User"]
в моем случае.
Я добавил метод BeginRequest в свой класс Logger, который я вызываю из Application_AuthenticateRequest, который загружает все соответствующие свойства log4net.
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
Logger.BeginRequest(Request);
}
И код метода:
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);
}
Я обнаружил, что должен был передать объект Request вместо использования HttpContext.Current.Request
в рамках метода. В противном случае я бы потерял информацию о пользователе и аутентификации. Обратите внимание, что IPNetworking
Класс мой, поэтому вам нужно будет предоставить свой собственный метод получения клиентского IP. AdaptivePropertyProvider
класс прямо из Марек Стой.