You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Nashorn引擎调试信息输出至TXT并获取执行报告?

我之前做过类似的Nashorn脚本调试和执行报告需求,这里有几个可行的方案,你可以根据自己的场景来选:

方案1:静态代码替换+执行(适合简单场景)

这个思路很直接:先把用户脚本里对context的引用替换成实际的上下文值,执行脚本得到结果后,再把替换后的代码和结果拼接成目标报告。

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class NashornSimpleReport {
    public static void main(String[] args) throws ScriptException {
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
        
        // 模拟业务上下文变量
        Map<String, Object> context = new HashMap<>();
        context.put("valueA", 15);
        context.put("valueB", 10);
        
        // 用户提交的原始脚本
        String userScript = "var a = context.valueA; var b = context.valueB; return a+b;";
        
        // 替换脚本中的context变量为实际值
        String replacedScript = userScript;
        Pattern contextVarPattern = Pattern.compile("context\\.(\\w+)");
        Matcher matcher = contextVarPattern.matcher(userScript);
        
        while (matcher.find()) {
            String varName = matcher.group(1);
            Object actualValue = context.get(varName);
            replacedScript = replacedScript.replace(matcher.group(), actualValue.toString());
        }
        
        // 执行替换后的脚本获取结果
        Object result = engine.eval(replacedScript);
        
        // 生成最终报告
        String executionReport = replacedScript + " == " + result.toString();
        System.out.println(executionReport);
        // 输出结果:var a = 15; var b = 10; return 15+10; == 25
    }
}

优缺点:

  • 优点:代码简单易实现,不需要依赖复杂API,适合逻辑直白的脚本。
  • 缺点:只能处理直接引用context的场景,对于context.valueA * 2这类复杂表达式、或者动态变量引用(比如context[varName])会失效,也无法捕获脚本执行过程中的中间变量变化。
方案2:使用Nashorn调试API(适合复杂场景)

Nashorn自带了调试接口jdk.nashorn.api.scripting.debug.Debugger,可以注册监听器捕获脚本执行的全生命周期事件——包括变量赋值、表达式计算、方法调用等,完全满足你获取执行详情的需求。

import jdk.nashorn.api.scripting.NashornScriptEngine;
import jdk.nashorn.api.scripting.debug.Debugger;
import jdk.nashorn.api.scripting.debug.DebugListener;
import jdk.nashorn.api.scripting.debug.Locator;

import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.HashMap;
import java.util.Map;

public class NashornDebugReport {
    public static void main(String[] args) throws ScriptException {
        NashornScriptEngine engine = (NashornScriptEngine) new ScriptEngineManager().getEngineByName("nashorn");
        
        // 注入业务上下文
        Map<String, Object> context = new HashMap<>();
        context.put("valueA", 15);
        context.put("valueB", 10);
        engine.put("context", context);
        
        String userScript = "var a = context.valueA; var b = context.valueB; return a+b;";
        
        // 收集执行过程中的变量和结果
        Map<String, Object> variableRecords = new HashMap<>();
        Object[] resultHolder = new Object[1];
        
        // 初始化调试器并注册监听器
        Debugger debugger = engine.getDebugger();
        debugger.setListener(new DebugListener() {
            // 忽略不需要的回调方法
            @Override public void breakpointReached(Debugger d, Locator l) {}
            @Override public void scriptLoaded(Debugger d, String s, Locator l) {}
            @Override public void scriptUnloaded(Debugger d, String s) {}
            @Override public void exceptionThrown(Debugger d, String s, Locator l) {}
            @Override public void sourceParsed(Debugger d, String s, String s1) {}
            @Override public void methodEntered(Debugger d, String s, Locator l) {}
            @Override public void methodExited(Debugger d, String s, Locator l) {}
            @Override public void output(Debugger d, String s) {}
            @Override public void inputRequested(Debugger d, String s) {}
            
            // 捕获变量赋值事件
            @Override
            public void variableUpdated(Debugger debugger, String varName, Object value, Locator locator) {
                // 过滤Nashorn内部变量(以$开头)
                if (!varName.startsWith("$")) {
                    variableRecords.put(varName, value);
                }
            }
        });
        
        // 执行脚本
        resultHolder[0] = engine.eval(userScript);
        
        // 拼接生成报告
        StringBuilder reportBuilder = new StringBuilder();
        // 先添加变量赋值语句
        variableRecords.forEach((key, val) -> reportBuilder.append("var ").append(key).append(" = ").append(val).append("; "));
        
        // 处理返回表达式,替换变量为实际值
        String returnExpr = userScript.substring(userScript.lastIndexOf("return") + 6).trim().replace(";", "");
        variableRecords.forEach((key, val) -> returnExpr = returnExpr.replace(key, val.toString()));
        
        reportBuilder.append("return ").append(returnExpr).append("; == ").append(resultHolder[0]);
        System.out.println(reportBuilder.toString());
        // 输出结果:var a = 15; var b = 10; return 15+10; == 25
    }
}

