66 createBase64DecoderTransformStream ,
77 createBase64EncoderTransformStream ,
88} from 'cloudflare-internal:streaming-base64' ;
9- import { withSpan } from 'cloudflare-internal:tracing-helpers' ;
9+ import { withSpan , type Span } from 'cloudflare-internal:tracing-helpers' ;
1010
1111type Fetcher = {
1212 fetch : typeof fetch ;
@@ -122,40 +122,51 @@ class ImageTransformerImpl implements ImageTransformer {
122122 async output (
123123 options : ImageOutputOptions
124124 ) : Promise < ImageTransformationResult > {
125- const formData = new StreamableFormData ( ) ;
125+ return await withSpan ( 'images_output' , async ( span ) => {
126+ span . setAttribute ( 'cloudflare.binding.type' , 'Images' ) ;
127+ const formData = new StreamableFormData ( ) ;
126128
127- this . #consume( ) ;
128- formData . append ( 'image' , this . #stream, { type : 'file' } ) ;
129+ this . #consume( ) ;
130+ formData . append ( 'image' , this . #stream, { type : 'file' } ) ;
129131
130- this . #serializeTransforms( formData ) ;
132+ this . #serializeTransforms( formData , span ) ;
131133
132- formData . append ( 'output_format' , options . format ) ;
133- if ( options . quality !== undefined ) {
134- formData . append ( 'output_quality' , options . quality . toString ( ) ) ;
135- }
134+ span . setAttribute ( 'cloudflare.images.options.format' , options . format ) ;
135+ formData . append ( 'output_format' , options . format ) ;
136136
137- if ( options . background !== undefined ) {
138- formData . append ( 'background' , options . background ) ;
139- }
137+ if ( options . quality !== undefined ) {
138+ span . setAttribute ( 'cloudflare.images.options.quality' , options . quality ) ;
139+ formData . append ( 'output_quality' , options . quality . toString ( ) ) ;
140+ }
140141
141- if ( options . anim !== undefined ) {
142- formData . append ( 'anim' , options . anim . toString ( ) ) ;
143- }
142+ if ( options . background !== undefined ) {
143+ span . setAttribute (
144+ 'cloudflare.images.options.background' ,
145+ options . background
146+ ) ;
147+ formData . append ( 'background' , options . background ) ;
148+ }
144149
145- const response = await this . #fetcher. fetch (
146- 'https://js.images.cloudflare.com/transform' ,
147- {
148- method : 'POST' ,
149- headers : {
150- 'content-type' : formData . contentType ( ) ,
151- } ,
152- body : formData . stream ( ) ,
150+ if ( options . anim !== undefined ) {
151+ span . setAttribute ( 'cloudflare.images.options.anim' , options . anim ) ;
152+ formData . append ( 'anim' , options . anim . toString ( ) ) ;
153153 }
154- ) ;
155154
156- await throwErrorIfErrorResponse ( 'TRANSFORM' , response ) ;
155+ const response = await this . #fetcher. fetch (
156+ 'https://js.images.cloudflare.com/transform' ,
157+ {
158+ method : 'POST' ,
159+ headers : {
160+ 'content-type' : formData . contentType ( ) ,
161+ } ,
162+ body : formData . stream ( ) ,
163+ }
164+ ) ;
165+
166+ await throwErrorIfErrorResponse ( 'TRANSFORM' , response , span ) ;
157167
158- return new TransformationResultImpl ( response ) ;
168+ return new TransformationResultImpl ( response ) ;
169+ } ) ;
159170 }
160171
161172 #consume( ) : void {
@@ -169,7 +180,7 @@ class ImageTransformerImpl implements ImageTransformer {
169180 this . #consumed = true ;
170181 }
171182
172- #serializeTransforms( formData : StreamableFormData ) : void {
183+ #serializeTransforms( formData : StreamableFormData , span : Span ) : void {
173184 const transforms : ( TargetedTransform | DrawCommand ) [ ] = [ ] ;
174185
175186 // image 0 is the canvas, so the first draw_image has index 1
@@ -211,6 +222,16 @@ class ImageTransformerImpl implements ImageTransformer {
211222 }
212223
213224 walkTransforms ( 0 , this . #transforms) ;
225+
226+ // The transforms are a set of operations which are applied to the image in order.
227+ // Attaching an attribute as JSON is a little odd, but I'm not sure if there is
228+ // a better way to do this.
229+ if ( transforms . length > 0 ) {
230+ span . setAttribute (
231+ 'cloudflare.images.options.transforms' ,
232+ JSON . stringify ( transforms )
233+ ) ;
234+ }
214235 formData . append ( 'transforms' , JSON . stringify ( transforms ) ) ;
215236 }
216237}
@@ -235,15 +256,18 @@ class ImagesBindingImpl implements ImagesBinding {
235256 options ?: ImageInputOptions
236257 ) : Promise < ImageInfoResponse > {
237258 return await withSpan ( 'images_info' , async ( span ) => {
259+ span . setAttribute ( 'cloudflare.binding.type' , 'Images' ) ;
238260 const body = new StreamableFormData ( ) ;
239261
262+ span . setAttribute (
263+ 'cloudflare.images.options.encoding' ,
264+ options ?. encoding ?? 'base64'
265+ ) ;
240266 const decodedStream =
241267 options ?. encoding === 'base64'
242268 ? stream . pipeThrough ( createBase64DecoderTransformStream ( ) )
243269 : stream ;
244270
245- span . setAttribute ( 'cloudflare.images.info.encoding' , options ?. encoding ) ;
246-
247271 body . append ( 'image' , decodedStream , { type : 'file' } ) ;
248272
249273 const response = await this . #fetcher. fetch (
@@ -257,22 +281,23 @@ class ImagesBindingImpl implements ImagesBinding {
257281 }
258282 ) ;
259283
260- await throwErrorIfErrorResponse ( 'INFO' , response ) ;
284+ await throwErrorIfErrorResponse ( 'INFO' , response , span ) ;
261285
262286 const r = ( await response . json ( ) ) as RawInfoResponse ;
263287
264- span . setAttribute ( 'cloudflare.images.info .format' , r . format ) ;
288+ span . setAttribute ( 'cloudflare.images.result .format' , r . format ) ;
265289
266290 if ( 'file_size' in r ) {
267- span . setAttribute ( 'cloudflare.images.info.file_size' , r . file_size ) ;
268- span . setAttribute ( 'cloudflare.images.info.width' , r . width ) ;
269- span . setAttribute ( 'cloudflare.images.info.height' , r . height ) ;
270- return {
291+ const ret = {
271292 fileSize : r . file_size ,
272293 width : r . width ,
273294 height : r . height ,
274295 format : r . format ,
275296 } ;
297+ span . setAttribute ( 'cloudflare.images.result.file_size' , ret . fileSize ) ;
298+ span . setAttribute ( 'cloudflare.images.result.width' , ret . width ) ;
299+ span . setAttribute ( 'cloudflare.images.result.height' , ret . height ) ;
300+ return ret ;
276301 }
277302
278303 return r ;
@@ -302,24 +327,29 @@ class ImagesErrorImpl extends Error implements ImagesError {
302327
303328async function throwErrorIfErrorResponse (
304329 operation : string ,
305- response : Response
330+ response : Response ,
331+ span : Span
306332) : Promise < void > {
307333 const statusHeader = response . headers . get ( 'cf-images-binding' ) || '' ;
308334
309335 const match = / e r r = ( \d + ) / . exec ( statusHeader ) ;
310336
311337 if ( match && match [ 1 ] ) {
338+ const errorMessage = await response . text ( ) ;
339+ span . setAttribute ( 'cloudflare.images.error.code' , match [ 1 ] ) ;
340+ span . setAttribute ( 'error.type' , errorMessage ) ;
312341 throw new ImagesErrorImpl (
313- `IMAGES_${ operation } _${ await response . text ( ) } ` . trim ( ) ,
342+ `IMAGES_${ operation } _${ errorMessage } ` . trim ( ) ,
314343 Number . parseInt ( match [ 1 ] )
315344 ) ;
316345 }
317346
318347 if ( response . status > 399 ) {
348+ const errorMessage = await response . text ( ) ;
349+ span . setAttribute ( 'cloudflare.images.error.code' , '9523' ) ;
350+ span . setAttribute ( 'error.type' , errorMessage ) ;
319351 throw new ImagesErrorImpl (
320- `Unexpected error response ${ response . status } : ${ (
321- await response . text ( )
322- ) . trim ( ) } `,
352+ `Unexpected error response ${ response . status } : ${ errorMessage . trim ( ) } ` ,
323353 9523
324354 ) ;
325355 }
0 commit comments