Loghosts mit log4j

Einleitung
Um Logmeldungen auf einem zentralen Loghost zu bündeln werden von Log4j grundsätzlich zwei Lösungsvarianten bereitgestellt.

Ohne die Log4j Welt verlassen zu müssen lassen sich mittes eines Gespanns aus org.apache.log4j.net.SocketAppender auf der Client-Seite und org.apache.log4j.net.SimpleSocketServer auf der Server-Seite LogEvent Objekte übertragen und auf dem Loghost in Dateien schreiben.

Die zweite Möglichkeit nutzt den org.apache.log4j.net.SyslogAppender, um die Logmeldung an einen lokalen oder auch entfernten Syslog Dienst weiterzugeben. Alles weitere wird dann je nach Konfiguration des laufenden Syslog Dienstes erledigt.

1. Log4j-Lösung

Bei dieser Lösung senden einer oder meherer Clients ihre Log4j LogEvents direkt an einen Log4j Server. Die Übertragung erfolgt via TCP Protokoll. Bei Verbindungsabbruch versucht der Client in regelmäßigen Abständen einen Reconnect zum Log4j Server. Die Client Anwendung wird dadurch aber nicht beeinflusst und läuft weiter.

Die Vorteiler dieser Lösung sind

  • Bei mehreren identischen Systemen (Cluster/Loadbalancing) kann für die Hauptkonfiguration auf dem Log4j Server eine einzige gemeinsame Konfigurationsdatei genutzt werden
  • Es muss kein zusätzlicher Dienst wie syslog-ng aufgesetzt und verwaltet werden.

Zu dieser Lösung existiert ein Projekt: https://idmvm1.rrze.uni-erlangen.de/CIT-src/trunk/cit-log4j-server

Client Anwendung
Die JAVA Client Anwendung muss natürlich Log4j nutzen.
Die vorhandene Log4j Konfiguration kann dann zum Senden der LogEvents an die Server Anwendung einfach wie im folgenden Beispiel angepasst werden.

Client Konfiguration

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

	<!--
		log4j.xml

		Basic configuration which sends all log messages to the
		configured loghost using a SocketAppender.

		To increase overall performance and limit network overhead
		logger level configuration is done here in addition to the
		configuration on the server side.

		If you modify logging levels here make sure the logging levels
		on the server side are consistent with what is configured here.
		Because what doesn't get sent here can't be received elsewhere ;)

	-->
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

	<!--
		#################################################################################################
		# APPENDER CONFIGURATION #
		#################################################################################################
	-->

	<!-- SOCKET appender -->
	<appender class="org.apache.log4j.net.SocketAppender" name="SOCKET">
		<param value="127.0.0.1" name="remoteHost" />
		<param value="8881" name="port" />
		<param value="5000" name="reconnectionDelay" />
		<filter class="org.apache.log4j.varia.LevelRangeFilter">
      		<param name="LevelMax" value="INFO" />
    	</filter>
	</appender>

	<!-- SOCKET_VERBOSE appender -->
	<!-- (also generates location info) -->
	<appender class="org.apache.log4j.net.SocketAppender" name="SOCKET_VERBOSE">
		<param value="127.0.0.1" name="remoteHost" />
		<param value="8881" name="port" />
		<param value="5000" name="reconnectionDelay" />
		<param value="true" name="locationInfo" />
		<filter class="org.apache.log4j.varia.LevelRangeFilter">
      		<param name="LevelMin" value="WARN" />
    	</filter>
	</appender>

	<!--
		#################################################################################################
		# LOGGING CONFIGURATION #
		#################################################################################################
	-->

	<!-- TOMCAT -->
	<logger name="org.apache.jk">
		<level value="info" />
	</logger>

	<logger name="org.apache.coyote">
		<level value="info" />
	</logger>

	<logger name="org.apache.tomcat">
		<level value="info" />
	</logger>

	<logger name="org.apache.catalina">
		<level value="info" />
	</logger>

	<logger name="org.apache.commons.digester">
		<level value="info" />
	</logger>

	<logger name="org.apache.jasper">
		<level value="info" />
	</logger>

	<!--
		#################################################################################################
		# ROOT LOGGER CONFIGURATION #
		#################################################################################################
	-->
	<root>
		<level value="info" />
		<appender-ref ref="SOCKET" />
		<appender-ref ref="SOCKET_VERBOSE" />
	</root>

