Skip to content

Commit

Permalink
fix: [plugins/prometheus] fixes for phases/shouldObserve for Yoga (#2326
Browse files Browse the repository at this point in the history
)

* fix: [plugins/prometheus] fixes for phases/shouldObserve implementation in Yoga

* make new API non breaking

* chore(dependencies): updated changesets for modified dependencies

* refactor to conditionally observe events based on phase

* 🚧 WIP

* chore(dependencies): updated changesets for modified dependencies

* missing TS error

* satisfies

* add all test cases

* fix fillLabelsFn and shouldObserve param types

* don't use OperationType enum from graphql for compatibility with 15

* update the changeset

* chore(dependencies): updated changesets for modified dependencies

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: enisdenjo <denis@denelop.com>
  • Loading branch information
3 people authored Nov 24, 2024
1 parent 4d82b34 commit 443fc15
Show file tree
Hide file tree
Showing 5 changed files with 1,553 additions and 802 deletions.
96 changes: 96 additions & 0 deletions .changeset/cold-seals-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
'@envelop/prometheus': minor
---

Allow to explicitly control which events and timing should be observe.

Each metric can now be configured to observe events and timings only for certain GraphQL pipeline
phases, or depending on the request context.

## Example: trace only execution and subscription errors

```ts
import { execute, parse, specifiedRules, subscribe, validate } from 'graphql'
import { envelop, useEngine } from '@envelop/core'
import { usePrometheus } from '@envelop/prometheus'

const TRACKED_OPERATION_NAMES = [
// make a list of operation that you want to monitor
]

const getEnveloped = envelop({
plugins: [
useEngine({ parse, validate, specifiedRules, execute, subscribe }),
usePrometheus({
metrics: {
// Here, an array of phases can be provided to enable the metric only on certain phases.
// In this example, only error happening during the execute and subscribe phases will tracked
graphql_envelop_phase_error: ['execute', 'subscribe']
}
}),
],
})
```

## Example: Monitor timing only of a set of operations by name

```ts
import { execute, parse, specifiedRules, subscribe, validate } from 'graphql'
import { envelop, useEngine } from '@envelop/core'
import { usePrometheus } from '@envelop/prometheus'

const TRACKED_OPERATION_NAMES = [
// make a list of operation that you want to monitor
]

const getEnveloped = envelop({
plugins: [
useEngine({ parse, validate, specifiedRules, execute, subscribe }),
usePrometheus({
metrics: {
graphql_yoga_http_duration: createHistogram({
registry,
histogram: {
name: 'graphql_envelop_request_duration',
help: 'Time spent on HTTP connection',
labelNames: ['operationName']
},
fillLabelsFn: ({ operationName }, _rawContext) => ({ operationName, }),
phases: ['execute', 'subscribe'],

// Here `shouldObserve` control if the request timing should be observed, based on context
shouldObserve: ({ operationName }) => TRACKED_OPERATIONS.includes(operationName),
})
},
})
]
})
```

## Default Behavior Change

A metric is enabled using `true` value in metrics options will observe in every
phases available.

Previously, which phase was observe was depending on which other metric were enabled. For example,
this config would only trace validation error:

```ts
usePrometheus({
metrics: {
graphql_envelop_phase_error: true,
graphql_envelop_phase_validate: true,
},
})
```

This is no longer the case. If you were relying on this behavior, please use an array of string to
restrict observed phases.

```ts
usePrometheus({
metrics: {
graphql_envelop_phase_error: ['validate'],
},
})
```
79 changes: 57 additions & 22 deletions packages/plugins/prometheus/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import type { GraphQLResolveInfo } from 'graphql';
import { Registry } from 'prom-client';
import { createCounter, createHistogram, createSummary, type AtLeastOne } from './utils.js';
import {
createCounter,
createHistogram,
createSummary,
type AtLeastOne,
type DeprecatedFieldInfo,
type FillLabelsFnParams,
} from './utils.js';

