UNPKG

@graphql-mesh/plugin-newrelic

Version:
125 lines (124 loc) 6.81 kB
import { useNewRelic } from '@envelop/newrelic'; import { stringInterpolator } from '@graphql-mesh/string-interpolation'; import { process } from '@graphql-mesh/cross-helpers'; import recordExternal from 'newrelic/lib/metrics/recorders/http_external.js'; import NAMES from 'newrelic/lib/metrics/names.js'; import cat from 'newrelic/lib/util/cat.js'; import { getHeadersObj } from '@graphql-mesh/utils'; import newRelic from 'newrelic'; const EnvelopAttributeName = 'Envelop_NewRelic_Plugin'; export default function useMeshNewrelic(options) { const instrumentationApi = newRelic === null || newRelic === void 0 ? void 0 : newRelic.shim; if (!(instrumentationApi === null || instrumentationApi === void 0 ? void 0 : 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(useNewRelic({ ...options, extractOperationName: options.extractOperationName ? context => stringInterpolator.parse(options.extractOperationName, { context, env: process.env, }) : undefined, })); }, onExecute({ args: { contextValue } }) { const operationSegment = instrumentationApi.getActiveSegment() || instrumentationApi.getSegment(); segmentByRequestContext.set(contextValue.request || contextValue, operationSegment); }, onDelegate({ sourceName, fieldName, args, context, key }) { var _a, _b; const parentSegment = instrumentationApi.getActiveSegment() || instrumentationApi.getSegment() || segmentByRequestContext.get(context.request || context); const delimiter = ((_b = (_a = parentSegment === null || parentSegment === void 0 ? void 0 : parentSegment.transaction) === null || _a === void 0 ? void 0 : _a.nameState) === null || _b === void 0 ? void 0 : _b.delimiter) || '/'; const sourceSegment = instrumentationApi.createSegment(`source${delimiter}${sourceName || 'unknown'}${delimiter}${fieldName}`, null, parentSegment); 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 === null || instrumentationApi === void 0 ? void 0 : instrumentationApi.agent; const parentSegment = instrumentationApi.getActiveSegment() || instrumentationApi.getSegment() || (context ? segmentByRequestContext.get(context.request || context) : undefined); const parsedUrl = new URL(url); const name = NAMES.EXTERNAL.PREFIX + parsedUrl.host + parsedUrl.pathname; const httpDetailSegment = instrumentationApi.createSegment(name, recordExternal(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 === null || parentSegment === void 0 ? void 0 : 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.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 = 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.extractCatHeaders(responseHeadersObj); const decodedAppData = cat.parseAppData(agent.config, appData); const attrs = httpDetailSegment.getAttributes(); const url = new URL(attrs.url); cat.assignCatToSegment(decodedAppData, httpDetailSegment, url.host); } catch (err) { logger.warn(err, 'Cannot add CAT data to segment'); } } httpDetailSegment.end(); }; }, }; }