Skip to content

Commit 31ad5c0

Browse files
chore(otel): avoid lazy loading workflow package in interceptors (#1872)
1 parent bc32cf1 commit 31ad5c0

File tree

12 files changed

+104
-112
lines changed

12 files changed

+104
-112
lines changed

packages/interceptors-opentelemetry/src/client/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
UPDATE_ID_ATTR_KEY,
2727
TERMINATE_REASON_ATTR_KEY,
2828
} from '../instrumentation';
29-
import { SpanName, SPAN_DELIMITER } from '../workflow';
29+
import { SpanName, SPAN_DELIMITER } from '../workflow/definitions';
3030

3131
export interface InterceptorOptions {
3232
readonly tracer?: otel.Tracer;

packages/interceptors-opentelemetry/src/worker/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ import {
1818
RUN_ID_ATTR_KEY,
1919
ACTIVITY_ID_ATTR_KEY,
2020
} from '../instrumentation';
21-
import { type OpenTelemetryWorkflowExporter, type SerializableSpan, SpanName, SPAN_DELIMITER } from '../workflow';
21+
import {
22+
type OpenTelemetryWorkflowExporter,
23+
type SerializableSpan,
24+
SpanName,
25+
SPAN_DELIMITER,
26+
} from '../workflow/definitions';
2227

2328
export interface InterceptorOptions {
2429
readonly tracer?: otel.Tracer;

packages/interceptors-opentelemetry/src/workflow/context-manager.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
import { type AsyncLocalStorage } from 'async_hooks';
22
import * as otel from '@opentelemetry/api';
3-
import { ensureWorkflowModuleLoaded } from './workflow-module-loader';
43

54
export class ContextManager implements otel.ContextManager {
65
// The workflow sandbox provides AsyncLocalStorage through globalThis.
76
protected storage: AsyncLocalStorage<otel.Context> = new (globalThis as any).AsyncLocalStorage();
87

9-
public constructor() {
10-
ensureWorkflowModuleLoaded();
11-
}
12-
138
active(): otel.Context {
14-
return this.storage!.getStore() || otel.ROOT_CONTEXT;
9+
return this.storage.getStore() || otel.ROOT_CONTEXT;
1510
}
1611

1712
bind<T>(context: otel.Context, target: T): T {
@@ -42,7 +37,7 @@ export class ContextManager implements otel.ContextManager {
4237
}
4338

4439
disable(): this {
45-
this.storage!.disable();
40+
this.storage.disable();
4641
return this;
4742
}
4843

@@ -53,6 +48,6 @@ export class ContextManager implements otel.ContextManager {
5348
...args: A
5449
): ReturnType<F> {
5550
const cb = thisArg == null ? fn : fn.bind(thisArg);
56-
return this.storage!.run(context, cb, ...args);
51+
return this.storage.run(context, cb, ...args);
5752
}
5853
}

packages/interceptors-opentelemetry/src/workflow/definitions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as otel from '@opentelemetry/api';
22
import * as tracing from '@opentelemetry/sdk-trace-base';
33
import { InstrumentationLibrary } from '@opentelemetry/core'; // eslint-disable deprecation/deprecation
4-
import { Sink, Sinks } from '@temporalio/workflow';
4+
import type { Sink, Sinks } from '@temporalio/workflow';
55

66
/**
77
* Serializable version of the opentelemetry Span for cross isolate copying

packages/interceptors-opentelemetry/src/workflow/index.ts

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
import { ContextManager } from './context-manager';
3737
import { SpanName, SPAN_DELIMITER } from './definitions';
3838
import { SpanExporter } from './span-exporter';
39-
import { ensureWorkflowModuleLoaded, getWorkflowModule, hasSdkFlag } from './workflow-module-loader';
39+
import { workflowInfo, ContinueAsNew, getActivator, SdkFlags } from './workflow-imports';
4040

4141
export * from './definitions';
4242

@@ -67,18 +67,12 @@ function getTracer(): otel.Tracer {
6767
export class OpenTelemetryInboundInterceptor implements WorkflowInboundCallsInterceptor {
6868
protected readonly tracer = getTracer();
6969

70-
public constructor() {
71-
ensureWorkflowModuleLoaded();
72-
}
73-
7470
public async execute(
7571
input: WorkflowExecuteInput,
7672
next: Next<WorkflowInboundCallsInterceptor, 'execute'>
7773
): Promise<unknown> {
78-
const { workflowInfo, ContinueAsNew } = getWorkflowModule();
79-
8074
const context = extractContextFromHeaders(input.headers);
81-
if (!hasSdkFlag('OpenTelemetryInterceporsAvoidsExtraYields')) await Promise.resolve();
75+
if (!getActivator().hasFlag(SdkFlags.OpenTelemetryInterceporsAvoidsExtraYields)) await Promise.resolve();
8276

8377
return await instrument({
8478
tracer: this.tracer,
@@ -94,10 +88,10 @@ export class OpenTelemetryInboundInterceptor implements WorkflowInboundCallsInte
9488
next: Next<WorkflowInboundCallsInterceptor, 'handleSignal'>
9589
): Promise<void> {
9690
// Tracing of inbound signals was added in v1.11.5.
97-
if (!hasSdkFlag('OpenTelemetryInterceptorsTracesInboundSignals')) return next(input);
91+
if (!getActivator().hasFlag(SdkFlags.OpenTelemetryInterceptorsTracesInboundSignals)) return next(input);
9892

9993
const context = extractContextFromHeaders(input.headers);
100-
if (!hasSdkFlag('OpenTelemetryInterceporsAvoidsExtraYields')) await Promise.resolve();
94+
if (!getActivator().hasFlag(SdkFlags.OpenTelemetryInterceporsAvoidsExtraYields)) await Promise.resolve();
10195

10296
return await instrument({
10397
tracer: this.tracer,
@@ -111,7 +105,7 @@ export class OpenTelemetryInboundInterceptor implements WorkflowInboundCallsInte
111105
input: UpdateInput,
112106
next: Next<WorkflowInboundCallsInterceptor, 'handleUpdate'>
113107
): Promise<unknown> {
114-
if (!hasSdkFlag('OpenTelemetryInterceptorsInstrumentsAllMethods')) return next(input);
108+
if (!getActivator().hasFlag(SdkFlags.OpenTelemetryInterceptorsInstrumentsAllMethods)) return next(input);
115109

116110
const context = extractContextFromHeaders(input.headers);
117111

@@ -127,7 +121,7 @@ export class OpenTelemetryInboundInterceptor implements WorkflowInboundCallsInte
127121
}
128122

129123
public validateUpdate(input: UpdateInput, next: Next<WorkflowInboundCallsInterceptor, 'validateUpdate'>): void {
130-
if (!hasSdkFlag('OpenTelemetryInterceptorsInstrumentsAllMethods')) return next(input);
124+
if (!getActivator().hasFlag(SdkFlags.OpenTelemetryInterceptorsInstrumentsAllMethods)) return next(input);
131125

132126
const context = extractContextFromHeaders(input.headers);
133127
instrumentSync({
@@ -145,7 +139,7 @@ export class OpenTelemetryInboundInterceptor implements WorkflowInboundCallsInte
145139
input: QueryInput,
146140
next: Next<WorkflowInboundCallsInterceptor, 'handleQuery'>
147141
): Promise<unknown> {
148-
if (!hasSdkFlag('OpenTelemetryInterceptorsInstrumentsAllMethods')) return next(input);
142+
if (!getActivator().hasFlag(SdkFlags.OpenTelemetryInterceptorsInstrumentsAllMethods)) return next(input);
149143

150144
const context = extractContextFromHeaders(input.headers);
151145

@@ -168,10 +162,6 @@ export class OpenTelemetryInboundInterceptor implements WorkflowInboundCallsInte
168162
export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsInterceptor {
169163
protected readonly tracer = getTracer();
170164

171-
public constructor() {
172-
ensureWorkflowModuleLoaded();
173-
}
174-
175165
public async scheduleActivity(
176166
input: ActivityInput,
177167
next: Next<WorkflowOutboundCallsInterceptor, 'scheduleActivity'>
@@ -181,7 +171,7 @@ export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsIn
181171
spanName: `${SpanName.ACTIVITY_START}${SPAN_DELIMITER}${input.activityType}`,
182172
fn: async () => {
183173
const headers = headersWithContext(input.headers);
184-
if (!hasSdkFlag('OpenTelemetryInterceporsAvoidsExtraYields')) await Promise.resolve();
174+
if (!getActivator().hasFlag(SdkFlags.OpenTelemetryInterceporsAvoidsExtraYields)) await Promise.resolve();
185175

186176
return next({
187177
...input,
@@ -196,14 +186,14 @@ export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsIn
196186
next: Next<WorkflowOutboundCallsInterceptor, 'scheduleLocalActivity'>
197187
): Promise<unknown> {
198188
// Tracing of local activities was added in v1.11.6.
199-
if (!hasSdkFlag('OpenTelemetryInterceptorsTracesLocalActivities')) return next(input);
189+
if (!getActivator().hasFlag(SdkFlags.OpenTelemetryInterceptorsTracesLocalActivities)) return next(input);
200190

201191
return await instrument({
202192
tracer: this.tracer,
203193
spanName: `${SpanName.ACTIVITY_START}${SPAN_DELIMITER}${input.activityType}`,
204194
fn: async () => {
205195
const headers = headersWithContext(input.headers);
206-
if (!hasSdkFlag('OpenTelemetryInterceporsAvoidsExtraYields')) await Promise.resolve();
196+
if (!getActivator().hasFlag(SdkFlags.OpenTelemetryInterceporsAvoidsExtraYields)) await Promise.resolve();
207197

208198
return next({
209199
...input,
@@ -217,7 +207,7 @@ export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsIn
217207
input: StartNexusOperationInput,
218208
next: Next<WorkflowOutboundCallsInterceptor, 'startNexusOperation'>
219209
): Promise<StartNexusOperationOutput> {
220-
if (!hasSdkFlag('OpenTelemetryInterceptorsInstrumentsAllMethods')) return next(input);
210+
if (!getActivator().hasFlag(SdkFlags.OpenTelemetryInterceptorsInstrumentsAllMethods)) return next(input);
221211

222212
return await instrument({
223213
tracer: this.tracer,
@@ -240,7 +230,7 @@ export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsIn
240230
spanName: `${SpanName.CHILD_WORKFLOW_START}${SPAN_DELIMITER}${input.workflowType}`,
241231
fn: async () => {
242232
const headers = headersWithContext(input.headers);
243-
if (!hasSdkFlag('OpenTelemetryInterceporsAvoidsExtraYields')) await Promise.resolve();
233+
if (!getActivator().hasFlag(SdkFlags.OpenTelemetryInterceporsAvoidsExtraYields)) await Promise.resolve();
244234

245235
return next({
246236
...input,
@@ -254,13 +244,12 @@ export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsIn
254244
input: ContinueAsNewInput,
255245
next: Next<WorkflowOutboundCallsInterceptor, 'continueAsNew'>
256246
): Promise<never> {
257-
const { ContinueAsNew } = getWorkflowModule();
258247
return await instrument({
259248
tracer: this.tracer,
260249
spanName: `${SpanName.CONTINUE_AS_NEW}${SPAN_DELIMITER}${input.options.workflowType}`,
261250
fn: async () => {
262251
const headers = headersWithContext(input.headers);
263-
if (!hasSdkFlag('OpenTelemetryInterceporsAvoidsExtraYields')) await Promise.resolve();
252+
if (!getActivator().hasFlag(SdkFlags.OpenTelemetryInterceporsAvoidsExtraYields)) await Promise.resolve();
264253

265254
return next({
266255
...input,
@@ -280,7 +269,7 @@ export class OpenTelemetryOutboundInterceptor implements WorkflowOutboundCallsIn
280269
spanName: `${SpanName.WORKFLOW_SIGNAL}${SPAN_DELIMITER}${input.signalName}`,
281270
fn: async () => {
282271
const headers = headersWithContext(input.headers);
283-
if (!hasSdkFlag('OpenTelemetryInterceporsAvoidsExtraYields')) await Promise.resolve();
272+
if (!getActivator().hasFlag(SdkFlags.OpenTelemetryInterceporsAvoidsExtraYields)) await Promise.resolve();
284273

285274
return next({
286275
...input,

packages/interceptors-opentelemetry/src/workflow/runtime.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
* Sets global variables required for importing opentelemetry in isolate
33
* @module
44
*/
5-
import { getWorkflowModuleIfAvailable } from './workflow-module-loader';
5+
import { inWorkflowContext } from './workflow-imports';
66

7-
const inWorkflowContext = getWorkflowModuleIfAvailable()?.inWorkflowContext;
8-
9-
if (inWorkflowContext?.()) {
7+
if (inWorkflowContext()) {
108
// Required by opentelemetry (pretend to be a browser)
119
Object.assign(globalThis, {
1210
performance: {

packages/interceptors-opentelemetry/src/workflow/span-exporter.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import * as tracing from '@opentelemetry/sdk-trace-base';
22
import { ExportResult, ExportResultCode } from '@opentelemetry/core';
33
import { OpenTelemetrySinks, SerializableSpan } from './definitions';
4-
import { ensureWorkflowModuleLoaded, getWorkflowModuleIfAvailable } from './workflow-module-loader';
5-
6-
const exporter = getWorkflowModuleIfAvailable()?.proxySinks<OpenTelemetrySinks>()?.exporter;
4+
import { proxySinks } from './workflow-imports';
75

86
export class SpanExporter implements tracing.SpanExporter {
9-
public constructor() {
10-
ensureWorkflowModuleLoaded();
11-
}
7+
private exporter?: OpenTelemetrySinks['exporter'];
128

139
public export(spans: tracing.ReadableSpan[], resultCallback: (result: ExportResult) => void): void {
14-
exporter!.export(spans.map((span) => this.makeSerializable(span)));
10+
if (!this.exporter) {
11+
this.exporter = proxySinks<OpenTelemetrySinks>().exporter;
12+
}
13+
this.exporter.export(spans.map((span) => this.makeSerializable(span)));
1514
resultCallback({ code: ExportResultCode.SUCCESS });
1615
}
1716

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Real workflow imports for otel interceptors.
3+
* This replaces the stub via webpack alias when bundled.
4+
*
5+
* @module
6+
*/
7+
export { inWorkflowContext, proxySinks, workflowInfo, AsyncLocalStorage, ContinueAsNew } from '@temporalio/workflow';
8+
export { SdkFlags } from '@temporalio/workflow/lib/flags';
9+
export { getActivator } from '@temporalio/workflow/lib/global-attributes';
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Workflow imports stub module.
3+
*
4+
* This module provides stubs for workflow functionality needed by interceptors.
5+
* When bundled by the workflow bundler, this is replaced with the real
6+
* implementation via NormalModuleReplacementPlugin.
7+
*
8+
* @module
9+
*/
10+
import type {
11+
inWorkflowContext as inWorkflowContextT,
12+
workflowInfo as workflowInfoT,
13+
proxySinks as proxySinksT,
14+
AsyncLocalStorage as AsyncLocalStorageT,
15+
ContinueAsNew as ContinueAsNewT,
16+
} from '@temporalio/workflow';
17+
import type { getActivator as getActivatorT } from '@temporalio/workflow/lib/global-attributes';
18+
import type { SdkFlags as SdkFlagsT } from '@temporalio/workflow/lib/flags';
19+
20+
import { IllegalStateError } from '@temporalio/common';
21+
22+
// always returns false since if using this implementation, we are outside of workflow context
23+
export const inWorkflowContext: typeof inWorkflowContextT = () => false;
24+
25+
// All of the following stubs will throw if used
26+
export const workflowInfo: typeof workflowInfoT = () => {
27+
throw new IllegalStateError('Workflow.workflowInfo(...) may only be used from a Workflow Execution.');
28+
};
29+
30+
export const ContinueAsNew = class ContinueAsNew {} as unknown as typeof ContinueAsNewT;
31+
32+
export const AsyncLocalStorage = class AsyncLocalStorage {} as unknown as typeof AsyncLocalStorageT;
33+
34+
export const getActivator: typeof getActivatorT = () => {
35+
throw new IllegalStateError('Workflow uninitialized');
36+
};
37+
38+
export const proxySinks: typeof proxySinksT = () => {
39+
throw new IllegalStateError('Proxied sinks functions may only be used from a Workflow Execution.');
40+
};
41+
42+
export const SdkFlags: typeof SdkFlagsT = {} as typeof SdkFlagsT;

packages/interceptors-opentelemetry/src/workflow/workflow-module-loader.ts

Lines changed: 0 additions & 64 deletions
This file was deleted.

0 commit comments

Comments
 (0)