XPagesのデバッグ

XPagesのデバッグについて説明してみよう。

Domino9.0から本格的なJavaScriptのDebuggerが搭載されたが、原始的とはいえ、printデバッグも効果的ではある。 printデバッグというのは、プログラムの途中にそこを通ったときのデータの中身などを出力するというものである。

XPagesの場合、print文を使うと、log.nsfに書き出される。サーバコンソールを開いておけばリアルタイムに表示される。

目次

画面上へのprintデバッグ

まずは、Web画面上に直接出す方法を説明する。動かしながらすぐに確認できるので、XPagesをはじめたころはよく使っていた方法だ。

sessionScopeにデバッグ文字列をセットするというもの。

XPage上に複数行編集ボックスを貼り付けておく。 その複数行編集ボックスのデータバインド先をsessionScope.debugにしておく。 sessionScopeを使ったデバッグ

これでボタン内のスクリプトで、sessionScope.debugに文字列を書き込めばこの編集ボックスに文字列が書き込まれる。

複数行にしているので、sessionScope.debug += ("\nitem.length = " + item.length); のように書いておくと、前回書き込まれた内容に 追加されていく。

クリアしたいときは、編集ボックス上で表示テキストを削除すればいい(文書が編集モードの場合だけ)。

オブジェクトの中身を表示したいときには、以下の関数を使うといいかも。ネタ元は、0502 - eto.com/dの「JavaScript debug」。 これをカスタマイズしてみた。

  function my_inspect(obj, indent) {
    if (indent == null) {
      indent = "";
    }
    if (typeof obj == "number") {
      // 数値の場合
      return obj;
    } else if (typeof obj == "string") {
      // 文字列の場合
      return "\"" + obj + "\"";
    } else if (typeof obj == "function") {
      // 関数の場合
      return obj;
    } else if ((typeof obj == "java.util.ArrayList") ||
               (typeof obj == "java.util.Vector")) {
      // 配列タイプ
      var pre_part = ( "<" + typeof(obj) + ">:[\n" );
      var analist = [];
      for(var index =0; index < obj.length; index++) {
        var item = obj[index];
        analist.push(indent + "  " + my_inspect(item, indent + "  "));
      }
      var after_part = ("\n" + indent + "]");
      return (pre_part + analist.join(",\n") + after_part);
    } else if ((typeof(obj) == "object" ) || 
               (typeof(obj) == "com.sun.faces.context.SessionMap")) {
      // オブジェクトの場合
      var pre_part = "<" + typeof(obj) + ">:{\n";
      var analist = [];
      for (var key in obj) {
        // 区切り文字
        var value = obj[key];
        if (!value) {
          // キーがあるが値がない場合
          analist.push( indent + "  " + key + " = undefined");
          continue;
        }
        analist.push(indent + "  " + key + " = " + my_inspect(value, indent + "  "));
      }
      var after_part =  "\n" + indent + "}";
      return (pre_part + analist.join(",\n") + after_part);
    } else {
      return "<<"+(typeof obj)+">>:{"+ obj + "}" ;
    }
  }
  

出力結果のサンプル。

  var obj = {}
  obj.member_string = "testtest";
  obj.member_value = 3
  obj.member_array = [1,2,3];
  sessionScope.my_obj = obj;
  sessionScope.debug_rep = ("sessionScope.my_obj  = " + my_inspect(sessionScope.my_obj));
  
  <出力結果>
  sessionScope.my_obj  = <object>:{
    member_string = "testtest",
    member_value = 3,
    member_array = <object>:{
      0 = 1,
      1 = 2,
      2 = 3
    }
  }
  

log.nsfへのprintデバッグ

XPagesのServer Side JavaScriptをデバッグするのに、log.nsfを使う方法も紹介する。

