@restatedev/restate-sdk-opentelemetry 0.0.0-SNAPSHOT-20260409150343

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-2024 - Restate Software, Inc., Restate GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # @restatedev/restate-sdk-opentelemetry
2
+
3
+ OpenTelemetry helpers for the Restate TypeScript SDK.
@@ -0,0 +1,25 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+
25
+ exports.__toESM = __toESM;
package/dist/index.cjs ADDED
@@ -0,0 +1,3 @@
1
+ const require_opentelemetry = require('./opentelemetry.cjs');
2
+
3
+ exports.openTelemetryHook = require_opentelemetry.openTelemetryHook;
@@ -0,0 +1,2 @@
1
+ import { OpenTelemetryHookContext, OpenTelemetryHookOptions, openTelemetryHook } from "./opentelemetry.cjs";
2
+ export { type OpenTelemetryHookContext, type OpenTelemetryHookOptions, openTelemetryHook };
@@ -0,0 +1,2 @@
1
+ import { OpenTelemetryHookContext, OpenTelemetryHookOptions, openTelemetryHook } from "./opentelemetry.js";
2
+ export { type OpenTelemetryHookContext, type OpenTelemetryHookOptions, openTelemetryHook };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { openTelemetryHook } from "./opentelemetry.js";
2
+
3
+ export { openTelemetryHook };
@@ -0,0 +1,155 @@
1
+ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
2
+ let __opentelemetry_api = require("@opentelemetry/api");
3
+ __opentelemetry_api = require_rolldown_runtime.__toESM(__opentelemetry_api);
4
+ let __opentelemetry_core = require("@opentelemetry/core");
5
+ __opentelemetry_core = require_rolldown_runtime.__toESM(__opentelemetry_core);
6
+ let __restatedev_restate_sdk = require("@restatedev/restate-sdk");
7
+ __restatedev_restate_sdk = require_rolldown_runtime.__toESM(__restatedev_restate_sdk);
8
+
9
+ //#region src/opentelemetry.ts
10
+ const HOOK_CONTEXT_IS_PROCESSING_SYMBOL = Symbol.for("@restatedev/restate-sdk/hooks.isProcessing");
11
+ const attemptHeadersGetter = {
12
+ get(carrier, key) {
13
+ const value = carrier.get(key);
14
+ if (Array.isArray(value)) return value[0];
15
+ return value ?? void 0;
16
+ },
17
+ keys(carrier) {
18
+ return [...carrier.keys()];
19
+ }
20
+ };
21
+ const traceContextPropagator = new __opentelemetry_core.W3CTraceContextPropagator();
22
+ function resolveTracer(tracer, ctx) {
23
+ return typeof tracer === "function" ? tracer(ctx) : tracer;
24
+ }
25
+ function resolveAttributes(source, ...args) {
26
+ if (typeof source === "function") return source(...args);
27
+ return source;
28
+ }
29
+ function getExceptionMessage(error) {
30
+ if (error instanceof Error) return error.message;
31
+ if (typeof error === "string") return error;
32
+ return "Unknown error";
33
+ }
34
+ function getExceptionValue(error) {
35
+ return error instanceof Error ? error : getExceptionMessage(error);
36
+ }
37
+ function getIsProcessing(ctx) {
38
+ const isProcessing = ctx[HOOK_CONTEXT_IS_PROCESSING_SYMBOL];
39
+ return typeof isProcessing === "function" ? isProcessing : () => true;
40
+ }
41
+ function wrapSpanSuppressingReplayEvents(span, isProcessing) {
42
+ const wrapped = {
43
+ spanContext: () => span.spanContext(),
44
+ setAttribute: (key, value) => {
45
+ span.setAttribute(key, value);
46
+ return wrapped;
47
+ },
48
+ setAttributes: (attributes) => {
49
+ span.setAttributes(attributes);
50
+ return wrapped;
51
+ },
52
+ addEvent: (name, attributesOrStartTime, startTime) => {
53
+ if (isProcessing()) span.addEvent(name, attributesOrStartTime, startTime);
54
+ return wrapped;
55
+ },
56
+ addLink: (link) => {
57
+ span.addLink(link);
58
+ return wrapped;
59
+ },
60
+ addLinks: (links) => {
61
+ span.addLinks(links);
62
+ return wrapped;
63
+ },
64
+ setStatus: (status) => {
65
+ span.setStatus(status);
66
+ return wrapped;
67
+ },
68
+ updateName: (name) => {
69
+ span.updateName(name);
70
+ return wrapped;
71
+ },
72
+ end: (endTime) => {
73
+ span.end(endTime);
74
+ },
75
+ isRecording: () => span.isRecording(),
76
+ recordException: (exception, time) => {
77
+ if (isProcessing()) span.recordException(exception, time);
78
+ }
79
+ };
80
+ return wrapped;
81
+ }
82
+ /**
83
+ * Creates a HooksProvider that integrates Restate invocations with
84
+ * OpenTelemetry tracing using the SDK hook system.
85
+ *
86
+ * The helper always creates one span per invocation attempt, with the standard
87
+ * Restate attributes `restate.invocation.id` and
88
+ * `restate.invocation.target`. Parent context extraction is always based on
89
+ * W3C trace context headers from the Restate attempt headers.
90
+ *
91
+ * When `runSpans` is enabled, it also creates child spans for `ctx.run()`
92
+ * closures that actually execute, adding the standard `restate.run.name`
93
+ * attribute.
94
+ */
95
+ function openTelemetryHook(options) {
96
+ return (ctx) => {
97
+ const runSpans = options.runSpans ?? true;
98
+ const suppressSpanEventsDuringReplay = options.suppressSpanEventsDuringReplay ?? true;
99
+ const tracer = resolveTracer(options.tracer, ctx);
100
+ const isProcessing = getIsProcessing(ctx);
101
+ const parentContext = traceContextPropagator.extract(__opentelemetry_api.context.active(), ctx.request.attemptHeaders, attemptHeadersGetter);
102
+ const target = String(ctx.request.target);
103
+ const attemptSpan = tracer.startSpan(`attempt ${target}`, { attributes: {
104
+ ...resolveAttributes(options.additionalAttemptAttributes, ctx),
105
+ "restate.invocation.id": ctx.request.id,
106
+ "restate.invocation.target": target
107
+ } }, parentContext);
108
+ const attemptContext = __opentelemetry_api.trace.setSpan(parentContext, suppressSpanEventsDuringReplay ? wrapSpanSuppressingReplayEvents(attemptSpan, isProcessing) : attemptSpan);
109
+ const hooks = { interceptor: { handler: async (next) => {
110
+ try {
111
+ await __opentelemetry_api.context.with(attemptContext, next);
112
+ attemptSpan.setStatus({ code: __opentelemetry_api.SpanStatusCode.OK });
113
+ } catch (e) {
114
+ if (!__restatedev_restate_sdk.internal.isSuspendedError(e)) {
115
+ attemptSpan.setStatus({
116
+ code: __opentelemetry_api.SpanStatusCode.ERROR,
117
+ message: getExceptionMessage(e)
118
+ });
119
+ attemptSpan.recordException(getExceptionValue(e));
120
+ }
121
+ throw e;
122
+ } finally {
123
+ attemptSpan.end();
124
+ }
125
+ } } };
126
+ if (runSpans) hooks.interceptor.run = (name, next) => {
127
+ const runSpan = tracer.startSpan(`run (${name})`, { attributes: {
128
+ ...resolveAttributes(options.additionalRunAttributes, ctx, name),
129
+ "restate.run.name": name
130
+ } }, attemptContext);
131
+ const runContext = __opentelemetry_api.trace.setSpan(attemptContext, runSpan);
132
+ return __opentelemetry_api.context.with(runContext, async () => {
133
+ try {
134
+ await next();
135
+ runSpan.setStatus({ code: __opentelemetry_api.SpanStatusCode.OK });
136
+ } catch (e) {
137
+ if (!__restatedev_restate_sdk.internal.isSuspendedError(e)) {
138
+ runSpan.setStatus({
139
+ code: __opentelemetry_api.SpanStatusCode.ERROR,
140
+ message: getExceptionMessage(e)
141
+ });
142
+ runSpan.recordException(getExceptionValue(e));
143
+ }
144
+ throw e;
145
+ } finally {
146
+ runSpan.end();
147
+ }
148
+ });
149
+ };
150
+ return hooks;
151
+ };
152
+ }
153
+
154
+ //#endregion
155
+ exports.openTelemetryHook = openTelemetryHook;
@@ -0,0 +1,60 @@
1
+ import { Attributes, Tracer } from "@opentelemetry/api";
2
+ import { HooksProvider, Request } from "@restatedev/restate-sdk";
3
+
4
+ //#region src/opentelemetry.d.ts
5
+ interface OpenTelemetryHookContext {
6
+ request: Request;
7
+ }
8
+ interface OpenTelemetryHookOptions {
9
+ /**
10
+ * Tracer used to create Restate spans.
11
+ *
12
+ * You can pass a single tracer instance, or resolve one per request
13
+ * (for example to vary the instrumentation scope by service).
14
+ */
15
+ tracer: Tracer | ((ctx: OpenTelemetryHookContext) => Tracer);
16
+ /**
17
+ * When `true`, create child spans for `ctx.run()` closures that actually
18
+ * execute. Replayed journaled runs are skipped by the hook system.
19
+ *
20
+ * @default true
21
+ */
22
+ runSpans?: boolean;
23
+ /**
24
+ * When `true`, suppress span events added to the attempt span while the
25
+ * invocation is replaying journaled work. Attributes are still recorded.
26
+ *
27
+ * This affects only the attempt span; `ctx.run()` spans are only created
28
+ * when the run closure actually executes.
29
+ *
30
+ * @default true
31
+ */
32
+ suppressSpanEventsDuringReplay?: boolean;
33
+ /**
34
+ * Additional attempt span attributes to attach alongside the standard
35
+ * Restate attributes.
36
+ */
37
+ additionalAttemptAttributes?: Attributes | ((ctx: OpenTelemetryHookContext) => Attributes | undefined);
38
+ /**
39
+ * Additional `ctx.run()` span attributes to attach alongside
40
+ * `restate.run.name`.
41
+ */
42
+ additionalRunAttributes?: Attributes | ((ctx: OpenTelemetryHookContext, name: string) => Attributes | undefined);
43
+ }
44
+ /**
45
+ * Creates a HooksProvider that integrates Restate invocations with
46
+ * OpenTelemetry tracing using the SDK hook system.
47
+ *
48
+ * The helper always creates one span per invocation attempt, with the standard
49
+ * Restate attributes `restate.invocation.id` and
50
+ * `restate.invocation.target`. Parent context extraction is always based on
51
+ * W3C trace context headers from the Restate attempt headers.
52
+ *
53
+ * When `runSpans` is enabled, it also creates child spans for `ctx.run()`
54
+ * closures that actually execute, adding the standard `restate.run.name`
55
+ * attribute.
56
+ */
57
+ declare function openTelemetryHook(options: OpenTelemetryHookOptions): HooksProvider;
58
+ //#endregion
59
+ export { OpenTelemetryHookContext, OpenTelemetryHookOptions, openTelemetryHook };
60
+ //# sourceMappingURL=opentelemetry.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opentelemetry.d.cts","names":[],"sources":["../src/opentelemetry.ts"],"sourcesContent":[],"mappings":";;;;UA2BiB,wBAAA;WACN;AADX;AAIiB,UAAA,wBAAA,CAAwB;EAO/B;;;;;;EAkCJ,MAAA,EAlCI,MAkCJ,GAAA,CAAA,CAAA,GAAA,EAlCoB,wBAkCpB,EAAA,GAlCiD,MAkCjD,CAAA;EACO;;;AAkIb;;;;;;;;;;;;;;;;;;gCA3IM,oBACO,6BAA6B;;;;;4BAOpC,oBACO,2CAA2C;;;;;;;;;;;;;;;iBAkIxC,iBAAA,UACL,2BACR"}
@@ -0,0 +1,60 @@
1
+ import { Attributes, Tracer } from "@opentelemetry/api";
2
+ import { HooksProvider, Request } from "@restatedev/restate-sdk";
3
+
4
+ //#region src/opentelemetry.d.ts
5
+ interface OpenTelemetryHookContext {
6
+ request: Request;
7
+ }
8
+ interface OpenTelemetryHookOptions {
9
+ /**
10
+ * Tracer used to create Restate spans.
11
+ *
12
+ * You can pass a single tracer instance, or resolve one per request
13
+ * (for example to vary the instrumentation scope by service).
14
+ */
15
+ tracer: Tracer | ((ctx: OpenTelemetryHookContext) => Tracer);
16
+ /**
17
+ * When `true`, create child spans for `ctx.run()` closures that actually
18
+ * execute. Replayed journaled runs are skipped by the hook system.
19
+ *
20
+ * @default true
21
+ */
22
+ runSpans?: boolean;
23
+ /**
24
+ * When `true`, suppress span events added to the attempt span while the
25
+ * invocation is replaying journaled work. Attributes are still recorded.
26
+ *
27
+ * This affects only the attempt span; `ctx.run()` spans are only created
28
+ * when the run closure actually executes.
29
+ *
30
+ * @default true
31
+ */
32
+ suppressSpanEventsDuringReplay?: boolean;
33
+ /**
34
+ * Additional attempt span attributes to attach alongside the standard
35
+ * Restate attributes.
36
+ */
37
+ additionalAttemptAttributes?: Attributes | ((ctx: OpenTelemetryHookContext) => Attributes | undefined);
38
+ /**
39
+ * Additional `ctx.run()` span attributes to attach alongside
40
+ * `restate.run.name`.
41
+ */
42
+ additionalRunAttributes?: Attributes | ((ctx: OpenTelemetryHookContext, name: string) => Attributes | undefined);
43
+ }
44
+ /**
45
+ * Creates a HooksProvider that integrates Restate invocations with
46
+ * OpenTelemetry tracing using the SDK hook system.
47
+ *
48
+ * The helper always creates one span per invocation attempt, with the standard
49
+ * Restate attributes `restate.invocation.id` and
50
+ * `restate.invocation.target`. Parent context extraction is always based on
51
+ * W3C trace context headers from the Restate attempt headers.
52
+ *
53
+ * When `runSpans` is enabled, it also creates child spans for `ctx.run()`
54
+ * closures that actually execute, adding the standard `restate.run.name`
55
+ * attribute.
56
+ */
57
+ declare function openTelemetryHook(options: OpenTelemetryHookOptions): HooksProvider;
58
+ //#endregion
59
+ export { OpenTelemetryHookContext, OpenTelemetryHookOptions, openTelemetryHook };
60
+ //# sourceMappingURL=opentelemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opentelemetry.d.ts","names":[],"sources":["../src/opentelemetry.ts"],"sourcesContent":[],"mappings":";;;;UA2BiB,wBAAA;WACN;AADX;AAIiB,UAAA,wBAAA,CAAwB;EAO/B;;;;;;EAkCJ,MAAA,EAlCI,MAkCJ,GAAA,CAAA,CAAA,GAAA,EAlCoB,wBAkCpB,EAAA,GAlCiD,MAkCjD,CAAA;EACO;;;AAkIb;;;;;;;;;;;;;;;;;;gCA3IM,oBACO,6BAA6B;;;;;4BAOpC,oBACO,2CAA2C;;;;;;;;;;;;;;;iBAkIxC,iBAAA,UACL,2BACR"}
@@ -0,0 +1,152 @@
1
+ import { SpanStatusCode, context, trace } from "@opentelemetry/api";
2
+ import { W3CTraceContextPropagator } from "@opentelemetry/core";
3
+ import { internal } from "@restatedev/restate-sdk";
4
+
5
+ //#region src/opentelemetry.ts
6
+ const HOOK_CONTEXT_IS_PROCESSING_SYMBOL = Symbol.for("@restatedev/restate-sdk/hooks.isProcessing");
7
+ const attemptHeadersGetter = {
8
+ get(carrier, key) {
9
+ const value = carrier.get(key);
10
+ if (Array.isArray(value)) return value[0];
11
+ return value ?? void 0;
12
+ },
13
+ keys(carrier) {
14
+ return [...carrier.keys()];
15
+ }
16
+ };
17
+ const traceContextPropagator = new W3CTraceContextPropagator();
18
+ function resolveTracer(tracer, ctx) {
19
+ return typeof tracer === "function" ? tracer(ctx) : tracer;
20
+ }
21
+ function resolveAttributes(source, ...args) {
22
+ if (typeof source === "function") return source(...args);
23
+ return source;
24
+ }
25
+ function getExceptionMessage(error) {
26
+ if (error instanceof Error) return error.message;
27
+ if (typeof error === "string") return error;
28
+ return "Unknown error";
29
+ }
30
+ function getExceptionValue(error) {
31
+ return error instanceof Error ? error : getExceptionMessage(error);
32
+ }
33
+ function getIsProcessing(ctx) {
34
+ const isProcessing = ctx[HOOK_CONTEXT_IS_PROCESSING_SYMBOL];
35
+ return typeof isProcessing === "function" ? isProcessing : () => true;
36
+ }
37
+ function wrapSpanSuppressingReplayEvents(span, isProcessing) {
38
+ const wrapped = {
39
+ spanContext: () => span.spanContext(),
40
+ setAttribute: (key, value) => {
41
+ span.setAttribute(key, value);
42
+ return wrapped;
43
+ },
44
+ setAttributes: (attributes) => {
45
+ span.setAttributes(attributes);
46
+ return wrapped;
47
+ },
48
+ addEvent: (name, attributesOrStartTime, startTime) => {
49
+ if (isProcessing()) span.addEvent(name, attributesOrStartTime, startTime);
50
+ return wrapped;
51
+ },
52
+ addLink: (link) => {
53
+ span.addLink(link);
54
+ return wrapped;
55
+ },
56
+ addLinks: (links) => {
57
+ span.addLinks(links);
58
+ return wrapped;
59
+ },
60
+ setStatus: (status) => {
61
+ span.setStatus(status);
62
+ return wrapped;
63
+ },
64
+ updateName: (name) => {
65
+ span.updateName(name);
66
+ return wrapped;
67
+ },
68
+ end: (endTime) => {
69
+ span.end(endTime);
70
+ },
71
+ isRecording: () => span.isRecording(),
72
+ recordException: (exception, time) => {
73
+ if (isProcessing()) span.recordException(exception, time);
74
+ }
75
+ };
76
+ return wrapped;
77
+ }
78
+ /**
79
+ * Creates a HooksProvider that integrates Restate invocations with
80
+ * OpenTelemetry tracing using the SDK hook system.
81
+ *
82
+ * The helper always creates one span per invocation attempt, with the standard
83
+ * Restate attributes `restate.invocation.id` and
84
+ * `restate.invocation.target`. Parent context extraction is always based on
85
+ * W3C trace context headers from the Restate attempt headers.
86
+ *
87
+ * When `runSpans` is enabled, it also creates child spans for `ctx.run()`
88
+ * closures that actually execute, adding the standard `restate.run.name`
89
+ * attribute.
90
+ */
91
+ function openTelemetryHook(options) {
92
+ return (ctx) => {
93
+ const runSpans = options.runSpans ?? true;
94
+ const suppressSpanEventsDuringReplay = options.suppressSpanEventsDuringReplay ?? true;
95
+ const tracer = resolveTracer(options.tracer, ctx);
96
+ const isProcessing = getIsProcessing(ctx);
97
+ const parentContext = traceContextPropagator.extract(context.active(), ctx.request.attemptHeaders, attemptHeadersGetter);
98
+ const target = String(ctx.request.target);
99
+ const attemptSpan = tracer.startSpan(`attempt ${target}`, { attributes: {
100
+ ...resolveAttributes(options.additionalAttemptAttributes, ctx),
101
+ "restate.invocation.id": ctx.request.id,
102
+ "restate.invocation.target": target
103
+ } }, parentContext);
104
+ const attemptContext = trace.setSpan(parentContext, suppressSpanEventsDuringReplay ? wrapSpanSuppressingReplayEvents(attemptSpan, isProcessing) : attemptSpan);
105
+ const hooks = { interceptor: { handler: async (next) => {
106
+ try {
107
+ await context.with(attemptContext, next);
108
+ attemptSpan.setStatus({ code: SpanStatusCode.OK });
109
+ } catch (e) {
110
+ if (!internal.isSuspendedError(e)) {
111
+ attemptSpan.setStatus({
112
+ code: SpanStatusCode.ERROR,
113
+ message: getExceptionMessage(e)
114
+ });
115
+ attemptSpan.recordException(getExceptionValue(e));
116
+ }
117
+ throw e;
118
+ } finally {
119
+ attemptSpan.end();
120
+ }
121
+ } } };
122
+ if (runSpans) hooks.interceptor.run = (name, next) => {
123
+ const runSpan = tracer.startSpan(`run (${name})`, { attributes: {
124
+ ...resolveAttributes(options.additionalRunAttributes, ctx, name),
125
+ "restate.run.name": name
126
+ } }, attemptContext);
127
+ const runContext = trace.setSpan(attemptContext, runSpan);
128
+ return context.with(runContext, async () => {
129
+ try {
130
+ await next();
131
+ runSpan.setStatus({ code: SpanStatusCode.OK });
132
+ } catch (e) {
133
+ if (!internal.isSuspendedError(e)) {
134
+ runSpan.setStatus({
135
+ code: SpanStatusCode.ERROR,
136
+ message: getExceptionMessage(e)
137
+ });
138
+ runSpan.recordException(getExceptionValue(e));
139
+ }
140
+ throw e;
141
+ } finally {
142
+ runSpan.end();
143
+ }
144
+ });
145
+ };
146
+ return hooks;
147
+ };
148
+ }
149
+
150
+ //#endregion
151
+ export { openTelemetryHook };
152
+ //# sourceMappingURL=opentelemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opentelemetry.js","names":["attemptHeadersGetter: TextMapGetter<\n ReadonlyMap<string, string | string[] | undefined>\n>","wrapped: Span","hooks: ReturnType<HooksProvider>"],"sources":["../src/opentelemetry.ts"],"sourcesContent":["/*\n * Copyright (c) 2023-2024 - Restate Software, Inc., Restate GmbH\n *\n * This file is part of the Restate SDK for Node.js/TypeScript,\n * which is released under the MIT license.\n *\n * You can find a copy of the license in file LICENSE in the root\n * directory of this repository or package, or at\n * https://github.com/restatedev/sdk-typescript/blob/main/LICENSE\n */\n\nimport {\n context,\n trace,\n SpanStatusCode,\n type Attributes,\n type Span,\n type TextMapGetter,\n type Tracer,\n} from \"@opentelemetry/api\";\nimport { W3CTraceContextPropagator } from \"@opentelemetry/core\";\nimport {\n internal,\n type HooksProvider,\n type Request,\n} from \"@restatedev/restate-sdk\";\n\nexport interface OpenTelemetryHookContext {\n request: Request;\n}\n\nexport interface OpenTelemetryHookOptions {\n /**\n * Tracer used to create Restate spans.\n *\n * You can pass a single tracer instance, or resolve one per request\n * (for example to vary the instrumentation scope by service).\n */\n tracer: Tracer | ((ctx: OpenTelemetryHookContext) => Tracer);\n\n /**\n * When `true`, create child spans for `ctx.run()` closures that actually\n * execute. Replayed journaled runs are skipped by the hook system.\n *\n * @default true\n */\n runSpans?: boolean;\n\n /**\n * When `true`, suppress span events added to the attempt span while the\n * invocation is replaying journaled work. Attributes are still recorded.\n *\n * This affects only the attempt span; `ctx.run()` spans are only created\n * when the run closure actually executes.\n *\n * @default true\n */\n suppressSpanEventsDuringReplay?: boolean;\n\n /**\n * Additional attempt span attributes to attach alongside the standard\n * Restate attributes.\n */\n additionalAttemptAttributes?:\n | Attributes\n | ((ctx: OpenTelemetryHookContext) => Attributes | undefined);\n\n /**\n * Additional `ctx.run()` span attributes to attach alongside\n * `restate.run.name`.\n */\n additionalRunAttributes?:\n | Attributes\n | ((ctx: OpenTelemetryHookContext, name: string) => Attributes | undefined);\n}\n\n// Hidden symbol key injected by the Restate SDK when instantiating hooks.\n// This is intentionally not part of the public hook context contract.\nconst HOOK_CONTEXT_IS_PROCESSING_SYMBOL = Symbol.for(\n \"@restatedev/restate-sdk/hooks.isProcessing\"\n);\n\nconst attemptHeadersGetter: TextMapGetter<\n ReadonlyMap<string, string | string[] | undefined>\n> = {\n get(carrier, key) {\n const value = carrier.get(key);\n if (Array.isArray(value)) {\n return value[0];\n }\n return value ?? undefined;\n },\n\n keys(carrier) {\n return [...carrier.keys()];\n },\n};\n\nconst traceContextPropagator = new W3CTraceContextPropagator();\n\nfunction resolveTracer(\n tracer: OpenTelemetryHookOptions[\"tracer\"],\n ctx: OpenTelemetryHookContext\n): Tracer {\n return typeof tracer === \"function\" ? tracer(ctx) : tracer;\n}\n\nfunction resolveAttributes<TArgs extends unknown[]>(\n source: Attributes | ((...args: TArgs) => Attributes | undefined) | undefined,\n ...args: TArgs\n): Attributes | undefined {\n if (typeof source === \"function\") {\n return source(...args);\n }\n return source;\n}\n\nfunction getExceptionMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n if (typeof error === \"string\") {\n return error;\n }\n return \"Unknown error\";\n}\n\nfunction getExceptionValue(error: unknown): Error | string {\n return error instanceof Error ? error : getExceptionMessage(error);\n}\n\nfunction getIsProcessing(ctx: OpenTelemetryHookContext): () => boolean {\n const isProcessing = (ctx as unknown as Record<PropertyKey, unknown>)[\n HOOK_CONTEXT_IS_PROCESSING_SYMBOL\n ];\n return typeof isProcessing === \"function\"\n ? (isProcessing as () => boolean)\n : () => true;\n}\n\nfunction wrapSpanSuppressingReplayEvents(\n span: Span,\n isProcessing: () => boolean\n): Span {\n const wrapped: Span = {\n spanContext: () => span.spanContext(),\n setAttribute: (key, value) => {\n span.setAttribute(key, value);\n return wrapped;\n },\n setAttributes: (attributes) => {\n span.setAttributes(attributes);\n return wrapped;\n },\n addEvent: (name, attributesOrStartTime, startTime) => {\n if (isProcessing()) {\n span.addEvent(name, attributesOrStartTime, startTime);\n }\n return wrapped;\n },\n addLink: (link) => {\n span.addLink(link);\n return wrapped;\n },\n addLinks: (links) => {\n span.addLinks(links);\n return wrapped;\n },\n setStatus: (status) => {\n span.setStatus(status);\n return wrapped;\n },\n updateName: (name) => {\n span.updateName(name);\n return wrapped;\n },\n end: (endTime) => {\n span.end(endTime);\n },\n isRecording: () => span.isRecording(),\n recordException: (exception, time) => {\n if (isProcessing()) {\n span.recordException(exception, time);\n }\n },\n };\n\n return wrapped;\n}\n\n/**\n * Creates a HooksProvider that integrates Restate invocations with\n * OpenTelemetry tracing using the SDK hook system.\n *\n * The helper always creates one span per invocation attempt, with the standard\n * Restate attributes `restate.invocation.id` and\n * `restate.invocation.target`. Parent context extraction is always based on\n * W3C trace context headers from the Restate attempt headers.\n *\n * When `runSpans` is enabled, it also creates child spans for `ctx.run()`\n * closures that actually execute, adding the standard `restate.run.name`\n * attribute.\n */\nexport function openTelemetryHook(\n options: OpenTelemetryHookOptions\n): HooksProvider {\n return (ctx) => {\n const runSpans = options.runSpans ?? true;\n const suppressSpanEventsDuringReplay =\n options.suppressSpanEventsDuringReplay ?? true;\n const tracer = resolveTracer(options.tracer, ctx);\n const isProcessing = getIsProcessing(ctx);\n const parentContext = traceContextPropagator.extract(\n context.active(),\n ctx.request.attemptHeaders,\n attemptHeadersGetter\n );\n\n const target = String(ctx.request.target);\n const attemptSpan = tracer.startSpan(\n `attempt ${target}`,\n {\n attributes: {\n ...resolveAttributes(options.additionalAttemptAttributes, ctx),\n \"restate.invocation.id\": ctx.request.id,\n \"restate.invocation.target\": target,\n },\n },\n parentContext\n );\n const attemptContext = trace.setSpan(\n parentContext,\n suppressSpanEventsDuringReplay\n ? wrapSpanSuppressingReplayEvents(attemptSpan, isProcessing)\n : attemptSpan\n );\n\n const hooks: ReturnType<HooksProvider> = {\n interceptor: {\n handler: async (next) => {\n try {\n await context.with(attemptContext, next);\n attemptSpan.setStatus({ code: SpanStatusCode.OK });\n } catch (e) {\n if (!internal.isSuspendedError(e)) {\n attemptSpan.setStatus({\n code: SpanStatusCode.ERROR,\n message: getExceptionMessage(e),\n });\n attemptSpan.recordException(getExceptionValue(e));\n }\n throw e;\n } finally {\n attemptSpan.end();\n }\n },\n },\n };\n\n if (runSpans) {\n hooks.interceptor!.run = (name, next) => {\n const runSpan = tracer.startSpan(\n `run (${name})`,\n {\n attributes: {\n ...resolveAttributes(options.additionalRunAttributes, ctx, name),\n \"restate.run.name\": name,\n },\n },\n attemptContext\n );\n const runContext = trace.setSpan(attemptContext, runSpan);\n\n return context.with(runContext, async () => {\n try {\n await next();\n runSpan.setStatus({ code: SpanStatusCode.OK });\n } catch (e) {\n if (!internal.isSuspendedError(e)) {\n runSpan.setStatus({\n code: SpanStatusCode.ERROR,\n message: getExceptionMessage(e),\n });\n runSpan.recordException(getExceptionValue(e));\n }\n throw e;\n } finally {\n runSpan.end();\n }\n });\n };\n }\n\n return hooks;\n };\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@restatedev/restate-sdk-opentelemetry",
3
+ "version": "0.0.0-SNAPSHOT-20260409150343",
4
+ "description": "OpenTelemetry helpers for the Restate TypeScript SDK",
5
+ "author": "Restate Developers",
6
+ "email": "code@restate.dev",
7
+ "license": "MIT",
8
+ "homepage": "https://github.com/restatedev/sdk-typescript#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/restatedev/sdk-typescript.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/restatedev/sdk-typescript/issues"
15
+ },
16
+ "type": "module",
17
+ "main": "./dist/index.cjs",
18
+ "module": "./dist/index.js",
19
+ "types": "./dist/index.d.cts",
20
+ "exports": {
21
+ ".": {
22
+ "import": "./dist/index.js",
23
+ "require": "./dist/index.cjs"
24
+ },
25
+ "./package.json": "./package.json"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "README.md"
30
+ ],
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "dependencies": {},
35
+ "devDependencies": {
36
+ "@opentelemetry/api": "^1.9.0",
37
+ "@opentelemetry/core": "^2.6.0",
38
+ "@restatedev/restate-sdk": "0.0.0-SNAPSHOT-20260409150343"
39
+ },
40
+ "peerDependencies": {
41
+ "@opentelemetry/api": ">=1.9.0",
42
+ "@opentelemetry/core": ">=2.6.0",
43
+ "@restatedev/restate-sdk": "^1.11.1"
44
+ },
45
+ "scripts": {
46
+ "test": "turbo run _test --filter={.}...",
47
+ "_test": "vitest run",
48
+ "build": "turbo run _build --filter={.}...",
49
+ "_build": "tsc --noEmit && tsdown",
50
+ "dev": "tsc --noEmit --watch",
51
+ "clean": "rm -rf dist *.tsbuildinfo .turbo",
52
+ "check:types": "turbo run _check:types --filter={.}...",
53
+ "_check:types": "tsc --noEmit --project tsconfig.build.json",
54
+ "lint": "eslint .",
55
+ "check:exports": "turbo run _check:exports --filter={.}...",
56
+ "_check:exports": "attw --pack .",
57
+ "check:api": "turbo run _check:api --filter={.}...",
58
+ "_check:api": "api-extractor run --local"
59
+ }
60
+ }