Skip to content

Commit 6ef2bf5

Browse files
feat: add tracing to Sandbox and SandboxClient, also allow passing to browser and node connectors (#150)
* feat: add tracing to Sandbox and SandboxClient, also allow passing to browser and node connectors * all sandbox client methods
1 parent 2f58d43 commit 6ef2bf5

13 files changed

Lines changed: 1465 additions & 501 deletions

File tree

src/Sandbox.ts

Lines changed: 127 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import {
66
import { VMTier } from "./VMTier";
77
import { API } from "./API";
88
import { SandboxClient } from "./SandboxClient";
9-
import { StartSandboxOpts } from "./types";
109
import { retryWithDelay } from "./utils/api";
10+
import { Tracer, SpanStatusCode } from "@opentelemetry/api";
1111

1212
export class Sandbox {
13+
private tracer?: Tracer;
14+
1315
/**
1416
* How the Sandbox booted up:
1517
* - RUNNING: Already running
@@ -41,36 +43,93 @@ export class Sandbox {
4143
constructor(
4244
public id: string,
4345
private api: API,
44-
private pitcherManagerResponse: PitcherManagerResponse
45-
) {}
46+
private pitcherManagerResponse: PitcherManagerResponse,
47+
tracer?: Tracer
48+
) {
49+
this.tracer = tracer;
50+
}
51+
52+
private async withSpan<T>(
53+
operationName: string,
54+
attributes: Record<string, string | number | boolean> = {},
55+
operation: () => Promise<T>
56+
): Promise<T> {
57+
if (!this.tracer) {
58+
return operation();
59+
}
60+
61+
return this.tracer.startActiveSpan(
62+
operationName,
63+
{ attributes },
64+
async (span) => {
65+
try {
66+
const result = await operation();
67+
span.setStatus({ code: SpanStatusCode.OK });
68+
return result;
69+
} catch (error) {
70+
span.setStatus({
71+
code: SpanStatusCode.ERROR,
72+
message: error instanceof Error ? error.message : String(error),
73+
});
74+
span.recordException(
75+
error instanceof Error ? error : new Error(String(error))
76+
);
77+
throw error;
78+
} finally {
79+
span.end();
80+
}
81+
}
82+
);
83+
}
4684

4785
/**
4886
* Updates the specs that this sandbox runs on. It will dynamically scale the sandbox to the
4987
* new specs without a reboot. Be careful when scaling specs down, if the VM is using more memory
5088
* than it can scale down to, it can become very slow.
5189
*/
5290
async updateTier(tier: VMTier): Promise<void> {
53-
await this.api.updateSpecs(this.id, {
54-
tier: tier.name,
55-
});
91+
return this.withSpan(
92+
"sandbox.updateTier",
93+
{
94+
"sandbox.id": this.id,
95+
"tier.name": tier.name
96+
},
97+
async () => {
98+
await this.api.updateSpecs(this.id, {
99+
tier: tier.name,
100+
});
101+
}
102+
);
56103
}
57104

58105
/**
59106
* Updates the hibernation timeout for this sandbox. This is the amount of seconds the sandbox
60107
* will be kept alive without activity before it is automatically hibernated. Activity can be sessions or interactions with any endpoints exposed by the Sandbox.
61108
*/
62109
async updateHibernationTimeout(timeoutSeconds: number): Promise<void> {
63-
await this.api.updateHibernationTimeout(this.id, {
64-
hibernation_timeout_seconds: timeoutSeconds,
65-
});
110+
return this.withSpan(
111+
"sandbox.updateHibernationTimeout",
112+
{
113+
"sandbox.id": this.id,
114+
"hibernation.timeoutSeconds": timeoutSeconds
115+
},
116+
async () => {
117+
await this.api.updateHibernationTimeout(this.id, {
118+
hibernation_timeout_seconds: timeoutSeconds,
119+
});
120+
}
121+
);
66122
}
67123

68124
private async initializeCustomSession(
69125
customSession: SessionCreateOptions,
70126
session: SandboxSession
71127
) {
72-
const client = await SandboxClient.create(session, async () =>
73-
this.getSession(await this.api.startVm(this.id), customSession)
128+
const client = await SandboxClient.create(
129+
session,
130+
async () => this.getSession(await this.api.startVm(this.id), customSession),
131+
undefined,
132+
this.tracer
74133
);
75134

76135
if (customSession.env) {
@@ -156,29 +215,42 @@ export class Sandbox {
156215
}
157216

158217
async connect(customSession?: SessionCreateOptions) {
159-
return await retryWithDelay(
218+
return this.withSpan(
219+
"sandbox.connect",
220+
{
221+
"sandbox.id": this.id,
222+
"session.hasCustomSession": !!customSession,
223+
"session.id": customSession?.id || "default"
224+
},
160225
async () => {
161-
const session = await this.getSession(
162-
this.pitcherManagerResponse,
163-
customSession
164-
);
226+
return await retryWithDelay(
227+
async () => {
228+
const session = await this.getSession(
229+
this.pitcherManagerResponse,
230+
customSession
231+
);
165232

166-
let client: SandboxClient | undefined;
233+
let client: SandboxClient | undefined;
167234

168-
// We might create a client here if git or env is configured, we can reuse that
169-
if (customSession) {
170-
client = await this.initializeCustomSession(customSession, session);
171-
}
235+
// We might create a client here if git or env is configured, we can reuse that
236+
if (customSession) {
237+
client = await this.initializeCustomSession(customSession, session);
238+
}
172239

173-
return (
174-
client ||
175-
SandboxClient.create(session, async () =>
176-
this.getSession(await this.api.startVm(this.id), customSession)
177-
)
240+
return (
241+
client ||
242+
SandboxClient.create(
243+
session,
244+
async () => this.getSession(await this.api.startVm(this.id), customSession),
245+
undefined,
246+
this.tracer
247+
)
248+
);
249+
},
250+
3,
251+
100
178252
);
179-
},
180-
3,
181-
100
253+
}
182254
);
183255
}
184256

@@ -192,20 +264,32 @@ export class Sandbox {
192264
async createSession(
193265
customSession?: SessionCreateOptions
194266
): Promise<SandboxSession> {
195-
if (customSession?.git || customSession?.env) {
196-
const configureSession = await this.getSession(
197-
this.pitcherManagerResponse,
198-
customSession
199-
);
200-
201-
const client = await this.initializeCustomSession(
202-
customSession,
203-
configureSession
204-
);
205-
206-
client?.dispose();
207-
}
267+
return this.withSpan(
268+
"sandbox.createSession",
269+
{
270+
"sandbox.id": this.id,
271+
"session.hasCustomSession": !!customSession,
272+
"session.id": customSession?.id || "default",
273+
"session.hasGit": !!customSession?.git,
274+
"session.hasEnv": !!customSession?.env
275+
},
276+
async () => {
277+
if (customSession?.git || customSession?.env) {
278+
const configureSession = await this.getSession(
279+
this.pitcherManagerResponse,
280+
customSession
281+
);
282+
283+
const client = await this.initializeCustomSession(
284+
customSession,
285+
configureSession
286+
);
208287

209-
return this.getSession(this.pitcherManagerResponse, customSession);
288+
client?.dispose();
289+
}
290+
291+
return this.getSession(this.pitcherManagerResponse, customSession);
292+
}
293+
);
210294
}
211295
}

0 commit comments

Comments
 (0)