Захватить имя пользователя с помощью log4net - PullRequest
19 голосов
/ 27 января 2011

В настоящее время я записываю все события log4net в базу данных, и, похоже, все работает отлично.Для захвата зарегистрированной учетной записи пользователя я использую этот фрагмент кода:

HttpContext context = HttpContext.Current;
if (context != null && context.User != null && context.User.Identity.IsAuthenticated)
{
    MDC.Set("user", HttpContext.Current.User.Identity.Name);
}

Код выглядит нормально, за исключением событий, с которыми не связан пользовательский контекст (т. Е. Пользователь на нашей общедоступной веб-странице),В этом случае захват log4net иногда записывает последнюю зарегистрированную учетную запись пользователя (плохо), а иногда записывает ноль (хорошо).Кто-нибудь получил эту функцию надежно работать во всех случаях?Мне кажется, я видел примечание, что MDC больше не является рекомендуемой функцией, но я не смог найти какие-либо рекомендуемые альтернативы.

Примечание. Мне кажется странным, что MDC настроен с учетной записью.имя, но никогда не очищается, если ни один пользователь не активен.Это может быть частью проблемы.Однако я не нашел ни одного фрагмента кода MDC, который бы также очищал имя пользователя.

Ответы [ 6 ]

27 голосов
/ 27 января 2011

Если информации, доступной в HttpContext, достаточно, то есть, если отправленный вами пример кода дает вам правильный ответ (за исключением проблемы MDC), и вы просто не захотите писать:

HttpContext context = HttpContext.Current; 
if (context != null && context.User != null && context.User.Identity.IsAuthenticated)
{     
  MDC.Set("user", HttpContext.Current.User.Identity.Name); 
} 

так часто, что вы можете добавить имя пользователя в свой журнал "автоматически", написав свой собственный шаблон PatternLayoutConverter для log4net.Их довольно легко написать, и вы можете настроить их в своей конфигурации регистрации log4net так же, как встроенные.

См. Этот вопрос для одного примера того, как написать собственный PatternLayoutConverter:

Настраиваемое свойство log4net PatternLayoutConverter (с индексом)

Используя пример по этой ссылке, вы можете сделать что-то вроде этого:

namespace Log4NetTest
{
  class HttpContextUserPatternConverter : PatternLayoutConverter
  {
    protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
    {
      string name = "";
      HttpContext context = HttpContext.Current;
      if (context != null && context.User != null && context.User.Identity.IsAuthenticated)
      {
        name = context.User.Identity.Name;
      }
      writer.Write(name);
    }
  }
}

Вы можете настроить это вlog4net примерно так:

  //Log HttpContext.Current.User.Identity.Name
  <layout type="log4net.Layout.PatternLayout">
    <param name="ConversionPattern" value="%d [%t] %-5p [User = %HTTPUser] %m%n"/>
    <converter>
      <name value="HTTPUser" />
      <type value="Log4NetTest.HttpContextUserPatternConverter" />
    </converter>
  </layout>

Кроме того, вы можете создать другие конвертеры шаблонов, которые используют параметр Option (см. пример по ссылке выше), чтобы извлечь конкретный элемент из HttpContext.Current.Items илиHttpContext.Current.Session collection.

Что-то вроде:

namespace Log4NetTest
{
  class HttpContextSessionPatternConverter : PatternLayoutConverter
  {
    protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
    {
      //Use the value in Option as a key into HttpContext.Current.Session
      string setting = "";

      HttpContext context = HttpContext.Current;
      if (context != null)
      {
        object sessionItem;
        sessionItem = context.Session[Option];
        if (sessionItem != null)
        {
          setting = sessionItem.ToString();
        }
        writer.Write(setting);
      }
    }
  }
}


namespace Log4NetTest
{
  class HttpContextItemPatternConverter : PatternLayoutConverter
  {
    protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
    {
      //Use the value in Option as a key into HttpContext.Current.Session
      string setting = "";

      HttpContext context = HttpContext.Current;
      if (context != null)
      {
        object item;
        item = context.Items[Option];
        if (item != null)
        {
          setting = item.ToString();
        }
        writer.Write(setting);
      }
    }
  }
}

