14
14
15
15
package software .amazon .lambda .powertools .testutils .tracing ;
16
16
17
+ import java .time .Duration ;
17
18
import java .time .Instant ;
18
19
import java .time .temporal .ChronoUnit ;
19
20
import java .util .Arrays ;
27
28
28
29
import com .fasterxml .jackson .core .JsonProcessingException ;
29
30
import com .fasterxml .jackson .databind .DeserializationFeature ;
31
+ import com .fasterxml .jackson .databind .MapperFeature ;
30
32
import com .fasterxml .jackson .databind .ObjectMapper ;
33
+ import com .fasterxml .jackson .databind .json .JsonMapper ;
31
34
35
+ import io .github .resilience4j .retry .RetryConfig ;
32
36
import software .amazon .awssdk .http .SdkHttpClient ;
33
37
import software .amazon .awssdk .http .urlconnection .UrlConnectionHttpClient ;
34
38
import software .amazon .awssdk .regions .Region ;
46
50
* Class in charge of retrieving the actual traces of a Lambda execution on X-Ray
47
51
*/
48
52
public class TraceFetcher {
49
-
50
- private static final ObjectMapper MAPPER = new ObjectMapper ()
51
- .configure (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES , false );
53
+ private static final ObjectMapper MAPPER = JsonMapper .builder ()
54
+ .disable (MapperFeature .REQUIRE_HANDLERS_FOR_JAVA8_TIMES )
55
+ .configure (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES , false )
56
+ .build ();
52
57
private static final Logger LOG = LoggerFactory .getLogger (TraceFetcher .class );
53
58
private static final SdkHttpClient httpClient = UrlConnectionHttpClient .builder ().build ();
54
59
private static final Region region = Region .of (System .getProperty ("AWS_DEFAULT_REGION" , "eu-west-1" ));
@@ -90,7 +95,12 @@ public Trace fetchTrace() {
90
95
return getTrace (traceIds );
91
96
};
92
97
93
- return RetryUtils .withRetry (supplier , "trace-fetcher" , TraceNotFoundException .class ).get ();
98
+ RetryConfig customConfig = RetryConfig .custom ()
99
+ .maxAttempts (120 ) // 120 attempts over 10 minutes
100
+ .waitDuration (Duration .ofSeconds (5 )) // 5 seconds between attempts
101
+ .build ();
102
+
103
+ return RetryUtils .withRetry (supplier , "trace-fetcher" , customConfig , TraceNotFoundException .class ).get ();
94
104
}
95
105
96
106
/**
@@ -104,26 +114,41 @@ private Trace getTrace(List<String> traceIds) {
104
114
.traceIds (traceIds )
105
115
.build ());
106
116
if (!tracesResponse .hasTraces ()) {
107
- throw new TraceNotFoundException ("No trace found" );
117
+ throw new TraceNotFoundException (String . format ( "No trace found for traceIds %s" , traceIds ) );
108
118
}
119
+
109
120
Trace traceRes = new Trace ();
110
121
tracesResponse .traces ().forEach (trace -> {
111
122
if (trace .hasSegments ()) {
112
123
trace .segments ().forEach (segment -> {
113
124
try {
114
125
SegmentDocument document = MAPPER .readValue (segment .document (), SegmentDocument .class );
115
126
if ("AWS::Lambda::Function" .equals (document .getOrigin ()) && document .hasSubsegments ()) {
116
- getNestedSubSegments (document .getSubsegments (), traceRes ,
117
- Collections .emptyList ());
127
+ LOG .debug ("Populating subsegments for document {}" , MAPPER .writeValueAsString (document ));
128
+ getNestedSubSegments (document .getSubsegments (), traceRes , Collections .emptyList ());
129
+ // If only the default (excluded) subsegments were populated we need to keep retrying for
130
+ // our custom subsegments. They might appear later.
131
+ if (traceRes .getSubsegments ().isEmpty ()) {
132
+ throw new TraceNotFoundException (
133
+ "Found AWS::Lambda::Function SegmentDocument with no non-excluded subsegments." );
134
+ }
135
+ } else if ("AWS::Lambda::Function" .equals (document .getOrigin ())) {
136
+ LOG .debug (
137
+ "Found AWS::Lambda::Function SegmentDocument with no subsegments. Retrying {}" ,
138
+ MAPPER .writeValueAsString (document ));
139
+ throw new TraceNotFoundException (
140
+ "Found AWS::Lambda::Function SegmentDocument with no subsegments." );
118
141
}
119
-
120
142
} catch (JsonProcessingException e ) {
121
143
LOG .error ("Failed to parse segment document: " + e .getMessage ());
122
144
throw new RuntimeException (e );
123
145
}
124
146
});
147
+ } else {
148
+ throw new TraceNotFoundException (String .format ("No segments found in trace %s" , trace .id ()));
125
149
}
126
150
});
151
+
127
152
return traceRes ;
128
153
}
129
154
@@ -149,21 +174,30 @@ private void getNestedSubSegments(List<SubSegment> subsegments, Trace traceRes,
149
174
* @return a list of trace ids
150
175
*/
151
176
private List <String > getTraceIds () {
177
+ LOG .debug ("Searching for traces from {} to {} with filter: {}" , start , end , filterExpression );
152
178
GetTraceSummariesResponse traceSummaries = xray .getTraceSummaries (GetTraceSummariesRequest .builder ()
153
179
.startTime (start )
154
180
.endTime (end )
155
- .timeRangeType (TimeRangeType .EVENT )
181
+ .timeRangeType (TimeRangeType .TRACE_ID )
156
182
.sampling (false )
157
183
.filterExpression (filterExpression )
158
184
.build ());
185
+
186
+ LOG .debug ("Found {} trace summaries" ,
187
+ traceSummaries .hasTraceSummaries () ? traceSummaries .traceSummaries ().size () : 0 );
188
+
159
189
if (!traceSummaries .hasTraceSummaries ()) {
160
- throw new TraceNotFoundException ("No trace id found" );
190
+ throw new TraceNotFoundException (String .format ("No trace id found for filter '%s' between %s and %s" ,
191
+ filterExpression , start , end ));
161
192
}
162
193
List <String > traceIds = traceSummaries .traceSummaries ().stream ().map (TraceSummary ::id )
163
194
.collect (Collectors .toList ());
164
195
if (traceIds .isEmpty ()) {
165
- throw new TraceNotFoundException ("No trace id found" );
196
+ throw new TraceNotFoundException (
197
+ String .format ("Empty trace summary found for filter '%s' between %s and %s" ,
198
+ filterExpression , start , end ));
166
199
}
200
+ LOG .debug ("Found trace IDs: {}" , traceIds );
167
201
return traceIds ;
168
202
}
169
203
@@ -183,9 +217,13 @@ public TraceFetcher build() {
183
217
if (end == null ) {
184
218
end = start .plus (1 , ChronoUnit .MINUTES );
185
219
}
186
- LOG .debug ("Looking for traces from {} to {} with filter {} and excluded segments {}" , start , end ,
187
- filterExpression , excludedSegments );
188
- return new TraceFetcher (start , end , filterExpression , excludedSegments );
220
+ // Expand search window by 1 minute on each side to account for timing imprecisions
221
+ Instant expandedStart = start .minus (1 , ChronoUnit .MINUTES );
222
+ Instant expandedEnd = end .plus (1 , ChronoUnit .MINUTES );
223
+ LOG .debug (
224
+ "Looking for traces from {} to {} (expanded from {} to {}) with filter {} and excluded segments {}" ,
225
+ expandedStart , expandedEnd , start , end , filterExpression , excludedSegments );
226
+ return new TraceFetcher (expandedStart , expandedEnd , filterExpression , excludedSegments );
189
227
}
190
228
191
229
public Builder start (Instant start ) {
0 commit comments