Skip to content
This repository has been archived by the owner on Jan 4, 2021. It is now read-only.

Self-managed Sentry scope support

License

Notifications You must be signed in to change notification settings

someimportantcompany/vedette

Repository files navigation

vedette

npm actions


Hi there πŸ‘‹

As of November 2020, Sentry supports adding specific information to the context on each captured error, so this package has been archived & deprecated.


Self-managed scope management for @sentry/node 🦾

Vedette: A mounted sentry positioned beyond an army's outposts to observe the movements of the enemy.

const Sentry = require('@sentry/node');
const Vedette = require('vedette');

Sentry.init();

const ved = new Vedette();

ved.setTag('hostname', 'localhost');
ved.setTags({ method: 'POST', endpoint: '/api' });

ved.setUser({ id: '11211', ip_address: '127.0.0.1' });

ved.setExtra('requestID', 'f05991fd-8719-4dd5-8e2e-a70c0ef0efbe');
ved.setExtras({ code: 'DROIDS_NOT_FOUND' });

ved.captureException(new Error('These are not the droids you are looking for, move along'));
ved.captureMessage('These are not the droids you are looking for, move along');

Vedette.captureException(new Error('These are not the droids you are looking for, move along'), {
  user: { ip_address: '127.0.0.1' },
  level: 'fatal',
});
Vedette.captureMessage('These are not the droids you are looking for, move along', {
  user: { id: '11211' },
  level: 'warning',
});

Installation

$ npm install --save git+https://github.com/someimportantcompany/vedette#1.1.3

Why?

Currently, @sentry/node doesn't appear to you to pass a scope as an argument, instead continuing to use the deprecated domains module. They have their (good) reasons, however they still could allow scopes to be portable for those that don't want to use domains in their code.

This library attempts to solve this by wrapping around the default scope functions & calling withScope before captureException/captureMessage to include all your locally-set properties. It also offers a couple of static methods to send errors/messages in one invocation.

API

Instance Methods

const ved = new Vedette();

ved.addBreadcrumb

ved.addBreadcrumb({
  level: 'info',
  message: 'These are not the droids you are looking for, move along',
});

Passes breadcrumbs to Sentry.addBreadcrumb.

ved.clearBreadcrumbs

ved.clearBreadcrumbs();

Clears all the breadcrumbs stored so far.

ved.setTag

ved.setTag('key', 'value');

Set one tag.

ved.setTags

ved.setTags({
  key1: 'value1',
  key2: 'value2',
});

Set multiple tags.

ved.setUser

ved.setUser({
  id: '11211',
  ip_address: '127.0.0.1',
});

Set the user properties.

ved.setExtra

ved.setExtra('key', 'value');

Set one extra property.

ved.setExtras

ved.setExtras({
  key1: 'value1',
  key2: 'value2',
});

ved.setLevel

ved.setLevel('warning');

Set a level for all particular errors & messages. Less useful in this form, more useful with Vedette.captureException & Vedette.captureMessage.

Levels directly from @sentry/types:

debug
log
info
warning
error
fatal
critical

ved.populateSentryScope

ved.populateSentryScope(scope);

Transfer the breadcrumbs, tags, user & extra data to the Sentry scope.

ved.captureException

ved.captureException(new Error('These are not the droids you are looking for, move along'));

ved.captureException(new Error('These are not the droids you are looking for, move along'), {
  // Optional tags
  tags: { tag1: 'value1' },
  // Optional user
  user: { id: '11211' },
  // Optional extras
  extra: { extra2: 'value2' },
  // Optional level
  level: 'fatal',
});

Capture an exception, transferring the breadcrumbs, tags, user & extra data to the Sentry scope before sending.

ved.captureMessage

ved.captureMessage('These are not the droids you are looking for, move along');

ved.captureMessage('These are not the droids you are looking for, move along', {
  // Optional tags
  tags: { tag1: 'value1' },
  // Optional user
  user: { id: '11211' },
  // Optional extras
  extra: { extra2: 'value2' },
  // Optional level
  level: 'fatal',
});

Capture a message, transferring the breadcrumbs, tags, user & extra data to the Sentry scope before sending.

Static Methods

For convenience, there are some static methods to complete common scenarios.

Vedette.captureException

Vedette.captureException(new Error('These are not the droids you are looking for, move along'));

Vedette.captureException(new Error('These are not the droids you are looking for, move along'), {
  // Optional tags
  tags: { tag1: 'value1' },
  // Optional user
  user: { id: '11211' },
  // Optional extras
  extra: { extra2: 'value2' },
  // Optional level
  level: 'fatal',
});

