Skip to content

Commit d1e0808

Browse files
authored
chore: use latest Node SDK for Otel (#1)
1 parent a5915dc commit d1e0808

File tree

5 files changed

+1034
-1012
lines changed

5 files changed

+1034
-1012
lines changed

packages/app-kit/package.json

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,31 +28,30 @@
2828
"dependencies": {
2929
"@databricks/sdk-experimental": "^0.15.0",
3030
"@opentelemetry/api": "^1.9.0",
31-
"@opentelemetry/api-logs": "^0.53.0",
32-
"@opentelemetry/auto-instrumentations-node": "^0.50.0",
33-
"@opentelemetry/context-async-hooks": "^1.26.0",
34-
"@opentelemetry/exporter-logs-otlp-proto": "^0.53.0",
35-
"@opentelemetry/exporter-metrics-otlp-proto": "^0.53.0",
36-
"@opentelemetry/exporter-trace-otlp-proto": "^0.53.0",
37-
"@opentelemetry/instrumentation": "^0.53.0",
38-
"@opentelemetry/resources": "^1.26.0",
39-
"@opentelemetry/sdk-logs": "^0.53.0",
40-
"@opentelemetry/sdk-metrics": "^1.26.0",
41-
"@opentelemetry/sdk-node": "^0.53.0",
42-
"@opentelemetry/sdk-trace-base": "^1.26.0",
43-
"@opentelemetry/sdk-trace-node": "^1.26.0",
44-
"@opentelemetry/semantic-conventions": "^1.27.0",
45-
"shared": "workspace:*",
46-
"zod-to-ts": "^2.0.0",
31+
"@opentelemetry/api-logs": "^0.208.0",
32+
"@opentelemetry/auto-instrumentations-node": "^0.67.0",
33+
"@opentelemetry/exporter-logs-otlp-proto": "^0.208.0",
34+
"@opentelemetry/exporter-metrics-otlp-proto": "^0.208.0",
35+
"@opentelemetry/exporter-trace-otlp-proto": "^0.208.0",
36+
"@opentelemetry/instrumentation": "^0.208.0",
37+
"@opentelemetry/instrumentation-express": "^0.57.0",
38+
"@opentelemetry/instrumentation-http": "^0.208.0",
39+
"@opentelemetry/resources": "^2.2.0",
40+
"@opentelemetry/sdk-logs": "^0.208.0",
41+
"@opentelemetry/sdk-metrics": "^2.2.0",
42+
"@opentelemetry/sdk-node": "^0.208.0",
43+
"@opentelemetry/semantic-conventions": "^1.38.0",
4744
"dotenv": "^16.6.1",
48-
"express": "^4.18.2",
45+
"express": "^4.22.0",
46+
"shared": "workspace:*",
4947
"vite": "npm:[email protected]",
50-
"ws": "^8.18.3"
48+
"ws": "^8.18.3",
49+
"zod-to-ts": "^2.0.0"
5150
},
5251
"devDependencies": {
53-
"@types/express": "^4.17.21",
52+
"@types/express": "^4.17.25",
5453
"@types/ws": "^8.18.1",
55-
"@vitejs/plugin-react": "^5.0.4"
54+
"@vitejs/plugin-react": "^5.1.1"
5655
},
5756
"overrides": {
5857
"vite": "npm:[email protected]"

packages/app-kit/src/telemetry/telemetry-manager.ts

Lines changed: 50 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import type { TelemetryOptions } from "shared";
2-
import { metrics } from "@opentelemetry/api";
3-
import { logs } from "@opentelemetry/api-logs";
42
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
5-
import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
63
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto";
74
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
85
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
@@ -11,47 +8,30 @@ import {
118
registerInstrumentations as otelRegisterInstrumentations,
129
} from "@opentelemetry/instrumentation";
1310
import {
14-
detectResourcesSync,
11+
detectResources,
1512
envDetector,
1613
hostDetector,
1714
processDetector,
18-
Resource,
15+
type Resource,
16+
resourceFromAttributes,
1917
} from "@opentelemetry/resources";
20-
import {
21-
BatchLogRecordProcessor,
22-
LoggerProvider,
23-
} from "@opentelemetry/sdk-logs";
24-
import {
25-
MeterProvider,
26-
PeriodicExportingMetricReader,
27-
} from "@opentelemetry/sdk-metrics";
28-
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
29-
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
18+
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
19+
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
20+
import { AlwaysOnSampler } from "@opentelemetry/sdk-trace-base";
3021
import {
3122
ATTR_SERVICE_NAME,
3223
ATTR_SERVICE_VERSION,
3324
} from "@opentelemetry/semantic-conventions";
3425
import { TelemetryProvider } from "./telemetry-provider";
3526
import type { TelemetryConfig } from "./types";
27+
import { NodeSDK } from "@opentelemetry/sdk-node";
3628

3729
export class TelemetryManager {
3830
private static readonly DEFAULT_EXPORT_INTERVAL_MS = 10000;
31+
private static readonly DEFAULT_FALLBACK_APP_NAME = "databricks-app";
3932

4033
private static instance?: TelemetryManager;
41-
private static shutdownRegistered = false;
42-
private tracerProvider?: NodeTracerProvider;
43-
private meterProvider?: MeterProvider;
44-
private loggerProvider?: LoggerProvider;
45-
private isInitialized = false;
46-
47-
private constructor() {}
48-
49-
static getInstance(): TelemetryManager {
50-
if (!TelemetryManager.instance) {
51-
TelemetryManager.instance = new TelemetryManager();
52-
}
53-
return TelemetryManager.instance;
54-
}
34+
private sdk?: NodeSDK;
5535

5636
/**
5737
* Create a scoped telemetry provider for a specific plugin.
@@ -68,43 +48,57 @@ export class TelemetryManager {
6848
return new TelemetryProvider(pluginName, globalManager, telemetryConfig);
6949
}
7050

51+
private constructor() {}
52+
53+
static getInstance(): TelemetryManager {
54+
if (!TelemetryManager.instance) {
55+
TelemetryManager.instance = new TelemetryManager();
56+
}
57+
return TelemetryManager.instance;
58+
}
59+
7160
static initialize(config: Partial<TelemetryConfig> = {}): void {
72-
TelemetryManager.registerShutdown();
7361
const instance = TelemetryManager.getInstance();
7462
instance._initialize(config);
7563
}
7664

7765
private _initialize(config: Partial<TelemetryConfig>): void {
78-
if (this.isInitialized) {
79-
return;
80-
}
66+
if (this.sdk) return;
8167

8268
if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
8369
console.log(
8470
"[Telemetry] OTEL_EXPORTER_OTLP_ENDPOINT not set; telemetry disabled",
8571
);
86-
this.isInitialized = true;
8772
return;
8873
}
8974

9075
try {
91-
const resource = this.createResource(config);
92-
this.setupTraces(resource, config);
93-
this.setupMetrics(resource, config);
94-
this.setupLogs(resource, config);
95-
96-
const instrumentations =
97-
config.instrumentations || this.getDefaultInstrumentations();
98-
otelRegisterInstrumentations({
99-
tracerProvider: this.tracerProvider,
100-
meterProvider: this.meterProvider,
101-
instrumentations,
76+
this.sdk = new NodeSDK({
77+
resource: this.createResource(config),
78+
autoDetectResources: false,
79+
sampler: new AlwaysOnSampler(),
80+
traceExporter: new OTLPTraceExporter({ headers: config.headers }),
81+
metricReaders: [
82+
new PeriodicExportingMetricReader({
83+
exporter: new OTLPMetricExporter({ headers: config.headers }),
84+
exportIntervalMillis:
85+
config.exportIntervalMs ||
86+
TelemetryManager.DEFAULT_EXPORT_INTERVAL_MS,
87+
}),
88+
],
89+
logRecordProcessors: [
90+
new BatchLogRecordProcessor(
91+
new OTLPLogExporter({ headers: config.headers }),
92+
),
93+
],
94+
instrumentations: this.getDefaultInstrumentations(),
10295
});
10396

104-
this.isInitialized = true;
97+
this.sdk.start();
98+
this.registerShutdown();
99+
console.log("[Telemetry] Initialized successfully");
105100
} catch (error) {
106-
console.error("[Telemetry] Failed to initialize telemetry:", error);
107-
this.isInitialized = true;
101+
console.error("[Telemetry] Failed to initialize:", error);
108102
}
109103
}
110104

@@ -115,8 +109,7 @@ export class TelemetryManager {
115109
*/
116110
registerInstrumentations(instrumentations: Instrumentation[]): void {
117111
otelRegisterInstrumentations({
118-
tracerProvider: this.tracerProvider,
119-
meterProvider: this.meterProvider,
112+
// global providers set by NodeSDK.start()
120113
instrumentations,
121114
});
122115
}
@@ -126,83 +119,17 @@ export class TelemetryManager {
126119
config.serviceName ||
127120
process.env.OTEL_SERVICE_NAME ||
128121
process.env.DATABRICKS_APP_NAME ||
129-
"databricks-app";
130-
131-
const initialResource = new Resource({
122+
TelemetryManager.DEFAULT_FALLBACK_APP_NAME;
123+
const initialResource = resourceFromAttributes({
132124
[ATTR_SERVICE_NAME]: serviceName,
133125
[ATTR_SERVICE_VERSION]: config.serviceVersion ?? undefined,
134126
});
135-
const detectedResource = detectResourcesSync({
127+
const detectedResource = detectResources({
136128
detectors: [envDetector, hostDetector, processDetector],
137129
});
138130
return initialResource.merge(detectedResource);
139131
}
140132

141-
private setupTraces(
142-
resource: Resource,
143-
config: Partial<TelemetryConfig>,
144-
): void {
145-
this.tracerProvider = new NodeTracerProvider({
146-
resource,
147-
});
148-
149-
const traceExporter = new OTLPTraceExporter({
150-
// reads the endpoint automatically
151-
headers: config.headers || {},
152-
});
153-
154-
const spanProcessor = new BatchSpanProcessor(traceExporter);
155-
this.tracerProvider.addSpanProcessor(spanProcessor);
156-
157-
const contextManager = new AsyncLocalStorageContextManager();
158-
contextManager.enable();
159-
160-
this.tracerProvider.register({
161-
contextManager: contextManager,
162-
});
163-
}
164-
165-
private setupMetrics(
166-
resource: Resource,
167-
config: Partial<TelemetryConfig>,
168-
): void {
169-
this.meterProvider = new MeterProvider({
170-
resource,
171-
});
172-
173-
const metricExporter = new OTLPMetricExporter({
174-
// reads the endpoint automatically
175-
headers: config.headers || {},
176-
});
177-
178-
const metricReader = new PeriodicExportingMetricReader({
179-
exporter: metricExporter,
180-
exportIntervalMillis:
181-
config.exportIntervalMs || TelemetryManager.DEFAULT_EXPORT_INTERVAL_MS,
182-
});
183-
184-
this.meterProvider.addMetricReader(metricReader);
185-
metrics.setGlobalMeterProvider(this.meterProvider);
186-
}
187-
188-
private setupLogs(
189-
resource: Resource,
190-
config: Partial<TelemetryConfig>,
191-
): void {
192-
this.loggerProvider = new LoggerProvider({
193-
resource,
194-
});
195-
196-
const logExporter = new OTLPLogExporter({
197-
// reads the endpoint automatically
198-
headers: config.headers || {},
199-
});
200-
201-
const logProcessor = new BatchLogRecordProcessor(logExporter);
202-
this.loggerProvider.addLogRecordProcessor(logProcessor);
203-
logs.setGlobalLoggerProvider(this.loggerProvider);
204-
}
205-
206133
private getDefaultInstrumentations(): Instrumentation[] {
207134
return [
208135
...getNodeAutoInstrumentations({
@@ -231,41 +158,22 @@ export class TelemetryManager {
231158
];
232159
}
233160

234-
private static registerShutdown() {
235-
if (TelemetryManager.shutdownRegistered) {
236-
return;
237-
}
238-
161+
private registerShutdown() {
239162
const shutdownFn = async () => {
240163
await TelemetryManager.getInstance().shutdown();
241164
};
242165
process.once("SIGTERM", shutdownFn);
243166
process.once("SIGINT", shutdownFn);
244-
TelemetryManager.shutdownRegistered = true;
245167
}
246168

247169
private async shutdown(): Promise<void> {
248-
if (!this.isInitialized) {
170+
if (!this.sdk) {
249171
return;
250172
}
251173

252174
try {
253-
if (this.tracerProvider) {
254-
await this.tracerProvider.shutdown();
255-
this.tracerProvider = undefined;
256-
}
257-
258-
if (this.meterProvider) {
259-
await this.meterProvider.shutdown();
260-
this.meterProvider = undefined;
261-
}
262-
263-
if (this.loggerProvider) {
264-
await this.loggerProvider.shutdown();
265-
this.loggerProvider = undefined;
266-
}
267-
268-
this.isInitialized = false;
175+
await this.sdk.shutdown();
176+
this.sdk = undefined;
269177
} catch (error) {
270178
console.error("[Telemetry] Error shutting down:", error);
271179
}

packages/app-kit/src/telemetry/tests/telemetry-manager.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ vi.mock("@opentelemetry/resources", async () => {
3333
>("@opentelemetry/resources");
3434
return {
3535
...actual,
36-
detectResourcesSync: vi.fn().mockReturnValue(
37-
new actual.Resource({
36+
detectResources: vi.fn(() => {
37+
return actual.resourceFromAttributes({
3838
"host.name": "test-host",
39-
}),
40-
),
39+
});
40+
}),
4141
};
4242
});
4343

@@ -69,17 +69,17 @@ describe("TelemetryManager", () => {
6969
expect(instance1).toBe(instance2);
7070
});
7171

72-
test("should call detectResourcesSync when initializing", async () => {
72+
test("should call detectResources when initializing", async () => {
7373
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "http://localhost:4318";
7474

75-
const { detectResourcesSync } = await import("@opentelemetry/resources");
75+
const { detectResources } = await import("@opentelemetry/resources");
7676
vi.clearAllMocks();
7777

7878
TelemetryManager.initialize({
7979
serviceName: "test-service-config",
8080
});
8181

82-
expect(detectResourcesSync).toHaveBeenCalled();
82+
expect(detectResources).toHaveBeenCalled();
8383
});
8484

8585
test("should initialize providers and create telemetry instances", async () => {

packages/app-kit/src/telemetry/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import type { Meter, Span, SpanOptions, Tracer } from "@opentelemetry/api";
22
import type { Logger, LogRecord } from "@opentelemetry/api-logs";
33
import type { Instrumentation } from "@opentelemetry/instrumentation";
4-
import type { NodeSDKConfiguration } from "@opentelemetry/sdk-node";
54

65
export interface TelemetryConfig {
76
serviceName?: string;
87
serviceVersion?: string;
9-
instrumentations?: NodeSDKConfiguration["instrumentations"];
8+
instrumentations?: Instrumentation[];
109
exportIntervalMs?: number;
1110
headers?: Record<string, string>;
1211
}

0 commit comments

Comments
 (0)