Adobe-CEP / CEP-Resources

Tools and documentation for building Creative Cloud app extensions with CEP
https://www.adobe.io/apis/creativecloud/cep.html
1.63k stars 834 forks source link

throw new Error() inside CSInterface.evalextendscript() not working on CEP 10? #356

Open berendvaneerde opened 3 years ago

berendvaneerde commented 3 years ago
const csInterface = new CSInterface();
csInterface.evalScript(`
    function throwMyError(){
        throw new Error('myError')
    } 
    throwMyError();
`);

forces the debugger when Preferences -> Scripting & Expressions -> Enable JavaSript Debugger is enabled But what if you want to catch some errors on the panel side in your javascript?

JRammos29 commented 3 years ago

I have the same problem. I want to throw an error from the JSX and handle it on the JS side. But it's not working. Any clues on this?

MaxJohnson commented 3 years ago

Create a listener in JS and a custom event emitter in JSX. I wrote a (not great cause I'm not a programmer) logger thing you can check out. https://preview.npmjs.com/package/extendscript_log https://github.com/MaxJohnson/extendscript_logfile

I also made a cep to jsx promise helper thing: https://preview.npmjs.com/package/cep-extendscript-eval-promise

JRammos29 commented 3 years ago

Hi @MaxJohnson.

Thanks for the share. The packages are very handy. I implemented the log and logFile. The messages are printed in the console, and the log files are created but don't have any content.

About the throw new Error object, like the function below, the log didn't catch the message "throw new Error". Just the myLog.log("log message") and myLog.error("error message"), messages. There's some way to catch the new Error message?

//Extendscript 

function errorTest(){
    myLog.log("log message");
    myLog.error("error message");
    throw new Error("===================== throw new Error =====================");
    alert("Error"); //not reached
}

//Javascript

// Hook up internal logging messages from extendscript scripts that support it
 csInterface.addEventListener('ExtendScript_Log', handleExtendscriptLog);

function handleExtendscriptLog(evt) {
    var label = evt.data.label;
    var logger = (!label || label == 'undefined') ? 'log' : label;
    var data = (typeof evt.data == 'string') ? JSON.parse(cleanJSONString(evt.data)) : evt.data;
    console[logger]('[CVP]', data.message);
}
MaxJohnson commented 3 years ago

Hi @MaxJohnson.

Thanks for the share. The packages are very handy. I implemented the log and logFile. The messages are printed in the console, and the log files are created but don't have any content.

I just hooked it up to test and here's how I implemented (on the host jsx end... didn't try CEP)

test_log = new ExtendScript_Log($.global, "Testr", 0, true, true); //(root, logName, logLevel, useLogFile, keepOldLogs)
try{
  log.log('testing log');
  throw new Error('oh noes');
}
catch ( e ) { log.error( e ); }

and my log file looks like this (yikes that formatting!):

Mon May 24 2021 15:18:32 GMT-0700: testing log
Mon May 24 2021 15:18:32 GMT-0700: [error] \tMessage: oh noes\n\tFile: ~/Desktop/TmpTesting.jsx\n\tLine: 325\n\tError Name: Error\n\tError Number: 1\t[TmpTesting.jsx]\nerror([Error:Error: oh noes])\n_getExceptionMessage([Error:Error: oh noes])\n

If you log an object, it'll try and JSON.stringify it and then you can unstrifgify in your JS app to make it a real error again. Trick being that you need to implement a JSON parser into your jsx context. Relevant snippet from the log constructor:

                if (JSON && typeof JSON.stringify === 'function') {
                    return JSON.stringify(message);
                } else {
                    return String(message);
                }
JRammos29 commented 3 years ago

Thanks for the reply.

In your example, you do not have the ES_LogFile instance. Both ES_Log and ES_Logfile packages can write files? How they must be used together?

And seems that the problems occur with csInterface (CEP), cause it returns just strings as result. So, I tried two ways:

MaxJohnson commented 3 years ago

Sorry you're having so many issues! The CEP, Node, JSX ecosystem is not the easiest to navigate...

