@graphql-mesh/plugin-prometheus
Version:
224 lines (219 loc) • 7.16 kB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
var utils = require('@graphql-mesh/utils');
var utils$1 = require('@graphql-tools/utils');
var pluginPrometheus = require('@graphql-yoga/plugin-prometheus');
var promClient = require('prom-client');
const DEFAULT_METRICS_CONFIG = {
graphql_envelop_deprecated_field: true,
graphql_envelop_request: true,
graphql_envelop_request_duration: true,
graphql_envelop_request_time_summary: true,
graphql_envelop_phase_parse: true,
graphql_envelop_phase_validate: true,
graphql_envelop_phase_context: true,
graphql_envelop_error_result: true,
graphql_envelop_execute_resolver: false,
graphql_envelop_phase_execute: true,
graphql_envelop_phase_subscribe: true,
graphql_envelop_schema_change: true,
graphql_yoga_http_duration: true,
graphql_gateway_fetch_duration: true,
graphql_gateway_subgraph_execute_duration: true,
graphql_gateway_subgraph_execute_errors: true
};
function useMeshPrometheus(pluginOptions) {
let registry;
if (!pluginOptions.registry) {
registry = promClient.register;
} else if (typeof pluginOptions.registry !== "string") {
registry = pluginOptions.registry;
} else {
registry = registryFromYamlConfig(pluginOptions);
}
const config = {
...pluginOptions,
registry,
metrics: {
...DEFAULT_METRICS_CONFIG,
...pluginOptions.metrics
}
};
const fetchLabelNames = [
"url",
"method",
"statusCode",
"statusText",
"requestHeaders",
"responseHeaders"
];
const { fetchRequestHeaders, fetchResponseHeaders } = pluginOptions.labels ?? {};
if (fetchRequestHeaders) {
fetchLabelNames.push("requestHeaders");
}
if (fetchResponseHeaders) {
fetchLabelNames.push("responseHeaders");
}
const fetchHistogram = pluginPrometheus.getHistogramFromConfig(
config,
"graphql_gateway_fetch_duration",
["fetch"],
{
labelNames: fetchLabelNames,
help: "Time spent on outgoing HTTP calls"
},
({ url, options, response }) => {
if (!options.method) {
throw new Error("Request method is missing from the options");
}
const labels = {
url,
method: options.method,
statusCode: response.status,
statusText: response.statusText
};
if (fetchRequestHeaders) {
labels["requestHeaders"] = JSON.stringify(
filterHeaders(fetchRequestHeaders, options.headers || {})
);
}
if (fetchResponseHeaders) {
labels["responseHeaders"] = JSON.stringify(
filterHeaders(fetchResponseHeaders, utils.getHeadersObj(response.headers))
);
}
return labels;
}
);
const subgraphExecuteHistogram = pluginPrometheus.getHistogramFromConfig(
config,
"graphql_gateway_subgraph_execute_duration",
["subgraphExecute"],
{
labelNames: ["subgraphName", "operationName", "operationType"],
help: "Time spent on subgraph execution"
},
({ subgraphName, executionRequest }) => ({
subgraphName,
operationName: executionRequest.operationName || "Anonymous",
operationType: executionRequest.operationType || "query"
})
);
const subgraphExecuteErrorCounter = pluginPrometheus.getCounterFromConfig(
config,
"graphql_gateway_subgraph_execute_errors",
["subgraphExecute"],
{
labelNames: ["subgraphName", "operationName", "operationType"],
help: "Number of errors on subgraph execution"
},
({ subgraphName, executionRequest }) => ({
subgraphName,
operationName: executionRequest.operationName || "Anonymous",
operationType: executionRequest.operationType || "query"
})
);
const onSubgraphExecute = (subgraphExecuteHistogram || subgraphExecuteErrorCounter) && ((payload) => {
const start = Date.now();
const { context } = payload.executionRequest.context;
const onResult = subgraphExecuteErrorCounter && (({ result }) => {
if (result.errors) {
result.errors.forEach(() => {
if (subgraphExecuteErrorCounter.shouldObserve(payload, context)) {
subgraphExecuteErrorCounter.counter.inc(
subgraphExecuteErrorCounter.fillLabelsFn(payload, context)
);
}
});
}
});
const onEnd = subgraphExecuteHistogram && (() => {
if (subgraphExecuteHistogram.shouldObserve(payload, context)) {
const end = Date.now();
const duration = (end - start) / 1e3;
subgraphExecuteHistogram.histogram.observe(
subgraphExecuteHistogram.fillLabelsFn(payload, context),
duration
);
}
});
return ({ result }) => {
if (utils$1.isAsyncIterable(result)) {
return {
onNext: onResult,
onEnd
};
}
onResult?.({ result });
onEnd?.();
return void 0;
};
});
const onFetch = fetchHistogram && (({ url, options, context }) => {
const start = Date.now();
return ({ response }) => {
const params = { url, options, response };
if (fetchHistogram.shouldObserve(params, context)) {
const end = Date.now();
const duration = (end - start) / 1e3;
fetchHistogram.histogram.observe(
fetchHistogram.fillLabelsFn({ url, options, response }, context),
duration
);
}
};
});
return {
onPluginInit({ addPlugin }) {
addPlugin(
// @ts-expect-error TODO: plugin context generic is missing in yoga's prometheus plugin
pluginPrometheus.usePrometheus(config)
);
},
onSubgraphExecute,
onFetch,
onDispose() {
return registry.clear();
}
};
}
function registryFromYamlConfig(config) {
if (!config.registry) {
throw new Error("Registry not defined in the YAML config");
}
const registry$ = utils.loadFromModuleExportExpression(config.registry, {
cwd: config.baseDir || globalThis.process?.cwd(),
importFn: config.importFn || utils.defaultImportFn,
defaultExportName: "default"
});
const registryProxy = Proxy.revocable(promClient.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) => config.logger.error(e));
return registryProxy.proxy;
}
function filterHeaders(allowList, headers) {
return Array.isArray(allowList) ? Object.fromEntries(
Object.entries(headers).filter(([key]) => allowList.includes(key))
) : headers;
}
Object.defineProperty(exports, "createCounter", {
enumerable: true,
get: function () { return pluginPrometheus.createCounter; }
});
Object.defineProperty(exports, "createHistogram", {
enumerable: true,
get: function () { return pluginPrometheus.createHistogram; }
});
Object.defineProperty(exports, "createSummary", {
enumerable: true,
get: function () { return pluginPrometheus.createSummary; }
});
exports.default = useMeshPrometheus;
;