LoginSignup
8
9

More than 5 years have passed since last update.

esprima, estraverse, escodegen使ってjavascriptの関数すべてにトレースログを仕込んでみる

Last updated at Posted at 2014-05-29

関数のトレースをとる必要があったのですが、
esprimaを使えばいけるじゃないかと思って試してみました。

ASTをたどるのは面倒いなーと思っていたら、
estraverseでさくっといけました。
関数がオブジェクトのメンバのときの名前がまだうまくとれていないですが、素の関数定義のときは関数名をとってきてます。

未知のソースの解析とかに利用しようと思います。
サーバー側でコードを注入して各関数の実行時間をとれば
chrome developer toolとか使えない環境向けのプロファイラも作れそうだなっとも思っています。

あと、アスペクト指向的にコード注入できると嬉しいだろうかとも思ってみたり。

logger_injection.coffee
esprima    = require 'esprima'
escodegen  = require 'escodegen'
estraverse = require 'estraverse'
fs         = require 'fs'

_funcID = 0

logger = (funcID, fileName, node, parent, loc) ->
  name = 'anonymous'
  if node.id?
    name = node.id.name
  else if parent.id
    name = parent.id.name
  logItem = {
    id: funcID,
    file: fileName,
    func: name,
    line: node.loc[loc].line
    phase: loc
  }
  esprima.parse('logger.trace(' + JSON.stringify(logItem) + ' );').body

injectLogger = (source, fileName) ->
  ast = esprima.parse source, {
    loc: true
  }
  estraverse.traverse ast, {
    enter: (node, parent) ->
      if (node.type == 'FunctionExpression' ||
          node.type == 'FunctionDeclaration')
        _funcID++
        node.body.body = logger(_funcID,
                               fileName,
                                   node,
                                 parent,
                                 'start').concat node.body.body
        node.body.body = node.body.body.concat logger(_funcID,
                                                     fileName,
                                                         node,
                                                       parent,
                                                        'end')
  }
  escodegen.generate ast

fs.readFile process.argv[2], (err, source) ->
  console.log injectLogger source

追記:
esprimaの作者の方のesmorphというのを使えばもっと簡単にできるようでした。
ただし、npmにあがっているのは少しバージョン古いようなので、
https://github.com/ariya/esmorph
からとってきて使いました。

logger_injection.coffee esmorph版
esprima    = require 'esprima'
escodegen  = require 'escodegen'
esmorph    = require 'esmorph'
fs         = require 'fs'

logger = (fileName, fn, phase) ->
  logItem = {
    file: fileName,
    func: fn.name,
    line: fn.line,
    range: fn.range,
    phase: phase,
    return: fn.return ? false
  }
  'logger.trace(' + JSON.stringify(logItem) + ' );'

injectLogger = (source, fileName) ->
  tracers = [
    (esmorph.Tracer.FunctionEntrance (fn) -> logger(fileName, fn, 'start')),
    (esmorph.Tracer.FunctionExit (fn) -> logger(fileName, fn, 'end'))
  ]
  code = esmorph.modify(source, tracers)

fs.readFile process.argv[2], (err, source) ->
  console.log injectLogger source.toString()
8
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
9