Quantcast
Channel: C2B2 Blog
Viewing all articles
Browse latest Browse all 223

Alternative Logging Frameworks for Application Servers: GlassFish

$
0
0

Introduction

Sometimes the default logger just isn't enough...
Welcome to the first instalment in what will be a four-part series on configuring Application Servers to use alternative Logging Frameworks. The first in this series will cover GlassFish, and how to configure it to make use of Log4j, and SLF4J with Logback.


This blog was written using a 64-bit Linux Distro, GlassFish 4.0, Logback 1.2.2, SLF4J 1.7.7, Log4j 2.0, and web-app version 3.1, with all coding for the tests done in NetBeans 8.0.

Log4j

Assuming you have downloaded both GlassFish and Log4j, begin by copying the log4j-api-2.0.jar and log4j-core-2.0.jar JARs to your GlassFish’s lib directory: $GF_Home/glassfish/lib

Next we need to provide GlassFish with a config file for it to get the logging configuration from. We will take advantage of Log4j automatic configuration, and place the configuration file on the GlassFish domain’s classpath. To this end, create a file called log4j2.xml and place it in the domain’s classpath: $GF_Home/glassfish/domains/domain1/lib/classes

As a quick note, I am not just calling the config file log4j2.xml for clarity’s sake; it must be named this for automatic configuration to work. If Log4j cannot find an XML or JSON file with the name log4j2 on the classpath, it will fall back to a default configuration where log messages of level error are output to the console.

