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 класс прямо из Марек Стой.

Другие вопросы по тегам