@graphql-mesh/plugin-prometheus
Version:
242 lines (241 loc) • 11.5 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.createSummary = exports.createHistogram = exports.createCounter = void 0;
exports.default = useMeshPrometheus;
const graphql_yoga_1 = require("graphql-yoga");
const prom_client_1 = require("prom-client");
const utils_1 = require("@graphql-mesh/utils");
const plugin_prometheus_1 = require("@graphql-yoga/plugin-prometheus");
Object.defineProperty(exports, "createCounter", { enumerable: true, get: function () { return plugin_prometheus_1.createCounter; } });
Object.defineProperty(exports, "createHistogram", { enumerable: true, get: function () { return plugin_prometheus_1.createHistogram; } });
Object.defineProperty(exports, "createSummary", { enumerable: true, get: function () { return plugin_prometheus_1.createSummary; } });
function useMeshPrometheus(pluginOptions) {
let registry;
if (!pluginOptions.registry) {
registry = prom_client_1.register;
}
else if (typeof pluginOptions.registry !== 'string') {
registry = pluginOptions.registry;
}
else {
// TODO: Remove this once Mesh v1 is released
// Mesh v1 config is now a TS config file, we don't need to load it from a string anymore
registry = registryFromYamlConfig(pluginOptions);
}
let fetchHistogram;
if (pluginOptions.fetchMetrics) {
const labelNames = ['url', 'method', 'statusCode', 'statusText'];
const { fetchRequestHeaders = pluginOptions.fetchRequestHeaders, fetchResponseHeaders = pluginOptions.fetchResponseHeaders, } = pluginOptions.labels || {};
if (fetchRequestHeaders) {
labelNames.push('requestHeaders');
}
if (fetchResponseHeaders) {
labelNames.push('responseHeaders');
}
fetchHistogram =
typeof pluginOptions.fetchMetrics === 'object'
? pluginOptions.fetchMetrics
: (0, plugin_prometheus_1.createHistogram)({
registry,
histogram: {
name: typeof pluginOptions.fetchMetrics === 'string'
? pluginOptions.fetchMetrics
: 'graphql_mesh_fetch_duration',
help: 'Time spent on outgoing HTTP calls',
labelNames,
},
fillLabelsFn: ({ url, options, response }) => {
const labels = {
url,
method: options.method,
statusCode: response.status,
statusText: response.statusText,
};
if (fetchRequestHeaders) {
labels.requestHeaders = JSON.stringify(options.headers);
}
if (fetchResponseHeaders) {
labels.responseHeaders = JSON.stringify((0, utils_1.getHeadersObj)(response.headers));
}
return labels;
},
});
}
let delegateHistogram;
if (pluginOptions.delegation) {
const delegationLabelNames = ['sourceName', 'typeName', 'fieldName'];
const { delegationArgs, delegationKey } = pluginOptions;
if (delegationArgs) {
delegationLabelNames.push('args');
}
if (delegationKey) {
delegationLabelNames.push('key');
}
delegateHistogram =
typeof pluginOptions.delegation === 'object'
? pluginOptions.delegation
: (0, plugin_prometheus_1.createHistogram)({
registry,
histogram: {
name: typeof pluginOptions.delegation === 'string'
? pluginOptions.delegation
: 'graphql_mesh_delegate_duration',
help: 'Time spent on delegate execution',
labelNames: delegationLabelNames,
},
fillLabelsFn: ({ sourceName, typeName, fieldName, args, key }) => {
return {
sourceName,
typeName,
fieldName,
args: delegationArgs ? JSON.stringify(args) : undefined,
key: delegationKey ? JSON.stringify(key) : undefined,
};
},
});
}
let subgraphExecuteHistogram;
if (pluginOptions.subgraphExecute !== false) {
const subgraphExecuteLabels = ['subgraphName'];
if (pluginOptions.labels?.operationName !== false) {
subgraphExecuteLabels.push('operationName');
}
if (pluginOptions.labels?.operationType !== false) {
subgraphExecuteLabels.push('operationType');
}
subgraphExecuteHistogram =
typeof pluginOptions.subgraphExecute === 'object'
? pluginOptions.subgraphExecute
: (0, plugin_prometheus_1.createHistogram)({
registry,
histogram: {
name: typeof pluginOptions.subgraphExecute === 'string'
? pluginOptions.subgraphExecute
: 'graphql_mesh_subgraph_execute_duration',
help: 'Time spent on subgraph execution',
labelNames: subgraphExecuteLabels,
},
fillLabelsFn: ({ subgraphName, executionRequest: { operationType = 'query', operationName }, }) => ({
subgraphName,
operationType: pluginOptions.labels?.operationType !== false ? operationType : undefined,
operationName: pluginOptions.labels?.operationName !== false
? operationName || 'Anonymous'
: undefined,
}),
});
}
let subgraphExecuteErrorCounter;
if (pluginOptions.subgraphExecuteErrors !== false) {
const subgraphExecuteErrorLabels = ['subgraphName'];
if (pluginOptions.labels?.operationName !== false) {
subgraphExecuteErrorLabels.push('operationName');
}
if (pluginOptions.labels?.operationType !== false) {
subgraphExecuteErrorLabels.push('operationType');
}
subgraphExecuteErrorCounter =
typeof pluginOptions.subgraphExecuteErrors === 'object'
? pluginOptions.subgraphExecuteErrors
: (0, plugin_prometheus_1.createCounter)({
registry,
counter: {
name: typeof pluginOptions.subgraphExecuteErrors === 'string'
? pluginOptions.subgraphExecuteErrors
: `graphql_mesh_subgraph_execute_errors`,
help: 'Number of errors on subgraph execution',
labelNames: subgraphExecuteErrorLabels,
},
fillLabelsFn: ({ subgraphName, executionRequest: { operationType = 'query', operationName }, }) => ({
subgraphName,
operationType: pluginOptions.labels?.operationType !== false ? operationType : undefined,
operationName: pluginOptions.labels?.operationName !== false
? operationName || 'Anonymous'
: undefined,
}),
});
}
return {
onPluginInit({ addPlugin }) {
addPlugin(
// TODO: fix usePrometheus typings to inherit the context
(0, plugin_prometheus_1.usePrometheus)({
...pluginOptions,
registry,
}));
},
onDelegate({ context, ...payload }) {
if (delegateHistogram) {
const start = Date.now();
return () => {
const end = Date.now();
const duration = end - start;
delegateHistogram.histogram.observe(delegateHistogram.fillLabelsFn(payload, context), duration);
};
}
return undefined;
},
onSubgraphExecute(payload) {
if (subgraphExecuteHistogram) {
const start = Date.now();
return ({ result }) => {
if ((0, graphql_yoga_1.isAsyncIterable)(result)) {
return {
onNext({ result }) {
if (result.errors) {
result.errors.forEach(() => {
subgraphExecuteErrorCounter?.counter.inc(subgraphExecuteErrorCounter.fillLabelsFn(payload, payload.executionRequest.context));
});
}
},
onEnd: () => {
const end = Date.now();
const duration = end - start;
subgraphExecuteHistogram.histogram.observe(subgraphExecuteHistogram.fillLabelsFn(payload, payload.executionRequest.context), duration);
},
};
}
if (result.errors) {
result.errors.forEach(() => {
subgraphExecuteErrorCounter?.counter.inc(subgraphExecuteErrorCounter.fillLabelsFn(payload, payload.executionRequest.context));
});
}
const end = Date.now();
const duration = end - start;
subgraphExecuteHistogram.histogram.observe(subgraphExecuteHistogram.fillLabelsFn(payload, payload.executionRequest.context), duration);
return undefined;
};
}
return undefined;
},
onFetch({ url, options, context }) {
if (fetchHistogram) {
const start = Date.now();
return ({ response }) => {
const end = Date.now();
const duration = end - start;
fetchHistogram.histogram.observe(fetchHistogram.fillLabelsFn({ url, options, response }, context), duration);
};
}
return undefined;
},
};
}
function registryFromYamlConfig(pluginOptions) {
const registry$ = (0, utils_1.loadFromModuleExportExpression)(pluginOptions.registry, {
cwd: pluginOptions.baseDir || globalThis.process?.cwd(),
importFn: pluginOptions.importFn || utils_1.defaultImportFn,
defaultExportName: 'default',
});
const registryProxy = Proxy.revocable(prom_client_1.register, {
get(target, prop, receiver) {
if (typeof target[prop] === 'function') {
return function (...args) {
return registry$.then(registry => registry[prop](...args));
};
}
return Reflect.get(target, prop, receiver);
},
});
registry$.then(() => registryProxy.revoke()).catch(e => pluginOptions.logger.error(e));
return registryProxy.proxy;
}
;