Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce Sentry #994

Merged
merged 1 commit into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
"@graphql-tools/graphql-file-loader": "^7.3.4",
"@graphql-tools/load": "^7.5.2",
"@rudderstack/rudder-sdk-node": "^1.1.2",
"@sentry/node": "^7.42.0",
"@sentry/tracing": "^7.42.0",
"@types/luxon": "^2.3.1",
"ajv-formats": "^2.1.1",
"axios": "0.24.0",
Expand Down
13 changes: 11 additions & 2 deletions packages/backend/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import createError from 'http-errors';
import express from 'express';
import appConfig from './config/app';
import cors from 'cors';

import { IRequest } from '@automatisch/types';
import appConfig from './config/app';
import corsOptions from './config/cors-options';
import morgan from './helpers/morgan';
import * as Sentry from './helpers/sentry.ee';
import appAssetsHandler from './helpers/app-assets-handler';
import webUIHandler from './helpers/web-ui-handler';
import errorHandler from './helpers/error-handler';
Expand All @@ -14,12 +17,16 @@ import {
} from './helpers/create-bull-board-handler';
import injectBullBoardHandler from './helpers/inject-bull-board-handler';
import router from './routes';
import { IRequest } from '@automatisch/types';

createBullBoardHandler(serverAdapter);

const app = express();

Sentry.init(app);

Sentry.attachRequestHandler(app);
Sentry.attachTracingHandler(app);

injectBullBoardHandler(app, serverAdapter);

appAssetsHandler(app);
Expand Down Expand Up @@ -50,6 +57,8 @@ app.use(function (req, res, next) {
next(createError(404));
});

Sentry.attachErrorHandler(app);

app.use(errorHandler);

export default app;
2 changes: 2 additions & 0 deletions packages/backend/src/config/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type AppConfig = {
stripeStarterPriceKey: string;
stripeGrowthPriceKey: string;
licenseKey: string;
sentryDsn: string;
};

const host = process.env.HOST || 'localhost';
Expand Down Expand Up @@ -115,6 +116,7 @@ const appConfig: AppConfig = {
stripeStarterPriceKey: process.env.STRIPE_STARTER_PRICE_KEY,
stripeGrowthPriceKey: process.env.STRIPE_GROWTH_PRICE_KEY,
licenseKey: process.env.LICENSE_KEY,
sentryDsn: process.env.SENTRY_DSN,
};

if (!appConfig.encryptionKey) {
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/controllers/stripe/webhooks.ee.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Response } from 'express';
import { IRequest } from '@automatisch/types';

import * as Sentry from '../../helpers/sentry.ee';
import Billing from '../../helpers/billing/index.ee';
import appConfig from '../../config/app';
import logger from '../../helpers/logger';
Expand All @@ -18,6 +20,8 @@ export default async (request: IRequest, response: Response) => {
return response.sendStatus(200);
} catch (error) {
logger.error(`Webhook Error: ${error.message}`);

Sentry.captureException(error);
return response.sendStatus(400);
}
};
1 change: 1 addition & 0 deletions packages/backend/src/errors/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { IJSONObject } from '@automatisch/types';

export default class BaseError extends Error {
details = {};
statusCode?: number;

constructor(error?: string | IJSONObject) {
let computedError: Record<string, unknown>;
Expand Down
9 changes: 9 additions & 0 deletions packages/backend/src/errors/quote-exceeded.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import BaseError from './base';

export default class QuotaExceededError extends BaseError {
constructor(error = 'The allowed task quota has been exhausted!') {
super(error);

this.statusCode = 422;
}
}
11 changes: 5 additions & 6 deletions packages/backend/src/helpers/error-handler.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { Request, Response } from 'express';
import { NextFunction, Request, Response } from 'express';
import logger from './logger';

type Error = {
message: string;
};
import BaseError from '../errors/base';

const errorHandler = (err: Error, req: Request, res: Response): void => {
// Do not remove `next` argument as the function signature will not fit for an error handler middleware
const errorHandler = (err: BaseError, req: Request, res: Response, next: NextFunction): void => {
if (err.message === 'Not Found') {
res.status(404).end();
} else {
logger.error(err.message);
res.status(500).end();
res.status(err.statusCode || 500).send(err.message);
}
};

Expand Down
11 changes: 11 additions & 0 deletions packages/backend/src/helpers/graphql-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { loadSchemaSync } from '@graphql-tools/load';
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
import { addResolversToSchema } from '@graphql-tools/schema';
import { applyMiddleware } from 'graphql-middleware';

import logger from '../helpers/logger';
import authentication from '../helpers/authentication';
import * as Sentry from '../helpers/sentry.ee';
import resolvers from '../graphql/resolvers';
import HttpError from '../errors/http';

Expand All @@ -28,6 +30,15 @@ const graphQLInstance = graphqlHTTP({
delete (error.originalError as HttpError).response;
}

Sentry.captureException(error, {
tags: { graphql: true },
extra: {
source: error.source?.body,
positions: error.positions,
path: error.path
}
})

return error;
},
});
Expand Down
51 changes: 51 additions & 0 deletions packages/backend/src/helpers/sentry.ee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Express } from 'express';
import * as Sentry from '@sentry/node';
import type { CaptureContext } from '@sentry/types';
import * as Tracing from '@sentry/tracing';

import appConfig from '../config/app';

