2 Replies Latest reply on Oct 20, 2008 1:18 AM by (silo)

    Looking for a logging strategy

      Hi,

      I'm scripting with JS and right now I'm looking for a logging strategy.

      Currently I'm using the $.writeln()-method. But that's just something for developing, because ESTK starts every time.

      I would like to log everything into a variable an at the end of the process, display it to the user.

      But that's a problem, because I do have a lot of script library files which I
      b #include
      across all other script files.

      Basically, I even need to include some files more than one time indirectly (e.g. fileA includes the log file and on the other hand fileB includes fileA and the log file). That causes me the most headache.

      So I hope there is some kind of standard solution for this issue.

      Thanks in advance.
      Silo
        • 1. Re: Looking for a logging strategy
          Level 1
          > I would like to log everything into a variable an at the end of the process, display it to the user.
          >

          First thing: Log to a file. It's extremely useful for debugging as well as
          development. Plus, you can do stuff like this on the OS X or XP/cygwin command line:
          tail -f myscript.log

          and the log messages will display on the terminal as they are being written. You
          could also have them written to the ESTK console, but I've found that to be of
          limited use, especially in larger scripts.

          Bob Stucky has some logging code on his site and I've appended some of mine below.

          The technique I use for handling include directives for a large number of
          interdependent library scripts is to not have them. I make a distinction between
          library scripts (which don't actually do anything when you attempt to run them)
          and application scripts (which actually do something useful when you execute them).

          When I write an application script, it has all of the include directives for the
          scripts it requires and the ones that they require etc...

          For documentation purposes, I usually have lines like this in the library
          scripts that indicate what include files they require.

          //-include "xlib/stdlib.js"
          //-include "xlib/XMPNameSpaces.jsx"

          I tried other techniques in the distant past and they all failed miserably.

          Here is the core library code. I have it spliced directly on my utility Stdlib
          class since just about everything I write requires that library script. I'll
          probably get around to breaking it out as a separate class later on, but I've
          never really had the need.


          Stdlib = function() {};

          Stdlib.log = function(msg) {
          var file;

          if (!Stdlib.log.enabled) {
          return;
          }

          if (!Stdlib.log.filename) {
          return;
          }

          if (!Stdlib.log.fptr) {
          file = new File(Stdlib.log.filename);
          if (Stdlib.log.append && file.exists) {
          if (!file.open("e", "TEXT", "????")) {
          Error.runtimeError(9002, "Unable to open log file(1) " +
          file + ": " + file.error);
          }
          file.seek(0, 2); // jump to the end of the file

          } else {
          if (!file.open("w", "TEXT", "????")) {
          if (!file.open("e", "TEXT", "????")) {
          Error.runtimeError(9002, "Unable to open log file(2) " +
          file + ": " + file.error);
          }
          file.seek(0, 0); // jump to the beginning of the file
          }
          }
          Stdlib.log.fptr = file;

          } else {
          file = Stdlib.log.fptr;
          if (!file.open("e", "TEXT", "????")) {
          Error.runtimeError(9002, "Unable to open log file(3) " +
          file + ": " + file.error);
          }
          file.seek(0, 2); // jump to the end of the file
          }

          if (isMac()) {
          file.lineFeed = "Unix";
          }

          if (Stdlib.log.encoding) {
          file.encoding = Stdlib.log.encoding;
          }

          if (msg) {
          msg.toString();
          }

          if (!file.writeln(new Date().toISODateString() + " - " + msg)) {
          Error.runtimeError(9002, "Unable to write to log file(4) " +
          file + ": " + file.error);
          }

          file.close();
          };
          Stdlib.log.filename = undefined; //"~/logfile.txt";
          Stdlib.log.enabled = false;
          Stdlib.log.append = false;
          Stdlib.log.setFile = function(filename, encoding) {
          Stdlib.log.filename = filename;
          Stdlib.log.enabled = filename != undefined;
          Stdlib.log.encoding = encoding;
          Stdlib.log.fptr = undefined;
          };
          Stdlib.log.setFilename = Stdlib.log.setFile;


          The code could be simpler but I need to be able to handle having a 'tail -f
          file.log' terminal up constantly across multiple executions of the script. I
          also need to be able to specify UTF-8, Ascii or whatever for the file encoding
          because sometimes it does matter.

          At the beginning of an application script, I'll add a line of code to initialize
          the logging, and I'm set.

          function main() {
          Stdlib.log.setFilename("/tmp/myscript.log");
          Stdlib.log("Start");

          //... app script code goes here

          Stdlib.log("Stop");
          }


          Here some utility functions that the log code needs:

          isWindows = function() { return $.os.match(/windows/i); };
          isMac = function() { return !isWindows(); };

          //
          // Format a Date object into a proper ISO 8601 date string
          //
          Stdlib.toISODateString = function(date, timeDesignator, dateOnly, precision) {
          if (!date) date = new Date();
          var str = '';
          if (timeDesignator == undefined) { timeDesignator = 'T' };
          function _zeroPad(val) { return (val < 10) ? '0' + val : val; }
          if (date instanceof Date) {
          str = (date.getFullYear() + '-' +
          _zeroPad(date.getMonth()+1,2) + '-' +
          _zeroPad(date.getDate(),2));
          if (!dateOnly) {
          str += (timeDesignator +
          _zeroPad(date.getHours(),2) + ':' +
          _zeroPad(date.getMinutes(),2) + ':' +
          _zeroPad(date.getSeconds(),2));
          if (precision && typeof(precision) == "number") {
          var ms = date.getMilliseconds();
          if (ms) {
          var millis = _zeroPad(ms.toString(),precision);
          var s = millis.slice(0, Math.min(precision, millis.length));
          str += "." + s;
          }
          }
          }
          }
          return str;
          };
          Date.prototype.toISODateString = function(timeDesignator, dateOnly, precision) {
          return Stdlib.toISODateString(this, timeDesignator, dateOnly, precision);
          };


          I use YYYY-MM-DDTHH:MM:SS strings to prefix each message. It helps keep things
          in order.

          Once this is in place, I can use these two routines:

          //
          // Thanks to Bob Stucky for this...
          //
          Stdlib.exceptionMessage = function(e) {
          var str = '';
          var fname = (!e.fileName ? '???' : decodeURI(e.fileName));
          str += " Message: " + e.message + '\n';
          str += " File: " + fname + '\n';
          str += " Line: " + (e.line || '???') + '\n';
          str += " Error Name: " + e.name + '\n';
          str += " Error Number: " + e.number + '\n';

          if (e.source) {
          var srcArray = e.source.split("\n");
          var a = e.line - 10;
          var b = e.line + 10;
          var c = e.line - 1;
          if (a < 0) {
          a = 0;
          }
          if (b > srcArray.length) {
          b = srcArray.length;
          }
          for ( var i = a; i < b; i++ ) {
          if ( i == c ) {
          str += " Line: (" + (i + 1) + ") >> " + srcArray[i] + '\n';
          } else {
          str += " Line: (" + (i + 1) + ") " + srcArray[i] + '\n';
          }
          }
          }

          if ($.stack) {
          str += '\n' + $.stack + '\n';
          }

          return str;
          };

          Stdlib.logException = function(e, msg, doAlert) {
          if (!Stdlib.log.enabled) {
          return;
          }

          if (doAlert == undefined) {
          doAlert = false;

          if (msg == undefined) {
          msg = '';
          } else if (isBoolean(msg)) {
          doAlert = msg;
          msg = '';
          }
          }

          doAlert = !!doAlert;

          var str = ((msg || '') + "\n" +
          "==============Exception==============\n" +
          Stdlib.exceptionMessage(e) +
          "==============End Exception==============\n");

          Stdlib.log(str);

          if (doAlert) {
          str += ("\r\rMore information can be found in the file:\r" +
          " " + decodeURI(Stdlib.log.fptr.fsName);

          alert(str);
          }
          };


          With these, I can write code like this:


          try {
          //...
          } catch (e) {
          Stdlib.logException(e, "Some message");
          }

          which will result in an excellently detailed error message in the log for the
          exception. If you are going to use logException or exceptionMessage, you need to
          change on bit in your code to get the full effect.

          Instead of
          throw Error("Something broke");

          do this:
          Error.runtimeError(9000, "Something broke");

          This will also cause an exception to get thrown, but you get all of the context
          information (line number, source, etc...) included with the exception. The 9000
          number is one that I picked for general app errors primarily because there don't
          appear to be JS or Adobe exceptions with numbers over 9000.


          This is probably more than you were looking for. I've been meaning to write up a
          tutorial on this stuff for awhile, so this discussion has been floating around
          in my head for a couple of months.

          Regardless, I hope it's useful.

          -X
          • 2. Re: Looking for a logging strategy
            Level 1
            Thanks a lot. It is very helpful.