Google Analyticsを利用してクライアントサイドのエラーのレポーティングを行う

数年前に記事やつぶやきをみたけたけど忘れてしまったのでメモ変わりに記載。ユニバーサルアナリティクス用のコードです。基本的にはwindow.onerrorイベントを補足してgaにイベントを送信するだけです。要素技術として、アナリティクスのイベント・トラッキングを利用しています。

ソースコード

window.onerror = function(message, file, line) {
    message = message + " at " + file + "[" + line + "]";
    // GAのイベントは category, action, labelの合計値が7kくらいまでおくれる"らしい"
    ga("send", "event", "error", "javascript", message.replace(/\r?\n/gm, "").substr(0, 1024 * 5));
    return false
};
// 以下のコードを適当に記載で確認
// throw new Error('error test');

イベントトラッキングの設計

アナリティクスのイベント集計の場合は、category(種類), action(行動), label(ラベル)と分けてトラッキングすることが可能です。今回は、categoryをerror。actionをjavascript。labelをonerrorから取れる各種情報と定義しました。
actionの部分をHogeExcepiton, BarException。labelにfileやline。スタックトレースと定義してもいいかもしれません。ベストプラクティスがあったら教えてください。

アナリティクスで確認する

  • [リアルタイム] → [イベント]
  • [行動]→[イベント]

から色々辿れる。

それユニバーサルアナリティクスのExceptionトラッキングで...

もしかして。このことですか?https://developers.google.com/analytics/devguides/collection/analyticsjs/exceptions。こちらでももちろんトラッキングできます。標準では閲覧用のビューやレポートが無いので自身でマイレポート設定が必要です。
アナリティクスは社内のマーケの人とかも見ると思うのでひっそりとトラッキングしたい場合はExceptionトラッキング方式をおすすめします。
レポートの設定はこちらから。http://stackoverflow.com/questions/21718481/report-for-exceptions-from-google-analytics-analytics-js-exception-tracking
iOSAndroidだとCrash & Exceptionsというビューがあるのでウェブ用のトラッキングでも出現期待ですね。

一番大事なこと

クライアントサイドエラーをトラッキングするのが目的ではなく修正するのが目的です。トラッキングするならただちに修正すること。修正する気持ちが無いならならトラッキングしないほうが懸命です。

AdobeAirでビデオを再生するときにアプリが一瞬黒くならないようにする設定

Air3.5で採用されたtrueオプションを使う。ビデオ再生するときに一瞬黒くなるんので気になりました。

application.xml

  <android>
    <!-- 追加する -->
    <containsVideo>true</containsVideo>
  </android>

FMSを使って録画するときのはまりポイント

Flash Media Serverを使って録画するときのお話です。3系での話。それ以外は知らない。

はまりがちなコード

マニュアル片手にやっていると次のようにコーディングしがち。関数のローカル変数になっていることがポイント。

Client.prototype.startRecord = function(path) {
    var stream = Stream.get(path);
    stream.play("livestream");
    stream.record();
}

こう書くと、数分後・数秒後にGCに回収されて録画が勝手にストップします。onStatusのコールバックも呼ばれません。中途半端に動く分、はまる。全く動かないなら気がつくけど…

はまらないコード

applicationなどのプロパティに指定しましょう。思い通りに動きます。

application.recordStreams = {};

Client.prototype.startRecord = function(path) {
    var stream = application.recordStreams[path] = Stream.get(path);
    stream.play("livestream");
    stream.record();
}

あと録画が終わったらapplication.recordStreamsの対象のStreamを削除しておきましょう。

あと…
  • NetConnection
  • NetStream

なども、同じことではまる。as3でもはまる。

TileWindowのメインカラーをcontrolBarGroup内のボタンに反映させたくない。

以下のサイトで質問されていることではまったことがあるのでメモ。
Re: http://stackoverflow.com/questions/6129939/button-in-titlewindow-has-wrong-color

Answer

controlBarGroupの中身が、TitleWindowのchromeColorが適用されるのが問題なので適用させなくしましょう。TitleWindow用のスキンを用意して適用するだけ。

package foo.bar.hoge
{
    import spark.components.Group;
    import spark.skins.spark.TitleWindowSkin;

    public final class TitleWindowSkin extends spark.skins.spark.TitleWindowSkin
    {
        private static const exclusions:Array = ["controlBarGroup"];

        public function TitleWindowSkin()
        {
            super();
        }

        override public function get colorizeExclusions():Array 
        {
            return super.colorizeExclusions.concat(exclusions);
        }
    }
}
<s:TitleWindow skinClass="foo.bar.hoge.TitleWindowSkin" />

環境

  • FLex 4.6での出来事。

FMSを使った時にQueryを解析するベストプラクティス

関数を自作しないという意味でのベストプラクティス。

var q = new LoadVars();
q.decode("foo=1&bar=2");

