69. XPages里的Java日志器

在48. 面向对象的LotusScript(十四)之Log4Dom上和49. 面向对象的LotusScript(十五)之Log4Dom下笔者介绍了一个简单实用的日志记录模块,代码用LotusScript写成,在Lotus Notes传统的客户端和web应用程序里都可以使用。在XPages开发里,需要可在SSJS或Java里使用的日志功能。用Java开发时,有很多现成的日志框架,比如Log4J和JDK自带的java.util.logging包。但是这些框架包含日志器层次(logger hierarchy)、过滤器(filter)、记录器(handler)和格式器(formatter)等特性,对于Lotus Notes平台上的开发来说过于复杂。Lotus Notes的环境有特殊性,现成和方便的记录日志的载体就是Notes文档,所以笔者在借鉴了OpenNTF上的Log4Dom项目后,编写了满足典型的Lotus Notes里日志需求的Java类NotesLogger。(与Log4Dom的Java版本比较,NotesLogger放弃了Log4J的可配制记录器的架构,直接将日志写入唯一的载体——指定Notes数据库的文档;更正了bug;简化了使用方法;减少了类的数量;调整了方法的名称和签名。)记录日志的文档使用的表单和视图与LotusScript的Log4Dom一样。下面就是在一个XPage的按钮里分别用xp:eventHandler的action和actionListener属性触发managed bean里的两个测试方法,分成两条日志文档。
	<xp:button value="Test eventHandler action" id="button1">
		<xp:eventHandler event="onclick" submit="true"
			refreshMode="complete" action="#{bean.testAction}">
		</xp:eventHandler>
	</xp:button>
	<xp:button value="Test eventHandler actionListener" id="button2">
		<xp:eventHandler event="onclick" submit="true"
			refreshMode="complete" actionListener="#{bean.testActionListener}">
		</xp:eventHandler>
	</xp:button>	
注意两处绑定使用的都是表达式语言,并且XPages继承自JSF,分别可以使用action和actionListener/actionListeners绑定两种签名不同的方法。action属性绑定的方法须为managed bean的公开方法,无参数且返回一个字符串;actionListener/actionListeners绑定的方法同样须为公开方法,但接受一个类型为javax.faces.event.ActionEvent的参数且返回类型为void。
    public void testActionListener(ActionEvent ae) throws NotesException{
        NotesLogger logger=new NotesLogger(XSPUtil.getSession());
        logger.setLogName("test actionListener from bean");
        logger.info(ae);
        logger.close();
    }
    
    public void testAction() throws NotesException{
        NotesLogger logger=new NotesLogger(XSPUtil.getSession());
        logger.setLogName("test action from bean");
        logger.info("Print message from NotesLogger");
        logger.close();
    }
产生的日志如下:
 69. XPages里的Java日志器_第1张图片
 69. XPages里的Java日志器_第2张图片
 69. XPages里的Java日志器_第3张图片
我们还可以稍微再简化一下NotesLogger的调用步骤。在一个managed bean里,很多方法都可能会写日志,我们可以在bean里声明一个logger字段作为共享的日志器,在bean的构造方法里或者利用managed bean的managed property初始化logger变量,如此一来在每个方法写日志时,就可省去创建日志器的环节。
下面是为此写的faces-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
  <managed-bean>
    <managed-bean-name>logger</managed-bean-name>
    <managed-bean-class>starrow.BeanLogger</managed-bean-class>
    <managed-bean-scope>application</managed-bean-scope>
  </managed-bean>
  <managed-bean>
    <managed-bean-name>bean</managed-bean-name>
    <managed-bean-class>starrow.Test</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
    <managed-property>
      <property-name>logger</property-name>
      <value>#{logger}</value>
    </managed-property>
  </managed-bean>
  <!--AUTOGEN-START-BUILDER: Automatically generated by IBM Lotus Domino Designer. Do not modify.-->
  <!--AUTOGEN-END-BUILDER: End of automatically generated section-->
</faces-config>
和相应的bean的片段:
    private BeanLogger logger;

    public void setLogger(BeanLogger logger) {
        this.logger = logger;
    }
    public void test() throws NotesException{
        logger.setLogName("from bean logger");
        logger.debug(1);
        logger.info("text");
        logger.warn(true);
        logger.error(new Date());
        String user=XSPUtil.getSession().getEffectiveUserName();
        logger.info("user: " + user);
        logger.flush();
        logger.close();
    }
产生的日志如下:
 
 
注意上面使用的日志类是BeanLogger,这是为了能将NotesLogger作为managed bean使用而继承自它的一个扩展类。
最后,我们来看看NotesLogger和BeanLogger的代码。

package starrow;

import lotus.domino.*;

import java.util.*;
import java.text.SimpleDateFormat;

/**
 * Log to Notes documents
 */

public class NotesLogger {

    // built in logging levels
    public static final int LEVEL_DEBUG=5;
    public static final int LEVEL_INFO=4;
    public static final int LEVEL_WARN = 3;
    public static final int LEVEL_ERROR=2;
    public static final int LEVEL_FATAL=1;
    public static final int LEVEL_NONE=0;

    protected int logLevel;
    private String logName;

