fix the opentelemetry setup
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Allow passing a `--require` argument through to the Node process during `package start`
|
||||
@@ -6,42 +6,37 @@ description: Tutorial to setup OpenTelemetry metrics and traces exporters in Bac
|
||||
|
||||
Backstage uses [OpenTelemetery](https://opentelemetry.io/) to instrument its components by reporting traces and metrics.
|
||||
|
||||
This tutorial shows how to setup exporters in your Backstage backend package. For demonstration purposes we will use the simple console exporters.
|
||||
This tutorial shows how to setup exporters in your Backstage backend package. For demonstration purposes we will use a Prometheus exporter, but you can adjust your solution to use another one that suits your needs; see for example the article on [OTLP exporters](https://opentelemetry.io/docs/instrumentation/js/exporters/).
|
||||
|
||||
## Install dependencies
|
||||
|
||||
We will use the OpenTelemetry Node SDK and the `auto-instrumentations-node` packages.
|
||||
|
||||
Backstage packages, such as the catalog, uses the OpenTelemetry API to send custom traces and metrics.
|
||||
Backstage packages, such as the catalog, use the OpenTelemetry API to send custom traces and metrics.
|
||||
The `auto-instrumentations-node` will automatically create spans for code called in libraries like Express.
|
||||
|
||||
```bash
|
||||
yarn --cwd packages/backend add @opentelemetry/sdk-node \
|
||||
yarn --cwd packages/backend add \
|
||||
@opentelemetry/sdk-node \
|
||||
@opentelemetry/auto-instrumentations-node \
|
||||
@opentelemetry/sdk-metrics \
|
||||
@opentelemetry/sdk-trace-node
|
||||
@opentelemetry/exporter-prometheus
|
||||
```
|
||||
|
||||
## Configure
|
||||
|
||||
In your `packages/backend` folder, create an `instrumentation.js` file.
|
||||
In your `packages/backend/src` folder, create an `instrumentation.js` file.
|
||||
|
||||
```typescript
|
||||
```typescript title="in packages/backend/src/instrumentation.js"
|
||||
const { NodeSDK } = require('@opentelemetry/sdk-node');
|
||||
const { ConsoleSpanExporter } = require('@opentelemetry/sdk-trace-node');
|
||||
const {
|
||||
getNodeAutoInstrumentations,
|
||||
} = require('@opentelemetry/auto-instrumentations-node');
|
||||
const {
|
||||
PeriodicExportingMetricReader,
|
||||
ConsoleMetricExporter,
|
||||
} = require('@opentelemetry/sdk-metrics');
|
||||
const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus');
|
||||
|
||||
const prometheus = new PrometheusExporter();
|
||||
const sdk = new NodeSDK({
|
||||
traceExporter: new ConsoleSpanExporter(),
|
||||
metricReader: new PeriodicExportingMetricReader({
|
||||
exporter: new ConsoleMetricExporter(),
|
||||
}),
|
||||
// You can add a traceExporter field here too
|
||||
metricReader: prometheus,
|
||||
instrumentations: [getNodeAutoInstrumentations()],
|
||||
});
|
||||
|
||||
@@ -51,42 +46,33 @@ sdk.start();
|
||||
You probably won't need all of the instrumentation inside `getNodeAutoInstrumentations()` so make sure to
|
||||
check the [documentation](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node) and tweak it properly.
|
||||
|
||||
It's important to setup the NodeSDK and the automatic instrumentation **before** importing any library.
|
||||
## Local Development Setup
|
||||
|
||||
This is why we will use the nodejs [`--require`](https://nodejs.org/api/cli.html#-r---require-module)
|
||||
flag when we start up the application.
|
||||
It's important to setup the NodeSDK and the automatic instrumentation **before**
|
||||
importing any library. This is why we will use the nodejs
|
||||
[`--require`](https://nodejs.org/api/cli.html#-r---require-module) flag when we
|
||||
start up the application.
|
||||
|
||||
In your `Dockerfile` add the `--require` flag which points to the `instrumentation.js` file
|
||||
For local development, you can add the required flag in your `packages/backend/package.json`.
|
||||
|
||||
```Dockerfile
|
||||
|
||||
# We need the instrumentation file inside the Docker image so we can use it with --require
|
||||
// highlight-add-next-line
|
||||
COPY --chown=node:node packages/backend/instrumentation.js ./
|
||||
|
||||
// highlight-remove-next-line
|
||||
CMD ["node", "packages/backend", "--config", "app-config.yaml"]
|
||||
// highlight-add-next-line
|
||||
CMD ["node", "--require", "./instrumentation.js", "packages/backend", "--config", "app-config.yaml"]
|
||||
```
|
||||
|
||||
## Run Backstage
|
||||
|
||||
The above configuration will only work in production once your start a Docker container from the image.
|
||||
|
||||
To be able to test locally you can import the `./instrumentation.js` file at the top (before all imports) of your backend `index.ts` file
|
||||
|
||||
```ts
|
||||
import '../instrumentation.js'
|
||||
// Other imports
|
||||
...
|
||||
```json title="packages/backend/package.json"
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start --require ./src/instrumentation.js",
|
||||
...
|
||||
```
|
||||
|
||||
You can now start your Backstage instance as usual, using `yarn dev`.
|
||||
|
||||
When the backend is started, you should see in your console traces and metrics emitted by OpenTelemetry.
|
||||
## Production Setup
|
||||
|
||||
Of course in production you probably won't use the console exporters but instead send traces and metrics to an OpenTelemetry Collector or other exporter using [OTLP exporters](https://opentelemetry.io/docs/instrumentation/js/exporters/).
|
||||
In your `Dockerfile` add the `--require` flag which points to the `instrumentation.js` file
|
||||
|
||||
```Dockerfile
|
||||
// highlight-remove-next-line
|
||||
CMD ["node", "packages/backend", "--config", "app-config.yaml"]
|
||||
// highlight-add-next-line
|
||||
CMD ["node", "--require", "./src/instrumentation.js", "packages/backend", "--config", "app-config.yaml"]
|
||||
```
|
||||
|
||||
If you need to disable/configure some OpenTelemetry feature there are lots of [environment variables](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/) which you can tweak.
|
||||
|
||||
|
||||
@@ -65,9 +65,6 @@
|
||||
"@backstage/plugin-techdocs-backend": "workspace:^",
|
||||
"@gitbeaker/node": "^35.1.0",
|
||||
"@octokit/rest": "^19.0.3",
|
||||
"@opentelemetry/api": "^1.4.1",
|
||||
"@opentelemetry/exporter-prometheus": "^0.50.0",
|
||||
"@opentelemetry/sdk-metrics": "^1.13.0",
|
||||
"azure-devops-node-api": "^12.0.0",
|
||||
"better-sqlite3": "^9.0.0",
|
||||
"dockerode": "^4.0.0",
|
||||
|
||||
@@ -56,19 +56,8 @@ import { ServerPermissionClient } from '@backstage/plugin-permission-node';
|
||||
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
|
||||
import { DefaultEventBroker } from '@backstage/plugin-events-backend';
|
||||
import { DefaultEventsService } from '@backstage/plugin-events-node';
|
||||
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
|
||||
import { MeterProvider } from '@opentelemetry/sdk-metrics';
|
||||
import { metrics } from '@opentelemetry/api';
|
||||
import { DefaultSignalsService } from '@backstage/plugin-signals-node';
|
||||
|
||||
// Expose opentelemetry metrics using a Prometheus exporter on
|
||||
// http://localhost:9464/metrics . See prometheus.yml in packages/backend for
|
||||
// more information on how to scrape it.
|
||||
const exporter = new PrometheusExporter();
|
||||
const meterProvider = new MeterProvider();
|
||||
metrics.setGlobalMeterProvider(meterProvider);
|
||||
meterProvider.addMetricReader(exporter);
|
||||
|
||||
function makeCreateEnv(config: Config) {
|
||||
const root = getRootLogger();
|
||||
const reader = UrlReaders.default({ logger: root, config });
|
||||
|
||||
@@ -21,9 +21,10 @@
|
||||
"build": "backstage-cli package build",
|
||||
"clean": "backstage-cli package clean",
|
||||
"lint": "backstage-cli package lint",
|
||||
"start": "backstage-cli package start",
|
||||
"start": "backstage-cli package start --require ./src/instrumentation.js",
|
||||
"test": "backstage-cli package test",
|
||||
"build-image": "docker build ../.. -f Dockerfile --tag example-backend"
|
||||
"build-image": "docker build ../.. -f Dockerfile --tag example-backend",
|
||||
"start:prometheus": "docker run --mount type=bind,source=./prometheus.yml,destination=/etc/prometheus/prometheus.yml --publish published=9090,target=9090,protocol=tcp prom/prometheus"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-defaults": "workspace:^",
|
||||
@@ -56,7 +57,10 @@
|
||||
"@backstage/plugin-search-backend-module-techdocs": "workspace:^",
|
||||
"@backstage/plugin-search-backend-node": "workspace:^",
|
||||
"@backstage/plugin-signals-backend": "workspace:^",
|
||||
"@backstage/plugin-techdocs-backend": "workspace:^"
|
||||
"@backstage/plugin-techdocs-backend": "workspace:^",
|
||||
"@opentelemetry/auto-instrumentations-node": "^0.43.0",
|
||||
"@opentelemetry/exporter-prometheus": "^0.50.0",
|
||||
"@opentelemetry/sdk-node": "^0.50.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^"
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const { NodeSDK } = require('@opentelemetry/sdk-node');
|
||||
const {
|
||||
getNodeAutoInstrumentations,
|
||||
} = require('@opentelemetry/auto-instrumentations-node');
|
||||
const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus');
|
||||
|
||||
// Expose opentelemetry metrics using a Prometheus exporter on
|
||||
// http://localhost:9464/metrics. See packages/backend/prometheus.yml for
|
||||
// more information on how to scrape it.
|
||||
const prometheus = new PrometheusExporter();
|
||||
|
||||
const sdk = new NodeSDK({
|
||||
// traceExporter: ...,
|
||||
metricReader: prometheus,
|
||||
instrumentations: [getNodeAutoInstrumentations()],
|
||||
});
|
||||
|
||||
sdk.start();
|
||||
@@ -280,6 +280,7 @@ Options:
|
||||
--check
|
||||
--inspect [host]
|
||||
--inspect-brk [host]
|
||||
--require <path>
|
||||
-h, --help
|
||||
```
|
||||
|
||||
|
||||
@@ -120,6 +120,7 @@ export function registerScriptCommand(program: Command) {
|
||||
'--inspect-brk [host]',
|
||||
'Enable debugger in Node.js environments, breaking before code starts',
|
||||
)
|
||||
.option('--require <path>', 'Add a --require argument to the node process')
|
||||
.action(lazy(() => import('./start').then(m => m.command)));
|
||||
|
||||
command
|
||||
|
||||
@@ -27,6 +27,7 @@ export async function command(opts: OptionValues): Promise<void> {
|
||||
checksEnabled: Boolean(opts.check),
|
||||
inspectEnabled: opts.inspect,
|
||||
inspectBrkEnabled: opts.inspectBrk,
|
||||
require: opts.require,
|
||||
};
|
||||
|
||||
switch (role) {
|
||||
|
||||
@@ -23,6 +23,7 @@ interface StartBackendOptions {
|
||||
checksEnabled: boolean;
|
||||
inspectEnabled: boolean;
|
||||
inspectBrkEnabled: boolean;
|
||||
require?: string;
|
||||
}
|
||||
|
||||
export async function startBackend(options: StartBackendOptions) {
|
||||
@@ -32,6 +33,7 @@ export async function startBackend(options: StartBackendOptions) {
|
||||
checksEnabled: false, // not supported
|
||||
inspectEnabled: options.inspectEnabled,
|
||||
inspectBrkEnabled: options.inspectBrkEnabled,
|
||||
require: options.require,
|
||||
});
|
||||
|
||||
await waitForExit();
|
||||
@@ -41,6 +43,7 @@ export async function startBackend(options: StartBackendOptions) {
|
||||
checksEnabled: options.checksEnabled,
|
||||
inspectEnabled: options.inspectEnabled,
|
||||
inspectBrkEnabled: options.inspectBrkEnabled,
|
||||
require: options.require,
|
||||
});
|
||||
|
||||
await waitForExit();
|
||||
@@ -70,6 +73,7 @@ export async function startBackendPlugin(options: StartBackendOptions) {
|
||||
checksEnabled: false, // not supported
|
||||
inspectEnabled: options.inspectEnabled,
|
||||
inspectBrkEnabled: options.inspectBrkEnabled,
|
||||
require: options.require,
|
||||
});
|
||||
|
||||
await waitForExit();
|
||||
@@ -87,6 +91,7 @@ export async function startBackendPlugin(options: StartBackendOptions) {
|
||||
checksEnabled: options.checksEnabled,
|
||||
inspectEnabled: options.inspectEnabled,
|
||||
inspectBrkEnabled: options.inspectBrkEnabled,
|
||||
require: options.require,
|
||||
});
|
||||
|
||||
await waitForExit();
|
||||
@@ -98,6 +103,7 @@ async function cleanDistAndServeBackend(options: {
|
||||
checksEnabled: boolean;
|
||||
inspectEnabled: boolean;
|
||||
inspectBrkEnabled: boolean;
|
||||
require?: string;
|
||||
}) {
|
||||
// Cleaning dist/ before we start the dev process helps work around an issue
|
||||
// where we end up with the entrypoint executing multiple times, causing
|
||||
|
||||
@@ -276,6 +276,9 @@ export async function createBackendConfig(
|
||||
: '--inspect-brk';
|
||||
runScriptNodeArgs.push(inspect);
|
||||
}
|
||||
if (options.require) {
|
||||
runScriptNodeArgs.push(`--require=${options.require}`);
|
||||
}
|
||||
|
||||
return {
|
||||
mode: isDev ? 'development' : 'production',
|
||||
|
||||
@@ -54,10 +54,12 @@ export type BackendBundlingOptions = {
|
||||
parallelism?: number;
|
||||
inspectEnabled: boolean;
|
||||
inspectBrkEnabled: boolean;
|
||||
require?: string;
|
||||
};
|
||||
|
||||
export type BackendServeOptions = BundlingPathsOptions & {
|
||||
checksEnabled: boolean;
|
||||
inspectEnabled: boolean;
|
||||
inspectBrkEnabled: boolean;
|
||||
require?: string;
|
||||
};
|
||||
|
||||
@@ -96,6 +96,9 @@ export async function startBackendExperimental(options: BackendServeOptions) {
|
||||
: '--inspect-brk';
|
||||
optionArgs.push(inspect);
|
||||
}
|
||||
if (options.require) {
|
||||
optionArgs.push(`--require=${options.require}`);
|
||||
}
|
||||
|
||||
const userArgs = process.argv
|
||||
.slice(['node', 'backstage-cli', 'package', 'start'].length)
|
||||
|
||||
Reference in New Issue
Block a user