Developer Guide

 Introducing the jWebSocket Logging PlugIn

The Logging PlugIn is a jWebSocket framework extension to provide an advanced logging system to be used by our server and client side applications to store log outputs. It was necessary to create this logging mechanism so we could have personalized log outputs, as well as filters and restrictions of the data that we wanted to log.
Logging PlugIn can be configured in order to store the log files in different configurable locations, for example, in the filesystem, in a MySQL database, in a MongoDB database or simply in the System Console.

LoggingPlugIn requirements

Logging PlugIn is just an extension of the Log4j logging system reusing it's functionalities. In order to allow the LoggingPlugIn to run we created an entry in our log4j.xml configuration file which simply adds our class org.jwebsocket.logging.JWSLog4JAppender which extends org.apache.log4j.AppenderSkeleton. This JWSLog4JAppender class contains a list of appenders which are called once the jWebSocketAppender is instantiated by the Log4j logging mechanism (See below configuration). 

Fragment of the log4j.xml configuration file within JWEBSOCKET_HOME folder.

<appender name="jWebSocketAppender" class="org.jwebsocket.logging.JWSLog4JAppender">
	<level value="debug"/>
</appender>

Once the JWSLog4JAppender is configured, the LoggingPlugIn uses this JWSLog4JAppender to add a list of Appenders configured in the LoggingPlugIn/logging.xml configuration file.

PlugIn Class Diagram


Figure 1: PlugIn Class Diagram

JWSJDBCAppender class

The PlugIn can be configured to include as many Appenders as the user wants, one of the most important appenders is the JWSJDBCAppender which is provided within the PlugIn package. The JWSJDBCAppender as it's name indicates is intended to store the logs in a relational database using the JDBCPlugIn.

Appender JDBC Connection configuration

Therefore if we want to use the JWSJDBCAppender to store the logs in our database we first need to create an alias for the connectionin the JDBCPlugIn configuration file located in JWEBSOCKET_HOME/conf/JDBCPlugIn/jdbc.xml as follows:

<!-- the MySQL DataSource (parameterized for configuration via a PropertyPlaceHolderConfigurer)  -->
<bean id="mysqlDataSource_jwsLogging" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
	<!-- the JDBC driver class for MySQL -->
	<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
	<!-- the URL to the MySQL server -->
	<property name="url" value="jdbc:mysql://localhost:3306/jwebsocket_logs"/>
	<!-- database user and password for this connection -->
	<property name="username" value="jwsLog"/>
	<!-- password, please ensure that the location of this 
	JDBC config file is properly secured in your network! -->
	<property name="password" value="log_password"/>
	<!-- Timeout in seconds before connection validation queries fail. -->
	<property name="validationQueryTimeout" value="5"/>
	<!-- The maximum number of milliseconds that the pool will wait 
	(when there are no available connections) for a connection to be 
	returned before throwing an exception, or <= 0 to wait indefinitely. -->
	<property name="maxWait" value="3000"/>
	<!-- The initial number of connections that are created 
	when the pool is started. -->
	<property name="initialSize" value="2"/>
	<!-- The maximum number of active connections that can be allocated 
	from this pool at the same time, or negative for no limit. --> 
	<property name="maxActive" value="2"/>
</bean>

<bean id="mySQLNativeAccess_jwsLogging" class="org.jwebsocket.plugins.jdbc.NativeAccess">
	<!-- here you can select which datasource to use 
	for the native JDBC access component (field "ref") -->
	<property name="dataSource" ref="mysqlDataSource_jwsLogging"/>

	<!-- MySQL command to retrieve a sequence value -->
	<property name="selectSequenceSQL" value="select getSequence('${sequence}')"/>
	<!-- MySQL command to call a function -->
	<property name="execFunctionSQL" value="select ${funcname}(${args})"/>
	<!-- MySQL command to execute a stored procedure -->
	<property name="execStoredProcSQL" value="call ${procname}(${args})"/>
</bean>

And last but not least, we still need to assign an alias to that connection in the same file within the JDBC PlugIn configuration settings:

<bean id="settings" class="org.jwebsocket.plugins.jdbc.Settings">
	<property name="connections">
		<map>
			<entry key="jwsLogging" value-ref="mySQLNativeAccess_jwsLogging"/>
			<!-- Other aliases here -->
		</map>
	</property>
</bean>

PlugIn configuration

