Add sentry tracing (#4279)
* Add sentry tracign * Improve Sentry loggin
This commit is contained in:
@ -3,7 +3,7 @@ import { gql } from '@apollo/client';
|
|||||||
import { responseData as person } from './useUpdateOneRecord';
|
import { responseData as person } from './useUpdateOneRecord';
|
||||||
|
|
||||||
export const query = gql`
|
export const query = gql`
|
||||||
query FindOneperson($objectRecordId: UUID!) {
|
query FindOnePerson($objectRecordId: UUID!) {
|
||||||
person(filter: { id: { eq: $objectRecordId } }) {
|
person(filter: { id: { eq: $objectRecordId } }) {
|
||||||
id
|
id
|
||||||
opportunities {
|
opportunities {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { gql } from '@apollo/client';
|
|||||||
|
|
||||||
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
|
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { capitalize } from '~/utils/string/capitalize';
|
||||||
|
|
||||||
export const useGenerateFindOneRecordQuery = () => {
|
export const useGenerateFindOneRecordQuery = () => {
|
||||||
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
|
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
|
||||||
@ -14,7 +15,9 @@ export const useGenerateFindOneRecordQuery = () => {
|
|||||||
depth?: number;
|
depth?: number;
|
||||||
}) => {
|
}) => {
|
||||||
return gql`
|
return gql`
|
||||||
query FindOne${objectMetadataItem.nameSingular}($objectRecordId: UUID!) {
|
query FindOne${capitalize(
|
||||||
|
objectMetadataItem.nameSingular,
|
||||||
|
)}($objectRecordId: UUID!) {
|
||||||
${objectMetadataItem.nameSingular}(filter: {
|
${objectMetadataItem.nameSingular}(filter: {
|
||||||
id: {
|
id: {
|
||||||
eq: $objectRecordId
|
eq: $objectRecordId
|
||||||
|
|||||||
@ -26,7 +26,7 @@ const meta: Meta<PageDecoratorArgs> = {
|
|||||||
parameters: {
|
parameters: {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
graphql.query('FindOneperson', () => {
|
graphql.query('FindOnePerson', () => {
|
||||||
return HttpResponse.json({
|
return HttpResponse.json({
|
||||||
data: {
|
data: {
|
||||||
person: mockedPeopleData[0],
|
person: mockedPeopleData[0],
|
||||||
|
|||||||
@ -21,7 +21,7 @@ const meta: Meta<PageDecoratorArgs> = {
|
|||||||
layout: 'fullscreen',
|
layout: 'fullscreen',
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
graphql.query('FindOnemessageChannel', () => {
|
graphql.query('FindOneMessageChannel', () => {
|
||||||
return HttpResponse.json({
|
return HttpResponse.json({
|
||||||
data: {
|
data: {
|
||||||
messageChannel: {
|
messageChannel: {
|
||||||
|
|||||||
@ -23,7 +23,7 @@ const meta: Meta<PageDecoratorArgs> = {
|
|||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
...graphqlMocks.handlers,
|
...graphqlMocks.handlers,
|
||||||
graphql.query('FindOneapiKey', () => {
|
graphql.query('FindOneApiKey', () => {
|
||||||
return HttpResponse.json({
|
return HttpResponse.json({
|
||||||
data: {
|
data: {
|
||||||
apiKey: {
|
apiKey: {
|
||||||
|
|||||||
@ -20,7 +20,7 @@ const meta: Meta<PageDecoratorArgs> = {
|
|||||||
parameters: {
|
parameters: {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
graphql.query('FindOnewebhook', () => {
|
graphql.query('FindOneWebhook', () => {
|
||||||
return HttpResponse.json({
|
return HttpResponse.json({
|
||||||
data: {
|
data: {
|
||||||
webhook: {
|
webhook: {
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { GraphQLSchema, GraphQLError } from 'graphql';
|
|||||||
import GraphQLJSON from 'graphql-type-json';
|
import GraphQLJSON from 'graphql-type-json';
|
||||||
import { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken';
|
import { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken';
|
||||||
import { GraphQLSchemaWithContext, YogaInitialContext } from 'graphql-yoga';
|
import { GraphQLSchemaWithContext, YogaInitialContext } from 'graphql-yoga';
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
|
||||||
import { TokenService } from 'src/core/auth/services/token.service';
|
import { TokenService } from 'src/core/auth/services/token.service';
|
||||||
import { CoreModule } from 'src/core/core.module';
|
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 { User } from 'src/core/user/user.entity';
|
||||||
import { useThrottler } from 'src/integrations/throttler/hooks/use-throttler';
|
import { useThrottler } from 'src/integrations/throttler/hooks/use-throttler';
|
||||||
import { JwtData } from 'src/core/auth/types/jwt-data.type';
|
import { JwtData } from 'src/core/auth/types/jwt-data.type';
|
||||||
|
import { useSentryTracing } from 'src/integrations/tracing/useSentryTracing';
|
||||||
|
|
||||||
import { CreateContextFactory } from './factories/create-context.factory';
|
import { CreateContextFactory } from './factories/create-context.factory';
|
||||||
|
|
||||||
@ -45,12 +47,30 @@ export class GraphQLConfigService
|
|||||||
|
|
||||||
createGqlOptions(): YogaDriverConfig {
|
createGqlOptions(): YogaDriverConfig {
|
||||||
const isDebugMode = this.environmentService.isDebugMode();
|
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 = {
|
const config: YogaDriverConfig = {
|
||||||
context: (context) => this.createContextFactory.create(context),
|
context: (context) => this.createContextFactory.create(context),
|
||||||
autoSchemaFile: true,
|
autoSchemaFile: true,
|
||||||
include: [CoreModule],
|
include: [CoreModule],
|
||||||
conditionalSchema: async (context) => {
|
conditionalSchema: async (context) => {
|
||||||
let user: User | undefined;
|
let user: User | undefined;
|
||||||
|
let workspace: Workspace | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!this.tokenService.isTokenPresent(context.req)) {
|
if (!this.tokenService.isTokenPresent(context.req)) {
|
||||||
@ -60,6 +80,7 @@ export class GraphQLConfigService
|
|||||||
const data = await this.tokenService.validateToken(context.req);
|
const data = await this.tokenService.validateToken(context.req);
|
||||||
|
|
||||||
user = data.user;
|
user = data.user;
|
||||||
|
workspace = data.workspace;
|
||||||
|
|
||||||
return await this.createSchema(context, data);
|
return await this.createSchema(context, data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -95,24 +116,17 @@ export class GraphQLConfigService
|
|||||||
? {
|
? {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
|
firstName: user.firstName,
|
||||||
|
lastName: user.lastName,
|
||||||
|
workspaceId: workspace?.id,
|
||||||
|
workspaceDisplayName: workspace?.displayName,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resolvers: { JSON: GraphQLJSON },
|
resolvers: { JSON: GraphQLJSON },
|
||||||
plugins: [
|
plugins: 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 (isDebugMode) {
|
if (isDebugMode) {
|
||||||
|
|||||||
@ -16,18 +16,14 @@ export class ExceptionHandlerSentryDriver
|
|||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: options.dsn,
|
dsn: options.dsn,
|
||||||
integrations: [
|
integrations: [
|
||||||
// enable HTTP calls tracing
|
|
||||||
new Sentry.Integrations.Http({ tracing: true }),
|
new Sentry.Integrations.Http({ tracing: true }),
|
||||||
// enable Express.js middleware tracing
|
|
||||||
new Sentry.Integrations.Express({ app: options.serverInstance }),
|
new Sentry.Integrations.Express({ app: options.serverInstance }),
|
||||||
new Sentry.Integrations.GraphQL(),
|
new Sentry.Integrations.GraphQL(),
|
||||||
new Sentry.Integrations.Postgres({
|
new Sentry.Integrations.Postgres(),
|
||||||
usePgNative: true,
|
|
||||||
}),
|
|
||||||
new ProfilingIntegration(),
|
new ProfilingIntegration(),
|
||||||
],
|
],
|
||||||
tracesSampleRate: 1.0,
|
tracesSampleRate: 1,
|
||||||
profilesSampleRate: 1.0,
|
profilesSampleRate: 0.05,
|
||||||
environment: options.debug ? 'development' : 'production',
|
environment: options.debug ? 'development' : 'production',
|
||||||
debug: options.debug,
|
debug: options.debug,
|
||||||
});
|
});
|
||||||
@ -52,9 +48,11 @@ export class ExceptionHandlerSentryDriver
|
|||||||
if (options?.user) {
|
if (options?.user) {
|
||||||
scope.setUser({
|
scope.setUser({
|
||||||
id: options.user.id,
|
id: options.user.id,
|
||||||
ip_address: options.user.ipAddress,
|
|
||||||
email: options.user.email,
|
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) {
|
if (user) {
|
||||||
scope.setUser({
|
scope.setUser({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
ip_address: user.ipAddress,
|
|
||||||
email: user.email,
|
email: user.email,
|
||||||
username: user.username,
|
firstName: user.firstName,
|
||||||
|
lastName: user.lastName,
|
||||||
|
workspaceId: user.workspaceId,
|
||||||
|
workspaceDisplayName: user.workspaceDisplayName,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
export interface ExceptionHandlerUser {
|
export interface ExceptionHandlerUser {
|
||||||
id?: string;
|
id?: string;
|
||||||
ipAddress?: string;
|
|
||||||
email?: string;
|
email?: string;
|
||||||
username?: string;
|
firstName?: string;
|
||||||
|
lastName?: string;
|
||||||
|
workspaceId?: string;
|
||||||
|
workspaceDisplayName?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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, () => {});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -2,6 +2,7 @@ import { NestFactory } from '@nestjs/core';
|
|||||||
import { ValidationPipe } from '@nestjs/common';
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||||
|
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
import { graphqlUploadExpress } from 'graphql-upload';
|
import { graphqlUploadExpress } from 'graphql-upload';
|
||||||
import bytes from 'bytes';
|
import bytes from 'bytes';
|
||||||
import { useContainer } from 'class-validator';
|
import { useContainer } from 'class-validator';
|
||||||
@ -27,6 +28,11 @@ const bootstrap = async () => {
|
|||||||
// Use our logger
|
// Use our logger
|
||||||
app.useLogger(logger);
|
app.useLogger(logger);
|
||||||
|
|
||||||
|
if (Sentry.isInitialized()) {
|
||||||
|
app.use(Sentry.Handlers.requestHandler());
|
||||||
|
app.use(Sentry.Handlers.tracingHandler());
|
||||||
|
}
|
||||||
|
|
||||||
// Apply validation pipes globally
|
// Apply validation pipes globally
|
||||||
app.useGlobalPipes(
|
app.useGlobalPipes(
|
||||||
new ValidationPipe({
|
new ValidationPipe({
|
||||||
|
|||||||
Reference in New Issue
Block a user