    protected String dbPath;
    protected Database logDb;
    private Document logDoc;
    private RichTextItem logItem;
    private boolean written = false;


    /**
     * @param db A NotesDatabase used for logging
     * @throws NotesException
     */
    public NotesLogger(Database db) throws NotesException{
        logLevel = LEVEL_DEBUG;
        logDb=db;
        if (!logDb.isOpen()){
            if (!logDb.open()){
                throw new NotesException(NotesError.NOTES_ERR_DATABASE_NOTOPEN, "Cannot open the log database.");
            }
        }
    }

    /**
     * @param s notes session
     * @param dbPath path of the log database
     * @throws NotesException
     */
    public NotesLogger(Session s, String dbPath) throws NotesException {
        //Database db=s.getDatabase(null, dbPath);
        this(s.getDatabase(null, dbPath));
    }

    /**
     * @param s notes session
     * @throws NotesException
     */
    public NotesLogger(Session s) throws NotesException{
        this(s.getCurrentDatabase());
    }
    

    public void setLogLevel(int level) {
        logLevel = level;    
    }

    public void setLogName(String name) {
        logName = name;    
    }

    public void info(Object message) throws NotesException {
        log(LEVEL_INFO, message);
    }

    public void warn(Object message) throws NotesException {
        log(LEVEL_WARN, message);
    }

    public void debug(Object message) throws NotesException {
        log(LEVEL_DEBUG, message);
    }

    public void error(Object message) throws NotesException {
        log(LEVEL_ERROR, message);
    }

    public void fatal(Object message) throws NotesException {
        log(LEVEL_FATAL, message);
    }


    public void log (int level, Object message) throws NotesException {
        if (level>logLevel) return;
            
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss aa", Locale.US);

        String text = new String();
        Date theCurrentDate = new Date();
        String theDate = dateFormat.format(theCurrentDate);
        String theLevel = new String();
        theLevel = theLevel+getLevelString(level);

        //System.out.println("Level :"+level+" - logLevel "+logLevel);    

        text = theDate+" ["+theLevel+"]: "+message;
        if (logDoc == null) {
            // create document and a RTF for the log
            logDoc = logDb.createDocument();
            logDoc.replaceItemValue("Form", "log");
            logDoc.replaceItemValue("LogName", logName);
            if (logDb.getParent().isOnServer()){
                logDoc.replaceItemValue("ScriptRunOn", "Server");
            }else{
                logDoc.replaceItemValue("ScriptRunOn", "Local");            
            }
            logItem = logDoc.createRichTextItem("logBody");
        }
        // write log entry
        logItem.appendText(text);
        logItem.addNewLine();

        written = true;

    }

    //convert from level numbers into strings
    private String getLevelString(int level) {

        String levelString="";
        switch (level) {
        case LEVEL_DEBUG:  levelString="DEBUG" ; break;
	case LEVEL_INFO:  levelString="INFO" ; break;
	case LEVEL_WARN:  levelString="WARN" ; break;
	case LEVEL_ERROR:  levelString="ERROR" ; break;
	case LEVEL_FATAL:  levelString="FATAL" ; break;
        default : levelString=levelString+"LEVEL "+level; break;
        } // end switch    

        return levelString;
    }

    /**
     * save the log document
     * @throws NotesException
     */
    public void flush() throws NotesException{
        if (written) {
            logDoc.save();
            written=false;
        }
    }
    
    /**
     * close the log via recycling the underlying resource.
     * @throws NotesException
     */
    public void close() throws NotesException {
        flush();
        logDoc.recycle();
        logDoc=null;
        if (this.dbPath!=""){
            //If the log db is not the current one, recycle it.
            //Even recycling the current db doesn't cause any error.
            this.logDb.recycle();
        }
    }
}

package starrow;

import starrow.xsp.XSPUtil;
import lotus.domino.NotesException;

public class BeanLogger extends NotesLogger implements java.io.Serializable{

    private static final long serialVersionUID = 1L;

    public BeanLogger(String dbPath) throws NotesException {
        super(XSPUtil.getSession());
        this.dbPath=dbPath;
        logLevel = LEVEL_DEBUG;
    }

    /**
     * Add a constructor with no parameter for bean creation
     * @throws NotesException
     */
    public BeanLogger() throws NotesException{
        this("");
    }
    
    public void log(int level, Object message) throws NotesException{
        //get the current db freshly        
        if (this.dbPath==""){
            logDb=XSPUtil.getDatabase();
        }else{
            logDb=XSPUtil.getDatabase(this.dbPath);
        }
        super.log(level, message);
    }
}
BeanLogger里不寻常的一点是在重载的log方法里,获取全新的日志数据库实例,这是因为XPages引擎会频繁地自动清理Java里Lotus Domino对象用到的后端对象(参看42. Lotus Notes中的垃圾回收之Java),BeanLogger用到的日志数据库和文档很快会变成无效状态,调用它会引发以下异常:
NotesException: Object has been removed or recycled
    at lotus.domino.local.NotesBase.CheckObject(Unknown Source)
    at lotus.domino.local.Document.save(Unknown Source)
    at lotus.domino.local.Document.save(Unknown Source)
    …

你可能感兴趣的:(java,日志,Lotus,notes,Xpages)