优缺点:

  • 优点:能深度捕获脚本执行的所有变量变化,支持复杂表达式、动态逻辑甚至嵌套函数的变量跟踪,灵活性极强。
  • 缺点:代码相对复杂,需要熟悉Nashorn调试API的回调逻辑,还要注意过滤内部变量避免报告混乱。
方案3:自定义脚本包装(灵活可控)

这个思路是把用户脚本包装在一个自定义的函数里,通过脚本自身的逻辑来收集变量和结果,不需要依赖调试API,定制性很高。

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class NashornWrappedReport {
    public static void main(String[] args) throws ScriptException {
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
        
        Map<String, Object> context = new HashMap<>();
        context.put("valueA", 15);
        context.put("valueB", 10);
        engine.put("context", context);
        
        String userScript = "var a = context.valueA; var b = context.valueB; return a+b;";
        
        // 包装脚本:添加变量收集和报告生成逻辑
        String wrappedScript = "var __report = [];\n" +
                "(function() {\n" +
                "    var __result;\n" +
                "    // 替换return语句为结果赋值\n" +
                "    " + userScript.replaceFirst("return ", "__result = ") + "\n" +
                "    // 收集当前作用域的变量\n" +
                "    for (var key in this) {\n" +
                "        if (!key.startsWith('$') && key !== '__report' && key !== '__result') {\n" +
                "            __report.push('var ' + key + ' = ' + this[key] + ';');\n" +
                "        }\n" +
                "    }\n" +
                "    // 添加返回表达式和结果\n" +
                "    var returnExpr = '" + userScript.substring(userScript.lastIndexOf("return") + 6).trim().replace(";", "") + "';\n" +
                "    __report.push('return ' + returnExpr + '; == ' + __result);\n" +
                "    return __report.join(' ');\n" +
                "})();";
        
        // 执行包装后的脚本获取报告
        String rawReport = (String) engine.eval(wrappedScript);
        // 替换context变量为实际值
        Pattern contextPattern = Pattern.compile("context\\.(\\w+)");
        Matcher matcher = contextPattern.matcher(rawReport);
        while (matcher.find()) {
            String varName = matcher.group(1);
            rawReport = rawReport.replace(matcher.group(), context.get(varName).toString());
        }
        
        System.out.println(rawReport);
        // 输出结果:var a = 15; var b = 10; return 15+10; == 25
    }
}

优缺点:

  • 优点:不需要依赖调试API,通过脚本逻辑实现,定制报告格式非常灵活,适合需要自定义输出样式的场景。
  • 缺点:需要处理脚本语法兼容性,比如用户脚本中的return语句、作用域问题,对于复杂的嵌套函数场景可能需要调整包装逻辑。

内容的提问来源于stack exchange,提问作者danillosl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:20:48