Sentry/Raven with Meteor on Server

Apologies @asad, I was on holidays :slight_smile:

Yes. It’s really simple on the client side.

In your client startup you will need something like this:

import ravenClient from 'raven-js';

Meteor.startup(function () {

    let sentryURL = 'https://' + Meteor.settings.public.sentryPublicKey + "@sentry.io/" + Meteor.settings.public.sentryAppId;

    ravenClient.config(sentryURL, {
        release: Meteor.settings.public.version,
        environment: Meteor.settings.public.environment,
        shouldSendCallback: function(data) {
            return Meteor.settings.public.environment !== "development";
        }
    }).install();
})

I’ve left our specific keys we use for keeping track of the version and environment and the callback we use so sentry doesn’t send anything when we’re in development mode.

Then, to add a little context add the following to your onLogin/Logout overrides.

Accounts.onLogin(function() {
    ravenClient.setUserContext({
        email: Meteor.user().emails[0].address,
        id: Meteor.userId()
    });
});

Accounts.onLogout(function() {
    ravenClient.setUserContext();
});
1 Like

Does this catch all errors or does Meteor swallow most errors and prevent them from being captured by Sentry? Because that has been my experience so far.

Yes - this gets all client side errors. Basically any exception that shows up in your browser console.

Gotta try this!

Does it play well with Kadira, or do I have to choose?

For using it on the server you have to remove the official meteorhacks:kadira, clone it locally and make the changes. Then you can keep sending performance related metrics to Kadira and send exceptions to Sentry.

1 Like

Ive been using sentry

Actually @asad. I’ve just noticed it doesn’t get all the errors, apologies. Specifically an error coming from a Tracker recompute which was asking Blaze to rerender. I had been focused on getting the server bits re-wired using modified Kadira code but didn’t spot that on the client there is also a part that needs re-wiring. Here’s the Kadira code un-modified. I will just change the part that sends the error to Kadira and re-route to Sentry in my local clone of Kadira.

var originalMeteorDebug = Meteor._debug;

Meteor._debug = function(m, s) {
  // We need to asign variables like this. Otherwise, 
  // we can't see proper error messages.
  // See: https://github.com/meteorhacks/kadira/issues/193
  var message = m;
  var stack = s;
  
  // track only if error tracking is enabled
  if(!Kadira.options.enableErrorTracking) {
    return originalMeteorDebug(message, stack);
  }

  // do not track if a zone is available (let zone handle the error)
  if(window.zone) {
    return originalMeteorDebug(message, stack);
  }

  // We hate Meteor._debug (no single usage pattern)
  if(message instanceof Error) {
    stack = message.stack;
    message = message.message
  } else if(typeof message == 'string' && stack === undefined) {
    stack = getStackFromMessage(message);
    message = firstLine(message);
  }

  // sometimes Meteor._debug is called with the stack concat to the message
  // FIXME Meteor._debug can be called in many ways
  if(message && stack === undefined) {
    stack = getStackFromMessage(message);
    message = firstLine(message);
  }

  var now = (new Date().getTime());
  Kadira.errors.sendError({
    appId : Kadira.options.appId,
    name : message,
    type : 'client',
    startTime : now,
    subType : 'meteor._debug',
    info : getBrowserInfo(),
    stacks : JSON.stringify([{at: now, events: [], stack: stack}]),
  });

  return originalMeteorDebug.apply(this, arguments);
};

var stackRegex = /^\s+at\s.+$/gm;
function getStackFromMessage (message) {
  // add empty string to add the empty line at start
  var stack = [''];
  var match;
  while(match = stackRegex.exec(message)) {
    stack.push(match[0]);
  }
  return stack.join('\n');
}

function firstLine (message) {
  return message.split('\n')[0];
}

Ok, for anyone interested, I think I have all client side errors now. If you’re using meteorhacks:kadira you’ll have to disable to the wrapping of the window.onerror and also Meteor._debug it does (in your local clone). window.onerror is then handled automatically by Raven and you can plug into Meteor._debug like this. Just call this function from your client startup. I left the original Kadira comments/issues in there.

import ravenClient from 'raven-js';

