2011-01-27 16 views
14

Attualmente sto scrivendo tutti gli eventi di log4net in un database e sembra funzionare correttamente. Per catturare la loggato account utente io uso questo pezzo di codice:Acquisisci nome utente con log4net

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

Il codice sembra ok, tranne che per gli eventi che non hanno alcun contesto utente ad essi associati (ad esempio un utente sulla nostra pagina web pubblica.). In tal caso, l'acquisizione di log4net sembra talvolta scrivere l'ultimo account utente registrato (errato) e talvolta scrivere un valore null (buono). Qualcuno ha questa funzione per funzionare in modo affidabile in tutti i casi? Credo di aver visto una nota che MDC non è più una funzione consigliata da utilizzare, ma non sono riuscito a trovare alternative consigliate.

Nota: Trovo strano che MDC sia impostato con un nome account, ma mai cancellato se nessun utente è attivo. Questo potrebbe essere parte del problema. Tuttavia, non ho trovato alcun codice di estensione MDC che cancelli anche il nome utente.

+0

Non si conosce MDC, ma in una clausola else si può impostare "utente" su 'null'? – Amy

+0

D'accordo, ma è per questo che ho aggiunto la nota sopra. Sono piuttosto stanco di impostare "utente" su null quando TUTTI gli estratti di codice appaiono esattamente come quelli che ho incollato. Lo verificherò, ma non è l'ideale se questo codice di registrazione fallisce una volta implementato in produzione con un numero maggiore di clienti rispetto al nostro DB di test. –

+0

A partire da Log4Net versione 1.2.11 è possibile ottenere il nome utente utilizzando un modello di appender. Vedi qui http://stackoverflow.com/a/26277219/203371 –

risposta

21

Se le informazioni che è disponibile nel HttpContext è sufficiente, cioè, se il codice di esempio che hai postato ti dà la risposta giusta (tranne che per il rilascio MDC) e si sarebbe solo piuttosto semplicemente non scrivere:

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

tanto spesso, allora si potrebbe essere in grado di aggiungere il nome utente per il registro "automaticamente" scrivendo il proprio PatternLayoutConverter personalizzato per log4net. Sono abbastanza facili da scrivere e puoi configurarli nella configurazione di log log4net proprio come quelli incorporati.

Vedere questa domanda per un esempio di come scrivere un PatternLayoutConverter personalizzato:

Custom log4net property PatternLayoutConverter (with index)

Utilizzando l'esempio a quel link, si potrebbe essere in grado di fare qualcosa del genere:

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); 
    } 
    } 
} 

Si configurerebbe questo in log4net qualcosa del genere:

//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> 

Inoltre, è possibile creare altri convertitori di pattern che utilizzano il parametro Option (vedere l'esempio nel collegamento sopra riportato) per estrarre un elemento specifico da HttpContext.Current.Items o HttpContext.Current.Collezioni di sessione.

Qualcosa di simile:

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); 
     } 
    } 
    } 
} 

si potrebbe anche trovare questi link utili:

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

Qui, il blogger propone una soluzione diversa per i valori di registrazione da HttpContext di quello che ho proposto. Leggi il post del blog per vedere la sua descrizione del problema e per la sua soluzione. Per riassumere la soluzione, memorizza un oggetto in GlobalDiagnosticContext (il nome più moderno per MDC). Quando log4net registra il valore dell'oggetto che utilizza ToString(). l'attuazione del suo oggetto recupera un valore dal HttpContext:

Quindi, si potrebbe fare qualcosa di simile:

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 ""; 
    } 
} 

È possibile inserire un'istanza di questo oggetto nel GlobalDiagnosticContext (MDC) all'inizio del vostro programma e restituirà sempre il valore corretto poiché accede a HttpContext.Current.

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

Questo sembra molto più semplice di quello che ho proposto!

Per completezza, se qualcuno vuole sapere come fare la stessa cosa in NLog, NLog sembra fare la maggior parte/tutte le informazioni HttpContext disponibile tramite il suo "aspnet- *" LayoutRenderers:

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

+0

+1 per la soluzione 'HttpContextUserNameProvider'. Questo aggira il problema di archiviare direttamente i dati del nome utente sui thread e invece fornisce i dati su richiesta - soluzione molto accurata. Dovresti comunque assicurarti che sia stata impostata un'istanza per ogni richiesta. –

+1

Inizialmente ho provato la seconda soluzione, ma sembra che MDC.set abbia solo una stringa, una stringa. Quindi non funzionerà davvero ... – CtrlDot

+0

