Log4J: Strategien zum Erstellen von Logger-Instanzen

70

Ich habe mich für das Log4J-Protokollierungsframework für ein neues Java-Projekt entschieden. Ich frage mich, welche Strategie ich zum Erstellen / Verwalten von Logger-Instanzen verwenden soll und warum.

  • eine Instanz von Logger pro Klasse, z

    class Foo {
        private static final Logger log = Logger.getLogger(Foo.class);
    }
    
  • eine Instanz von Logger pro Thread
  • eine Instanz von Logger pro Anwendung
  • Horizontales Slicing: Eine Instanz von Logger in jeder Ebene einer Anwendung (z. B. die Ansichtsebene, die Controller-Ebene und die Persistenz-Ebene).
  • Vertikales Slicing: Eine Instanz von Logger in funktionalen Partitionen der Anwendung

Hinweis: Dieses Problem wird in diesen Artikeln bereits teilweise berücksichtigt:

Was ist der Aufwand für die Erstellung eines Log4j-Loggers?

Adrian
quelle
Hinweis: Der "ultimative" Weg besteht höchstwahrscheinlich darin, den Aufrufstapel zu untersuchen, um den Anrufer zu erhalten. Nach meiner Erfahrung sind Java-Programme robuster, wenn Sie Dinge explizit codieren, anstatt sich darauf zu verlassen, sie magisch zu erhalten.
Thorbjørn Ravn Andersen
Ein Fehler in der JRE führt dazu, dass die Protokollierung falsch ist.
Thorbjørn Ravn Andersen

Antworten:

46

Normalerweise werden Logger pro Klasse eingerichtet, da dies eine nette logische Komponente ist. Threads sind bereits Teil der Protokollnachrichten (sofern Ihr Filter sie anzeigt), sodass das Aufteilen von Protokollierern auf diese Weise wahrscheinlich redundant ist.

In Bezug auf anwendungs- oder schichtbasierte Logger besteht das Problem darin, dass Sie einen Platz finden müssen, an dem Sie das Logger-Objekt anbringen können. Keine wirklich große Sache. Das größere Problem ist, dass einige Klassen auf mehreren Ebenen aus mehreren Anwendungen verwendet werden können. Es kann schwierig sein, Ihren Logger richtig zu machen. Oder zumindest knifflig.

... und das Letzte, was Sie wollen, sind schlechte Annahmen in Ihrem Protokollierungssetup.

Wenn Sie sich für Anwendungen und Ebenen interessieren und einfache Trennstellen haben, ist der NDC der richtige Weg. Der Code kann manchmal etwas übertrieben sein, aber ich weiß nicht, wie oft ich von einem genauen Kontextstapel gespeichert wurde, der mir zeigt, dass Foo.bar () von Anwendung X in Schicht Y aufgerufen wurde.

PSpeed
quelle
Wie wäre es, wenn ich eine Singelton-Klasse habe, die eine statische Instanz des Loggers hat? und alle Klassen auf dieses Mitglied der Singelton-Klasse zugreifen lassen?
Jack
33

Die am häufigsten verwendete Strategie besteht darin, einen Logger pro Klasse zu erstellen. Wenn Sie neue Threads erstellen, geben Sie ihnen einen nützlichen Namen, damit ihre Protokollierung leicht unterschieden werden kann.

Das Erstellen von Protokollierern pro Klasse bietet den Vorteil, dass Sie die Protokollierung in der Paketstruktur Ihrer Klassen ein- und ausschalten können:

log4j.logger.org.apache = INFO
log4j.logger.com.example = DEBUG
log4j.logger.com.example.verbose = ERROR

Das oben Gesagte würde den gesamten Apache-Bibliothekscode auf INFOLevel setzen und die Protokollierung von Ihrem eigenen Code auf DEBUGLevel umstellen, mit Ausnahme des ausführlichen Pakets.