</log4j:configuration>

Server Anwendung
Auf Serverseite muss aus dem Log4j Packet die Main Methode Klasse org.apache.log4j.net.SimpleSocketServer gestartet werden.

Ein einfacher Aufruf zum Start des SimpleSocketServer mit ListenPort 8881 und Konfigurationsdatei log4j.xml sieht so aus:

java -cp log4j-1.2.16.jar org.apache.log4j.net.SimpleSocketServer 8881 log4j.xml

Die ankommenden LogEvents werden dann wie in der serveseitigen log4j.xml konfiguriert verarbeitet und auf dem Server geloggt.
Zu beachten ist dabei, dass keine weiteren Informationen vom Client nachgeholt werden können. Differrieren also die logLevel auf Client und Server oder werden keine LocationInfos auf dem Client generiert, so können diese auf dem Server auch ncht geloggt werden.

Um mehrere SimpleSocketServer auf verschiedenen Ports komfortabel zu starten und wieder stoppen zu können empfiehlt sich ein Shellscript.

startup.sh

#!/bin/bash

####
# Configuration
####
PORTS=8881:8882:8883
#PORTS=8881

####
# Constants
####
CLASSPATH=dependency/*
CONFIG=log4j.xml
LOG_PATH=.

####
# Code
####
IFS=:
for PORT in $PORTS; do

	# Environment variables
	HOST=`hostname`
	INSTANCE=:$PORT
	LOGBASE=$LOG_PATH/logs-$PORT

	if [ ! -d "$LOGBASE" ]; then
		mkdir -p $LOGBASE
	fi

	java -DHOST=$HOST -DINSTANCE=$INSTANCE -DLOGBASE=$LOGBASE -cp "$CLASSPATH" org.apache.log4j.net.SimpleSocketServer $PORT $CONFIG > $LOGBASE/catalina.out &
	PID=$!

	echo $PID > run-$PORT.pid

	echo "Started org.apache.log4j.net.SimpleSocketServer on port $PORT (PID $PID)."
done

shutdown.sh

#!/bin/bash

for i in `ls *.pid 2>/dev/null`; do

	PID=`cat $i`
	echo "$i (PID $PID) -- Shutting down..."
	kill $PID

	if [ "$?" == "0" ]; then
		rm $i
	fi

done

Server Konfiguration

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<!--
 log4j.properties
-->
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

	<!--
	#################################################################################################
	#                                  APPENDER CONFIGURATION                                       #
	#################################################################################################
	-->

	<!-- ################################################# -->
	<!-- ### TOMCAT appender (catalina.out/tomcat.log) ### -->
	<!-- ################################################# -->

	<!-- TOMCAT appender (catalina.out) -->
	<appender class="de.rrze.ppsa.log.ANSIConsoleAppender" name="TOMCAT">
		<param value="System.out" name="Target" />
		<layout class="org.apache.log4j.PatternLayout">
			<param value="[TOMCAT] %d{ISO8601} [%t] %5p %c{1} - %m%n" name="ConversionPattern" />
		</layout>
		<filter class="org.apache.log4j.varia.LevelRangeFilter">
      		<param name="LevelMax" value="INFO" />
    	</filter>
	</appender>

	<!-- TOMCAT_VERBOSE appender (catalina.out) -->
	<appender class="de.rrze.ppsa.log.ANSIConsoleAppender" name="TOMCAT_VERBOSE">
		<param value="System.out" name="Target" />
		<layout class="org.apache.log4j.PatternLayout">
			<param
				value="[TOMCAT] %d{ISO8601} [%t] %5p %C:%L%n=> Message: %m%n=> File: %F%n=> Caller: %l%n"
				name="ConversionPattern" />
		</layout>
		<filter class="org.apache.log4j.varia.LevelRangeFilter">
      		<param name="LevelMin" value="WARN" />
    	</filter>
	</appender>

	<!-- TOMCAT_FILE appender (tomcat.log) -->
	<appender class="org.apache.log4j.RollingFileAppender" name="TOMCAT_FILE">
		<param value="10 " name="MaxBackupIndex" />
		<param value="100MB " name="MaxFileSize" />
		<param value="${LOGBASE}/tomcat.log" name="File" />
		<layout class="org.apache.log4j.PatternLayout">
			<param value="[TOMCAT] %d{ISO8601} [%t] %5p %c{1} - %m%n" name="ConversionPattern" />
		</layout>
	</appender>

	<!-- TOMCAT_FILE_VERBOSE appender (tomcat.log) -->
	<appender class="org.apache.log4j.RollingFileAppender" name="TOMCAT_FILE_VERBOSE">
		<param value="10" name="MaxBackupIndex" />
		<param value="100MB " name="MaxFileSize" />
		<param value="${LOGBASE}/tomcat.log" name="File" />
		<layout class="org.apache.log4j.PatternLayout">
			<param
				value="[TOMCAT] %d{ISO8601} [%t] %5p %C:%L%n=> Message: %m%n=> File: %F%n=> Caller: %l%n"
				name="ConversionPattern" />
		</layout>
		<filter class="org.apache.log4j.varia.LevelRangeFilter">
      		<param name="LevelMin" value="WARN" />
    	</filter>
	</appender>

	<!-- ############################################### -->
	<!-- ### LOG4J appender (catalina.out/log4j.log) ### -->
	<!-- ############################################### -->

	<!-- LOG4J appender (catalina.out) -->
	<appender class="de.rrze.ppsa.log.ANSIConsoleAppender" name="LOG4J">
		<param value="System.out" name="Target" />
		<layout class="org.apache.log4j.PatternLayout">
			<param value="[LOG4J] %d{ISO8601} [%t] %5p %c{1} - %m%n" name="ConversionPattern" />
		</layout>
		<filter class="org.apache.log4j.varia.LevelRangeFilter">
      		<param name="LevelMax" value="INFO" />
    	</filter>
	</appender>

	<!-- LOG4J_VERBOSE appender (catalina.out) -->
	<appender class="de.rrze.ppsa.log.ANSIConsoleAppender" name="LOG4J_VERBOSE">
		<param value="System.out" name="Target" />
		<layout class="org.apache.log4j.PatternLayout">
			<param
				value="[LOG4J] %d{ISO8601} [%t] %5p %C:%L%n=> Message: %m%n=> File: %F%n=> Caller: %l%n"
				name="ConversionPattern" />
		</layout>
		<filter class="org.apache.log4j.varia.LevelRangeFilter">
      		<param name="LevelMin" value="WARN" />
    	</filter>
	</appender>

	<!-- LOG4J_FILE appender (log4j.log) -->
	<appender class="org.apache.log4j.RollingFileAppender" name="LOG4J_FILE">
		<param value="100MB " name="MaxFileSize" />
		<param value="${LOGBASE}/log4j.log" name="File" />
		<param value="10 " name="MaxBackupIndex" />
		<layout class="org.apache.log4j.PatternLayout">
			<param value="[LOG4J] %d{ISO8601} [%t] %5p %c{1} - %m%n" name="ConversionPattern" />
		</layout>
	</appender>

	<!-- LOG4J_FILE_VERBOSE appender (log4j.log) -->
	<appender class="org.apache.log4j.RollingFileAppender" name="LOG4J_FILE_VERBOSE">
		<param value="10" name="MaxBackupIndex" />
		<param value="100MB " name="MaxFileSize" />
		<param value="${LOGBASE}/log4j.log" name="File" />
		<layout class="org.apache.log4j.PatternLayout">
			<param
				value="[LOG4J] %d{ISO8601} [%t] %5p %C:%L%n=> Message: %m%n=> File: %F%n=> Caller: %l%n"
				name="ConversionPattern" />
		</layout>
		<filter class="org.apache.log4j.varia.LevelRangeFilter">
      		<param name="LevelMin" value="WARN" />
    	</filter>
	</appender>

	<!--
	#################################################################################################
	#                                  LOGGING CONFIGURATION                                        #
	#################################################################################################
	-->

	<!-- TOMCAT -->
	<logger additivity="false" name="org.apache.jk">
		<level value="all" />
		<appender-ref ref="TOMCAT" />
		<appender-ref ref="TOMCAT_VERBOSE" />
		<appender-ref ref="TOMCAT_FILE" />
		<appender-ref ref="TOMCAT_FILE_VERBOSE" />
		<appender-ref ref="SMTP" />
	</logger>

	<logger additivity="false" name="org.apache.coyote">
		<level value="all" />
		<appender-ref ref="TOMCAT" />
		<appender-ref ref="TOMCAT_VERBOSE" />
		<appender-ref ref="TOMCAT_FILE" />
		<appender-ref ref="TOMCAT_FILE_VERBOSE" />
		<appender-ref ref="SMTP" />
	</logger>

	<logger additivity="false" name="org.apache.tomcat">
		<level value="all" />
		<appender-ref ref="TOMCAT" />
		<appender-ref ref="TOMCAT_VERBOSE" />
		<appender-ref ref="TOMCAT_FILE" />
		<appender-ref ref="TOMCAT_FILE_VERBOSE" />
		<appender-ref ref="SMTP" />
	</logger>

	<logger additivity="false" name="org.apache.catalina">
		<level value="all" />
		<appender-ref ref="TOMCAT" />
		<appender-ref ref="TOMCAT_VERBOSE" />
		<appender-ref ref="TOMCAT_FILE" />
		<appender-ref ref="TOMCAT_FILE_VERBOSE" />
		<appender-ref ref="SMTP" />
	</logger>

	<logger additivity="false" name="org.apache.commons.digester">
		<level value="all" />
		<appender-ref ref="TOMCAT" />
		<appender-ref ref="TOMCAT_VERBOSE" />
		<appender-ref ref="TOMCAT_FILE" />
		<appender-ref ref="TOMCAT_FILE_VERBOSE" />
		<appender-ref ref="SMTP" />
	</logger>

	<logger additivity="false" name="org.apache.jasper">
		<level value="all" />
		<appender-ref ref="TOMCAT" />
		<appender-ref ref="TOMCAT_VERBOSE" />
		<appender-ref ref="TOMCAT_FILE" />
		<appender-ref ref="TOMCAT_FILE_VERBOSE" />
		<appender-ref ref="SMTP" />
	</logger>

	<!-- LOG4J -->
	<logger additivity="false" name="org.apache.log4j">
		<level value="all" />
		<appender-ref ref="LOG4J" />
		<appender-ref ref="LOG4J_VERBOSE" />
		<appender-ref ref="LOG4J_FILE" />
		<appender-ref ref="LOG4J_FILE_VERBOSE" />
		<appender-ref ref="SMTP" />
	</logger>

	<!--
	#################################################################################################
	#                               SPECIAL FIXES CONFIGURATION                                     #
	#################################################################################################
	-->

	<!--
		Handles "Startup when already running" or
		"Shutdown when already stopped" errors, so that
		no mails are sent for these cases.
	-->
	<logger additivity="false" name="org.apache.coyote.http11.Http11Protocol">
		<level value="all" />
		<appender-ref ref="TOMCAT" />
		<appender-ref ref="TOMCAT_VERBOSE" />
	</logger>
	<logger additivity="false" name="org.apache.catalina.startup.Catalina">
		<level value="all" />
		<appender-ref ref="TOMCAT" />
		<appender-ref ref="TOMCAT_VERBOSE" />
	</logger>

	<!--
	#################################################################################################
	#                                 ROOT LOGGER CONFIGURATION                                     #
	#################################################################################################
	-->
	<root>
		<level value="all" />
		<appender-ref ref="DEFAULT" />
		<appender-ref ref="DEFAULT_VERBOSE" />
		<appender-ref ref="DEFAULT_FILE" />
		<appender-ref ref="DEFAULT_FILE_VERBOSE" />
		<appender-ref ref="SMTP" />
	</root>

</log4j:configuration>

2. Syslog-Lösung

Bei dieser Lösung werden die Logmeldungen in der Clientanwendung von Log4j verarbeitet und fertig gerendert als String an den konfigurierten Syslog Dienst geschickt. Diese Übertragung nutzt das UDP Protokoll. Ein „Reconnect“ bei Downtime des Syslog Dienstes ist also nicht nötig.

Der Syslog Dienst bietet verschiedene Filtermöglichkeiten, um die empfangenen Logmeldungen in Logdateien einzusortieren. Außerdem werden Logmeldungen über konfigurierbare Kanäle — genannt Facilities — empfangen. Diese sind allerdings statisch definiert und nur begrenzt vorhanden. Für generische Anwendungen stehen die Facilities LOCAL0 bis LOCAL7 bereit.

Die Vorteile dieser Lösung sind

  • Ist bereits eine Infrastruktur mit syslogbasiertem zentralem Logging vorhanden, kann diese einfach genutzt werden.

Client Anwendung
Die JAVA Client Anwendung muss natürlich Log4j nutzen.
Die vorhandene Log4j Konfiguration kann dann zum Senden der LogEvents an die Server Anwendung einfach wie im folgenden Beispiel angepasst werden.

Client Konfiguration

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

	<!--
		log4j.xml

		Basic configuration which sends all log messages to the
		configured syslog host using a SyslogAppender.

	-->
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

	<!--
		#################################################################################################
		# APPENDER CONFIGURATION #
		#################################################################################################
	-->

	<!-- SYSLOG appender -->
	<appender class="org.apache.log4j.net.SyslogAppender" name="SYSLOG">
		<param value="127.0.0.1" name="syslogHost" />
		<param value="LOCAL0" name="facility" />
		<layout class="org.apache.log4j.PatternLayout">
			<param value="[UNKNOWN] %d{ISO8601} [%t] %5p %c - %m%n" name="ConversionPattern" />
		</layout>
		<filter class="org.apache.log4j.varia.LevelRangeFilter">
      		<param name="LevelMax" value="INFO" />
    	</filter>
	</appender>

	<!-- SYSLOG_VERBOSE appender -->
	<!-- (also generates location info) -->
	<appender class="org.apache.log4j.net.SyslogAppender" name="SYSLOG_VERBOSE">
		<param value="127.0.0.1" name="syslogHost" />
		<param value="LOCAL0" name="facility" />
		<layout class="org.apache.log4j.PatternLayout">
			<param
				value="[UNKNOWN] %d{ISO8601} [%t] %5p %C:%L%n=> Message: %m%n%n=> File: %F%n=> Caller: %l%n%n"
				name="ConversionPattern" />
		</layout>
		<filter class="org.apache.log4j.varia.LevelRangeFilter">
      		<param name="LevelMin" value="WARN" />
    	</filter>
	</appender>

	<!--
		#################################################################################################
		# LOGGING CONFIGURATION #
		#################################################################################################
	-->

	<!-- TOMCAT -->
	<logger name="org.apache.jk">
		<level value="info" />
	</logger>

	<logger name="org.apache.coyote">
		<level value="info" />
	</logger>

	<logger name="org.apache.tomcat">
		<level value="info" />
	</logger>

	<logger name="org.apache.catalina">
		<level value="info" />
	</logger>

	<logger name="org.apache.commons.digester">
		<level value="info" />
	</logger>

	<logger name="org.apache.jasper">
		<level value="info" />
	</logger>

	<!--
		#################################################################################################
		# ROOT LOGGER CONFIGURATION #
		#################################################################################################
	-->
	<root>
		<level value="info" />
		<appender-ref ref="SYSLOG" />
		<appender-ref ref="SYSLOG_VERBOSE" />
	</root>

</log4j:configuration>