From e4fdacfa9d85dd6c7ccfbed5ee2142ab4c2a28ba Mon Sep 17 00:00:00 2001 From: Valentin Cocaud Date: Tue, 7 May 2024 16:26:03 +0200 Subject: [PATCH] add documentation for prometheus plugin --- .../src/pages/docs/features/monitoring.mdx | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 website/src/pages/docs/features/monitoring.mdx diff --git a/website/src/pages/docs/features/monitoring.mdx b/website/src/pages/docs/features/monitoring.mdx new file mode 100644 index 0000000000..1a100af088 --- /dev/null +++ b/website/src/pages/docs/features/monitoring.mdx @@ -0,0 +1,177 @@ +--- +description: How to monitor your GraphQL server using Prometheus. +--- + +import { Callout } from '@theguild/components' + +# Monitoring + +Once in production, it's important to monitor your GraphQL server. + +A common way to monitor your server is by using Prometheus, a popular, free and open-source +monitoring system. + +## Getting Started + +To get started, you need to install the `@graphql-yoga/plugin-prometheus` package. + +This plugin tracks the complete execution flow, and reports metrics using Prometheus tracing (based +on `prom-client`). + +```sh npm2yarn +npm i prom-client @graphql-yoga/plugin-prometheus +``` + +You can then add the plugin to your GraphQL server: + +```ts +import { execute, parse, specifiedRules, subscribe, validate } from 'graphql' +import { envelop, useEngine } from '@envelop/core' +import { usePrometheus } from '@envelop/prometheus' + +const getEnveloped = envelop({ + plugins: [ + useEngine({ parse, validate, specifiedRules, execute, subscribe }), + // ... other plugins ... + usePrometheus({ + endpoint: '/metrics', // optional, default is `/metrics`, you can disable it by setting it to `false` if registry is configured in "push" mode + // all optional, and by default, all set to false, please opt-in to the metrics you wish to get + requestCount: true, // requires `execute` to be true as well + requestSummary: true, // requires `execute` to be true as well + parse: true, + validate: true, + contextBuilding: true, + execute: true, + errors: true, + resolvers: true, // requires "execute" to be `true` as well + resolversWhitelist: ['Mutation.*', 'Query.user'], // reports metrics als for these resolvers, leave `undefined` to report all fields + deprecatedFields: true, + registry: myRegistry // If you are using a custom prom-client registry, please set it here + }) + ] +}) +``` + + + Tracing resolvers using `resolvers: true` might have a performance impact on your GraphQL runtime. + Please consider to test it locally first and then decide if it's needed. + + +Now, you can access the metrics by visiting the `/metrics` endpoint of your server. + +You can then configure Prometheus to scrape the metrics from your server by using this URL. + +## Configuration + +### Available metrics + +You can opt-in to collect tracing from the following phases: + +- HTTP request process time (`http`) +- Graphql successful requests (`requestCount`) +- Graphql request summary (`requestSummary`) +- `errors` (categorized by `phase`) +- `resolvers` tracing and runtime +- deprecated fields usage (`deprecatedFields`) +- `parse` execution time +- `validate` execution time +- `contextBuilding` execution time +- `execute` execution time +- `subscribe` execution time +- count of schema changes (`schemaChangeCount`) + +> You can also customize each phase reporter, and add custom metadata and labels to the metrics. + +### Custom registry + +You can customize the `prom-client` `Registry` object if you are using a custom one, by passing it +along with the configuration object: + +```ts +import { execute, parse, specifiedRules, subscribe, validate } from 'graphql' +import { Registry } from 'prom-client' +import { envelop, useEngine } from '@envelop/core' + +const myRegistry = new Registry() + +const getEnveloped = envelop({ + plugins: [ + useEngine({ parse, validate, specifiedRules, execute, subscribe }), + // ... other plugins ... + usePrometheus({ + // ... config ... + registry: myRegistry + }) + ] +}) +``` + +> Note: if you are using custom `prom-client` instances, you need to make sure to pass your registry +> there as well. + +### Introspection + +If you wish to disable introspection logging, you can use `skipIntrospection: true` in your config +object. + +### Customize metrics options and labels + +Each metric can be configured and custom `labels` can be added to provide more metadata. You can +create a custom extraction function for every `Histogram` / `Summary` / `Counter`: + +```ts +import { execute, parse, specifiedRules, subscribe, validate } from 'graphql' +import { Histogram, register as registry } from 'prom-client' +import { envelop, useEngine } from '@envelop/core' +import { createHistogram, usePrometheus } from '@envelop/prometheus' + +const getEnveloped = envelop({ + plugins: [ + useEngine({ parse, validate, specifiedRules, execute, subscribe }), + // ... other plugins ... + usePrometheus({ + // all optional, and by default, all set to false, please opt-in to the metrics you wish to get + parse: createHistogram({ + registry: registry // make sure to add your custom registry, if you are not using the default one + histogram: new Histogram({ + name: 'my_custom_name', + help: 'HELP ME', + labelNames: ['opText'] as const, + }), + fillLabelsFn: params => { + // if you wish to fill your `labels` with metadata, you can use the params in order to get access to things like DocumentNode, operationName, operationType, `error` (for error metrics) and `info` (for resolvers metrics) + return { + opText: print(params.document) + } + } + }) + }) + ] +}) +``` + +## Caveats + +Due to Prometheus client API limitations, if this plugin is initialized multiple times, only the +metrics configuration of the first initialization will be applied. + +If necessary, use a different registry instance for each plugin instance, or clear the registry +before plugin initialization. + +```ts +function usePrometheusWithRegistry() { + // create a new registry instance for each plugin instance + const registry = new Registry() + + // or just clear the registry if you use only on plugin instance at a time + registry.clear() + + return usePrometheus({ + registry, + ... + }) +} +``` + +Keep in mind that this implies potential data loss in pull mode if some data is produced between +last pull and the re-initialization of the plugin.