Hacking Eclipse’s GlassFish plug-in to show better log messages

I’ve been using Eclipse 3.7 (Indigo) and GlassFish 3.1 for some projects in which I use Java EE 6. To integrate GlassFish in Eclipse I use a GlassFish plug-in that you can easily install pointing Eclipse to the update site http://download.java.net/glassfish/eclipse/helios.

This integration works great! I can deploy Enterprise Applications to GlassFish, start and stop the server, enable/disable automatic redeploy when something in the project changes, etc. Furthermore, Eclipse shows GlassFish log entries (i.e. everything that is sent to $GLASSFISH_HOME/glassfish/domains/domain1/logs/server.log) in its Console view so you can see what’s going on from the IDE. However, it was bothering me that the log entries were formatted in a way that didn’t show the logger name (usually the package/class that originated of the message) and I couldn’t configure a custom log formatter to change them.

Therefore, I came up with a hack to change it and I’m posting the instructions here if anyone else wants to try it. Before the instructions, however, it’s important to note three things:

  • I’ve been calling this a hack for a good reason: it might break your Eclipse installation and you might have to reinstall Eclipse or something. You’ve been warned!
  • I don’t distribute the modified plug-in here because I didn’t have the patience to read its license to see if this is allowed. To avoid problems, I give you the instructions and you can repeat them;
  • I’m not a skilled Eclipse plug-in developer, so I’m just changing the current format of the log messages to a different one, which will continued to be fixed. A skilled plug-in developer would add options to customize the format of the log messages. Who knows? Maybe I’ll try that to learn more about Eclipse plug-ins and then I could even contribute back to the GlassFish project! If you think this is a good idea, I encourage you to do so! Leave a comment if you do that…

Given the above disclaimer, let’s start the hack. Here’s a step-by-step procedure:

  1. I will assume you have installed: GlassFish 3.1, Eclipse 3.7 (Indigo) and the GlassFish plug-in for Eclipse;

  2. Checkout the source code for the GlassFish plug-in using Subversion: [cci_bash]svn checkout https://svn.java.net/svn/glassfishplugins~svn[/cci_bash] (this will take a while… I don’t have a lot of experience with SVN, so if anyone knows how to checkout only the part we are interested here, I’d appreciate if you leave the instructions as a comment to this post);

  3. In the downloaded sources, open the file trunk/eclipse-support/oepe/oracle.eclipse.tools.glassfish/src/com/sun/enterprise/jst/server/sunappsrv/log/V3LogFilter.java with your favorite Java editor and hack it appropriately. This is discussed after the instructions, below;

  4. At this point I tried to rebuild the plug-in using the ant build file in trunk/eclipse-support/oepe/oracle.eclipse.tools.glassfish/, but that didn’t work. So I just compiled the hacked class by opening a terminal/command prompt at the trunk/eclipse-support/oepe/oracle.eclipse.tools.glassfish/src and using javac: [cci_bash]javac com/sun/enterprise/jst/server/sunappsrv/log/V3LogFilter.java[/cci_bash] (you might need to use [cci]\[/cci] instead of [cci]/[/cci] if you’re using Windows);

  5. The Java compiler should create three class files in the com/sun/enterprise/jst/server/sunappsrv/log/ folder: V3LogFilter.class, V3LogFilter$Filter.class and V3LogFilter$LogFileFilter.class. Check that they were indeed created. We will use them further ahead;

  6. In your Eclipse installation, copy the file plugins/oracle.eclipse.tools.glassfish_3.2.3.201106220649.jar to a temporary folder and unpack it using your favorite ZIP tool, creating the oracle.eclipse.tools.glassfish_3.2.3.201106220649 folder with its contents;

  7. Copy the class files mentioned in step 5 to the appropriate folder (i.e., com/sun/enterprise/jst/server/sunappsrv/log/) inside the oracle.eclipse.tools.glassfish_3.2.3.201106220649 folder, overwriting the files that are there. We are basically changing the existing implementation of the log filter with our hacked one;

  8. Now, repack the plug-in using the jar tool from the JDK. Open the terminal in the oracle.eclipse.tools.glassfish_3.2.3.201106220649 folder and execute: [cci_bash]jar cvfm ../oracle.eclipse.tools.glassfish_3.2.3.201106220649.jar META-INF/MANIFEST.MF *[/cci_bash] (if the file oracle.eclipse.tools.glassfish_3.2.3.201106220649.jar already exists in the parent directory, delete it beforehand);

  9. Copy the newly packaged oracle.eclipse.tools.glassfish_3.2.3.201106220649.jar file created by the jar tool to Eclipse’s plugins/ folder, overwriting the one that was installed from the GlassFish plug-in update site;

  10. Finally, start Eclipse with [cci_bash]eclipse -clean[/cci_bash] so the plug-ins can be reloaded.

