@envelop/prometheus
Version:
This plugin tracks the complete execution flow, and reports metrics using Prometheus tracing (based on `prom-client`).
193 lines (192 loc) • 7.05 kB
JavaScript
import { visit, visitWithTypeInfo, } from 'graphql';
import { Counter, register as defaultRegistry, Histogram, Summary, } from 'prom-client';
const histograms = new WeakMap();
const summaries = new WeakMap();
const counters = new WeakMap();
export function shouldTraceFieldResolver(info, whitelist) {
if (!whitelist) {
return true;
}
const parentType = info.parentType.name;
const fieldName = info.fieldName;
const coordinate = `${parentType}.${fieldName}`;
return whitelist.includes(coordinate) || whitelist.includes(`${parentType}.*`);
}
function getOperation(document) {
return document.definitions[0];
}
export function createFillLabelFnParams(parseResult, context, filterParams) {
if (parseResult === null) {
return null;
}
if (parseResult instanceof Error) {
return null;
}
const operation = getOperation(parseResult);
return filterParams({
document: parseResult,
operationName: context?.params?.operationName || operation.name?.value || 'Anonymous',
operationType: operation.operation,
});
}
export function registerHistogram(registry, conf) {
if (!histograms.has(registry)) {
histograms.set(registry, new Map());
}
const registryHistograms = histograms.get(registry);
if (!registryHistograms.has(conf.name)) {
conf.registers = [registry];
registryHistograms.set(conf.name, new Histogram(conf));
}
return registryHistograms.get(conf.name);
}
export function createHistogram(options) {
return {
histogram: registerHistogram(options.registry, options.histogram),
// histogram: new Histogram(options.histogram),
fillLabelsFn: options.fillLabelsFn,
};
}
export function registerSummary(registry, conf) {
if (!summaries.has(registry)) {
summaries.set(registry, new Map());
}
const registrySummaries = summaries.get(registry);
if (!registrySummaries.has(conf.name)) {
conf.registers = [registry];
registrySummaries.set(conf.name, new Summary(conf));
}
return registrySummaries.get(conf.name);
}
export function createSummary(options) {
return {
summary: registerSummary(options.registry, options.summary),
fillLabelsFn: options.fillLabelsFn,
};
}
export function registerCounter(registry, conf) {
if (!counters.has(registry)) {
counters.set(registry, new Map());
}
const registryCounters = counters.get(registry);
if (!registryCounters.has(conf.name)) {
conf.registers = [registry];
registryCounters.set(conf.name, new Counter(conf));
}
return registryCounters.get(conf.name);
}
export function createCounter(options) {
return {
counter: registerCounter(options.registry, options.counter),
fillLabelsFn: options.fillLabelsFn,
};
}
export function getHistogramFromConfig(config, phase, histogram, fillLabelsFn = params => filterFillParamsFnParams(config, {
operationName: params.operationName,
operationType: params.operationType,
})) {
const metric = config.metrics[phase];
return typeof metric === 'object'
? metric
: metric === true
? createHistogram({
registry: config.registry || defaultRegistry,
histogram: {
name: typeof metric === 'string' ? metric : phase,
labelNames: ['operationType', 'operationName'].filter(label => labelExists(config, label)),
...histogram,
},
fillLabelsFn,
})
: undefined;
}
export function getSummaryFromConfig(config, phase, summary, fillLabelsFn = params => filterFillParamsFnParams(config, {
operationName: params.operationName,
operationType: params.operationType,
})) {
const metric = config.metrics[phase];
return typeof metric === 'object'
? metric
: metric === true
? createSummary({
registry: config.registry || defaultRegistry,
summary: {
name: typeof metric === 'string' ? metric : phase,
labelNames: ['operationType', 'operationName'].filter(label => labelExists(config, label)),
...summary,
},
fillLabelsFn,
})
: undefined;
}
export function getCounterFromConfig(config, phase, counter, fillLabelsFn = params => filterFillParamsFnParams(config, {
operationName: params.operationName,
operationType: params.operationType,
})) {
const metric = config.metrics[phase];
return typeof metric === 'object'
? metric
: metric === true
? createCounter({
registry: config.registry || defaultRegistry,
counter: {
name: typeof metric === 'string' ? metric : phase,
labelNames: ['operationType', 'operationName'].filter(label => labelExists(config, label)),
...counter,
},
fillLabelsFn,
})
: undefined;
}
export function extractDeprecatedFields(node, typeInfo) {
const found = [];
visit(node, visitWithTypeInfo(typeInfo, {
Argument: () => {
const argument = typeInfo.getArgument();
const field = typeInfo.getFieldDef();
if (field &&
argument &&
(argument.deprecationReason != null || argument.isDeprecated)) {
found.push({
fieldName: argument.name,
// the GraphQLArgument type doesn't contain context regarding the mutation the argument was passed to
// however, when visiting an argument, typeInfo.getFieldDef returns the mutation
typeName: field.name, // this is the mutation name
});
}
},
Field: () => {
const field = typeInfo.getFieldDef();
if (field && (field.deprecationReason != null || field.isDeprecated)) {
found.push({
fieldName: field.name,
typeName: typeInfo.getParentType().name || '',
});
}
},
}));
return found;
}
export function labelExists(config, label) {
const labelFlag = config.labels?.[label];
if (labelFlag == null) {
return true;
}
return labelFlag;
}
export function filterFillParamsFnParams(config, params) {
return Object.fromEntries(Object.entries(params).filter(([key]) => labelExists(config, key)));
}
const clearRegistry = new WeakMap();
export function instrumentRegistry(registry) {
if (!clearRegistry.has(registry)) {
clearRegistry.set(registry, registry.clear.bind(registry));
}
registry.clear = () => {
histograms.delete(registry);
summaries.delete(registry);
counters.delete(registry);
clearRegistry.get(registry)();
};
return registry;
}