Logging-Strategie für meine Web-App

Ich habe mich bisher mit Logging nicht so tiefschürfend beschäftigt, sondern habe mich irgendwie durchgemogelt. Jetzt muss ich eine Logging-Strategie für eine kleine Web-Applikation erstellen, wo “einfach ein bisschen rumprobieren” wohl nicht mehr ausreicht.

Voraussetzungen:

  • Jetty-Server, nicht embedded
  • Spark-Framework, über SparkFilter in web.xml gestartet
  • Java 8 Applikation, verwendet aktuell Log4j2 (ist mir aber im Grund egal)
  • z.Z. verwende ich Maven, soll aber auf Gradle umgestellt werden
  • Logging innerhalb der Anwendung mit entsprechender log4j2.properties funktioniert bereits, das Zeugs geht zur Konsole und zu einem “rollenden” Log-File

Meine log4j2.properties sehen z.Z. so aus:


status = error
name = PropertiesConfig

property.filename = logs/application.log

filters = threshold

filter.threshold.type = ThresholdFilter
filter.threshold.level = debug

appenders = console, rolling

appender.console.type = Console
appender.console.name = console
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %N %d{HH:mm:ss} %-5p %c{1}:%L - %m%n

appender.rolling.type = RollingFile
appender.rolling.name = rolling
appender.rolling.fileName = ${filename}
appender.rolling.filePattern = logs/application-%d{MM-dd-yy}-%i.log.gz
appender.rolling.layout.type = PatternLayout
appender.rolling.layout.pattern = %N %d{HH:mm:ss} %-5p %c{1}:%L - %m%n
appender.rolling.policies.type = Policies
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
appender.rolling.policies.time.interval = 1
appender.rolling.policies.time.modulate = true
appender.rolling.strategy.type = DefaultRolloverStrategy
appender.rolling.strategy.max = 20

rootLogger.level = debug
rootLogger.appenderRef.rolling.ref = rolling
rootLogger.appenderRef.console.ref = console

Ich hätte lieber Properties als XML oder so, aber da bin ich flexibel

Ich möchte folgendes erreichen:

  • alle Log-Messages außerhalb meines Applications-Packages sollen in ein separates rollendes Log-File server.log
  • alle “normalen” Log-Messages innerhalb meines Applications-Packages sollen in ein separates rollendes Log-File application.log
  • bestimmte Log-Messages (ich habe was von Markern gelesen, ist das das richtige?) sollen in ein separates rollendes Log-File tracking.log
  • natürlich möchte ich die Log-Level für die einzelnen Files separat setzen können
  • alle Log-Messages, die in die Files gehen, sollen auch in der Konsole angezeigt werden (eventuell mit anderem Pattern)

Die Haupt-Schwierigkeiten sind für mich:

  • wie es aussieht, brauche ich SLF4J, um die Jetty- (und wohl auch die Spark-) Messages abzufangen, und mir ist nicht klar, welche Dependencies und Settings ich dafür brauche
  • mir ist nicht so richtig klar, wie ich die Messages geeignet aufteile (Application oder nicht, und innerhalb der Applikation die separaten Tracking-Messages)
  • Ich weiß nicht, ob es praktikabel ist, in der Applikation weiter direkt Log4j2 zu verwenden, oder ob ich auch da besser zu SLF4J wechseln sollte

Wäre cool wenn mir jemand grob skizzieren könnte, wie ich das alles hinbekomme.

Also ich kann dir da nur aus meiner kleinen Erfahrungswelt berichten:
Persönlich habe ich als Maven Dependendcy für SLF4J nur “slf4j-api”. Wenn du dann Bindings brauchst, musst du dir nur die entsprechende Dependency dazuholen.
Wenn du die Konfiguration mit XML willst, könntest du dir logback anschauen (hat auch ein Maven SLF4J Binding). Dort kannst du verschiedene Logger für verschiedene "package Bereiche2 angeben die unterschiedliche Formate und Ausgabeziele haben können (wenn ich das bisher richtig überblickt habe).

Ich meine, man kann Logback auch so konfigurieren, dass die Messages sowohl in eine Datei als auch Konsole gehen.
Leider weiß ich auch nicht genau was du mit rolling meinst (vermutlich, dass die Datei nicht unendlich groß wird), da weiß ich nicht wie es mit Logback aussieht.