If you follow the steps above and use the same hack as I did, the log messages should change from this:

[cc_java]INFO: Registered org.glassfish.ha.store.adapter.cache.ShoalBackingStoreProxy for persistence-type = replicated in BackingStoreFactoryRegistry[/cc_java]

To this (notice the inclusion of the class that logged the message — [cci_java]BackingStoreFactoryRegistry[/cci_java]):

[cc_java]INFO: [BackingStoreFactoryRegistry] Registered org.glassfish.ha.store.adapter.cache.ShoalBackingStoreProxy for persistence-type = replicated in BackingStoreFactoryRegistry[/cc_java]

The hack

Finally, let’s talk about the hack itself, i.e., the changes in the Java source file that should be made during step 3 of the above instructions. Changes should be made in the [cci_java]process()[/cci_java] method of the [cci_java]LogFileFilter[/cci_java] internal class in the V3LogFilter.java file. Below I explain what needs to be changed and why, but at the end of the post there’s the complete hacked method if you’re not interested in the explanation. Here’s the original method:

[cc_java]public String process(char c) {
String result = null;

switch(state) {
case 0:
if(c == ‘[‘) {
state = 1;
} else {
if(c == ‘\n’) {
if(msg.length() > 0) {
msg.append(c);
result = msg.toString();
msg.setLength(0);
}
} else if(c != ‘\r’) {
msg.append(c);
}
}
break;
case 1:
if(c == ‘#’) {
state = 2;
} else {
state = 0;
if(c == ‘\n’) {
if(msg.length() > 0) {
msg.append(c);
result = msg.toString();
msg.setLength(0);
}
} else if(c != ‘\r’) {
msg.append(‘[‘);
msg.append(c);
}
}
break;
case 2:
if(c == ‘|’) {
state = 3;
msg.setLength(0);
} else {
if(c == ‘\n’) {
if(msg.length() > 0) {
msg.append(c);
result = msg.toString();
msg.setLength(0);
}
} else if(c != ‘\r’) {
state = 0;
msg.append(‘[‘);
msg.append(‘#’);
msg.append(c);
}
}
break;
case 3:
if(c == ‘|’) {
state = 4;
time = msg.toString();
msg.setLength(0);
} else {
msg.append(c);
}
break;
case 4:
if(c == ‘|’) {
state = 5;
type = getLocalizedType(msg.toString());
msg.setLength(0);
} else {
msg.append(c);
}
break;
case 5:
if(c == ‘|’) {
state = 6;
version = msg.toString();
msg.setLength(0);
} else {
msg.append(c);
}
break;
case 6:
if(c == ‘|’) {
state = 7;
classinfo = msg.toString();
msg.setLength(0);
} else {
msg.append(c);
}
break;
case 7:
if(c == ‘|’) {
state = 8;
threadinfo = msg.toString();
msg.setLength(0);
} else {
msg.append(c);
}
break;
case 8:
if(c == ‘|’) {
state = 9;
message = msg.toString();
} else if(c == ‘\n’) {
if(msg.length() > 0) { // suppress blank lines in multiline messages
msg.append(‘\n’);
result = !multiline ? type + “: ” + msg.toString() : msg.toString();
multiline = true;
msg.setLength(0);
}
} else if(c != ‘\r’) {
msg.append(c);
}
break;
case 9:
if(c == ‘#’) {
state = 10;
} else {
state = 8;
msg.append(‘|’);
msg.append(c);
}
break;
case 10:
if(c == ‘]’) {
state = 0;
msg.setLength(0);
result = (multiline ? message : type + “: ” + message) + ‘\n’;
reset();
} else {
state = 8;
msg.append(‘|’);
msg.append(‘#’);
msg.append(c);
}
break;
}
return result;
}[/cc_java]

This method is called to filter log messages that are sent to GlassFish’s server.log. Messages sent to this log are in a specific format which is not easy to read by humans, and hence the GlassFish plug-in parses it and reformats it. For example, the example message shown before is originally like this:

[cc_java][#|2011-08-04T10:13:40.733+0200|INFO|glassfish3.1|org.glassfish.ha.store.spi.BackingStoreFactoryRegistry|_ThreadID=10;_ThreadName=Thread-1;|Registered org.glassfish.ha.store.adapter.cache.ShoalBackingStoreProxy for persistence-type = replicated in BackingStoreFactoryRegistry|#][/cc_java]

So what the process method does is parse each character of this message, appending them to a buffer called [cci_java]msg[/cci_java] and changing the [cci_java]state[/cci_java] of the parsing procedure whenever a separator (e.g., [cci_java]|[/cci_java] or [cci_java]#[/cci_java]) is found. When there’s a state change, the contents of the buffer are copied to specific variables (e.g. [cci_java]time[/cci_java] for the timestamp, [cci_java]type[/cci_java] for the level of the message, and so on) and the buffer is reset.

When the [cci_java]state = 10[/cci_java], the message was read completely and the result of the parsing is produced in the following line:

[cc_java]result = (multiline ? message : type + “: ” + message) + ‘\n’;[/cc_java]

So this is the line to change to include any information you want to show. The complete name of the logger (e.g., [cci_java]org.glassfish.ha.store.spi.BackingStoreFactoryRegistry[/cci_java]) is stored in the variable [cci_java]classinfo[/cci_java], but if you want to display only the last part (the class name), replace the above code with the one below:

[cc_java]int idx = classinfo.lastIndexOf(‘.’);
String sender = (idx == -1) ? classinfo : classinfo.substring(idx + 1);
result = (multiline ? message : type + “: [” + sender + “] ” + message) + ‘\n’;[/cc_java]

If you want to show more information (for instance, the [cci_java]time[/cci_java]), just add it to the result string. Notice, however, that the plug-in will provide coloring of log messages as long as the result starts with [cci_java]type + “:”[/cci_java] (e.g., [cci_java]INFO: …[/cci_java], [cci_java]WARNING: …[/cci_java]), so I wouldn’t change that part of the format if I were you.

That’s it! Here’s the complete hacked method:

[cc_java]public String process(char c) {
String result = null;

switch(state) {
case 0:
if(c == ‘[‘) {
state = 1;
} else {
if(c == ‘\n’) {
if(msg.length() > 0) {
msg.append(c);
result = msg.toString();
msg.setLength(0);
}
} else if(c != ‘\r’) {
msg.append(c);
}
}
break;
case 1:
if(c == ‘#’) {
state = 2;
} else {
state = 0;
if(c == ‘\n’) {
if(msg.length() > 0) {
msg.append(c);
result = msg.toString();
msg.setLength(0);
}
} else if(c != ‘\r’) {
msg.append(‘[‘);
msg.append(c);
}
}
break;
case 2:
if(c == ‘|’) {
state = 3;
msg.setLength(0);
} else {
if(c == ‘\n’) {
if(msg.length() > 0) {
msg.append(c);
result = msg.toString();
msg.setLength(0);
}
} else if(c != ‘\r’) {
state = 0;
msg.append(‘[‘);
msg.append(‘#’);
msg.append(c);
}
}
break;
case 3:
if(c == ‘|’) {
state = 4;
time = msg.toString();
msg.setLength(0);
} else {
msg.append(c);
}
break;
case 4:
if(c == ‘|’) {
state = 5;
type = getLocalizedType(msg.toString());
msg.setLength(0);
} else {
msg.append(c);
}
break;
case 5:
if(c == ‘|’) {
state = 6;
version = msg.toString();
msg.setLength(0);
} else {
msg.append(c);
}
break;
case 6:
if(c == ‘|’) {
state = 7;
classinfo = msg.toString();
msg.setLength(0);
} else {
msg.append(c);
}
break;
case 7:
if(c == ‘|’) {
state = 8;
threadinfo = msg.toString();
msg.setLength(0);
} else {
msg.append(c);
}
break;
case 8:
if(c == ‘|’) {
state = 9;
message = msg.toString();
} else if(c == ‘\n’) {
if(msg.length() > 0) { // suppress blank lines in multiline messages
msg.append(‘\n’);
result = !multiline ? type + “: ” + msg.toString() : msg.toString();
multiline = true;
msg.setLength(0);
}
} else if(c != ‘\r’) {
msg.append(c);
}
break;
case 9:
if(c == ‘#’) {
state = 10;
} else {
state = 8;
msg.append(‘|’);
msg.append(c);
}
break;
case 10:
if(c == ‘]’) {
state = 0;
msg.setLength(0);
result = (multiline ? message : type + “: ” + message) + ‘\n’;

// BEGIN — Hack by Vitor E. Silva Souza — add the class name to the log message.
int idx = classinfo.lastIndexOf(‘.’);
String sender = (idx == -1) ? classinfo : classinfo.substring(idx + 1);
result = (multiline ? message : type + “: [” + sender + “] ” + message) + ‘\n’;
// END — Hack

reset();
} else {
state = 8;
msg.append(‘|’);
msg.append(‘#’);
msg.append(c);
}
break;
}
return result;
}[/cc_java]

Hope it works for you the way it worked for me!