In your example, you do not have the ES_LogFile instance. Both ES_Log and ES_Logfile packages can write files? How they must be used together?

The .jsx files need to be loaded but once they are, you only need to create an instance of the ES_Log with the logfile option enabled and it will create it's own instance of that internally. Something to be aware of though, is that if you are calling into the JSX context from CEP, that is persistent so if you send the evalFile command from your JS CEP, it'll have your log constructors for the rest of the session...

$.evalFile( "<fullpath>/extendscript_log/dist/extendscript_log.jsxinc")
$.evalFile( "<fullpath>/extendscript_logfile/dist/extendscript_logfile.jsxinc")
test_log = new ExtendScript_Log($.global, "Testr", 0, true);// <-- last arg tells log to make a logfile on init

try{
  log.log('testing log');
  throw new Error('oh noes');
}
catch ( e ) { log.error( e );}

Looks like I made my Log auto-parse error objects into that string format, so I need to update it to only do that for printing to the console... good bug find, thanks!

The problem is that seems the event enters the process queue and is executed after the next script instruction. So it doesn't throw the Error to interrupt before the program continues. There's some way to prior the event handler execution? Or any ideas to achieve this?

If I read that right, your main CEP panel JS is continuing on before the error from the script can be handled? The jsx script is run asynchronously, so your CEP is not waiting for it to complete. That's why I ended up writing that little cep-extendscript-eval-promise library... so I could do async chaining when I needed to wait for a jsx script to finish before moving on.

Here's a messy example chopped from a panel I was working on... again... not a programmer, just persistent.

 var esp = require('cep-extendscript-eval-promise');

    lifterPath = path.resolve(thisExtensionPath, 'host/libs/lifter.dev.js');
    esp.enableLog(true);
    esp.evalScript('\'Testing basic esp logging...\'', false).then(console.log, console.error);

  // Initialize jsx libraries into jsx context
  esp.cwd(thisExtensionPath)// change jsx working directory so things know where they are...
    .then(function evalLifterScript() {
      return esp.evalFile(lifterPath);
    })
    .then(// (resolveFunc(){}, rejectFunc(){}) format
      function requiredLoadedResolve() {
        console.info('Loaded Lifter libs successfully.');
      },
      function requiredLoadedReject(data) {
        console.error('Failed to load Lifter libs successfully.', data);
      }
    );
MaxJohnson commented 3 years ago

I updated the extendscript_log to pass the error object before it is reformatted. I also fixed up the formatting for the log output for errors. NPM hasn't updated, but the github project has latest. https://github.com/MaxJohnson/extendscript_log#readme

JRammos29 commented 3 years ago

Hi Max. Maybe I expressed myself wrong in the previous post.

What I'm trying to do (it can sound weird) is propagate the error itself from the JSX to the JS (CEP panel). Don't know if there any mean of doing that. I can do it with some hack and workaround, but throw the error in JSX and catch it in the JS it's not working. I have had tried with promises also, but it's the same.

