11import { Sandbox } from "./Sandbox" ;
22import { API } from "./API" ;
33import { getDefaultTemplateTag , getStartOptions } from "./utils/api" ;
4+ import { Tracer , SpanStatusCode } from "@opentelemetry/api" ;
45
56import {
67 CreateSandboxOpts ,
@@ -16,11 +17,48 @@ import {
1617 * This class provides methods for creating and managing sandboxes.
1718 */
1819export class Sandboxes {
20+ private tracer ?: Tracer ;
21+
1922 get defaultTemplateId ( ) {
2023 return getDefaultTemplateTag ( this . api . getClient ( ) ) ;
2124 }
2225
23- constructor ( private api : API ) { }
26+ constructor ( private api : API , tracer ?: Tracer ) {
27+ this . tracer = tracer ;
28+ }
29+
30+ private async withSpan < T > (
31+ operationName : string ,
32+ attributes : Record < string , string | number | boolean > = { } ,
33+ operation : ( ) => Promise < T >
34+ ) : Promise < T > {
35+ if ( ! this . tracer ) {
36+ return operation ( ) ;
37+ }
38+
39+ return this . tracer . startActiveSpan (
40+ operationName ,
41+ { attributes } ,
42+ async ( span ) => {
43+ try {
44+ const result = await operation ( ) ;
45+ span . setStatus ( { code : SpanStatusCode . OK } ) ;
46+ return result ;
47+ } catch ( error ) {
48+ span . setStatus ( {
49+ code : SpanStatusCode . ERROR ,
50+ message : error instanceof Error ? error . message : String ( error ) ,
51+ } ) ;
52+ span . recordException (
53+ error instanceof Error ? error : new Error ( String ( error ) )
54+ ) ;
55+ throw error ;
56+ } finally {
57+ span . end ( ) ;
58+ }
59+ }
60+ ) ;
61+ }
2462
2563 private async createTemplateSandbox (
2664 opts ?: CreateSandboxOpts & StartSandboxOpts
@@ -62,26 +100,44 @@ export class Sandboxes {
62100 * Note! On CLEAN bootups the setup will run again. When hibernated a new snapshot will be created.
63101 */
64102 async resume ( sandboxId : string ) {
65- const startResponse = await this . api . startVm ( sandboxId ) ;
66- return new Sandbox ( sandboxId , this . api , startResponse ) ;
103+ return this . withSpan (
104+ "sandboxes.resume" ,
105+ { "sandbox.id" : sandboxId } ,
106+ async ( ) => {
107+ const startResponse = await this . api . startVm ( sandboxId ) ;
108+ return new Sandbox ( sandboxId , this . api , startResponse ) ;
109+ }
110+ ) ;
67111 }
68112
69113 /**
70114 * Shuts down a sandbox. Files will be saved, and the sandbox will be stopped.
71115 */
72116 async shutdown ( sandboxId : string ) : Promise < void > {
73- await this . api . shutdown ( sandboxId ) ;
117+ return this . withSpan (
118+ "sandboxes.shutdown" ,
119+ { "sandbox.id" : sandboxId } ,
120+ async ( ) => {
121+ await this . api . shutdown ( sandboxId ) ;
122+ }
123+ ) ;
74124 }
75125
76126 /**
77127 * Forks a sandbox. This will create a new sandbox from the given sandbox.
78128 * @deprecated This will be removed shortly to avoid having multiple ways of doing the same thing
79129 */
80130 public async fork ( sandboxId : string , opts ?: StartSandboxOpts ) {
81- return this . create ( {
82- id : sandboxId ,
83- ...opts ,
84- } ) ;
131+ return this . withSpan (
132+ "sandboxes.fork" ,
133+ { "sandbox.id" : sandboxId } ,
134+ async ( ) => {
135+ return this . create ( {
136+ id : sandboxId ,
137+ ...opts ,
138+ } ) ;
139+ }
140+ ) ;
85141 }
86142
87143 /**
@@ -91,33 +147,54 @@ export class Sandboxes {
91147 * Will resolve once the sandbox is restarted with its setup running.
92148 */
93149 public async restart ( sandboxId : string , opts ?: StartSandboxOpts ) {
94- try {
95- await this . shutdown ( sandboxId ) ;
96- } catch ( e ) {
97- throw new Error ( "Failed to shutdown VM, " + String ( e ) ) ;
98- }
150+ return this . withSpan (
151+ "sandboxes.restart" ,
152+ { "sandbox.id" : sandboxId } ,
153+ async ( ) => {
154+ try {
155+ await this . shutdown ( sandboxId ) ;
156+ } catch ( e ) {
157+ throw new Error ( "Failed to shutdown VM, " + String ( e ) ) ;
158+ }
99159
100- try {
101- const startResponse = await this . api . startVm ( sandboxId , opts ) ;
160+ try {
161+ const startResponse = await this . api . startVm ( sandboxId , opts ) ;
102162
103- return new Sandbox ( sandboxId , this . api , startResponse ) ;
104- } catch ( e ) {
105- throw new Error ( "Failed to start VM, " + String ( e ) ) ;
106- }
163+ return new Sandbox ( sandboxId , this . api , startResponse ) ;
164+ } catch ( e ) {
165+ throw new Error ( "Failed to start VM, " + String ( e ) ) ;
166+ }
167+ }
168+ ) ;
107169 }
108170 /**
109171 * Hibernates a sandbox. Files will be saved, and the sandbox will be put to sleep. Next time
110172 * you resume the sandbox it will continue from the last state it was in.
111173 */
112174 async hibernate ( sandboxId : string ) : Promise < void > {
113- await this . api . hibernate ( sandboxId ) ;
175+ return this . withSpan (
176+ "sandboxes.hibernate" ,
177+ { "sandbox.id" : sandboxId } ,
178+ async ( ) => {
179+ await this . api . hibernate ( sandboxId ) ;
180+ }
181+ ) ;
114182 }
115183
116184 /**
117185 * Create a sandbox from a template. By default we will create a sandbox from the default universal template.
118186 */
119187 async create ( opts ?: CreateSandboxOpts & StartSandboxOpts ) : Promise < Sandbox > {
120- return this . createTemplateSandbox ( opts ) ;
188+ return this . withSpan (
189+ "sandboxes.create" ,
190+ {
191+ "template.id" : opts ?. id || this . defaultTemplateId ,
192+ "sandbox.privacy" : opts ?. privacy || "unlisted" ,
193+ } ,
194+ async ( ) => {
195+ return this . createTemplateSandbox ( opts ) ;
196+ }
197+ ) ;
121198 }
122199
123200 /**
@@ -155,59 +232,70 @@ export class Sandboxes {
155232 pagination ?: PaginationOpts ;
156233 } = { }
157234 ) : Promise < SandboxListResponse > {
158- const limit = opts . limit ?? 50 ;
159- let allSandboxes : SandboxInfo [ ] = [ ] ;
160- let currentPage = opts . pagination ?. page ?? 1 ;
161- let pageSize = opts . pagination ?. pageSize ?? limit ;
162- let totalCount = 0 ;
163- let nextPage : number | null = null ;
235+ return this . withSpan (
236+ "sandboxes.list" ,
237+ {
238+ "list.limit" : opts . limit ?? 50 ,
239+ "list.tags" : opts . tags ?. join ( "," ) || "" ,
240+ "list.orderBy" : opts . orderBy || "inserted_at" ,
241+ "list.direction" : opts . direction || "desc" ,
242+ } ,
243+ async ( ) => {
244+ const limit = opts . limit ?? 50 ;
245+ let allSandboxes : SandboxInfo [ ] = [ ] ;
246+ let currentPage = opts . pagination ?. page ?? 1 ;
247+ let pageSize = opts . pagination ?. pageSize ?? limit ;
248+ let totalCount = 0 ;
249+ let nextPage : number | null = null ;
164250
165- while ( true ) {
166- const info = await this . api . listSandboxes ( {
167- tags : opts . tags ?. join ( "," ) ,
168- page : currentPage ,
169- page_size : pageSize ,
170- order_by : opts . orderBy ,
171- direction : opts . direction ,
172- status : opts . status ,
173- } ) ;
174- totalCount = info . pagination . total_records ;
175- nextPage = info . pagination . next_page ;
251+ while ( true ) {
252+ const info = await this . api . listSandboxes ( {
253+ tags : opts . tags ?. join ( "," ) ,
254+ page : currentPage ,
255+ page_size : pageSize ,
256+ order_by : opts . orderBy ,
257+ direction : opts . direction ,
258+ status : opts . status ,
259+ } ) ;
260+ totalCount = info . pagination . total_records ;
261+ nextPage = info . pagination . next_page ;
176262
177- const sandboxes = info . sandboxes . map ( ( sandbox ) => ( {
178- id : sandbox . id ,
179- createdAt : new Date ( sandbox . created_at ) ,
180- updatedAt : new Date ( sandbox . updated_at ) ,
181- title : sandbox . title ?? undefined ,
182- description : sandbox . description ?? undefined ,
183- privacy : privacyFromNumber ( sandbox . privacy ) ,
184- tags : sandbox . tags ,
185- } ) ) ;
263+ const sandboxes = info . sandboxes . map ( ( sandbox ) => ( {
264+ id : sandbox . id ,
265+ createdAt : new Date ( sandbox . created_at ) ,
266+ updatedAt : new Date ( sandbox . updated_at ) ,
267+ title : sandbox . title ?? undefined ,
268+ description : sandbox . description ?? undefined ,
269+ privacy : privacyFromNumber ( sandbox . privacy ) ,
270+ tags : sandbox . tags ,
271+ } ) ) ;
186272
187- const newSandboxes = sandboxes . filter (
188- ( sandbox ) =>
189- ! allSandboxes . some ( ( existing ) => existing . id === sandbox . id )
190- ) ;
191- allSandboxes = [ ...allSandboxes , ...newSandboxes ] ;
273+ const newSandboxes = sandboxes . filter (
274+ ( sandbox ) =>
275+ ! allSandboxes . some ( ( existing ) => existing . id === sandbox . id )
276+ ) ;
277+ allSandboxes = [ ...allSandboxes , ...newSandboxes ] ;
192278
193- // Stop if we've hit the limit or there are no more pages
194- if ( ! nextPage || allSandboxes . length >= limit ) {
195- break ;
196- }
279+ // Stop if we've hit the limit or there are no more pages
280+ if ( ! nextPage || allSandboxes . length >= limit ) {
281+ break ;
282+ }
197283
198- currentPage = nextPage ;
199- }
284+ currentPage = nextPage ;
285+ }
200286
201- return {
202- sandboxes : allSandboxes ,
203- hasMore : totalCount > allSandboxes . length ,
204- totalCount,
205- pagination : {
206- currentPage,
207- nextPage : allSandboxes . length >= limit ? nextPage : null ,
208- pageSize,
209- } ,
210- } ;
287+ return {
288+ sandboxes : allSandboxes ,
289+ hasMore : totalCount > allSandboxes . length ,
290+ totalCount,
291+ pagination : {
292+ currentPage,
293+ nextPage : allSandboxes . length >= limit ? nextPage : null ,
294+ pageSize,
295+ } ,
296+ } ;
297+ }
298+ ) ;
211299 }
212300
213301 /**
0 commit comments