JavaAppender

Sends messages to a custom Java class.
Valid in: XML configuration

Syntax

<appender class="JavaAppender" name="appender-name">
<param name="Class" value="class"/>
<param name="ClassPath" value="class-path"/>
<param name="MaxBufferedEvents" value="maximum-buffered-events"/>
<param name="SASEncodedPassword" value="SAS-encoded-password"/>
<layout>
<param name="ConversionPattern" value="conversion-pattern"/>
</layout>
</appender>

Syntax Description

class="JavaAppender" name="appender-name"
specifies the user-assigned name for this instance of JavaAppender.
Default None
Requirement These element attributes are required.
name="Class" value="class"
specifies the custom Java class to which events are to be sent. The class that you specify must support the following methods:
  • A no-argument constructor for creating the object.
  • An append method for handling each event:
    void append( int level, String logger, String msg )
The class can also support the following methods:
void setOption( String name, Object value ) specifies the handling for any parameters in the configuration file that are not recognized by JavaAppender
void activate() is called after all of the options have been set and before any calls to the append method
void destroy is called when the appender is destroyed
Default com.sas.jms.logging.JMSAppender. If you use the default class, then JavaAppender has the same functionality and uses the same parameters as JMSAppender. For details, see JMSAppender.
Requirement This parameter is required if you want to use a class other than com.sas.jms.logging.JMSAppender.
name="ClassPath" value="class-path"
specifies the path for classes that are included in this appender configuration.
Default None
Requirement This parameter is not required.
Interaction When JavaAppender searches for classes to load, the CLASSPATH environment variable takes precedence over the ClassPath parameter, and the ClassPath parameter takes precedence over the JARs that are provided by SAS.
name="MaxBufferedEvents" value="maximum-buffered-events"
specifies the maximum number of events that can be waiting to be sent to the Java class. JavaAppender stores events in an internal list that is bounded by the number specified in this parameter. A worker thread removes events from the list and sends them to the Java class. In the meantime, SAS can continue processing without waiting for the Java class. When the list contains the specified number of events, the appender blocks further events until the list can accommodate them.
Tip
A high number favors performance over reliability. A catastrophic error could cause events in the list to be lost. A high number also increases the memory usage of the appender.
Tip
A low number favors reliability and memory usage over performance.
Default Infinite
Requirement This parameter is not required.
name="SASEncodedPassword" value="SAS-encoded-password"
specifies a plain-text password or a password that has been encoded by using sas001, sas002, or sas003 encoding. If the password is encoded, JavaAppender converts it to plain text before sending it to the Java class.
Tip
For information about how to obtain the encoded password, see PWENCODE Procedure in Base SAS Procedures Guide.
Default None.
Requirement This parameter is not required.
name="ConversionPattern" value="conversion-pattern"
specifies how the log message is written to the Java class.
Default None. If a conversion pattern is not specified, then the log event produces an empty string.
Requirement This parameter is not required.
See Pattern Layouts

Details

If additional parameters are needed by your custom Java class, you can include them in the appender configuration. Any parameters that JavaAppender does not recognize are passed to the custom Java class.

Examples

Example 1: Displaying a Supplementary Log Window for Trace Events

This example uses a custom Java class to display trace events in a separate log window in the SAS windowing environment, while preserving the contents of the main Log window.
Step 1: Create a custom Java class called TraceWindow.
Note that the main class cannot inherit directly from JFrame. To prevent a HeadlessException, you must set the java.awt.headless system property before the JFrame constructor runs.
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import java.awt.Font;
 
public class TraceWindow
{
    JFrame window;
    JTextArea console;
    String separator;
 
    public TraceWindow(){
        System.setProperty( "java.awt.headless", "false" );
        separator = System.getProperty("line.separator");
    }
 
    public void activate() {
        window = new JFrame( "Trace window" );
        console = new JTextArea();
        console.setFont( new Font("Courier New", Font.PLAIN, 12) );
        console.setEditable( false );
        JScrollPane scroll = new JScrollPane( console );
        window.getContentPane().add(scroll);
        window.setSize( 600, 600 );
        window.setLocation( 200, 200 );
        window.setVisible( true );
    }
 
