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いいじゃん!