Log Management für Spring Boot Applikationen mit Logstash, Elasticsearch and Kibana

4 Kommentare

In diesem Blogartikel gebe ich eine kleine Einführung, wie man sehr schnell mit dem bekannten ELK Stack (Elasticsearch-Logstash-Kibana) eine Log Management Lösung für Spring Boot basierte Microservices aufsetzt. Ich werde zwei Methoden zeigen, wie man die Anwendungslogdateien einliest und zu der zentralen Elasticsearch Instanz transportiert. Grundsätzlich ist dies auch interessant für Anwender, die nicht Spring Boot einsetzen, aber bekannte Logging Frameworks, wie z.B. Logback, Log4J o.ä. verwenden.

Dieser Artikel geht nicht konkret auf die verwendeten Technologien ein und beschreibt sie von Grund auf. Im Netz gibt es dazu jedoch genügend Informationen. Falls noch kein Vorwissen besteht, sollte man sich vorher etwas über Elasticsearch, Logstash und Kibana informieren, bevor man hier startet. Eine gute Informationsquelle ist die Webseite von Elasticsearch, wo einige interessante Webinare und Dokumentationen zu finden sind. Meine Kollegen von codecentric haben ebenfalls schon über Themen im Zusammenhang mit Elasticsearch geblogged. Der Grund warum ich mich für Spring Boot als Beispiel entschieden habe ist, dass wir dieses Framework aktuell in einigen Projekten einsetzen. Ich glaube fest dran, dass Spring Boot dabei helfen wird den nächsten großen Schritt im Bereich der Enterprise Java Architekturen zu gehen. Bei der Einführung von Microservices wird jedoch auch eine größere Menge an Logdateien anfallen, so dass man hier auf jeden Fall eine gute Lösung braucht, um den Überblick zu behalten.

Zuerst klonen wir das Beispiel Git Repository und wechseln in dieses Verzeichnis.

git clone http://github.com/denschu/elk-example
cd elk-example

Die Spring Boot Beispielanwendung ist ein einfacher Batchjob und liegt in dem Verzeichnis „loggging-example-batch“. Die JVM wird mit folgenden Kommandos gestartet:

cd loggging-example-batch/
mvn spring-boot:run

In der Datei „/tmp/server.log“ sollten nun schon einige Logeinträge zu finden sein, die so ähnlich aussehen, wie diese hier:

2014-10-10 17:21:10.358  INFO 11871 --- [           main] .t.TomcatEmbeddedServletContainerFactory : Server initialized with port: 8090
2014-10-10 17:21:10.591  INFO 11871 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2014-10-10 17:21:10.592  INFO 11871 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/7.0.55
2014-10-10 17:21:10.766  INFO 11871 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2014-10-10 17:21:10.766  INFO 11871 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2901 ms
2014-10-10 17:21:11.089  INFO 11322 [main] --- s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8090/http

Die Frage ist nun, wie man diese Datei einliest und weitertransportiert. Dazu setzen wir den ELK Stack nun auf und probieren zwei Varianten aus, wie man die Logdateien mit Logstash verarbeiten kann.

Vorbereitung

Elasticsearch

Dazu öffnen wir eine neue Shell und laden Elasticsearch zunächst herunter. Danach kann die Instanz ohne weitere Anpassungen gestartet werden.

curl -O https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.1.1.tar.gz
tar zxvf elasticsearch-1.1.1.tar.gz
./elasticsearch-1.1.1/bin/elasticsearch

Kibana

In einer weitere Shell wird dann das Webfrontend Kibana installiert. Das zu entpackende Archiv enthält ein JavaScript-basiertes Dashboard, welches mit jedem beliebigen HTTP Server ausgeliefert werden kann. In diesem Beispiel wird der mitgelieferte Webserver von Python verwendet.

curl -O https://download.elasticsearch.org/kibana/kibana/kibana-3.1.0.tar.gz
tar zxvf kibana-3.1.0.tar.gz
cd kibana-3.1.0/
python -m SimpleHTTPServer 8087

Bitte jetzt zunächst prüfen, ob das vorinstallierte Logstash Dashboard sich korrekt mit dem laufenden Elasticsearch Server verbindet. Als default verwendet Kibana hier die URL „http://localhost:9200“ (siehe config.js).

http://localhost:8087/index.html#/dashboard/file/logstash.json

Logstash Agent

Für das Einlesen und den Transport der Logdateien werden so genannte Logstash Agenten verwendet. In einer neuen Shell wird dieser Agent ins Zielverzeichnis entpackt.

