Add sentry tracing (#4279)

* Add sentry tracign

* Improve Sentry loggin
This commit is contained in:
Félix Malfait
2024-03-04 16:31:15 +01:00
committed by GitHub
parent 63d403454c
commit 6d70540cdc
11 changed files with 113 additions and 31 deletions

View File

@ -3,7 +3,7 @@ import { gql } from '@apollo/client';
import { responseData as person } from './useUpdateOneRecord';
export const query = gql`
query FindOneperson($objectRecordId: UUID!) {
query FindOnePerson($objectRecordId: UUID!) {
person(filter: { id: { eq: $objectRecordId } }) {
id
opportunities {

View File

@ -2,6 +2,7 @@ import { gql } from '@apollo/client';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { capitalize } from '~/utils/string/capitalize';
export const useGenerateFindOneRecordQuery = () => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
@ -14,7 +15,9 @@ export const useGenerateFindOneRecordQuery = () => {
depth?: number;
}) => {
return gql`
query FindOne${objectMetadataItem.nameSingular}($objectRecordId: UUID!) {
query FindOne${capitalize(
objectMetadataItem.nameSingular,
)}($objectRecordId: UUID!) {
${objectMetadataItem.nameSingular}(filter: {
id: {
eq: $objectRecordId

View File

@ -26,7 +26,7 @@ const meta: Meta<PageDecoratorArgs> = {
parameters: {
msw: {
handlers: [
graphql.query('FindOneperson', () => {
graphql.query('FindOnePerson', () => {
return HttpResponse.json({
data: {
person: mockedPeopleData[0],

View File

@ -21,7 +21,7 @@ const meta: Meta<PageDecoratorArgs> = {
layout: 'fullscreen',
msw: {
handlers: [
graphql.query('FindOnemessageChannel', () => {
graphql.query('FindOneMessageChannel', () => {
return HttpResponse.json({
data: {
messageChannel: {

View File

@ -23,7 +23,7 @@ const meta: Meta<PageDecoratorArgs> = {
msw: {
handlers: [
...graphqlMocks.handlers,
graphql.query('FindOneapiKey', () => {
graphql.query('FindOneApiKey', () => {
return HttpResponse.json({
data: {
apiKey: {

View File

@ -20,7 +20,7 @@ const meta: Meta<PageDecoratorArgs> = {
parameters: {
msw: {
handlers: [
graphql.query('FindOnewebhook', () => {
graphql.query('FindOneWebhook', () => {
return HttpResponse.json({
data: {
webhook: {

View File

@ -10,6 +10,7 @@ import { GraphQLSchema, GraphQLError } from 'graphql';
import GraphQLJSON from 'graphql-type-json';
import { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken';
import { GraphQLSchemaWithContext, YogaInitialContext } from 'graphql-yoga';
import * as Sentry from '@sentry/node';
import { TokenService } from 'src/core/auth/services/token.service';
import { CoreModule } from 'src/core/core.module';
@ -23,6 +24,7 @@ import { useExceptionHandler } from 'src/integrations/exception-handler/hooks/us
import { User } from 'src/core/user/user.entity';
import { useThrottler } from 'src/integrations/throttler/hooks/use-throttler';
import { JwtData } from 'src/core/auth/types/jwt-data.type';
import { useSentryTracing } from 'src/integrations/tracing/useSentryTracing';
import { CreateContextFactory } from './factories/create-context.factory';
@ -45,12 +47,30 @@ export class GraphQLConfigService
createGqlOptions(): YogaDriverConfig {
const isDebugMode = this.environmentService.isDebugMode();
const plugins = [
useThrottler({
ttl: this.environmentService.getApiRateLimitingTtl(),
limit: this.environmentService.getApiRateLimitingLimit(),
identifyFn: (context) => {
return context.user?.id ?? context.req.ip ?? 'anonymous';
},
}),
useExceptionHandler({
exceptionHandlerService: this.exceptionHandlerService,
}),
];
if (Sentry.isInitialized()) {
plugins.push(useSentryTracing());
}
const config: YogaDriverConfig = {
context: (context) => this.createContextFactory.create(context),
autoSchemaFile: true,
include: [CoreModule],
conditionalSchema: async (context) => {
let user: User | undefined;
let workspace: Workspace | undefined;
try {
if (!this.tokenService.isTokenPresent(context.req)) {
@ -60,6 +80,7 @@ export class GraphQLConfigService
const data = await this.tokenService.validateToken(context.req);
user = data.user;
workspace = data.workspace;
return await this.createSchema(context, data);
} catch (error) {
@ -95,24 +116,17 @@ export class GraphQLConfigService
? {
id: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
workspaceId: workspace?.id,
workspaceDisplayName: workspace?.displayName,
}
: undefined,
);
}
},
resolvers: { JSON: GraphQLJSON },
plugins: [
useThrottler({
ttl: this.environmentService.getApiRateLimitingTtl(),
limit: this.environmentService.getApiRateLimitingLimit(),
identifyFn: (context) => {
return context.user?.id ?? context.req.ip ?? 'anonymous';
},
}),
useExceptionHandler({
exceptionHandlerService: this.exceptionHandlerService,
}),
],
plugins: plugins,
};
if (isDebugMode) {

View File

@ -16,18 +16,14 @@ export class ExceptionHandlerSentryDriver
Sentry.init({
dsn: options.dsn,
integrations: [
// enable HTTP calls tracing
new Sentry.Integrations.Http({ tracing: true }),
// enable Express.js middleware tracing
new Sentry.Integrations.Express({ app: options.serverInstance }),
new Sentry.Integrations.GraphQL(),
new Sentry.Integrations.Postgres({
usePgNative: true,
}),
new Sentry.Integrations.Postgres(),
new ProfilingIntegration(),
],
tracesSampleRate: 1.0,
profilesSampleRate: 1.0,
tracesSampleRate: 1,
profilesSampleRate: 0.05,
environment: options.debug ? 'development' : 'production',
debug: options.debug,
});
@ -52,9 +48,11 @@ export class ExceptionHandlerSentryDriver
if (options?.user) {
scope.setUser({
id: options.user.id,
ip_address: options.user.ipAddress,
email: options.user.email,
username: options.user.username,
firstName: options.user.firstName,
lastName: options.user.lastName,
workspaceId: options.user.workspaceId,
workspaceDisplayName: options.user.workspaceDisplayName,
});
}
@ -98,9 +96,11 @@ export class ExceptionHandlerSentryDriver
if (user) {
scope.setUser({
id: user.id,
ip_address: user.ipAddress,
email: user.email,
username: user.username,
firstName: user.firstName,
lastName: user.lastName,
workspaceId: user.workspaceId,
workspaceDisplayName: user.workspaceDisplayName,
});
}

View File

@ -1,6 +1,8 @@
export interface ExceptionHandlerUser {
id?: string;
ipAddress?: string;
email?: string;
username?: string;
firstName?: string;
lastName?: string;
workspaceId?: string;
workspaceDisplayName?: string;
}

View File

@ -0,0 +1,57 @@
import * as Sentry from '@sentry/node';
import {
handleStreamOrSingleExecutionResult,
Plugin,
getDocumentString,
} from '@envelop/core';
import { OperationDefinitionNode, Kind, print } from 'graphql';
import { GraphQLContext } from 'src/graphql-config/graphql-config.service';
export const useSentryTracing = <
PluginContext extends GraphQLContext,
>(): Plugin<PluginContext> => {
return {
onExecute({ args }) {
const transactionName = args.operationName || 'Anonymous Operation';
const rootOperation = args.document.definitions.find(
(o) => o.kind === Kind.OPERATION_DEFINITION,
) as OperationDefinitionNode;
const operationType = rootOperation.operation;
const user = args.contextValue.user;
const workspace = args.contextValue.workspace;
const document = getDocumentString(args.document, print);
Sentry.setTags({
operationName: transactionName,
operation: operationType,
});
const scope = Sentry.getCurrentScope();
scope.setTransactionName(transactionName);
if (user) {
scope.setUser({
id: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
workspaceId: workspace?.id,
workspaceDisplayName: workspace?.displayName,
});
}
if (document) {
scope.setExtra('document', document);
}
return {
onExecuteDone(payload) {
return handleStreamOrSingleExecutionResult(payload, () => {});
},
};
},
};
};

View File

@ -2,6 +2,7 @@ import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { NestExpressApplication } from '@nestjs/platform-express';
import * as Sentry from '@sentry/node';
import { graphqlUploadExpress } from 'graphql-upload';
import bytes from 'bytes';
import { useContainer } from 'class-validator';
@ -27,6 +28,11 @@ const bootstrap = async () => {
// Use our logger
app.useLogger(logger);
if (Sentry.isInitialized()) {
app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.tracingHandler());
}
// Apply validation pipes globally
app.useGlobalPipes(
new ValidationPipe({