The JWebSocketLoggingPlugIn is easily configurable by just modifying the file JWEBSOCKET_HOME/conf/LoggingPlugIn/logging.xml, please note that here you can include as many appenders as you want:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" " http://www.springframework.org/dtd/spring-beans.dtd"> 
<beans>
	<!-- this is the simple log4j logger which logs to all targets configured in the log4j.xml -->
	<bean id="targetLog4J" class="org.jwebsocket.plugins.logging.Log4JLogger">
	</bean>

	<bean id="demoAppender" class="org.jwebsocket.plugins.logging.DemoAppender">
	</bean>

	<bean id="jdbcAppender" class="org.jwebsocket.plugins.logging.JWSJDBCAppender">
		<property name="JDBCConnAlias" value="jwsLogging"/>
		<property name="JDBCPlugInID" value="jws.jdbc"/>
		<property name="TableName" value="logs_table"/>
		<!-- Cleanup Interval and LogDuration are configured with one of the 
		following values: <value Integer><MICROSECOND | SECOND | MINUTE | 
		HOUR | DAY | WEEK | MONTH | QUARTER| YEAR>
		the cleanup mechanism will be executed by the first time on server startup -->
		<property name="CleanupInterval" value="1 DAY"/>
		<!-- Log Duration in the database, for example, a LogDuration=1DAY 
		indicates to MySQL server to remove the logs with date less than 
		1 day before the current date -->
		<!-- MICROSECOND, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR --> 
		<property name="LogDuration" value="1 MONTH"/>
		<property name="CleanupQuery">
			<value><![CDATA[DELETE FROM `${db_table}` WHERE `time_stamp` < ADDDATE(NOW(), INTERVAL - ${log_duration});]]></value>
		</property>
		
		<property name="CreateTableQuery" value="CREATE TABLE 
			IF NOT EXISTS ${db_table} (id int(20) unsigned NOT NULL auto_increment, message varchar(10000),
			time_stamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, level varchar(20), class_name varchar(255), 
			method_name varchar(255), line_number int(20), filename varchar(300), 
			logger_name varchar(255), thread_name varchar(255), 
			stack_trace varchar(1024) not null, username varchar(50) not null, 
			ip_number varchar(40), hostname varchar(255), product varchar(255), 
			module varchar(255), classification varchar(255), version varchar(255), 
			environment varchar(255), error_code varchar(255), system varchar(255), 
			system_version varchar(255), condition_value varchar(255), source varchar(255), 
			target varchar(255),connector_id varchar(255), primary key (id));"/>
		<property name="InsertQuery" value="INSERT INTO ${db_table} (time_stamp, level, class_name, 
			method_name, line_number, filename, logger_name, thread_name, 
			stack_trace, message, username, ip_number, hostname, product,
			module, classification, version, environment, error_code,
			system, system_version, condition_value, source, target, connector_id) VALUES 
			('%d{yyyy-MM-dd HH:mm:ss}','%p', '%c', '%M', '%L', '%f', '%g', '%T', '%s', '%m', '%u', 
			'%n', '%h', '%P', '%D', '%C', '%v', '%e', '%E', '%S', '%V', '%o', 
			'%U', '%G', '%O')"/>
		
		<!-- fieldFilterList, this property allows us to set a certain 
		white/black List configuration patterns that will restrict the 
		events that are being logged in the database.
		For example, if we only want to log the TokenServer class 
		information, then we need to add the following configuration:
			<property name="fieldName" value="class_name" />
			<property name="blackList" value="*" />
			<property name="whiteList" value="*TokenServer" />
		Other pattern configurations could be:
			<property name="blackList" value="startsWith*, *contains*, *endsWith, equals" />
		NOTE: Please don't use wildcard for whiteList and blackList at the 
		same time, this doesn't make any sense
		Uncomment the below configuration to see it working
		-->
		<!--<property name="fieldFilterList">
			<list>
				<bean class="org.jwebsocket.logging.LoggingEventFieldFilter">
					<property name="fieldName" value="class_name" />
					<property name="whiteList" value="*" />
					<property name="blackList" value="*TokenServer" />
				</bean>
				<bean class="org.jwebsocket.logging.LoggingEventFieldFilter">
					<property name="fieldName" value="hostname" />
					<property name="blackList" value="localhost" />
				</bean>
			</list>
		</property>-->
	</bean>

	<!-- here you configure which logger of the above ones to use in the plug-in -->
	<bean id="org.jwebsocket.plugins.logging.settings" class="org.jwebsocket.plugins.logging.Settings">
		<property name="target" ref="targetLog4J"/>
		<property name="appenders">
			<list>
				<!-- Select here the appenders to be used -->
				<!-- <ref local="demoAppender"/> -->
				<ref local="jdbcAppender"/>
			</list>
		</property>
	</bean>

</beans>