Auf jeden Fall würde ich empfehlen gegen SLF4J zu Programmieren, da du so zunächst deine log4j2 Konfiguration behalten kannst und später dann einfach das Binding austauschst und alles weiter funktionieren sollte.

Zwecks Maven -> Gradle. Soweit ich Gradle verstanden habe solltest du da keine Probleme kriegen, da man auf das Maven Central zugreifen kann und sich die Dependencies laden kann.

Standardhinweis, falls zustimmend und nicht eh schon verfolgt:
die Breite der eigenen Anwendung so weit wie möglich von Abhängigkeiten freihalten,
interne Methoden fürs Logging, ggfs. + internes Enum, für LogLevel, ‘Marker’ usw.,

alle Meldungen gehen in eine einzige (austauschbare) Klasse,
meinetwegen auch um bei UnitTests (oder Debug in IDE) das Schreiben von Logs auf Festplatte abzufangen…,

ob dort dann System.out.println() genutzt wird oder Log-Framework(s) A, B, C, das ist beliebig austauschbar, auch erst wenn dafür Zeit,
Hauptanwendung mit 100 Klassen davon nicht berührt

[QUOTE=SlaterB]Standardhinweis, falls zustimmend und nicht eh schon verfolgt:
die Breite der eigenen Anwendung so weit wie möglich von Abhängigkeiten freihalten,
interne Methoden fürs Logging, ggfs. + internes Enum, für LogLevel, ‘Marker’ usw.,

alle Meldungen gehen in eine einzige (austauschbare) Klasse,
meinetwegen auch um bei UnitTests (oder Debug in IDE) das Schreiben von Logs auf Festplatte abzufangen…,

ob dort dann System.out.println() genutzt wird oder Log-Framework(s) A, B, C, das ist beliebig austauschbar, auch erst wenn dafür Zeit,
Hauptanwendung mit 100 Klassen davon nicht berührt[/QUOTE]

Das ist ja das Konzept von SLF4J, dass F steht nicht für Framework sondern für Facade. Anders formuliert, wenn Landei jetzt hingeht und das ganze so baut, wie beschrieben, dann kommt SLF4J heraus.

Da hilft es dann auch nichts, die eigene Anwendung von Abhängigkeiten freizuhalten, wenn man dann selbst zusätzlich zur eigenen Anwendung eine Eigene-Logging-Facade bauen muss und auch noch fest in die eigene Anwendung einbaut.

Prinzipiell stimme ich dir zu, dass dies meist die beste Vorgehensweise bei jeglichen Abhängigkeiten und deren Umgang ist, aber in diesem Fall passt das eben nicht wirklich.

sobald man das Hello World-Beispiel

import org.slf4j.LoggerFactory;

public class HelloWorld {
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
  }
}

verwendet ist es geschehen, Abhängigkeit an die API,

Rückführungen der Logmeldungen etwa wieder ins eigene Programm für Liste in GUI,
dynamisch umstellbare Einstellungen, temporäres Mitlesen von Log bestimmter User, Filter & Co. ist vielleicht unmöglich bis unnötig schwer,
externe Konfigurationsfiles, exotische Umwege wie [LOGBACK-687] Swing Logger GUI (edit level and show root logger) - QOS.ch JIRA ?

einfach schlecht, da hilft kein gutes Konzept

vollkommen sauber ohne direkte Nachteile ist es mit programminternen Methoden

  public void methode() {
    logInfo("Hello World");
  }
}

und diese Meldung kommt über interne Kanäle an einer zentraler Stelle an,

  • je nach gewünschten Aufwand ausgestattet mit Hintergrund-Info über Klasse, Prozess, ausführenden User, Zusatzdaten,
  • je nach Einstellung zur Weiterverarbeitung an dieser einzigen Stelle des Programms an SLF4J oder andere weitergeleitet, oder zusätzlich je nach Bedingungen zur Email gemacht
    oder lieber in GUI angezeigt, temporär in Liste gemerkt, die Zusatzdaten zur individuellen Begutachtung vorgehalten, Statistiken über Auftreten bestimmter Meldungen usw. (temporär einschaltbar), Grenzen nicht bekannt

