UNPKG

@graphql-mesh/plugin-prometheus

Version:
224 lines (219 loc) • 7.16 kB
'use strict'; 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;