UNPKG

@graphql-mesh/plugin-newrelic

Version:
137 lines (136 loc) 7.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const newrelic_1 = tslib_1.__importDefault(require("newrelic")); const names_js_1 = tslib_1.__importDefault(require("newrelic/lib/metrics/names.js")); const http_external_js_1 = tslib_1.__importDefault(require("newrelic/lib/metrics/recorders/http_external.js")); const cat_js_1 = tslib_1.__importDefault(require("newrelic/lib/util/cat.js")); const newrelic_2 = require("@envelop/newrelic"); const cross_helpers_1 = require("@graphql-mesh/cross-helpers"); const string_interpolation_1 = require("@graphql-mesh/string-interpolation"); const utils_1 = require("@graphql-mesh/utils"); const EnvelopAttributeName = 'Envelop_NewRelic_Plugin'; function useMeshNewrelic(options, { instrumentationApi = newrelic_1.default?.shim, agentApi = newrelic_1.default } = {}) { if (!instrumentationApi?.agent) { options.logger.debug('Agent unavailable. Please check your New Relic Agent configuration and ensure New Relic is enabled.'); return {}; } instrumentationApi.agent.metrics .getOrCreateMetric(`Supportability/ExternalModules/${EnvelopAttributeName}`) .incrementCallCount(); const logger = instrumentationApi.logger.child({ component: EnvelopAttributeName }); const segmentByRequestContext = new WeakMap(); return { onPluginInit({ addPlugin }) { addPlugin((0, newrelic_2.useNewRelic)({ ...options, shim: instrumentationApi, extractOperationName: options.extractOperationName ? context => string_interpolation_1.stringInterpolator.parse(options.extractOperationName, { context, env: cross_helpers_1.process.env, }) : undefined, })); }, onRequest({ url, requestHandler, setRequestHandler }) { const currentTransaction = instrumentationApi.agent.tracer.getTransaction(); if (!currentTransaction) { setRequestHandler((...args) => agentApi.startWebTransaction(url.pathname, () => requestHandler(...args))); } }, onExecute({ args: { contextValue } }) { const operationSegment = instrumentationApi.getActiveSegment() || instrumentationApi.getSegment(); segmentByRequestContext.set(contextValue.request || contextValue, operationSegment); }, onDelegate({ sourceName, fieldName, args, context, key }) { const parentSegment = instrumentationApi.getActiveSegment() || instrumentationApi.getSegment() || segmentByRequestContext.get(context.request || context); const delimiter = parentSegment?.transaction?.nameState?.delimiter || '/'; const sourceSegment = instrumentationApi.createSegment(`source${delimiter}${sourceName || 'unknown'}${delimiter}${fieldName}`, null, parentSegment); if (!sourceSegment) { return undefined; } if (options.includeResolverArgs) { if (args) { sourceSegment.addAttribute('args', JSON.stringify(args)); } if (key) { sourceSegment.addAttribute('key', JSON.stringify(key)); } } sourceSegment.start(); return ({ result }) => { if (options.includeRawResult) { sourceSegment.addAttribute('result', JSON.stringify(result)); } sourceSegment.end(); }; }, onFetch({ url, options, context }) { const agent = instrumentationApi?.agent; const parentSegment = instrumentationApi.getActiveSegment() || instrumentationApi.getSegment() || (context ? segmentByRequestContext.get(context.request || context) : undefined); const parsedUrl = new URL(url); const name = names_js_1.default.EXTERNAL.PREFIX + parsedUrl.host + parsedUrl.pathname; const httpDetailSegment = instrumentationApi.createSegment(name, (0, http_external_js_1.default)(parsedUrl.host, 'graphql-mesh'), parentSegment); if (!httpDetailSegment) { logger.error(`Unable to create segment for external request: ${name}`); return undefined; } httpDetailSegment.start(); httpDetailSegment.addAttribute('url', url); parsedUrl.searchParams.forEach((value, key) => { httpDetailSegment.addAttribute(`request.parameters.${key}`, value); }); httpDetailSegment.addAttribute('procedure', options.method || 'GET'); const transaction = parentSegment?.transaction; if (transaction) { const outboundHeaders = Object.create(null); if (agent.config.encoding_key && transaction.syntheticsHeader) { outboundHeaders['x-newrelic-synthetics'] = transaction.syntheticsHeader; } if (agent.config.distributed_tracing.enabled) { transaction.insertDistributedTraceHeaders(outboundHeaders); } else if (agent.config.cross_application_tracer.enabled) { cat_js_1.default.addCatHeaders(agent.config, transaction, outboundHeaders); } else { logger.trace('Both DT and CAT are disabled, not adding headers!'); } for (const key in outboundHeaders) { options.headers[key] = outboundHeaders[key]; } } for (const key in options.headers) { httpDetailSegment.addAttribute(`request.headers.${key}`, options.headers[key]); } return ({ response }) => { httpDetailSegment.addAttribute('http.statusCode', response.status); httpDetailSegment.addAttribute('http.statusText', response.statusText); const responseHeadersObj = (0, utils_1.getHeadersObj)(response.headers); for (const key in responseHeadersObj) { httpDetailSegment.addAttribute(`response.headers.${key}`, responseHeadersObj[key]); } if (agent.config.cross_application_tracer.enabled && !agent.config.distributed_tracing.enabled) { try { const { appData } = cat_js_1.default.extractCatHeaders(responseHeadersObj); const decodedAppData = cat_js_1.default.parseAppData(agent.config, appData); const attrs = httpDetailSegment.getAttributes(); const url = new URL(attrs.url); cat_js_1.default.assignCatToSegment(decodedAppData, httpDetailSegment, url.host); } catch (err) { logger.warn(err, 'Cannot add CAT data to segment'); } } httpDetailSegment.end(); }; }, }; } exports.default = useMeshNewrelic;