export const wrapMeteorDebug = function()  {

    let originalMeteorDebug = Meteor._debug;
    Meteor._debug = function(m, s) {
        // We need to asign variables like this. Otherwise, 
        // we can't see proper error messages.
        // See: https://github.com/meteorhacks/kadira/issues/193
        var message = m;
        var stack = s;
        
        // We hate Meteor._debug (no single usage pattern)
        if(message instanceof Error) {
            ravenClient.captureException(message);
        } else if(typeof message === 'string') { 
            let extra = {level: "error"};
            //meteor._debug never seems to receive a stack here but just incase let's add it as context.
            if(stack) {
                extra.stack = stack;
            } else {
                //otherwise let's generate a stack trace
                extra.stacktrace = true;
            }
            ravenClient.captureMessage(message, extra);
        }
        return originalMeteorDebug.apply(this, arguments);
    };
}
5 Likes

It can be usefull to do a proper meteor package to configure correctly Sentry with actual version of Meteor, isn’t it ? I had a look at https://github.com/deepwell/meteor-raven but it does not suit Meteor actual best parctices (es6…)

Unfortunately, I do not have time to dive in such thing actually…

That package is really out of date and does very little unfortunately.

For a really useful solution I think the best bet would be to fork meteorhacks:kadira and replace all the calls to the kadira server with ones to the sentry one. We could also replace all the trace calls with the equivalent to add sentry breadcrumbs which would be really cool.

I had started by just ripping pieces out of kadira as I needed them but I soon found that kadira was doing lots of other necessary things that I would need - the main example being accessing things like Meteor.userId from outside fibers. Kadira solves this by attaching the info when publications or methods are run and then it can use it internally when an error occurs.

So yeah, meteorhacks:kadira would be the best starting point I believe.

The best would be to refactor in a way to be able to choose our monitoring system (Sentry, AppDynamics, New Relic…)

Yeah, I totally agree.

Wouldn’t it be lovely if this was something Meteor provided an easy way to hook into.

Now that Kadira APM is open-sourced, could it be worth taking another shot at this?

3 Likes

The post by @marklynch works beautifully!

1 Like

I’m glad to hear that @batist , but I don’t understand @marklynch’s instructions. Could you explain them step by step?

Sure:

Step 1:
Add the following to your client: (In my case /imports/startup/client/index.js)

Meteor.startup(function () {
    let sentryURL = 'https://' + Meteor.settings.public.sentryPublicKey + '@sentry.io/' + Meteor.settings.public.sentryAppId;
    let settings = {
        release: commitInfo.appHash, // In my case this is the commit hash - but this can be a random string
        environment: Meteor.settings.public.environment, // your environment prod / test / staging ... can also be a random string
        autoBreadcrumbs: {
            http: true // This is cool, auto clicks / navigations / etc.
        },
        shouldSendCallback: function(data) {
            return commitInfo.appHash !== 'dev'; // here you can decide if you want to send the errors to Sentry (in my case if the appHash equals 'dev', I don't want to send the errors.
        }
    };

    ravenClient.config(sentryURL, settings).install();

    let originalMeteorDebug = Meteor._debug;
    Meteor._debug = function(m, s) {
        // We need to asign variables like this. Otherwise,
        // we can't see proper error messages.
        // See: https://github.com/meteorhacks/kadira/issues/193
        var message = m;
        var stack = s;

        // We hate Meteor._debug (no single usage pattern)
        if(message instanceof Error) {
            ravenClient.captureException(message);
        } else if(typeof message === 'string') {
            let extra = {level: "error"};
            //meteor._debug never seems to receive a stack here but just incase let's add it as context.
            if(stack) {
                extra.stack = stack;
            } else {
                //otherwise let's generate a stack trace
                extra.stacktrace = true;
            }
            ravenClient.captureMessage(message, extra);
        }
        return originalMeteorDebug.apply(this, arguments);
    };
});

Step 2 (optional):
Attach the account onLogin & onLogout to also track the user info. (add this in the same file)

Accounts.onLogin(function() {
    ravenClient.setUserContext({
        email: Meteor.user().emails[0].address,
        id: Meteor.userId()
    });
});

Accounts.onLogout(function() {
    ravenClient.setUserContext();
});

Hope this helps!

5 Likes

Thanks a thousand times, @batist. I will attempt this soon.

Could you also explain what to do based on the quote below?

Is it enough to simply run meteor remove meteorhacks:kadira?

What do I do about Meteor.debug?

No idea, I did not remove it yet…

Thanks for this. We are trying it out. One surprise was how enabling autoBreadcrumbs hides the file and line number for console.logs saying printer.js:170 instead of the actual file and line that did the console.log.

I had to alter the line below:

extra.stack = stack;

would result in an Invalid parameter message at sentry. It should become which will actually add the stacktrace for those events.

extra.stacktrace = stack;

Otherwise I received all template helper issues as one error in sentry without stacktrace.