Skip to content

Commit c70f28e

Browse files
committed
feat: allow customizing OpenTelemetry infrastructure
1 parent 4558cad commit c70f28e

File tree

3 files changed

+30
-5
lines changed

3 files changed

+30
-5
lines changed

src/bootstrap.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import assert from 'node:assert';
44
import { config } from 'dotenv';
55
import { readPackageUp } from 'read-package-up';
66
import type { NormalizedPackageJson } from 'read-package-up';
7+
import { NodeSDKConfiguration } from '@opentelemetry/sdk-node';
78

89
import type {
910
AnyServiceLocals,
@@ -64,6 +65,7 @@ async function getServiceDetails(argv: BootstrapArguments = {}) {
6465
rootDirectory: path.dirname(pkg.path),
6566
name: parts[parts.length - 1],
6667
version: pkg.packageJson.version,
68+
customizer: (pkg.packageJson.config?.telemetry as { customizer?: string })?.customizer,
6769
};
6870
}
6971

@@ -81,7 +83,7 @@ export async function bootstrap<
8183
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
8284
RLocals extends RequestLocals = RequestLocals,
8385
>(argv?: BootstrapArguments) {
84-
const { main, rootDirectory, name, version } = await getServiceDetails(argv);
86+
const { main, rootDirectory, name, version, customizer } = await getServiceDetails(argv);
8587

8688
let entrypoint: string;
8789
let codepath: 'build' | 'dist' | 'src' = 'build';
@@ -104,12 +106,23 @@ export async function bootstrap<
104106

105107
const absoluteEntrypoint = path.resolve(rootDirectory, entrypoint);
106108
if (argv?.telemetry) {
109+
let otelCustomizer:
110+
| ((options: Partial<NodeSDKConfiguration>) => Partial<NodeSDKConfiguration>)
111+
| undefined = undefined;
112+
if (customizer) {
113+
// Customize OTEL with a dynamic import based on the codePath (so put it in src, generally)
114+
otelCustomizer = (await import(`$(codePath}/${customizer}`)).NodeSDKConfiguration;
115+
if (typeof otelCustomizer === 'object') {
116+
otelCustomizer = (v) => ({ ...v, ...(otelCustomizer as Partial<NodeSDKConfiguration>) });
117+
}
118+
}
107119
return startWithTelemetry<SLocals, RLocals>({
108120
name,
109121
rootDirectory,
110122
service: absoluteEntrypoint,
111123
codepath,
112124
version,
125+
customizer: otelCustomizer,
113126
});
114127
}
115128

src/telemetry/index.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,14 @@ let telemetrySdk: opentelemetry.NodeSDK | undefined;
7272
* In addition, since we have to load it right away before configuration
7373
* is available, we can't use configuration to decide anything.
7474
*/
75-
export async function startGlobalTelemetry(serviceName: string) {
75+
export async function startGlobalTelemetry(
76+
serviceName: string,
77+
customizer?:
78+
| ((
79+
options: Partial<opentelemetry.NodeSDKConfiguration>,
80+
) => Partial<opentelemetry.NodeSDKConfiguration>)
81+
| undefined,
82+
) {
7683
if (!prometheusExporter) {
7784
const { metrics, logs, NodeSDK } = opentelemetry;
7885

@@ -90,7 +97,7 @@ export async function startGlobalTelemetry(serviceName: string) {
9097
prometheusExporter = new PrometheusExporter({ preventServerStart: true });
9198
const instrumentations = getAutoInstrumentations();
9299
const logExporter = getLogExporter();
93-
telemetrySdk = new NodeSDK({
100+
const options: Partial<opentelemetry.NodeSDKConfiguration> = {
94101
serviceName,
95102
autoDetectResources: false,
96103
resource,
@@ -111,7 +118,8 @@ export async function startGlobalTelemetry(serviceName: string) {
111118
},
112119
},
113120
],
114-
});
121+
};
122+
telemetrySdk = new NodeSDK(customizer ? customizer(options) : options);
115123
telemetrySdk.start();
116124
}
117125
}
@@ -130,7 +138,7 @@ export async function startWithTelemetry<
130138
SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
131139
RLocals extends RequestLocals = RequestLocals,
132140
>(options: DelayLoadServiceStartOptions) {
133-
await startGlobalTelemetry(options.name);
141+
await startGlobalTelemetry(options.name, options.customizer);
134142

135143
// eslint-disable-next-line import/no-unresolved, @typescript-eslint/no-var-requires
136144
const { startApp, listen } = (await import('../express-app/app.js')) as {

src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { Request, Response } from 'express';
66
import type { Application } from 'express-serve-static-core';
77
import type { middleware } from 'express-openapi-validator';
88
import type { Meter } from '@opentelemetry/api';
9+
import type { NodeSDKConfiguration } from '@opentelemetry/sdk-node';
910
import { ShortstopHandler } from '@sesamecare-oss/confit';
1011

1112
import { ConfigurationSchema } from './config/schema.js';
@@ -144,6 +145,9 @@ export interface ServiceStartOptions<
144145

145146
export interface DelayLoadServiceStartOptions extends Omit<ServiceStartOptions, 'service'> {
146147
service: string;
148+
customizer?:
149+
| ((options: Partial<NodeSDKConfiguration>) => Partial<NodeSDKConfiguration>)
150+
| undefined;
147151
}
148152

149153
// Handled by service.configure

0 commit comments

Comments
 (0)