@@ -14,6 +14,7 @@ use std::path::PathBuf;
1414use tempfile:: NamedTempFile ;
1515use tokio:: fs;
1616use tokio:: process:: Command ;
17+ use tracing:: { debug, error, info, instrument, warn} ;
1718
1819/// Data structure containing information needed to generate an OpenGraph image
1920/// for a crates.io crate.
@@ -108,20 +109,27 @@ impl OgImageGenerator {
108109 /// let generator = OgImageGenerator::from_environment()?;
109110 /// # Ok::<(), crates_io_og_image::OgImageError>(())
110111 /// ```
112+ #[ instrument]
111113 pub fn from_environment ( ) -> Result < Self , OgImageError > {
112114 let typst_path = var ( "TYPST_PATH" ) . map_err ( OgImageError :: EnvVarError ) ?;
113115 let font_path = var ( "TYPST_FONT_PATH" ) . map_err ( OgImageError :: EnvVarError ) ?;
114116
115- let mut generator = if let Some ( path) = typst_path {
117+ let mut generator = if let Some ( ref path) = typst_path {
118+ debug ! ( typst_path = %path, "Using custom Typst binary path from environment" ) ;
116119 Self :: new ( PathBuf :: from ( path) )
117120 } else {
121+ debug ! ( "Using default Typst binary path (assumes 'typst' in PATH)" ) ;
118122 Self :: default ( )
119123 } ;
120124
121- if let Some ( font_path) = font_path {
125+ if let Some ( ref font_path) = font_path {
126+ debug ! ( font_path = %font_path, "Setting custom font path from environment" ) ;
122127 let current_dir = std:: env:: current_dir ( ) ?;
123128 let font_path = current_dir. join ( font_path) . canonicalize ( ) ?;
129+ debug ! ( resolved_font_path = %font_path. display( ) , "Resolved font path" ) ;
124130 generator = generator. with_font_path ( font_path) ;
131+ } else {
132+ debug ! ( "No custom font path specified, using Typst default font discovery" ) ;
125133 }
126134
127135 Ok ( generator)
@@ -153,6 +161,7 @@ impl OgImageGenerator {
153161 /// This method handles both asset-based avatars (which are copied from the bundled assets)
154162 /// and URL-based avatars (which are downloaded from the internet).
155163 /// Returns a mapping from avatar source to the local filename.
164+ #[ instrument( skip( self , data) , fields( crate . name = %data. name) ) ]
156165 async fn process_avatars < ' a > (
157166 & self ,
158167 data : & ' a OgImageData < ' _ > ,
@@ -166,34 +175,71 @@ impl OgImageGenerator {
166175 let filename = format ! ( "avatar_{index}.png" ) ;
167176 let avatar_path = assets_dir. join ( & filename) ;
168177
178+ debug ! (
179+ author_name = %author. name,
180+ avatar_url = %avatar,
181+ avatar_path = %avatar_path. display( ) ,
182+ "Processing avatar for author {}" , author. name
183+ ) ;
184+
169185 // Get the bytes either from the included asset or download from URL
170186 let bytes = if * avatar == "test-avatar" {
187+ debug ! ( "Using bundled test avatar" ) ;
171188 // Copy directly from included bytes
172189 Bytes :: from_static ( include_bytes ! ( "../template/assets/test-avatar.png" ) )
173190 } else {
191+ debug ! ( url = %avatar, "Downloading avatar from URL: {avatar}" ) ;
174192 // Download the avatar from the URL
175- let response = client. get ( * avatar) . send ( ) . await . map_err ( |err| {
193+ let response = client
194+ . get ( * avatar)
195+ . send ( )
196+ . await
197+ . map_err ( |err| OgImageError :: AvatarDownloadError {
198+ url : avatar. to_string ( ) ,
199+ source : err,
200+ } ) ?
201+ . error_for_status ( )
202+ . map_err ( |err| OgImageError :: AvatarDownloadError {
203+ url : avatar. to_string ( ) ,
204+ source : err,
205+ } ) ?;
206+
207+ let content_length = response. content_length ( ) ;
208+ debug ! (
209+ url = %avatar,
210+ content_length = ?content_length,
211+ status = %response. status( ) ,
212+ "Avatar download response received"
213+ ) ;
214+
215+ let bytes = response. bytes ( ) . await ;
216+ let bytes = bytes. map_err ( |err| {
217+ error ! ( url = %avatar, error = %err, "Failed to read avatar response bytes" ) ;
176218 OgImageError :: AvatarDownloadError {
177219 url : ( * avatar) . to_string ( ) ,
178220 source : err,
179221 }
180222 } ) ?;
181223
182- let bytes = response. bytes ( ) . await ;
183- bytes. map_err ( |err| OgImageError :: AvatarDownloadError {
184- url : ( * avatar) . to_string ( ) ,
185- source : err,
186- } ) ?
224+ debug ! ( url = %avatar, size_bytes = bytes. len( ) , "Avatar downloaded successfully" ) ;
225+ bytes
187226 } ;
188227
189228 // Write the bytes to the avatar file
190- fs:: write ( & avatar_path, bytes) . await . map_err ( |err| {
229+ fs:: write ( & avatar_path, & bytes) . await . map_err ( |err| {
191230 OgImageError :: AvatarWriteError {
192231 path : avatar_path. clone ( ) ,
193232 source : err,
194233 }
195234 } ) ?;
196235
236+ debug ! (
237+ author_name = %author. name,
238+ path = %avatar_path. display( ) ,
239+ size_bytes = bytes. len( ) ,
240+ "Avatar processed and written successfully"
241+ ) ;
242+
197243 // Store the mapping from the avatar source to the numbered filename
198244 avatar_map. insert ( * avatar, filename) ;
199245 }
@@ -232,19 +278,32 @@ impl OgImageGenerator {
232278 /// # Ok(())
233279 /// # }
234280 /// ```
281+ #[ instrument( skip( self , data) , fields(
282+ crate . name = %data. name,
283+ crate . version = %data. version,
284+ author_count = data. authors. len( ) ,
285+ ) ) ]
235286 pub async fn generate ( & self , data : OgImageData < ' _ > ) -> Result < NamedTempFile , OgImageError > {
287+ let start_time = std:: time:: Instant :: now ( ) ;
288+ info ! ( "Starting OpenGraph image generation" ) ;
289+
236290 // Create a temporary folder
237291 let temp_dir = tempfile:: tempdir ( ) . map_err ( OgImageError :: TempDirError ) ?;
292+ debug ! ( temp_dir = %temp_dir. path( ) . display( ) , "Created temporary directory" ) ;
238293
239294 // Create assets directory and copy logo and icons
240295 let assets_dir = temp_dir. path ( ) . join ( "assets" ) ;
296+ debug ! ( assets_dir = %assets_dir. display( ) , "Creating assets directory" ) ;
241297 fs:: create_dir ( & assets_dir) . await ?;
298+
299+ debug ! ( "Copying bundled assets to temporary directory" ) ;
242300 let cargo_logo = include_bytes ! ( "../template/assets/cargo.png" ) ;
243301 fs:: write ( assets_dir. join ( "cargo.png" ) , cargo_logo) . await ?;
244302 let rust_logo_svg = include_bytes ! ( "../template/assets/rust-logo.svg" ) ;
245303 fs:: write ( assets_dir. join ( "rust-logo.svg" ) , rust_logo_svg) . await ?;
246304
247305 // Copy SVG icons
306+ debug ! ( "Copying SVG icon assets" ) ;
248307 let code_branch_svg = include_bytes ! ( "../template/assets/code-branch.svg" ) ;
249308 fs:: write ( assets_dir. join ( "code-branch.svg" ) , code_branch_svg) . await ?;
250309 let code_svg = include_bytes ! ( "../template/assets/code.svg" ) ;
@@ -257,24 +316,36 @@ impl OgImageGenerator {
257316 fs:: write ( assets_dir. join ( "weight-hanging.svg" ) , weight_hanging_svg) . await ?;
258317
259318 // Process avatars - download URLs and copy assets
319+ let avatar_start_time = std:: time:: Instant :: now ( ) ;
320+ info ! ( "Processing avatars" ) ;
260321 let avatar_map = self . process_avatars ( & data, & assets_dir) . await ?;
322+ let avatar_duration = avatar_start_time. elapsed ( ) ;
323+ info ! (
324+ avatar_count = avatar_map. len( ) ,
325+ duration_ms = avatar_duration. as_millis( ) ,
326+ "Avatar processing completed"
327+ ) ;
261328
262329 // Copy the static Typst template file
263330 let template_content = include_str ! ( "../template/og-image.typ" ) ;
264331 let typ_file_path = temp_dir. path ( ) . join ( "og-image.typ" ) ;
332+ debug ! ( template_path = %typ_file_path. display( ) , "Copying Typst template" ) ;
265333 fs:: write ( & typ_file_path, template_content) . await ?;
266334
267335 // Create a named temp file for the output PNG
268336 let output_file = NamedTempFile :: new ( ) . map_err ( OgImageError :: TempFileError ) ?;
337+ debug ! ( output_path = %output_file. path( ) . display( ) , "Created output file" ) ;
269338
270339 // Serialize data and avatar_map to JSON
271- let json_data = serde_json:: to_string ( & data) ;
272- let json_data = json_data. map_err ( OgImageError :: JsonSerializationError ) ?;
340+ debug ! ( "Serializing data and avatar map to JSON" ) ;
341+ let json_data =
342+ serde_json:: to_string ( & data) . map_err ( OgImageError :: JsonSerializationError ) ?;
273343
274- let json_avatar_map = serde_json :: to_string ( & avatar_map ) ;
275- let json_avatar_map = json_avatar_map . map_err ( OgImageError :: JsonSerializationError ) ?;
344+ let json_avatar_map =
345+ serde_json :: to_string ( & avatar_map ) . map_err ( OgImageError :: JsonSerializationError ) ?;
276346
277347 // Run typst compile command with input data
348+ info ! ( "Running Typst compilation command" ) ;
278349 let mut command = Command :: new ( & self . typst_binary_path ) ;
279350 command. arg ( "compile" ) . arg ( "--format" ) . arg ( "png" ) ;
280351
@@ -286,8 +357,11 @@ impl OgImageGenerator {
286357
287358 // Pass in the font path if specified
288359 if let Some ( font_path) = & self . typst_font_path {
360+ debug ! ( font_path = %font_path. display( ) , "Using custom font path" ) ;
289361 command. arg ( "--font-path" ) . arg ( font_path) ;
290362 command. arg ( "--ignore-system-fonts" ) ;
363+ } else {
364+ debug ! ( "Using system font discovery" ) ;
291365 }
292366
293367 // Pass input and output file paths
@@ -304,19 +378,41 @@ impl OgImageGenerator {
304378 command. env ( "HOME" , home) ;
305379 }
306380
381+ let compilation_start_time = std:: time:: Instant :: now ( ) ;
307382 let output = command. output ( ) . await ;
308383 let output = output. map_err ( OgImageError :: TypstNotFound ) ?;
384+ let compilation_duration = compilation_start_time. elapsed ( ) ;
309385
310386 if !output. status . success ( ) {
311387 let stderr = String :: from_utf8_lossy ( & output. stderr ) . to_string ( ) ;
312388 let stdout = String :: from_utf8_lossy ( & output. stdout ) . to_string ( ) ;
389+ error ! (
390+ exit_code = ?output. status. code( ) ,
391+ stderr = %stderr,
392+ stdout = %stdout,
393+ duration_ms = compilation_duration. as_millis( ) ,
394+ "Typst compilation failed"
395+ ) ;
313396 return Err ( OgImageError :: TypstCompilationError {
314397 stderr,
315398 stdout,
316399 exit_code : output. status . code ( ) ,
317400 } ) ;
318401 }
319402
403+ let output_size_bytes = fs:: metadata ( output_file. path ( ) ) . await ;
404+ let output_size_bytes = output_size_bytes. map ( |m| m. len ( ) ) . unwrap_or ( 0 ) ;
405+
406+ debug ! (
407+ duration_ms = compilation_duration. as_millis( ) ,
408+ output_size_bytes, "Typst compilation completed successfully"
409+ ) ;
410+
411+ let duration = start_time. elapsed ( ) ;
412+ info ! (
413+ duration_ms = duration. as_millis( ) ,
414+ output_size_bytes, "OpenGraph image generation completed successfully"
415+ ) ;
320416 Ok ( output_file)
321417 }
322418}
@@ -332,6 +428,19 @@ impl Default for OgImageGenerator {
332428#[ cfg( test) ]
333429mod tests {
334430 use super :: * ;
431+ use tracing:: dispatcher:: DefaultGuard ;
432+ use tracing:: { Level , subscriber} ;
433+ use tracing_subscriber:: fmt;
434+
435+ fn init_tracing ( ) -> DefaultGuard {
436+ let subscriber = fmt ( )
437+ . compact ( )
438+ . with_max_level ( Level :: DEBUG )
439+ . with_test_writer ( )
440+ . finish ( ) ;
441+
442+ subscriber:: set_default ( subscriber)
443+ }
335444
336445 const fn author ( name : & str ) -> OgImageAuthorData < ' _ > {
337446 OgImageAuthorData :: new ( name, None )
@@ -464,6 +573,7 @@ mod tests {
464573
465574 #[ tokio:: test]
466575 async fn test_generate_og_image_snapshot ( ) {
576+ let _guard = init_tracing ( ) ;
467577 let data = create_simple_test_data ( ) ;
468578
469579 if let Some ( image_data) = generate_image ( data) . await {
@@ -473,6 +583,7 @@ mod tests {
473583
474584 #[ tokio:: test]
475585 async fn test_generate_og_image_overflow_snapshot ( ) {
586+ let _guard = init_tracing ( ) ;
476587 let data = create_overflow_test_data ( ) ;
477588
478589 if let Some ( image_data) = generate_image ( data) . await {
@@ -482,6 +593,7 @@ mod tests {
482593
483594 #[ tokio:: test]
484595 async fn test_generate_og_image_minimal_snapshot ( ) {
596+ let _guard = init_tracing ( ) ;
485597 let data = create_minimal_test_data ( ) ;
486598
487599 if let Some ( image_data) = generate_image ( data) . await {
@@ -491,6 +603,7 @@ mod tests {
491603
492604 #[ tokio:: test]
493605 async fn test_generate_og_image_escaping_snapshot ( ) {
606+ let _guard = init_tracing ( ) ;
494607 let data = create_escaping_test_data ( ) ;
495608
496609 if let Some ( image_data) = generate_image ( data) . await {
0 commit comments