@graphql-mesh/plugin-newrelic
Version:
180 lines (179 loc) • 9.91 kB
JavaScript
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();
};
},
};
}
;