@envelop/prometheus 9.1.0 → 9.2.0-alpha-20240118143545-03bf31a7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cjs/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.usePrometheus = exports.createSummary = exports.createHistogram = exports.createCounter = void 0;
3
+ exports.usePrometheus = exports.execStartTimeMap = exports.fillLabelsFnParamsMap = exports.createSummary = exports.createHistogram = exports.createCounter = void 0;
4
4
  /* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
5
5
  const graphql_1 = require("graphql");
6
6
  const prom_client_1 = require("prom-client");
@@ -10,14 +10,24 @@ const utils_js_1 = require("./utils.js");
10
10
  Object.defineProperty(exports, "createCounter", { enumerable: true, get: function () { return utils_js_1.createCounter; } });
11
11
  Object.defineProperty(exports, "createHistogram", { enumerable: true, get: function () { return utils_js_1.createHistogram; } });
12
12
  Object.defineProperty(exports, "createSummary", { enumerable: true, get: function () { return utils_js_1.createSummary; } });
13
- const promPluginContext = Symbol('promPluginContext');
14
- const promPluginExecutionStartTimeSymbol = Symbol('promPluginExecutionStartTimeSymbol');
13
+ exports.fillLabelsFnParamsMap = new WeakMap();
14
+ exports.execStartTimeMap = new WeakMap();
15
15
  const usePrometheus = (config = {}) => {
16
16
  let typeInfo = null;
17
17
  const parseHistogram = (0, utils_js_1.getHistogramFromConfig)(config, 'parse', 'graphql_envelop_phase_parse', 'Time spent on running GraphQL "parse" function');
18
18
  const validateHistogram = (0, utils_js_1.getHistogramFromConfig)(config, 'validate', 'graphql_envelop_phase_validate', 'Time spent on running GraphQL "validate" function');
19
19
  const contextBuildingHistogram = (0, utils_js_1.getHistogramFromConfig)(config, 'contextBuilding', 'graphql_envelop_phase_context', 'Time spent on building the GraphQL context');
20
20
  const executeHistogram = (0, utils_js_1.getHistogramFromConfig)(config, 'execute', 'graphql_envelop_phase_execute', 'Time spent on running the GraphQL "execute" function');
21
+ function labelExists(label) {
22
+ const labelFlag = config.labels?.[label];
23
+ if (labelFlag == null) {
24
+ return true;
25
+ }
26
+ return labelFlag;
27
+ }
28
+ function filterFillParamsFnParams(params) {
29
+ return Object.fromEntries(Object.entries(params).filter(([key]) => labelExists(key)));
30
+ }
21
31
  const resolversHistogram = typeof config.resolvers === 'object'
22
32
  ? config.resolvers
23
33
  : config.resolvers === true
@@ -31,10 +41,10 @@ const usePrometheus = (config = {}) => {
31
41
  'fieldName',
32
42
  'typeName',
33
43
  'returnType',
34
- ],
44
+ ].filter(labelExists),
35
45
  registers: [config.registry || prom_client_1.register],
36
46
  }),
37
- fillLabelsFn: params => ({
47
+ fillLabelsFn: params => filterFillParamsFnParams({
38
48
  operationName: params.operationName,
39
49
  operationType: params.operationType,
40
50
  fieldName: params.info?.fieldName,
@@ -50,10 +60,10 @@ const usePrometheus = (config = {}) => {
50
60
  histogram: new prom_client_1.Histogram({
51
61
  name: 'graphql_envelop_request_duration',
52
62
  help: 'Time spent on running the GraphQL operation from parse to execute',
53
- labelNames: ['operationType', 'operationName'],
63
+ labelNames: ['operationType', 'operationName'].filter(labelExists),
54
64
  registers: [config.registry || prom_client_1.register],
55
65
  }),
56
- fillLabelsFn: params => ({
66
+ fillLabelsFn: params => filterFillParamsFnParams({
57
67
  operationName: params.operationName,
58
68
  operationType: params.operationType,
59
69
  }),
@@ -66,10 +76,10 @@ const usePrometheus = (config = {}) => {
66
76
  summary: new prom_client_1.Summary({
67
77
  name: 'graphql_envelop_request_time_summary',
68
78
  help: 'Summary to measure the time to complete GraphQL operations',
69
- labelNames: ['operationType', 'operationName'],
79
+ labelNames: ['operationType', 'operationName'].filter(labelExists),
70
80
  registers: [config.registry || prom_client_1.register],
71
81
  }),
72
- fillLabelsFn: params => ({
82
+ fillLabelsFn: params => filterFillParamsFnParams({
73
83
  operationName: params.operationName,
74
84
  operationType: params.operationType,
75
85
  }),
@@ -82,10 +92,10 @@ const usePrometheus = (config = {}) => {
82
92
  counter: new prom_client_1.Counter({
83
93
  name: 'graphql_envelop_error_result',
84
94
  help: 'Counts the amount of errors reported from all phases',
85
- labelNames: ['operationType', 'operationName', 'path', 'phase'],
95
+ labelNames: ['operationType', 'operationName', 'path', 'phase'].filter(labelExists),
86
96
  registers: [config.registry || prom_client_1.register],
87
97
  }),
88
- fillLabelsFn: params => ({
98
+ fillLabelsFn: params => filterFillParamsFnParams({
89
99
  operationName: params.operationName,
90
100
  operationType: params.operationType,
91
101
  path: params.error?.path?.join('.'),
@@ -100,10 +110,10 @@ const usePrometheus = (config = {}) => {
100
110
  counter: new prom_client_1.Counter({
101
111
  name: 'graphql_envelop_request',
102
112
  help: 'Counts the amount of GraphQL requests executed through Envelop',
103
- labelNames: ['operationType', 'operationName'],
113
+ labelNames: ['operationType', 'operationName'].filter(labelExists),
104
114
  registers: [config.registry || prom_client_1.register],
105
115
  }),
106
- fillLabelsFn: params => ({
116
+ fillLabelsFn: params => filterFillParamsFnParams({
107
117
  operationName: params.operationName,
108
118
  operationType: params.operationType,
109
119
  }),
@@ -116,10 +126,10 @@ const usePrometheus = (config = {}) => {
116
126
  counter: new prom_client_1.Counter({
117
127
  name: 'graphql_envelop_deprecated_field',
118
128
  help: 'Counts the amount of deprecated fields used in selection sets',
119
- labelNames: ['operationType', 'operationName', 'fieldName', 'typeName'],
129
+ labelNames: ['operationType', 'operationName', 'fieldName', 'typeName'].filter(labelExists),
120
130
  registers: [config.registry || prom_client_1.register],
121
131
  }),
122
- fillLabelsFn: params => ({
132
+ fillLabelsFn: params => filterFillParamsFnParams({
123
133
  operationName: params.operationName,
124
134
  operationType: params.operationType,
125
135
  fieldName: params.deprecationInfo?.fieldName,
@@ -127,26 +137,39 @@ const usePrometheus = (config = {}) => {
127
137
  }),
128
138
  })
129
139
  : undefined;
130
- const onParse = ({ context, extendContext, params }) => {
140
+ const schemaChangeCounter = typeof config.schemaChangeCount === 'object'
141
+ ? config.schemaChangeCount
142
+ : config.schemaChangeCount === true
143
+ ? (0, utils_js_1.createCounter)({
144
+ counter: new prom_client_1.Counter({
145
+ name: 'graphql_envelop_schema_change',
146
+ help: 'Counts the amount of schema changes',
147
+ registers: [config.registry || prom_client_1.register],
148
+ }),
149
+ fillLabelsFn: () => ({}),
150
+ })
151
+ : undefined;
152
+ const onParse = ({ context, params }) => {
131
153
  if (config.skipIntrospection && (0, core_1.isIntrospectionOperationString)(params.source)) {
132
154
  return;
133
155
  }
134
156
  const startTime = Date.now();
135
157
  return params => {
136
158
  const totalTime = (Date.now() - startTime) / 1000;
137
- const internalContext = (0, utils_js_1.createInternalContext)(params.result);
138
- if (internalContext) {
139
- extendContext({
140
- [promPluginContext]: internalContext,
141
- });
142
- parseHistogram?.histogram.observe(parseHistogram.fillLabelsFn(internalContext, context), totalTime);
159
+ let fillLabelsFnParams = exports.fillLabelsFnParamsMap.get(params.result);
160
+ if (!fillLabelsFnParams) {
161
+ fillLabelsFnParams = (0, utils_js_1.createFillLabelFnParams)(params.result, filterFillParamsFnParams);
162
+ exports.fillLabelsFnParamsMap.set(context, fillLabelsFnParams);
163
+ }
164
+ if (fillLabelsFnParams) {
165
+ parseHistogram?.histogram.observe(parseHistogram.fillLabelsFn(fillLabelsFnParams, context), totalTime);
143
166
  if (deprecationCounter && typeInfo) {
144
- const deprecatedFields = (0, utils_js_1.extractDeprecatedFields)(internalContext.document, typeInfo);
167
+ const deprecatedFields = (0, utils_js_1.extractDeprecatedFields)(fillLabelsFnParams.document, typeInfo);
145
168
  if (deprecatedFields.length > 0) {
146
169
  for (const depField of deprecatedFields) {
147
170
  deprecationCounter.counter
148
171
  .labels(deprecationCounter.fillLabelsFn({
149
- ...internalContext,
172
+ ...fillLabelsFnParams,
150
173
  deprecationInfo: depField,
151
174
  }, context))
152
175
  .inc();
@@ -166,13 +189,14 @@ const usePrometheus = (config = {}) => {
166
189
  };
167
190
  const onValidate = validateHistogram
168
191
  ? ({ context }) => {
169
- if (!context[promPluginContext]) {
192
+ const fillLabelsFnParams = exports.fillLabelsFnParamsMap.get(context);
193
+ if (!fillLabelsFnParams) {
170
194
  return undefined;
171
195
  }
172
196
  const startTime = Date.now();
173
197
  return ({ valid }) => {
174
198
  const totalTime = (Date.now() - startTime) / 1000;
175
- const labels = validateHistogram.fillLabelsFn(context[promPluginContext], context);
199
+ const labels = validateHistogram.fillLabelsFn(fillLabelsFnParams, context);
176
200
  validateHistogram.histogram.observe(labels, totalTime);
177
201
  if (!valid) {
178
202
  errorsCounter?.counter
@@ -187,33 +211,36 @@ const usePrometheus = (config = {}) => {
187
211
  : undefined;
188
212
  const onContextBuilding = contextBuildingHistogram
189
213
  ? ({ context }) => {
190
- if (!context[promPluginContext]) {
214
+ const fillLabelsFnParams = exports.fillLabelsFnParamsMap.get(context);
215
+ if (!fillLabelsFnParams) {
191
216
  return undefined;
192
217
  }
193
218
  const startTime = Date.now();
194
219
  return () => {
195
220
  const totalTime = (Date.now() - startTime) / 1000;
196
- contextBuildingHistogram.histogram.observe(contextBuildingHistogram.fillLabelsFn(context[promPluginContext], context), totalTime);
221
+ contextBuildingHistogram.histogram.observe(contextBuildingHistogram.fillLabelsFn(fillLabelsFnParams, context), totalTime);
197
222
  };
198
223
  }
199
224
  : undefined;
200
225
  const onExecute = executeHistogram
201
226
  ? ({ args }) => {
202
- if (!args.contextValue[promPluginContext]) {
227
+ const fillLabelsFnParams = exports.fillLabelsFnParamsMap.get(args.contextValue);
228
+ if (!fillLabelsFnParams) {
203
229
  return undefined;
204
230
  }
205
231
  const startTime = Date.now();
206
232
  reqCounter?.counter
207
- .labels(reqCounter.fillLabelsFn(args.contextValue[promPluginContext], args.contextValue))
233
+ .labels(reqCounter.fillLabelsFn(fillLabelsFnParams, args.contextValue))
208
234
  .inc();
209
235
  const result = {
210
236
  onExecuteDone: ({ result }) => {
211
237
  const totalTime = (Date.now() - startTime) / 1000;
212
- executeHistogram.histogram.observe(executeHistogram.fillLabelsFn(args.contextValue[promPluginContext], args.contextValue), totalTime);
213
- requestTotalHistogram?.histogram.observe(requestTotalHistogram.fillLabelsFn(args.contextValue[promPluginContext], args.contextValue), totalTime);
214
- if (requestSummary && args.contextValue[promPluginExecutionStartTimeSymbol]) {
215
- const summaryTime = (Date.now() - args.contextValue[promPluginExecutionStartTimeSymbol]) / 1000;
216
- requestSummary.summary.observe(requestSummary.fillLabelsFn(args.contextValue[promPluginContext], args.contextValue), summaryTime);
238
+ executeHistogram.histogram.observe(executeHistogram.fillLabelsFn(fillLabelsFnParams, args.contextValue), totalTime);
239
+ requestTotalHistogram?.histogram.observe(requestTotalHistogram.fillLabelsFn(fillLabelsFnParams, args.contextValue), totalTime);
240
+ const execStartTime = exports.execStartTimeMap.get(args.contextValue);
241
+ if (requestSummary && execStartTime) {
242
+ const summaryTime = (Date.now() - execStartTime) / 1000;
243
+ requestSummary.summary.observe(requestSummary.fillLabelsFn(fillLabelsFnParams, args.contextValue), summaryTime);
217
244
  }
218
245
  if (errorsCounter &&
219
246
  !(0, core_1.isAsyncIterable)(result) &&
@@ -222,7 +249,7 @@ const usePrometheus = (config = {}) => {
222
249
  for (const error of result.errors) {
223
250
  errorsCounter.counter
224
251
  .labels(errorsCounter.fillLabelsFn({
225
- ...args.contextValue[promPluginContext],
252
+ ...fillLabelsFnParams,
226
253
  errorPhase: 'execute',
227
254
  error,
228
255
  }, args.contextValue))
@@ -234,13 +261,14 @@ const usePrometheus = (config = {}) => {
234
261
  return result;
235
262
  }
236
263
  : undefined;
264
+ const countedSchemas = new WeakSet();
237
265
  return {
238
- onEnveloped({ extendContext }) {
239
- extendContext({
240
- [promPluginExecutionStartTimeSymbol]: Date.now(),
241
- });
266
+ onEnveloped({ context }) {
267
+ if (!exports.execStartTimeMap.has(context)) {
268
+ exports.execStartTimeMap.set(context, Date.now());
269
+ }
242
270
  },
243
- onPluginInit({ addPlugin }) {
271
+ onPluginInit({ addPlugin, registerContextErrorHandler }) {
244
272
  if (resolversHistogram) {
245
273
  addPlugin((0, on_resolve_1.useOnResolve)(({ info, context }) => {
246
274
  const shouldTrace = (0, utils_js_1.shouldTraceFieldResolver)(info, config.resolversWhitelist);
@@ -250,17 +278,35 @@ const usePrometheus = (config = {}) => {
250
278
  const startTime = Date.now();
251
279
  return () => {
252
280
  const totalTime = (Date.now() - startTime) / 1000;
281
+ const fillLabelsFnParams = exports.fillLabelsFnParamsMap.get(context);
253
282
  const paramsCtx = {
254
- ...context[promPluginContext],
283
+ ...fillLabelsFnParams,
255
284
  info,
256
285
  };
257
286
  resolversHistogram.histogram.observe(resolversHistogram.fillLabelsFn(paramsCtx, context), totalTime);
258
287
  };
259
288
  }));
260
289
  }
290
+ registerContextErrorHandler(({ context }) => {
291
+ const fillLabelsFnParams = exports.fillLabelsFnParamsMap.get(context);
292
+ let extraLabels;
293
+ if (fillLabelsFnParams) {
294
+ extraLabels = contextBuildingHistogram?.fillLabelsFn(fillLabelsFnParams, context);
295
+ }
296
+ errorsCounter?.counter
297
+ .labels({
298
+ ...extraLabels,
299
+ phase: 'context',
300
+ })
301
+ .inc();
302
+ });
261
303
  },
262
304
  onSchemaChange({ schema }) {
263
305
  typeInfo = new graphql_1.TypeInfo(schema);
306
+ if (schemaChangeCounter && !countedSchemas.has(schema)) {
307
+ schemaChangeCounter.counter.inc();
308
+ countedSchemas.add(schema);
309
+ }
264
310
  },
265
311
  onParse,
266
312
  onValidate,
package/cjs/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractDeprecatedFields = exports.getHistogramFromConfig = exports.createCounter = exports.createSummary = exports.createHistogram = exports.createInternalContext = exports.shouldTraceFieldResolver = void 0;
3
+ exports.extractDeprecatedFields = exports.getHistogramFromConfig = exports.createCounter = exports.createSummary = exports.createHistogram = exports.createFillLabelFnParams = exports.shouldTraceFieldResolver = void 0;
4
4
  const graphql_1 = require("graphql");
5
5
  const prom_client_1 = require("prom-client");
6
6
  function shouldTraceFieldResolver(info, whitelist) {
@@ -16,7 +16,7 @@ exports.shouldTraceFieldResolver = shouldTraceFieldResolver;
16
16
  function getOperation(document) {
17
17
  return document.definitions[0];
18
18
  }
19
- function createInternalContext(parseResult) {
19
+ function createFillLabelFnParams(parseResult, filterParams) {
20
20
  if (parseResult === null) {
21
21
  return null;
22
22
  }
@@ -24,13 +24,13 @@ function createInternalContext(parseResult) {
24
24
  return null;
25
25
  }
26
26
  const operation = getOperation(parseResult);
27
- return {
27
+ return filterParams({
28
28
  document: parseResult,
29
29
  operationName: operation.name?.value || 'Anonymous',
30
30
  operationType: operation.operation,
31
- };
31
+ });
32
32
  }
33
- exports.createInternalContext = createInternalContext;
33
+ exports.createFillLabelFnParams = createFillLabelFnParams;
34
34
  function createHistogram(options) {
35
35
  return options;
36
36
  }
package/esm/index.js CHANGED
@@ -3,16 +3,26 @@ import { TypeInfo } from 'graphql';
3
3
  import { Counter, register as defaultRegistry, Histogram, Summary } from 'prom-client';
4
4
  import { isAsyncIterable, isIntrospectionOperationString, } from '@envelop/core';
5
5
  import { useOnResolve } from '@envelop/on-resolve';
6
- import { createCounter, createHistogram, createInternalContext, createSummary, extractDeprecatedFields, getHistogramFromConfig, shouldTraceFieldResolver, } from './utils.js';
6
+ import { createCounter, createFillLabelFnParams, createHistogram, createSummary, extractDeprecatedFields, getHistogramFromConfig, shouldTraceFieldResolver, } from './utils.js';
7
7
  export { createCounter, createHistogram, createSummary, };
8
- const promPluginContext = Symbol('promPluginContext');
9
- const promPluginExecutionStartTimeSymbol = Symbol('promPluginExecutionStartTimeSymbol');
8
+ export const fillLabelsFnParamsMap = new WeakMap();
9
+ export const execStartTimeMap = new WeakMap();
10
10
  export const usePrometheus = (config = {}) => {
11
11
  let typeInfo = null;
12
12
  const parseHistogram = getHistogramFromConfig(config, 'parse', 'graphql_envelop_phase_parse', 'Time spent on running GraphQL "parse" function');
13
13
  const validateHistogram = getHistogramFromConfig(config, 'validate', 'graphql_envelop_phase_validate', 'Time spent on running GraphQL "validate" function');
14
14
  const contextBuildingHistogram = getHistogramFromConfig(config, 'contextBuilding', 'graphql_envelop_phase_context', 'Time spent on building the GraphQL context');
15
15
  const executeHistogram = getHistogramFromConfig(config, 'execute', 'graphql_envelop_phase_execute', 'Time spent on running the GraphQL "execute" function');
16
+ function labelExists(label) {
17
+ const labelFlag = config.labels?.[label];
18
+ if (labelFlag == null) {
19
+ return true;
20
+ }
21
+ return labelFlag;
22
+ }
23
+ function filterFillParamsFnParams(params) {
24
+ return Object.fromEntries(Object.entries(params).filter(([key]) => labelExists(key)));
25
+ }
16
26
  const resolversHistogram = typeof config.resolvers === 'object'
17
27
  ? config.resolvers
18
28
  : config.resolvers === true
@@ -26,10 +36,10 @@ export const usePrometheus = (config = {}) => {
26
36
  'fieldName',
27
37
  'typeName',
28
38
  'returnType',
29
- ],
39
+ ].filter(labelExists),
30
40
  registers: [config.registry || defaultRegistry],
31
41
  }),
32
- fillLabelsFn: params => ({
42
+ fillLabelsFn: params => filterFillParamsFnParams({
33
43
  operationName: params.operationName,
34
44
  operationType: params.operationType,
35
45
  fieldName: params.info?.fieldName,
@@ -45,10 +55,10 @@ export const usePrometheus = (config = {}) => {
45
55
  histogram: new Histogram({
46
56
  name: 'graphql_envelop_request_duration',
47
57
  help: 'Time spent on running the GraphQL operation from parse to execute',
48
- labelNames: ['operationType', 'operationName'],
58
+ labelNames: ['operationType', 'operationName'].filter(labelExists),
49
59
  registers: [config.registry || defaultRegistry],
50
60
  }),
51
- fillLabelsFn: params => ({
61
+ fillLabelsFn: params => filterFillParamsFnParams({
52
62
  operationName: params.operationName,
53
63
  operationType: params.operationType,
54
64
  }),
@@ -61,10 +71,10 @@ export const usePrometheus = (config = {}) => {
61
71
  summary: new Summary({
62
72
  name: 'graphql_envelop_request_time_summary',
63
73
  help: 'Summary to measure the time to complete GraphQL operations',
64
- labelNames: ['operationType', 'operationName'],
74
+ labelNames: ['operationType', 'operationName'].filter(labelExists),
65
75
  registers: [config.registry || defaultRegistry],
66
76
  }),
67
- fillLabelsFn: params => ({
77
+ fillLabelsFn: params => filterFillParamsFnParams({
68
78
  operationName: params.operationName,
69
79
  operationType: params.operationType,
70
80
  }),
@@ -77,10 +87,10 @@ export const usePrometheus = (config = {}) => {
77
87
  counter: new Counter({
78
88
  name: 'graphql_envelop_error_result',
79
89
  help: 'Counts the amount of errors reported from all phases',
80
- labelNames: ['operationType', 'operationName', 'path', 'phase'],
90
+ labelNames: ['operationType', 'operationName', 'path', 'phase'].filter(labelExists),
81
91
  registers: [config.registry || defaultRegistry],
82
92
  }),
83
- fillLabelsFn: params => ({
93
+ fillLabelsFn: params => filterFillParamsFnParams({
84
94
  operationName: params.operationName,
85
95
  operationType: params.operationType,
86
96
  path: params.error?.path?.join('.'),
@@ -95,10 +105,10 @@ export const usePrometheus = (config = {}) => {
95
105
  counter: new Counter({
96
106
  name: 'graphql_envelop_request',
97
107
  help: 'Counts the amount of GraphQL requests executed through Envelop',
98
- labelNames: ['operationType', 'operationName'],
108
+ labelNames: ['operationType', 'operationName'].filter(labelExists),
99
109
  registers: [config.registry || defaultRegistry],
100
110
  }),
101
- fillLabelsFn: params => ({
111
+ fillLabelsFn: params => filterFillParamsFnParams({
102
112
  operationName: params.operationName,
103
113
  operationType: params.operationType,
104
114
  }),
@@ -111,10 +121,10 @@ export const usePrometheus = (config = {}) => {
111
121
  counter: new Counter({
112
122
  name: 'graphql_envelop_deprecated_field',
113
123
  help: 'Counts the amount of deprecated fields used in selection sets',
114
- labelNames: ['operationType', 'operationName', 'fieldName', 'typeName'],
124
+ labelNames: ['operationType', 'operationName', 'fieldName', 'typeName'].filter(labelExists),
115
125
  registers: [config.registry || defaultRegistry],
116
126
  }),
117
- fillLabelsFn: params => ({
127
+ fillLabelsFn: params => filterFillParamsFnParams({
118
128
  operationName: params.operationName,
119
129
  operationType: params.operationType,
120
130
  fieldName: params.deprecationInfo?.fieldName,
@@ -122,26 +132,39 @@ export const usePrometheus = (config = {}) => {
122
132
  }),
123
133
  })
124
134
  : undefined;
125
- const onParse = ({ context, extendContext, params }) => {
135
+ const schemaChangeCounter = typeof config.schemaChangeCount === 'object'
136
+ ? config.schemaChangeCount
137
+ : config.schemaChangeCount === true
138
+ ? createCounter({
139
+ counter: new Counter({
140
+ name: 'graphql_envelop_schema_change',
141
+ help: 'Counts the amount of schema changes',
142
+ registers: [config.registry || defaultRegistry],
143
+ }),
144
+ fillLabelsFn: () => ({}),
145
+ })
146
+ : undefined;
147
+ const onParse = ({ context, params }) => {
126
148
  if (config.skipIntrospection && isIntrospectionOperationString(params.source)) {
127
149
  return;
128
150
  }
129
151
  const startTime = Date.now();
130
152
  return params => {
131
153
  const totalTime = (Date.now() - startTime) / 1000;
132
- const internalContext = createInternalContext(params.result);
133
- if (internalContext) {
134
- extendContext({
135
- [promPluginContext]: internalContext,
136
- });
137
- parseHistogram?.histogram.observe(parseHistogram.fillLabelsFn(internalContext, context), totalTime);
154
+ let fillLabelsFnParams = fillLabelsFnParamsMap.get(params.result);
155
+ if (!fillLabelsFnParams) {
156
+ fillLabelsFnParams = createFillLabelFnParams(params.result, filterFillParamsFnParams);
157
+ fillLabelsFnParamsMap.set(context, fillLabelsFnParams);
158
+ }
159
+ if (fillLabelsFnParams) {
160
+ parseHistogram?.histogram.observe(parseHistogram.fillLabelsFn(fillLabelsFnParams, context), totalTime);
138
161
  if (deprecationCounter && typeInfo) {
139
- const deprecatedFields = extractDeprecatedFields(internalContext.document, typeInfo);
162
+ const deprecatedFields = extractDeprecatedFields(fillLabelsFnParams.document, typeInfo);
140
163
  if (deprecatedFields.length > 0) {
141
164
  for (const depField of deprecatedFields) {
142
165
  deprecationCounter.counter
143
166
  .labels(deprecationCounter.fillLabelsFn({
144
- ...internalContext,
167
+ ...fillLabelsFnParams,
145
168
  deprecationInfo: depField,
146
169
  }, context))
147
170
  .inc();
@@ -161,13 +184,14 @@ export const usePrometheus = (config = {}) => {
161
184
  };
162
185
  const onValidate = validateHistogram
163
186
  ? ({ context }) => {
164
- if (!context[promPluginContext]) {
187
+ const fillLabelsFnParams = fillLabelsFnParamsMap.get(context);
188
+ if (!fillLabelsFnParams) {
165
189
  return undefined;
166
190
  }
167
191
  const startTime = Date.now();
168
192
  return ({ valid }) => {
169
193
  const totalTime = (Date.now() - startTime) / 1000;
170
- const labels = validateHistogram.fillLabelsFn(context[promPluginContext], context);
194
+ const labels = validateHistogram.fillLabelsFn(fillLabelsFnParams, context);
171
195
  validateHistogram.histogram.observe(labels, totalTime);
172
196
  if (!valid) {
173
197
  errorsCounter?.counter
@@ -182,33 +206,36 @@ export const usePrometheus = (config = {}) => {
182
206
  : undefined;
183
207
  const onContextBuilding = contextBuildingHistogram
184
208
  ? ({ context }) => {
185
- if (!context[promPluginContext]) {
209
+ const fillLabelsFnParams = fillLabelsFnParamsMap.get(context);
210
+ if (!fillLabelsFnParams) {
186
211
  return undefined;
187
212
  }
188
213
  const startTime = Date.now();
189
214
  return () => {
190
215
  const totalTime = (Date.now() - startTime) / 1000;
191
- contextBuildingHistogram.histogram.observe(contextBuildingHistogram.fillLabelsFn(context[promPluginContext], context), totalTime);
216
+ contextBuildingHistogram.histogram.observe(contextBuildingHistogram.fillLabelsFn(fillLabelsFnParams, context), totalTime);
192
217
  };
193
218
  }
194
219
  : undefined;
195
220
  const onExecute = executeHistogram
196
221
  ? ({ args }) => {
197
- if (!args.contextValue[promPluginContext]) {
222
+ const fillLabelsFnParams = fillLabelsFnParamsMap.get(args.contextValue);
223
+ if (!fillLabelsFnParams) {
198
224
  return undefined;
199
225
  }
200
226
  const startTime = Date.now();
201
227
  reqCounter?.counter
202
- .labels(reqCounter.fillLabelsFn(args.contextValue[promPluginContext], args.contextValue))
228
+ .labels(reqCounter.fillLabelsFn(fillLabelsFnParams, args.contextValue))
203
229
  .inc();
204
230
  const result = {
205
231
  onExecuteDone: ({ result }) => {
206
232
  const totalTime = (Date.now() - startTime) / 1000;
207
- executeHistogram.histogram.observe(executeHistogram.fillLabelsFn(args.contextValue[promPluginContext], args.contextValue), totalTime);
208
- requestTotalHistogram?.histogram.observe(requestTotalHistogram.fillLabelsFn(args.contextValue[promPluginContext], args.contextValue), totalTime);
209
- if (requestSummary && args.contextValue[promPluginExecutionStartTimeSymbol]) {
210
- const summaryTime = (Date.now() - args.contextValue[promPluginExecutionStartTimeSymbol]) / 1000;
211
- requestSummary.summary.observe(requestSummary.fillLabelsFn(args.contextValue[promPluginContext], args.contextValue), summaryTime);
233
+ executeHistogram.histogram.observe(executeHistogram.fillLabelsFn(fillLabelsFnParams, args.contextValue), totalTime);
234
+ requestTotalHistogram?.histogram.observe(requestTotalHistogram.fillLabelsFn(fillLabelsFnParams, args.contextValue), totalTime);
235
+ const execStartTime = execStartTimeMap.get(args.contextValue);
236
+ if (requestSummary && execStartTime) {
237
+ const summaryTime = (Date.now() - execStartTime) / 1000;
238
+ requestSummary.summary.observe(requestSummary.fillLabelsFn(fillLabelsFnParams, args.contextValue), summaryTime);
212
239
  }
213
240
  if (errorsCounter &&
214
241
  !isAsyncIterable(result) &&
@@ -217,7 +244,7 @@ export const usePrometheus = (config = {}) => {
217
244
  for (const error of result.errors) {
218
245
  errorsCounter.counter
219
246
  .labels(errorsCounter.fillLabelsFn({
220
- ...args.contextValue[promPluginContext],
247
+ ...fillLabelsFnParams,
221
248
  errorPhase: 'execute',
222
249
  error,
223
250
  }, args.contextValue))
@@ -229,13 +256,14 @@ export const usePrometheus = (config = {}) => {
229
256
  return result;
230
257
  }
231
258
  : undefined;
259
+ const countedSchemas = new WeakSet();
232
260
  return {
233
- onEnveloped({ extendContext }) {
234
- extendContext({
235
- [promPluginExecutionStartTimeSymbol]: Date.now(),
236
- });
261
+ onEnveloped({ context }) {
262
+ if (!execStartTimeMap.has(context)) {
263
+ execStartTimeMap.set(context, Date.now());
264
+ }
237
265
  },
238
- onPluginInit({ addPlugin }) {
266
+ onPluginInit({ addPlugin, registerContextErrorHandler }) {
239
267
  if (resolversHistogram) {
240
268
  addPlugin(useOnResolve(({ info, context }) => {
241
269
  const shouldTrace = shouldTraceFieldResolver(info, config.resolversWhitelist);
@@ -245,17 +273,35 @@ export const usePrometheus = (config = {}) => {
245
273
  const startTime = Date.now();
246
274
  return () => {
247
275
  const totalTime = (Date.now() - startTime) / 1000;
276
+ const fillLabelsFnParams = fillLabelsFnParamsMap.get(context);
248
277
  const paramsCtx = {
249
- ...context[promPluginContext],
278
+ ...fillLabelsFnParams,
250
279
  info,
251
280
  };
252
281
  resolversHistogram.histogram.observe(resolversHistogram.fillLabelsFn(paramsCtx, context), totalTime);
253
282
  };
254
283
  }));
255
284
  }
285
+ registerContextErrorHandler(({ context }) => {
286
+ const fillLabelsFnParams = fillLabelsFnParamsMap.get(context);
287
+ let extraLabels;
288
+ if (fillLabelsFnParams) {
289
+ extraLabels = contextBuildingHistogram?.fillLabelsFn(fillLabelsFnParams, context);
290
+ }
291
+ errorsCounter?.counter
292
+ .labels({
293
+ ...extraLabels,
294
+ phase: 'context',
295
+ })
296
+ .inc();
297
+ });
256
298
  },
257
299
  onSchemaChange({ schema }) {
258
300
  typeInfo = new TypeInfo(schema);
301
+ if (schemaChangeCounter && !countedSchemas.has(schema)) {
302
+ schemaChangeCounter.counter.inc();
303
+ countedSchemas.add(schema);
304
+ }
259
305
  },
260
306
  onParse,
261
307
  onValidate,
package/esm/utils.js CHANGED
@@ -12,7 +12,7 @@ export function shouldTraceFieldResolver(info, whitelist) {
12
12
  function getOperation(document) {
13
13
  return document.definitions[0];
14
14
  }
15
- export function createInternalContext(parseResult) {
15
+ export function createFillLabelFnParams(parseResult, filterParams) {
16
16
  if (parseResult === null) {
17
17
  return null;
18
18
  }
@@ -20,11 +20,11 @@ export function createInternalContext(parseResult) {
20
20
  return null;
21
21
  }
22
22
  const operation = getOperation(parseResult);
23
- return {
23
+ return filterParams({
24
24
  document: parseResult,
25
25
  operationName: operation.name?.value || 'Anonymous',
26
26
  operationType: operation.operation,
27
- };
27
+ });
28
28
  }
29
29
  export function createHistogram(options) {
30
30
  return options;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envelop/prometheus",
3
- "version": "9.1.0",
3
+ "version": "9.2.0-alpha-20240118143545-03bf31a7",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
6
  "@envelop/core": "^5.0.0",
@@ -14,4 +14,12 @@ export type PrometheusTracingPluginConfig = {
14
14
  deprecatedFields?: boolean | ReturnType<typeof createCounter>;
15
15
  registry?: Registry;
16
16
  skipIntrospection?: boolean;
17
+ schemaChangeCount?: boolean | ReturnType<typeof createCounter>;
18
+ labels?: {
19
+ operationName?: boolean;
20
+ operationType?: boolean;
21
+ fieldName?: boolean;
22
+ typeName?: boolean;
23
+ returnType?: boolean;
24
+ };
17
25
  };
@@ -14,4 +14,12 @@ export type PrometheusTracingPluginConfig = {
14
14
  deprecatedFields?: boolean | ReturnType<typeof createCounter>;
15
15
  registry?: Registry;
16
16
  skipIntrospection?: boolean;
17
+ schemaChangeCount?: boolean | ReturnType<typeof createCounter>;
18
+ labels?: {
19
+ operationName?: boolean;
20
+ operationType?: boolean;
21
+ fieldName?: boolean;
22
+ typeName?: boolean;
23
+ returnType?: boolean;
24
+ };
17
25
  };
@@ -2,10 +2,6 @@ import { Plugin } from '@envelop/core';
2
2
  import { PrometheusTracingPluginConfig } from './config.cjs';
3
3
  import { createCounter, createHistogram, createSummary, FillLabelsFnParams } from './utils.cjs';
4
4
  export { PrometheusTracingPluginConfig, createCounter, createHistogram, createSummary, FillLabelsFnParams, };
5
- declare const promPluginContext: unique symbol;
6
- declare const promPluginExecutionStartTimeSymbol: unique symbol;
7
- type PluginInternalContext = {
8
- [promPluginContext]: FillLabelsFnParams;
9
- [promPluginExecutionStartTimeSymbol]: number;
10
- };
11
- export declare const usePrometheus: (config?: PrometheusTracingPluginConfig) => Plugin<PluginInternalContext>;
5
+ export declare const fillLabelsFnParamsMap: WeakMap<any, FillLabelsFnParams | null>;
6
+ export declare const execStartTimeMap: WeakMap<any, number>;
7
+ export declare const usePrometheus: (config?: PrometheusTracingPluginConfig) => Plugin;
@@ -2,10 +2,6 @@ import { Plugin } from '@envelop/core';
2
2
  import { PrometheusTracingPluginConfig } from './config.js';
3
3
  import { createCounter, createHistogram, createSummary, FillLabelsFnParams } from './utils.js';
4
4
  export { PrometheusTracingPluginConfig, createCounter, createHistogram, createSummary, FillLabelsFnParams, };
5
- declare const promPluginContext: unique symbol;
6
- declare const promPluginExecutionStartTimeSymbol: unique symbol;
7
- type PluginInternalContext = {
8
- [promPluginContext]: FillLabelsFnParams;
9
- [promPluginExecutionStartTimeSymbol]: number;
10
- };
11
- export declare const usePrometheus: (config?: PrometheusTracingPluginConfig) => Plugin<PluginInternalContext>;
5
+ export declare const fillLabelsFnParamsMap: WeakMap<any, FillLabelsFnParams | null>;
6
+ export declare const execStartTimeMap: WeakMap<any, number>;
7
+ export declare const usePrometheus: (config?: PrometheusTracingPluginConfig) => Plugin;
@@ -16,7 +16,7 @@ export type FillLabelsFnParams = {
16
16
  deprecationInfo?: DeprecatedFieldInfo;
17
17
  };
18
18
  export declare function shouldTraceFieldResolver(info: GraphQLResolveInfo, whitelist: string[] | undefined): boolean;
19
- export declare function createInternalContext(parseResult: AfterParseEventPayload<any>['result']): FillLabelsFnParams | null;
19
+ export declare function createFillLabelFnParams(parseResult: AfterParseEventPayload<any>['result'], filterParams: (params: FillLabelsFnParams) => FillLabelsFnParams | null): FillLabelsFnParams | null;
20
20
  export type FillLabelsFn<LabelNames extends string> = (params: FillLabelsFnParams, rawContext: any) => Record<LabelNames, string>;
21
21
  export declare function createHistogram<LabelNames extends string>(options: {
22
22
  histogram: Histogram<LabelNames>;
@@ -16,7 +16,7 @@ export type FillLabelsFnParams = {
16
16
  deprecationInfo?: DeprecatedFieldInfo;
17
17
  };
18
18
  export declare function shouldTraceFieldResolver(info: GraphQLResolveInfo, whitelist: string[] | undefined): boolean;
19
- export declare function createInternalContext(parseResult: AfterParseEventPayload<any>['result']): FillLabelsFnParams | null;
19
+ export declare function createFillLabelFnParams(parseResult: AfterParseEventPayload<any>['result'], filterParams: (params: FillLabelsFnParams) => FillLabelsFnParams | null): FillLabelsFnParams | null;
20
20
  export type FillLabelsFn<LabelNames extends string> = (params: FillLabelsFnParams, rawContext: any) => Record<LabelNames, string>;
21
21
  export declare function createHistogram<LabelNames extends string>(options: {
22
22
  histogram: Histogram<LabelNames>;