Skip to content

Commit a4eead1

Browse files
committed
chore: account for not loaded subsegments
1 parent 4f613fe commit a4eead1

File tree

2 files changed

+70
-33
lines changed

2 files changed

+70
-33
lines changed

packages/testing/src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,10 @@ type GetXRayTraceDetailsOptions = {
169169
* The expected number of segments in each trace
170170
*/
171171
expectedSegmentsCount: number;
172+
/**
173+
* The name of the function that the trace is expected to be associated with
174+
*/
175+
functionName: string;
172176
};
173177

174178
/**

packages/testing/src/xray-traces-utils.ts

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,56 @@ const retriableGetTraceIds = (options: GetXRayTraceIdsOptions) =>
9393
}
9494
}, retryOptions);
9595

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+
};
133+
96134
/**
97135
* Parse and sort the trace segments by start time
98136
*
99137
* @param trace - The trace to parse and sort
100138
* @param expectedSegmentsCount - The expected segments count for the trace
139+
* @param functionName - The function name to find the main Powertools subsegment
101140
*/
102-
const parseAndSortTrace = (trace: Trace, expectedSegmentsCount: number) => {
141+
const parseAndSortTrace = (
142+
trace: Trace,
143+
expectedSegmentsCount: number,
144+
functionName: string
145+
) => {
103146
const { Id: id, Segments: segments } = trace;
104147
if (segments === undefined || segments.length !== expectedSegmentsCount) {
105148
throw new Error(
@@ -116,9 +159,14 @@ const parseAndSortTrace = (trace: Trace, expectedSegmentsCount: number) => {
116159
);
117160
}
118161

162+
const parsedDocument = JSON.parse(Document) as XRayTraceDocumentParsed;
163+
if (parsedDocument.origin === 'AWS::Lambda::Function') {
164+
findMainPowertoolsSubsegment(parsedDocument, functionName);
165+
}
166+
119167
parsedSegments.push({
120168
Id,
121-
Document: JSON.parse(Document) as XRayTraceDocumentParsed,
169+
Document: parsedDocument,
122170
});
123171
}
124172

@@ -141,15 +189,14 @@ const parseAndSortTrace = (trace: Trace, expectedSegmentsCount: number) => {
141189
const getTraceDetails = async (
142190
options: GetXRayTraceDetailsOptions
143191
): Promise<XRayTraceParsed[]> => {
144-
const { traceIds, expectedSegmentsCount } = options;
192+
const { traceIds, expectedSegmentsCount, functionName } = options;
145193
const response = await xrayClient.send(
146194
new BatchGetTracesCommand({
147195
TraceIds: traceIds,
148196
})
149197
);
150198

151-
const traces = response.Traces;
152-
199+
const { Traces: traces } = response;
153200
if (traces === undefined || traces.length !== traceIds.length) {
154201
throw new Error(
155202
`Expected ${traceIds.length} traces, got ${traces ? traces.length : 0}`
@@ -158,7 +205,9 @@ const getTraceDetails = async (
158205

159206
const parsedAndSortedTraces: XRayTraceParsed[] = [];
160207
for (const trace of traces) {
161-
parsedAndSortedTraces.push(parseAndSortTrace(trace, expectedSegmentsCount));
208+
parsedAndSortedTraces.push(
209+
parseAndSortTrace(trace, expectedSegmentsCount, functionName)
210+
);
162211
}
163212

164213
return parsedAndSortedTraces.sort(
@@ -194,7 +243,7 @@ const retriableGetTraceDetails = (options: GetXRayTraceDetailsOptions) =>
194243
}, retryOptions);
195244

196245
/**
197-
* 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
198247
*/
199248
const findPowertoolsFunctionSegment = (
200249
trace: XRayTraceParsed,
@@ -211,30 +260,7 @@ const findPowertoolsFunctionSegment = (
211260
}
212261

213262
const document = functionSegment.Document;
214-
215-
const maybePowertoolsSubsegment = document.subsegments?.find(
216-
(subsegment) =>
217-
subsegment.name.startsWith('## index.') ||
218-
subsegment.name === 'Invocation'
219-
);
220-
221-
if (!maybePowertoolsSubsegment) {
222-
throw new Error(`Main subsegment not found for ${functionName} segment`);
223-
}
224-
225-
if (maybePowertoolsSubsegment.name === 'Invocation') {
226-
const powertoolsSubsegment = maybePowertoolsSubsegment.subsegments?.find(
227-
(subsegment) => subsegment.name.startsWith('## index.')
228-
);
229-
230-
if (!powertoolsSubsegment) {
231-
throw new Error(`Main subsegment not found for ${functionName} segment`);
232-
}
233-
234-
return powertoolsSubsegment;
235-
}
236-
237-
return maybePowertoolsSubsegment;
263+
return findMainPowertoolsSubsegment(document, functionName);
238264
};
239265

240266
/**
@@ -288,6 +314,7 @@ const getXRayTraceData = async (
288314
const traces = await retriableGetTraceDetails({
289315
traceIds,
290316
expectedSegmentsCount,
317+
functionName: resourceName,
291318
});
292319

293320
if (!traces) {
@@ -303,9 +330,15 @@ const getXRayTraceData = async (
303330
* @param options - The options to get the X-Ray trace data, including the start time, resource name, expected traces count, and expected segments count
304331
*/
305332
const getTraces = async (
306-
options: GetXRayTraceIdsOptions & Omit<GetXRayTraceDetailsOptions, 'traceIds'>
333+
options: GetXRayTraceIdsOptions &
334+
Omit<GetXRayTraceDetailsOptions, 'traceIds' | 'functionName'> & {
335+
resourceName: string;
336+
}
307337
): Promise<EnrichedXRayTraceDocumentParsed[]> => {
308-
const traces = await getXRayTraceData(options);
338+
const traces = await getXRayTraceData({
339+
...options,
340+
functionName: options.resourceName,
341+
});
309342

310343
const { resourceName } = options;
311344

0 commit comments

Comments
 (0)