@drej/otel 0.1.1 → 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.
@@ -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,12 +1,23 @@
1
1
  {
2
2
  "name": "@drej/otel",
3
- "version": "0.1.1",
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
+ },
4
16
  "publishConfig": {
5
17
  "access": "public"
6
18
  },
7
- "main": "./src/index.ts",
8
- "peerDependencies": {
9
- "@opentelemetry/api": "^1.0.0"
19
+ "scripts": {
20
+ "build": "tsdown"
10
21
  },
11
22
  "dependencies": {
12
23
  "@drej/core": "workspace:*"
@@ -14,6 +25,10 @@
14
25
  "devDependencies": {
15
26
  "@opentelemetry/api": "1.9.1",
16
27
  "bun-types": "1.3.14",
28
+ "tsdown": "0.22.3",
17
29
  "typescript": "6.0.3"
30
+ },
31
+ "peerDependencies": {
32
+ "@opentelemetry/api": "^1.0.0"
18
33
  }
19
34
  }
package/CHANGELOG.md DELETED
@@ -1,12 +0,0 @@
1
- # @drej/otel
2
-
3
- ## 0.1.1
4
-
5
- ### Patch Changes
6
-
7
- - Updated dependencies [22b8a32]
8
- - Updated dependencies [799b6dd]
9
- - Updated dependencies [8d9d8bb]
10
- - Updated dependencies [2fd33e0]
11
- - Updated dependencies [0d94c2a]
12
- - @drej/core@0.3.0
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
- }