curl -O https://download.elasticsearch.org/logstash/logstash/logstash-1.4.2.tar.gz
tar zxvf logstash-1.4.2.tar.gz

Methode 1: Verarbeitung der Logdateien mit Grok

Die am häufigsten verwendete Methode zum verarbeiten der Dateien ist die Benutzung von Grok Filtern. Mit Hilfe von Patterns ist Grok in der Lage die relevanten Informationen aus den Logstatements zu extrahieren. Für unsere Spring Boot Beispielanwendung bzw. die dort verwendete Logback Konfiguration habe ich bereits einen Grok Filter erstellt, den wir nun direkt verwenden können:

input {
  stdin {}
  file {
    path =>  [ "/tmp/server.log" ]
  }
}
filter {
   multiline {
      pattern => "^(%{TIMESTAMP_ISO8601})"
      negate => true
      what => "previous"
   }
   grok {
      # Do multiline matching with (?m) as the above mutliline filter may add newlines to the log messages.
      match => [ "message", "(?m)^%{TIMESTAMP_ISO8601:logtime}%{SPACE}%{LOGLEVEL:loglevel} %{SPACE}%{NUMBER:pid}%{SPACE}%{SYSLOG5424SD:threadname}%{SPACE}---%{SPACE}%{JAVACLASSSHORT:classname}%{SPACE}:%{SPACE}%{GREEDYDATA:logmessage}" ]
   }
}
output {
  elasticsearch { host => "localhost" }
}

Damit die verkürzten Klassennamen (z.b. „o.s.web.context.ContextLoader“) korrekt eingelesen werden, muss noch ein zusätzliches Pattern (JAVACLASSSHORT) in Logstash registriert werden:

cp custompatterns logstash-1.4.2/patterns/

Logstash Agent

Starte nun den Logstash Agenten mit der Spring Boot Konfiguration von oben. Diese Konfiguration befindet sich bereits im Beispielprojekt unter logstash-spring-boot.conf.

./logstash-1.4.2/bin/logstash agent -v -f logstash-spring-boot.conf

Mit cURL bzw. einem HTTP POST Request wird der Job nun gestartet, damit auch Logstatements erzeugt werden.

curl --data 'jobParameters=pathToFile=classpath:partner-import.csv' localhost:8090/batch/operations/jobs/flatfileJob

In dem vorkonfigurierten Logstash Dashboard in Kibana sollten nun die ersten Logeinträge zu sehen sein.

http://localhost:8087/index.html#/dashboard/file/logstash.json

kibana

Methode 2: JSON Logback Encoder

Ein Nachteil von Methode 1 ist, dass es manchmal nicht so einfach ist ein voll funktionsfähiges Grok Pattern zu erstellen, dass alle Besonderheiten des Formats der Logeinträge berücksichtigt. Gerade mehrzeilige Logeinträge sind hier oft problematisch. Das in Spring Boot verwendete Log Format ist hier noch ein sehr gutes Beispiel, da größtenteils fixe Spaltengrößen verwendet werden. Eine Alternative dazu ist die direkte Erzeugung der Logeinträge im JSON-Format. Dazu wird nur ein weitere Maven Artefakt in der pom.xml benötigt (Dies ist bereits in der Beispiel Applikation eingetragen!).

<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>2.5</version>
</dependency>

… und noch ein spezieller Logstash Encoder in der Logback Konfigurationsdatei „logback.xml“, der letztendlich für die Erzeugung des JSON-Formats zuständig ist (Ebenfalls schon im Beispiel enthalten!).

<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>

Die neue Logstash Konfiguration (logstash-json.conf) ist nun viel kleiner und einfacher zu lesen:

input {
  file {
    path =>  [ "/tmp/server.log.json" ]
    codec =>   json {
      charset => "UTF-8"
    }
  }
}
 
output {
  elasticsearch { host => "localhost" }
}

Alternative Log Transporter

Der Logstash Agent benötigt leider etwas mehr Speicher (bis zu 1GB) und ist somit nicht so optimal für kleinere Server (z.b. EC2 Micro Instances) geeignet. Für unsere Demo hier spielt das zwar keine große Rolle, aber insbesondere im Umfeld von Microservice-Umgebungen ist es empfehlenswert auf einen anderen Log Shipper zu setzen, wie z.B. den Logstash Forwarder (aka Lumberjack). Weitere Informationen dazu sind hier zu finden. Und für die JavaScript-Kollegen gibt es natürlich auch eine Implementierung für Node.JS.

