Meteor

Meteor Client Error Logging to the Server

As convenient as it is to develop mobile apps in Meteor using Cordova (PhoneGap), it is difficult if not impossible to view errors, logging, or other things happening in real time on real mobile hardware. These errors and logs are output to the user's console. Because of this, they will most likely never be reported. So, taking inspiration from Loggly, we implemented a few Meteor server methods which take a message as an argument and logs that message to the server and the console. This technique will also be helpful in finding client error on Meteor webapps.

Lets run through what we did.

First things first: adding a Meteor method to deal with different types and levels of logging. In our case, we opted for INFO,WARN, and ERROR level logging. Upon establishing these, we implemented a simple method signature to include both the logging level as well as the message needing to be logged via our new method. We also want to make sure this method does not block other DDP messages from being processed. We use this.unblock() to accomplish this (to learn more about the meteor unblock read about it on meteorhacks.com). 

clientLog: function(level,message) {
      this.unblock();
      var user = Meteor.call("getUserEmail");
      switch (level) {
        case LEVEL_WARN:
          console.log("WARN-CLIENT:",user,":",message); 
          break;
        case LEVEL_ERROR:
          console.log("ERROR-CLIENT:",user,":",message);
          break;
        default:
          console.log("INFO-CLIENT:",user,":",message);   
      }
    },
  });

We then make a call to get the user name of the logged in user which we can associate this log. We are using the accounts-password package so we just look in the database to find the corresponding user record and then grab the email address.

getUserEmail: function() {
  if(this.userId) {
    var userRecord = Meteor.users.findOne(this.userId);
    return userRecord["emails"][0]["address"];
  } else {
    return "";
  }
}

Now to use these Method we just make a call from the client. To make it easier we just monkey patch console.log, console.warn, and window.error to perform the proper server call. So all you need to do is add console.log (or console.warn) in your client code and it will automatically be logged on the server as well as the client.

//remap console.log to send to the server
  var clientLogger = console.log;
  var clientWarnLogger = console.warn;
  console.log = function() {
    clientLogger.apply(this,arguments);
    return Meteor.call('clientLog', LEVEL_LOG, arguments);
  };
  console.warn = function() {
    clientWarnLogger.apply(this,arguments);
    return Meteor.call('clientLog', LEVEL_WARN, arguments);
  };
  window.onerror = function() {
    return Meteor.call('clientLog', LEVEL_ERROR, arguments);
  };

Here is the final product.

var LEVEL_LOG = "log";
var LEVEL_WARN = "warn";
var LEVEL_ERROR = "error";
if (Meteor.isServer) {
  Meteor.methods({
    getUserEmail: function() {
      if(this.userId) {
        var userRecord = Meteor.users.findOne(this.userId);
        return userRecord["emails"][0]["address"];
      } else {
        return "";
      }
    },
    clientLog: function(level,message) {
      this.unblock();
      var user = Meteor.call("getUserEmail");
      switch (level) {
        case LEVEL_INFO:
          console.log("INFO-CLIENT:",user,":",message);   
          break;
        case LEVEL_WARN:
          console.log("WARN-CLIENT:",user,":",message); 
          break;
        case LEVEL_ERROR:
          console.log("ERROR-CLIENT:",user,":",message);
          break;
      }
    },
  });
}

Meteor.startup(function() {
  //remap console.log to send to the server
  var clientLogger = console.log;
  console.log = function() {
    clientLogger.apply(this,arguments);
    return Meteor.call('clientLog', LEVEL_LOG, arguments);
  };
  console.warn = function() {
    return Meteor.call('clientLog', LEVEL_WARN, arguments);
  };
  window.onerror = function() {
    return Meteor.call('clientLog', LEVEL_ERROR, arguments);
  };
});

Hope that helps in debugging any future client issues.