ようへい

2012年8月16日木曜日

[JavaScript] ログ出力ライブラリ (console へ 出力レベル機能を追加)

作成に至る発端

Log4js もあるが、ほとんどデバッグ目的のログ出力なので、console.assertconsole.dir、可変長引数の console.log で出力する事が多い。
文字列のみを出力する Log4js はオブジェクトや、XMLの出力において、console での出力に圧倒的に劣る。
また、Log4js自体のファイルサイズも大きい。
しかし、console は、ログ出力レベルを指定して出力をコントロールするといった機能が無い。
そんな事を考えていると、以下の記事が目に入り、ライブラリを作るかという事になった。
console.log は長くてつらい #JavaScript - Qiita
http://qiita.com/items/6f2ae599207fa9af323a

挫折

難しくないだろうと、こんなコードを書いた。
var LoggerLevel={
  ALL:-99,
  DEBUG:-1,
  INFO:0,
  WARN:1,
  ERROR:2,
  OFF:99
},
self=null;
var Logger=function(level){
  self=this;
  self.level=isNaN(level) ? LoggerLevel.INFO : level;
};
for(var key in console){
  var level=LoggerLevel[key.toUpperCase()];
  if(!level){level=99};
  (
    function(k,l){
      Logger.prototype[k]=function(){
        self=this;
        if(self.level<=l){
          return console[k].apply(console, arguments);
        }
      }
    }
  )(key, level);
};
こんな感じで実行する。
var logger=new Logger(LoggerLevel.INFO);
logger.debug("debugメッセージ");
logger.info("infoメッセージ");
logger.warn("warnメッセージ");
logger.error("errorメッセージ");
logger.dir([1,2,3,4,5]);
ブラウザのコンソールを確認すると、うまく出力されているようだ。
ん・・・コンソールに表示される、どこでログ出力が呼ばれたかという行番号が、logger.js の console[k].apply の行番号になっている。
本当は、logger.info等を実行している行番号が出てほしいんだけど・・・。
これじゃデバッグが不便になるな。
うむ~まいった。
なやんでいると、こんな記事を見つけた。
console.logのエイリアス - hokaccha.hamalog v2
http://d.hatena.ne.jp/hokaccha/20111216/1324026093 bindね。
これを使うと、bindされたファンクションを、コンストラクタとして扱えるようになるという事。
bind | Mozilla Developer Network
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind

そして完成

bindを使い、以下のように書き直した。
var LoggerLevel={
  ALL:-99,
  DEBUG:-1,
  INFO:0,
  WARN:1,
  ERROR:2,
  OFF:99
},
self=null;
var Logger=function(level){
  self=this;
  self.level=isNaN(level) ? LoggerLevel.INFO : level;
};
for(var key in console){
  var level=LoggerLevel[key.toUpperCase()];
  if(!level){level=99};
  Logger.prototype[key]=(
    function(k,l){
      self=this;
      if(self.level<=l){
        if(Function.bind){
          return console[k].bind(console);
        }else{
          return console[k].apply(console, arguments);
        }
      }
    }
  )(key, level);
};
先ほどと同じように実行してみる。
今度は、ちゃんと呼び出し元のファイル名、行番号でコンソールに出力された。
めでたしめでたし。

ちょっと修正

上のコードでも問題ないんだけど、ちょっと見直し。
ログ出力の度いちいちFunction.bindを判定するのもなぁ・・・と思ったので以下のようにした。
var LoggerLevel={
  ALL:-99,
  DEBUG:-1,
  INFO:0,
  WARN:1,
  ERROR:2,
  OFF:99
},
self=null;
var Logger=function(level){
  self=this;
  self.level=isNaN(level) ? LoggerLevel.INFO : level;
};
for(var key in console){
  var level=LoggerLevel[key.toUpperCase()];
  if(!level){level=LoggerLevel.OFF};
  if(Function.bind){
    Logger.prototype[key]=(
      function(k,l){
        self=this;
        if(self.level<=l){
          return console[k].bind(console);
        }
      }
    )(key, level);
  }else{
    Logger.prototype[key]=(
      function(k,l){
        self=this;
        if(self.level<=l){
          return console[k].apply(console, arguments);
        }
      }
    )(key, level);
  }
};
Loggerのprototypeを作る際にあらかじめ判定するようにしてみた。
console.timeとか使った際に、判定が入ることによる誤差を無くすための処置。

更に修正

var LoggerLevel={
  ALL:-99,
  DEBUG:-1,
  INFO:0,
  WARN:1,
  ERROR:2,
  OFF:99
};
var Logger=function(level){
  var self=this;
  self.level=isNaN(level) ? LoggerLevel.INFO : level;
  self.make();
};
Logger.prototype.make=function(){
  var self=this;
  for(var key in console){
    var l=LoggerLevel[key.toUpperCase()];
    if(!l){l=LoggerLevel.OFF};
    if(self.level<=l){
      if(Function.bind){
        Logger.prototype[key]=(
          function(k){
            return console[k].bind(console);
          }
        )(key);
      }else{
        Logger.prototype[key]=(
          function(k){
            return console[k].apply(console, arguments);
          }
        )(key);
      }
    }else{
      Logger.prototype[key]=function(){};
    }
  }
};
さらに無駄な処理を省いた
一応最終版。

ダウンロード

非圧縮版(915B)
https://sites.google.com/site/logroid/files/logger.js
圧縮版(661B)
https://sites.google.com/site/logroid/files/logger.min.js
関連記事

0 件のコメント:

コメントを投稿