export function init(app?: Express) {
if (!appConfig.isCloud) return;

return Sentry.init({
enabled: !!appConfig.sentryDsn,
dsn: appConfig.sentryDsn,
integrations: [
app && new Sentry.Integrations.Http({ tracing: true }),
app && new Tracing.Integrations.Express({ app }),
app && new Tracing.Integrations.GraphQL(),
].filter(Boolean),
tracesSampleRate: 1.0,
});
}


export function attachRequestHandler(app: Express) {
if (!appConfig.isCloud) return;

app.use(Sentry.Handlers.requestHandler());
}

export function attachTracingHandler(app: Express) {
if (!appConfig.isCloud) return;

app.use(Sentry.Handlers.tracingHandler());
}

export function attachErrorHandler(app: Express) {
if (!appConfig.isCloud) return;

app.use(Sentry.Handlers.errorHandler({
shouldHandleError() {
// TODO: narrow down the captured errors in time as we receive samples
return true;
}
}));
}

export function captureException(exception: any, captureContext?: CaptureContext) {
if (!appConfig.isCloud) return;

return Sentry.captureException(exception, captureContext);
}
3 changes: 2 additions & 1 deletion packages/backend/src/models/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Step from './step';
import User from './user';
import Execution from './execution';
import Telemetry from '../helpers/telemetry';
import QuotaExceededError from '../errors/quote-exceeded';

class Flow extends Base {
id!: string;
Expand Down Expand Up @@ -152,7 +153,7 @@ class Flow extends Base {
const hasExceeded = await this.checkIfQuotaExceeded();

if (hasExceeded) {
throw new Error('The allowed task quota has been exhausted!');
throw new QuotaExceededError();
}

return this;
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/routes/webhooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const exposeError = (handler: RequestHandler) => async (req: IRequest, res: Resp
try {
await handler(req, res, next);
} catch (err) {
res.status(422).send(err);
next(err);
}
}

Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/worker.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import * as Sentry from './helpers/sentry.ee';

Sentry.init();

import './config/orm';
import './helpers/check-worker-readiness';
import './workers/flow';
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/workers/action.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Worker } from 'bullmq';

import * as Sentry from '../helpers/sentry.ee';
import redisConfig from '../config/redis';
import logger from '../helpers/logger';
import Step from '../models/step';
Expand Down Expand Up @@ -65,6 +67,12 @@ worker.on('failed', (job, err) => {
logger.info(
`JOB ID: ${job.id} - FLOW ID: ${job.data.flowId} has failed to start with ${err.message}`
);

Sentry.captureException(err, {
extra: {
jobId: job.id,
}
});
});

process.on('SIGTERM', async () => {
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/workers/delete-user.ee.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Worker } from 'bullmq';

import * as Sentry from '../helpers/sentry.ee';
import redisConfig from '../config/redis';
import logger from '../helpers/logger';
import User from '../models/user';
Expand Down Expand Up @@ -37,6 +39,12 @@ worker.on('failed', (job, err) => {
logger.info(
`JOB ID: ${job.id} - The user with the ID of '${job.data.id}' has failed to be deleted! ${err.message}`
);

Sentry.captureException(err, {
extra: {
jobId: job.id,
}
});
});

process.on('SIGTERM', async () => {
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/workers/email.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Worker } from 'bullmq';

import * as Sentry from '../helpers/sentry.ee';
import redisConfig from '../config/redis';
import logger from '../helpers/logger';
import mailer from '../helpers/mailer.ee';
Expand Down Expand Up @@ -30,6 +32,12 @@ worker.on('failed', (job, err) => {
logger.info(
`JOB ID: ${job.id} - ${job.data.subject} email to ${job.data.email} has failed to send with ${err.message}`
);

Sentry.captureException(err, {
extra: {
jobId: job.id,
}
});
});

process.on('SIGTERM', async () => {
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/workers/flow.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Worker } from 'bullmq';

import * as Sentry from '../helpers/sentry.ee';
import redisConfig from '../config/redis';
import logger from '../helpers/logger';
import triggerQueue from '../queues/trigger';
Expand Down Expand Up @@ -65,6 +67,12 @@ worker.on('failed', (job, err) => {
logger.info(
`JOB ID: ${job.id} - FLOW ID: ${job.data.flowId} has failed to start with ${err.message}`
);

Sentry.captureException(err, {
extra: {
jobId: job.id,
}
});
});

process.on('SIGTERM', async () => {
Expand Down
10 changes: 9 additions & 1 deletion packages/backend/src/workers/trigger.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Worker } from 'bullmq';

import { IJSONObject, ITriggerItem } from '@automatisch/types';
import * as Sentry from '../helpers/sentry.ee';
import redisConfig from '../config/redis';
import logger from '../helpers/logger';
import { IJSONObject, ITriggerItem } from '@automatisch/types';
import actionQueue from '../queues/action';
import Step from '../models/step';
import { processTrigger } from '../services/trigger';
Expand Down Expand Up @@ -51,6 +53,12 @@ worker.on('failed', (job, err) => {
logger.info(
`JOB ID: ${job.id} - FLOW ID: ${job.data.flowId} has failed to start with ${err.message}`
);

Sentry.captureException(err, {
extra: {
jobId: job.id,
}
});
});

process.on('SIGTERM', async () => {
Expand Down
1 change: 1 addition & 0 deletions packages/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,5 +333,6 @@ declare module 'axios' {

export interface IRequest extends Request {
rawBody?: Buffer;
currentUser?: IUser;
}