@envelop/statsd
Version:
This plugin tracks the complete execution flow, and reports metrics using StatsD (based on `hot-shots`).
91 lines (90 loc) • 3.48 kB
JavaScript
import { isAsyncIterable, isIntrospectionOperationString, } from '@envelop/core';
export const metricNames = {
operationCount: 'operations.count',
errorCount: 'operations.error_count',
latency: 'operations.latency',
};
const statsDPluginTagsSymbol = Symbol('statsDPluginTagsSymbol');
const statsDPluginExecutionStartTimeSymbol = Symbol('statsDPluginExecutionStartTimeSymbol');
function getOperation(document) {
return document.definitions.find((def) => def.kind === 'OperationDefinition');
}
function isParseFailure(parseResult) {
return parseResult === null || parseResult instanceof Error;
}
function getTags(context) {
return context[statsDPluginTagsSymbol];
}
export const useStatsD = (options) => {
const { client, prefix = 'graphql', skipIntrospection = false } = options;
function createMetricName(name) {
return `${prefix}.${name}`;
}
function increaseErrorCount(tags) {
client.increment(createMetricName(metricNames.errorCount), tags);
}
function increaseOperationCount(tags) {
client.increment(createMetricName(metricNames.operationCount), tags);
}
return {
onEnveloped({ extendContext }) {
extendContext({
[statsDPluginExecutionStartTimeSymbol]: Date.now(),
});
},
onParse({ extendContext, params }) {
if (skipIntrospection && isIntrospectionOperationString(params.source)) {
return;
}
return function onParseDone(payload) {
if (isParseFailure(payload.result)) {
increaseErrorCount();
increaseOperationCount();
}
else {
const operation = getOperation(payload.result);
extendContext({
[statsDPluginTagsSymbol]: {
operation: operation?.name?.value || 'anonymous',
},
});
}
};
},
onValidate({ context }) {
const tags = getTags(context);
if (!tags) {
return undefined;
}
return function onValidateDone({ valid }) {
if (!valid) {
increaseErrorCount(tags);
increaseOperationCount(tags);
}
};
},
onExecute({ args }) {
const tags = getTags(args.contextValue);
if (!tags) {
return undefined;
}
return {
onExecuteDone({ result }) {
const latency = Date.now() - args.contextValue[statsDPluginExecutionStartTimeSymbol];
if (isAsyncIterable(result)) {
// eslint-disable-next-line no-console
console.warn(`Plugin "statsd" encountered a AsyncIterator which is not supported yet, so tracing data is not available for the operation.`);
return;
}
increaseOperationCount(tags);
if (result.errors && Array.isArray(result.errors)) {
increaseErrorCount(tags);
}
else {
client.histogram(createMetricName(metricNames.latency), latency, tags);
}
},
};
},
};
};