    public void terminate() {
        window.dispose();
    }
 
    public void append( int level, String logger, String msg ){
        window.setVisible( true );
        console.append( msg );
        console.append( separator );
        console.setCaretPosition( console.getText().length() );
    }
}
Step 2: Create the following logging configuration file. The file specifies JavaAppender with the custom Java class TraceWindow, which is displayed in the preceding code. Because of the specified threshold, only trace events are sent to the custom class.
<?xml version="1.0"?>
<logging:configuration xmlns:logging="http://www.sas.com/xml/logging/1.0/" 
      threshold="trace">
   <appender name="java" class="JavaAppender">
      <param name="Class" value="TraceWindow"/>
      <layout>
         <param name="ConversionPattern" value="[%F:%L] %c %6p - %m"/>
      </layout>
   </appender>
   <root>
      <level value="info"/>
      <appender-ref ref="java"/>
   </root>
 </logging:configuration>

Example 2: Sending Events to a Database Using a JDBC Driver

This example uses a custom Java class to connect to a MySQL server via JDBC and write events to a table.
Step 1: Use the following MySQL code to create the database and the table:
CREATE DATABASE log; 
CREATE TABLE log ( level varchar(10), logger varchar(50), msg varchar(500) );
Step 2: Create the following Java class, called LogToJDBC. This class uses JDBC to connect to the MySQL server and writes events to the table.
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
 
class LogToJDBC {
    String              driver;
    String              url;
    String              tablename;
    String              username;
    String              password;
    long                rowsetsize;
    long                eventCount;
 
    Connection          connection;
    PreparedStatement   stmt;
 
    public LogToJDBC() {
        /*-------------------------------------------------------------+
         |  Nothing to do in the constructor.                          |
         +-------------------------------------------------------------*/
    }
 
    /*-----------------------------------------------------------------+
     |  Appender API                                                   |
     +-----------------------------------------------------------------*/
    public void setOption( String name, Object value ) {
        if( name.equalsIgnoreCase( "driver" ) ) {
            driver = value.toString();
        } else if( name.equalsIgnoreCase( "url" ) ) {
            url = value.toString();
        } else if( name.equalsIgnoreCase( "tablename" ) ) {
            tablename= value.toString();
        } else if( name.equalsIgnoreCase( "username" ) ) {
            username = value.toString();
        } else if( name.equalsIgnoreCase( "password" ) ) {
            password = value.toString();
        } else if( name.equalsIgnoreCase( "rowsetsize" ) ) {
            rowsetsize = Long.valueOf(value.toString());
        }
    }
 
    public void activate() throws Exception {
        /*-------------------------------------------------------------+
         |  Set up all of our JDBC objects and set any properties we   |
         |  cached from setOption.                                     |
         +-------------------------------------------------------------*/
        Class.forName( driver );
        connection = DriverManager.getConnection( url, username,
                                                          password );
        connection.setAutoCommit( false );
        stmt = connection.prepareStatement( "insert into " + tablename + 
             " (level, logger, msg ) values (?, ?, ?)" );
    }
 
    public void append( int level, String logger, String msg ) throws Exception {
        /*-------------------------------------------------------------+
         |  Set the columns of our statement and if we have batched    |
         |  enough rows then commit them to the data base.             |
         +-------------------------------------------------------------*/
        stmt.setString( 1, new Integer(level).toString() );
        stmt.setString( 2, logger );
        stmt.setString( 3, msg );
        stmt.addBatch();
        eventCount++;
        if( eventCount >= rowsetsize )
        {
            stmt.executeBatch();
            connection.commit();
            eventCount = 0L;
        }
    }
 
