Saturday, May 19, 2012

Logger and Log Level in Google Apps Script

If you have become used to some finer control for logging, you may not be very satisfied with the one in Apps Script.

To fix it, you only need a few lines of code:

this.log = function(/*level, args*/) {
    var args = Array.prototype.slice.call(arguments),
    level = args.shift();
    if (this.level >= level) {
      this.logger.log([new Date(), level].concat(args.map(function(o, i, a) {
         return Utilities.jsonStringify(o); })).join(this.delimiter));
    }
    return this;
};

For convenience, we may add some additional functions:

  var levels = ['', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'];
  for (var i = 1; i < levels.length; i++) {
    this[levels[i]] = (function() {
      var args = Array.prototype.slice.call(arguments);
      return function() {
        return this.log.apply(this, args.concat(Array.prototype.slice.call(arguments)));
      };
    }(i));
  }

Here is the complete sample.

Sunday, May 13, 2012

Cache 101 with Google Apps Script

If you have used the cache service long enough in your Google Apps Script, you probably have known you could not cache anything larger than 100KB.

If you are absolutely certain the values you are going to cache will not hit that magic number, you will be fine; but what if there is a chance your value could be 100 + 1? You can always check the size before you put, but that would be cumbersome and your code would be messy.

Here is a simple ready-to-use wrapper which hides the complexity and still performs equally well for size under 100KB.

It uses the first byte as an indicator: 'v' or 'k'. If the size is less than (100KB -1), it stores the data value; otherwise it chunks the data and stores the auto assigned keys as the value for the given key. The generated keys consist of the given key appended with a running number.

In addition, the wrapper automatically does the conversion between JSON object and string text, so you may store object instead.

To simplify the interface, you specify a creator function in place of a value. i.e.

cache.get(key, creator);

If the value exists in cache, it returns immediately; otherwise the creator function will be called.

Sample

Saturday, April 14, 2012

Return From Apps Script Service

A script written in Google Apps Script can be hosted on a Google Sites website and published as service. The service may be accessed anywhere on the web.

Very promising, but if you do not already know, before you get too excited,
a published script is a service which may only return and get:

1. Nothing or null

The script completed but did not return anything.

2. Any object but not UiApp

The script completed but the returned value is not a supported return type.

3. UiApp instance

A nice busy loading spinner and a page full of Javascript which will eventually run and show the UI you constructed in Apps Script.

As you can see, the third option, the only officially documented return type, is what you are expected to do, but unlikely is exactly what you had in mind for a service.

In a multi-tier business environment, the last thing one would associate with a user interface is service - literally.

If you are required to integrate with other services of your own or a third party, exposing services as HTML and scripts is certainly not very desirable.

If you wish to return your own object, the right approach probably would be to file a feature request and wait...

Or if you can't wait and still love Apps Script, here is what you can do.

You may know that Apps Script does not support a user-defined exception or at least not the expected way. If you throw one, you will get something similar to the following:

Syntax error: ... line: ? (line xx)

this time however in HTML text proper - no Javascript.

So the obvious solution is to throw a custom object e.g. JSON, and assume the other party will catch it.

function sendJson(e) {
throw '<![CDATA[' + JSON.stringify(e) + ']]';
}

Calling the service will get for example:

Syntax error: <![CDATA[{"email":'liqiang@example.com'}]]> line: ? (line xx)

if {"email":"liqiang@example.com"} is thrown.

It is trivial to just extract, decode, and parse the content to get the JSON back.

function getJson(s) {
JSON.parse( s.substring(s.indexOf('[CDATA[') + 7, s.lastIndexOf(']]')) );
}


From tests, big objects could be passed, size would not be an issue.

If someday, Google decides to support proper user-defined exception, by contract, your code should still work.

Sample Code