What about the logs, I tried the example you gave and they are working well, even with error messages (but the log file still doesn't have content). I changed the ExtendScript_Log to the new version and now seems that the error object, besides the error info itself, also has the entire source code from the file where the error occurred (and this is a safety issue). Maybe you can fix the error object info by removing the source file content or just revert the last changes.

And despite an auto-declared-non-developer, it's such a very good package. Congrats.

MaxJohnson commented 3 years ago

What I'm trying to do (it can sound weird) is propagate the error itself from the JSX to the JS (CEP panel). Don't know if there any mean of doing that. I can do it with some hack and workaround, but throw the error in JSX and catch it in the JS it's not working. I have had tried with promises also, but it's the same.

I do not think you can do what you want to do. The JSX context is like a separate virtual-machine. Each panel makes its own jsx context and keeps it in the background. The only thing you can throw between those contexts is a string message.

Short of hacking into the Error.prototype to inject that external event emitter, I don't see how you can catch arbitrary errors in the jsx directly, either. As far as I know, you have to wrap a try{}catch(){} and send from there.

What about the logs, I tried the example you gave and they are working well, even with error messages (but the log file still doesn't have content). I changed the ExtendScript_Log to the new version and now seems that the error object, besides the error info itself, also has the entire source code from the file where the error occurred (and this is a safety issue). Maybe you can fix the error object info by removing the source file content or just revert the last changes.

I have not been able to reproduce the issue with empty log files, but thanks to your feedback, I was able to update the log and logfile repos with better error object handling and option for adding a custom output directory for log files.

1.4.0 (2021-05-27) New Features

Other Changes

https://github.com/MaxJohnson/extendscript_log/blob/master/CHANGELOG.md https://github.com/MaxJohnson/extendscript_logfile#readme

And despite an auto-declared-non-developer, it's such a very good package. Congrats.

Thanks for the feedback and keeping up with this!

JRammos29 commented 3 years ago

Yeah. Seems that it's not possible. But since this topic it's related to this, I left the question.

I still couldn't make the log write to file without declaring an instance for ES_Logfile and use its object. In fact, I was getting the file, but no content. I made some tests and seems that when using new Date() concatenated directly to the message, writes no content to the file (don't know why). Changing to the method, _getISODate() (the same used to get the date for the file name), has written to file.

Well, the code below is how I'm trying to use the logs. What must I change to use just the ES_Log instance to log to both console and file?

#include "extendscript_log.jsxinc";
#include "extendscript_logfile.jsxinc";

var ROOT_DIR = File($.fileName).parent.parent.fsName;

var LOGS_DIR = ROOT_DIR + getPathSeparator() + "logs/"

var CVP_LOGGER_FILE = new ExtendScript_LogFile ($.global, null, LOGS_DIR)
var CVP_LOGGER = new ExtendScript_Log(($.global, "CVP", 0, true, true, LOGS_DIR));

CVP_LOGGER.log(LOGS_DIR);
// CVP_LOGGER.logFile.log(LOGS_DIR);
CVP_LOGGER_FILE.writeln(LOGS_DIR);
MaxJohnson commented 3 years ago

Oh! Thanks for the code... You have the args for the new ExtendScript_Log() double wrapped in ()! I removed those extra parens and it does not need the extra log file init...

Try this. It gave me a valid named log file with output. "2021-05-27_200752-CVP.log"


// var CVP_LOGGER_FILE = new ExtendScript_LogFile ($.global, null, LOGS_DIR)
var CVP_LOGGER = new ExtendScript_Log($.global, "CVP", 0, true, true, LOGS_DIR);

CVP_LOGGER.log('LOGS_DIR);
// CVP_LOGGER.logFile.log(LOGS_DIR);
// CVP_LOGGER_FILE.writeln(LOGS_DIR);
JRammos29 commented 3 years ago

Great! It's working. It's weird because seems that I saw the double () in some example, and even when I removed before, the script won't work. But it's alright.

The only thing is the problem with writing the date remains. I tried with the original ES_Logfile again, but no content in the file. Still just using the function that returns the formatted date. By the way, would be good if there be an optional param to date format.

Thanks.

MaxJohnson commented 3 years ago

I pushed a final change to the extendscript_logfile.jsxinc... name formatting changed for files and entries, but the main adds were

1.2.0

(2021-05-28)

New Features
  • add option to use date in log entries or not, add user facing setDirectory() and useDate()

so you should be able to do this now

CVP_LOGGER.logFile.setDirectory(LOGS_DIR);
CVP_LOGGER.logFile.useDate(false);

So now you can disable the date and add it to your message manually however you want.

Also, a heads up about the CEP version of the JSX is that it does not like #include at all. You may have to use $.evalFile() to include other scripts and if those use global vars, it will be persistent in your environment.

JRammos29 commented 3 years ago

The optional date flag is nice, but logs that you won't use the date it's a bit uncommon. The idea about the date format is because many people have different preferences on the amount of detail they need or want in a log file. And maybe the need to append a date with the log/error message (in case of the flag is off) on each call, sounds a few verbose.

Another thing I was thinking about is maybe you can turn the two packages into only one (providing options of when to use each or both) since it's more common to use both features together than separately,

Thanks for sharing the package, the help, and the improvements. And also for advising about the use of #include with CEP.