Tuesday, May 12, 2020 / JavaScript, Rhino, Groovy

Rhino で js を実行中にファイルにログを書き出したい

Java から evaluateString する形で javascript コードを実行する場合に logger.log() で指定したファイルにログを書き出したい、という場合の解決方法.

Logger を自分で用意して使えばOK

Logger という ScriptableObject を自分でつくって解決する:

class Logger extends ScriptableObject {
    @Override
    String getClassName() { return "Logger" }

    Logger(){}

    private File file
    void jsConstructor(String filePath){
        this.file = new File(filePath)
    }

    @JSFunction
    void log(String msg){
       file.text = msg
    }
}

このクラスを logger という名前で 現在の scope に存在させる:

def logFile = new File('log.txt')
ScriptableObject.defineClass(scope, Logger.class)
def logger = cx.newObject(scope, "Logger", logFile.absolutePath)
ScriptableObject.putProperty(scope, "logger", logger)

ポイントは cx.newObject するときに、保存するログファイルのパスを渡しておきます.

あとは、たとえばこんな js を書く:

var value = 1+2;
logger.log( ''+value );

これを evaluteString すればよい:

cx.evaluateString(scope, script0, "<cmd>", 1, null)

補足

Logger クラスの log 関数内が...

@JSFunction
void log(String msg){
    file.text = msg
}

このような実装になっているので、一つのメッセージしかファイルに保存できない. (新しいメッセージがくると前のメッセージは消える.)
これでは不便なので、古いメッセージに追記する形に修正します.

@JSFunction
void log(String msg){
    def lines = []
    if( file.exists() ){
        lines = file.text.readLines()
    }
    lines << msg
    file.text = lines.join(System.getProperty('line.separator'))
}

これで、複数の logger.log() をしてもログを全部保存できるようになりました.

コード全体:

@Grab(group='org.mozilla', module='rhino', version='1.7.12')
import org.mozilla.javascript.Context
import org.mozilla.javascript.ScriptableObject
import org.mozilla.javascript.annotations.JSFunction

class Logger extends ScriptableObject {
    @Override
    String getClassName() { return "Logger" }

    Logger(){}

    private File file
    void jsConstructor(String filePath){
        this.file = new File(filePath)
    }

    @JSFunction
    void log(String msg){
        def lines = []
        if( file.exists() ){
            lines = file.text.readLines()
        }
        lines << msg
        file.text = lines.join(System.getProperty('line.separator'))
    }
}

def script0 = '''\
    |logger.log('message-1');
    |logger.log('message-2');
    |logger.log('message-3');
    |'''.stripMargin('|')

def cx = Context.enter()
def scope = cx.initStandardObjects()

def logFile = new File('log.txt')
ScriptableObject.defineClass(scope, Logger.class)
def logger = cx.newObject(scope, "Logger", logFile.absolutePath )
ScriptableObject.putProperty(scope, "logger", logger)

cx.evaluateString(scope, script0, "<cmd>", 1, null)

Context.exit()

以上です.