55
66package io .opentelemetry .instrumentation .openai .v1_1 ;
77
8+ import static io .opentelemetry .instrumentation .api .internal .SemconvExceptionSignal .emitExceptionAsLogs ;
9+ import static io .opentelemetry .instrumentation .api .internal .SemconvExceptionSignal .emitExceptionAsSpanEvents ;
10+ import static io .opentelemetry .sdk .testing .assertj .OpenTelemetryAssertions .assertThat ;
811import static io .opentelemetry .sdk .testing .assertj .OpenTelemetryAssertions .equalTo ;
912import static io .opentelemetry .sdk .testing .assertj .OpenTelemetryAssertions .satisfies ;
13+ import static io .opentelemetry .semconv .ExceptionAttributes .EXCEPTION_MESSAGE ;
14+ import static io .opentelemetry .semconv .ExceptionAttributes .EXCEPTION_STACKTRACE ;
15+ import static io .opentelemetry .semconv .ExceptionAttributes .EXCEPTION_TYPE ;
1016import static io .opentelemetry .semconv .incubating .GenAiIncubatingAttributes .GEN_AI_OPERATION_NAME ;
1117import static io .opentelemetry .semconv .incubating .GenAiIncubatingAttributes .GEN_AI_PROVIDER_NAME ;
1218import static io .opentelemetry .semconv .incubating .GenAiIncubatingAttributes .GEN_AI_REQUEST_ENCODING_FORMATS ;
1824import static io .opentelemetry .semconv .incubating .GenAiIncubatingAttributes .GenAiProviderNameIncubatingValues .OPENAI ;
1925import static io .opentelemetry .semconv .incubating .GenAiIncubatingAttributes .GenAiTokenTypeIncubatingValues .INPUT ;
2026import static java .util .Collections .singletonList ;
27+ import static java .util .stream .Collectors .toList ;
2128import static org .assertj .core .api .Assertions .assertThat ;
2229import static org .assertj .core .api .Assertions .catchThrowable ;
2330
2835import com .openai .errors .OpenAIIoException ;
2936import com .openai .models .embeddings .CreateEmbeddingResponse ;
3037import com .openai .models .embeddings .EmbeddingCreateParams ;
38+ import io .opentelemetry .api .logs .Severity ;
3139import io .opentelemetry .api .trace .Span ;
3240import io .opentelemetry .api .trace .SpanKind ;
3341import io .opentelemetry .context .Context ;
3442import io .opentelemetry .instrumentation .testing .internal .AutoCleanupExtension ;
43+ import io .opentelemetry .sdk .logs .data .LogRecordData ;
44+ import java .util .List ;
3545import java .util .concurrent .CompletionException ;
46+ import org .awaitility .Awaitility ;
3647import org .junit .jupiter .api .Test ;
3748import org .junit .jupiter .api .extension .RegisterExtension ;
3849
@@ -239,19 +250,26 @@ void connectionError() {
239250 trace ->
240251 trace .hasSpansSatisfyingExactly (
241252 maybeWithTransportSpan (
242- span ->
243- span .hasName ("embeddings text-embedding-3-small" )
244- .hasKind (SpanKind .CLIENT )
245- .hasException (thrown )
246- .hasAttributesSatisfyingExactly (
247- equalTo (GEN_AI_PROVIDER_NAME , OPENAI ),
248- equalTo (GEN_AI_OPERATION_NAME , EMBEDDINGS ),
249- equalTo (GEN_AI_REQUEST_MODEL , MODEL ),
250- // Newer versions of the library populate base64 when unset by
251- // the user.
252- satisfies (
253- GEN_AI_REQUEST_ENCODING_FORMATS ,
254- val -> val .isIn (singletonList ("base64" ), null ))))));
253+ span -> {
254+ span .hasName ("embeddings text-embedding-3-small" )
255+ .hasKind (SpanKind .CLIENT )
256+ .hasAttributesSatisfyingExactly (
257+ equalTo (GEN_AI_PROVIDER_NAME , OPENAI ),
258+ equalTo (GEN_AI_OPERATION_NAME , EMBEDDINGS ),
259+ equalTo (GEN_AI_REQUEST_MODEL , MODEL ),
260+ // Newer versions of the library populate base64 when unset by
261+ // the user.
262+ satisfies (
263+ GEN_AI_REQUEST_ENCODING_FORMATS ,
264+ val -> val .isIn (singletonList ("base64" ), null )));
265+ if (emitExceptionAsSpanEvents ()) {
266+ span .hasException (thrown );
267+ }
268+ })));
269+
270+ if (emitExceptionAsLogs ()) {
271+ assertClientExceptionLog ();
272+ }
255273
256274 getTesting ()
257275 .waitAndAssertMetrics (
@@ -270,4 +288,23 @@ void connectionError() {
270288 equalTo (GEN_AI_OPERATION_NAME , EMBEDDINGS ),
271289 equalTo (GEN_AI_REQUEST_MODEL , MODEL )))));
272290 }
291+
292+ private void assertClientExceptionLog () {
293+ Awaitility .await ()
294+ .untilAsserted (
295+ () -> {
296+ List <LogRecordData > logs =
297+ getTesting ().logRecords ().stream ()
298+ .filter (log -> "gen_ai.client.operation.exception" .equals (log .getEventName ()))
299+ .collect (toList ());
300+ assertThat (logs ).hasSize (1 );
301+ assertThat (logs .get (0 ))
302+ .hasSeverity (Severity .WARN )
303+ .hasEventName ("gen_ai.client.operation.exception" )
304+ .hasAttributesSatisfyingExactly (
305+ satisfies (EXCEPTION_TYPE , val -> val .isNotNull ()),
306+ satisfies (EXCEPTION_MESSAGE , val -> val .isNotNull ()),
307+ satisfies (EXCEPTION_STACKTRACE , val -> val .isNotNull ()));
308+ });
309+ }
273310}
0 commit comments