2222import io .opentelemetry .api .metrics .LongCounter ;
2323import io .opentelemetry .context .Context ;
2424import io .opentelemetry .sdk .common .CompletableResultCode ;
25+ import io .opentelemetry .sdk .common .InstrumentationScopeInfo ;
2526import io .opentelemetry .sdk .resources .Resource ;
2627import io .opentelemetry .sdk .trace .ReadWriteSpan ;
2728import io .opentelemetry .sdk .trace .ReadableSpan ;
29+ import io .opentelemetry .sdk .trace .data .EventData ;
2830import io .opentelemetry .sdk .trace .data .SpanData ;
31+ import io .opentelemetry .sdk .trace .internal .data .ExceptionEventData ;
32+ import java .util .ArrayList ;
33+ import java .util .Arrays ;
34+ import java .util .List ;
2935import org .junit .jupiter .api .BeforeEach ;
3036import org .junit .jupiter .api .Test ;
3137
3238/** Unit tests for {@link AwsSpanMetricsProcessor}. */
3339class AwsSpanMetricsProcessorTest {
34-
3540 // Test constants
3641 private static final boolean CONTAINS_ATTRIBUTES = true ;
3742 private static final boolean CONTAINS_NO_ATTRIBUTES = false ;
@@ -56,6 +61,32 @@ private enum ExpectedStatusMetric {
5661
5762 private AwsSpanMetricsProcessor awsSpanMetricsProcessor ;
5863
64+ static class ThrowableWithMethodGetStatusCode extends Throwable {
65+ private final int httpStatusCode ;
66+
67+ ThrowableWithMethodGetStatusCode (int httpStatusCode ) {
68+ this .httpStatusCode = httpStatusCode ;
69+ }
70+
71+ public int getStatusCode () {
72+ return this .httpStatusCode ;
73+ }
74+ }
75+
76+ static class ThrowableWithMethodStatusCode extends Throwable {
77+ private final int httpStatusCode ;
78+
79+ ThrowableWithMethodStatusCode (int httpStatusCode ) {
80+ this .httpStatusCode = httpStatusCode ;
81+ }
82+
83+ public int statusCode () {
84+ return this .httpStatusCode ;
85+ }
86+ }
87+
88+ static class ThrowableWithoutStatusCode extends Throwable {}
89+
5990 @ BeforeEach
6091 public void setUpMocks () {
6192 errorCounterMock = mock (LongCounter .class );
@@ -149,6 +180,16 @@ public void testOnEndMetricsGenerationWithLatency() {
149180 verify (latencyHistogramMock , times (1 )).record (eq (5.5 ), eq (metricAttributes ));
150181 }
151182
183+ @ Test
184+ public void testOnEndMetricsGenerationWithAwsStatusCodes () {
185+ validateMetricsGeneratedForAwsStatusCode (399L , ExpectedStatusMetric .NEITHER );
186+ validateMetricsGeneratedForAwsStatusCode (400L , ExpectedStatusMetric .ERROR );
187+ validateMetricsGeneratedForAwsStatusCode (499L , ExpectedStatusMetric .ERROR );
188+ validateMetricsGeneratedForAwsStatusCode (500L , ExpectedStatusMetric .FAULT );
189+ validateMetricsGeneratedForAwsStatusCode (599L , ExpectedStatusMetric .FAULT );
190+ validateMetricsGeneratedForAwsStatusCode (600L , ExpectedStatusMetric .NEITHER );
191+ }
192+
152193 @ Test
153194 public void testOnEndMetricsGenerationWithStatusCodes () {
154195 // Invalid HTTP status codes
@@ -192,13 +233,44 @@ private static ReadableSpan buildReadableSpanMock(Attributes spanAttributes) {
192233
193234 // Configure spanData
194235 SpanData mockSpanData = mock (SpanData .class );
236+ InstrumentationScopeInfo awsSdkScopeInfo =
237+ InstrumentationScopeInfo .builder ("aws-sdk" ).setVersion ("version" ).build ();
238+ when (mockSpanData .getInstrumentationScopeInfo ()).thenReturn (awsSdkScopeInfo );
195239 when (mockSpanData .getAttributes ()).thenReturn (spanAttributes );
196240 when (mockSpanData .getTotalAttributeCount ()).thenReturn (spanAttributes .size ());
197241 when (readableSpanMock .toSpanData ()).thenReturn (mockSpanData );
198242
199243 return readableSpanMock ;
200244 }
201245
246+ private static ReadableSpan buildReadableSpanWithThrowableMock (Throwable throwable ) {
247+ // config http status code as null
248+ Attributes spanAttributes = Attributes .of (HTTP_STATUS_CODE , null );
249+ ReadableSpan readableSpanMock = mock (ReadableSpan .class );
250+ SpanData mockSpanData = mock (SpanData .class );
251+ InstrumentationScopeInfo awsSdkScopeInfo =
252+ InstrumentationScopeInfo .builder ("aws-sdk" ).setVersion ("version" ).build ();
253+ ExceptionEventData mockEventData = mock (ExceptionEventData .class );
254+ List <EventData > events = new ArrayList <>(Arrays .asList (mockEventData ));
255+
256+ // Configure latency
257+ when (readableSpanMock .getLatencyNanos ()).thenReturn (TEST_LATENCY_NANOS );
258+
259+ // Configure attributes
260+ when (readableSpanMock .getAttribute (any ()))
261+ .thenAnswer (invocation -> spanAttributes .get (invocation .getArgument (0 )));
262+
263+ // Configure spanData
264+ when (mockSpanData .getInstrumentationScopeInfo ()).thenReturn (awsSdkScopeInfo );
265+ when (mockSpanData .getAttributes ()).thenReturn (spanAttributes );
266+ when (mockSpanData .getTotalAttributeCount ()).thenReturn (spanAttributes .size ());
267+ when (mockSpanData .getEvents ()).thenReturn (events );
268+ when (mockEventData .getException ()).thenReturn (throwable );
269+ when (readableSpanMock .toSpanData ()).thenReturn (mockSpanData );
270+
271+ return readableSpanMock ;
272+ }
273+
202274 private void configureMocksForOnEnd (ReadableSpan readableSpanMock , Attributes metricAttributes ) {
203275 // Configure generated attributes
204276 when (generatorMock .generateMetricAttributesFromSpan (
@@ -214,6 +286,35 @@ private void validateMetricsGeneratedForHttpStatusCode(
214286 configureMocksForOnEnd (readableSpanMock , metricAttributes );
215287
216288 awsSpanMetricsProcessor .onEnd (readableSpanMock );
289+ validateMetrics (metricAttributes , expectedStatusMetric );
290+ }
291+
292+ private void validateMetricsGeneratedForAwsStatusCode (
293+ Long awsStatusCode , ExpectedStatusMetric expectedStatusMetric ) {
294+ Throwable throwableWithMethodGetStatusCode =
295+ new ThrowableWithMethodGetStatusCode (awsStatusCode .intValue ());
296+ validateMetricsGeneratedByThrowable (throwableWithMethodGetStatusCode , expectedStatusMetric );
297+
298+ Throwable throwableWithMethodStatusCode =
299+ new ThrowableWithMethodGetStatusCode (awsStatusCode .intValue ());
300+ validateMetricsGeneratedByThrowable (throwableWithMethodStatusCode , expectedStatusMetric );
301+
302+ Throwable throwableWithoutStatusCode = new ThrowableWithoutStatusCode ();
303+ validateMetricsGeneratedByThrowable (throwableWithoutStatusCode , ExpectedStatusMetric .NEITHER );
304+ }
305+
306+ private void validateMetricsGeneratedByThrowable (
307+ Throwable throwable , ExpectedStatusMetric expectedStatusMetric ) {
308+ ReadableSpan readableSpanMock = buildReadableSpanWithThrowableMock (throwable );
309+ Attributes metricAttributes = buildMetricAttributes (CONTAINS_ATTRIBUTES );
310+ configureMocksForOnEnd (readableSpanMock , metricAttributes );
311+
312+ awsSpanMetricsProcessor .onEnd (readableSpanMock );
313+ validateMetrics (metricAttributes , expectedStatusMetric );
314+ }
315+
316+ private void validateMetrics (
317+ Attributes metricAttributes , ExpectedStatusMetric expectedStatusMetric ) {
217318 switch (expectedStatusMetric ) {
218319 case ERROR :
219320 verify (errorCounterMock , times (1 )).add (eq (1L ), eq (metricAttributes ));
0 commit comments