Anmelden in Java und allgemein: Best Practices?

76

Manchmal, wenn ich meinen Protokollcode sehe, frage ich mich, ob ich es richtig mache. Es gibt vielleicht keine endgültige Antwort darauf, aber ich habe folgende Bedenken:

Bibliotheksklassen

Ich habe mehrere Bibliotheksklassen, die möglicherweise einige INFONachrichten protokollieren . Schwerwiegende Fehler werden als Ausnahmen gemeldet. Derzeit habe ich eine statische Protokollierungsinstanz in meinen Klassen mit dem Klassennamen als Protokollierungsnamen. (Log4j ist: Logger.getLogger(MyClass.class))

Ist das der richtige Weg? Möglicherweise möchte der Benutzer dieser Bibliotheksklasse keine Nachrichten von meiner Implementierung oder möchte sie in ein anwendungsspezifisches Protokoll umleiten. Sollte ich dem Benutzer erlauben, einen Logger von der "Außenwelt" aus einzustellen? Wie gehen Sie mit solchen Fällen um?

Allgemeine Protokolle

In einigen Anwendungen möchten meine Klassen möglicherweise eine Protokollnachricht in ein bestimmtes Protokoll schreiben, das nicht durch den Klassennamen gekennzeichnet ist. (Dh :) HTTP Request logWas ist der beste Weg, so etwas zu tun? Ein Suchdienst kommt mir in den Sinn ...

Malax
quelle

Antworten:

42

Ihre Konventionen sind ziemlich normal und ziemlich gut (imho).

Die einzige Sache, die Sie beobachten sollten, ist die Speicherfragmentierung durch übermäßige Debug-Aufrufe ohne Verbindung. Mit Log4J (und den meisten anderen Java-Protokollierungsframeworks) erhalten Sie am Ende Folgendes:

if (log.isDebugEnabled()) {
  log.debug("...");
}

Das Erstellen dieser Protokollnachricht (die Sie wahrscheinlich nicht verwenden) kann teuer sein, insbesondere wenn sie tausend- oder millionenfach ausgeführt wird.

Ihre INFO-Level-Protokollierung sollte nicht zu "gesprächig" sein (und was Sie sagen, klingt so, als wäre es nicht so). INFO-Nachrichten sollten im Allgemeinen aussagekräftig und aussagekräftig sein, z. B. wenn die Anwendung gestartet und gestoppt wird. Dinge, die Sie vielleicht wissen möchten, wenn Sie auf ein Problem stoßen. Die Debug- / Fine-Level-Protokollierung wird häufiger verwendet, wenn Sie tatsächlich ein Problem haben, das Sie diagnostizieren möchten. Die Debug- / Feinprotokollierung wird normalerweise nur bei Bedarf aktiviert. Informationen sind normalerweise immer verfügbar.

Wenn jemand keine spezifischen INFO-Nachrichten von Ihren Klassen möchte, kann er natürlich Ihre log4j-Konfiguration ändern, um sie nicht zu erhalten. Log4j ist in dieser Abteilung sehr einfach (im Gegensatz zur Java 1.4-Protokollierung).

Was Ihre HTTP-Sache betrifft, habe ich im Allgemeinen nicht festgestellt, dass dies ein Problem bei der Java-Protokollierung ist, da normalerweise eine einzelne Klasse für das verantwortlich ist, woran Sie interessiert sind, sodass Sie es nur an einer Stelle platzieren müssen. Wenn Sie (meiner Erfahrung nach selten) allgemeine Protokollnachrichten für scheinbar nicht verwandte Klassen wünschen, geben Sie einfach ein Token ein, nach dem Sie leicht suchen können.

Cletus
quelle
4
Als Fyi kann man in Scala eine Debug-Methode schreiben, die genau diesen Code ohne die Ausführlichkeit unter Verwendung von By-Name-Parametern ausführt. Der Compiler konvertiert den Code im Argument debug () automatisch in eine Funktion, die nur ausgewertet wird, wenn debug () beschließt, ihn aufzurufen. Verketten Sie die Zeichenfolge also nur, wenn isDebugEnabled () true ist. Beispielcode, der dies implementiert: override def debug (msg: => AnyRef) = if (isDebugEnabled) logger.debug (msg) github.com/dpp/liftweb/blob/…
Blair Zajac
8
Dies ist ein weiterer guter Grund, das von slf4j bereitgestellte {} -Konstrukt zu verwenden.
Thorbjørn Ravn Andersen
16

