At 06:59 AM 4/1/2003 -0500, you wrote:
Ceki Gülcü wrote:
At 10:14 PM 3/31/2003 -0500, you wrote:

Hello all,

I finally have more time as a major deadline has passed at work. I was thinking about writing an improved JDBC appender. My intention is to create a new appender from scratch. As discussed previously, I figured I would have an abstract base class with an abstract getConnection() class and two concrete derivatives; one for getting the connection from a JNDI context and one for getting the connection from the DriverManager.

As suggested by others, it is preferable to use composition to offer choices. Offering choices by derivation (or inheritance) is OK as long as you have a single choice, for example the connection type. However, if you would also like the offer a choice on say LoggingEvent to DB mapping, then inheritance will result in combinatorial explosion.

OK, I'm not really convinced, but since this is your architecture, I'll defer to your judgment. (See below for a related question.)

In that case, let me try to give you another example. We have a RollingFileAppender that is a subclass of FileAppender. In turn, DailyRollingAppender is a subclass of RollingFileAppender where the former rolls based on date or time and the latter rolls over based on file size. Users have repeatedly asked for appenders capable of rolling over based on file size and/or time. Other users have expressed their wish to control the naming of files and others have asked for the compression of rolled over files. It becomes impossibly complex to offers all these alternatives if you only rely on class inheritance. It is much more manageable to break up functionally to pieces (components). Thus, we would have a rolling appender composed of a user specified copying strategy and triggering strategy. The copying strategy determines how files are rolled over (their names, the number of rolled over files, file compression, etc.) whereas the triggering strategy determines when the log file is rolled over. More below.


2) Is there a good place to look to get a feel for how the configuration works?

The options, a.k.a. properties, of appenders, layouts or filters are inferred dynamically using standard JavaBeans conventions. Any setter method taking a single primitive java type, an Integer, a Long, a String or a Boolean parameter implies an option name. For example, given that the FileAppender class contains setAppend(boolean), setBufferSize(int) and setFile(String) as member methods, then it follows that Append, BufferSize and File are all valid option names. Log4j can also deal with setter methods taking an org.apache.log4j.Level parameter. For example, since the AppenderSkeleton class[1] has setThreshold(Level) as a member method, Threshold is a valid option for all log4j appenders extending the AppenderSkeleton class. In log4j version 1.3, you can also include sub-components in config files written in XML. Seeing a sub-element within an appender element, DOMConfigurator will automatically instantiate and configure the sub-element. I'll write complete example within the next few days. If I forget, please do not hesitate to remind me.

OK this part seems clear, but...


I'd like to do the connection factory above via an interface a la the layout. How do I make a property whose value is the name of a class that should be instantiated and then passed to the setter?

Here is an example that hopefully will make the point clear:


-- File Trigger.java ----------------------------------
import org.apache.log4j.spi.LoggingEvent;

interface Trigger {
  public boolean isTriggeringEvent(LoggingEvent event);
}

-- File CountingTrigger ----------------------------------
import org.apache.log4j.spi.LoggingEvent;

public class CountingTrigger implements Trigger {
  static final int DEFAULT_LIMIT = 5;

  int count = 0;
  int limit = DEFAULT_LIMIT;

  public CountingTrigger() {
  }

  public int getLimit() { return limit; }
  public void setLimit(int i) { limit = i; }

  public boolean isTriggeringEvent(LoggingEvent event) {
    count++;
    if(count >= limit) {
      return true;
    } else {
      return false;
    }
  }
}

-- File XAppender ----------------------------------
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.ErrorCode;
import org.apache.log4j.Layout;

// A funny appender that writes to the console. However, in case of a
// triggering event, it just does not log.

public class XAppender extends AppenderSkeleton {

Trigger trigger;

  public XAppender() {
  }

  public void setTrigger(Trigger trigger) {
    this.trigger = trigger;
  }

  public Trigger getTrigger() {
    return trigger;
  }

  public void append(LoggingEvent event) {
    if(this.layout == null) {
      errorHandler.error("No layout set for the appender named ["+ name+"].",
                         null,  ErrorCode.MISSING_LAYOUT);
      return;
    }

    if(trigger.isTriggeringEvent(event)) {
      errorHandler.error("Triggering event occured.");
      return;
    }

    // output the events as formatted by our layout
    System.out.print(this.layout.format(event));

    // if our layout does not handle exceptions, we have to do it.
    if(layout.ignoresThrowable()) {
      String[] t = event.getThrowableStrRep();
      if (t != null) {
        int len = t.length;
        for(int i = 0; i < len; i++) {
          System.out.println(t[i]);
        }
      }
    }
  }

  public void close() {
    // The variable 'closed' is defined in AppenderSkeleton.
    if(this.closed)
      return;
    this.closed = true;
  }

  public boolean requiresLayout() {
    return true;
  }

}

-- File Test1.java ---------------------------------

import org.apache.log4j.*;