Some of the JWSJDBCAppender arguments explained below:

 
  • JDBCConnAlias: Please note that the JWSJDBCAppender requires as argument the connection alias that we previously created in the JDBCPlugIn configuration. 
  • JDBCPlugInID: The identifier of the JDBCPlugIn, so we can access to it from within the JWSJDBCAppender class.
  • TableName: A unique centralized place where we write the name of the table that will be used for all queries.
  • CleanupInterval & LogDuration: As everyone knows a log table in the database could reach up to 500 or more Mb per day, so we decided to provide a cleanup mechanism that executes every configured time and removes from the database all information before the current time minus the LogDuration.
  • CleanupQuery: The JDBCAppender is easily configurable allowing the user even to decide what kind of query would clean up the database, this of course provides more flexibility to our users and more clean code in our PlugIn.
  • CreateTableQuery & InsertQuery: The queries that will be used by the JWSJDBCPatternLayout in order to replace variables like "%d{yyyy-MM-dd HH:mm:ss}" by the current time or "%c" by the class_name and return the query ready to be executed by the JDBC PlugIn. JWSJDBCPatternLayout utilizes a JWSPatternParser which receives an Apache LoggingEvent and also a Query, then it formats the event filling every single variable from the query returning a clean query to be inserted in the database. These both queries are clearly visible and configurable by the user, so they are not hardcoded in our code.
  • FieldFilterList: This property allows us to set a certain white/black List configuration patterns that will restrict the events that are being logged in the database. For example, if we only want to log the output from the TokenServer class, then we need to add the following configuration to our FieldFilterList:
  • <property name="fieldFilterList">
         <list>
            <bean class="org.jwebsocket.logging.LoggingEventFieldFilter">
               <property name="fieldName" value="class_name" />
               <property name="blackList" value="*" />
               <property name="whiteList" value="*TokenServer" />
             </bean>
        </list>
    </property>
 
Log4JLogger
The Log4JLogger is a Logger implementation for log4j which logs to all targets configured in the log4j.xml file. In other words, it is an abstraction layer that passes events of type "INFO, WARN, ERROR and FATAL" to the configured org.apache.log4j.Logger within the log4j.xml. It is used by the PlugIn in order to log all incomig tokens from the LoggingPlugIn on the client side, allowing us to log not only LoggingEvents from the server, but also LoggingEvents from the client.

Configuring the LoggingPlugIn from the client side (JavaScript):
 

1- Required libraries to load the plugin on the client:
<script src="../../res/js/jWebSocket.js" type="text/javascript"></script>
<script src="../../res/js/jwsLoggingPlugIn.js" type="text/javascript"></script>
 
2- Configure your log level:
var lLogLevel = jws.LoggingPlugIn.DEBUG;
// jws.LoggingPlugIn.FATAL
// jws.LoggingPlugIn.INFO
// jws.LoggingPlugIn.WARN
// jws.LoggingPlugIn.ERROR
3- Send a simple log line to the server:
function sendLog() {
	var lMsg = eMessage.value;
	log("Logging '" + lMsg + "'...");
	try {
		var lRes = lWSC.loggingLog(lLogLevel, lMsg, {level: lLogLevel}, {
			OnResponse: function (aToken) {
				log("Logged: " + aToken.msg);
			}
		});
		if (0 !== lRes.code) {
			log("Error: " + lRes.msg);
		}
	} catch (ex) {
		log("Exception: " + ex.message);
	}
}
4- Some other log functions that may help you:
function debug() {
	var lMsg = eMessage.value;
	log("Debug '" + lMsg + "'...");
	lWSC.debug(lMsg, {info: "debug"});
}

function info() {
	var lMsg = eMessage.value;
	log("Info '" + lMsg + "'...");
	lWSC.info(lMsg, {info: "info", client: "Logging Demo", user: lWSC.getUsername()});
}

function warn() {
	var lMsg = eMessage.value;
	log("Warn '" + lMsg + "'...");
	lWSC.warn(lMsg, {info: "warn"});
}

function error() {
	var lMsg = eMessage.value;
	log("Error '" + lMsg + "'...");
	lWSC.error(lMsg, {info: "error"});
}

function fatal() {
	var lMsg = eMessage.value;
	log("Fatal '" + lMsg + "'...");
	lWSC.fatal(lMsg, {info: "fatal"});
}
Please feel free to visit our LoggingPlugIn demo jWebSocketClient/demos/logging/logging.htm available in the download package.

 

How does the logs table look on my local MySQL server:
Conclusions
With the LoggingPlugIn we have reached an advanced scalable logging system that can be configured and personalized reusing all the functionalities of Apache Log4j library and still adapting it to our specific requirements.
The JWSJDBCAppender class is aimed to let the user log all the logging output of our jWebSocketServer to the database. This information can be easily queried with Jasper Reports or any other Database PDF reporter. The biggest advantage that this PlugIn has is that we can add or remove new columns to the database without touching the code by just modifying the configuration parameters CreateTableQuery and InsertQuery from the JWSJDBCAppender class.
In the near future the will be supported appenders to save the data in non-relatinal databases like MongoDB.
Publications

Learn more about WebSockets in general, get background information and gain deeper insight!

Join jWebSocket

Wether developer, designer or translator – join the jWebSocket team and grow together with our success!

Copyright © 2013 Innotrade GmbH. All rights reserved.