@graphql-mesh/plugin-prometheus
Version:
228 lines (227 loc) • 9.52 kB
JavaScript
import { Counter, register as defaultRegistry, Histogram, Summary } from 'prom-client';
import { getHeadersObj, loadFromModuleExportExpression } from '@graphql-mesh/utils';
import { usePrometheus, } from '@graphql-yoga/plugin-prometheus';
import { commonFillLabelsFnForEnvelop, commonLabelsForEnvelop, createHistogramForEnvelop, } from './createHistogramForEnvelop.js';
export default async function useMeshPrometheus(pluginOptions) {
const registry = pluginOptions.registry
? await loadFromModuleExportExpression(pluginOptions.registry, {
cwd: pluginOptions.baseDir,
importFn: pluginOptions.importFn,
defaultExportName: 'default',
})
: defaultRegistry;
let fetchHistogram;
if (pluginOptions.fetch) {
const name = typeof pluginOptions.fetch === 'string' ? pluginOptions.fetch : 'graphql_mesh_fetch_duration';
const labelNames = ['url', 'method', 'statusCode', 'statusText'];
if (pluginOptions.fetchRequestHeaders) {
labelNames.push('requestHeaders');
}
if (pluginOptions.fetchResponseHeaders) {
labelNames.push('responseHeaders');
}
fetchHistogram = new Histogram({
name,
help: 'Time spent on outgoing HTTP calls',
labelNames,
registers: [registry],
});
}
let delegateHistogram;
if (pluginOptions.delegation) {
const name = typeof pluginOptions.delegation === 'string'
? pluginOptions.delegation
: 'graphql_mesh_delegate_duration';
delegateHistogram = new Histogram({
name,
help: 'Time spent on delegate execution',
labelNames: ['sourceName', 'typeName', 'fieldName', 'args', 'key'],
registers: [registry],
});
}
let httpHistogram;
if (pluginOptions.http) {
const labelNames = ['url', 'method', 'statusCode', 'statusText'];
if (pluginOptions.httpRequestHeaders) {
labelNames.push('requestHeaders');
}
if (pluginOptions.httpResponseHeaders) {
labelNames.push('responseHeaders');
}
const name = typeof pluginOptions.http === 'string' ? pluginOptions.http : 'graphql_mesh_http_duration';
httpHistogram = {
histogram: new Histogram({
name,
help: 'Time spent on incoming HTTP requests',
labelNames,
registers: [registry],
}),
fillLabelsFn(_, { request, response }) {
const labels = {
url: request.url,
method: request.method,
statusCode: response.status,
statusText: response.statusText,
};
if (pluginOptions.httpRequestHeaders) {
labels.requestHeaders = JSON.stringify(getHeadersObj(request.headers));
}
if (pluginOptions.httpResponseHeaders) {
labels.responseHeaders = JSON.stringify(getHeadersObj(response.headers));
}
return labels;
},
};
}
let requestCounter;
if (pluginOptions.requestCount) {
const name = typeof pluginOptions.requestCount === 'string'
? pluginOptions.requestCount
: 'graphql_mesh_request_count';
requestCounter = {
counter: new Counter({
name,
help: 'Counts the amount of GraphQL requests executed',
labelNames: commonLabelsForEnvelop,
registers: [registry],
}),
fillLabelsFn: commonFillLabelsFnForEnvelop,
};
}
let requestSummary;
if (pluginOptions.requestSummary) {
const name = typeof pluginOptions.requestSummary === 'string'
? pluginOptions.requestSummary
: 'graphql_mesh_request_time_summary';
requestSummary = {
summary: new Summary({
name,
help: 'Summary to measure the time to complete GraphQL operations',
labelNames: commonLabelsForEnvelop,
registers: [registry],
}),
fillLabelsFn: commonFillLabelsFnForEnvelop,
};
}
let errorsCounter;
if (pluginOptions.errors) {
const name = typeof pluginOptions.errors === 'string' ? pluginOptions.errors : 'graphql_mesh_error_result';
errorsCounter = {
counter: new Counter({
name,
help: 'Counts the amount of errors reported from all phases',
labelNames: ['operationType', 'operationName', 'path', 'phase'],
registers: [registry],
}),
fillLabelsFn: params => ({
operationName: params.operationName,
operationType: params.operationType,
path: params.error?.path?.join('.'),
phase: params.errorPhase,
}),
};
}
let deprecatedCounter;
if (pluginOptions.deprecatedFields) {
const name = typeof pluginOptions.deprecatedFields === 'string'
? pluginOptions.deprecatedFields
: 'graphql_mesh_deprecated_fields';
deprecatedCounter = {
counter: new Counter({
name,
help: 'Counts the amount of deprecated fields used in selection sets',
labelNames: ['operationType', 'operationName', 'fieldName', 'typeName'],
registers: [registry],
}),
fillLabelsFn: params => ({
operationName: params.operationName,
operationType: params.operationType,
fieldName: params.deprecationInfo?.fieldName,
typeName: params.deprecationInfo?.typeName,
}),
};
}
return {
onPluginInit({ addPlugin }) {
addPlugin(usePrometheus({
...pluginOptions,
http: httpHistogram,
requestCount: requestCounter,
requestTotalDuration: createHistogramForEnvelop({
defaultName: 'graphql_mesh_request_duration',
valueFromConfig: pluginOptions.requestTotalDuration,
help: 'Time spent on running the GraphQL operation from parse to execute',
registry,
}),
requestSummary,
parse: createHistogramForEnvelop({
defaultName: 'graphql_mesh_parse_duration',
valueFromConfig: pluginOptions.parse,
help: 'Time spent on parsing the GraphQL operation',
registry,
}),
validate: createHistogramForEnvelop({
defaultName: 'graphql_mesh_validate_duration',
valueFromConfig: pluginOptions.validate,
help: 'Time spent on validating the GraphQL operation',
registry,
}),
contextBuilding: createHistogramForEnvelop({
defaultName: 'graphql_mesh_context_building_duration',
valueFromConfig: pluginOptions.contextBuilding,
help: 'Time spent on building the GraphQL context',
registry,
}),
execute: createHistogramForEnvelop({
defaultName: 'graphql_mesh_execute_duration',
valueFromConfig: pluginOptions.execute,
help: 'Time spent on executing the GraphQL operation',
registry,
}),
errors: errorsCounter,
deprecatedFields: deprecatedCounter,
registry,
}));
},
onDelegate({ sourceName, typeName, fieldName, args, key }) {
if (delegateHistogram) {
const start = Date.now();
return () => {
const end = Date.now();
const duration = end - start;
delegateHistogram.observe({
sourceName,
typeName,
fieldName,
args: JSON.stringify(args),
key: JSON.stringify(key),
}, duration);
};
}
return undefined;
},
onFetch({ url, options }) {
if (fetchHistogram) {
const start = Date.now();
return ({ response }) => {
const end = Date.now();
const duration = end - start;
const labels = {
url,
method: options.method,
statusCode: response.status,
statusText: response.statusText,
};
if (pluginOptions.fetchRequestHeaders) {
labels.requestHeaders = JSON.stringify(options.headers);
}
if (pluginOptions.fetchResponseHeaders) {
labels.responseHeaders = JSON.stringify(getHeadersObj(response.headers));
}
fetchHistogram.observe(labels, duration);
};
}
return undefined;
},
};
}