获取ExtendScript中错误的堆栈跟踪

9
当我在ExtendScript中捕获错误时,希望能够记录它的堆栈跟踪信息。但是,在ExtendScript中,似乎错误并不包含堆栈跟踪信息,因此我正在尝试将堆栈跟踪信息添加到错误中。
目前我所知的唯一获取堆栈跟踪信息的方法是使用$.stack。字段$.stack包含当前堆栈跟踪信息,当你访问该字段时获取的就是当前的堆栈跟踪信息。
我的第一次尝试是创建一个包含堆栈信息的自定义错误对象。 Error对象非常特殊,它可以获取创建它的代码行和文件名。例如:
try {
    throw new Error("Houston, we have a problem.");
}
catch (e) {
    $.writeln("Line: " + e.line);
    $.writeln("File: " + e.fileName);
    $.writeln("Message: " + e.message);
}

将打印:

Line: 2
File: ~/Desktop/Source1.jsx
Message: Houston, we have a problem.

我认为不可能创建具有此能力的自定义对象。我能做到的最接近的是这个:
```html

我不认为可以通过这种方式创建你自己的对象并拥有此能力。我能找到的最相似的方法是:

```
function MyError(msg, file, line) {
    this.message = msg;
    this.fileName = file;
    this.line = line;
    this.stack = $.stack;
}

try {
    throw new MyError("Houston, we have a problem.", $.fileName, $.line);
}
catch (e) {
    $.writeln("Line: " + e.line);
    $.writeln("File: " + e.fileName);
    $.writeln("Message: " + e.message);
    $.writeln("Stack: " + e.stack);
}

这会打印出:

Line: 9
File: ~/Desktop/Source2.jsx
Message: Houston, we have a problem.
Stack: [Source3.jsx]
MyError("Houston, we have a p"...,"~/Desktop/Source2.js"...,9)

在这里,我们可以看到我正在创建自己的错误对象,并明确传递了行和文件名(因为MyError无法自己找出)。当创建错误时,我还包括了当前堆栈。
当我调用自己的错误对象时,这很好用,但是当其他代码调用常规错误对象或自动生成错误时(例如通过非法访问),它就不起作用了。我想能够获取任何错误的堆栈跟踪,无论它如何生成。
其他方法可能是修改“Error”的构造函数、修改“Error”的原型或完全替换“Error”对象。我还没有能够使任何这些方法起作用。
另一个想法是在我的代码的每个单独方法中放置一个catch块,并在错误尚未具有堆栈的情况下将当前堆栈添加到错误中。如果可能的话,我想避免使用此选项。
我已经没有想法了。有没有办法获取错误的堆栈跟踪?

如果您能使某些功能正常工作,我会非常感兴趣。我的最大问题是一旦编译成 jsxbin 格式,我就无法从错误中得到任何有用的信息。因此我进行了大量的日志记录以便于从中调试。 - Anna Forrest
个人而言,我不会使用$.write或$.writeln指令。首先,如果未打开ExtendScript Toolkit,则它们将使其出现在最前面。如果没有警告用户,这可能会让终端用户感到困惑。此外,在循环内部使用它们可能会非常缓慢。 我更喜欢将日志文件写入磁盘。我有自己的方法,但您也可以利用此库:http://creative-scripts.com/logging-with-a-smile/ - Loic
5个回答

2
我找到了一个部分解决方案,虽然不完美。
事实1:`Error.prototype` 是一个错误对象。
事实2:每当创建一个错误时,都会调用方法 `Error.prototype.toString`。
事实3:字段 `Error.prototype.toString` 可以被修改。
该方法通常只返回字符串 "Error",因此我们可以用自己的方法替换它,该方法存储堆栈,然后返回字符串 "Error"。
Error.prototype.toString = function() {
    if (typeof this.stack === "undefined" || this.stack === null) {
        this.stack = "placeholder";
        // The previous line is needed because the next line may indirectly call this method.
        this.stack = $.stack;
    }
    return "Error";
}

try {
    throw new Error("Houston, we have a problem.");
}
catch (e) {
    $.writeln("Line: " + e.line);
    $.writeln("File: " + e.fileName);
    $.writeln("Message: " + e.message);
    $.writeln("Stack: " + e.stack);
}

结果:

Line: 11
File: ~/Desktop/Source10.jsx
Message: Houston, we have a problem.
Stack: [Source10.jsx]
toString()

它可以工作!唯一的问题是自动错误。

Error.prototype.toString = function() {
    if (typeof this.stack === "undefined" || this.stack === null) {
        this.stack = "placeholder";
        // The previous line is needed because the next line may indirectly call this method.
        this.stack = $.stack;
    }
    return "Error";
}

try {
    var foo = null;
    foo.bar;
}
catch (e) {
    $.writeln("Line: " + e.line);
    $.writeln("File: " + e.fileName);
    $.writeln("Message: " + e.message);
    $.writeln("Stack: " + e.stack);
}

结果:

Line: 12
File: ~/Desktop/Source12.jsx
Message: null is not an object
Stack: undefined

因此,它并不能解决所有错误,但是它在进步中。


2

我想到了另一个解决方案,但这个方案需要你改变一些代码。不要像通常那样调用方法:

myObject.myMethod1("Hello", "world");

您需要切换到调用以下方法:

myObject.do("myMethod1", "Hello", "world");

以下是完整的示例说明:

这是一个完整的演示:

Object.prototype.do = function stackHelper() {
    // Convert the arguments into an array.
    var argumentArray = Array.prototype.slice.call(arguments);
    // Remove the first argument, which is the function's name.
    var functionString = argumentArray.shift();
    try {
        this[functionString].apply(this, argumentArray);
    }
    catch (e) {
        if (typeof e.stack === "undefined" || e.stack === null) {
            e.stack = $.stack;
        }
        throw e;
    }
};

var myObject = {
    myMethod1: function myMethod1(myArg1, myArg2){
        this.do("myMethod2", myArg1, myArg2);
    },

    myMethod2: function myMethod2(myArg1, myArg2){
        this.do("myMethod3", myArg1, myArg2);
    },

    myMethod3: function myMethod3(myArg1, myArg2){
        $.writeln(myArg1 + ", " + myArg2 + "!");
        var foo = null;
        foo.bar; // Throws an error.
    },
};

try {
    myObject.do("myMethod1", "Hello", "world");
}
catch (e) {
    $.writeln("Stack: " + e.stack);
}

输出结果如下:
Hello, world!
Stack: [do.jsx]
stackHelper("myMethod1","Hello","world")
myMethod1("Hello","world")
stackHelper("myMethod2","Hello","world")
myMethod2("Hello","world")
stackHelper("myMethod3","Hello","world")

这不是一个很好的解决方案,但至少它适用于所有错误。


1
据我所知,您无法修改Error.prototype.toString函数的[native Code]。因此,我想出了这个解决方案:
function ReturnCustomErrorString(e, additionalMessage)
{
    try {
        var errorString = e.toString();
        errorString = errorString.concat("\n", "additionalMessage: " + additionalMessage + "\n", "file: " + e.fileName + "\n", "line: " + e.line + "\n", "stack-trace: \n" + $.stack);
        return errorString;
    }
    catch (e) {
        alert("Error in : " + ReturnCustomErrorString.name + "(...)\n" + e);
        exit();
    }
}

使用方法:

try {
    // code that does throw an error
} catch (e) { 
    alert(ReturnCustomErrorString(e)); 
}

在编写这个函数之前,我经常在catch块中执行类似于以下操作:
alert(e);

现在我正在执行alert(ReturnCustomErrorString(e));,但是我获得了更多有用的信息。所以目前我认为这个解决方案相当不错。


0
如果您只需要显示自定义消息,我写了这段代码。我认为这解决了问题,对我来说没问题。
try
{
    app.selection[0].contents = 1
}
catch (myError)
{
    alert(myError.number); // For check the number error
    if (myError.number == 30477)
    {
        alert("Mensagem Edu\n" + "Line: " + myError.line + "\n" + "File: " + myError.fileName + "\n" + "Message: " + myError.message + "\n" + "Stack: " + myError.stack);
        exit();
    }
    else (myError);
    {}
    exit();
}

有人得到了除了undefined之外的东西,用于myError.stack吗?这就是OP问题的重点。 - Philippe-André Lorin

0

我也发现扩展Error对象会导致问题,在ExtendScript中它有一个“特殊状态”,不幸的是。

我能想到的最好的方法是以下:

function WrappedError(error) {
    return {
        name: 'ExtendScriptError',
        message: error.message,
        source: error.source,
        line: error.line,
        stack: $.stack,
    }
}

这样使用:

throw WrappedError(new Error('Error Message Goes Here'))

使其工作的关键是在发生错误的实际行上创建一个真正的"错误"(对象),这样我们就可以在包装的错误中获得正确的行号,而且我们还可以访问"err.source",这将为稍后提供上下文非常有趣。
接下来,在从"CEP"端评估ExtendScript代码时,我将调用包装在try/catch中:
function safeRun(code) {
    const safeCode = `(function () {
        function errorToPretty (err) {
            var stack = (err.stack && err.stack.split('\\n')) || []
            var lines = (err.source && err.source.split('\\n')) || []
            err.line--
            stack.shift()
            stack.shift()
            stack.pop()
            stack.reverse()
            return {
                name: err.name,
                message: err.message,
                line: err.line,
                code: err.code,
                context: [
                    lines[err.line - 2] || '',
                    lines[err.line - 1] || '',
                    '---> ' + lines[err.line] || '',
                    lines[err.line + 1] || '',
                    lines[err.line + 2] || ''
                ],
                stack: stack
            }
        }
        try {
            return ${code}
        } catch (err) {
            return JSON.stringify(errorToPretty(err))
        }
    })()`
    return evalExtendscript(safeCode).then((res) => {
        if (typeof res === 'object' && res.stack && res.line && res.message && res.context) {
            const e = new Error(res.message + '\n\n' + res.context.join('\n'))
            e.name = res.name
            e.stack = `${res.name}: ${res.message}
        ${res.stack.map((func) => `at ${func} (extendscript.js:0:0)`).join('\n    ')}`
            throw e
        }
        return res
    })
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接