@sentry/node
Version:
Sentry Node SDK using OpenTelemetry for performance instrumentation
123 lines (102 loc) • 4.07 kB
JavaScript
import { _optionalChain } from '@sentry/utils';
import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core';
import { defineIntegration, getClient, getIsolationScope, getDefaultIsolationScope, captureException, spanToJSON, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
import { logger } from '@sentry/utils';
import { generateInstrumentOnce } from '../../../otel/instrument.js';
import { SentryNestInstrumentation } from './sentry-nest-instrumentation.js';
const INTEGRATION_NAME = 'Nest';
const instrumentNestCore = generateInstrumentOnce('Nest-Core', () => {
return new NestInstrumentation();
});
const instrumentNestCommon = generateInstrumentOnce('Nest-Common', () => {
return new SentryNestInstrumentation();
});
const instrumentNest = Object.assign(
() => {
instrumentNestCore();
instrumentNestCommon();
},
{ id: INTEGRATION_NAME },
);
const _nestIntegration = (() => {
return {
name: INTEGRATION_NAME,
setupOnce() {
instrumentNest();
},
};
}) ;
/**
* Nest framework integration
*
* Capture tracing data for nest.
*/
const nestIntegration = defineIntegration(_nestIntegration);
/**
* Setup an error handler for Nest.
*/
function setupNestErrorHandler(app, baseFilter) {
// Sadly, NestInstrumentation has no requestHook, so we need to add the attributes here
// We register this hook in this method, because if we register it in the integration `setup`,
// it would always run even for users that are not even using Nest.js
const client = getClient();
if (client) {
client.on('spanStart', span => {
addNestSpanAttributes(span);
});
}
app.useGlobalInterceptors({
intercept(context, next) {
if (getIsolationScope() === getDefaultIsolationScope()) {
logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.');
return next.handle();
}
if (context.getType() === 'http') {
const req = context.switchToHttp().getRequest();
if (req.route) {
getIsolationScope().setTransactionName(`${_optionalChain([req, 'access', _ => _.method, 'optionalAccess', _2 => _2.toUpperCase, 'call', _3 => _3()]) || 'GET'} ${req.route.path}`);
}
}
return next.handle();
},
});
const wrappedFilter = new Proxy(baseFilter, {
get(target, prop, receiver) {
if (prop === 'catch') {
const originalCatch = Reflect.get(target, prop, receiver);
return (exception, host) => {
const exceptionIsObject = typeof exception === 'object' && exception !== null;
const exceptionStatusCode = exceptionIsObject && 'status' in exception ? exception.status : null;
const exceptionErrorProperty = exceptionIsObject && 'error' in exception ? exception.error : null;
/*
Don't report expected NestJS control flow errors
- `HttpException` errors will have a `status` property
- `RpcException` errors will have an `error` property
*/
if (exceptionStatusCode !== null || exceptionErrorProperty !== null) {
return originalCatch.apply(target, [exception, host]);
}
captureException(exception);
return originalCatch.apply(target, [exception, host]);
};
}
return Reflect.get(target, prop, receiver);
},
});
app.useGlobalFilters(wrappedFilter);
}
function addNestSpanAttributes(span) {
const attributes = spanToJSON(span).data || {};
// this is one of: app_creation, request_context, handler
const type = attributes['nestjs.type'];
// If this is already set, or we have no nest.js span, no need to process again...
if (attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] || !type) {
return;
}
span.setAttributes({
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.nestjs',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: `${type}.nestjs`,
});
}
export { instrumentNest, nestIntegration, setupNestErrorHandler };
//# sourceMappingURL=nest.js.map