rsp
quelle
1
Ich sehe den Vorteil, wenn Sie die Protokollierungsstufe nach Paketstruktur steuern möchten. Gibt es einen besseren Weg, dies zu erreichen, als private static Logger logger = Logger.getLogger(MyClass.class);in jede Klasse zu kopieren ?
Marcus Leon
Da Sie eine Möglichkeit benötigen, eine Logger-Instanz sowohl für Ihren Paket- als auch für Ihren Klassennamen bereitzustellen, sehe ich keine Alternative zum Erstellen einer Instanz pro Klasse. Wenn Sie einen Logger pro Paket erstellen würden, müssten Sie dennoch einen Verweis auf die Logger-Instanz abrufen, um ihn verwenden zu können.
rsp
Welchen LoggerKonstruktor verwenden Sie, um einen Loggerfür ein Paket zu erstellen ? Ist der getLogger(String name)Name nur ein String, der das Paket angibt (dh "abc")? Wenn Sie dies tun, können Sie die Protokollierungsstufe so konfigurieren, wie Sie a la erwähnen log4j.logger.a.b.c = INFO?
Marcus Leon
2
Ja, ich denke, Sie würden die String-Variante wie in verwenden. private static Logger logger = Logger.getLogger(MyClass.class.getPackage());Die Logger-Hierarchie funktioniert auch, wenn Sie Logger mit den Namen "ab", "abc" usw. erstellen. Siehe das Handbuch unter logging.apache.org/log4j/1.2/manual.html
rsp
14

Ich bin mir sicher, dass dies keine bewährte Methode ist, aber ich habe zuvor einige Startzeiten für Anwendungen festgelegt, um Codezeilen zu speichern. Insbesondere beim Einfügen von:

Logger logger = Logger.getLogger(MyClass.class);

... Entwickler vergessen oft, "MyClass" in den aktuellen Klassennamen zu ändern, und mehrere Logger zeigen immer auf die falsche Stelle. Das ist schlecht.

Ich habe gelegentlich geschrieben:

static Logger logger = LogUtil.getInstance(); 

Und:

class LogUtil {
   public Logger getInstance() {
      String callingClassName = 
         Thread.currentThread().getStackTrace()[2].getClass().getCanonicalName();
      return Logger.getLogger(callingClassName);
   }
}

Die "2" in diesem Code könnte falsch sein, aber das Wesentliche ist da; Nehmen Sie einen Leistungstreffer, um (beim Laden der Klasse als statische Variable) den Klassennamen zu finden, damit ein Entwickler nicht wirklich die Möglichkeit hat, dies falsch einzugeben oder einen Fehler einzuführen.

Ich bin im Allgemeinen nicht begeistert von Leistungseinbußen, um Entwicklerfehler zur Laufzeit zu vermeiden, aber wenn es einmal als Singleton passiert? Klingt für mich oft nach einem guten Handel.

Dean J.
quelle
Ich habe Ihre Antwort aktualisiert - sie wurde nicht kompiliert und ich habe getClass (). GetCanonicalName () anstelle von nur getClassName () verwendet. Oh, und 2 ist in der Tat die magische Zahl.
Ripper234
Ich habe aus den von Ihnen angegebenen Gründen eine ähnliche Klasse geschrieben. Damit alles benötigt wird, ist: static final ClassLogger log = new ClassLogger (); Ich habe slf4j integriert, um viele Protokollimplementierungen zu ermöglichen. Einzelheiten finden Sie unter org.sormula.log.ClassLogger unter www.sormula.org.
Jeff Miller
1
Mit getStackTrace () [2] .getClass (). GetCanonicalName () erhalten Sie immer "java.lang.StackTraceElement". Ich habe die gewünschten Ergebnisse mit 'callingClassName = Thread.currentThread (). GetStackTrace () [2] .getClassName ()' erhalten. Ich habe für die Antwort gestimmt, sie aber nicht bearbeitet. Danke für den Tipp.
Bigleftie
Ich habe Ihr LogUtil in meinem Spring-Boot-Projekt ausprobiert. Es druckt "[pool-2-thread-6] jlStackTraceElement" anstelle des Klassennamens
valijon
10

Wie von anderen gesagt, würde ich einen Logger pro Klasse erstellen:

private final static Logger LOGGER = Logger.getLogger(Foo.class);

oder

private final Logger logger = Logger.getLogger(this.getClass());

