2010-12-15 8 views
6

Chciałbym dodać prefiks kategorii do wszystkich wiadomości w istniejących komunikatach logowania. Jednak żmudne jest dodawanie tego przedrostka do wszystkich istniejących komunikatów logowania jeden po drugim. Czy istnieje sposób, w jaki mogę po prostu dodać atrybut do poziomu klasy, a następnie wszystkie wiadomości w tej klasie będą rejestrowane dla określonej kategorii?Jak dodać prefiks kategorii do wiadomości log4net?

Zamiast drodze teraz jak poniżej,

Log.Info("[Ref] Level 1 Starts ..."); 

naprawdę chcę coś takiego lub innego sposobu określenia log4net.ILog.

[LoggingCategory("Ref")] 
public class MyClass 
{ 
    public void MyMethod() 
    { 
     Log.Info("Level 1 Starts ..."); 
    } 
} 
+1

log4net przy użyciu hierarchii rejestratorów dla takiej cechy –

+0

Jaka jest kategoria faktycznie reprezentująca, tj. W jaki sposób obiekty logujące się do tej samej kategorii będą powiązane? Teoretycznie możesz zrobić to wszystko w config przez powiązania między rejestratorami i aplikantami, które odpowiadają każdej kategorii, np. Wszystkie logowania z obiektów w przestrzeniach nazw X, Y i Z używają aplikacji dostarczającej reprezentującej kategorię A. Jednak w zależności od odpowiedzi na moje pierwsze pytanie, które może stać się uciążliwe. –

+0

Co staram się tutaj osiągnąć, to aby pliki dziennika były łatwiejsze do analizowania, gdy są używane w określonych klasach. Na przykład, jeśli klasa A i klasa B są ze sobą powiązane i chciałbym szczególnie, aby wszystkie linie powiązane z klasami A i B zostały usunięte z głównego pliku dziennika. Moje doświadczenie, które zwykle znajdowałem w ten sposób, było dla mnie bardzo pomocne. – tonyjy

Odpowiedz

4

ciekawy problem, szorstki próba ...

Log4NetLogger - rejestrowanie adapter

public class Log4NetLogger 
{ 
    private readonly ILog _logger; 
    private readonly string _category; 

    public Log4NetLogger(Type type) 
    { 
     _logger = LogManager.GetLogger(type); 
     _category = GetCategory(); 
    } 

    private string GetCategory() 
    { 
     var attributes = new StackFrame(2).GetMethod().DeclaringType.GetCustomAttributes(typeof(LoggingCategoryAttribute), false); 
     if (attributes.Length == 1) 
     { 
      var attr = (LoggingCategoryAttribute)attributes[0]; 
      return attr.Category; 
     } 
     return string.Empty; 
    } 

    public void Debug(string message) 
    { 
     if(_logger.IsDebugEnabled) _logger.Debug(string.Format("[{0}] {1}", _category, message)); 
    } 
} 

LoggingCategoryAttribute - dotyczy klas

[AttributeUsage(AttributeTargets.Class)] 
public class LoggingCategoryAttribute : Attribute 
{ 
    private readonly string _category; 

    public LoggingCategoryAttribute(string category) 
    { 
     _category = category; 
    } 

    public string Category { get { return _category; } } 
} 

LogTester - implementacja testu

[LoggingCategory("LT")] 
public class LogTester 
{ 
    private static readonly Log4NetLogger Logger = new Log4NetLogger(typeof(LogTester)); 

    public void Test() 
    { 
     Logger.Debug("This log message should have a prepended category"); 
    } 
} 
+0

Wygląda to całkiem fajnie, ale wywołanie GetCategory może być kosztowne przy każdym wywołaniu rejestrowania. – wageoghe

+0

Czy możesz przenieść GetCategory do konstruktora? Wtedy uniknie połączenia GetCategory na każdym wywołaniu rejestrowania. – tonyjy

+0

@tonyjy, @wageoghe - dobry połów, zmodyfikował kod, by wywołać GetCategory raz w czasie budowy. – Jonathan

7

Pytasz, jak to zrobić za pomocą atrybutu. @ Sugestia Jonathana wygląda na to, że prawdopodobnie będzie dobrze działać, ale możesz osiągnąć dobry wynik dzięki wykorzystaniu wbudowanych funkcji log4net.

Jeśli chcesz pogrupować klasy w "kategorie", możesz pobrać program rejestrujący na podstawie nazwy kategorii, a nie nazwy klasy. Po skonfigurowaniu formatu wyjściowego można użyć tokenu formatowania rejestratora, aby log4net zapisał nazwę rejestratora w danych wyjściowych.

Zazwyczaj można by odzyskać rejestratora na podstawie nazwy klasy, jak to:

public class Typical 
{ 
    private static readonly ILog logger = 
     LogManager.GetLogger 
      (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 

    public void F() 
    { 
    logger.Info("this message will be tagged with the classname"); 
    } 
} 

jest całkowicie dopuszczalne, aby odzyskać rejestratory podstawie arbitralnej nazwą jak to:

public class A 
{ 
    private static readonly ILog logger = LogManager.GetLogger("REF"); 

    public void F() 
    { 
    logger.Info("this message will be tagged with REF"); 
    } 
} 

public class B 
{ 
    private static readonly ILog logger = LogManager.GetLogger("REF"); 

    public void F() 
    { 
    logger.Info("this message will be tagged with REF"); 
    } 
} 

public class C 
{ 
    private static readonly ILog logger = LogManager.GetLogger("UMP"); 

