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