Zusammenfassend lässt sich sagen, dass der ELK Stack (Elasticsearch-Logstash-Kibana) eine gute Kombination ist, um ein komplettes Log Management ausschließlich mit Open Source Technologien durchzuführen. Bei größeren Umgebungen mit einer sehr großen Menge an Logdateien sollte man ggf. noch ein zusätzlichen Transport (z.B. Redis) nutzen, um die einzelnen Komponenten (Log Server, Log Shipper) voneinander zu entkoppeln. In der nächsten Zeit werde ich noch weitere Themen im Zusammenhang mit Microservices beleuchten. Also seid gespannt und ich freu mich natürlich immer über Feedback :-).

Dennis Schulte

Dennis Schulte ist seit 2009 als Senior IT Consultant bei der codecentric AG tätig. Er unterstützt seine Kunden insbesondere im Bereich Enterprise-Architekturen, Microservices, Continuous Delivery, DevOps und der Optimierung von IT-Prozessen. Aufgrund seiner langjährigen Erfahrung als Architekt und Entwickler verfügt er über ein umfassendes Wissen im Bereich Java und Open-Source-Technologien. Seine Projektschwerpunkte liegen in der Architekturberatung und der Durchführung von Projekten in Enterprise-Unternehmen.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentare

  • Jochen

    Eine weitere (und von mir bevorzugte) Variante ist, dass die Spring Boot Anwendung ohne Umweg über das lokale Dateisystem direkt die Logs an den Logserver schickt.

    Wenn hierfür das GELF-Protokoll genutzt wird (was u. a. von Graylog2 oder logstash mit GELF-Input unterstützt wird), können weitere nützliche Informationen (etwa der Inhalt des MDC) direkt mitgeschickt und verarbeitet werden. Damit spart man sich auch das unnötige wiederholte Parsen der Logeinträge mit Grok oder anderen Filtern.

    Es gibt inzwischen für praktisch alle gebräuchlichen Java Logging Frameworks GELF Appender, etwa für Logback, log4j, log4j 2 oder auch das gute alte java.util.logging.

    Disclaimer: Ich arbeite bei TORCH, der Firma hinter Graylog2.

  • Dennis Schulte

    29. Oktober 2014 von Dennis Schulte

    Hallo Jochen,

    vielen Dank für Deinen Kommentar.

    Das direkte Senden an den Logserver ist auf jeden Fall auch eine Alternative. Wobei hier natürlich ggf. Logstatements verloren gehen können, wenn man ein unsicheres Protokoll wie z.B. UDP verwendet oder der Server gerade nicht zur Verfügung steht. Der Vorteil bei dieser Variante ist natürlich, dass man keinen zusätzlichen Agenten auf der Anwendungsserver-Seite braucht.

    Das Mitsenden von Variablen (z.B. aus dem MDC) funktioniert bei der obigen JSON-Lösung auch, z.B. so:


    true
    {„testvar“:“${mdcVariable}“,“version“:“${pom.version}“}

    Viele Grüße
    Dennis

  • Jochen

    Das direkte Senden an den Logserver ist auf jeden Fall auch eine Alternative. Wobei hier natürlich ggf. Logstatements verloren gehen können, wenn man ein unsicheres Protokoll wie z.B. UDP verwendet oder der Server gerade nicht zur Verfügung steht.

    Gleiches trifft natürlich auch auf das im Blogpost vorgestellte Setup mit dediziertem Logging Agent (logstash oder logstash-forwarder) zu.

    Um zumindest das Fehlerszenario „Logserver nicht erreichbar“ zu meistern, können alle gängigen Java Logging Frameworks auch mehrere Appender gleichzeitig nutzen, um z. B. die Logmeldungen zusätzlich (wie gehabt) in eine lokale Datei zu schreiben.

    • Dennis Schulte

      30. Oktober 2014 von Dennis Schulte

      Gleiches trifft natürlich auch auf das im Blogpost vorgestellte Setup mit dediziertem Logging Agent (logstash oder logstash-forwarder) zu.

      Das stimmt, und deswegen auch der Hinweis auf die Entkopplung mit Redis, damit auch wirklich kein Eintrag verloren geht.

      Ich würde auf jeden Fall weiterhin mit File-Appendern arbeiten, um immer ganz sicher alle Logstatements zu erfassen. Gerade in der Anfangsphase ist das noch ein sehr wertvolles Mittel 🙂

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.