Capture an exception, optionally throwing some tags, user & extra data into the mix in one function call.

Vedette.captureMessage

Vedette.captureMessage('These are not the droids you are looking for, move along');

Vedette.captureMessage('These are not the droids you are looking for, move along', {
  // Optional tags
  tags: { tag1: 'value1' },
  // Optional user
  user: { id: '11211' },
  // Optional extras
  extra: { extra2: 'value2' },
  // Optional level
  level: 'warning',
});

Capture a message, optionally throwing some tags, user & extra data into the mix in one function call.

Examples

Express Example

const assert = require('http-assert');
const bodyParser = require('body-parser');
const express = require('express');
const Sequelize = require('sequelize');
const Sentry = require('@sentry/node');
const Vedette = require('vedette');

Sentry.init({
  environment: process.env.NODE_ENV || 'development',
});

const app = express();
const sequel = new Sequelize('project', 'root', 'password', {
  host: '127.0.0.1',
  dialect: 'mysql',
});
const users = sequel.import('./UserModel');

app.use(bodyParser.json({ limit: '1mb' }));

app.use((req, res, next) => {
  req.ved = new Vedette();
  next();
});

app.get('/droids', function (req, res, next) {
  throw new Error('These are not the droids you are looking for, move along');
});

app.use((err, req, res, next) => {
  req.ved = req.ved || new Vedette();

  req.ved.setTags({
    method: req.method,
    path: req.route.path,
  });

  req.ved.captureException(err);

  res.status(err.status || 500).json(`${err}`);
})

app.listen(8080, () => console.log('πŸš€  Server ready at http://localhost:3000/'));

GraphQL Example

const Sentry = require('@sentry/node');
const Vedette = require('vedette');
const { ApolloServer, gql } = require('apollo-server');

Sentry.init({
  environment: process.env.NODE_ENV || 'development',
});

const typeDefs = gql`
  type Book {
    title: String
    author: String
  }

  type Query {
    books: [ Book ]
  }
`;

const resolvers = {
  Query: {
    books(parent, args, ctx) {
      ctx.ved.addBreadcrumb({ message: 'Hit the books query' });

      throw new Error('These are not the droids you are looking for, move along');

      return [
        {
          title: 'Harry Potter and the Chamber of Secrets',
          author: 'J.K. Rowling',
        },
        {
          title: 'Jurassic Park',
          author: 'Michael Crichton',
        },
      ];
    },
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context() {
    return {
      ved: new Vedette(),
    };
  },
  formatResponse(result, info) {
    const { context: ctx } = info;

    if (result.errors) {
      result.errors = result.errors.forEach(err => {
        if (err.originalError) {
          (ctx.ved || Vedette).captureException(err.originalError);
        } else {
          (ctx.ved || Vedette).captureException(err);
        }
      });
    }

    return result;
  },
});

server.listen().then(({ url }) => console.log(`πŸš€  Server ready at ${url}`));

AWS Lambda Example

const Sentry = require('@sentry/node');
const Vedette = require('vedette');

Sentry.init({
  environment: process.env.NODE_ENV || 'development',
});

module.exports.handler1 = function handler1(event, context, callback) {
  const ved = new Vedette();

  try {
    throw new Error('These are not the droids you are looking for, move along');
    callback();
  } catch (err) {
    // Capture the exception
    ved.captureException(err);
    // Push the exception to Sentry before Lambda finishes
    await Sentry.flush(2000);
    // Trigger the callback
    callback(err);
  }
};

module.exports.handler2 = function handler2(event, context, callback) {
  try {
    throw new Error('These are not the droids you are looking for, move along');
    callback();
  } catch (err) {
    // Capture the exception
    ved.captureException(err);
    // Push the exception to Sentry before Lambda finishes
    await Sentry.flush(2000);
    // Trigger the callback
    callback(err);
  }
};

Vue.js (2.x) Example

import Vue from 'vue';
import * as Sentry from '@sentry/browser';
import { Vue as VueIntegration } from '@sentry/integrations';
import Vedette from 'vedette';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  integrations: [ new VueIntegration({ Vue, attachProps: true }) ],
});

const app = new Vue({
  computed: {
    ved: () => new Vedette(),
  },
  mounted() {
    this.ved.setTag('origin', window.location.origin);
    this.ved.setUser({ id: '11211' });
  },
  methods: {
    sendException() {
      const err = new Error('These are not the droids you are looking for, move along');
      this.ved.captureException(err);
    },
  },
});

app.$mount('#app');