UNPKG

@sentry/node

Version:

Sentry Node SDK using OpenTelemetry for performance instrumentation

317 lines (314 loc) 10.2 kB
import { context, trace } from '@opentelemetry/api'; import { InstrumentationBase, InstrumentationNodeModuleDefinition, InstrumentationNodeModuleFile, isWrapped, safeExecuteInTheMiddle } from '@opentelemetry/instrumentation'; import { SpanNames } from './enum.js'; import { AttributeNames } from './enums/AttributeNames.js'; import { OTEL_GRAPHQL_DATA_SYMBOL } from './symbols.js'; import { OPERATION_NOT_SUPPORTED } from './internal-types.js'; import { getOperation, endSpan, isPromise, addSpanSource, addInputVariableAttributes, wrapFieldResolver, wrapFields } from './utils.js'; import { SDK_VERSION } from '@sentry/core'; const PACKAGE_NAME = "@sentry/instrumentation-graphql"; const DEFAULT_CONFIG = { mergeItems: false, depth: -1, allowValues: false, ignoreResolveSpans: false }; const supportedVersions = [">=14.0.0 <17"]; class GraphQLInstrumentation extends InstrumentationBase { constructor(config = {}) { super(PACKAGE_NAME, SDK_VERSION, { ...DEFAULT_CONFIG, ...config }); } setConfig(config = {}) { super.setConfig({ ...DEFAULT_CONFIG, ...config }); } init() { const module = new InstrumentationNodeModuleDefinition("graphql", supportedVersions); module.files.push(this._addPatchingExecute()); module.files.push(this._addPatchingParser()); module.files.push(this._addPatchingValidate()); return module; } _addPatchingExecute() { return new InstrumentationNodeModuleFile( "graphql/execution/execute.js", supportedVersions, // cannot make it work with appropriate type as execute function has 2 //types and/cannot import function but only types (moduleExports) => { if (isWrapped(moduleExports.execute)) { this._unwrap(moduleExports, "execute"); } this._wrap(moduleExports, "execute", this._patchExecute(moduleExports.defaultFieldResolver)); return moduleExports; }, (moduleExports) => { if (moduleExports) { this._unwrap(moduleExports, "execute"); } } ); } _addPatchingParser() { return new InstrumentationNodeModuleFile( "graphql/language/parser.js", supportedVersions, (moduleExports) => { if (isWrapped(moduleExports.parse)) { this._unwrap(moduleExports, "parse"); } this._wrap(moduleExports, "parse", this._patchParse()); return moduleExports; }, (moduleExports) => { if (moduleExports) { this._unwrap(moduleExports, "parse"); } } ); } _addPatchingValidate() { return new InstrumentationNodeModuleFile( "graphql/validation/validate.js", supportedVersions, (moduleExports) => { if (isWrapped(moduleExports.validate)) { this._unwrap(moduleExports, "validate"); } this._wrap(moduleExports, "validate", this._patchValidate()); return moduleExports; }, (moduleExports) => { if (moduleExports) { this._unwrap(moduleExports, "validate"); } } ); } _patchExecute(defaultFieldResolved) { const instrumentation = this; return function execute(original) { return function patchExecute() { let processedArgs; if (arguments.length >= 2) { const args = arguments; processedArgs = instrumentation._wrapExecuteArgs( args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], defaultFieldResolved ); } else { const args = arguments[0]; processedArgs = instrumentation._wrapExecuteArgs( args.schema, args.document, args.rootValue, args.contextValue, args.variableValues, args.operationName, args.fieldResolver, args.typeResolver, defaultFieldResolved ); } const operation = getOperation(processedArgs.document, processedArgs.operationName); const span = instrumentation._createExecuteSpan(operation, processedArgs); processedArgs.contextValue[OTEL_GRAPHQL_DATA_SYMBOL] = { source: processedArgs.document ? processedArgs.document || processedArgs.document[OTEL_GRAPHQL_DATA_SYMBOL] : void 0, span, fields: {} }; return context.with(trace.setSpan(context.active(), span), () => { return safeExecuteInTheMiddle( () => { return original.apply(this, [processedArgs]); }, (err, result) => { instrumentation._handleExecutionResult(span, err, result); } ); }); }; }; } _handleExecutionResult(span, err, result) { const config = this.getConfig(); if (result === void 0 || err) { endSpan(span, err); return; } if (isPromise(result)) { result.then( (resultData) => { if (typeof config.responseHook !== "function") { endSpan(span); return; } this._executeResponseHook(span, resultData); }, (error) => { endSpan(span, error); } ); } else { if (typeof config.responseHook !== "function") { endSpan(span); return; } this._executeResponseHook(span, result); } } _executeResponseHook(span, result) { const { responseHook } = this.getConfig(); if (!responseHook) { return; } safeExecuteInTheMiddle( () => { responseHook(span, result); }, (err) => { if (err) { this._diag.error("Error running response hook", err); } endSpan(span, void 0); }, true ); } _patchParse() { const instrumentation = this; return function parse(original) { return function patchParse(source, options) { return instrumentation._parse(this, original, source, options); }; }; } _patchValidate() { const instrumentation = this; return function validate(original) { return function patchValidate(schema, documentAST, rules, options, typeInfo) { return instrumentation._validate(this, original, schema, documentAST, rules, typeInfo, options); }; }; } _parse(obj, original, source, options) { const config = this.getConfig(); const span = this.tracer.startSpan(SpanNames.PARSE); return context.with(trace.setSpan(context.active(), span), () => { return safeExecuteInTheMiddle( () => { return original.call(obj, source, options); }, (err, result) => { if (result) { const operation = getOperation(result); if (!operation) { span.updateName(SpanNames.SCHEMA_PARSE); } else if (result.loc) { addSpanSource(span, result.loc, config.allowValues); } } endSpan(span, err); } ); }); } _validate(obj, original, schema, documentAST, rules, typeInfo, options) { const span = this.tracer.startSpan(SpanNames.VALIDATE, {}); return context.with(trace.setSpan(context.active(), span), () => { return safeExecuteInTheMiddle( () => { return original.call(obj, schema, documentAST, rules, options, typeInfo); }, (err, errors) => { if (!documentAST.loc) { span.updateName(SpanNames.SCHEMA_VALIDATE); } if (errors && errors.length) { span.recordException({ name: AttributeNames.ERROR_VALIDATION_NAME, message: JSON.stringify(errors) }); } endSpan(span, err); } ); }); } _createExecuteSpan(operation, processedArgs) { const config = this.getConfig(); const span = this.tracer.startSpan(SpanNames.EXECUTE, {}); if (operation) { const { operation: operationType, name: nameNode } = operation; span.setAttribute(AttributeNames.OPERATION_TYPE, operationType); const operationName = nameNode?.value; if (operationName) { span.setAttribute(AttributeNames.OPERATION_NAME, operationName); span.updateName(`${operationType} ${operationName}`); } else { span.updateName(operationType); } } else { let operationName = " "; if (processedArgs.operationName) { operationName = ` "${processedArgs.operationName}" `; } operationName = OPERATION_NOT_SUPPORTED.replace("$operationName$", operationName); span.setAttribute(AttributeNames.OPERATION_NAME, operationName); } if (processedArgs.document?.loc) { addSpanSource(span, processedArgs.document.loc, config.allowValues); } if (processedArgs.variableValues && config.allowValues) { addInputVariableAttributes(span, processedArgs.variableValues); } return span; } _wrapExecuteArgs(schema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, typeResolver, defaultFieldResolved) { if (!contextValue) { contextValue = {}; } if (contextValue[OTEL_GRAPHQL_DATA_SYMBOL] || this.getConfig().ignoreResolveSpans) { return { schema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, typeResolver }; } const isUsingDefaultResolver = fieldResolver == null; const fieldResolverForExecute = fieldResolver ?? defaultFieldResolved; fieldResolver = wrapFieldResolver( this.tracer, () => this.getConfig(), fieldResolverForExecute, isUsingDefaultResolver ); if (schema) { wrapFields(schema.getQueryType(), this.tracer, () => this.getConfig()); wrapFields(schema.getMutationType(), this.tracer, () => this.getConfig()); } return { schema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, typeResolver }; } } export { GraphQLInstrumentation }; //# sourceMappingURL=instrumentation.js.map