@@ -63,62 +63,102 @@ async def _fetch_v3_api(
6363) -> YouTubeMetadata :
6464 """Fetch metadata using the official YouTube Data API v3."""
6565
66- url = (
67- "https://www.googleapis.com/youtube/v3/videos?part=snippet"
68- f"&id={ youtube_id } &key={ api_key } "
69- )
70- async with httpx .AsyncClient (timeout = timeout , follow_redirects = True ) as client :
71- resp = await client .get (url )
72- if resp .status_code != 200 :
73- raise MetadataFetchError (
74- f"Data API returned HTTP { resp .status_code } : { resp .text [:200 ]} "
75- )
76- data = resp .json ()
77- items = data .get ("items" ) or []
78- if not items :
79- raise MetadataFetchError (
80- "Video not found or no snippet returned from Data API"
81- )
66+ # ------------------------------------------------------------------
67+ # Observability – manual span & histogram timing
68+ # ------------------------------------------------------------------
69+
70+ from opentelemetry import trace
71+ import time
72+ from app .metrics import YOUTUBE_FETCH_DURATION_SECONDS
73+
74+ tracer = trace .get_tracer (__name__ )
75+
76+ start_time = time .perf_counter ()
8277
83- snippet = items [ 0 ]. get ( "snippet " ) or {}
84- return YouTubeMetadata (
85- title = snippet . get ( "title" , "" ),
86- description = snippet . get ( "description" ),
87- thumbnail_url = snippet . get ( "thumbnails" ), # handled by validator
88- tags = snippet . get ( "tags" , []),
78+ with tracer . start_as_current_span ( "youtube.fetch_v3_api " ) as span :
79+ span . set_attribute ( "youtube.video_id" , youtube_id )
80+
81+ url = (
82+ "https://www.googleapis.com/youtube/v3/videos?part =snippet"
83+ f"&id= { youtube_id } &key= { api_key } "
8984 )
85+ async with httpx .AsyncClient (timeout = timeout , follow_redirects = True ) as client :
86+ resp = await client .get (url )
87+ if resp .status_code != 200 :
88+ raise MetadataFetchError (
89+ f"Data API returned HTTP { resp .status_code } : { resp .text [:200 ]} "
90+ )
91+ data = resp .json ()
92+ items = data .get ("items" ) or []
93+ if not items :
94+ raise MetadataFetchError (
95+ "Video not found or no snippet returned from Data API"
96+ )
97+
98+ snippet = items [0 ].get ("snippet" ) or {}
99+ result = YouTubeMetadata (
100+ title = snippet .get ("title" , "" ),
101+ description = snippet .get ("description" ),
102+ thumbnail_url = snippet .get ("thumbnails" ), # handled by validator
103+ tags = snippet .get ("tags" , []),
104+ )
105+
106+ # Record duration & size metrics
107+ duration = time .perf_counter () - start_time
108+ YOUTUBE_FETCH_DURATION_SECONDS .labels (method = "v3_api" ).observe (duration )
109+ span .set_attribute ("duration_ms" , int (duration * 1000 ))
110+ span .set_attribute ("title_length" , len (result .title ))
111+
112+ return result
90113
91114
92115async def _fetch_oembed (youtube_id : str , timeout : float ) -> YouTubeMetadata :
93116 """Fetch metadata using YouTube's public oEmbed endpoint."""
94117
95- url = (
96- "https://www.youtube.com/oembed?format=json&url="
97- f"https://youtu.be/{ youtube_id } "
98- )
99- print (f"DEBUG _fetch_oembed: url={ url } " )
100- async with httpx .AsyncClient (timeout = timeout , follow_redirects = True ) as client :
101- resp = await client .get (url )
102- if resp .status_code != 200 :
103- raise MetadataFetchError (
104- f"oEmbed returned HTTP { resp .status_code } : { resp .text [:200 ]} "
105- )
106- print (f"DEBUG _fetch_oembed: resp={ resp .text } " )
107- data = resp .json ()
108- title = data .get ("title" )
109- if not title :
110- raise MetadataFetchError ("oEmbed response missing title field" )
111- thumb = (
112- data .get ("thumbnail_url" )
113- or f"https://i.ytimg.com/vi/{ youtube_id } /hqdefault.jpg"
114- )
118+ from opentelemetry import trace
119+ import time
120+ from app .metrics import YOUTUBE_FETCH_DURATION_SECONDS
121+
122+ tracer = trace .get_tracer (__name__ )
123+
124+ start_time = time .perf_counter ()
115125
116- return YouTubeMetadata (
117- title = title ,
118- description = None , # oEmbed does not provide description
119- thumbnail_url = thumb ,
120- tags = [],
126+ with tracer .start_as_current_span ("youtube.fetch_oembed" ) as span :
127+ span .set_attribute ("youtube.video_id" , youtube_id )
128+
129+ url = (
130+ "https://www.youtube.com/oembed?format=json&url="
131+ f"https://youtu.be/{ youtube_id } "
121132 )
133+ print (f"DEBUG _fetch_oembed: url={ url } " )
134+ async with httpx .AsyncClient (timeout = timeout , follow_redirects = True ) as client :
135+ resp = await client .get (url )
136+ if resp .status_code != 200 :
137+ raise MetadataFetchError (
138+ f"oEmbed returned HTTP { resp .status_code } : { resp .text [:200 ]} "
139+ )
140+ print (f"DEBUG _fetch_oembed: resp={ resp .text } " )
141+ data = resp .json ()
142+ title = data .get ("title" )
143+ if not title :
144+ raise MetadataFetchError ("oEmbed response missing title field" )
145+ thumb = (
146+ data .get ("thumbnail_url" )
147+ or f"https://i.ytimg.com/vi/{ youtube_id } /hqdefault.jpg"
148+ )
149+
150+ result = YouTubeMetadata (
151+ title = title ,
152+ description = None , # oEmbed does not provide description
153+ thumbnail_url = thumb ,
154+ tags = [],
155+ )
156+
157+ duration = time .perf_counter () - start_time
158+ YOUTUBE_FETCH_DURATION_SECONDS .labels (method = "oembed" ).observe (duration )
159+ span .set_attribute ("duration_ms" , int (duration * 1000 ))
160+
161+ return result
122162
123163
124164# ---------------------------------------------------------------------------
0 commit comments