From b05f813adacaed7f3a2132da52552b4511ccbc15 Mon Sep 17 00:00:00 2001 From: Valentin Cocaud Date: Tue, 12 Nov 2024 13:44:52 +0100 Subject: [PATCH 1/4] fix: [plugins/prometheus] fixes for phases/shouldObserve implementation in Yoga --- packages/plugins/prometheus/src/index.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/plugins/prometheus/src/index.ts b/packages/plugins/prometheus/src/index.ts index 5ad379097..f5c405d7f 100644 --- a/packages/plugins/prometheus/src/index.ts +++ b/packages/plugins/prometheus/src/index.ts @@ -16,7 +16,13 @@ import { type OnPluginInitHook, } from '@envelop/core'; import { useOnResolve } from '@envelop/on-resolve'; -import { PrometheusTracingPluginConfig, type MetricsConfig } from './config.js'; +import { + CounterMetricOption, + HistogramMetricOption, + PrometheusTracingPluginConfig, + SummaryMetricOption, + type MetricsConfig, +} from './config.js'; import { createCounter, createFillLabelFnParams, @@ -41,6 +47,9 @@ export { HistogramAndLabels, PrometheusTracingPluginConfig, SummaryAndLabels, + HistogramMetricOption, + CounterMetricOption, + SummaryMetricOption, createCounter, createHistogram, createSummary, From 4a669205ed98dc2154f5e0ff1f39ed225304a978 Mon Sep 17 00:00:00 2001 From: Valentin Cocaud Date: Sat, 16 Nov 2024 00:02:41 +0100 Subject: [PATCH 2/4] make new API non breaking --- packages/plugins/prometheus/src/index.ts | 40 +++++++++++----------- packages/plugins/prometheus/src/utils.ts | 42 +++++++++++++++++------- 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/packages/plugins/prometheus/src/index.ts b/packages/plugins/prometheus/src/index.ts index f5c405d7f..ace1fc089 100644 --- a/packages/plugins/prometheus/src/index.ts +++ b/packages/plugins/prometheus/src/index.ts @@ -221,7 +221,7 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => deprecationCounter, schemaChangeCounter, ]) { - metric?.phases.forEach(phase => phasesToHook.add(phase)); + metric?.phases!.forEach(phase => phasesToHook.add(phase)); } const onParse: OnParseHook<{}> = ({ context, params }) => { @@ -239,7 +239,7 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => if (!fillLabelsFnParams) { // means that we got a parse error - if (errorsCounter?.shouldObserve({ error: params.result, errorPhase: 'parse' }, context)) { + if (errorsCounter?.shouldObserve!({ error: params.result, errorPhase: 'parse' }, context)) { // TODO: use fillLabelsFn errorsCounter?.counter.labels({ phase: 'parse' }).inc(); } @@ -249,7 +249,7 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => const totalTime = (Date.now() - startTime) / 1000; - if (parseHistogram?.shouldObserve(fillLabelsFnParams, context)) { + if (parseHistogram?.shouldObserve!(fillLabelsFnParams, context)) { parseHistogram?.histogram.observe( parseHistogram.fillLabelsFn(fillLabelsFnParams, context), totalTime, @@ -265,7 +265,7 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => deprecationInfo: depField, }; - if (deprecationCounter.shouldObserve(deprecationLabelParams, context)) { + if (deprecationCounter.shouldObserve!(deprecationLabelParams, context)) { deprecationCounter.counter .labels(deprecationCounter.fillLabelsFn(deprecationLabelParams, context)) .inc(); @@ -286,15 +286,15 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => return ({ valid }) => { const totalTime = (Date.now() - startTime) / 1000; let labels; - if (validateHistogram?.shouldObserve(fillLabelsFnParams, context)) { + if (validateHistogram?.shouldObserve!(fillLabelsFnParams, context)) { labels = validateHistogram.fillLabelsFn(fillLabelsFnParams, context); validateHistogram.histogram.observe(labels, totalTime); } if ( !valid && - errorsCounter?.phases.includes('validate') && - errorsCounter?.shouldObserve(fillLabelsFnParams, context) + errorsCounter?.phases!.includes('validate') && + errorsCounter?.shouldObserve!(fillLabelsFnParams, context) ) { // TODO: we should probably iterate over validation errors to report each error. errorsCounter?.counter @@ -311,7 +311,7 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => const fillLabelsFnParams = fillLabelsFnParamsMap.get(context); if ( !fillLabelsFnParams || - !contextBuildingHistogram?.shouldObserve(fillLabelsFnParams, context) + !contextBuildingHistogram?.shouldObserve!(fillLabelsFnParams, context) ) { return; } @@ -333,16 +333,16 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => return; } - const shouldObserveRequsets = reqCounter?.shouldObserve(fillLabelsFnParams, args.contextValue); - const shouldObserveExecute = executeHistogram?.shouldObserve( + const shouldObserveRequsets = reqCounter?.shouldObserve!(fillLabelsFnParams, args.contextValue); + const shouldObserveExecute = executeHistogram?.shouldObserve!( fillLabelsFnParams, args.contextValue, ); - const shouldObserveRequestTotal = requestTotalHistogram?.shouldObserve( + const shouldObserveRequestTotal = requestTotalHistogram?.shouldObserve!( fillLabelsFnParams, args.contextValue, ); - const shouldObserveSummary = requestSummary?.shouldObserve( + const shouldObserveSummary = requestSummary?.shouldObserve!( fillLabelsFnParams, args.contextValue, ); @@ -375,7 +375,7 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => error, }; - if (errorsCounter!.shouldObserve(labelParams, args.contextValue)) { + if (errorsCounter!.shouldObserve!(labelParams, args.contextValue)) { errorsCounter!.counter .labels(errorsCounter!.fillLabelsFn(labelParams, args.contextValue)) .inc(); @@ -441,16 +441,16 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => return undefined; } - const shouldObserveRequsets = reqCounter?.shouldObserve(fillLabelsFnParams, args.contextValue); - const shouldObserveExecute = executeHistogram?.shouldObserve( + const shouldObserveRequsets = reqCounter?.shouldObserve!(fillLabelsFnParams, args.contextValue); + const shouldObserveExecute = executeHistogram?.shouldObserve!( fillLabelsFnParams, args.contextValue, ); - const shouldObserveRequestTotal = requestTotalHistogram?.shouldObserve( + const shouldObserveRequestTotal = requestTotalHistogram?.shouldObserve!( fillLabelsFnParams, args.contextValue, ); - const shouldObserveSummary = requestSummary?.shouldObserve( + const shouldObserveSummary = requestSummary?.shouldObserve!( fillLabelsFnParams, args.contextValue, ); @@ -573,7 +573,7 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => registerContextErrorHandler(({ context, error }) => { const fillLabelsFnParams = fillLabelsFnParamsMap.get(context); if ( - errorsCounter.shouldObserve( + errorsCounter.shouldObserve!( { error: error as GraphQLError, errorPhase: 'context', ...fillLabelsFnParamsMap }, context, ) @@ -610,14 +610,14 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => onSchemaChange({ schema }) { typeInfo = new TypeInfo(schema); - if (schemaChangeCounter?.shouldObserve({}, null) && !countedSchemas.has(schema)) { + if (schemaChangeCounter?.shouldObserve!({}, null) && !countedSchemas.has(schema)) { schemaChangeCounter.counter.inc(); countedSchemas.add(schema); } }, onEnveloped: hookIf('execute', onEnveloped) ?? hookIf('subscribe', onEnveloped), onPluginInit: - errorsCounter?.phases.includes('context') || resolversHistogram ? onPluginInit : undefined, + errorsCounter?.phases!.includes('context') || resolversHistogram ? onPluginInit : undefined, onParse: hookIf('parse', onParse), onValidate: hookIf('validate', onValidate), onContextBuilding: hookIf('context', onContextBuilding), diff --git a/packages/plugins/prometheus/src/utils.ts b/packages/plugins/prometheus/src/utils.ts index a90bfef8a..7181644bf 100644 --- a/packages/plugins/prometheus/src/utils.ts +++ b/packages/plugins/prometheus/src/utils.ts @@ -95,8 +95,8 @@ export type HistogramAndLabels< > = { histogram: Histogram; fillLabelsFn: FillLabelsFn; - phases: Phases; - shouldObserve: ShouldObservePredicate; + phases?: Phases; + shouldObserve?: ShouldObservePredicate; }; export function registerHistogram( @@ -142,8 +142,10 @@ export function createHistogram< * * The possible values accepted in this list depends on the metric, * please refer to metric type or documentation to know which phases ar available. + * + * By default, all available phases are observed */ - phases: Phases; + phases?: Phases; /** * A function called for each event that can be observed. * If it is provided, an event will be observed only if it returns true. @@ -167,8 +169,8 @@ export type SummaryAndLabels< > = { summary: Summary; fillLabelsFn: FillLabelsFn; - phases: Phases; - shouldObserve: ShouldObservePredicate; + phases?: Phases; + shouldObserve?: ShouldObservePredicate; }; export function registerSummary( @@ -214,8 +216,10 @@ export function createSummary< * * The possible values accepted in this list depends on the metric, * please refer to metric type or documentation to know which phases ar available. + * + * By default, all available phases are observed */ - phases: Phases; + phases?: Phases; /** * A function called for each event that can be observed. * If it is provided, an event will be observed only if it returns true. @@ -239,8 +243,8 @@ export type CounterAndLabels< > = { counter: Counter; fillLabelsFn: FillLabelsFn; - phases: Phases; - shouldObserve: ShouldObservePredicate; + phases?: Phases; + shouldObserve?: ShouldObservePredicate; }; /** @@ -286,8 +290,10 @@ export function createCounter< * * The possible values accepted in this list depends on the metric, * please refer to metric type or documentation to know which phases ar available. + * + * By default, all available phases are observed */ - phases: Phases; + phases?: Phases; /** * A function called for each event that can be observed. * If it is provided, an event will be observed only if it returns true. @@ -332,7 +338,11 @@ export function getHistogramFromConfig< ); } } else if (typeof metric === 'object') { - return metric as HistogramAndLabels; + const customMetric = metric as HistogramAndLabels; + if (!customMetric.phases) { + customMetric.phases = availablePhases; + } + return customMetric; } if (metric !== true) { @@ -380,7 +390,11 @@ export function getSummaryFromConfig< ); } } else if (typeof metric === 'object') { - return metric as SummaryAndLabels; + const customMetric = metric as SummaryAndLabels; + if (!customMetric.phases) { + customMetric.phases = availablePhases; + } + return customMetric; } if (metric !== true) { @@ -426,7 +440,11 @@ export function getCounterFromConfig< ); } } else if (typeof metric === 'object') { - return metric as CounterAndLabels; + const customMetric = metric as CounterAndLabels; + if (!customMetric.phases) { + customMetric.phases = availablePhases; + } + return customMetric; } if (metric !== true) { From c3b9e71dd9b6642896ecad66264a20398b56d74f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 15 Nov 2024 23:03:12 +0000 Subject: [PATCH 3/4] chore(dependencies): updated changesets for modified dependencies --- .changeset/@envelop_graphql-jit-2326-dependencies.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/@envelop_graphql-jit-2326-dependencies.md diff --git a/.changeset/@envelop_graphql-jit-2326-dependencies.md b/.changeset/@envelop_graphql-jit-2326-dependencies.md new file mode 100644 index 000000000..79077aee0 --- /dev/null +++ b/.changeset/@envelop_graphql-jit-2326-dependencies.md @@ -0,0 +1,5 @@ +--- +"@envelop/graphql-jit": patch +--- +dependencies updates: + - Updated dependency [`graphql-jit@0.8.7` ↗︎](https://www.npmjs.com/package/graphql-jit/v/0.8.7) (from `0.8.6`, in `dependencies`) From 78ea8d1b8abb6ba89f9a443e4742e9c43bb1b145 Mon Sep 17 00:00:00 2001 From: Valentin Cocaud Date: Tue, 19 Nov 2024 12:05:19 +0100 Subject: [PATCH 4/4] refactor to conditionally observe events based on phase --- packages/plugins/prometheus/src/index.ts | 699 +++++++++--------- packages/plugins/prometheus/test/prom.spec.ts | 166 +++-- pnpm-lock.yaml | 86 +-- 3 files changed, 532 insertions(+), 419 deletions(-) diff --git a/packages/plugins/prometheus/src/index.ts b/packages/plugins/prometheus/src/index.ts index ace1fc089..9337fccf1 100644 --- a/packages/plugins/prometheus/src/index.ts +++ b/packages/plugins/prometheus/src/index.ts @@ -1,19 +1,24 @@ /* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ -import { ExecutionResult, GraphQLSchema, TypeInfo, type GraphQLError } from 'graphql'; +import { + ExecutionResult, + GraphQLSchema, + OperationTypeNode, + TypeInfo, + type GraphQLError, +} from 'graphql'; import { register as defaultRegistry } from 'prom-client'; import { isAsyncIterable, isIntrospectionOperationString, OnContextBuildingHook, OnExecuteHook, - OnExecuteHookResult, OnParseHook, OnSubscribeHook, - OnSubscribeHookResult, OnValidateHook, Plugin, type OnEnvelopedHook, type OnPluginInitHook, + type OnSchemaChangeHook, } from '@envelop/core'; import { useOnResolve } from '@envelop/on-resolve'; import { @@ -61,10 +66,38 @@ export { export const fillLabelsFnParamsMap = new WeakMap(); export const execStartTimeMap = new WeakMap(); +type PhaseHandler = {}, Params = FillLabelsFnParams> = { + shouldHandle: (params: Params, context: unknown) => boolean; + handler: ( + args: OtherArgs & { + params: Params; + context: unknown; + totalTime: number; + }, + ) => void; +}; + export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => { - let typeInfo: TypeInfo | null = null; config.registry = instrumentRegistry(config.registry || defaultRegistry); + const phasesToHook = { + parse: [] as PhaseHandler[], + validate: [] as PhaseHandler[], + context: [] as PhaseHandler[], + execute: { + end: [] as PhaseHandler[], + result: [] as PhaseHandler<{ result: ExecutionResult }>[], + }, + subscribe: { + end: [] as PhaseHandler[], + result: [] as PhaseHandler<{ result: ExecutionResult }>[], + error: [] as PhaseHandler<{ error: unknown }>[], + }, + pluginInit: [] as OnPluginInitHook>[], + enveloped: [] as OnEnvelopedHook>[], + schema: [] as OnSchemaChangeHook[], + }; + const parseHistogram = getHistogramFromConfig<['parse'], MetricsConfig>( config, 'graphql_envelop_phase_parse', @@ -73,6 +106,15 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => help: 'Time spent on running GraphQL "parse" function', }, ); + if (parseHistogram) { + phasesToHook.parse.push({ + shouldHandle: parseHistogram.shouldObserve!, + handler: ({ params, context, totalTime }) => { + parseHistogram.histogram.observe(parseHistogram.fillLabelsFn(params, context), totalTime); + }, + }); + } + const validateHistogram = getHistogramFromConfig<['validate'], MetricsConfig>( config, 'graphql_envelop_phase_validate', @@ -81,6 +123,16 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => help: 'Time spent on running GraphQL "validate" function', }, ); + if (validateHistogram) { + phasesToHook.validate.push({ + shouldHandle: validateHistogram.shouldObserve!, + handler: ({ params, context, totalTime }) => { + const labels = validateHistogram.fillLabelsFn(params, context); + validateHistogram.histogram.observe(labels, totalTime); + }, + }); + } + const contextBuildingHistogram = getHistogramFromConfig<['context'], MetricsConfig>( config, 'graphql_envelop_phase_context', @@ -89,6 +141,16 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => help: 'Time spent on building the GraphQL context', }, ); + if (contextBuildingHistogram) { + phasesToHook.context.push({ + shouldHandle: contextBuildingHistogram.shouldObserve!, + handler: ({ params, context, totalTime }) => { + const labels = contextBuildingHistogram.fillLabelsFn(params, context); + contextBuildingHistogram.histogram.observe(labels, totalTime); + }, + }); + } + const executeHistogram = getHistogramFromConfig<['execute'], MetricsConfig>( config, 'graphql_envelop_phase_execute', @@ -97,6 +159,16 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => help: 'Time spent on running the GraphQL "execute" function', }, ); + if (executeHistogram) { + phasesToHook.execute.end.push({ + shouldHandle: executeHistogram.shouldObserve!, + handler: ({ params, context, totalTime }) => { + const labels = executeHistogram.fillLabelsFn(params, context); + executeHistogram.histogram.observe(labels, totalTime); + }, + }); + } + const subscribeHistogram = getHistogramFromConfig<['subscribe'], MetricsConfig>( config, 'graphql_envelop_phase_subscribe', @@ -105,6 +177,15 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => help: 'Time spent on running the GraphQL "subscribe" function', }, ); + if (subscribeHistogram) { + phasesToHook.subscribe.end.push({ + shouldHandle: subscribeHistogram.shouldObserve!, + handler: ({ params, context, totalTime }) => { + const labels = subscribeHistogram.fillLabelsFn(params, context); + subscribeHistogram.histogram.observe(labels, totalTime); + }, + }); + } const resolversHistogram = getHistogramFromConfig<['execute', 'subscribe'], MetricsConfig>( config, @@ -123,6 +204,41 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => returnType: params.info?.returnType.toString()!, }), ); + if (resolversHistogram) { + phasesToHook.pluginInit.push(({ addPlugin }) => { + addPlugin( + useOnResolve(({ info, context }) => { + const phase = + info.operation.operation === OperationTypeNode.SUBSCRIPTION ? 'subscribe' : 'execute'; + + if ( + !resolversHistogram.phases?.includes(phase) || + !shouldTraceFieldResolver(info, config.resolversWhitelist) + ) { + return undefined; + } + + const fillLabelsFnParams = fillLabelsFnParamsMap.get(context); + const paramsCtx = { ...fillLabelsFnParams, info }; + + if (!resolversHistogram.shouldObserve!(paramsCtx, context)) { + return undefined; + } + + const startTime = Date.now(); + + return () => { + const totalTime = (Date.now() - startTime) / 1000; + + resolversHistogram.histogram.observe( + resolversHistogram.fillLabelsFn(paramsCtx, context), + totalTime, + ); + }; + }), + ); + }); + } const requestTotalHistogram = getHistogramFromConfig<['execute', 'subscribe'], MetricsConfig>( config, @@ -132,6 +248,18 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => help: 'Time spent on running the GraphQL operation from parse to execute', }, ); + if (requestTotalHistogram) { + const handler: PhaseHandler = { + shouldHandle: requestTotalHistogram.shouldObserve!, + handler: ({ params, context, totalTime }) => { + const labels = requestTotalHistogram!.fillLabelsFn(params, context); + requestTotalHistogram!.histogram.observe(labels, totalTime); + }, + }; + for (const phase of requestTotalHistogram.phases!) { + phasesToHook[phase].end.push(handler); + } + } const requestSummary = getSummaryFromConfig<['execute', 'subscribe'], MetricsConfig>( config, @@ -141,6 +269,26 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => help: 'Summary to measure the time to complete GraphQL operations', }, ); + if (requestSummary) { + phasesToHook.enveloped.push(({ context }) => { + if (!execStartTimeMap.has(context)) { + execStartTimeMap.set(context, Date.now()); + } + }); + const handler: PhaseHandler = { + shouldHandle: (params, context) => + requestSummary.shouldObserve!(params, context) && execStartTimeMap.has(context), + handler: ({ params, context }) => { + const execStartTime = execStartTimeMap.get(context); + const summaryTime = (Date.now() - execStartTime!) / 1000; + const labels = requestSummary!.fillLabelsFn(params, context); + requestSummary!.summary.observe(labels, summaryTime); + }, + }; + for (const phase of requestSummary.phases!) { + phasesToHook[phase].end.push(handler); + } + } const errorsCounter = getCounterFromConfig< ['parse', 'validate', 'context', 'execute', 'subscribe'], @@ -167,6 +315,70 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => return filterFillParamsFnParams(config, labels); }, ); + if (errorsCounter) { + (['parse', 'validate'] as const) + .filter(phase => errorsCounter.phases!.includes(phase)) + .forEach(phase => { + phasesToHook[phase].push({ + shouldHandle: (params, context) => + !!params.errorPhase && errorsCounter.shouldObserve!(params, context), + handler: ({ params, context }) => { + const labels = errorsCounter.fillLabelsFn(params, context); + errorsCounter?.counter.labels(labels).inc(); + }, + }); + }); + + (['execute', 'subscribe'] as const) + .filter(phase => errorsCounter.phases!.includes(phase)) + .forEach(phase => { + phasesToHook[phase].result.push({ + shouldHandle: errorsCounter.shouldObserve!, + handler: ({ result, params, context }) => { + if (!result.errors?.length) { + return; + } + for (const error of result.errors) { + const labelParams = { ...params, errorPhase: 'execute', error }; + + if (errorsCounter!.shouldObserve!(labelParams, context)) { + errorsCounter!.counter + .labels(errorsCounter!.fillLabelsFn(labelParams, context)) + .inc(); + } + } + }, + }); + }); + + if (errorsCounter.phases!.includes('subscribe')) { + phasesToHook.subscribe.error.push({ + shouldHandle: errorsCounter.shouldObserve!, + handler: ({ params, context, error }) => { + const labels = errorsCounter.fillLabelsFn(params, context); + errorsCounter.counter.labels(labels).inc(); + }, + }); + } + + if (errorsCounter.phases!.includes('context')) { + phasesToHook.pluginInit.push(({ registerContextErrorHandler }) => { + registerContextErrorHandler(({ context, error }) => { + const fillLabelsFnParams = fillLabelsFnParamsMap.get(context); + // FIXME: unsafe cast here, but it's ok, fillabelfn is doing duck typing anyway + const params = { + error: error as GraphQLError, + errorPhase: 'context', + ...fillLabelsFnParams, + }; + + if (errorsCounter.shouldObserve!(params, context)) { + errorsCounter.counter.labels(errorsCounter?.fillLabelsFn(params, context)).inc(); + } + }); + }); + } + } const reqCounter = getCounterFromConfig<['execute', 'subscribe'], MetricsConfig>( config, @@ -176,6 +388,17 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => help: 'Counts the amount of GraphQL requests executed through Envelop', }, ); + if (reqCounter) { + const handler: PhaseHandler = { + shouldHandle: reqCounter.shouldObserve!, + handler: ({ params, context }) => { + reqCounter!.counter.labels(reqCounter!.fillLabelsFn(params, context)).inc(); + }, + }; + for (const phase of reqCounter.phases!) { + phasesToHook[phase].end.push(handler); + } + } const deprecationCounter = getCounterFromConfig<['parse'], MetricsConfig>( config, @@ -193,6 +416,34 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => typeName: params.deprecationInfo?.typeName!, }), ); + if (deprecationCounter) { + let typeInfo: TypeInfo | null = null; + phasesToHook.schema.push(({ schema }) => { + typeInfo = new TypeInfo(schema); + }); + + phasesToHook.parse.push({ + shouldHandle: (params, context) => + // If parse error happens, we can't explore the query document + !!typeInfo && !params.errorPhase && deprecationCounter.shouldObserve!(params, context), + handler: ({ params, context }) => { + const deprecatedFields = extractDeprecatedFields(params.document!, typeInfo!); + + for (const depField of deprecatedFields) { + const deprecationLabelParams = { + ...params, + deprecationInfo: depField, + }; + + if (deprecationCounter.shouldObserve!(deprecationLabelParams, context)) { + deprecationCounter.counter + .labels(deprecationCounter.fillLabelsFn(deprecationLabelParams, context)) + .inc(); + } + } + }, + }); + } const schemaChangeCounter = getCounterFromConfig<['schema'], MetricsConfig>( config, @@ -204,24 +455,15 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => }, () => ({}), ); + if (schemaChangeCounter) { + const countedSchemas = new WeakSet(); - // parse is mandatory, because it sets up label params for the whole request. - const phasesToHook = new Set(['parse']); - for (const metric of [ - parseHistogram, - validateHistogram, - contextBuildingHistogram, - executeHistogram, - subscribeHistogram, - resolversHistogram, - requestTotalHistogram, - requestSummary, - errorsCounter, - reqCounter, - deprecationCounter, - schemaChangeCounter, - ]) { - metric?.phases!.forEach(phase => phasesToHook.add(phase)); + phasesToHook.schema.push(({ schema }) => { + if (schemaChangeCounter?.shouldObserve!({}, null) && !countedSchemas.has(schema)) { + schemaChangeCounter.counter.inc(); + countedSchemas.add(schema); + } + }); } const onParse: OnParseHook<{}> = ({ context, params }) => { @@ -237,41 +479,15 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => ); fillLabelsFnParamsMap.set(context, fillLabelsFnParams); - if (!fillLabelsFnParams) { - // means that we got a parse error - if (errorsCounter?.shouldObserve!({ error: params.result, errorPhase: 'parse' }, context)) { - // TODO: use fillLabelsFn - errorsCounter?.counter.labels({ phase: 'parse' }).inc(); - } - // TODO: We should probably always report parse timing, error or not. - return; - } - - const totalTime = (Date.now() - startTime) / 1000; - - if (parseHistogram?.shouldObserve!(fillLabelsFnParams, context)) { - parseHistogram?.histogram.observe( - parseHistogram.fillLabelsFn(fillLabelsFnParams, context), - totalTime, - ); - } - - if (deprecationCounter && typeInfo) { - const deprecatedFields = extractDeprecatedFields(fillLabelsFnParams.document!, typeInfo); - - for (const depField of deprecatedFields) { - const deprecationLabelParams = { - ...fillLabelsFnParams, - deprecationInfo: depField, - }; + const args = { + context, + totalTime: (Date.now() - startTime) / 1000, + params: fillLabelsFnParams ?? { error: params.result, errorPhase: 'parse' }, + }; - if (deprecationCounter.shouldObserve!(deprecationLabelParams, context)) { - deprecationCounter.counter - .labels(deprecationCounter.fillLabelsFn(deprecationLabelParams, context)) - .inc(); - } - } - } + phasesToHook.parse + .filter(({ shouldHandle }) => shouldHandle(args.params, context)) + .forEach(({ handler }) => handler(args)); }; }; @@ -284,344 +500,169 @@ export const usePrometheus = (config: PrometheusTracingPluginConfig): Plugin => const startTime = Date.now(); return ({ valid }) => { - const totalTime = (Date.now() - startTime) / 1000; - let labels; - if (validateHistogram?.shouldObserve!(fillLabelsFnParams, context)) { - labels = validateHistogram.fillLabelsFn(fillLabelsFnParams, context); - validateHistogram.histogram.observe(labels, totalTime); - } + const args = { + params: valid ? fillLabelsFnParams : { ...fillLabelsFnParams, errorPhase: 'validate' }, + context, + totalTime: (Date.now() - startTime) / 1000, + }; - if ( - !valid && - errorsCounter?.phases!.includes('validate') && - errorsCounter?.shouldObserve!(fillLabelsFnParams, context) - ) { - // TODO: we should probably iterate over validation errors to report each error. - errorsCounter?.counter - // TODO: Use fillLabelsFn - .labels( - errorsCounter.fillLabelsFn({ ...fillLabelsFnParams, errorPhase: 'validate' }, context), - ) - .inc(); - } + phasesToHook.validate + .filter(({ shouldHandle }) => shouldHandle(args.params, context)) + .forEach(({ handler }) => handler(args)); + + // TODO: we should probably iterate over validation errors to report each error. }; }; const onContextBuilding: OnContextBuildingHook<{}> | undefined = ({ context }) => { const fillLabelsFnParams = fillLabelsFnParamsMap.get(context); - if ( - !fillLabelsFnParams || - !contextBuildingHistogram?.shouldObserve!(fillLabelsFnParams, context) - ) { + if (!fillLabelsFnParams) { return; } const startTime = Date.now(); - return () => { - const totalTime = (Date.now() - startTime) / 1000; - contextBuildingHistogram.histogram.observe( - contextBuildingHistogram.fillLabelsFn(fillLabelsFnParams, context), - totalTime, - ); + const args = { + context, + params: fillLabelsFnParams, + totalTime: (Date.now() - startTime) / 1000, }; + + phasesToHook.context + .filter(({ shouldHandle }) => shouldHandle(fillLabelsFnParams, context)) + .forEach(({ handler }) => handler(args)); }; - const onExecute: OnExecuteHook<{}> | undefined = ({ args }) => { - const fillLabelsFnParams = fillLabelsFnParamsMap.get(args.contextValue); + const onExecute: OnExecuteHook<{}> = ({ args: { contextValue: context } }) => { + const fillLabelsFnParams = fillLabelsFnParamsMap.get(context); if (!fillLabelsFnParams) { return; } - const shouldObserveRequsets = reqCounter?.shouldObserve!(fillLabelsFnParams, args.contextValue); - const shouldObserveExecute = executeHistogram?.shouldObserve!( - fillLabelsFnParams, - args.contextValue, + const endHandlers = phasesToHook.execute.end.filter(({ shouldHandle }) => + shouldHandle(fillLabelsFnParams, context), ); - const shouldObserveRequestTotal = requestTotalHistogram?.shouldObserve!( - fillLabelsFnParams, - args.contextValue, + const resultHandlers = phasesToHook.execute.result.filter(({ shouldHandle }) => + shouldHandle(fillLabelsFnParams, context), ); - const shouldObserveSummary = requestSummary?.shouldObserve!( - fillLabelsFnParams, - args.contextValue, - ); - - const shouldHandleEnd = - shouldObserveRequsets || - shouldObserveExecute || - shouldObserveRequestTotal || - shouldObserveSummary; - const shouldHandleResult = errorsCounter !== undefined; - - if (!shouldHandleEnd && !shouldHandleResult) { - return; + if (endHandlers.length + resultHandlers.length === 0) { + return undefined; } const startTime = Date.now(); - if (shouldObserveRequsets) { - reqCounter?.counter - .labels(reqCounter.fillLabelsFn(fillLabelsFnParams, args.contextValue)) - .inc(); - } - - function handleResult(result: ExecutionResult) { - if (result.errors && result.errors.length > 0) { - for (const error of result.errors) { - const labelParams = { - ...fillLabelsFnParams, - errorPhase: 'execute', - error, - }; - if (errorsCounter!.shouldObserve!(labelParams, args.contextValue)) { - errorsCounter!.counter - .labels(errorsCounter!.fillLabelsFn(labelParams, args.contextValue)) - .inc(); - } - } - } + function handleResult({ result }: { result: ExecutionResult }) { + const totalTime = (Date.now() - startTime) / 1000; + const args = { params: fillLabelsFnParams!, context, totalTime, result }; + resultHandlers.forEach(({ handler }) => handler(args)); } - const result: OnExecuteHookResult<{}> = { - onExecuteDone: ({ result }) => { - const handleEnd = () => { - const totalTime = (Date.now() - startTime) / 1000; - if (shouldObserveExecute) { - executeHistogram!.histogram.observe( - executeHistogram!.fillLabelsFn(fillLabelsFnParams, args.contextValue), - totalTime, - ); - } - - if (shouldObserveRequestTotal) { - requestTotalHistogram!.histogram.observe( - requestTotalHistogram!.fillLabelsFn(fillLabelsFnParams, args.contextValue), - totalTime, - ); - } - - if (shouldObserveSummary) { - const execStartTime = execStartTimeMap.get(args.contextValue); - if (execStartTime) { - const summaryTime = (Date.now() - execStartTime) / 1000; - - requestSummary!.summary.observe( - requestSummary!.fillLabelsFn(fillLabelsFnParams, args.contextValue), - summaryTime, - ); - } - } - }; + const handleEnd = () => { + const totalTime = (Date.now() - startTime) / 1000; + const args = { params: fillLabelsFnParams, context, totalTime }; + endHandlers.forEach(({ handler }) => handler(args)); + }; - if (!isAsyncIterable(result)) { - shouldHandleResult && handleResult(result); - shouldHandleEnd && handleEnd(); - return undefined; - } else { + return { + onExecuteDone: ({ result }) => { + if (isAsyncIterable(result)) { return { - onNext: shouldHandleResult - ? ({ result }) => { - handleResult(result); - } - : undefined, - onEnd: shouldHandleEnd ? handleEnd : undefined, + onNext: resultHandlers.length ? handleResult : undefined, + onEnd: endHandlers.length ? handleEnd : undefined, }; + } else { + handleResult({ result }); + handleEnd(); + return undefined; } }, }; - - return result; }; - const onSubscribe: OnSubscribeHook<{}> | undefined = ({ args }) => { - const fillLabelsFnParams = fillLabelsFnParamsMap.get(args.contextValue); + const onSubscribe: OnSubscribeHook<{}> = ({ args: { contextValue: context } }) => { + const fillLabelsFnParams = fillLabelsFnParamsMap.get(context); if (!fillLabelsFnParams) { - return undefined; + return; } - const shouldObserveRequsets = reqCounter?.shouldObserve!(fillLabelsFnParams, args.contextValue); - const shouldObserveExecute = executeHistogram?.shouldObserve!( - fillLabelsFnParams, - args.contextValue, + const endHandlers = phasesToHook.subscribe.end.filter(({ shouldHandle }) => + shouldHandle(fillLabelsFnParams, context), ); - const shouldObserveRequestTotal = requestTotalHistogram?.shouldObserve!( - fillLabelsFnParams, - args.contextValue, + const resultHandlers = phasesToHook.subscribe.result.filter(({ shouldHandle }) => + shouldHandle(fillLabelsFnParams, context), ); - const shouldObserveSummary = requestSummary?.shouldObserve!( - fillLabelsFnParams, - args.contextValue, + const errorHandlers = phasesToHook.subscribe.error.filter(({ shouldHandle }) => + shouldHandle(fillLabelsFnParams, context), ); - const shouldHandleEnd = - shouldObserveRequsets || - shouldObserveExecute || - shouldObserveRequestTotal || - shouldObserveSummary; - - const shouldHandleResult = errorsCounter !== undefined; - - const startTime = Date.now(); - if (shouldObserveRequsets) { - reqCounter?.counter - .labels(reqCounter.fillLabelsFn(fillLabelsFnParams, args.contextValue)) - .inc(); + if (endHandlers.length + resultHandlers.length + errorHandlers.length === 0) { + return undefined; } - function handleResult(result: ExecutionResult) { - if (errorsCounter && result.errors && result.errors.length > 0) { - for (const error of result.errors) { - errorsCounter.counter - .labels( - errorsCounter.fillLabelsFn( - { - ...fillLabelsFnParams, - errorPhase: 'execute', - error, - }, - args.contextValue, - ), - ) - .inc(); - } - } - } + const startTime = Date.now(); - if (!shouldHandleEnd && !shouldHandleResult) { - return; + function handleResult({ result }: { result: ExecutionResult }) { + const totalTime = (Date.now() - startTime) / 1000; + const args = { params: fillLabelsFnParams!, context, totalTime, result }; + resultHandlers.forEach(({ handler }) => handler(args)); } - const result: OnSubscribeHookResult<{}> = { - onSubscribeResult: ({ result }) => { - const handleEnd = () => { - const totalTime = (Date.now() - startTime) / 1000; - if (shouldObserveExecute) { - subscribeHistogram!.histogram.observe( - subscribeHistogram!.fillLabelsFn(fillLabelsFnParams, args.contextValue), - totalTime, - ); - } - if (shouldObserveRequestTotal) { - requestTotalHistogram?.histogram.observe( - requestTotalHistogram.fillLabelsFn(fillLabelsFnParams, args.contextValue), - totalTime, - ); - } + const handleEnd = () => { + const totalTime = (Date.now() - startTime) / 1000; + const args = { params: fillLabelsFnParams, context, totalTime }; + endHandlers.forEach(({ handler }) => handler(args)); + }; - if (shouldObserveSummary) { - const execStartTime = execStartTimeMap.get(args.contextValue); - if (execStartTime) { - const summaryTime = (Date.now() - execStartTime) / 1000; + const handleError = ({ error }: { error: unknown }) => { + const totalTime = (Date.now() - startTime) / 1000; + const args = { params: fillLabelsFnParams, context, totalTime, error }; + errorHandlers.forEach(({ handler }) => handler(args)); + }; - requestSummary!.summary.observe( - requestSummary!.fillLabelsFn(fillLabelsFnParams, args.contextValue), - summaryTime, - ); - } - } - }; - if (!isAsyncIterable(result)) { - shouldHandleResult && handleResult(result); - shouldHandleEnd && handleEnd(); - return undefined; - } else { + return { + onSubscribeResult: ({ result }) => { + if (isAsyncIterable(result)) { return { - onNext: shouldHandleResult - ? ({ result }) => { - handleResult(result); - } - : undefined, - onEnd: shouldHandleEnd ? handleEnd : undefined, + onNext: resultHandlers.length ? handleResult : undefined, + onEnd: endHandlers.length ? handleEnd : undefined, }; + } else { + handleResult({ result }); + handleEnd(); + return undefined; } }, + onSubscribeError: errorHandlers.length ? handleError : undefined, }; - - return result; }; - const onPluginInit: OnPluginInitHook<{}> = ({ addPlugin, registerContextErrorHandler }) => { - if (resolversHistogram) { - addPlugin( - useOnResolve(({ info, context }) => { - const shouldTrace = shouldTraceFieldResolver(info, config.resolversWhitelist); - - if (!shouldTrace) { - return undefined; - } - - const startTime = Date.now(); - - return () => { - const totalTime = (Date.now() - startTime) / 1000; - const fillLabelsFnParams = fillLabelsFnParamsMap.get(context); - const paramsCtx = { - ...fillLabelsFnParams, - info, - }; - resolversHistogram.histogram.observe( - resolversHistogram.fillLabelsFn(paramsCtx, context), - totalTime, - ); - }; - }), - ); - } - if (errorsCounter) { - registerContextErrorHandler(({ context, error }) => { - const fillLabelsFnParams = fillLabelsFnParamsMap.get(context); - if ( - errorsCounter.shouldObserve!( - { error: error as GraphQLError, errorPhase: 'context', ...fillLabelsFnParamsMap }, - context, - ) - ) { - errorsCounter.counter - .labels( - errorsCounter?.fillLabelsFn( - // FIXME: unsafe cast here, but it's ok, fillabelfn is doing duck typing anyway - { ...fillLabelsFnParams, errorPhase: 'context', error: error as GraphQLError }, - context, - ), - ) - .inc(); - } - }); + const onPluginInit: OnPluginInitHook<{}> = payload => { + for (const handler of phasesToHook.pluginInit) { + handler(payload); } }; - const onEnveloped: OnEnvelopedHook<{}> = ({ context }) => { - if (!execStartTimeMap.has(context)) { - execStartTimeMap.set(context, Date.now()); + const onEnveloped: OnEnvelopedHook<{}> = payload => { + for (const handler of phasesToHook.enveloped) { + handler(payload); } }; - function hookIf(phase: string, hook: T): T | undefined { - if (phasesToHook.has(phase)) { - return hook; + const onSchemaChange: OnSchemaChangeHook = payload => { + for (const handler of phasesToHook.schema) { + handler(payload); } - return undefined; - } + }; - const countedSchemas = new WeakSet(); return { - onSchemaChange({ schema }) { - typeInfo = new TypeInfo(schema); - - if (schemaChangeCounter?.shouldObserve!({}, null) && !countedSchemas.has(schema)) { - schemaChangeCounter.counter.inc(); - countedSchemas.add(schema); - } - }, - onEnveloped: hookIf('execute', onEnveloped) ?? hookIf('subscribe', onEnveloped), - onPluginInit: - errorsCounter?.phases!.includes('context') || resolversHistogram ? onPluginInit : undefined, - onParse: hookIf('parse', onParse), - onValidate: hookIf('validate', onValidate), - onContextBuilding: hookIf('context', onContextBuilding), - onExecute: hookIf('execute', onExecute), - onSubscribe: hookIf('subscribe', onSubscribe), + onParse, // onParse is required, because it sets up the label params WeakMap + onSchemaChange: phasesToHook.schema.length ? onSchemaChange : undefined, + onPluginInit: phasesToHook.pluginInit.length ? onPluginInit : undefined, + onEnveloped: phasesToHook.enveloped.length ? onEnveloped : undefined, + onValidate: phasesToHook.validate.length ? onValidate : undefined, + onContextBuilding: phasesToHook.context.length ? onContextBuilding : undefined, + onExecute: phasesToHook.execute ? onExecute : undefined, + onSubscribe: phasesToHook.subscribe ? onSubscribe : undefined, }; }; diff --git a/packages/plugins/prometheus/test/prom.spec.ts b/packages/plugins/prometheus/test/prom.spec.ts index e2ba97e3a..5319551f6 100644 --- a/packages/plugins/prometheus/test/prom.spec.ts +++ b/packages/plugins/prometheus/test/prom.spec.ts @@ -1,6 +1,6 @@ import { ASTNode, buildSchema, print as graphQLPrint } from 'graphql'; import { Registry } from 'prom-client'; -import { useExtendContext } from '@envelop/core'; +import { Plugin, useExtendContext } from '@envelop/core'; import { assertSingleExecutionValue, createTestkit } from '@envelop/testing'; import { makeExecutableSchema } from '@graphql-tools/schema'; import { @@ -67,13 +67,22 @@ describe('Prom Metrics plugin', () => { }, }); - function prepare(config: PrometheusTracingPluginConfig, registry: Registry = new Registry()) { + function prepare( + config: PrometheusTracingPluginConfig, + registry: Registry = new Registry(), + plugins: Plugin[] = [], + ) { const plugin = usePrometheus({ ...config, registry, }); + const teskit = createTestkit( - [plugin, useExtendContext(() => new Promise(resolve => setTimeout(resolve, 250)))], + [ + plugin, + useExtendContext(() => new Promise(resolve => setTimeout(resolve, 250))), + ...plugins, + ], schema, ); @@ -152,10 +161,9 @@ describe('Prom Metrics plugin', () => { assertSingleExecutionValue(result); expect(result.errors?.length).toBe(1); - expect(await metricString('graphql_envelop_error_result')).toContain( - 'graphql_envelop_error_result{phase="parse"} 1', - ); + console.log(await metricString('graphql_envelop_error_result')); expect(await metricCount('graphql_envelop_error_result')).toBe(1); + expect(await metricString('graphql_envelop_error_result')).toContain('phase="parse"'); expect(await metricCount('graphql_envelop_phase_parse')).toBe(0); }); @@ -202,7 +210,6 @@ describe('Prom Metrics plugin', () => { opText: print(params.document!), }; }, - phases: ['parse'], }), }, }, @@ -237,7 +244,6 @@ describe('Prom Metrics plugin', () => { opText: print(params.document!), }; }, - phases: ['validate'], }), }, }, @@ -356,7 +362,6 @@ describe('Prom Metrics plugin', () => { opText: print(params.document!), }; }, - phases: ['context'], }), }, }, @@ -396,50 +401,30 @@ describe('Prom Metrics plugin', () => { it('should trace error and timing during contextBuilding', async () => { const registry = new Registry(); - const testKit = createTestkit( + const { execute, metricCount, metricValue, metricString } = prepare( + { + metrics: { + graphql_envelop_error_result: true, + graphql_envelop_phase_context: true, + }, + }, + registry, [ - usePrometheus({ - metrics: { - graphql_envelop_error_result: true, - graphql_envelop_phase_context: true, - }, - registry, - }), useExtendContext(() => { throw new Error('error'); }), ], - schema, ); + try { - await testKit.execute('query { regularField }'); + await execute('query { regularField }'); } catch (e) {} - const metrics = await registry.getMetricsAsJSON(); - expect(metrics).toEqual([ - { - help: 'Time spent on building the GraphQL context', - name: 'graphql_envelop_phase_context', - type: 'histogram', - values: [], - aggregator: 'sum', - }, - { - help: 'Counts the amount of errors reported from all phases', - name: 'graphql_envelop_error_result', - type: 'counter', - values: [ - { - labels: { - operationName: 'Anonymous', - operationType: 'query', - phase: 'context', - }, - value: 1, - }, - ], - aggregator: 'sum', - }, - ]); + expect(await metricValue('graphql_envelop_phase_context', 'count')).toBe(1); + expect(await metricCount('graphql_envelop_error_result')).toBe(1); + const errorMetric = await metricString('graphql_envelop_error_result'); + expect(errorMetric).toContain('phase="context"'); + expect(errorMetric).toContain('operationName="Anonymous"'); + expect(errorMetric).toContain('operationType="query"'); }); it('should trace error during contextBuilding', async () => { @@ -501,7 +486,6 @@ describe('Prom Metrics plugin', () => { opText: print(params.document!), }; }, - phases: ['execute'], }), }, }, @@ -633,7 +617,6 @@ describe('Prom Metrics plugin', () => { errorMessage: params.error!.message, }; }, - phases: ['context', 'execute', 'parse', 'subscribe', 'validate'], }), }, }, @@ -649,6 +632,37 @@ describe('Prom Metrics plugin', () => { ); }); + it('Should allow to use custom Counter and custom phases', async () => { + const registry = new Registry(); + const { execute, metricCount, metricString } = prepare( + { + metrics: { + graphql_envelop_error_result: createCounter({ + registry, + counter: { + name: 'test_error', + help: 'HELP ME', + labelNames: ['opText', 'errorMessage'] as const, + }, + fillLabelsFn: params => { + return { + opText: print(params.document!), + errorMessage: params.error!.message, + }; + }, + phases: ['context'], + }), + }, + }, + registry, + ); + const result = await execute('query { errorField }'); + assertSingleExecutionValue(result); + + expect(result.errors?.length).toBe(1); + expect(await metricCount('test_error')).toBe(0); + }); + it('Should not trace parse errors when not needed', async () => { const { execute, metricCount } = prepare({ metrics: { @@ -704,6 +718,64 @@ describe('Prom Metrics plugin', () => { ); }); + it('Should allow custom metric options', async () => { + const registry = new Registry(); + const { execute, metricCount, metricString, allMetrics } = prepare( + { + metrics: { + graphql_envelop_execute_resolver: createHistogram({ + registry, + fillLabelsFn: ({ document }) => ({ + opText: print(document!), + }), + histogram: { + name: 'graphql_envelop_execute_resolver', + help: 'test', + labelNames: ['opText'] as const, + }, + }), + }, + }, + registry, + ); + const result = await execute('query { regularField }'); + assertSingleExecutionValue(result); + + expect(result.errors).toBeUndefined(); + expect(await metricCount('graphql_envelop_execute_resolver', 'count')).toBe(1); + expect(await metricString('graphql_envelop_execute_resolver')).toContain( + 'graphql_envelop_execute_resolver_count{opText="{\\n regularField\\n}"} 1', + ); + }); + + it('Should allow custom metric options', async () => { + const registry = new Registry(); + const { execute, metricCount, metricString, allMetrics } = prepare( + { + metrics: { + graphql_envelop_execute_resolver: createHistogram({ + registry, + fillLabelsFn: ({ document }) => ({ + opText: print(document!), + }), + histogram: { + name: 'graphql_envelop_execute_resolver', + help: 'test', + labelNames: ['opText'] as const, + }, + phases: ['subscribe'], + }), + }, + }, + registry, + ); + const result = await execute('query { regularField }'); + assertSingleExecutionValue(result); + + expect(result.errors).toBeUndefined(); + expect(await metricCount('graphql_envelop_execute_resolver', 'count')).toBe(0); + }); + it('Should trace only specified resolvers when resolversWhitelist is used', async () => { const { execute, metricCount, metricString } = prepare({ metrics: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ccc623f9..20ec6d490 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5136,6 +5136,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -5561,8 +5566,8 @@ packages: bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - bn.js@4.12.0: - resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} + bn.js@4.12.1: + resolution: {integrity: sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==} bn.js@5.2.1: resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} @@ -5813,8 +5818,9 @@ packages: resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==} engines: {node: '>=8'} - cipher-base@1.0.4: - resolution: {integrity: sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==} + cipher-base@1.0.5: + resolution: {integrity: sha512-xq7ICKB4TMHUx7Tz1L9O2SGKOhYMOTR32oir45Bq28/AQTpHogKgHcoYFSdRbMtddl+ozNXfXY9jWcgYKmde0w==} + engines: {node: '>= 0.10'} cjs-module-lexer@1.2.3: resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} @@ -6078,8 +6084,9 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} - crypto-browserify@3.12.0: - resolution: {integrity: sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==} + crypto-browserify@3.12.1: + resolution: {integrity: sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==} + engines: {node: '>= 0.10'} css-declaration-sorter@7.2.0: resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==} @@ -6530,8 +6537,8 @@ packages: elkjs@0.8.2: resolution: {integrity: sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==} - elliptic@6.5.7: - resolution: {integrity: sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==} + elliptic@6.6.1: + resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -7513,10 +7520,6 @@ packages: resolution: {integrity: sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==} engines: {node: '>=4'} - hash-base@3.1.0: - resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==} - engines: {node: '>=4'} - hash-it@6.0.0: resolution: {integrity: sha512-KHzmSFx1KwyMPw0kXeeUD752q/Kfbzhy6dAZrjXV9kAIXGqzGvv8vhkUqj+2MGZldTo0IBpw6v7iWE7uxsvH0w==} @@ -9939,8 +9942,8 @@ packages: resolution: {integrity: sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==} engines: {node: '>=0.6'} - qs@6.13.0: - resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + qs@6.13.1: + resolution: {integrity: sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==} engines: {node: '>=0.6'} qs@6.7.0: @@ -16444,6 +16447,8 @@ snapshots: acorn@8.12.1: {} + acorn@8.14.0: {} + agent-base@6.0.2: dependencies: debug: 4.3.4 @@ -16759,7 +16764,7 @@ snapshots: asn1.js@4.10.1: dependencies: - bn.js: 4.12.0 + bn.js: 4.12.1 inherits: 2.0.4 minimalistic-assert: 1.0.1 @@ -16963,7 +16968,7 @@ snapshots: bluebird@3.7.2: {} - bn.js@4.12.0: {} + bn.js@4.12.1: {} bn.js@5.2.1: {} @@ -17068,7 +17073,7 @@ snapshots: browserify-aes@1.2.0: dependencies: buffer-xor: 1.0.3 - cipher-base: 1.0.4 + cipher-base: 1.0.5 create-hash: 1.2.0 evp_bytestokey: 1.0.3 inherits: 2.0.4 @@ -17082,7 +17087,7 @@ snapshots: browserify-des@1.0.2: dependencies: - cipher-base: 1.0.4 + cipher-base: 1.0.5 des.js: 1.1.0 inherits: 2.0.4 safe-buffer: 5.2.1 @@ -17099,7 +17104,7 @@ snapshots: browserify-rsa: 4.1.1 create-hash: 1.2.0 create-hmac: 1.1.7 - elliptic: 6.5.7 + elliptic: 6.6.1 hash-base: 3.0.4 inherits: 2.0.4 parse-asn1: 5.1.7 @@ -17362,7 +17367,7 @@ snapshots: ci-info@4.0.0: {} - cipher-base@1.0.4: + cipher-base@1.0.5: dependencies: inherits: 2.0.4 safe-buffer: 5.2.1 @@ -17579,12 +17584,12 @@ snapshots: create-ecdh@4.0.4: dependencies: - bn.js: 4.12.0 - elliptic: 6.5.7 + bn.js: 4.12.1 + elliptic: 6.6.1 create-hash@1.2.0: dependencies: - cipher-base: 1.0.4 + cipher-base: 1.0.5 inherits: 2.0.4 md5.js: 1.3.5 ripemd160: 2.0.2 @@ -17592,7 +17597,7 @@ snapshots: create-hmac@1.1.7: dependencies: - cipher-base: 1.0.4 + cipher-base: 1.0.5 create-hash: 1.2.0 inherits: 2.0.4 ripemd160: 2.0.2 @@ -17632,7 +17637,7 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - crypto-browserify@3.12.0: + crypto-browserify@3.12.1: dependencies: browserify-cipher: 1.0.1 browserify-sign: 4.2.3 @@ -17640,6 +17645,7 @@ snapshots: create-hash: 1.2.0 create-hmac: 1.1.7 diffie-hellman: 5.0.3 + hash-base: 3.0.4 inherits: 2.0.4 pbkdf2: 3.1.2 public-encrypt: 4.0.3 @@ -18033,7 +18039,7 @@ snapshots: diffie-hellman@5.0.3: dependencies: - bn.js: 4.12.0 + bn.js: 4.12.1 miller-rabin: 4.0.1 randombytes: 2.1.0 @@ -18108,9 +18114,9 @@ snapshots: elkjs@0.8.2: {} - elliptic@6.5.7: + elliptic@6.6.1: dependencies: - bn.js: 4.12.0 + bn.js: 4.12.1 brorand: 1.1.0 hash.js: 1.1.7 hmac-drbg: 1.0.1 @@ -19501,12 +19507,6 @@ snapshots: inherits: 2.0.4 safe-buffer: 5.2.1 - hash-base@3.1.0: - dependencies: - inherits: 2.0.4 - readable-stream: 3.6.2 - safe-buffer: 5.2.1 - hash-it@6.0.0: {} hash.js@1.1.7: @@ -20559,7 +20559,7 @@ snapshots: jsonc-eslint-parser@2.4.0: dependencies: - acorn: 8.12.1 + acorn: 8.14.0 eslint-visitor-keys: 3.4.3 espree: 9.6.1 semver: 7.6.3 @@ -20918,7 +20918,7 @@ snapshots: md5.js@1.3.5: dependencies: - hash-base: 3.1.0 + hash-base: 3.0.4 inherits: 2.0.4 safe-buffer: 5.2.1 @@ -21616,7 +21616,7 @@ snapshots: miller-rabin@4.0.1: dependencies: - bn.js: 4.12.0 + bn.js: 4.12.1 brorand: 1.1.0 mime-db@1.52.0: {} @@ -22016,7 +22016,7 @@ snapshots: buffer: 4.9.2 console-browserify: 1.2.0 constants-browserify: 1.0.0 - crypto-browserify: 3.12.0 + crypto-browserify: 3.12.1 domain-browser: 1.2.0 events: 3.3.0 https-browserify: 1.0.0 @@ -22766,7 +22766,7 @@ snapshots: public-encrypt@4.0.3: dependencies: - bn.js: 4.12.0 + bn.js: 4.12.1 browserify-rsa: 4.1.1 create-hash: 1.2.0 parse-asn1: 5.1.7 @@ -22811,7 +22811,7 @@ snapshots: dependencies: side-channel: 1.0.6 - qs@6.13.0: + qs@6.13.1: dependencies: side-channel: 1.0.6 @@ -23254,7 +23254,7 @@ snapshots: ripemd160@2.0.2: dependencies: - hash-base: 3.1.0 + hash-base: 3.0.4 inherits: 2.0.4 robust-predicates@3.0.1: {} @@ -24064,7 +24064,7 @@ snapshots: terser@4.8.1: dependencies: - acorn: 8.12.1 + acorn: 8.14.0 commander: 2.20.3 source-map: 0.6.1 source-map-support: 0.5.21 @@ -24602,7 +24602,7 @@ snapshots: url@0.11.4: dependencies: punycode: 1.4.1 - qs: 6.13.0 + qs: 6.13.1 urlpattern-polyfill@10.0.0: {}