Ich habe es jedoch in der Vergangenheit als nützlich empfunden, andere Informationen im Logger zu haben. Wenn Sie beispielsweise eine Website haben, können Sie die Benutzer-ID in jede Protokollnachricht aufnehmen. Auf diese Weise können Sie alles verfolgen, was ein Benutzer tut (sehr nützlich zum Debuggen von Problemen usw.).

Der einfachste Weg, dies zu tun, ist die Verwendung eines MDC. Sie können jedoch einen Logger verwenden, der für jede Instanz der Klasse mit dem Namen einschließlich der Benutzer-ID erstellt wurde.

Ein weiterer Vorteil der Verwendung eines MDC besteht darin, dass Sie bei Verwendung von SL4J die Einstellungen abhängig von den Werten in Ihrem MDC ändern können. Wenn Sie also alle Aktivitäten für einen bestimmten Benutzer auf DEBUG-Ebene protokollieren und alle anderen Benutzer auf ERROR belassen möchten, können Sie dies tun. Sie können je nach MDC auch unterschiedliche Ausgaben an unterschiedliche Stellen umleiten.

Einige nützliche Links:

http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/MDC.html

http://www.slf4j.org/api/index.html?org/slf4j/MDC.html

Matthew Farwell
quelle
Ich mag das this.getClass()Teil. Auf diese Weise treten keine Kopier- / Einfügefehler bezüglich des Loggernamens auf.
Winklerrr
4
  • Erstellen Sie einen Logger pro Klasse.
  • Wenn Sie Abhängigkeiten haben, für die eine Commons-Protokollierung erforderlich ist (sehr wahrscheinlich), verwenden Sie die Brücke von slf4j für die Commons-Protokollierung. Instanziieren Sie Ihre Logger (pro Klasse) über die Commons Logging-Oberfläche:private static final Log log = LogFactory.getLog(MyClass.class);
  • Manifestieren Sie dieses Muster in Ihrer IDE mithilfe von Verknüpfungen. Zu diesem Zweck verwende ich die Live-Vorlagen von IDEA .
  • Stellen Sie Threads Kontextinformationen mithilfe eines NDC (Thread-lokaler Stapel von Zeichenfolgen) oder eines MDC (Thread-lokale Zuordnung von Zeichenfolge →?) Bereit.

Beispiele für Vorlagen:

private static final Log log = LogFactory.getLog($class$.class); // live template 'log'

if (log.isDebugEnabled())
    log.debug(String.format("$string$", $vars$)); // live template 'ld', 'lw', 'le' ...
gähnen
quelle
3

Eine weitere Option: Sie können versuchen, AspectJ beim Protokollieren zu schneiden. Überprüfen Sie hier: Vereinfachen Sie Ihre Protokollierung . (Wenn Sie AOP nicht verwenden möchten , können Sie slf4j suchen. )

//Without AOP

    Class A{
       methodx(){
        logger.info("INFO");
       }
    }

    Class B{
       methody(){
        logger.info("INFO");
       }
    }

//With AOP

    Class A{
       methodx(){
         ......
       }
    }

    Class B{
       methody(){
         ......
       }
    }

    Class LoggingInterceptor{

       //Catched defined method before process
       public void before(...xyz){
         logger.info("INFO" + ...xyz);
       }

       //Catched defined method after processed          
       public void after(...xyz){
         logger.info("INFO" + ...xyz);
       }
       .....

    }

PS: AOP wird besser sein, es ist trocken (nicht wiederholen) .

baybora.oren
quelle
2

Die beste und einfachste Methode zum Erstellen benutzerdefinierter Protokollierer, die nicht mit einem Klassennamen verknüpft sind, ist:

// create logger
Logger customLogger = Logger.getLogger("myCustomLogName");

// create log file, where messages will be sent, 
// you can also use console appender
FileAppender fileAppender = new FileAppender(new PatternLayout(), 
                                             "/home/user/some.log");

// sometimes you can call this if you reuse this logger 
// to avoid useless traces
customLogger.removeAllAppenders();

// tell to logger where to write
customLogger.addAppender(fileAppender);

 // send message (of type :: info, you can use also error, warn, etc)
customLogger.info("Hello! message from custom logger");