@CtrlDot 'MDC' è deprecato, questa soluzione funziona con' GlobalContext', che sostituisce alcuni dei suoi casi d'uso. Usa 'GlobalContext.Properties [" user "] = new HttpContextUserNameProvider();'. (E forse questo modo di impostare le proprietà personalizzate era già disponibile con MDC.) –

2

Questa è pura speculazione, ma sembra molto simile a un problema correlato ai thread di richiesta condivisi, ad esempio ThreadPool threads. Quando si imposta un valore MDC, questo viene associato al thread corrente e tale thread verrà restituito al ThreadPool alla fine della richiesta e quindi riutilizzato per le richieste successive. Se il valore non viene sovrascritto, è possibile visualizzare i vecchi valori sulle nuove richieste.

Considerare la gestione di questi dati negli eventi di inizio e fine della richiesta, in cui è possibile impostare il nome utente all'avvio di una richiesta e quindi cancellarlo alla fine della richiesta. Questo dovrebbe dare a questi dati la durata corretta, cioè per tutta la durata della richiesta.

+0

Risposta intelligente. Non sono sicuro che questo sia davvero il nostro problema, ma è qualcosa che cercherò. C'è un altro modo per impostare un nome utente senza l'uso di MDC (cioè semplicemente come parte della chiamata della funzione di registrazione)? –

+0

@Simon Se si dispone di un livello di riferimento indiretto per le chiamate di registrazione, è possibile includere queste informazioni nel messaggio di registro, ad es. pre-in sospeso al messaggio. –

2

Esistono due modi diversi per eseguire ciò che si desidera %identity e %username.

Questi possono essere utilizzati nel modello di appender.

L'ho fatto prima ma con risultati inaspettati fino a quando ho letto il seguente post che ha chiarito per me.

Vedere questo annuncio: Log4Net can't find %username property when I name the file in my appender

+0

Mi sembra che% identity funzioni solo quando l'assembly di registrazione ha un riferimento a System.Web (E quindi l'accesso a HttpContext.Current.User, che presumo sia dove% identity trova il nome utente) –

+0

% identity ha funzionato per me nel mio App MVC Vedi https://logging.apache.org/log4net/log4net-1.2.13/release/sdk/log4net.Layout.PatternLayout.html per ulteriori opzioni. – garethb

18

Secondo Log4Net official API docs, MDC è deprecato:

Diverso da quello che MDC.Set accettano solo le stringhe come valori, quindi l'ultima soluzione da @wageoghe non può funzionare (quella che usa HttpContextUserNameProvider)

La mia soluzione è stata quella di utilizzare HttpContextUserNameProvider con log4net.GlobalContext, ha anche suggerito in official API docs:

  • Aggiungere questo dispensati dopo l'inizializzazione log4net (per esempio in Global.Application_Start)

    log4net.GlobalContext.Properties["user"] = new HttpContextUserNameProvider(); 
    
  • Aggiungi questa classe

    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 ""; 
        } 
    } 
    
  • Modifica configurazione log4net aggiungendo il valore della proprietà "utente", per esempio:

    <layout type="log4net.Layout.PatternLayout" value="%property{user}"/> 
    
+1

dopo aver provato alcune soluzioni, sentire questa dovrebbe essere la risposta. –

+3

Questa soluzione non ha il supporto di appendici bufferizzati come ado net appender: i provider di proprietà vengono valutati per impostazione predefinita al momento del flush del buffer. Quindi utilizzerà il contesto HTTP della richiesta che ha causato lo svuotamento, non della richiesta che ha emesso il log. Fortunatamente il provider deve solo implementare 'log4net.Core.IFixingRequired' per essere valutato al tempo di registrazione piuttosto che al tempo di chiusura del buffer. Vedere [la mia risposta] (http://stackoverflow.com/a/32308262/1178314) su un'altra domanda per un esempio. –

-1

Ho usato la soluzione di wageoghe e Gian Marco Gherardi, ma piuttosto che GlobalContext hanno creato il contesto dispensati filo prima la registrazione del messaggio:

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

no. non farlo. –

+0

@CasperLeonNielsen per favore spiegate perché non – Thierry

8

a partire dalla versione Log4Net 1.2.11 è ora possibile utilizzare semplicemente il modello appender per ottenere l'utente autorizzato tramite l'ASP .NET richiesta per esempio

%aspnet-request{AUTH_USER} 
+1

Questo ottiene la variabile del server AUTH_USER, che viene impostata dall'intestazione della richiesta. Quindi questo formato funzionerebbe solo in scenari limitati, come l'app ASP.NET che utilizza l'autenticazione di Windows. –