【问题标题】:Getting the stack trace of an error in ExtendScript在 ExtendScript 中获取错误的堆栈跟踪
【发布时间】:2013-04-18 13:40:35
【问题描述】:

当我在 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.

我认为用这种能力创建自己的对象是不可能的。我能得到的最接近的是:

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 格式,我就不会从错误中得到任何有用的信息。我已经完成了大量日志记录,因此我可以从中进行调试。
  • 我个人不会使用 $.write 或 $.writeln 指令。首先,如果您没有打开,他们会预先提供 ExtendScript Toolkit。如果他们没有得到警告,那可能会让最终用户感到困惑。此外,它可能真的很慢,尤其是在循环内部。我更喜欢将日志文件写入磁盘。我有我自己的食谱,但你可以利用这个库:creative-scripts.com/logging-with-a-smile

标签: adobe adobe-indesign extendscript indesign-server


【解决方案1】:

我还发现扩展 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
    })
}

【讨论】:

    【解决方案2】:

    据我所知,您无法修改 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));,但我得到了更多有用的信息。所以目前我觉得这个解决方案还不错。

    【讨论】:

      【解决方案3】:

      如果您只需要显示自定义消息,我编写了此代码。 我认为解决了,...对我来说没关系。

      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 问题的重点吗?
      【解决方案4】:

      我提出了另一种解决方案,不过这个解决方案需要您更改一些代码。而不是像往常一样调用方法:

      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")
      

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

      【讨论】:

        【解决方案5】:

        这并不完美,但我找到了部分解决方案。

        事实 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
        

        所以它并不适用于所有错误,而是它的进步。

        【讨论】:

          猜你喜欢
          • 2019-03-05
          • 2014-08-20
          • 2010-10-11
          • 2018-09-19
          • 1970-01-01
          • 2019-02-07
          • 2013-02-23
          • 2018-01-29
          • 1970-01-01
          相关资源
          最近更新 更多