export type PrometheusTracingPluginConfig = {
/**
Expand Down Expand Up @@ -54,7 +62,7 @@ export type MetricsConfig = {
* - string[]: Enable the metric on a list of phases
* - ReturnType<typeof createCounter>: Enable the metric with custom configuration
*/
graphql_envelop_request?: CounterMetricOption<AtLeastOne<'execute' | 'subscribe'>>;
graphql_envelop_request?: CounterMetricOption<'execute' | 'subscribe'>;

/**
* Tracks the duration of the complete GraphQL operation execution.
Expand All @@ -67,7 +75,7 @@ export type MetricsConfig = {
* - number[]: Enable the metric with custom buckets
* - ReturnType<typeof createHistogram>: Enable the metric with custom configuration
*/
graphql_envelop_request_duration?: HistogramMetricOption<AtLeastOne<'execute' | 'subscribe'>>;
graphql_envelop_request_duration?: HistogramMetricOption<'execute' | 'subscribe'>;
/**
* Provides a summary of the time spent on the GraphQL operation execution.
* It reports the same timing than graphql_envelop_request_duration but as a summary.
Expand All @@ -78,7 +86,7 @@ export type MetricsConfig = {
* - string[]: Enable the metric on a list of phases
* - ReturnType<typeof createSummary>: Enable the metric with custom configuration
*/
graphql_envelop_request_time_summary?: SummaryMetricOption<AtLeastOne<'execute' | 'subscribe'>>;
graphql_envelop_request_time_summary?: SummaryMetricOption<'execute' | 'subscribe'>;
/**
* Tracks the duration of the parse phase of the GraphQL execution.
* It reports the time spent parsing the incoming GraphQL operation.
Expand All @@ -91,7 +99,7 @@ export type MetricsConfig = {
* - number[]: Enable the metric with custom buckets
* - ReturnType<typeof createHistogram>: Enable the metric with custom configuration
*/
graphql_envelop_phase_parse?: HistogramMetricOption<['parse']>;
graphql_envelop_phase_parse?: HistogramMetricOption<'parse'>;
/**
* Tracks the duration of the validate phase of the GraphQL execution.
* It reports the time spent validating the incoming GraphQL operation.
Expand All @@ -104,7 +112,7 @@ export type MetricsConfig = {
* - number[]: Enable the metric with custom buckets
* - ReturnType<typeof createHistogram>: Enable the metric with custom configuration
*/
graphql_envelop_phase_validate?: HistogramMetricOption<['validate']>;
graphql_envelop_phase_validate?: HistogramMetricOption<'validate'>;
/**
* Tracks the duration of the context phase of the GraphQL execution.
* It reports the time spent building the context object that will be passed to the executors.
Expand All @@ -117,7 +125,7 @@ export type MetricsConfig = {
* - number[]: Enable the metric with custom buckets
* - ReturnType<typeof createHistogram>: Enable the metric with custom configuration
*/
graphql_envelop_phase_context?: HistogramMetricOption<['context']>;
graphql_envelop_phase_context?: HistogramMetricOption<'context'>;
/**
* Tracks the duration of the execute phase of the GraphQL execution.
* It reports the time spent actually resolving the response of the incoming operation.
Expand All @@ -131,7 +139,7 @@ export type MetricsConfig = {
* - number[]: Enable the metric with custom buckets
* - ReturnType<typeof createHistogram>: Enable the metric with custom configuration
*/
graphql_envelop_phase_execute?: HistogramMetricOption<['execute']>;
graphql_envelop_phase_execute?: HistogramMetricOption<'execute'>;
/**
* This metric tracks the duration of the subscribe phase of the GraphQL execution.
* It reports the time spent initiating a subscription (which doesn’t include actually sending the first response).
Expand All @@ -144,7 +152,7 @@ export type MetricsConfig = {
* - number[]: Enable the metric with custom buckets
* - ReturnType<typeof createHistogram>: Enable the metric with custom configuration
*/
graphql_envelop_phase_subscribe?: HistogramMetricOption<['subscribe']>;
graphql_envelop_phase_subscribe?: HistogramMetricOption<'subscribe'>;
/**
* This metric tracks the number of errors that returned by the GraphQL execution.
* It counts all errors found in response, but it also includes errors from other GraphQL
Expand All @@ -158,7 +166,12 @@ export type MetricsConfig = {
* - ReturnType<typeof createCounter>: Enable the metric with custom configuration
*/
graphql_envelop_error_result?: CounterMetricOption<
AtLeastOne<'parse' | 'validate' | 'context' | 'execute' | 'subscribe'>
'parse' | 'validate' | 'context' | 'execute' | 'subscribe',
string,
FillLabelsFnParams & {
error: unknown;
errorPhase: 'parse' | 'validate' | 'context' | 'execute' | 'subscribe';
}
>;
/**
* This metric tracks the number of deprecated fields used in the GraphQL operation.
Expand All @@ -170,7 +183,11 @@ export type MetricsConfig = {
* - string[]: Enable the metric on a list of phases
* - ReturnType<typeof createCounter>: Enable the metric with custom configuration
*/
graphql_envelop_deprecated_field?: CounterMetricOption<['parse']>;
graphql_envelop_deprecated_field?: CounterMetricOption<
'parse',
string,
FillLabelsFnParams & { deprecationInfo: DeprecatedFieldInfo }
>;
/**
* This metric tracks the number of schema changes that have occurred since the gateway started.
* If you are using a plugin that modifies the schema on the fly,
Expand All @@ -184,7 +201,7 @@ export type MetricsConfig = {
* - string[]: Enable the metric on a list of phases
* - ReturnType<typeof createCounter>: Enable the metric with custom configuration
*/
graphql_envelop_schema_change?: CounterMetricOption<['schema']>;
graphql_envelop_schema_change?: CounterMetricOption<'schema', string, {}>;
/**
* This metric tracks the duration of each resolver execution.
*
Expand All @@ -199,7 +216,13 @@ export type MetricsConfig = {
* - number[]: Enable the metric with custom buckets
* - ReturnType<typeof createHistogram>: Enable the metric with custom configuration
*/
graphql_envelop_execute_resolver?: HistogramMetricOption<AtLeastOne<'subscribe' | 'execute'>>;
graphql_envelop_execute_resolver?: HistogramMetricOption<
'subscribe' | 'execute',
string,
FillLabelsFnParams & {
info: GraphQLResolveInfo;
}
>;
};

