Logging chat by room

From EsWiki

Jump to: navigation, search

Logging is extremely useful on the server, however the built-in logging feature puts all the logs for a given day into the same file. Chat room applications sometimes need to refer to the chat logs for a given room, to investigate a complaint about a user. Having all those lines of chat logged to the standard ES4 log is not only confusing but it makes it harder to find error messages.

One solution is to log all the chat to a database, but a busy chat application would drown in database calls. A more complicated solution is to have the chat room itself keep a log in RAM of the last so many minutes of chat, and only record the log if a complaint is submitted. The problem here is that sometimes the complaint is submitted after crucial information has already been dropped from the chat room's log, and remembering more than a few minutes may require large amounts of RAM if the chat application is busy.

Here's one solution where each line of chat is logged to a file on the ES4's hard drive, with a separate file for each room, for each day. If the hard drive fills up too quickly, the older logs can be deleted, or the source code can be modified so that once a day all the logs older than 2 days are deleted.

The idea here is to have a single instance of a ChatLogger object as a managed object, and to make a ChatPlugin that can be added to any room that needs its chat logged.

Contents

Source Code

You can download the source code for this example which includes all the Java classes needed. It does not include the Extension.xml (shown below) or a client that adds the ChatPlugin to a room (explained below).

ChatLogger

Create a Java class named ChatLogger that extends Thread and exposes an addChatEvent method that takes the parameters that you want logged and logs them. There are many ways to do this of course. The way chosen for this example places the chat logs in server/chatlogs/ on the hard drive of the ES4. See the source code provided in the link above.

ChatLoggerFactory

Next we create the ChatLoggerFactory, which is the part that allows ChatLogger to be a managed object. It will look something like this:

   package com.electrotank.examples.chatlogger;
   
   import com.electrotank.electroserver4.extensions.ManagedObjectFactory;
   import com.electrotank.electroserver4.extensions.ManagedObjectFactoryLifeCycle;
   import com.electrotank.electroserver4.extensions.api.ManagedObjectFactoryApi;
   import com.electrotank.electroserver4.extensions.api.value.EsObjectRO;
   
   public class ChatLoggerFactory implements ManagedObjectFactory, ManagedObjectFactoryLifeCycle {
   
       private ManagedObjectFactoryApi api;
       private ChatLogger chatLogger;
   
       @Override
       public void init( EsObjectRO parameters ) {
               chatLogger = new ChatLogger(  );
               chatLogger.start();
       }
   
       @Override
       public void destroy() {}
   
       @Override
       public Object acquireObject( EsObjectRO esObjectRO ) {
           return chatLogger;
       }
   
       @Override
       public void releaseObject( Object o ) {}
   
       @Override
       public ManagedObjectFactoryApi getApi() {
           return api;
       }
   
       @Override
       public void setApi( ManagedObjectFactoryApi api ) {
           this.api = api;
       }
   }

ChatPlugin

Next we create a ChatPlugin that uses the managed object. It will look something like this:

   package com.electrotank.examples.chatlogger;
   
   import com.electrotank.electroserver4.extensions.BasePlugin;
   import com.electrotank.electroserver4.extensions.ChainAction;
   import com.electrotank.electroserver4.extensions.api.value.EsObjectRO;
   import com.electrotank.electroserver4.extensions.api.value.UserPublicMessageContext;
   
   public class ChatPlugin extends BasePlugin {
   
       private ChatLogger chatLogger = null;
   
       @Override
       public ChainAction userSendPublicMessage(UserPublicMessageContext message) {
           String chatLine = message.getMessage();
           String user = message.getUserName();
           getChatLogger().addChatEvent(user, chatLine, getApi().getRoomId(), getApi().getZoneId());
           return ChainAction.OkAndContinue;
       }
   
       private ChatLogger getChatLogger() {
           if (chatLogger == null) {
               chatLogger = (ChatLogger) getApi().acquireManagedObject("ChatLogger", null);
           }
           return chatLogger;
       }
   }

Notice the userSendPublicMessage method. This method is invoked when a user tries to send a public message, so if the message fails the chat filter, it will still be logged. This method could be used for fancy chat filtering too; see PluginPigLatin for an example of changing the message before it is broadcast.

Any other method in ChatPlugin can log information to the room's chat log as well. Perhaps you want to periodically record the names of all users in the room, or scores, or just a count of users in the room. Perhaps you want a record of when each user joins or leaves the room. Just use getChatLogger().addChatEvent to pass the appropriate parameters. Alternatively you could expose another method in ChatLogger for adding a chat event of a different type.

Extension.xml

The next step is to tell the ES4 to use these classes. Here is a bare bones Extension.xml that only does chat logging. You can have the chat logger running in its own separate extension, or you can add it to another one. Most projects just have a single extension with everything in the same Extension.xml file.

   <?xml version="1.0" encoding="utf-8" ?>
   <Extension>
       <Name>ExtensionNameHere</Name>
       <ManagedObjects>
           <ManagedObject>
               <Handle>ChatLogger</Handle>
               <Type>Java</Type>
               <Path>com.electrotank.examples.chatlogger.ChatLoggerFactory</Path>
           </ManagedObject>
       </ManagedObjects>
       <Plugins>
           <Plugin>
               <Handle>ChatPlugin</Handle>
               <Type>Java</Type>
               <Path>com.electrotank.examples.chatlogger.ChatPlugin</Path>
           </Plugin>
       </Plugins>
   </Extension>

The handle for the managed object (ChatLogger) must match the string used in getApi().acquireManagedObject exactly, or the managed object returned will be null. Any plugin wanting to use the managed object must be in the same extension as the managed object.

Client

In the client function that creates the room, we have to specify that the ChatPlugin is added to the room. Take whatever client you are using (or the SimpleChat example) and find the function that builds the CreateRoomRequest. Before sending the request, add lines similar to these:

   			var pl:Plugin = new Plugin();
   			pl.setExtensionName("ExtensionNameHere");
      			pl.setPluginHandle("ChatPlugin");
   			pl.setPluginName("ChatPlugin");
   			crr.setPlugins([pl]);

"ExtensionNameHere" and "ChatPlugin" need to match exactly the strings given in Extension.xml for the extension name and the plugin handle.

Note that you may need to add another import to the class:

   	import com.electrotank.electroserver4.plugin.Plugin;

Deploy

Compile everything, deploy the extension, and restart ES4. Test using your client to send some chat, then look in your ES4's installation directory for server/chatlogs/ to see if there is a text file there. The name of the text file will give the zoneId, roomId, and date of the chat, and inside the text file each line will give a timestamp, username, and the message sent.

Personal tools
download