sobald man einmal logger.info("Hello World"); außerhalb der zentralen Stelle schreibt, hat man verloren,

einziger Vorteil von SLF4J wäre, dass entsprechender Code wömöglich leichter in andere Programme zu kopieren wären, falls dort genau dieser Standard passt,

nie zu oft gepostet:

:wink:
aber Wissen über das eigene Programm ist einer der Grundpfeiler, da darf es ruhig ein internes umfangreiches System geben,

edit: auch verknüpfbar mit Exceptions die durchs Programm springen, falls das Konzept nicht an sich abgelehnt

Meine Dependencies für das Logging sehen wie folgt aus:

ext.log4j2Version = '2.6.2'
ext.slf4jVersion = '1.7.21'

dependencies {
    compile "org.slf4j:slf4j-api:$slf4jVersion"
    runtime "org.slf4j:jcl-over-slf4j:$slf4jVersion"
    runtime "org.apache.logging.log4j:log4j-slf4j-impl:$log4j2Version"
    runtime "org.apache.logging.log4j:log4j-api:$log4j2Version"
    runtime "org.apache.logging.log4j:log4j-core:$log4j2Version"
}

Dabei verwende ich aber immer das slf4j-API, du müsstest, damit das bei dir ohne Codeänderung funktioniert, lediglich das log4j-api zu einer compiletime-dependency machen.
jcl-over-slf4j brauchst du nicht unbedingt, nur wenn irgendwo in einer Dependency auch noch apache commons logging verwendet wird.

Ich bekomme diesen Fehler:


SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

Dabei habe ich den ganzen Krempel in Maven:


        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.6.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.6.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.6.2</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.21</version>
        </dependency>

(Hab nicht so viel Ahnung, vorweg).
SLF4J Error Codes
Genauer gelesen?

Ansonsten:
Log4j Bridge

Eventuell ist hier log4j-over-slf4j von Interesse?

[QUOTE=CroNut](Hab nicht so viel Ahnung, vorweg).
SLF4J Error Codes
Genauer gelesen? [/quote]

Ja, ich habe aber StaticLoggerBinder (für Log4j2) über meine Dependencies drin. Was soll ich mehr machen als die richtige Klasse bereitzustellen?

Ansonsten:
Log4j Bridge

Eventuell ist hier log4j-over-slf4j von Interesse?

Ich denke, das ist genau die falsche Richtung. Ich will SLF4-Log-Messages in ein Log4j2-Log stopfen, nicht Code, der Log4j2-Messages verwendet, mittels SLF4J irgendwo anders hin loggen lassen.

auf das Fettgedruckte hier achten? :

Placing one (and only one) of slf4j-nop.jar, slf4j-simple.jar, slf4j-log4j12.jar, slf4j-jdk14.jar or logback-classic.jar on the class path should solve the problem.

also auch darauf achten dass nicht durch irgendwen, etwa den genannten ‚Jetty-Server‘, noch irgendwo weitere Versionen der API rumliegen, weitere Jars?

nur für allernötigsten Notfall genannt,
und für andere die hier vielleicht die Fehlermeldung lesen, ist ja auch nach Suchmaschinen zur Fehlermeldung die ersten Treffer:
java - SLF4J: Failed to load class „org.slf4j.impl.StaticLoggerBinder“ - Stack Overflow
Failed to load class org.slf4j.impl.StaticLoggerBinder and Unable to load native-hadoop library - Stack Overflow


evtl. ein paar Schritte zurück,

  • ohne Server usw. nur lokales Projekt, meinetwegen noch mit maven & Co., geht es da?
  • oder gar zu ganz archaischen Programmen nur mit CLASSPATH/ IDE-CLASSPATH,
    normale einzelne selbst besorgte, lokal auf Festplatte vorhandene Libraries eingefügt, läuft es da? :wink:

wenn es da geht und mit maven oder im größeren Umfang mit Server nicht, dann zumindest evtl. kein slf4j-Fehler, wäre eine Information

Da scheint das Artefakt log4j-slf4j-impl nicht korrekt im Classpath gefunden zu werden. Dort ist der StaticLoggerBinder nämlich drin.