trace(q.foo);

Stream.publishQueryStringの解析が楽になりますね。

社内IRCへWebSocketで接続してみたい

ちょうど去年の3月末に某CSKを退社。去年の今頃新しい会社に拾われました。入った感想としては、驚きの文化として社内のコミュニケーションツールIRCを使っているというところ。慣れるまでは大変でした。 IT企業だと案外普通みたいですね。
さて本題、社内IRCブラウザーからアクセスしてみたかったのでInspIRCdをWebSocketに対応させてみました。InspIRCdは、C++によるIRCの実装です。

HTMLソース

<html>
<script>
webSocket = new WebSocket('ws://localhost:3000/');
webSocket.onopen = function() {
    console.log("open");
    this.send("NICK shogo4405\r\n");
    this.send("USER shogo4405 * 0 :shogo4405\r\n");
};
webSocket.onmessage = function(event) {
    console.log(event);
};
webSocket.onerror = function(event) {
    console.log(event);
}
webSocket.onclose = function(event) {
    console.log(event);
}
</script>
<body><h1>It works!</h1></body></html>

ブラウザはChrome

http://tools.ietf.org/html/rfc6455 に対応させています。2012/04/27現在は、最新版のChromeが対応しているのかな。

キャプチャーだとみにくいのですが確かに、IRCの情報が流れてきています。

NOTICE Auth :Welcome to Omega!\r\n:penguin.omega.org.za 001 shogo4405 :Welcome to the Omega IRC Network shogo4405!shogo4405@0::1\r\n:penguin.omega.org.za 002 shogo4405 :Your host is penguin.omega.org.za, running version InspIRCd-2.0\r\n:penguin.omega.org.za 003 shogo4405 :This server was created 20:20:03 Apr 14 2012\r\n:penguin.omega.org.za 004 shogo4405 penguin.omega.org.za InspIRCd-2.0 iosw biklmnopstv bklov\r\n:penguin.omega.org.za 005 shogo4405 AWAYLEN=201 CASEMAPPING=rfc1459 CHANMODES=b,k,l,imnpst CHANTYPES=#

InspiRCdのほうは

inspircd.conf

WebSocketのデフォルトポートが80らしいのですが、私の環境ではポートが塞がっているため3000番に設定しています。websocket="m_websocket"がおまじないです。
今回作成した自作モジュールをloadするだけ。

<bind address="" port="3000" websocket="m_websocket" type="clients">
<module name="m_websocket.so">

InspIRCdの再起動

./inspircd restart

ということで、PORT3000がIRC over WebSocketというべきでしょうかWebSocketに対応します。デフォルトの6667は、プレーンなIRCでも運用できますね。

最後に

あー。社内のIRCサーバーは、InspIRCdじゃなかったー。まずはそこからだ。

ソースコード

githubに置いておきますが、まだまだ実装段階なのでバギーです。ましなものが出来たらv1.0.0つけて公開しようと思います。そのうち...
https://github.com/shogo4405/m_websocket

firebugの計測関数(console.time/console.timeEnd)を自動挿入

firebugの計測関数(console.time/console.timEnd)をつかってメソッドの実行時間の計測するときなどに、計測関数を挿入忘れと削除忘れしないために自動的に挿入するためのコードです
開発のときには、ブックマークレットで呼び出しておいて使うと便利かも(しません)。

フレームワーク部分(interceptor.js)

aop = {};
aop.interceptors = {};
aop.interceptors.TimeInterceptor = function(){};
aop.interceptors.TimeInterceptor.prototype.create = function(lambda) {
    var module = this.module;
    return function() {
        console.time(module);
        lambda.apply(this, arguments);
        console.timeEnd(module);
    };
};

aop.aspect = function(namespace, interceptor) {
    namespace = namespace.split('.');

    // 1. foo.barのオブジェクトを取得
    var obj = window;
    for (var i = 0, f = namespace.length; i < f;i++) {
        obj = obj[namespace[i]];
    };

    // 2. foo.barのオブジェクトを全捜査してフィールドがfunctionのときに、
    // 既存のfunctionをInterceptorでラップしている。
    namespace = namespace.join('.');
    for (i in obj) {
        var field = obj[i];
        interceptor.module = namespace + '.' + i;

        if (typeof(field) !== 'function') {
            continue;
        };

        // 既存のfunctionのプロパティなどをInterceptorでラップしたfunctionにコピーしている。
        var newField = obj[i] = interceptor.create(field);
        for (var j in field) {
            newField[j] = field[j];
        };
    };
};
動かすためのサンプル
foo = {};
foo.bar = {};
foo.bar.hoge = function() { alert('hoge'); };

// foo.barにTimeInterceptorを適用
aop.aspect('foo.bar', new aop.interceptors.TimeInterceptor());
foo.bar.hoge();

課題とか

  • prototypeに対応は未考慮