@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 +21 -0
- package/README.md +3 -0
- package/dist/_virtual/rolldown_runtime.cjs +25 -0
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/opentelemetry.cjs +155 -0
- package/dist/opentelemetry.d.cts +60 -0
- package/dist/opentelemetry.d.cts.map +1 -0
- package/dist/opentelemetry.d.ts +60 -0
- package/dist/opentelemetry.d.ts.map +1 -0
- package/dist/opentelemetry.js +152 -0
- package/dist/opentelemetry.js.map +1 -0
- package/package.json +60 -0
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,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
package/dist/index.d.cts
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -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
|
+
}
|