@temporalio/interceptors-opentelemetry 1.13.1 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{LICENSE.md → LICENSE} +6 -8
- package/lib/client/index.d.ts +9 -1
- package/lib/client/index.js +122 -3
- package/lib/client/index.js.map +1 -1
- package/lib/instrumentation.d.ts +21 -3
- package/lib/instrumentation.js +52 -12
- package/lib/instrumentation.js.map +1 -1
- package/lib/worker/index.d.ts +8 -7
- package/lib/worker/index.js +29 -4
- package/lib/worker/index.js.map +1 -1
- package/lib/workflow/context-manager.d.ts +2 -1
- package/lib/workflow/context-manager.js +8 -2
- package/lib/workflow/context-manager.js.map +1 -1
- package/lib/workflow/definitions.d.ts +45 -1
- package/lib/workflow/definitions.js +44 -0
- package/lib/workflow/definitions.js.map +1 -1
- package/lib/workflow/index.d.ts +12 -1
- package/lib/workflow/index.js +112 -12
- package/lib/workflow/index.js.map +1 -1
- package/lib/workflow/runtime.js +3 -2
- package/lib/workflow/runtime.js.map +1 -1
- package/lib/workflow/span-exporter.d.ts +1 -0
- package/lib/workflow/span-exporter.js +5 -25
- package/lib/workflow/span-exporter.js.map +1 -1
- package/lib/workflow/workflow-module-loader.d.ts +28 -0
- package/lib/workflow/workflow-module-loader.js +56 -0
- package/lib/workflow/workflow-module-loader.js.map +1 -0
- package/package.json +18 -16
- package/src/client/index.ts +178 -5
- package/src/instrumentation.ts +62 -11
- package/src/worker/index.ts +48 -13
- package/src/workflow/context-manager.ts +13 -5
- package/src/workflow/definitions.ts +54 -0
- package/src/workflow/index.ts +146 -12
- package/src/workflow/runtime.ts +4 -2
- package/src/workflow/span-exporter.ts +7 -3
- package/src/workflow/workflow-module-loader.ts +64 -0
package/src/client/index.ts
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import * as otel from '@opentelemetry/api';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import type {
|
|
3
|
+
Next,
|
|
4
|
+
WorkflowSignalInput,
|
|
5
|
+
WorkflowSignalWithStartInput,
|
|
6
|
+
WorkflowStartInput,
|
|
7
|
+
WorkflowStartOutput,
|
|
8
|
+
WorkflowStartUpdateInput,
|
|
9
|
+
WorkflowStartUpdateOutput,
|
|
10
|
+
WorkflowStartUpdateWithStartInput,
|
|
11
|
+
WorkflowStartUpdateWithStartOutput,
|
|
12
|
+
WorkflowQueryInput,
|
|
13
|
+
WorkflowTerminateInput,
|
|
14
|
+
WorkflowCancelInput,
|
|
15
|
+
WorkflowDescribeInput,
|
|
16
|
+
WorkflowClientInterceptor,
|
|
17
|
+
TerminateWorkflowExecutionResponse,
|
|
18
|
+
RequestCancelWorkflowExecutionResponse,
|
|
19
|
+
DescribeWorkflowExecutionResponse,
|
|
20
|
+
} from '@temporalio/client';
|
|
21
|
+
import {
|
|
22
|
+
instrument,
|
|
23
|
+
headersWithContext,
|
|
24
|
+
RUN_ID_ATTR_KEY,
|
|
25
|
+
WORKFLOW_ID_ATTR_KEY,
|
|
26
|
+
UPDATE_ID_ATTR_KEY,
|
|
27
|
+
TERMINATE_REASON_ATTR_KEY,
|
|
28
|
+
} from '../instrumentation';
|
|
4
29
|
import { SpanName, SPAN_DELIMITER } from '../workflow';
|
|
5
30
|
|
|
6
31
|
export interface InterceptorOptions {
|
|
@@ -24,7 +49,8 @@ export class OpenTelemetryWorkflowClientInterceptor implements WorkflowClientInt
|
|
|
24
49
|
tracer: this.tracer,
|
|
25
50
|
spanName: `${SpanName.WORKFLOW_START}${SPAN_DELIMITER}${input.workflowType}`,
|
|
26
51
|
fn: async (span) => {
|
|
27
|
-
const headers =
|
|
52
|
+
const headers = headersWithContext(input.headers);
|
|
53
|
+
span.setAttribute(WORKFLOW_ID_ATTR_KEY, input.options.workflowId);
|
|
28
54
|
const runId = await next({ ...input, headers });
|
|
29
55
|
span.setAttribute(RUN_ID_ATTR_KEY, runId);
|
|
30
56
|
return runId;
|
|
@@ -36,10 +62,157 @@ export class OpenTelemetryWorkflowClientInterceptor implements WorkflowClientInt
|
|
|
36
62
|
return await instrument({
|
|
37
63
|
tracer: this.tracer,
|
|
38
64
|
spanName: `${SpanName.WORKFLOW_SIGNAL}${SPAN_DELIMITER}${input.signalName}`,
|
|
39
|
-
fn: async () => {
|
|
40
|
-
|
|
65
|
+
fn: async (span) => {
|
|
66
|
+
span.setAttribute(WORKFLOW_ID_ATTR_KEY, input.workflowExecution.workflowId);
|
|
67
|
+
const headers = headersWithContext(input.headers);
|
|
41
68
|
await next({ ...input, headers });
|
|
42
69
|
},
|
|
43
70
|
});
|
|
44
71
|
}
|
|
72
|
+
|
|
73
|
+
async startWithDetails(
|
|
74
|
+
input: WorkflowStartInput,
|
|
75
|
+
next: Next<WorkflowClientInterceptor, 'startWithDetails'>
|
|
76
|
+
): Promise<WorkflowStartOutput> {
|
|
77
|
+
return await instrument({
|
|
78
|
+
tracer: this.tracer,
|
|
79
|
+
spanName: `${SpanName.WORKFLOW_START}${SPAN_DELIMITER}${input.workflowType}`,
|
|
80
|
+
fn: async (span) => {
|
|
81
|
+
const headers = headersWithContext(input.headers);
|
|
82
|
+
span.setAttribute(WORKFLOW_ID_ATTR_KEY, input.options.workflowId);
|
|
83
|
+
const output = await next({ ...input, headers });
|
|
84
|
+
span.setAttribute(RUN_ID_ATTR_KEY, output.runId);
|
|
85
|
+
return output;
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async startUpdate(
|
|
91
|
+
input: WorkflowStartUpdateInput,
|
|
92
|
+
next: Next<WorkflowClientInterceptor, 'startUpdate'>
|
|
93
|
+
): Promise<WorkflowStartUpdateOutput> {
|
|
94
|
+
return await instrument({
|
|
95
|
+
tracer: this.tracer,
|
|
96
|
+
spanName: `${SpanName.WORKFLOW_START_UPDATE}${SPAN_DELIMITER}${input.updateName}`,
|
|
97
|
+
fn: async (span) => {
|
|
98
|
+
span.setAttribute(WORKFLOW_ID_ATTR_KEY, input.workflowExecution.workflowId);
|
|
99
|
+
if (input.options.updateId) {
|
|
100
|
+
span.setAttribute(UPDATE_ID_ATTR_KEY, input.options.updateId);
|
|
101
|
+
}
|
|
102
|
+
const headers = headersWithContext(input.headers);
|
|
103
|
+
const output = await next({ ...input, headers });
|
|
104
|
+
span.setAttribute(RUN_ID_ATTR_KEY, output.workflowRunId);
|
|
105
|
+
return output;
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async startUpdateWithStart(
|
|
111
|
+
input: WorkflowStartUpdateWithStartInput,
|
|
112
|
+
next: Next<WorkflowClientInterceptor, 'startUpdateWithStart'>
|
|
113
|
+
): Promise<WorkflowStartUpdateWithStartOutput> {
|
|
114
|
+
return await instrument({
|
|
115
|
+
tracer: this.tracer,
|
|
116
|
+
spanName: `${SpanName.WORKFLOW_UPDATE_WITH_START}${SPAN_DELIMITER}${input.updateName}`,
|
|
117
|
+
fn: async (span) => {
|
|
118
|
+
span.setAttribute(WORKFLOW_ID_ATTR_KEY, input.workflowStartOptions.workflowId);
|
|
119
|
+
if (input.updateOptions.updateId) {
|
|
120
|
+
span.setAttribute(UPDATE_ID_ATTR_KEY, input.updateOptions.updateId);
|
|
121
|
+
}
|
|
122
|
+
const workflowStartHeaders = headersWithContext(input.workflowStartHeaders);
|
|
123
|
+
const updateHeaders = headersWithContext(input.updateHeaders);
|
|
124
|
+
const output = await next({ ...input, workflowStartHeaders, updateHeaders });
|
|
125
|
+
if (output.workflowExecution.runId) {
|
|
126
|
+
span.setAttribute(RUN_ID_ATTR_KEY, output.workflowExecution.runId);
|
|
127
|
+
}
|
|
128
|
+
return output;
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async signalWithStart(
|
|
134
|
+
input: WorkflowSignalWithStartInput,
|
|
135
|
+
next: Next<WorkflowClientInterceptor, 'signalWithStart'>
|
|
136
|
+
): Promise<string> {
|
|
137
|
+
return await instrument({
|
|
138
|
+
tracer: this.tracer,
|
|
139
|
+
spanName: `${SpanName.WORKFLOW_SIGNAL_WITH_START}${SPAN_DELIMITER}${input.workflowType}`,
|
|
140
|
+
fn: async (span) => {
|
|
141
|
+
span.setAttribute(WORKFLOW_ID_ATTR_KEY, input.options.workflowId);
|
|
142
|
+
const headers = headersWithContext(input.headers);
|
|
143
|
+
const runId = await next({ ...input, headers });
|
|
144
|
+
span.setAttribute(RUN_ID_ATTR_KEY, runId);
|
|
145
|
+
return runId;
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async query(input: WorkflowQueryInput, next: Next<WorkflowClientInterceptor, 'query'>): Promise<unknown> {
|
|
151
|
+
return await instrument({
|
|
152
|
+
tracer: this.tracer,
|
|
153
|
+
spanName: `${SpanName.WORKFLOW_QUERY}${SPAN_DELIMITER}${input.queryType}`,
|
|
154
|
+
fn: async (span) => {
|
|
155
|
+
const headers = headersWithContext(input.headers);
|
|
156
|
+
span.setAttribute(WORKFLOW_ID_ATTR_KEY, input.workflowExecution.workflowId);
|
|
157
|
+
if (input.workflowExecution.runId) {
|
|
158
|
+
span.setAttribute(RUN_ID_ATTR_KEY, input.workflowExecution.runId);
|
|
159
|
+
}
|
|
160
|
+
return await next({ ...input, headers });
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async terminate(
|
|
166
|
+
input: WorkflowTerminateInput,
|
|
167
|
+
next: Next<WorkflowClientInterceptor, 'terminate'>
|
|
168
|
+
): Promise<TerminateWorkflowExecutionResponse> {
|
|
169
|
+
return await instrument({
|
|
170
|
+
tracer: this.tracer,
|
|
171
|
+
spanName: SpanName.WORKFLOW_TERMINATE,
|
|
172
|
+
fn: async (span) => {
|
|
173
|
+
span.setAttribute(WORKFLOW_ID_ATTR_KEY, input.workflowExecution.workflowId);
|
|
174
|
+
if (input.workflowExecution.runId) {
|
|
175
|
+
span.setAttribute(RUN_ID_ATTR_KEY, input.workflowExecution.runId);
|
|
176
|
+
}
|
|
177
|
+
if (input.reason) {
|
|
178
|
+
span.setAttribute(TERMINATE_REASON_ATTR_KEY, input.reason);
|
|
179
|
+
}
|
|
180
|
+
return await next(input);
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async cancel(
|
|
186
|
+
input: WorkflowCancelInput,
|
|
187
|
+
next: Next<WorkflowClientInterceptor, 'cancel'>
|
|
188
|
+
): Promise<RequestCancelWorkflowExecutionResponse> {
|
|
189
|
+
return await instrument({
|
|
190
|
+
tracer: this.tracer,
|
|
191
|
+
spanName: SpanName.WORKFLOW_CANCEL,
|
|
192
|
+
fn: async (span) => {
|
|
193
|
+
span.setAttribute(WORKFLOW_ID_ATTR_KEY, input.workflowExecution.workflowId);
|
|
194
|
+
if (input.workflowExecution.runId) {
|
|
195
|
+
span.setAttribute(RUN_ID_ATTR_KEY, input.workflowExecution.runId);
|
|
196
|
+
}
|
|
197
|
+
return await next(input);
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async describe(
|
|
203
|
+
input: WorkflowDescribeInput,
|
|
204
|
+
next: Next<WorkflowClientInterceptor, 'describe'>
|
|
205
|
+
): Promise<DescribeWorkflowExecutionResponse> {
|
|
206
|
+
return await instrument({
|
|
207
|
+
tracer: this.tracer,
|
|
208
|
+
spanName: SpanName.WORKFLOW_DESCRIBE,
|
|
209
|
+
fn: async (span) => {
|
|
210
|
+
span.setAttribute(WORKFLOW_ID_ATTR_KEY, input.workflowExecution.workflowId);
|
|
211
|
+
if (input.workflowExecution.runId) {
|
|
212
|
+
span.setAttribute(RUN_ID_ATTR_KEY, input.workflowExecution.runId);
|
|
213
|
+
}
|
|
214
|
+
return await next(input);
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
}
|
|
45
218
|
}
|
package/src/instrumentation.ts
CHANGED
|
@@ -3,19 +3,38 @@
|
|
|
3
3
|
* @module
|
|
4
4
|
*/
|
|
5
5
|
import * as otel from '@opentelemetry/api';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
type Headers,
|
|
8
|
+
ApplicationFailure,
|
|
9
|
+
ApplicationFailureCategory,
|
|
10
|
+
defaultPayloadConverter,
|
|
11
|
+
} from '@temporalio/common';
|
|
7
12
|
|
|
8
13
|
/** Default trace header for opentelemetry interceptors */
|
|
9
14
|
export const TRACE_HEADER = '_tracer-data';
|
|
10
15
|
/** As in workflow run id */
|
|
11
16
|
export const RUN_ID_ATTR_KEY = 'run_id';
|
|
17
|
+
/** As in workflow id */
|
|
18
|
+
export const WORKFLOW_ID_ATTR_KEY = 'temporalWorkflowId';
|
|
19
|
+
/** As in activity id */
|
|
20
|
+
export const ACTIVITY_ID_ATTR_KEY = 'temporalActivityId';
|
|
21
|
+
/** As in update id */
|
|
22
|
+
export const UPDATE_ID_ATTR_KEY = 'temporalUpdateId';
|
|
23
|
+
/** As in termination reason */
|
|
24
|
+
export const TERMINATE_REASON_ATTR_KEY = 'temporalTerminateReason';
|
|
25
|
+
/** As in Nexus service */
|
|
26
|
+
export const NEXUS_SERVICE_ATTR_KEY = 'temporalNexusService';
|
|
27
|
+
/** As in Nexus operation */
|
|
28
|
+
export const NEXUS_OPERATION_ATTR_KEY = 'temporalNexusOperation';
|
|
29
|
+
/** As in Nexus endpoint */
|
|
30
|
+
export const NEXUS_ENDPOINT_ATTR_KEY = 'temporalNexusEndpoint';
|
|
12
31
|
|
|
13
32
|
const payloadConverter = defaultPayloadConverter;
|
|
14
33
|
|
|
15
34
|
/**
|
|
16
35
|
* If found, return an otel Context deserialized from the provided headers
|
|
17
36
|
*/
|
|
18
|
-
export
|
|
37
|
+
export function extractContextFromHeaders(headers: Headers): otel.Context | undefined {
|
|
19
38
|
const encodedSpanContext = headers[TRACE_HEADER];
|
|
20
39
|
if (encodedSpanContext === undefined) {
|
|
21
40
|
return undefined;
|
|
@@ -27,7 +46,7 @@ export async function extractContextFromHeaders(headers: Headers): Promise<otel.
|
|
|
27
46
|
/**
|
|
28
47
|
* Given headers, return new headers with the current otel context inserted
|
|
29
48
|
*/
|
|
30
|
-
export
|
|
49
|
+
export function headersWithContext(headers: Headers): Headers {
|
|
31
50
|
const carrier = {};
|
|
32
51
|
otel.propagation.inject(otel.context.active(), carrier, otel.defaultTextMapSetter);
|
|
33
52
|
return { ...headers, [TRACE_HEADER]: payloadConverter.toPayload(carrier) };
|
|
@@ -43,20 +62,41 @@ async function wrapWithSpan<T>(
|
|
|
43
62
|
span.setStatus({ code: otel.SpanStatusCode.OK });
|
|
44
63
|
return ret;
|
|
45
64
|
} catch (err: any) {
|
|
46
|
-
|
|
47
|
-
if (acceptableErrors === undefined || !acceptableErrors(err)) {
|
|
48
|
-
const statusCode = isBenignErr ? otel.SpanStatusCode.UNSET : otel.SpanStatusCode.ERROR;
|
|
49
|
-
span.setStatus({ code: statusCode, message: (err as Error).message ?? String(err) });
|
|
50
|
-
span.recordException(err);
|
|
51
|
-
} else {
|
|
52
|
-
span.setStatus({ code: otel.SpanStatusCode.OK });
|
|
53
|
-
}
|
|
65
|
+
maybeAddErrorToSpan(err, span, acceptableErrors);
|
|
54
66
|
throw err;
|
|
55
67
|
} finally {
|
|
56
68
|
span.end();
|
|
57
69
|
}
|
|
58
70
|
}
|
|
59
71
|
|
|
72
|
+
function wrapWithSpanSync<T>(
|
|
73
|
+
span: otel.Span,
|
|
74
|
+
fn: (span: otel.Span) => T,
|
|
75
|
+
acceptableErrors?: (err: unknown) => boolean
|
|
76
|
+
): T {
|
|
77
|
+
try {
|
|
78
|
+
const ret = fn(span);
|
|
79
|
+
span.setStatus({ code: otel.SpanStatusCode.OK });
|
|
80
|
+
return ret;
|
|
81
|
+
} catch (err: any) {
|
|
82
|
+
maybeAddErrorToSpan(err, span, acceptableErrors);
|
|
83
|
+
throw err;
|
|
84
|
+
} finally {
|
|
85
|
+
span.end();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function maybeAddErrorToSpan(err: any, span: otel.Span, acceptableErrors?: (err: unknown) => boolean): void {
|
|
90
|
+
const isBenignErr = err instanceof ApplicationFailure && err.category === ApplicationFailureCategory.BENIGN;
|
|
91
|
+
if (acceptableErrors === undefined || !acceptableErrors(err)) {
|
|
92
|
+
const statusCode = isBenignErr ? otel.SpanStatusCode.UNSET : otel.SpanStatusCode.ERROR;
|
|
93
|
+
span.setStatus({ code: statusCode, message: (err as Error).message ?? String(err) });
|
|
94
|
+
span.recordException(err);
|
|
95
|
+
} else {
|
|
96
|
+
span.setStatus({ code: otel.SpanStatusCode.OK });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
60
100
|
export interface InstrumentOptions<T> {
|
|
61
101
|
tracer: otel.Tracer;
|
|
62
102
|
spanName: string;
|
|
@@ -65,6 +105,8 @@ export interface InstrumentOptions<T> {
|
|
|
65
105
|
acceptableErrors?: (err: unknown) => boolean;
|
|
66
106
|
}
|
|
67
107
|
|
|
108
|
+
export type InstrumentOptionsSync<T> = Omit<InstrumentOptions<T>, 'fn'> & { fn: (span: otel.Span) => T };
|
|
109
|
+
|
|
68
110
|
/**
|
|
69
111
|
* Wraps `fn` in a span which ends when function returns or throws
|
|
70
112
|
*/
|
|
@@ -82,3 +124,12 @@ export async function instrument<T>({
|
|
|
82
124
|
}
|
|
83
125
|
return await tracer.startActiveSpan(spanName, async (span) => await wrapWithSpan(span, fn, acceptableErrors));
|
|
84
126
|
}
|
|
127
|
+
|
|
128
|
+
export function instrumentSync<T>({ tracer, spanName, fn, context, acceptableErrors }: InstrumentOptionsSync<T>): T {
|
|
129
|
+
if (context) {
|
|
130
|
+
return otel.context.with(context, () => {
|
|
131
|
+
return tracer.startActiveSpan(spanName, (span) => wrapWithSpanSync(span, fn, acceptableErrors));
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return tracer.startActiveSpan(spanName, (span) => wrapWithSpanSync(span, fn, acceptableErrors));
|
|
135
|
+
}
|
package/src/worker/index.ts
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import * as otel from '@opentelemetry/api';
|
|
2
|
-
import { Resource } from '@opentelemetry/resources';
|
|
3
|
-
import { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base';
|
|
4
|
-
import { Context as ActivityContext } from '@temporalio/activity';
|
|
5
|
-
import {
|
|
6
|
-
|
|
2
|
+
import type { Resource } from '@opentelemetry/resources';
|
|
3
|
+
import type { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base';
|
|
4
|
+
import type { Context as ActivityContext } from '@temporalio/activity';
|
|
5
|
+
import type {
|
|
6
|
+
Next,
|
|
7
7
|
ActivityInboundCallsInterceptor,
|
|
8
8
|
ActivityOutboundCallsInterceptor,
|
|
9
|
-
GetLogAttributesInput,
|
|
10
9
|
InjectedSink,
|
|
11
|
-
|
|
10
|
+
GetLogAttributesInput,
|
|
11
|
+
GetMetricTagsInput,
|
|
12
|
+
ActivityExecuteInput,
|
|
12
13
|
} from '@temporalio/worker';
|
|
13
|
-
import {
|
|
14
|
-
|
|
14
|
+
import {
|
|
15
|
+
instrument,
|
|
16
|
+
extractContextFromHeaders,
|
|
17
|
+
WORKFLOW_ID_ATTR_KEY,
|
|
18
|
+
RUN_ID_ATTR_KEY,
|
|
19
|
+
ACTIVITY_ID_ATTR_KEY,
|
|
20
|
+
} from '../instrumentation';
|
|
21
|
+
import { type OpenTelemetryWorkflowExporter, type SerializableSpan, SpanName, SPAN_DELIMITER } from '../workflow';
|
|
15
22
|
|
|
16
23
|
export interface InterceptorOptions {
|
|
17
24
|
readonly tracer?: otel.Tracer;
|
|
@@ -34,16 +41,26 @@ export class OpenTelemetryActivityInboundInterceptor implements ActivityInboundC
|
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
async execute(input: ActivityExecuteInput, next: Next<ActivityInboundCallsInterceptor, 'execute'>): Promise<unknown> {
|
|
37
|
-
const context =
|
|
44
|
+
const context = extractContextFromHeaders(input.headers);
|
|
38
45
|
const spanName = `${SpanName.ACTIVITY_EXECUTE}${SPAN_DELIMITER}${this.ctx.info.activityType}`;
|
|
39
|
-
return await instrument({
|
|
46
|
+
return await instrument({
|
|
47
|
+
tracer: this.tracer,
|
|
48
|
+
spanName,
|
|
49
|
+
fn: (span) => {
|
|
50
|
+
span.setAttribute(WORKFLOW_ID_ATTR_KEY, this.ctx.info.workflowExecution.workflowId);
|
|
51
|
+
span.setAttribute(RUN_ID_ATTR_KEY, this.ctx.info.workflowExecution.runId);
|
|
52
|
+
span.setAttribute(ACTIVITY_ID_ATTR_KEY, this.ctx.info.activityId);
|
|
53
|
+
return next(input);
|
|
54
|
+
},
|
|
55
|
+
context,
|
|
56
|
+
});
|
|
40
57
|
}
|
|
41
58
|
}
|
|
42
59
|
|
|
43
60
|
/**
|
|
44
|
-
* Intercepts calls to emit logs from an Activity.
|
|
61
|
+
* Intercepts calls to emit logs and metrics from an Activity.
|
|
45
62
|
*
|
|
46
|
-
* Attach OpenTelemetry context tracing attributes to emitted log messages, if appropriate.
|
|
63
|
+
* Attach OpenTelemetry context tracing attributes to emitted log messages and metrics, if appropriate.
|
|
47
64
|
*/
|
|
48
65
|
export class OpenTelemetryActivityOutboundInterceptor implements ActivityOutboundCallsInterceptor {
|
|
49
66
|
constructor(protected readonly ctx: ActivityContext) {}
|
|
@@ -65,6 +82,24 @@ export class OpenTelemetryActivityOutboundInterceptor implements ActivityOutboun
|
|
|
65
82
|
return next(input);
|
|
66
83
|
}
|
|
67
84
|
}
|
|
85
|
+
|
|
86
|
+
public getMetricTags(
|
|
87
|
+
input: GetMetricTagsInput,
|
|
88
|
+
next: Next<ActivityOutboundCallsInterceptor, 'getMetricTags'>
|
|
89
|
+
): GetMetricTagsInput {
|
|
90
|
+
const span = otel.trace.getSpan(otel.context.active());
|
|
91
|
+
const spanContext = span?.spanContext();
|
|
92
|
+
if (spanContext && otel.isSpanContextValid(spanContext)) {
|
|
93
|
+
return next({
|
|
94
|
+
trace_id: spanContext.traceId,
|
|
95
|
+
span_id: spanContext.spanId,
|
|
96
|
+
trace_flags: `0${spanContext.traceFlags.toString(16)}`,
|
|
97
|
+
...input,
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
return next(input);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
68
103
|
}
|
|
69
104
|
|
|
70
105
|
/**
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import * as otel from '@opentelemetry/api';
|
|
2
|
-
import {
|
|
2
|
+
import { ensureWorkflowModuleLoaded, getWorkflowModuleIfAvailable } from './workflow-module-loader';
|
|
3
|
+
|
|
4
|
+
const AsyncLocalStorage = getWorkflowModuleIfAvailable()?.AsyncLocalStorage;
|
|
3
5
|
|
|
4
6
|
export class ContextManager implements otel.ContextManager {
|
|
5
|
-
|
|
7
|
+
// If `@temporalio/workflow` is not available, ignore for now.
|
|
8
|
+
// When ContextManager is constructed module resolution error will be thrown.
|
|
9
|
+
protected storage = AsyncLocalStorage ? new AsyncLocalStorage<otel.Context>() : undefined;
|
|
10
|
+
|
|
11
|
+
public constructor() {
|
|
12
|
+
ensureWorkflowModuleLoaded();
|
|
13
|
+
}
|
|
6
14
|
|
|
7
15
|
active(): otel.Context {
|
|
8
|
-
return this.storage
|
|
16
|
+
return this.storage!.getStore() || otel.ROOT_CONTEXT;
|
|
9
17
|
}
|
|
10
18
|
|
|
11
19
|
bind<T>(context: otel.Context, target: T): T {
|
|
@@ -36,7 +44,7 @@ export class ContextManager implements otel.ContextManager {
|
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
disable(): this {
|
|
39
|
-
this.storage
|
|
47
|
+
this.storage!.disable();
|
|
40
48
|
return this;
|
|
41
49
|
}
|
|
42
50
|
|
|
@@ -47,6 +55,6 @@ export class ContextManager implements otel.ContextManager {
|
|
|
47
55
|
...args: A
|
|
48
56
|
): ReturnType<F> {
|
|
49
57
|
const cb = thisArg == null ? fn : fn.bind(thisArg);
|
|
50
|
-
return this.storage
|
|
58
|
+
return this.storage!.run(context, cb, ...args);
|
|
51
59
|
}
|
|
52
60
|
}
|
|
@@ -54,6 +54,56 @@ export enum SpanName {
|
|
|
54
54
|
*/
|
|
55
55
|
WORKFLOW_SIGNAL_WITH_START = 'SignalWithStartWorkflow',
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Workflow is queried
|
|
59
|
+
*/
|
|
60
|
+
WORKFLOW_QUERY = 'QueryWorkflow',
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Workflow update is started by client
|
|
64
|
+
*/
|
|
65
|
+
WORKFLOW_START_UPDATE = 'StartWorkflowUpdate',
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Workflow is started with an update
|
|
69
|
+
*/
|
|
70
|
+
WORKFLOW_UPDATE_WITH_START = 'UpdateWithStartWorkflow',
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Workflow handles an incoming signal
|
|
74
|
+
*/
|
|
75
|
+
WORKFLOW_HANDLE_SIGNAL = 'HandleSignal',
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Workflow handles an incoming query
|
|
79
|
+
*/
|
|
80
|
+
WORKFLOW_HANDLE_QUERY = 'HandleQuery',
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Workflow handles an incoming update
|
|
84
|
+
*/
|
|
85
|
+
WORKFLOW_HANDLE_UPDATE = 'HandleUpdate',
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Workflow validates an incoming update
|
|
89
|
+
*/
|
|
90
|
+
WORKFLOW_VALIDATE_UPDATE = 'ValidateUpdate',
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Workflow is terminated
|
|
94
|
+
*/
|
|
95
|
+
WORKFLOW_TERMINATE = 'TerminateWorkflow',
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Workflow is cancelled
|
|
99
|
+
*/
|
|
100
|
+
WORKFLOW_CANCEL = 'CancelWorkflow',
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Workflow is described
|
|
104
|
+
*/
|
|
105
|
+
WORKFLOW_DESCRIBE = 'DescribeWorkflow',
|
|
106
|
+
|
|
57
107
|
/**
|
|
58
108
|
* Workflow run is executing
|
|
59
109
|
*/
|
|
@@ -74,6 +124,10 @@ export enum SpanName {
|
|
|
74
124
|
* Workflow is continuing as new
|
|
75
125
|
*/
|
|
76
126
|
CONTINUE_AS_NEW = 'ContinueAsNew',
|
|
127
|
+
/**
|
|
128
|
+
* Nexus operation is started
|
|
129
|
+
*/
|
|
130
|
+
NEXUS_OPERATION_START = 'StartNexusOperation',
|
|
77
131
|
}
|
|
78
132
|
|
|
79
133
|
export const SPAN_DELIMITER = ':';
|