App EngineのLogをXMPPで飛ばす.

Log をリアルタイムで見たい

App Engineにはよくできたログコンソールがあり,フィルタリングとかもできるのだが,Webベースの悲しさ,必ずリロードしなければ最新の情報をみることができない.

これを解決する素晴らしい記事がこちら.叢雲の歌:XMPPを使ったログの通知.LoggerのAppenderとしてXMPPメッセージを投げるものを作り,それを使ってすべてのログをIMで飛ばす.具体的には,GmailのChatウィンドウで読めるようになる.
こりゃ便利.

java.util.loggingでできないか

こちらの記事では,ロギングライブラリとして,sl4jとlogback-classicを使っている.これをなんとか,AppEngine 標準のjava.util.loggingライブラリを使ってできないかというのを試してみた.
結論から言うと,できない.っていうか,できるんだけどすごく中途半端.

理想的には,logging.propertiesでHandlerを追加し,Handlerに対してログレベルも設定したいのだけど,logging.propertiesで Handlerを追加しても,読んでくれないようだ.また,カスタムHandlerの中から,logging.propertiesで設定された内容を読みに行くと,セキュリティ例外で落ちる.

さらに,カスタムHandlerで,独自にログレベルを設定しようとするだけで,落ちる.お手上げ.

できること

じゃあ,何ができるかというと,サーブレットの中で,明示的にLoggerにhandlerを追加すれば,ログの送信はできる.ただし,ログレベルは,logging.properties の .loglevel でグローバルに設定したものと同じになる.

使い方はこんな感じ.ここではstatic に宣言している

public class XMPPServlet extends HttpServlet {
    static Logger logger = Logger.getLogger("XMPPServlet");
    static private String notifyTo = "XXXXXXXXX@gmail.com";
    static {
        logger.addHandler(new XMPPHandler(notifyTo));
    }

    public void doGet(HttpServletRequest req, 
                      HttpServletResponse resp)
            throws IOException {
        logger.severe("xxxxx");
        logger.warning("yyyyy");
        logger.info("zzzzz");
     
        resp.setContentType("text/plain");
        resp.getWriter().println("Hello, world");
    }
}

XMPPHandler

カスタムハンドラはこんな感じ.java.util.logging.Handlerを実装したものになっている.StreamHandlerの実装を参考にした.

import java.io.IOException;
import java.util.logging.*;
import com.google.appengine.api.xmpp.*;

public class XMPPHandler extends Handler{
    private XMPPService service = XMPPServiceFactory.getXMPPService();  
    private Formatter formatter = new SimpleFormatter();
    private String notifyTo;

    public XMPPHandler(String notifyTo){
        this.notifyTo = notifyTo;
    }
    public void close() throws SecurityException {}
    public void flush() {}

    public void publish(LogRecord record) {
        if (!isLoggable(record)) 
            return;
        try {
            String msg = formatter.format(record);
            write(msg);
        } catch (Exception ex) {
            reportError(null, ex, ErrorManager.FORMAT_FAILURE);
            return;
        }
    }
    private void write(String msg) throws IOException{
        JID jid = new JID(notifyTo);  
        Message message = new MessageBuilder()  
            .withMessageType(MessageType.CHAT)  
            .withRecipientJids(jid)  
            .withBody(msg)
            .build();  
        SendResponse resp = service.sendMessage(message);
        SendResponse.Status status = resp.getStatusMap().get(jid);
        if (status != SendResponse.Status.SUCCESS)
            throw new IOException("Failed to send message: " + status);
    }
}

所感

logging.propertiesで設定できないのは,大変残念だが,それでもそれなりに使えなくはない.リアルタイムでログが見えるのは気持ちがいい,と思う.XMPPいいじゃん!