@@ -83,18 +83,66 @@ const retriableGetTraceIds = (options: GetXRayTraceIdsOptions) =>
8383 endTime . getTime ( ) / 1000
8484 ) } --filter-expression 'resource.arn ENDSWITH ":function:${ options . resourceName } "'`
8585 ) ;
86+
87+ throw new Error (
88+ `Failed to get trace IDs after ${ retryOptions . retries } retries` ,
89+ { cause : error }
90+ ) ;
8691 }
8792 retry ( error ) ;
8893 }
89- } ) ;
94+ } , retryOptions ) ;
95+
96+ /**
97+ * Find the main Powertools subsegment in the trace
98+ *
99+ * A main Powertools subsegment is identified by the `## index.` suffix. Depending on the
100+ * runtime, it may also be identified by the `Invocation` name.
101+ *
102+ * @param trace - The trace to find the main Powertools subsegment
103+ * @param functionName - The function name to find the main Powertools subsegment
104+ */
105+ const findMainPowertoolsSubsegment = (
106+ trace : XRayTraceDocumentParsed ,
107+ functionName : string
108+ ) => {
109+ const maybePowertoolsSubsegment = trace . subsegments ?. find (
110+ ( subsegment ) =>
111+ subsegment . name . startsWith ( '## index.' ) ||
112+ subsegment . name === 'Invocation'
113+ ) ;
114+
115+ if ( ! maybePowertoolsSubsegment ) {
116+ throw new Error ( `Main subsegment not found for ${ functionName } segment` ) ;
117+ }
118+
119+ if ( maybePowertoolsSubsegment . name === 'Invocation' ) {
120+ const powertoolsSubsegment = maybePowertoolsSubsegment . subsegments ?. find (
121+ ( subsegment ) => subsegment . name . startsWith ( '## index.' )
122+ ) ;
123+
124+ if ( ! powertoolsSubsegment ) {
125+ throw new Error ( `Main subsegment not found for ${ functionName } segment` ) ;
126+ }
127+
128+ return powertoolsSubsegment ;
129+ }
130+
131+ return maybePowertoolsSubsegment ;
132+ } ;
90133
91134/**
92135 * Parse and sort the trace segments by start time
93136 *
94137 * @param trace - The trace to parse and sort
95138 * @param expectedSegmentsCount - The expected segments count for the trace
139+ * @param functionName - The function name to find the main Powertools subsegment
96140 */
97- const parseAndSortTrace = ( trace : Trace , expectedSegmentsCount : number ) => {
141+ const parseAndSortTrace = (
142+ trace : Trace ,
143+ expectedSegmentsCount : number ,
144+ functionName : string
145+ ) => {
98146 const { Id : id , Segments : segments } = trace ;
99147 if ( segments === undefined || segments . length !== expectedSegmentsCount ) {
100148 throw new Error (
@@ -111,9 +159,14 @@ const parseAndSortTrace = (trace: Trace, expectedSegmentsCount: number) => {
111159 ) ;
112160 }
113161
162+ const parsedDocument = JSON . parse ( Document ) as XRayTraceDocumentParsed ;
163+ if ( parsedDocument . origin === 'AWS::Lambda::Function' ) {
164+ findMainPowertoolsSubsegment ( parsedDocument , functionName ) ;
165+ }
166+
114167 parsedSegments . push ( {
115168 Id,
116- Document : JSON . parse ( Document ) as XRayTraceDocumentParsed ,
169+ Document : parsedDocument ,
117170 } ) ;
118171 }
119172
@@ -136,15 +189,14 @@ const parseAndSortTrace = (trace: Trace, expectedSegmentsCount: number) => {
136189const getTraceDetails = async (
137190 options : GetXRayTraceDetailsOptions
138191) : Promise < XRayTraceParsed [ ] > => {
139- const { traceIds, expectedSegmentsCount } = options ;
192+ const { traceIds, expectedSegmentsCount, functionName } = options ;
140193 const response = await xrayClient . send (
141194 new BatchGetTracesCommand ( {
142195 TraceIds : traceIds ,
143196 } )
144197 ) ;
145198
146- const traces = response . Traces ;
147-
199+ const { Traces : traces } = response ;
148200 if ( traces === undefined || traces . length !== traceIds . length ) {
149201 throw new Error (
150202 `Expected ${ traceIds . length } traces, got ${ traces ? traces . length : 0 } `
@@ -153,7 +205,9 @@ const getTraceDetails = async (
153205
154206 const parsedAndSortedTraces : XRayTraceParsed [ ] = [ ] ;
155207 for ( const trace of traces ) {
156- parsedAndSortedTraces . push ( parseAndSortTrace ( trace , expectedSegmentsCount ) ) ;
208+ parsedAndSortedTraces . push (
209+ parseAndSortTrace ( trace , expectedSegmentsCount , functionName )
210+ ) ;
157211 }
158212
159213 return parsedAndSortedTraces . sort (
@@ -168,16 +222,28 @@ const getTraceDetails = async (
168222 * @param options - The options to get trace details, including the trace IDs and expected segments count
169223 */
170224const retriableGetTraceDetails = ( options : GetXRayTraceDetailsOptions ) =>
171- promiseRetry ( async ( retry ) => {
225+ promiseRetry ( async ( retry , attempt ) => {
172226 try {
173227 return await getTraceDetails ( options ) ;
174228 } catch ( error ) {
229+ if ( attempt === retryOptions . retries ) {
230+ console . log (
231+ `Manual query: aws xray batch-get-traces --trace-ids ${
232+ options . traceIds
233+ } `
234+ ) ;
235+
236+ throw new Error (
237+ `Failed to get trace details after ${ retryOptions . retries } retries` ,
238+ { cause : error }
239+ ) ;
240+ }
175241 retry ( error ) ;
176242 }
177- } ) ;
243+ } , retryOptions ) ;
178244
179245/**
180- * Find the main function segment in the trace identified by the `## index.` suffix
246+ * Find the main function segment within the `AWS::Lambda::Function` segment
181247 */
182248const findPowertoolsFunctionSegment = (
183249 trace : XRayTraceParsed ,
@@ -194,30 +260,7 @@ const findPowertoolsFunctionSegment = (
194260 }
195261
196262 const document = functionSegment . Document ;
197-
198- const maybePowertoolsSubsegment = document . subsegments ?. find (
199- ( subsegment ) =>
200- subsegment . name . startsWith ( '## index.' ) ||
201- subsegment . name === 'Invocation'
202- ) ;
203-
204- if ( ! maybePowertoolsSubsegment ) {
205- throw new Error ( `Main subsegment not found for ${ functionName } segment` ) ;
206- }
207-
208- if ( maybePowertoolsSubsegment . name === 'Invocation' ) {
209- const powertoolsSubsegment = maybePowertoolsSubsegment . subsegments ?. find (
210- ( subsegment ) => subsegment . name . startsWith ( '## index.' )
211- ) ;
212-
213- if ( ! powertoolsSubsegment ) {
214- throw new Error ( `Main subsegment not found for ${ functionName } segment` ) ;
215- }
216-
217- return powertoolsSubsegment ;
218- }
219-
220- return maybePowertoolsSubsegment ;
263+ return findMainPowertoolsSubsegment ( document , functionName ) ;
221264} ;
222265
223266/**
@@ -271,6 +314,7 @@ const getXRayTraceData = async (
271314 const traces = await retriableGetTraceDetails ( {
272315 traceIds,
273316 expectedSegmentsCount,
317+ functionName : resourceName ,
274318 } ) ;
275319
276320 if ( ! traces ) {
@@ -286,9 +330,15 @@ const getXRayTraceData = async (
286330 * @param options - The options to get the X-Ray trace data, including the start time, resource name, expected traces count, and expected segments count
287331 */
288332const getTraces = async (
289- options : GetXRayTraceIdsOptions & Omit < GetXRayTraceDetailsOptions , 'traceIds' >
333+ options : GetXRayTraceIdsOptions &
334+ Omit < GetXRayTraceDetailsOptions , 'traceIds' | 'functionName' > & {
335+ resourceName : string ;
336+ }
290337) : Promise < EnrichedXRayTraceDocumentParsed [ ] > => {
291- const traces = await getXRayTraceData ( options ) ;
338+ const traces = await getXRayTraceData ( {
339+ ...options ,
340+ functionName : options . resourceName ,
341+ } ) ;
292342
293343 const { resourceName } = options ;
294344
@@ -305,45 +355,6 @@ const getTraces = async (
305355 return mainSubsegments ;
306356} ;
307357
308- /**
309- * Get the X-Ray trace data for a given resource name without the main subsegments.
310- *
311- * This is useful when we are testing cases where Active Tracing is disabled and we don't have the main subsegments.
312- *
313- * @param options - The options to get the X-Ray trace data, including the start time, resource name, expected traces count, and expected segments count
314- */
315- const getTracesWithoutMainSubsegments = async (
316- options : GetXRayTraceIdsOptions & Omit < GetXRayTraceDetailsOptions , 'traceIds' >
317- ) : Promise < EnrichedXRayTraceDocumentParsed [ ] > => {
318- const traces = await getXRayTraceData ( options ) ;
319-
320- const { resourceName } = options ;
321-
322- const lambdaFunctionSegments = [ ] ;
323- for ( const trace of traces ) {
324- const functionSegment = trace . Segments . find (
325- ( segment ) => segment . Document . origin === 'AWS::Lambda::Function'
326- ) ;
327-
328- if ( ! functionSegment ) {
329- throw new Error (
330- `AWS::Lambda::Function segment not found for ${ resourceName } `
331- ) ;
332- }
333-
334- const lambdaFunctionSegment = functionSegment . Document ;
335- const enrichedSubsegment = {
336- ...lambdaFunctionSegment ,
337- subsegments : parseSubsegmentsByName (
338- lambdaFunctionSegment . subsegments ?? [ ]
339- ) ,
340- } ;
341- lambdaFunctionSegments . push ( enrichedSubsegment ) ;
342- }
343-
344- return lambdaFunctionSegments ;
345- } ;
346-
347358export {
348359 getTraceIds ,
349360 retriableGetTraceIds ,
@@ -352,5 +363,4 @@ export {
352363 findPowertoolsFunctionSegment ,
353364 getTraces ,
354365 parseSubsegmentsByName ,
355- getTracesWithoutMainSubsegments ,
356366} ;
0 commit comments