fix the opentelemetry setup

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2024-02-28 13:30:52 +01:00
parent bb5b6ee309
commit c5d7b40b4b
15 changed files with 1687 additions and 88 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Allow passing a `--require` argument through to the Node process during `package start`
+30 -44
View File
@@ -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.
-3
View File
@@ -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",
-11
View File
@@ -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 });
+7 -3
View File
@@ -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:^"
+34
View File
@@ -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();
+1
View File
@@ -280,6 +280,7 @@ Options:
--check
--inspect [host]
--inspect-brk [host]
--require <path>
-h, --help
```
+1
View File
@@ -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
+3
View File
@@ -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',
+2
View File
@@ -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)
+1594 -27
View File
File diff suppressed because it is too large Load Diff