FlashPlayer10.1でバグレポート
などと、弊社のババが呟きました。
ローカルマシンでは正しく動作するのに、別マシンで動かしたら「ActionScriptエラーが発生しました」というポップアップ…。そんなエラーが発生したクライアントの動作環境が分かれば、環境依存するような問題を解決しやすくなります。
Flash Player 10.1からグローバルなエラー処理が可能となります。
つまり、クライアントで発生したエラー内容を記録する仕組みを実現できるようになりました。
今回は、この仕組みを AS3 + GAE/J(GoogleAppEngin for Java)を利用して簡単に構築してみようと思います。
※開発には、FlashPlayer10.1 beta 3(2010/03/02現在)をインストールする必要があります。
※GAE/Jを開発する環境も別途必要になります。
AS3 : UncaughtErrorMonitorクラス
package{
import flash.display.DisplayObject;
import flash.events.ErrorEvent;
import flash.events.UncaughtErrorEvent;
import flash.external.ExternalInterface;
import flash.net.URLRequest;
import flash.net.URLRequestMethod;
import flash.net.URLVariables;
import flash.net.sendToURL;
import flash.system.Capabilities;
/**
* エラー補足モニタークラス
*
* グローバルエラーを監視し、エラーが発生した場合に内容をポストします
*
* @author maegawa@bascule
*/
public class UncaughtErrorMonitor{
/**
* コンストラクタ
* @param clazz
*/
public function UncaughtErrorMonitor( clazz:PrivateClass ){
// インスタンスは生成させない
}
/**
* 初期化する
* @param root :ドキュメントクラス
*/
public static function init( root:DisplayObject ):void{
if( root && root.loaderInfo ){
root.loaderInfo.uncaughtErrorEvents.addEventListener( UncaughtErrorEvent.UNCAUGHT_ERROR, function( e:UncaughtErrorEvent ):void{
var errorID :uint = 0;
var type :String = "";
var message :String = "";
var location:String = "";
if( e.error is Error ){ // Errorを捕捉した場合
var error:Error = e.error as Error;
errorID = error.errorID;
type = error.name;
message = error.message;
location = error.getStackTrace();
}else
if( e.error is ErrorEvent ){ // ErrorEventを捕捉した場合
var event:ErrorEvent = e.error as ErrorEvent;
errorID = event.errorID;
type = event.type;
message = event.text;
}else{
return; // Error、ErrorEventを補足した場合は処理を中断
}
var url:String = "http://GAEアプリID.appspot.com/regist";
var variables:URLVariables = new URLVariables;
variables.errorID = errorID; // エラーID
variables.type = type; // エラータイプ
variables.message = message; // エラーメッセージ
variables.location = location; // エラー発生場所
variables.swf = Capabilities.version; // SWFのバージョン判定
variables.userAgent = getUserAgent(); // UserAgent判定
var request:URLRequest = new URLRequest( url );
request.data = variables;
request.method = URLRequestMethod.POST;
try{
sendToURL( request );
}catch( e:Error ){
}
} );
}
}
/**
* ユーザーエージェントを取得する
* @return
*/
private static function getUserAgent():String{
try{
if( ExternalInterface.available ){
return ExternalInterface.call( "function(){ return navigator.userAgent; }" );
}
}catch( e:Error ){}
return "unknow";
}
}
}
class PrivateClass{}
続いて、サーバー側を準備します。
GAE : UncaughterrorServletクラス
package jp.bascule;
import java.io.IOException;
import java.util.Date;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Transaction;
@SuppressWarnings("serial")
public class RegistUncaughtErrorServlet extends HttpServlet {
public void doGet( HttpServletRequest req, HttpServletResponse res ) throws IOException {
res.sendError( 400 );
}
public void doPost( HttpServletRequest req, HttpServletResponse res ) throws IOException {
try {
String errorID = req.getParameter( "errorID" );// エラーID
String type = req.getParameter( "type" );// エラータイプ
String message = req.getParameter( "" );// エラーメッセージ
String location = req.getParameter( "location" );// エラー発生場所
String swf = req.getParameter( "swf" );// SWFのバージョン判定
String userAgent = req.getParameter( "userAgent" );// UserAgent判定
Date date = new Date();
long time = date.getTime();
//
Entity entity = new Entity( "UncaughtError" );
entity.setProperty( "appkey", appkey );
entity.setProperty( "error_id", errorID );
entity.setProperty( "error_type", type );
entity.setProperty( "error_message", message );
entity.setProperty( "error_location", location );
entity.setProperty( "swf_version", swf );
entity.setProperty( "user_agent", userAgent );
entity.setProperty( "datetime", time );
DatastoreService service = DatastoreServiceFactory.getDatastoreService();
Transaction transaction = service.beginTransaction();
try {
service.put(transaction, entity);
transaction.commit();
} finally {
if (transaction.isActive())
transaction.rollback();
}
res.getWriter().print( "success" );
} catch (Exception e) {
res.getWriter().print( "error" );
e.printStackTrace();
}
}
}
GAE:web.xml
<?xml version="1.0" encoding="utf-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <servlet> <servlet-name>RegistUncaughtError</servlet-name> <servlet-class>jp.bascule.RegistUncaughtErrorServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>RegistUncaughtError</servlet-name> <url-pattern>/regist</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app>
この内容をGAEにデプロイすれば完了です。
AS3:Testクラス
package{
import flash.display.Sprite;
import flash.events.ErrorEvent;
/**
* Testクラス
*
* UncaughtErrorMonitorクラスの動作をチェックします。
* 意図的にError、ErrorEventをスローさせます。
*/
public class Test extends Sprite{
public function Test(){
// モニタリングを開始
UncaughtErrorMoniter.init( this );
// Errorの場合
throw new Error( "エラーが発生!" );
// ErrorEventの場合
//throw new ErrorEvent( ErrorEvent.ERROR, false, false, "エラーイベントが発生!" );
}
}
}
こんな感じで呼び出しておけば、クライアント側でエラーが発生した場合に勝手に内容がポストされます。
いくつかのプロジェクトで仕組みを使いまわすのであれば、アプリケーション識別子を引数に加えてあげればいいだけです。さらに、画面サイズ、ネットワーク環境など、その他の情報も記録しておけば、よりスムーズに問題を解決できそうです。
GAEの管理画面にはDatastoreViewerというデータを確認する画面があるので、一覧表示する画面を用意する必要もありません。GAEは無料で使用可能ですが、データ保存容量には限界があります。とくに情報を残す必要がないのであれば、cronを利用して定期的に内容を削除すればよいと思います。