Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 10 additions & 16 deletions packages/opentelemetry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ An [OpenTelemetry](https://opentelemetry.io/docs/what-is-opentelemetry/) client

* [Usage](#usage)
* [Setup](#setup)
* [Automated setup with `--require`](#automated-setup-with---require)
* [Automated setup with `require()`](#automated-setup-with-require)
* [Automated setup with `--import`](#automated-setup-with---import)
* [Automated setup with `import`](#automated-setup-with-import)
* [Manual setup](#manual-setup)
* [Sending custom metrics](#sending-custom-metrics)
* [Running in production](#running-in-production)
Expand Down Expand Up @@ -59,17 +59,17 @@ npm install --save @dotcom-reliability-kit/opentelemetry

You can set up OpenTelemetry in a number of ways, each has pros and cons which we'll outline in the sections below.

#### Automated setup with `--require`
#### Automated setup with `--import`

You can completely avoid code changes by setting up OpenTelemetry using the Node.js [`--require` command-line option](https://nodejs.org/api/cli.html#-r---require-module):
You can completely avoid code changes by setting up OpenTelemetry using the Node.js [`--import` command-line option](https://nodejs.org/api/cli.html#importmodule):

```sh
node --require @dotcom-reliability-kit/opentelemetry/setup ./my-app.js
node --import @dotcom-reliability-kit/opentelemetry/setup ./my-app.js
```

This will import our setup script _before_ any of your code. OpenTelemetry will be [configured](#configuration-options) with environment variables.

For environments where you can't modify the `node` command directly (e.g. AWS Lambda) you'll need to specify this using the `NODE_OPTIONS` environment variable set to `--require @dotcom-reliability-kit/opentelemetry/setup`.
For environments where you can't modify the `node` command directly (e.g. AWS Lambda) you'll need to specify this using the `NODE_OPTIONS` environment variable set to `--import @dotcom-reliability-kit/opentelemetry/setup`.

<table>
<tr>
Expand All @@ -86,20 +86,18 @@ For environments where you can't modify the `node` command directly (e.g. AWS La
</td>
<td>
<ul>
<li>It may be easy to accidentally remove the `--require`</li>
<li>It may be easy to accidentally remove the `--import`</li>
</ul>
</td>
</tr>
</table>

#### Automated setup with `require()`
#### Automated setup with `import`

If you can't use `--require`, e.g. because your tooling won't allow it, then you can include the setup script directly in your code:
If you can't use `--import`, e.g. because your tooling won't allow it, then you can include the setup script directly in your code:

```js
import '@dotcom-reliability-kit/opentelemetry/setup';
// or
require('@dotcom-reliability-kit/opentelemetry/setup');
```

OpenTelemetry will be [configured](#configuration-options) with environment variables.
Expand Down Expand Up @@ -134,8 +132,6 @@ If you'd like to customise the OpenTelemetry config more and have control over w

```js
import * as opentelemetry from '@dotcom-reliability-kit/opentelemetry';
// or
const opentelemetry = require('@dotcom-reliability-kit/opentelemetry');
```

Call the function, passing in [configuration options](#configuration-options):
Expand Down Expand Up @@ -178,8 +174,6 @@ In your code, load in the `getMeter` function:

```js
import { getMeter } from '@dotcom-reliability-kit/opentelemetry';
// or
const { getMeter } = require('@dotcom-reliability-kit/opentelemetry');
```

You can now use it in the same way as the built-in OpenTelemetry equivalent. For more information, see the [OpenTelemetry Meter documentation](https://opentelemetry.io/docs/specs/otel/metrics/api/#meter).
Expand Down Expand Up @@ -249,7 +243,7 @@ Some details about how we're implementing OpenTelemetry. This is to help avoid a

Depending on the way you set up OpenTelemetry, you can either configure it via environment variables or options passed into an object.

For automated setups ([here](#automated-setup-with---require) and [here](#automated-setup-with-require)) you'll need to use environment variables, e.g.
For automated setups ([here](#automated-setup-with---import) and [here](#automated-setup-with-import)) you'll need to use environment variables, e.g.

```sh
EXAMPLE=true npm start
Expand Down
10 changes: 5 additions & 5 deletions packages/opentelemetry/lib/config/instrumentations.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { logRecoverableError } = require('@dotcom-reliability-kit/log-error');
const { UserInputError } = require('@dotcom-reliability-kit/errors');
import { UserInputError } from '@dotcom-reliability-kit/errors';
import { logRecoverableError } from '@dotcom-reliability-kit/log-error';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';

/**
* @import { NodeSDKConfiguration } from '@opentelemetry/sdk-node'
Expand All @@ -15,7 +15,7 @@ const IGNORED_REQUEST_PATHS = ['/__gtg', '/__health', '/favicon.ico'];
*
* @returns {NodeSDKConfiguration['instrumentations']}
*/
exports.createInstrumentationConfig = function createInstrumentationConfig() {
export function createInstrumentationConfig() {
return getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-http': {
ignoreIncomingRequestHook
Expand All @@ -24,7 +24,7 @@ exports.createInstrumentationConfig = function createInstrumentationConfig() {
enabled: false
}
});
};
}

/**
* NOTE: this is not a filter like you know it. The name gives us a clue:
Expand Down
16 changes: 9 additions & 7 deletions packages/opentelemetry/lib/config/metrics.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-proto');
const { CompressionAlgorithm } = require('@opentelemetry/otlp-exporter-base');
const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-node').metrics;
const { default: logger } = require('@dotcom-reliability-kit/logger');
const { METRICS_USER_AGENT } = require('./user-agents');
import logger from '@dotcom-reliability-kit/logger';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto';
import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base';
import { metrics } from '@opentelemetry/sdk-node';
import { METRICS_USER_AGENT } from './user-agents.js';

const { PeriodicExportingMetricReader } = metrics;

/**
* @import { NodeSDKConfiguration } from '@opentelemetry/sdk-node'
Expand All @@ -15,7 +17,7 @@ const { METRICS_USER_AGENT } = require('./user-agents');
* @param {MetricsOptions} options
* @returns {Partial<NodeSDKConfiguration>}
*/
exports.createMetricsConfig = function createMetricsConfig(options) {
export function createMetricsConfig(options) {
/** @type {Partial<NodeSDKConfiguration>} */
const config = {};

Expand Down Expand Up @@ -53,4 +55,4 @@ exports.createMetricsConfig = function createMetricsConfig(options) {
}

return config;
};
}
15 changes: 7 additions & 8 deletions packages/opentelemetry/lib/config/resource.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
const appInfo = require('@dotcom-reliability-kit/app-info').semanticConventions;
const { defaultResource, resourceFromAttributes } = require('@opentelemetry/sdk-node').resources;
const { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } = require('@opentelemetry/semantic-conventions');
import { semanticConventions as appInfo } from '@dotcom-reliability-kit/app-info';

/**
* @import { resources } from '@opentelemetry/sdk-node'
*/
import { resources } from '@opentelemetry/sdk-node';
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';

const { defaultResource, resourceFromAttributes } = resources;

// These are hard-coded because they're unstable and OpenTelemetry advices we do this:
// https://github.com/open-telemetry/opentelemetry-js/blob/main/semantic-conventions/README.md#unstable-semconv
Expand All @@ -18,7 +17,7 @@ const ATTR_SERVICE_INSTANCE_ID = 'service.instance.id';
*
* @returns {resources.Resource}
*/
exports.createResourceConfig = function createResourceConfig() {
export function createResourceConfig() {
// We set OpenTelemetry resource attributes based on app data
return defaultResource().merge(
resourceFromAttributes({
Expand All @@ -30,4 +29,4 @@ exports.createResourceConfig = function createResourceConfig() {
[ATTR_DEPLOYMENT_ENVIRONMENT]: appInfo.deployment.environment
})
);
};
}
13 changes: 7 additions & 6 deletions packages/opentelemetry/lib/config/tracing.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-proto');
const { NoopSpanProcessor, TraceIdRatioBasedSampler } = require('@opentelemetry/sdk-node').tracing;
import logger from '@dotcom-reliability-kit/logger';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { tracing } from '@opentelemetry/sdk-node';
import { TRACING_USER_AGENT } from './user-agents.js';

const { default: logger } = require('@dotcom-reliability-kit/logger');
const { TRACING_USER_AGENT } = require('./user-agents.js');
const { NoopSpanProcessor, TraceIdRatioBasedSampler } = tracing;

/**
* @import { NodeSDKConfiguration } from '@opentelemetry/sdk-node'
Expand All @@ -17,7 +18,7 @@ const DEFAULT_SAMPLE_PERCENTAGE = 5;
* @param {TracingOptions} options
* @returns {Partial<NodeSDKConfiguration>}
*/
exports.createTracingConfig = function createTracingConfig(options) {
export function createTracingConfig(options) {
/** @type {Partial<NodeSDKConfiguration>} */
const config = {};

Expand Down Expand Up @@ -61,4 +62,4 @@ exports.createTracingConfig = function createTracingConfig(options) {
}

return config;
};
}
16 changes: 10 additions & 6 deletions packages/opentelemetry/lib/config/user-agents.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
const appInfo = require('@dotcom-reliability-kit/app-info');
const packageJson = require('../../package.json');
const metricExporterPackageJson = require('@opentelemetry/exporter-metrics-otlp-proto/package.json');
const traceExporterPackageJson = require('@opentelemetry/exporter-trace-otlp-proto/package.json');
import appInfo from '@dotcom-reliability-kit/app-info';
import metricExporterPackageJson from '@opentelemetry/exporter-metrics-otlp-proto/package.json' with {
type: 'json'
};
import traceExporterPackageJson from '@opentelemetry/exporter-trace-otlp-proto/package.json' with {
type: 'json'
};
import packageJson from '../../package.json' with { type: 'json' };

const BASE_USER_AGENT = `FTSystem/${appInfo.systemCode} (${packageJson.name}/${packageJson.version})`;

exports.METRICS_USER_AGENT = `${BASE_USER_AGENT} (${metricExporterPackageJson.name}/${metricExporterPackageJson.version})`;
exports.TRACING_USER_AGENT = `${BASE_USER_AGENT} (${traceExporterPackageJson.name}/${traceExporterPackageJson.version})`;
export const METRICS_USER_AGENT = `${BASE_USER_AGENT} (${metricExporterPackageJson.name}/${metricExporterPackageJson.version})`;
export const TRACING_USER_AGENT = `${BASE_USER_AGENT} (${traceExporterPackageJson.name}/${traceExporterPackageJson.version})`;
13 changes: 6 additions & 7 deletions packages/opentelemetry/lib/config/views.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const { AggregationType, InstrumentType } = require('@opentelemetry/sdk-node').metrics;
const { default: logger } = require('@dotcom-reliability-kit/logger');
import logger from '@dotcom-reliability-kit/logger';
import { metrics } from '@opentelemetry/sdk-node';

const { AggregationType, InstrumentType } = metrics;

/**
* @import { NodeSDKConfiguration } from '@opentelemetry/sdk-node'
Expand All @@ -13,10 +15,7 @@ const { default: logger } = require('@dotcom-reliability-kit/logger');
* @param {ViewOptions} options
* @returns {Partial<NodeSDKConfiguration>}
*/
exports.createViewConfig = function createViewConfig({
httpClientDurationBuckets,
httpServerDurationBuckets
}) {
export function createViewConfig({ httpClientDurationBuckets, httpServerDurationBuckets }) {
const views = [
...buildHistogramView({
instrumentName: 'http.client.duration',
Expand All @@ -30,7 +29,7 @@ exports.createViewConfig = function createViewConfig({
})
];
return views.length ? { views } : {};
};
}

/**
* @param {object} options
Expand Down
37 changes: 24 additions & 13 deletions packages/opentelemetry/lib/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const { createInstrumentationConfig } = require('./config/instrumentations.js');
const { createMetricsConfig } = require('./config/metrics.js');
const { createResourceConfig } = require('./config/resource.js');
const { createTracingConfig } = require('./config/tracing.js');
const { createViewConfig } = require('./config/views.js');
const { HostMetrics } = require('@opentelemetry/host-metrics');
const opentelemetry = require('@opentelemetry/sdk-node');
const { default: logger } = require('@dotcom-reliability-kit/logger');
import logger from '@dotcom-reliability-kit/logger';
import { HostMetrics } from '@opentelemetry/host-metrics';
import opentelemetry from '@opentelemetry/sdk-node';
import { createInstrumentationConfig } from './config/instrumentations.js';
import { createMetricsConfig } from './config/metrics.js';
import { createResourceConfig } from './config/resource.js';
import { createTracingConfig } from './config/tracing.js';
import { createViewConfig } from './config/views.js';

/**
* @import { Instances, Options } from '@dotcom-reliability-kit/opentelemetry'
Expand All @@ -25,7 +25,7 @@ let instances;
* @param {Options} [options]
* @returns {Instances}
*/
function setupOpenTelemetry({
export function setup({
authorizationHeader,
logInternals,
metrics: metricsOptions,
Expand Down Expand Up @@ -92,7 +92,12 @@ function setupOpenTelemetry({
// This is a known issue, the workaround is to import 'https' to ensure
// that the instrumented version is used by node-fetch. See:
// https://github.com/open-telemetry/opentelemetry-js-contrib/issues/2440
require('node:https');
import('node:https').catch(
// We need a catch here to ensure we don't exit the process if this fails
// for some reason, however we don't care about it in the test coverage
/* node:coverage ignore next */
() => {}
);

// Set up host metrics if we have a metrics endpoint
if (metricsOptions?.endpoint) {
Expand All @@ -114,7 +119,7 @@ function setupOpenTelemetry({
* @param {opentelemetry.api.MeterOptions} [options]
* @returns {opentelemetry.api.Meter}
*/
function getMeter(name, version, options) {
export function getMeter(name, version, options) {
if (!instances) {
throw Object.assign(
new Error(
Expand All @@ -128,5 +133,11 @@ function getMeter(name, version, options) {
return opentelemetry.api.metrics.getMeter(name, version, options);
}

exports.setup = setupOpenTelemetry;
exports.getMeter = getMeter;
/**
* Helper method for us to clear instances during testing.
*
* @private
*/
export function clearInstances() {
instances = undefined;
}
59 changes: 59 additions & 0 deletions packages/opentelemetry/lib/setup-from-env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { setup } from './index.js';

/**
* @import { MetricsOptions, TracingOptions, ViewOptions } from '@dotcom-reliability-kit/opentelemetry'
*/

/**
* @param {NodeJS.ProcessEnv} env
*/
export function setupFromEnv(env) {
/** @type {TracingOptions | undefined} */
let tracing;
if (env.OPENTELEMETRY_TRACING_ENDPOINT) {
tracing = {
authorizationHeader: env.OPENTELEMETRY_AUTHORIZATION_HEADER,
endpoint: env.OPENTELEMETRY_TRACING_ENDPOINT,
samplePercentage: env.OPENTELEMETRY_TRACING_SAMPLE_PERCENTAGE
? Number(env.OPENTELEMETRY_TRACING_SAMPLE_PERCENTAGE)
: undefined
};
}

/** @type {MetricsOptions | undefined} */
let metrics;
if (env.OPENTELEMETRY_METRICS_ENDPOINT) {
metrics = {
apiGatewayKey: env.OPENTELEMETRY_API_GATEWAY_KEY,
endpoint: env.OPENTELEMETRY_METRICS_ENDPOINT
};
}

/**
* @param {string} input
* @returns {number[]}
*/
function parseListOfNumbers(input) {
return input.split(',').map((item) => Number(item.trim()));
}

/** @type {ViewOptions} */
const views = {};
if (env.OPENTELEMETRY_VIEWS_HTTP_SERVER_DURATION_BUCKETS) {
views.httpServerDurationBuckets = parseListOfNumbers(
env.OPENTELEMETRY_VIEWS_HTTP_SERVER_DURATION_BUCKETS
);
}
if (env.OPENTELEMETRY_VIEWS_HTTP_CLIENT_DURATION_BUCKETS) {
views.httpClientDurationBuckets = parseListOfNumbers(
env.OPENTELEMETRY_VIEWS_HTTP_CLIENT_DURATION_BUCKETS
);
}

setup({
logInternals: Boolean(env.OPENTELEMETRY_LOG_INTERNALS),
metrics,
tracing,
views
});
}
1 change: 1 addition & 0 deletions packages/opentelemetry/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "@dotcom-reliability-kit/opentelemetry",
"version": "3.2.5",
"type": "module",
"description": "An OpenTelemetry client that's preconfigured for drop-in use in FT apps.",
"repository": {
"type": "git",
Expand Down
Loading
Loading