forked from vendure-ecommerce/vendure
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(sentry-plugin): Add sentry plugin
- Loading branch information
1 parent
2254576
commit cde0a46
Showing
19 changed files
with
486 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
yarn-error.log | ||
lib | ||
e2e/__data__/*.sqlite |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Vendure Sentry Plugin | ||
|
||
Integrates your Vendure server with the [Sentry](https://sentry.io/) application monitoring service. | ||
|
||
`npm install @vendure/sentry-plugin` | ||
|
||
For documentation, see [docs.vendure.io/typescript-api/core-plugins/sentry-plugin/](https://docs.vendure.io/typescript-api/core-plugins/sentry-plugin/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export * from './src/sentry-plugin'; | ||
export * from './src/sentry.service'; | ||
export * from './src/types'; | ||
export * from './src/constants'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{ | ||
"name": "@vendure/sentry-plugin", | ||
"version": "2.1.4", | ||
"license": "MIT", | ||
"main": "lib/index.js", | ||
"types": "lib/index.d.ts", | ||
"files": [ | ||
"lib/**/*" | ||
], | ||
"scripts": { | ||
"watch": "tsc -p ./tsconfig.build.json --watch", | ||
"build": "rimraf lib && tsc -p ./tsconfig.build.json", | ||
"lint": "eslint --fix ." | ||
}, | ||
"homepage": "https://www.vendure.io", | ||
"funding": "https://github.com/sponsors/michaelbromley", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"peerDependencies": { | ||
"@sentry/node": "^7.85.0" | ||
}, | ||
"devDependencies": { | ||
"@vendure/common": "^2.1.4", | ||
"@vendure/core": "^2.1.4", | ||
"@sentry/node": "^7.85.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { Args, Mutation, Resolver } from '@nestjs/graphql'; | ||
import { Allow, Permission, UserInputError } from '@vendure/core'; | ||
|
||
import { SentryService } from '../sentry.service'; | ||
import { ErrorTestService } from './error-test.service'; | ||
|
||
declare const a: number; | ||
|
||
@Resolver() | ||
export class SentryAdminTestResolver { | ||
constructor(private sentryService: SentryService, private errorTestService: ErrorTestService) {} | ||
|
||
@Allow(Permission.SuperAdmin) | ||
@Mutation() | ||
async createTestError(@Args() args: { errorType: string }) { | ||
switch (args.errorType) { | ||
case 'UNCAUGHT_ERROR': | ||
return a / 10; | ||
case 'THROWN_ERROR': | ||
throw new UserInputError('SentryPlugin Test Error'); | ||
case 'CAPTURED_ERROR': | ||
this.sentryService.captureException(new Error('SentryPlugin Direct error')); | ||
return true; | ||
case 'CAPTURED_MESSAGE': | ||
this.sentryService.captureMessage('Captured message'); | ||
return true; | ||
case 'DATABASE_ERROR': | ||
await this.errorTestService.createDatabaseError(); | ||
return true; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import gql from 'graphql-tag'; | ||
|
||
export const testApiExtensions = gql` | ||
enum TestErrorType { | ||
UNCAUGHT_ERROR | ||
THROWN_ERROR | ||
CAPTURED_ERROR | ||
CAPTURED_MESSAGE | ||
DATABASE_ERROR | ||
} | ||
extend type Mutation { | ||
createTestError(errorType: TestErrorType!): Boolean | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { TransactionalConnection } from '@vendure/core'; | ||
|
||
@Injectable() | ||
export class ErrorTestService { | ||
constructor(private connection: TransactionalConnection) {} | ||
|
||
createDatabaseError() { | ||
return this.connection.rawConnection.query('SELECT * FROM non_existent_table'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export const SENTRY_PLUGIN_OPTIONS = 'SENTRY_PLUGIN_OPTIONS'; | ||
export const SENTRY_TRANSACTION_KEY = 'SENTRY_PLUGIN_TRANSACTION'; | ||
export const loggerCtx = 'SentryPlugin'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* eslint-disable @typescript-eslint/require-await */ | ||
import { | ||
ApolloServerPlugin, | ||
GraphQLRequestListener, | ||
GraphQLRequestContext, | ||
GraphQLRequestContextDidEncounterErrors, | ||
} from '@apollo/server'; | ||
import { Transaction, setContext } from '@sentry/node'; | ||
|
||
import { SENTRY_TRANSACTION_KEY } from './constants'; | ||
|
||
/** | ||
* Based on https://github.com/ntegral/nestjs-sentry/issues/97#issuecomment-1252446807 | ||
*/ | ||
export class SentryApolloPlugin implements ApolloServerPlugin { | ||
constructor(private options: { enableTracing: boolean }) {} | ||
|
||
async requestDidStart({ | ||
request, | ||
contextValue, | ||
}: GraphQLRequestContext<any>): Promise<GraphQLRequestListener<any>> { | ||
const { enableTracing } = this.options; | ||
const transaction: Transaction | undefined = contextValue.req[SENTRY_TRANSACTION_KEY]; | ||
if (request.operationName) { | ||
if (enableTracing) { | ||
// set the transaction Name if we have named queries | ||
transaction?.setName(request.operationName); | ||
} | ||
setContext('Graphql Request', { | ||
operation_name: request.operationName, | ||
variables: request.variables, | ||
}); | ||
} | ||
|
||
return { | ||
// hook for transaction finished | ||
async willSendResponse(context) { | ||
transaction?.finish(); | ||
}, | ||
async executionDidStart() { | ||
return { | ||
// hook for each new resolver | ||
willResolveField({ info }) { | ||
if (enableTracing) { | ||
const span = transaction?.startChild({ | ||
op: 'resolver', | ||
description: `${info.parentType.name}.${info.fieldName}`, | ||
}); | ||
return () => { | ||
span?.finish(); | ||
}; | ||
} | ||
}, | ||
}; | ||
}, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { Inject, Injectable, NestMiddleware } from '@nestjs/common'; | ||
import { Request, Response, NextFunction } from 'express'; | ||
|
||
import { SENTRY_PLUGIN_OPTIONS, SENTRY_TRANSACTION_KEY } from './constants'; | ||
import { SentryService } from './sentry.service'; | ||
import { SentryPluginOptions } from './types'; | ||
|
||
@Injectable() | ||
export class SentryContextMiddleware implements NestMiddleware { | ||
constructor( | ||
@Inject(SENTRY_PLUGIN_OPTIONS) private options: SentryPluginOptions, | ||
private sentryService: SentryService, | ||
) {} | ||
|
||
use(req: Request, res: Response, next: NextFunction) { | ||
if (this.options.enableTracing) { | ||
const transaction = this.sentryService.startTransaction({ | ||
op: 'resolver', | ||
name: `GraphQLTransaction`, | ||
}); | ||
req[SENTRY_TRANSACTION_KEY] = transaction; | ||
} | ||
next(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import { MiddlewareConsumer, NestModule } from '@nestjs/common'; | ||
import { APP_FILTER } from '@nestjs/core'; | ||
import { PluginCommonModule, VendurePlugin } from '@vendure/core'; | ||
|
||
import { SentryAdminTestResolver } from './api/admin-test.resolver'; | ||
import { testApiExtensions } from './api/api-extensions'; | ||
import { ErrorTestService } from './api/error-test.service'; | ||
import { SENTRY_PLUGIN_OPTIONS } from './constants'; | ||
import { SentryApolloPlugin } from './sentry-apollo-plugin'; | ||
import { SentryContextMiddleware } from './sentry-context.middleware'; | ||
import { SentryExceptionsFilter } from './sentry.filter'; | ||
import { SentryService } from './sentry.service'; | ||
import { SentryPluginOptions } from './types'; | ||
|
||
const SentryOptionsProvider = { | ||
provide: SENTRY_PLUGIN_OPTIONS, | ||
useFactory: () => SentryPlugin.options, | ||
}; | ||
|
||
/** | ||
* @description | ||
* This plugin integrates the [Sentry](https://sentry.io) error tracking & performance monitoring | ||
* service with your Vendure server. In addition to capturing errors, it also provides built-in | ||
* support for [tracing](https://docs.sentry.io/product/sentry-basics/concepts/tracing/) as well as | ||
* enriching your Sentry events with additional context about the request. | ||
* | ||
* ## Pre-requisites | ||
* | ||
* This plugin depends on access to Sentry, which can be self-hosted or used as a cloud service. | ||
* | ||
* If using the hosted SaaS option, you must have a Sentry account and a project set up ([sign up here](https://sentry.io/signup/)). When setting up your project, | ||
* select the "Node.js" platform and no framework. | ||
* | ||
* Once set up, you will be given a [Data Source Name (DSN)](https://docs.sentry.io/product/sentry-basics/concepts/dsn-explainer/) | ||
* which you will need to provide to the plugin. | ||
* | ||
* ## Installation | ||
* | ||
* Install this plugin as well as the `@sentry/node` package: | ||
* | ||
* ```sh | ||
* npm install --save @vendure/sentry-plugin @sentry/node | ||
* ``` | ||
* | ||
* ## Configuration | ||
* | ||
* Before using the plugin, you must configure it with the DSN provided by Sentry: | ||
* | ||
* ```ts | ||
* import { VendureConfig } from '\@vendure/core'; | ||
* import { SentryPlugin } from '\@vendure/sentry-plugin'; | ||
* | ||
* export const config: VendureConfig = { | ||
* // ... | ||
* plugins: [ | ||
* // ... | ||
* // highlight-start | ||
* SentryPlugin.init({ | ||
* dsn: process.env.SENTRY_DSN, | ||
* // Optional configuration | ||
* includeErrorTestMutation: true, | ||
* enableTracing: true, | ||
* // you can also pass in any of the options from @sentry/node | ||
* }), | ||
* // highlight-end | ||
* ], | ||
* }; | ||
*``` | ||
* | ||
* ## Tracing | ||
* | ||
* This plugin includes built-in support for [tracing](https://docs.sentry.io/product/sentry-basics/concepts/tracing/), which allows you to see the performance of your | ||
* GraphQL resolvers in the Sentry dashboard. To enable tracing, set the `enableTracing` option to `true` as shown above. | ||
* | ||
* ## Instrumenting your own code | ||
* | ||
* You may want to add your own custom spans to your code. To do so, you can use the `Sentry` object | ||
* just as you would in any Node application. For example: | ||
* | ||
* ```ts | ||
* import * as Sentry from "\@sentry/node"; | ||
* | ||
* export class MyService { | ||
* async myMethod() { | ||
* Sentry.setContext('My Custom Context,{ | ||
* key: 'value', | ||
* }); | ||
* } | ||
* } | ||
* ``` | ||
* | ||
* ## Error test mutation | ||
* | ||
* To test whether your Sentry configuration is working correctly, you can set the `includeErrorTestMutation` option to `true`. This will add a mutation to the Admin API | ||
* which will throw an error of the type specified in the `errorType` argument. For example: | ||
* | ||
* ```gql | ||
* mutation CreateTestError { | ||
* createTestError(errorType: DATABASE_ERROR) | ||
* } | ||
* ``` | ||
* | ||
* You should then be able to see the error in your Sentry dashboard (it may take a couple of minutes to appear). | ||
* | ||
* @docsCategory core plugins/SentryPlugin | ||
*/ | ||
@VendurePlugin({ | ||
imports: [PluginCommonModule], | ||
providers: [ | ||
SentryOptionsProvider, | ||
SentryService, | ||
ErrorTestService, | ||
{ | ||
provide: APP_FILTER, | ||
useClass: SentryExceptionsFilter, | ||
}, | ||
], | ||
configuration: config => { | ||
config.apiOptions.apolloServerPlugins.push( | ||
new SentryApolloPlugin({ | ||
enableTracing: !!SentryPlugin.options.enableTracing, | ||
}), | ||
); | ||
return config; | ||
}, | ||
adminApiExtensions: { | ||
schema: () => (SentryPlugin.options.includeErrorTestMutation ? testApiExtensions : undefined), | ||
resolvers: () => (SentryPlugin.options.includeErrorTestMutation ? [SentryAdminTestResolver] : []), | ||
}, | ||
exports: [SentryService], | ||
compatibility: '^2.0.0', | ||
}) | ||
export class SentryPlugin implements NestModule { | ||
static options: SentryPluginOptions = {} as any; | ||
|
||
configure(consumer: MiddlewareConsumer): any { | ||
consumer.apply(SentryContextMiddleware).forRoutes('*'); | ||
} | ||
|
||
static init(options: SentryPluginOptions) { | ||
this.options = options; | ||
return this; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import type { ArgumentsHost, ExceptionFilter } from '@nestjs/common'; | ||
import { Catch, ExecutionContext } from '@nestjs/common'; | ||
import { GqlContextType, GqlExecutionContext } from '@nestjs/graphql'; | ||
import { setContext } from '@sentry/node'; | ||
|
||
import { SentryService } from './sentry.service'; | ||
|
||
@Catch() | ||
export class SentryExceptionsFilter implements ExceptionFilter { | ||
constructor(private readonly sentryService: SentryService) {} | ||
|
||
catch(exception: Error, host: ArgumentsHost): void { | ||
if (host.getType<GqlContextType>() === 'graphql') { | ||
const gqlContext = GqlExecutionContext.create(host as ExecutionContext); | ||
const info = gqlContext.getInfo(); | ||
setContext('GraphQL Error Context', { | ||
fieldName: info.fieldName, | ||
path: info.path, | ||
}); | ||
} | ||
const variables = (exception as any).variables; | ||
if (variables) { | ||
setContext('GraphQL Error Variables', variables); | ||
} | ||
this.sentryService.captureException(exception); | ||
} | ||
} |
Oops, something went wrong.