public class Test1 {
  static public void main(String[] args) throws Exception {

    if(args.length != 1)
      usage("Wrong number of arguments.");

int runLength = Integer.parseInt(args[0]);

    Logger root = Logger.getRootLogger();
    XAppender xappender = new XAppender();
    xappender.setLayout(new PatternLayout());
    xappender.setTrigger(new CountingTrigger());
    root.addAppender(xappender);


Logger logger = Logger.getLogger(Test1.class);


    for(int i = 1; i <= runLength; i++) {
      if((i % 10 ) < 9) {
        logger.debug("This is a debug message. Message number: " + i );
      } else {
        logger.warn("This is a warning message. Message number: " + i );
      }
    }
  }

static void usage(String msg) {
System.err.println(msg);
System.err.println(
"Usage: java "+Test1.class.getName()+" runLength \n"+
" runLength (integer) the number of logs to generate\n");
System.exit(1);
}
}


-- File Test2.java -------------------------------------------------

import org.apache.log4j.*;
import org.apache.log4j.xml.DOMConfigurator;

public class Test2 {
  static public void main(String[] args) throws Exception {

    if(args.length != 2)
      usage("Wrong number of arguments.");

    int runLength = Integer.parseInt(args[0]);
    String configFile = args[1];

    if(configFile.endsWith(".xml")) {
      new DOMConfigurator().configure(configFile);
    } else {
      usage("Config file must be in xml.");
    }

Logger logger = Logger.getLogger(Test2.class);

    for(int i = 1; i <= runLength; i++) {
      if((i % 10 ) < 9) {
        logger.debug("This is a debug message. Message number: " + i );
      } else {
        logger.warn("This is a warning message. Message number: " + i );
      }
    }
  }

static void usage(String msg) {
System.err.println(msg);
System.err.println(
"Usage: java "+Test2.class.getName()+" runLength configFile\n"+
" runLength (integer) the number of logs to generate\n"+
" configFile a log4j configuration file in XML.");
System.exit(1);
}
}


-- File x.xml ----------------------------------------------

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration debug="true"
                   xmlns:log4j='http://jakarta.apache.org/log4j/'>

  <appender name="X" class="XAppender">
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d %-5p %c - %m%n"/>
    </layout>
    <trigger class="CountingTrigger">
      <param name="limit" value="3"/>
    </trigger>
  </appender>

  <root>
    <level value ="debug"/>
    <appender-ref ref="X" />
  </root>
</log4j:configuration>


Running Test2 with x.xml yields:


> java Test2 9 x.xml
piblicID: [null]
systemId: [dummy://log4j.dtd]
log4j:ERROR Parsing error on line 10 and column -1
log4j:ERROR Element "appender" does not allow "trigger" here.
log4j:ERROR Parsing error on line 10 and column -1
log4j:ERROR Element type "trigger" is not declared.
log4j:ERROR Parsing error on line 10 and column -1
log4j:ERROR Attribute "class" is not declared for element "trigger".
log4j: Threshold ="null".
log4j: Level value for root is [debug].
log4j: root level set to DEBUG
log4j: Class name: [XAppender]
log4j: xml.DOMConfigurator - Handling nested <layout> for appender X
log4j: config.PropertySetter - Found setter method for property [layout] in class XAppender
log4j: xml.DOMConfigurator - Will instantiate instance of class [org.apache.log4j.PatternLayout]
log4j: xml.DOMConfigurator - Configuring parameter [ConversionPattern] for <layout>.
log4j: Setting property [conversionPattern] to [%d %-5p %c - %m%n].
log4j: config.PropertySetter - Set child component of type [org.apache.log4j.PatternLayout] for [XAppender].
log4j: xml.DOMConfigurator - Handling nested <trigger> for appender X
log4j: config.PropertySetter - Found setter method for property [trigger] in class XAppender
log4j: xml.DOMConfigurator - Will instantiate instance of class [CountingTrigger]
log4j: xml.DOMConfigurator - Configuring parameter [limit] for <trigger>.
log4j: Setting property [limit] to [3].
log4j: config.PropertySetter - Set child component of type [CountingTrigger] for [XAppender].
log4j: Adding appender named [X] to category [root].
2003-04-01 15:27:01,986 DEBUG Test2 - This is a debug message. Message number: 1
2003-04-01 15:27:01,986 DEBUG Test2 - This is a debug message. Message number: 2
log4j:ERROR Triggering event occured.


I ran the above on JDK 1.4.1. I had compiled the latest version of log4j from CVS. My CLASSPATH variable looks as follows:

CLASSPATH=.:/java/j2sdk1.4.1_01/jre/lib/rt.jar:/log4j-1.3aplha/dist/classes/

where log4j-1.3aplha was obtained from our CVS repository.

The interesting part is obviously the fact that DOMConfigurator can deal with nested components.

Let me know if your have any questions or if the above is not clear. I hope this helps,

Thanks,
Ray

--
Ceki



--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]



Reply via email to