Das Folgende sind die Richtlinien, die ich in all meinen Projekten befolge, um eine gute Leistung sicherzustellen. Ich bin gekommen, um diese Richtlinien auf der Grundlage der Eingaben aus verschiedenen Quellen im Internet zu erstellen.

Nach wie vor glaube ich, dass Log4j 2 bei weitem die beste Option für die Anmeldung in Java ist.

Die Benchmarks finden Sie hier . Die Praxis, die ich befolge, um die beste Leistung zu erzielen, ist wie folgt:

  1. Ich vermeide die Verwendung von SLF4J im Moment aus folgenden Gründen:
  2. Führen Sie alle regulären Protokollierungen mit dem asynchronen Logger durch, um eine bessere Leistung zu erzielen
  3. Protokollieren Sie Fehlermeldungen in einer separaten Datei mit dem synchronen Logger, da die Fehlermeldungen sofort angezeigt werden sollen
  4. Verwenden Sie bei der regulären Protokollierung keine Standortinformationen wie Dateiname, Klassenname, Methodenname und Zeilennummer, da das Framework zum Ableiten dieser Informationen einen Snapshot des Stapels erstellt und diesen durchläuft. Dies wirkt sich auf die Leistung aus. Verwenden Sie daher die Standortinformationen nur im Fehlerprotokoll und nicht im regulären Protokoll
  5. Verwenden Sie zum Verfolgen einzelner Anforderungen, die von separaten Threads verarbeitet werden, die Verwendung des Thread-Kontexts und der zufälligen UUID, wie hier erläutert
  6. Da wir Fehler in einer separaten Datei protokollieren, ist es sehr wichtig, dass wir die Kontextinformationen auch im Fehlerprotokoll protokollieren. Wenn die Anwendung beispielsweise beim Verarbeiten einer Datei auf einen Fehler gestoßen ist, drucken Sie den Dateinamen und den zu verarbeitenden Dateidatensatz in der Fehlerprotokolldatei zusammen mit dem Stacktrace
  7. Die Protokolldatei sollte grep-fähig und leicht verständlich sein. Wenn eine Anwendung beispielsweise Kundendatensätze in mehreren Dateien verarbeitet, sollte jede Protokollnachricht wie folgt aussehen:
12:01:00,127 INFO FILE_NAME=file1.txt - Processing starts
12:01:00,127 DEBUG FILE_NAME=file1.txt, CUSTOMER_ID=756
12:01:00,129 INFO FILE_NAME=file1.txt - Processing ends
  1. Protokollieren Sie alle SQL-Anweisungen mit einem SQL-Marker wie unten gezeigt und verwenden Sie einen Filter, um ihn zu aktivieren oder zu deaktivieren:
private static final Marker sqlMarker = 
  MarkerManager.getMarker("SQL");

private void method1() {
    logger.debug(sqlMarker, "SELECT * FROM EMPLOYEE");
}
  1. Protokollieren Sie alle Parameter mit Java 8 Lambdas. Dadurch wird die Anwendung vor der Formatierungsnachricht geschützt, wenn die angegebene Protokollstufe deaktiviert ist:
int i=5, j=10;
logger.info("Sample output {}, {}", ()->i, ()->j);
  1. Verwenden Sie keine String-Verkettung. Verwenden Sie die parametrisierte Nachricht wie oben gezeigt

  2. Verwenden Sie das dynamische Neuladen der Protokollierungskonfiguration, damit die Anwendung die Änderungen in der Protokollierungskonfiguration automatisch neu lädt, ohne dass die Anwendung neu gestartet werden muss

  3. Verwenden Sie nicht printStackTrace()oderSystem.out.println()

  4. Die Anwendung sollte den Logger vor dem Beenden herunterfahren:

LogManager.shutdown();
  1. Schließlich verwende ich als Referenz für alle die folgende Konfiguration:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration monitorinterval="300" status="info" strict="true">
    <Properties>
        <Property name="filePath">${env:LOG_ROOT}/SAMPLE</Property>
        <Property name="filename">${env:LOG_ROOT}/SAMPLE/sample
        </Property>
        <property name="logSize">10 MB</property>
    </Properties>
    <Appenders>
        <RollingFile name="RollingFileRegular" fileName="${filename}.log"
            filePattern="${filePath}/sample-%d{yyyy-dd-MM}-%i.log">
            <Filters>
                <MarkerFilter marker="SQL" onMatch="DENY"
                    onMismatch="NEUTRAL" />
            </Filters>
            <PatternLayout>
                <Pattern>%d{HH:mm:ss,SSS} %m%n
                </Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy
                    interval="1" modulate="true" />
                <SizeBasedTriggeringPolicy
                    size="${logSize}" />

            </Policies>
        </RollingFile>
        <RollingFile name="RollingFileError" 
            fileName="${filename}_error.log"
            filePattern="${filePath}/sample_error-%d{yyyy-dd-MM}-%i.log"
            immediateFlush="true">
            <PatternLayout>
                <Pattern>%d{HH:mm:ss,SSS} %p %c{1.}[%L] [%t] %m%n
                </Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy
                    interval="1" modulate="true" />
                <SizeBasedTriggeringPolicy
                    size="${logSize}" />
            </Policies>
        </RollingFile>
    </Appenders>
    <Loggers>
        <AsyncLogger name="com"
            level="trace">
            <AppenderRef ref="RollingFileRegular"/>
        </AsyncLogger>
        <Root includeLocation="true" level="trace">
            <AppenderRef ref="RollingFileError" level="error" />
        </Root>
    </Loggers>
</Configuration>
  1. Die erforderlichen Maven-Abhängigkeiten finden Sie hier:
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.8.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.8.1</version>
</dependency>
<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.3.6</version>
</dependency>
<!-- (Optional)To be used when working 
with the applications using Log4j 1.x -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-1.2-api</artifactId>
    <version>2.8.1</version>
</dependency>
Saptarshi Basu
quelle
1
Ich stimme den meisten Ihrer Punkte zu. Insbesondere die Anbieter in 8. sollten verwendet werden und if (LOGGER.isDebugEnabled()) ...sollten überhaupt als veraltet angesehen werden.
Aitch
13

In der Antwort von @cletus schrieb er über das Problem von

if (log.isDebugEnabled()) {
  log.debug("val is " + value);
}

Dies könnte durch die Verwendung von SLF4J überwunden werden . Es bietet eine Formatierungshilfe

log.debug("val is {}", value);

Dabei wird die Nachricht nur erstellt, wenn die Ebene debuggt.

Also, nowaday, mit SL4J und seine Begleiter Logger, Logback wird empfohlen , für die Leistung und Stabilitätsgründen.

serv-inc
quelle
2
Der Begriff "ausgewertet" ist möglicherweise etwas irreführend, da Java den Aufruf nach Namen nicht unterstützt . Allerdings parametrisiert Meldungen nicht „entstehen die Kosten für die Parameter Konstruktion, falls die Log - Anweisung ist deaktiviert“. Wie Sie bereits betont haben, ist dies viel prägnanter und führt zu einer besseren Leistung.
Beatngu13
@ beatngu13: Danke, ist es jetzt OK?
serv-inc
Klar, du hattest sowieso meine Gegenstimme. ;) Aber danke für die Klarstellung.
Beatngu13
2
Ich habe eine Frage. Wenn wir die Variable ändern: valuezu einer Methode : getValue(). Und die Methode könnte viel Zeit kosten. Haben die if (log.isDebugEnabled()) {log.debug("val is " + getValues());}gleichen Kosten wielog.debug("val is {}", getValue());
user2256235
1
@ user2256235: Wie von @user erwähnt, werden beide Anweisungen sofort ausgewertet. Wenn Sie Java 8 und ein geeignetes Protokollierungsframework verwenden, können Sie alternativ a übergeben Supplier. Schauen Sie sich diesen Blog-Beitrag an, um einige Beispiele mit Log4j zu sehen.
Beatngu13
7

In Bezug auf das Instanziieren von Loggern hatte ich einige Erfolge bei der Verwendung einer Eclipse Java-Vorlage zum Einrichten meiner Logger:

private static Logger log = Logger.getLogger(${enclosing_type}.class);