export type LabelsConfig = {
Expand Down Expand Up @@ -247,22 +270,34 @@ export type LabelsConfig = {
phase?: boolean;
};

export type HistogramMetricOption<Phases extends string[], LabelNames extends string = string> =
export type HistogramMetricOption<
Phases,
LabelNames extends string = string,
Params extends Record<string, unknown> = FillLabelsFnParams,
> =
| boolean
| string
| BucketsConfig
| Phases
| ReturnType<typeof createHistogram<Phases, LabelNames>>;
| AtLeastOne<Phases>
| ReturnType<typeof createHistogram<Phases, LabelNames, Params>>;
export type BucketsConfig = AtLeastOne<number>;

export type CounterMetricOption<Phases extends string[], LabelNames extends string = string> =
export type CounterMetricOption<
Phases,
LabelNames extends string = string,
Params extends Record<string, unknown> = FillLabelsFnParams,
> =
| boolean
| string
| Phases
| ReturnType<typeof createCounter<Phases, LabelNames>>;
| AtLeastOne<Phases>
| ReturnType<typeof createCounter<Phases, LabelNames, Params>>;

export type SummaryMetricOption<Phases extends string[], LabelNames extends string = string> =
export type SummaryMetricOption<
Phases,
LabelNames extends string = string,
Params extends Record<string, unknown> = FillLabelsFnParams,
> =
| boolean
| string
| Phases
| ReturnType<typeof createSummary<Phases, LabelNames>>;
| AtLeastOne<Phases>
| ReturnType<typeof createSummary<Phases, LabelNames, Params>>;
Loading

0 comments on commit 443fc15

Please sign in to comment.