    public void destroy() throws Exception {
        /*-------------------------------------------------------------+
         |  Send any batched events to the server.                     |
         +-------------------------------------------------------------*/
        if( eventCount > 0 )
        {
            stmt.executeBatch();
            connection.commit();
            eventCount = 0L;
        }
        /*-------------------------------------------------------------+
         |  Clean up our objects if we have any.                       |
         +-------------------------------------------------------------*/
        if( stmt != null )
            stmt.close();
        if( connection != null )
            connection.close();
    }
 
}
Step 3: Create the following logging configuration file. This file specifies JavaAppender with the custom Java class LogToJDBC, which is displayed in the preceding code. The Driver, URL, TableName, UserName, Password, and RowSetSize parameters are passed to LogToJDBC, which uses them to connect to the server and to write to the table.
<?xml version="1.0" encoding="UTF-8" ?> 
<logging:configuration xmlns:logging="http://www.sas.com/xml/logging/1.0/">
   <appender name="java" class="JavaAppender">
      <param name="classpath" 
             value="mysql-connector-java-commercial-5.0.8-bin.jar"/>
      <param name="class" value="LogToJDBC"/>
      <param name="Driver" value="com.mysql.jdbc.Driver"/>
      <param name="URL" value="jdbc:mysql://mysql.example.com:3306/log"/> 
      <param name="TableName" value="log"/>
      <param name="UserName" value="myusername"/>
      <param name="Password" value="mypassword"/>
      <param name="RowSetSize" value="1000" />
      <param name="MaxBufferedEvents" value="2000" /> 
      <layout>
         <param name="conversionpattern" value="%sn %d %c %p %m"/>
      </layout>
   </appender>
   <root>
      <level value="info" /> 
      <appender-ref ref="java" /> 
   </root>
</logging:configuration>

Example 3: Sending Events to log4j Appenders

This example uses a custom Java class to invoke a log4j configuration. The example uses org.apache.log4j.net.SocketServer to listen for events, which are then sent to the log4j ConsoleAppender. By following these steps, you can send events to any log4j appender.
Step 1: Create a custom Java class called SendToLog4j that sends events from SAS to log4j:
import org.apache.log4j.*;
 
public class SendToLog4j
{
    public SendToLog4j(){
        PropertyConfigurator.configure(System.getProperty("log4j.configuration"));
    }
 
    public void append( int level, String logger, String msg ){
        Logger l = Logger.getLogger( logger );
        switch( level ) {
            case 0:
            case 1:
            case 2:
                l.log( Level.TRACE, msg );
                break;
            case 3:
                l.log( Level.DEBUG, msg );
                break;
            default:
            case 4:
                l.log( Level.INFO, msg );
                break;
            case 5:
                l.log( Level.WARN, msg );
                break;
            case 6:
                l.log( Level.ERROR, msg );
                break;
            case 7:
                l.log( Level.FATAL, msg );
                break;
        }
    }
}
Step 2: Create a SAS logging configuration file called socket.xml. In the file, specify JavaAppender with the custom Java class SendToLog4j.
<?xml version="1.0"?>
<logging:configuration xmlns:logging="http://www.sas.com/xml/logging/1.0/"
      threshold="trace">
   <appender name="java" class="JavaAppender">
      <param name="class" value="SendToLog4j"/>      
      <layout>
         <param name="ConversionPattern" value="%d - %S{hostname} - %S{pid} 
            - %c - %m"/>
      </layout>
   </appender>
   <root>
      <level value="info"/>
      <appender-ref ref="java"/>
   </root>
</logging:configuration>
Step 3: Create a client-side log4j configuration file called client.properties. In the file, specify the use of SocketAppender to listen for events.
log4j.rootLogger=DEBUG,A2
log4j.appender.A2=org.apache.log4j.net.SocketAppender
log4j.appender.A2.Port=55555
log4j.appender.A2.RemoteHost=localhost
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
Step 4: Create a server-side log4j configuration file called server.properties. In the file, specify the use of ConsoleAppender. (Any log4j appender could be specified.)
log4j.rootLogger=DEBUG,A2
log4j.appender.A2=org.apache.log4j.net.SocketAppender
log4j.appender.A2.Port=55555
log4j.appender.A2.RemoteHost=localhost
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
Step 5: Use the following command to start the socket server. The command specifies the server.properties log4j configuration file.
java –classpath log4j.jar org.apache.log4j.net.SocketServer 55555 
server.properties
Step 6: Specify the following options when you start SAS. The first option specifies the socket.xml logging configuration file, and the second option passes the client.properties log4j configuration file to the SAS Java environment.
-logconfigloc socket.xml
-jreoptions '(-Dlog4j.configuration=client.properties)'