サーバサイドのスクリプトライブラリを用意し、以下の関数を入りつけておく。 log.nsfに書き込むので、データの構造も1行で表現しないといけない。

  // デバッグ関数
  function inspect(obj) {
    if (obj === null) {
      return "null";
    } else if (obj === undefined) {
      return "undefined";
    }
    // swicthは===で比較される
    switch(typeof obj) {
    case "number":
      // 数値
      return obj;
    case "string":
      // 文字列
      return "\"" + obj + "\"";
    case "function":
      // 関数の場合
      return obj;
    case "java.util.ArrayList":
    case "java.util.Vector":
      // 配列タイプ
      var part1 = "[";
      var list = [];
      for(var index=0; index < obj.length; index++) {
        var item = obj[index];
        list.push(inspect(item));
      }
      return (part1 + list.join(",") + "]");
    case "object":
    case "com.sun.faces.context.SessionMap":
      //オブジェクト(Arrayかも)
      if (obj instanceof Array) {
        var part1 = "[";
        var list = [];
        for(var index=0; index < obj.length; index++) {
          var value = obj[index];
          list.push(inspect(value));
        }
        if (list.length == 0) {
          // 日付型みたいなのはkeyがないので、文字列に変換する
          list.push(obj);
        }
        return (part1 + list.join(",") + "]");
      } else {
        if (typeof(obj) == "object") {
          var part1 = "{";
        } else {
          var part1 = "<" + typeof(obj) + ">:{";
        }
        var list = [];
        for(var key in obj) {
          var value = obj[key];
          if (typeof(value) != "function") {
            list.push(key + ":" + inspect(value));
          }
        }
        if (list.length == 0) {
          // 日付型みたいなのはkeyがないので、文字列に変換する
          list.push(obj);
        }
        return (part1 + list.join(",") + "}");
      }
      break;
    default:
      return "<<" + typeof(obj) + ">>:{" + obj + "}";
    }
  }
  
  
  function debug(message, obj) {
    var header = "[DEBUG][" + @UserName(1) + "]:";
    if (obj === undefined) {
      print(header + message);
    } else {
      print(header + message + inspect(obj));
    }
  }
  

後は、このスクリプトライブラリをXPageのリソースとして組み込んでおけば、ServerSideスクリプトで、調べたいオブジェクトをdebug()に渡せば、log.nsfに以下のように記録される。

  var data = ["aaa", "bbb", "ccc", "ddd"];
  debug("data=", data);
  

この結果は、こんな感じで記録される。

  06/15/2013 09:43:07 PM  HTTP JVM: [DEBUG][CN=Administrator/O=tech]:data = [1,"aaa","3",4]
  

タイムスタンプの形式は、OSのLANG環境変数で変わるはず(Linux系の場合)。

ただ、log.nsfへの出力結果は他のログも出力されるし、debug()の出力結果も、1行の長さがながいと、複数行に分割されてしまい、ちょっと読みにくい。 そこでlog.nsfから必要な情報を抜き出すRubyのスクリプトを用意してみた。

これを使うと、NotesClientを入れているPCから最新のログからdebug()が出力した結果だけ抽出してくれる。

  #!ruby -Ks
  require 'notes_lib'
  
  def get_loglines(log_values)
    loglines = []
    log_values.each {|values|
      values.each {|oneline|
        loglines.push oneline if oneline =~ /HTTP\sJVM:/
      }
    }
    return loglines
  end
  
  ns = Notes::NotesSession.new
  db = ns.database("technotes", "log.nsf")
  view = db.view("MiscEvents")
  
  LASTLOG_COUNT = 5
  
  doc = view.getLastDocument
  log_values = []
  LASTLOG_COUNT.times do
    break unless doc
    log_values.unshift doc["EventList"].values
    doc = view.getPrevDocument(doc)
  end
  
  flat_loglines = get_loglines(log_values)
  
  mode = "IDLE"
  loglist = []
  flat_loglines.each {|oneline|
    case mode
    when "IDLE"
      if oneline =~ /^(.+)HTTP\sJVM\:\s\[DEBUG\](.+)/
        timestamp = $1.strip
        log = $2
        loglist.push "[#{timestamp}]#{log}"
        mode = "HEAD"
      end
    when "HEAD"
      if oneline =~ /^(.+)HTTP\sJVM\:\s\[DEBUG\](.+)/
        # 分割がなかった場合
        timestamp = $1.strip
        log = $2
        loglist.push "[#{timestamp}]#{log}"
        mode = "HEAD"
      elsif oneline =~ /^(.+)HTTP\sJVM\:\s(.+)/
        # 分割されてた続きを見つけた
        log = $2
        loglist[-1] += log
        if loglist[-1].size > 10000
          mode = "IDLE"
        end
      else
        mode = "IDLE"
      end
    end
  }
  puts loglist.join("\n")
  

注意事項

このスクリプトを使ってて気づいたのだが、ログが複数行に分割されるとき、1文字程度情報が欠落することがある。 log.nsfへ記録する時点で欠落してるので、ログシステムの問題だろう。

あと、以下のケースだと、"subproperty"を表示してくれない。

  var data = ["aaa", "bbb", "ccc", "ddd"];
  data.subproperty = "TEST";
  debug("data=", data);
  

これ、自作のinspect()の問題じゃなく、dataがArrayだからこうなるような気がする。 ためしに、XPagesに組み込まれているtoJson()でdataをJSON文字列にしても、subpropertyの値が記録されない。 JavaScriptのArrayというのはそういうものなのかも。