Inside the config file we just created, copy and paste the below to provide a simple test logging configuration:
<?xml version="1.0" encoding="UTF-8"?>  
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration> 
This will print out log messages of any level called from the application to the server.log log file of the domain or instance that the application is running on, only printing the hour and minute that the message was logged at (probably bad practice, but it helps to prove that it's working).

And that’s it! This configuration will apply itself to all instances created under this domain, allowing you to use Log4j with all of your applications.

Testing it Out

If you need something to test this with: create a servlet; import the log4j-core-2.0.jar and log4j-api-2.0.jar JARs into the project; and import Log4j into the servlet:
 import org.apache.logging.log4j.*;  
Then declare and initialise a logger:
 private static Logger logger = LogManager.getLogger(TestServlet.class.getName());
Finally, add some log statements inside the processRequest method:
 protected void processRequest(HttpServletRequest request, HttpServletResponse response)  
throws ServletException, IOException
{
response.setContentType("text/html;charset=UTF-8");
try (PrintWriter out = response.getWriter())
{
logger.trace("Tracing");
logger.debug("Debugging!");
logger.info("Info - something may be about to go wrong...");
logger.warn("Warning! Upcoming error!");
logger.error("Aaaaaaaahhhhhhhhhhhhhh!!!!!!!!!!!");
}
}
Deploy and run the application (just create a simple web page that directs to it), and within the GlassFish server.log file you should see the following messages:
 [2014-07-29T10:58:10.511+0100] [glassfish 4.0] [INFO] [] [] [tid: _ThreadID=21 _ThreadName=Thread-3] [timeMillis: 1406627890511] [levelValue: 800] [[  
10:58 [http-listener-1(4)] TRACE testing.TestServlet - Tracing]]
[2014-07-29T10:58:10.511+0100] [glassfish 4.0] [INFO] [] [] [tid: _ThreadID=21 _ThreadName=Thread-3] [timeMillis: 1406627890511] [levelValue: 800] [[
10:58 [http-listener-1(4)] DEBUG testing.TestServlet - Debugging!]]
[2014-07-29T10:58:10.511+0100] [glassfish 4.0] [INFO] [] [] [tid: _ThreadID=21 _ThreadName=Thread-3] [timeMillis: 1406627890511] [levelValue: 800] [[
10:58 [http-listener-1(4)] INFO testing.TestServlet - Info - something may be about to go wrong...]]
[2014-07-29T10:58:10.511+0100] [glassfish 4.0] [INFO] [] [] [tid: _ThreadID=21 _ThreadName=Thread-3] [timeMillis: 1406627890511] [levelValue: 800] [[
10:58 [http-listener-1(4)] WARN testing.TestServlet - Warning! Upcoming error!]]
[2014-07-29T10:58:10.511+0100] [glassfish 4.0] [INFO] [] [] [tid: _ThreadID=21 _ThreadName=Thread-3] [timeMillis: 1406627890511] [levelValue: 800] [[
10:58 [http-listener-1(4)] ERROR testing.TestServlet - Aaaaaaaahhhhhhhhhhhhhh!!!!!!!!!!!]]
SLF4J with Logback

SLF4J is a façade, acting as the “middleman” between the actual logger and the application, and can be used with several logging frameworks (including the GlassFish default java.util.logging, and Log4j). The JAR files GlassFish will need for it to use SLF4J are (assuming you’re using the same version):
  • logback-classic-1.2.2.jar
  • logback-core-1.1.2.jar
  • slf4j-api-1.7.7.jar
  • jul-to-slf4j-1.7.7.jar
The first two JARs are available from Logback, whereas the last two are included in the download for SLF4J. Place these JARs into the endorsed directory, which can be found under the lib directory: $GF_Home/glassfish/lib/endorsed

Create and place a configuration file named logback.xml in the domain’s config directory: $GF_Home/glassfish/domains/domain1/config

As we are not using the same method of automatic configuration as we were with Log4j, the config file does not need to match a prescribed name, or be placed on the classpath, so you can name and place it as you like (we specify the name and location later). Copy and paste the following into this newly created config file:
<configuration debug="true">  
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${com.sun.aas.instanceRoot}/logs/server.log</file>
<append>true</append>
<encoder>
<Pattern>%d{HH:mm:ss} [%thread] %-5level %logger{52} - %msg%n</Pattern>
</encoder>
</appender>
<root>
<level value="TRACE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
Next is to create a bridge between the default java.util.logging logger, and SLF4J. In the logging.properties file (found under $GF_Home/glassfish/domains/domain1/config), under the “All attribute details” heading, do the following:
  • Change the handlers attribute to org.slf4j.bridge.SLF4JBridgeHandler
    • This is to bridge any java.util.logging log statements to SLF4J
  • Set com.sun.enterprise.server.logging.GFFileHandler.file to ${com.sun.aas.instanceRoot}/tmp/server.log
    • This is to stop there being any file conflicts from both GlassFish and SLF4J trying to write to the same file (only a few lines of code will be logged here when GlassFish is started as it won't have loaded SLF4J yet).
  • Add the following attributes:
    • com.sun.enterprise.server.logging.GFFileHandler.formatter=com.sun.enterprise.server.logging.UniformLogFormatter
    • com.sun.enterprise.server.logging.GFFileHandler.alarms=false
With our properties configured, we now need to point and instruct GlassFish to actually use them! Open up the admin console, navigate to the JVM Options tab of the JVM Settings page, under the server-config settings, and add the following options:
  • -Djava.util.logging.config.file=${com.sun.aas.instanceRoot}/config/logging.properties
  • -Dlogback.configurationFile=file:///${com.sun.aas.instanceRoot}/config/logback.xml
Restart GlassFish, and away you go! Unlike when using Log4j with GlassFish (directly), Logback will be used for almost all of the logging statements from GlassFish, not just those explicitly called from within the application. This convenience is not without cost though; the java.util.logging to SLF4J bridge has to translate the LogRecord objects used in java.util.logging to the SLF4J equivalent. This causes a 20% performance cost increase for enabled log statements, and a 6000% (!) increase for disabled log statements. A solution exists for the massive cost increase for disabled log statements however;add the highlighted line to the logback configuration file (logback.xml). This propagates changes made to the level of Logback loggers to the java.util.logging framework, meaning that only enabled log statements are sent over the SLF4J bridge (with the 20% cost increase):
<configuration debug="true">  
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
Note, that since we are specifying the location of the configuration and properties files as being under the instance root, the configuration detailed in this blog can only currently be used for the Admin Server; GlassFish will not properly propagate the settings to instances created under this domain. To use SLF4J for each of the instances created under this domain (using this current configuration), you must copy and paste both the logging.properties and logback.xml files to the config directory of each instance (or place them in a location accessible by all instances that does not rely on the instanceRoot variable), as well as add the JVM Options that we added to the configuration settings of each of the instances (or add them to the default-config so any new instances have them set automatically upon creation).

Testing it Out

As before, if you need something to test this out with: create a servlet; import the logback-classic-1.2.2.jar logback-core-1.1.2.jar, and slf4j-api-1.7.7.jar JARs into the project; and import SLF4J into the servlet:
 import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Declare and initialise a logger:
 private static Logger logger = LoggerFactory.getLogger(TestServlet.class.getName()); 
And add the logging messages to the processRequest method:
 protected void processRequest(HttpServletRequest request, HttpServletResponse response)  
throws ServletException, IOException
{
response.setContentType("text/html;charset=UTF-8");
try (PrintWriter out = response.getWriter())
{
logger.trace("Tracing");
logger.debug("Debugging!");
logger.info("Info - something may be about to go wrong...");
logger.warn("Warning! Upcoming error!");
logger.error("Aaaaaaaahhhhhhhhhhhhhh!!!!!!!!!!!");
}
}
Within the GlassFish server.log file, you should see not only our log messages, but the other debug messages as well:
11:24 [http-listener-1(1)] DEBUG org.glassfish.grizzly.websockets.WebSocketFilter - handleRead websocket: null content-size=0 headers=
HttpRequestPacket (
method=GET
url=/LogbackTest/TestServlet
query=testybutton=push+meh%21
protocol=HTTP/1.1
content-length=-1
headers=[
host=linux-njt2:8080
user-agent=Mozilla/5.0 (X11; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0
accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-language=en-gb,en;q=0.5
accept-encoding=gzip, deflate
referer=http://linux-njt2:8080/LogbackTest/
connection=keep-alive]
)
11:24 [http-listener-1(1)] DEBUG org.glassfish.grizzly.filterchain.DefaultFilterChain - after execute filter. filter=org.glassfish.grizzly.extras.addons.WebSocketAddOnProvider$GlassfishWebSocketFilter@28d6271 context=FilterChainContext [connection=TCPNIOConnection{localSocketAddress={/127.0.0.1:8080}, peerSocketAddress={/127.0.0.1:43194}}, operation=READ, message=org.glassfish.grizzly.http.HttpContent@40e01f50, address=/127.0.0.1:43194] nextAction=org.glassfish.grizzly.filterchain.InvokeAction@4f86bdf5
11:24 [http-listener-1(1)] DEBUG org.glassfish.grizzly.filterchain.DefaultFilterChain - Execute filter. filter=org.glassfish.grizzly.http.server.HttpServerFilter@1039ea06 context=FilterChainContext [connection=TCPNIOConnection{localSocketAddress={/127.0.0.1:8080}, peerSocketAddress={/127.0.0.1:43194}}, operation=READ, message=org.glassfish.grizzly.http.HttpContent@40e01f50, address=/127.0.0.1:43194]
11:24 [http-listener-1(1)] TRACE testing.TestServlet - tracing
11:24 [http-listener-1(1)] DEBUG testing.TestServlet - Debugggggggggggggging!
11:24 [http-listener-1(1)] INFO testing.TestServlet - infoooooooo
11:24 [http-listener-1(1)] WARN testing.TestServlet - warnings!
11:24 [http-listener-1(1)] ERROR testing.TestServlet - aaaaaaaahhhhhhhhhhhhhh!!!!!!!!!!!
11:24 [http-listener-1(1)] TRACE org.glassfish.grizzly.ProcessorExecutor - executing connection (TCPNIOConnection{localSocketAddress={/127.0.0.1:8080}, peerSocketAddress={/127.0.0.1:43194}}). IOEvent=NONE processor=org.glassfish.grizzly.filterchain.DefaultFilterChain@73f5289
Wrapping Up 
That’s it for the first instalment in this logging series, showing you that, albeit with a bit of tinkering, GlassFish can be configured to use other logging frameworks, potentially affording you greater logging flexibility. While you wait for the next in the series (covering WebLogic), check out some of our other blogs on GlassFish, such as How to install GlassFish on Ubuntu Touch, or our Introduction to Connection Pools in GlassFish.


Viewing all articles
Browse latest Browse all 223

Trending Articles