Wenn Sie jetzt einen anderen Logger in derselben Klasse benötigen, kein Problem :) Erstellen Sie einfach einen neuen

// create logger
Logger otherCustomLogger = Logger.getLogger("myOtherCustomLogName");

Sehen Sie sich jetzt den obigen Code an und erstellen Sie einen neuen Dateiordner, damit Ihre Ausgabe in einer anderen Datei gesendet wird

Dies ist nützlich für (mindestens) 2 Situationen

  • wenn Sie einen separaten Fehler von info und warnen möchten

  • Wenn Sie mehrere Prozesse verwalten und von jedem Prozess eine Ausgabe benötigen

ps. habe Fragen ? Fragen Sie nur! :) :)

Rodislav Moldovan
quelle
1
Dieser Ansatz ist gut, wenn Sie keinen gemeinsamen Code haben, wenn Sie zwei verschiedene Protokolldateien für zwei verschiedene Prozesse haben und beide Prozesse einen gemeinsamen Code haben, dann ist dies ein Problem.
Herr Spark
0

Die übliche Konvention ist "eine logger pr-Klasse und verwenden Sie den Klassennamen als Namen". Das ist ein guter Rat.

Meine persönliche Erfahrung ist, dass diese Logger-Variable NICHT als statisch deklariert werden sollte, sondern als Instanzvariable, die für jede neue abgerufen wird. Auf diese Weise kann das Protokollierungsframework zwei Aufrufe je nach Herkunft unterschiedlich behandeln. Eine statische Variable ist für ALLE Instanzen dieser Klasse (in diesem Klassenladeprogramm) identisch.

Außerdem sollten Sie alle Möglichkeiten mit dem Protokollierungs-Backend Ihrer Wahl kennenlernen. Möglicherweise haben Sie Möglichkeiten, die Sie nicht für möglich gehalten haben.

Thorbjørn Ravn Andersen
quelle
Nur eine Anmerkung, zumindest für log4j: Wenn Sie den Klassennamen als Suchfunktion verwenden, handelt es sich jedes Mal um dieselbe Logger-Instanz. Kein Grund, es für jede Klasseninstanz immer wieder abzurufen. Sie können es auch statisch behalten.
PSpeed
1
Es gibt Vor- und Nachteile für statische Logger. Sie können in verwalteten Containern problematisch sein und sollten überhaupt nicht in Code verwendet werden, der in OSGi ausgeführt werden soll. Siehe SLF4Js Vor- und Nachteile: slf4j.org/faq.html#declared_static
SteveD
0

Wenn Sie mehrere EARs / WARs bereitstellen, ist es möglicherweise besser, die Datei log4j.jar weiter oben in der Classloader-Hierarchie zu verpacken.
dh nicht in WAR oder EAR, sondern im System-Classloader Ihres Containers, andernfalls schreiben mehrere Log4J-Instanzen gleichzeitig in dieselbe Datei, was zu seltsamem Verhalten führt.

Bas
quelle
-1

Wenn Ihre Anwendung den SOA-Prinzipien folgt, haben Sie für jeden Dienst A die folgenden Komponenten:

  1. Ein Controller
  2. Eine Service-Implementierung
  3. Ein Vollstrecker
  4. Eine Ausdauer

So wird es einfacher, ein aController.log aService.log aExecutor.log und ein aPersistance.log zu haben

Dies ist eine schichtbasierte Trennung, sodass alle Ihre Remoting / REST / SOAP-Klassen in das aController.log schreiben

Alle Ihre Planungsmechanismen, Backend-Dienste usw. werden in aService.log geschrieben

Alle Aufgabenausführungen werden in aExecutor.log usw. geschrieben.

Wenn Sie einen Multithread-Executor haben, müssen Sie möglicherweise einen Protokollspeicher oder eine andere Technik verwenden, um Protokollnachrichten für mehrere Threads richtig auszurichten.

Auf diese Weise haben Sie immer 4 Protokolldateien, was nicht viel und nicht zu wenig ist. Ich sage Ihnen aus Erfahrung, dass dies das Leben wirklich einfacher macht.

Qaiser Habib
quelle