UNPKG

@graphql-mesh/plugin-newrelic

Version:
180 lines (179 loc) • 9.91 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = useMeshNewrelic; const tslib_1 = require("tslib"); const newrelic_1 = tslib_1.__importDefault(require("newrelic")); const attribute_filter_js_1 = tslib_1.__importDefault(require("newrelic/lib/config/attribute-filter.js")); 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 DESTS = attribute_filter_js_1.default.DESTINATIONS; const EnvelopAttributeName = 'Envelop_NewRelic_Plugin'; function isPromise(value) { return value?.then != null; } 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(); const headersToTrack = { 'user-agent': 'userAgent', host: 'host', 'content-type': 'contentType', 'content-length': 'contentLength', accept: 'accept', }; function sendResAttributes(res, currentSegment = instrumentationApi.getSegment(), currentTransaction = instrumentationApi.agent.tracer.getTransaction()) { currentSegment.addAttribute('http.statusCode', res.status); currentTransaction.trace.attributes.addAttribute(DESTS.TRANS_COMMON, 'http.statusCode', res.status); currentTransaction.statusCode = res.status; currentSegment.addAttribute('http.statusText', res.statusText); currentTransaction.trace.attributes.addAttribute(DESTS.TRANS_COMMON, 'http.statusText', res.statusText); } 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({ request, url, requestHandler, setRequestHandler }) { const currentTransaction = instrumentationApi.agent.tracer.getTransaction(); if (!currentTransaction) { setRequestHandler((...args) => agentApi.startWebTransaction(url.pathname, () => { const currentSegment = instrumentationApi.getSegment(); segmentByRequestContext.set(request, currentSegment); const currentTransaction = instrumentationApi.agent.tracer.getTransaction(); currentTransaction.trace.attributes.addAttribute(DESTS.TRANS_COMMON, 'request.uri', url.pathname); currentSegment.addAttribute('request.uri', url.pathname); currentTransaction.parsedUrl = url; currentTransaction.verb = request.method; currentTransaction.trace.attributes.addAttribute(DESTS.TRANS_COMMON, 'request.method', request.method); currentTransaction.nameState.setVerb(request.method); currentSegment.addAttribute('request.method', request.method); for (const headerName in headersToTrack) { const headerValue = request.headers.get(headerName); if (headerValue) { const key = `request.headers.${headersToTrack[headerName]}`; currentTransaction.trace.attributes.addAttribute(DESTS.TRANS_COMMON, key, headerValue); currentSegment.addAttribute(key, headerValue); } } const res$ = requestHandler(...args); return (0, utils_1.mapMaybePromise)(res$, res => { sendResAttributes(res); return res; }); })); } }, 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(); }; }, }; }