@drej/otel 0.1.0 → 0.2.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/dist/index.d.mts +32 -0
- package/dist/index.mjs +83 -0
- package/package.json +25 -8
- package/src/index.ts +0 -117
- package/tsconfig.json +0 -13
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Tracer } from "@opentelemetry/api";
|
|
2
|
+
import { SandboxHooks } from "@drej/core";
|
|
3
|
+
|
|
4
|
+
//#region src/index.d.ts
|
|
5
|
+
interface OtelHooksOptions {
|
|
6
|
+
/** Include exit code as a span attribute on exec spans. Default: true. */
|
|
7
|
+
recordExitCode?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Returns `SandboxHooks` that emit OpenTelemetry traces for every sandbox run.
|
|
11
|
+
*
|
|
12
|
+
* Pass the result via the `hooks` field of `SandboxOptions` when calling `client.sandbox()`.
|
|
13
|
+
*
|
|
14
|
+
* Span structure:
|
|
15
|
+
* ```
|
|
16
|
+
* sandbox.run ← root span (sandboxId attribute)
|
|
17
|
+
* sandbox.exec ← child per exec (cmd, exitCode, seq)
|
|
18
|
+
* sandbox.checkpoint ← child per checkpoint (snapshotId)
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { otelHooks } from "@drej/otel";
|
|
24
|
+
* import { trace } from "@opentelemetry/api";
|
|
25
|
+
*
|
|
26
|
+
* const tracer = trace.getTracer("my-app");
|
|
27
|
+
* const sb = await client.sandbox({ image: "node:22", resources: { cpu: "500m", memory: "256Mi" }, hooks: otelHooks(tracer) });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare function otelHooks(tracer: Tracer, opts?: OtelHooksOptions): SandboxHooks;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { OtelHooksOptions, otelHooks };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { SpanStatusCode, context, trace } from "@opentelemetry/api";
|
|
2
|
+
//#region src/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* Returns `SandboxHooks` that emit OpenTelemetry traces for every sandbox run.
|
|
5
|
+
*
|
|
6
|
+
* Pass the result via the `hooks` field of `SandboxOptions` when calling `client.sandbox()`.
|
|
7
|
+
*
|
|
8
|
+
* Span structure:
|
|
9
|
+
* ```
|
|
10
|
+
* sandbox.run ← root span (sandboxId attribute)
|
|
11
|
+
* sandbox.exec ← child per exec (cmd, exitCode, seq)
|
|
12
|
+
* sandbox.checkpoint ← child per checkpoint (snapshotId)
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { otelHooks } from "@drej/otel";
|
|
18
|
+
* import { trace } from "@opentelemetry/api";
|
|
19
|
+
*
|
|
20
|
+
* const tracer = trace.getTracer("my-app");
|
|
21
|
+
* const sb = await client.sandbox({ image: "node:22", resources: { cpu: "500m", memory: "256Mi" }, hooks: otelHooks(tracer) });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
function otelHooks(tracer, opts = {}) {
|
|
25
|
+
const { recordExitCode = true } = opts;
|
|
26
|
+
let rootSpan;
|
|
27
|
+
let rootCtx;
|
|
28
|
+
const execSpans = /* @__PURE__ */ new Map();
|
|
29
|
+
return {
|
|
30
|
+
onSandboxCreated(sandboxId, name) {
|
|
31
|
+
rootCtx = context.active();
|
|
32
|
+
rootSpan = tracer.startSpan("sandbox.run", { attributes: {
|
|
33
|
+
"drej.sandbox.id": sandboxId,
|
|
34
|
+
"drej.sandbox.name": name
|
|
35
|
+
} }, rootCtx);
|
|
36
|
+
},
|
|
37
|
+
onExecStart(sandboxId, seq, cmd) {
|
|
38
|
+
if (!rootSpan || !rootCtx) return;
|
|
39
|
+
const spanCtx = trace.setSpan(rootCtx, rootSpan);
|
|
40
|
+
const span = tracer.startSpan("sandbox.exec", { attributes: {
|
|
41
|
+
"drej.sandbox.id": sandboxId,
|
|
42
|
+
"drej.exec.seq": seq,
|
|
43
|
+
"drej.exec.cmd": cmd
|
|
44
|
+
} }, spanCtx);
|
|
45
|
+
execSpans.set(seq, span);
|
|
46
|
+
},
|
|
47
|
+
onExecComplete(_sandboxId, seq, result) {
|
|
48
|
+
const span = execSpans.get(seq);
|
|
49
|
+
if (!span) return;
|
|
50
|
+
if (recordExitCode) span.setAttribute("process.exit_code", result.exitCode);
|
|
51
|
+
span.setStatus({ code: result.exitCode === 0 ? SpanStatusCode.OK : SpanStatusCode.ERROR });
|
|
52
|
+
span.end();
|
|
53
|
+
execSpans.delete(seq);
|
|
54
|
+
},
|
|
55
|
+
onCheckpoint(sandboxId, snapshotId, name) {
|
|
56
|
+
if (!rootSpan || !rootCtx) return;
|
|
57
|
+
const spanCtx = trace.setSpan(rootCtx, rootSpan);
|
|
58
|
+
const span = tracer.startSpan("sandbox.checkpoint", { attributes: {
|
|
59
|
+
"drej.sandbox.id": sandboxId,
|
|
60
|
+
"drej.snapshot.id": snapshotId,
|
|
61
|
+
...name ? { "drej.checkpoint.name": name } : {}
|
|
62
|
+
} }, spanCtx);
|
|
63
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
64
|
+
span.end();
|
|
65
|
+
},
|
|
66
|
+
onSandboxClosed(_sandboxId) {
|
|
67
|
+
rootSpan?.setStatus({ code: SpanStatusCode.OK });
|
|
68
|
+
rootSpan?.end();
|
|
69
|
+
rootSpan = void 0;
|
|
70
|
+
},
|
|
71
|
+
onSandboxFailed(_sandboxId, error) {
|
|
72
|
+
rootSpan?.recordException(error);
|
|
73
|
+
rootSpan?.setStatus({
|
|
74
|
+
code: SpanStatusCode.ERROR,
|
|
75
|
+
message: error.message
|
|
76
|
+
});
|
|
77
|
+
rootSpan?.end();
|
|
78
|
+
rootSpan = void 0;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { otelHooks };
|
package/package.json
CHANGED
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drej/otel",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"files": [
|
|
5
|
+
"dist"
|
|
6
|
+
],
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.mjs",
|
|
9
|
+
"types": "./dist/index.d.mts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"types": "./dist/index.d.mts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsdown"
|
|
8
21
|
},
|
|
9
22
|
"dependencies": {
|
|
10
23
|
"@drej/core": "workspace:*"
|
|
11
24
|
},
|
|
12
25
|
"devDependencies": {
|
|
13
|
-
"@opentelemetry/api": "
|
|
14
|
-
"bun-types": "
|
|
15
|
-
"
|
|
26
|
+
"@opentelemetry/api": "1.9.1",
|
|
27
|
+
"bun-types": "1.3.14",
|
|
28
|
+
"tsdown": "0.22.3",
|
|
29
|
+
"typescript": "6.0.3"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@opentelemetry/api": "^1.0.0"
|
|
16
33
|
}
|
|
17
34
|
}
|
package/src/index.ts
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import type { Tracer, Span, SpanStatusCode } from "@opentelemetry/api";
|
|
2
|
-
import { SpanStatusCode as StatusCode, context, trace } from "@opentelemetry/api";
|
|
3
|
-
import type {
|
|
4
|
-
WorkflowHooks,
|
|
5
|
-
StepHookInfo,
|
|
6
|
-
StepCompleteHookInfo,
|
|
7
|
-
StepFailedHookInfo,
|
|
8
|
-
WorkflowCompleteHookInfo,
|
|
9
|
-
WorkflowFailedHookInfo,
|
|
10
|
-
WorkflowHookInfo,
|
|
11
|
-
} from "@drej/core";
|
|
12
|
-
|
|
13
|
-
export interface OtelHooksOptions {
|
|
14
|
-
/** Include sandbox ID as a span attribute when present in step output. Default: true. */
|
|
15
|
-
recordSandboxId?: boolean;
|
|
16
|
-
/** Include exit code as a span attribute on exec_command steps. Default: true. */
|
|
17
|
-
recordExitCode?: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Returns `WorkflowHooks` that emit OpenTelemetry traces for every workflow run.
|
|
22
|
-
*
|
|
23
|
-
* Pass the result to `client.run(wf, { hooks: otelHooks(tracer) })`.
|
|
24
|
-
*
|
|
25
|
-
* Span structure:
|
|
26
|
-
* ```
|
|
27
|
-
* workflow.run ← root span, name = workflow name
|
|
28
|
-
* workflow.step ← child per step, name = step type
|
|
29
|
-
* workflow.step
|
|
30
|
-
* ```
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* ```ts
|
|
34
|
-
* import { otelHooks } from "@drej/otel";
|
|
35
|
-
* import { trace } from "@opentelemetry/api";
|
|
36
|
-
*
|
|
37
|
-
* const tracer = trace.getTracer("my-app");
|
|
38
|
-
* const run = await client.run(wf, { hooks: otelHooks(tracer) });
|
|
39
|
-
* ```
|
|
40
|
-
*/
|
|
41
|
-
export function otelHooks(tracer: Tracer, opts: OtelHooksOptions = {}): WorkflowHooks {
|
|
42
|
-
const { recordSandboxId = true, recordExitCode = true } = opts;
|
|
43
|
-
|
|
44
|
-
let rootSpan: Span | undefined;
|
|
45
|
-
let rootCtx: ReturnType<typeof context.active> | undefined;
|
|
46
|
-
const stepSpans = new Map<number, Span>();
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
onWorkflowStart({ workflowName, runId }: WorkflowHookInfo) {
|
|
50
|
-
rootCtx = context.active();
|
|
51
|
-
rootSpan = tracer.startSpan(
|
|
52
|
-
"workflow.run",
|
|
53
|
-
{
|
|
54
|
-
attributes: {
|
|
55
|
-
"drej.workflow.name": workflowName,
|
|
56
|
-
"drej.run.id": runId,
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
rootCtx,
|
|
60
|
-
);
|
|
61
|
-
},
|
|
62
|
-
|
|
63
|
-
onStepStart({ stepIndex, stepId }: StepHookInfo) {
|
|
64
|
-
if (!rootSpan || !rootCtx) return;
|
|
65
|
-
const spanCtx = trace.setSpan(rootCtx, rootSpan);
|
|
66
|
-
const span = tracer.startSpan(
|
|
67
|
-
"workflow.step",
|
|
68
|
-
{
|
|
69
|
-
attributes: {
|
|
70
|
-
"drej.step.index": stepIndex,
|
|
71
|
-
"drej.step.type": stepId,
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
spanCtx,
|
|
75
|
-
);
|
|
76
|
-
stepSpans.set(stepIndex, span);
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
onStepComplete({ stepIndex, output }: StepCompleteHookInfo) {
|
|
80
|
-
const span = stepSpans.get(stepIndex);
|
|
81
|
-
if (!span) return;
|
|
82
|
-
if (recordSandboxId) {
|
|
83
|
-
const sandboxId = (output as Record<string, unknown>)?.sandboxId;
|
|
84
|
-
if (typeof sandboxId === "string") span.setAttribute("drej.sandbox.id", sandboxId);
|
|
85
|
-
}
|
|
86
|
-
if (recordExitCode) {
|
|
87
|
-
const exitCode = (output as Record<string, unknown>)?.exitCode;
|
|
88
|
-
if (typeof exitCode === "number") span.setAttribute("process.exit_code", exitCode);
|
|
89
|
-
}
|
|
90
|
-
span.setStatus({ code: StatusCode.OK });
|
|
91
|
-
span.end();
|
|
92
|
-
stepSpans.delete(stepIndex);
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
onStepFailed({ stepIndex, error }: StepFailedHookInfo) {
|
|
96
|
-
const span = stepSpans.get(stepIndex);
|
|
97
|
-
if (!span) return;
|
|
98
|
-
span.recordException(error);
|
|
99
|
-
span.setStatus({ code: StatusCode.ERROR, message: error.message });
|
|
100
|
-
span.end();
|
|
101
|
-
stepSpans.delete(stepIndex);
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
onWorkflowComplete(_info: WorkflowCompleteHookInfo) {
|
|
105
|
-
rootSpan?.setStatus({ code: StatusCode.OK });
|
|
106
|
-
rootSpan?.end();
|
|
107
|
-
rootSpan = undefined;
|
|
108
|
-
},
|
|
109
|
-
|
|
110
|
-
onWorkflowFailed({ error }: WorkflowFailedHookInfo) {
|
|
111
|
-
rootSpan?.recordException(error);
|
|
112
|
-
rootSpan?.setStatus({ code: StatusCode.ERROR, message: error.message });
|
|
113
|
-
rootSpan?.end();
|
|
114
|
-
rootSpan = undefined;
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"strict": true,
|
|
7
|
-
"noEmit": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"allowImportingTsExtensions": true,
|
|
10
|
-
"types": ["bun-types"]
|
|
11
|
-
},
|
|
12
|
-
"include": ["src"]
|
|
13
|
-
}
|