@graphql-yoga/plugin-prometheus
Version:
Prometheus plugin for GraphQL Yoga.
98 lines (97 loc) • 4.2 kB
JavaScript
import { getOperationAST } from 'graphql';
import { register as defaultRegistry } from 'prom-client';
import { createCounter, createHistogram, createSummary, getCounterFromConfig, getHistogramFromConfig, getSummaryFromConfig, usePrometheus as useEnvelopPrometheus, } from '@envelop/prometheus';
export { createCounter, createHistogram, createSummary, getHistogramFromConfig, getCounterFromConfig, getSummaryFromConfig, };
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,
};
export function usePrometheus(options) {
const endpoint = options.endpoint || '/metrics';
const registry = options.registry || defaultRegistry;
const resolvedOptions = {
...options,
metrics: {
...DEFAULT_METRICS_CONFIG,
...options.metrics,
},
};
const basePlugin = {
onPluginInit({ addPlugin }) {
addPlugin(useEnvelopPrometheus({ ...resolvedOptions, registry }));
addPlugin({
onRequest({ url, fetchAPI, endResponse }) {
if (endpoint && url.pathname === endpoint) {
return registry.metrics().then(metrics => {
endResponse(new fetchAPI.Response(metrics, {
headers: {
'Content-Type': registry.contentType,
},
}));
});
}
return undefined;
},
});
},
};
const httpHistogram = getHistogramFromConfig(resolvedOptions, 'graphql_yoga_http_duration', ['request'], {
help: 'Time spent on HTTP connection',
labelNames: ['operationName', 'operationType', 'method', 'statusCode', 'url'],
}, (params, { request, response }) => ({
method: request.method,
statusCode: response.status,
operationType: params.operationType || 'unknown',
operationName: params.operationName || 'Anonymous',
url: request.url,
}));
// We don't need to register any hooks if the metric is not enabled
if (!httpHistogram) {
return basePlugin;
}
const startByRequest = new WeakMap();
const paramsByRequest = new WeakMap();
return {
...basePlugin,
onRequest({ request }) {
startByRequest.set(request, Date.now());
},
onParse({ context }) {
// If only it is Yoga, we calculate HTTP request time
if (context.request) {
return ({ result: document, context }) => {
const operationAST = getOperationAST(document, context.params.operationName);
const params = {
document,
operationName: operationAST?.name?.value,
operationType: operationAST?.operation,
};
paramsByRequest.set(context.request, params);
};
}
return undefined;
},
onResponse({ request, response, serverContext }) {
const start = startByRequest.get(request);
const params = paramsByRequest.get(request);
if (start && params) {
const context = { ...serverContext, request, response };
const completeParams = { ...params, request, response };
if (httpHistogram.shouldObserve(completeParams, context)) {
httpHistogram.histogram.observe(httpHistogram.fillLabelsFn(completeParams, context), (Date.now() - start) / 1000);
}
}
},
};
}