9

When I catch an error in ExtendScript, I would like to be able to log its stack trace. It appears that errors do not contain stack traces in ExtendScript, so I'm playing around with the idea of adding stack traces to errors.

The only way I know of to get a stack trace is $.stack. The field $.stack contains the current stack trace at the moment that you access the field.

My first attempt was to create my own error object that includes the stack. The Error object is very special in that it can get the line and filename of the code that created it. For example,

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

Will print:

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

I don't think it's possible to create your own object with this ability. The closest I can get is this:

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);
}

Which prints:

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)

Here we can see that I'm creating my own error object and explicitly passing it the line and file name (since MyError can't figure that out on its own). I've also included the current stack when the error gets created.

This works fine when I call my own error object, but it doesn't work when other code calls the regular Error object or when an error is generated automatically (e.g. by illegal access). I want to be able to get the stack trace of any error, no matter how it is generated.

Other approaches might be to modify Error's constructor, modify Error's prototype, or replace the Error object entirely. I haven't been able to get any of these approaches to work.

Another idea would be to put a catch block in every single method of my code and add the current stack to the error if it doesn't already have one. I would like to avoid this option if possible.

I'm out of ideas. Is there any way to get the stack trace of errors?

4

5 回答 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

所以它不适用于所有错误,但它的进步。

于 2013-04-25T16:16:42.447 回答
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")

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

于 2013-05-10T16:54:55.597 回答
1

据我所知,您无法修改 Error.prototype.toString-function 的 [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-block 中做这样的事情:

alert(e);

现在我正在做alert(ReturnCustomErrorString(e));,但我得到了更多有用的信息。所以目前我认为这个解决方案非常好。

于 2017-07-26T21:11:11.033 回答
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();
}
于 2016-05-28T17:53:29.950 回答
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
    })
}
于 2021-09-07T09:37:57.750 回答