Dies vermeidet das Problem, dass eine JVM mit Ihrem Stack-Trace herumfummelt, und reduziert (möglicherweise trivial) den Overhead, der durch das Erstellen des Stack-Trace entsteht.

Das Tolle an der Verwendung einer solchen Vorlage ist, dass Sie sie mit Ihrem Team teilen können, wenn Sie einen einheitlichen Standard für Logger festlegen möchten.

Es sieht so aus, als ob IntelliJ dasselbe Konzept für eine Vorlagenvariable unterstützt, die den Namen des umschließenden Typs darstellt. Ich sehe keine Möglichkeit, dies in NetBeans einfach zu tun.

justinpitts
quelle
5

Ich überprüfe die Protokollebenen einer Anwendung und erkenne derzeit ein Muster:

private static final Logger logger = Logger.getLogger(Things.class)

public void bla() {
  logger.debug("Starting " + ...)
  // Do stuff
  ...
  logger.debug("Situational")

  // Algorithms
  for(Thing t : things) {
    logger.trace(...)
  }

  // Breaking happy things
  if(things.isEmpty){
    logger.warn("Things shouldn't be empty!")
  }

  // Catching things
  try {
    ...
  } catch(Exception e) {
    logger.error("Something bad happened")
  }

  logger.info("Completed "+...)
}

Eine log4j2-Datei definiert einen Socket-Appender mit einem Failover-Appender. Und ein Konsolen-Appender. Manchmal verwende ich log4j2-Marker, wenn es die Situation erfordert.

Dachte, eine zusätzliche Perspektive könnte helfen.

Patrick
quelle
4

Die bevorzugte Option für die Art der von Ihnen beschriebenen log4j-Konfiguration ist die Verwendung der log4j-Konfigurationsdatei . Auf diese Weise können Benutzer Ihrer Implementierung genau das tun, was Sie verlangen, da sie Ihre Konfiguration später mit etwas überschreiben können, das für ihre eigene Implementierung besser geeignet ist. Sehen Sie hier für eine sehr gründliche Grundierung.

John Feminella
quelle
... gibt es auch ein Nicht-XML-Format, das etwas besser lesbar ist.
KarlP
4

Ich habe das wahrscheinlich irgendwo gestohlen, aber es ist schön.

Es verringert das Risiko, dass Logger beim Kopieren und Einfügen von Refactoring verwechselt werden, und es ist weniger zu tippen.

In Ihrem Code:

private final static Logger logger = LoggerFactory.make();

... und in LoggerFactory:

public static Logger make() {
    Throwable t = new Throwable();
    StackTraceElement directCaller = t.getStackTrace()[1];
    return Logger.getLogger(directCaller.getClassName());
}

(Beachten Sie, dass der Stackdump während der Initialisierung ausgeführt wird. Der Stacktrace wird wahrscheinlich nicht von der JVM optimiert, es gibt jedoch keine Garantien.)

KarlP
quelle
* ... höchstwahrscheinlich von Dr. Heinz M. Kabutz gestohlen. Aber ich bin nicht sicher
KarlP
10
Beachten Sie, dass eine VM Stack-Frames weglassen oder andere Optimierungen durchführen kann, sodass möglicherweise ein leeres Array von StackTraceElements angezeigt wird. Weitere Informationen finden Sie unter java.sun.com/j2se/1.5.0/docs/api/java/lang/… .
Bombe
1
Sie haben den Typ in Ihrer Klassenvariablendefinition verpasst.
Sebastian
3

Außerdem halte ich es für wichtig, Simple Logging Facade für Java (SLF4J) ( http://www.slf4j.org/ ) zu bezeichnen. Aufgrund einiger Probleme bei der Verwendung unterschiedlicher Protokollierungsframeworks in diversifizierten Teilen eines großen Projekts ist SLF4J der De-facto-Standard zur Lösung eines Problems bei der erfolgreichen Verwaltung dieser Teile, nicht wahr?

Der zweite Gedanke: Es scheint, dass einige Aufgaben der alten Schule durch aspektorientierte Programmierung ersetzt werden können. Spring frmwrk hat eine eigene Implementierung , einen AOP-Ansatz für die Protokollierung, der hier bei StackOverflow und hier im Spring-Blog berücksichtigt wird .

Yauhen
quelle