Вы также можете найти эти ссылки полезными:

http://piers7.blogspot.com/2005/12/log4net-context-problems-with-aspnet.html

Здесь,блоггер предлагает другое решение для регистрации значений из HttpContext, чем то, что я предложил.Прочтите сообщение в блоге, чтобы увидеть его описание проблемы и ее решение.Чтобы подвести итог решения, он сохраняет объект в GlobalDiagnosticContext (более современное название для MDC).Когда log4net регистрирует значение объекта, он использует ToString ().Реализация его объекта извлекает значение из HttpContext:

Итак, вы можете сделать что-то вроде этого:

public class HttpContextUserNameProvider
{
  public override string ToString()
  {
    HttpContext context = HttpContext.Current;  
    if (context != null && context.User != null && context.User.Identity.IsAuthenticated)
    {
      return context.Identity.Name;
    }
    return "";
  }
}

Вы можете поместить экземпляр этого объекта в GlobalDiagnosticContext (MDC)в начале вашей программы, и она всегда будет возвращать правильное значение, так как она обращается к HttpContext.Current.

MDC.Set("user", new HttpContextUserNameProvider());

Это кажется намного проще, чем я предлагал!

Для полноты, если кто-тохочет знать, как сделать то же самое в NLog, NLog, по-видимому, делает большинство / всю информацию HttpContext доступной через своих aspnet- * LayoutRenderers:

https://github.com/nlog/nlog/wiki/Layout-Renderers

21 голосов
/ 25 января 2012

Согласно официальным документам API Log4Net , MDC устарела:

MDC устарела и была заменена свойствами. Текущая реализация MDC пересылается в ThreadContext.Properties.

Кроме этого MDC.Set принимает только строки в качестве значений, поэтому последнее решение от @wageoghe не может работать (то, которое использует HttpContextUserNameProvider)

Мое решение было использовать HttpContextUserNameProvider с log4net.GlobalContext, также предлагается в официальных документах API :

  • Добавьте это сразу после инициализации log4net (например, в Global.Application_Start)

    log4net.GlobalContext.Properties["user"] = new HttpContextUserNameProvider();
    
  • Добавить этот класс

    public class HttpContextUserNameProvider
    {
        public override string ToString()
        {
            HttpContext context = HttpContext.Current;
            if (context != null && context.User != null && context.User.Identity.IsAuthenticated)
            {
                return context.User.Identity.Name;
            }
            return "";
        }
    }
    
  • Измените конфигурацию log4net, добавив значение свойства "user", например:

    <layout type="log4net.Layout.PatternLayout" value="%property{user}"/>
    
10 голосов
/ 09 октября 2014

Начиная с версии Log4Net 1.2.11 , теперь вы можете просто использовать шаблон appender, чтобы получить авторизованного пользователя через запрос ASP .NET, например

%aspnet-request{AUTH_USER}
2 голосов
/ 27 января 2011

Есть два разных способа делать то, что вы хотите %identity и %username.

Они могут использоваться в вашем шаблоне appender.

Я делал это раньше, но снеожиданные результаты, пока я не прочитал следующий пост, который прояснил его для меня.

См. это сообщение: Log4Net не может найти свойство% username, когда я называю файл в моем приложении

2 голосов
/ 27 января 2011

Это чисто предположение, но это звучит очень похоже на то, что это может быть проблема, связанная с потоками общих запросов, то есть потоками ThreadPool. Когда вы устанавливаете значение MDC, оно связывается с текущим потоком, и этот поток возвращается в ThreadPool в конце запроса, а затем повторно используется для последующих запросов. Если значение не перезаписано, вы можете увидеть старые значения в новых запросах.

Рассмотрите возможность управления этими данными в событиях начала и окончания запроса, где вы можете задать имя пользователя при запуске запроса, а затем очистить его при завершении запроса. Это должно дать этим данным правильное время жизни, т.е. время жизни запроса.

0 голосов
/ 21 февраля 2013

Я использовал решение wageoghe и Gian Marco Gherardi, но вместо GlobalContext сразу установил контекст потока перед записью сообщения:

ThreadContext.Properties["user"] = new HttpContextUserNameProvider();
...