    public void F() 
    { 
    logger.Info("this message will be tagged with UMP"); 
    } 
} 

W poprzednim przykładzie , klasy A i B są uważane za znajdujące się w tej samej "kategorii", więc odzyskały program rejestrujący o tej samej nazwie. Klasa C jest w innej kategorii, więc odzyskała rejestrator o innej nazwie.

Można skonfigurować rejestratory (w pliku config) z własnym „kategorii” hierarchii:

App 
App.DataAccess 
App.DataAccess.Create 
App.DataAccess.Read 
App.DataAccess.Update 
App.DataAccess.Delete 
App.UI 
App.UI.Login 
App.UI.Query 
App.UI.Options 

Można również skonfigurować format wyjściowy Rejestrator zalogować tylko część w pełni kwalifikowanej nazwy rejestratora. Coś takiego:

%logger:2 

Aby uzyskać 2 ostatnie części pełnej nazwy.Na przykład, jeśli nazwa w pełni kwalifikowana klasie jest:

NameSpaceA.NameSpaceB.NameSpaceC.Class 

Wtedy powyżej Format byłoby wyjście to jak nazwa Rejestrator:

NameSpaceC.Class 

nie jestem 100% pewny o składni bo Haven” Użyłem tego i nie mogę znaleźć dobrego przykładu w tej chwili.

Jedną wadą tego podejścia jest to, że musisz zdefiniować i zapamiętać, jakie są twoje kategorie i musisz zdecydować, jaka kategoria jest dla każdej klasy (masz również ten problem, jeśli chcesz ozdobić każdą klasę atrybutem zawierającym jego kategoria). Ponadto, jeśli masz kilka klas w tej samej kategorii, nie możesz włączyć ani wyłączyć logowania ani zmienić poziomu rejestrowania dla podzbioru tych klas.

Może warto byłoby, aby zalogować jeden z nazw w hierarchii przestrzeni nazw:

Może ty „kategoryzacji” swoje klasy na podstawie ich nazw. Tak więc możesz chcieć zarejestrować bezpośrednią macierzystą przestrzeń nazw klasy jako jej kategorię.

Tak więc, dla w pełni kwalifikowanej nazwy klasy powyżej, możesz chcieć zarejestrować "NameSpaceC" jako "kategorię" lub nazwę rejestratora.

Nie jestem pewien, czy można to zrobić po wyjęciu z pudełka z log4net, ale można łatwo napisać PatternLayoutConverter, aby uzyskać nazwę rejestratora i usunąć nazwę klasy i wszelkie przestrzenie nazw "wyższego poziomu".

Oto link do przykładu niestandardowego PatternLayoutConverter. Pobiera parametr, który w moim przypadku chciałbym użyć do wyszukania wartości w słowniku. W tym przypadku parametr mógłby reprezentować przesunięcie od END w pełni kwalifikowanej nazwy rejestratora (taka sama interpretacja jak parametr wbudowanego obiektu układu logger nazwa wbudowanego log4net), ale można dodać dodatkowy kod, aby logować TYLKO pojedynczą przestrzeń nazw w tym indeks.

Custom log4net property PatternLayoutConverter (with index)

Ponownie, biorąc pod uwagę to pełną nazwę klasy:

NameSpaceA.NameSpaceB.NameSpaceC.Class 

Można rozważyć natychmiastowe nazw rodzic być "kategoria". Jeśli zdefiniowano niestandardowy PatternLayoutConverter, category i zajęło to parametr, a następnie konfiguracja może wyglądać następująco:

%category 

Domyślnie byłoby zwrócić podciąg między ostatnim a przedostatnim '.' znaków. Podany parametr może zwrócić dowolną dyskretną przestrzeń nazw w górę łańcucha.

PatternLayoutConverter może wyglądać mniej więcej tak (niesprawdzone):

class CategoryLookupPatternConverter : PatternLayoutConverter 
    { 
    protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent) 
    { 
     //Assumes logger name is fully qualified classname. Need smarter code to handle 
     //arbitrary logger names. 
     string [] names = loggingEvent.LoggerName.Split('.'); 
     string cat = names[names.Length - 1]; 
     writer.Write(setting); 
    } 
    } 

Albo, używając właściwości Option, aby uzyskać n-tej nazwy przestrzeni nazw (w stosunku do końca):

class CategoryLookupPatternConverter : PatternLayoutConverter 
    { 
    protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent) 
    { 
     //Assumes logger name is fully qualified classname. Need smarter code to handle 
     //arbitrary logger names. 
     string [] names = loggingEvent.LoggerName.Split('.'); 
     string cat; 
     if (Option > 0 && Option < names.Length) 
     { 
     cat = names[names.Length - Option]; 
     } 
     else 
     { 
     string cat = names[names.Length - 1]; 
     } 
     writer.Write(setting); 
    } 

    } 

Pomysł z @ Jonathan jest całkiem fajny, ale dodaje trochę dodatkowego kodowania z twojej strony, aby zdefiniować i utrzymać nowe opakowanie loggera (ale wiele osób robi to i nie uważa tego za szczególnie uciążliwe obciążenie). Oczywiście, moje niestandardowe pomysły PatternLayoutConverter wymagają również niestandardowego kodu z Twojej strony.

Kolejną wadą jest to, że GetCategory wygląda tak, że wywołanie każdego połączenia rejestracyjnego może być dość kosztowne.