@@ -83,18 +83,66 @@ const retriableGetTraceIds = (options: GetXRayTraceIdsOptions) =>
83
83
endTime . getTime ( ) / 1000
84
84
) } --filter-expression 'resource.arn ENDSWITH ":function:${ options . resourceName } "'`
85
85
) ;
86
+
87
+ throw new Error (
88
+ `Failed to get trace IDs after ${ retryOptions . retries } retries` ,
89
+ { cause : error }
90
+ ) ;
86
91
}
87
92
retry ( error ) ;
88
93
}
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
+ } ;
90
133
91
134
/**
92
135
* Parse and sort the trace segments by start time
93
136
*
94
137
* @param trace - The trace to parse and sort
95
138
* @param expectedSegmentsCount - The expected segments count for the trace
139
+ * @param functionName - The function name to find the main Powertools subsegment
96
140
*/
97
- const parseAndSortTrace = ( trace : Trace , expectedSegmentsCount : number ) => {
141
+ const parseAndSortTrace = (
142
+ trace : Trace ,
143
+ expectedSegmentsCount : number ,
144
+ functionName : string
145
+ ) => {
98
146
const { Id : id , Segments : segments } = trace ;
99
147
if ( segments === undefined || segments . length !== expectedSegmentsCount ) {
100
148
throw new Error (
@@ -111,9 +159,14 @@ const parseAndSortTrace = (trace: Trace, expectedSegmentsCount: number) => {
111
159
) ;
112
160
}
113
161
162
+ const parsedDocument = JSON . parse ( Document ) as XRayTraceDocumentParsed ;
163
+ if ( parsedDocument . origin === 'AWS::Lambda::Function' ) {
164
+ findMainPowertoolsSubsegment ( parsedDocument , functionName ) ;
165
+ }
166
+
114
167
parsedSegments . push ( {
115
168
Id,
116
- Document : JSON . parse ( Document ) as XRayTraceDocumentParsed ,
169
+ Document : parsedDocument ,
117
170
} ) ;
118
171
}
119
172
@@ -136,15 +189,14 @@ const parseAndSortTrace = (trace: Trace, expectedSegmentsCount: number) => {
136
189
const getTraceDetails = async (
137
190
options : GetXRayTraceDetailsOptions
138
191
) : Promise < XRayTraceParsed [ ] > => {
139
- const { traceIds, expectedSegmentsCount } = options ;
192
+ const { traceIds, expectedSegmentsCount, functionName } = options ;
140
193
const response = await xrayClient . send (
141
194
new BatchGetTracesCommand ( {
142
195
TraceIds : traceIds ,
143
196
} )
144
197
) ;
145
198
146
- const traces = response . Traces ;
147
-
199
+ const { Traces : traces } = response ;
148
200
if ( traces === undefined || traces . length !== traceIds . length ) {
149
201
throw new Error (
150
202
`Expected ${ traceIds . length } traces, got ${ traces ? traces . length : 0 } `
@@ -153,7 +205,9 @@ const getTraceDetails = async (
153
205
154
206
const parsedAndSortedTraces : XRayTraceParsed [ ] = [ ] ;
155
207
for ( const trace of traces ) {
156
- parsedAndSortedTraces . push ( parseAndSortTrace ( trace , expectedSegmentsCount ) ) ;
208
+ parsedAndSortedTraces . push (
209
+ parseAndSortTrace ( trace , expectedSegmentsCount , functionName )
210
+ ) ;
157
211
}
158
212
159
213
return parsedAndSortedTraces . sort (
@@ -168,16 +222,28 @@ const getTraceDetails = async (
168
222
* @param options - The options to get trace details, including the trace IDs and expected segments count
169
223
*/
170
224
const retriableGetTraceDetails = ( options : GetXRayTraceDetailsOptions ) =>
171
- promiseRetry ( async ( retry ) => {
225
+ promiseRetry ( async ( retry , attempt ) => {
172
226
try {
173
227
return await getTraceDetails ( options ) ;
174
228
} 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
+ }
175
241
retry ( error ) ;
176
242
}
177
- } ) ;
243
+ } , retryOptions ) ;
178
244
179
245
/**
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
181
247
*/
182
248
const findPowertoolsFunctionSegment = (
183
249
trace : XRayTraceParsed ,
@@ -194,30 +260,7 @@ const findPowertoolsFunctionSegment = (
194
260
}
195
261
196
262
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 ) ;
221
264
} ;
222
265
223
266
/**
@@ -271,6 +314,7 @@ const getXRayTraceData = async (
271
314
const traces = await retriableGetTraceDetails ( {
272
315
traceIds,
273
316
expectedSegmentsCount,
317
+ functionName : resourceName ,
274
318
} ) ;
275
319
276
320
if ( ! traces ) {
@@ -286,9 +330,15 @@ const getXRayTraceData = async (
286
330
* @param options - The options to get the X-Ray trace data, including the start time, resource name, expected traces count, and expected segments count
287
331
*/
288
332
const getTraces = async (
289
- options : GetXRayTraceIdsOptions & Omit < GetXRayTraceDetailsOptions , 'traceIds' >
333
+ options : GetXRayTraceIdsOptions &
334
+ Omit < GetXRayTraceDetailsOptions , 'traceIds' | 'functionName' > & {
335
+ resourceName : string ;
336
+ }
290
337
) : Promise < EnrichedXRayTraceDocumentParsed [ ] > => {
291
- const traces = await getXRayTraceData ( options ) ;
338
+ const traces = await getXRayTraceData ( {
339
+ ...options ,
340
+ functionName : options . resourceName ,
341
+ } ) ;
292
342
293
343
const { resourceName } = options ;
294
344
@@ -305,45 +355,6 @@ const getTraces = async (
305
355
return mainSubsegments ;
306
356
} ;
307
357
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
-
347
358
export {
348
359
getTraceIds ,
349
360
retriableGetTraceIds ,
@@ -352,5 +363,4 @@ export {
352
363
findPowertoolsFunctionSegment ,
353
364
getTraces ,
354
365
parseSubsegmentsByName ,
355
- getTracesWithoutMainSubsegments ,
356
366
} ;
0 commit comments