Skip to content

Commit 09fbe2c

Browse files
committed
Add gen_ai.client.operation.exception event name to GenAI instrumentations
1 parent eccaca9 commit 09fbe2c

4 files changed

Lines changed: 107 additions & 25 deletions

File tree

instrumentation/openai/openai-java-1.1/library/build.gradle.kts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,14 @@ tasks {
1313
withType<Test>().configureEach {
1414
systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
1515
}
16+
17+
val testExceptionSignalLogs by registering(Test::class) {
18+
testClassesDirs = sourceSets.test.get().output.classesDirs
19+
classpath = sourceSets.test.get().runtimeClasspath
20+
jvmArgs("-Dotel.semconv.exception.signal.opt-in=logs")
21+
}
22+
23+
check {
24+
dependsOn(testExceptionSignalLogs)
25+
}
1626
}

instrumentation/openai/openai-java-1.1/library/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/OpenAITelemetryBuilder.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
import io.opentelemetry.api.logs.Logger;
1515
import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiAttributesExtractor;
1616
import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiClientMetrics;
17+
import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiExceptionEventExtractors;
1718
import io.opentelemetry.instrumentation.api.incubator.semconv.genai.GenAiSpanNameExtractor;
1819
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
20+
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
1921
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
22+
import io.opentelemetry.instrumentation.api.internal.Experimental;
2023

2124
/** A builder of {@link OpenAITelemetry}. */
2225
@SuppressWarnings("IdentifierName") // Want to match library's convention
@@ -47,24 +50,29 @@ public OpenAITelemetryBuilder setCaptureMessageContent(boolean captureMessageCon
4750
* Returns a new {@link OpenAITelemetry} with the settings of this {@link OpenAITelemetryBuilder}.
4851
*/
4952
public OpenAITelemetry build() {
50-
Instrumenter<ChatCompletionCreateParams, ChatCompletion> chatInstrumenter =
53+
InstrumenterBuilder<ChatCompletionCreateParams, ChatCompletion> chatBuilder =
5154
Instrumenter.<ChatCompletionCreateParams, ChatCompletion>builder(
5255
openTelemetry,
5356
INSTRUMENTATION_NAME,
5457
GenAiSpanNameExtractor.create(ChatAttributesGetter.INSTANCE))
5558
.addAttributesExtractor(GenAiAttributesExtractor.create(ChatAttributesGetter.INSTANCE))
56-
.addOperationMetrics(GenAiClientMetrics.get())
57-
.buildInstrumenter();
59+
.addOperationMetrics(GenAiClientMetrics.get());
60+
Experimental.setExceptionEventExtractor(chatBuilder, GenAiExceptionEventExtractors.client());
61+
Instrumenter<ChatCompletionCreateParams, ChatCompletion> chatInstrumenter =
62+
chatBuilder.buildInstrumenter();
5863

59-
Instrumenter<EmbeddingCreateParams, CreateEmbeddingResponse> embeddingsInstrumenter =
64+
InstrumenterBuilder<EmbeddingCreateParams, CreateEmbeddingResponse> embeddingsBuilder =
6065
Instrumenter.<EmbeddingCreateParams, CreateEmbeddingResponse>builder(
6166
openTelemetry,
6267
INSTRUMENTATION_NAME,
6368
GenAiSpanNameExtractor.create(EmbeddingAttributesGetter.INSTANCE))
6469
.addAttributesExtractor(
6570
GenAiAttributesExtractor.create(EmbeddingAttributesGetter.INSTANCE))
66-
.addOperationMetrics(GenAiClientMetrics.get())
67-
.buildInstrumenter(SpanKindExtractor.alwaysClient());
71+
.addOperationMetrics(GenAiClientMetrics.get());
72+
Experimental.setExceptionEventExtractor(
73+
embeddingsBuilder, GenAiExceptionEventExtractors.client());
74+
Instrumenter<EmbeddingCreateParams, CreateEmbeddingResponse> embeddingsInstrumenter =
75+
embeddingsBuilder.buildInstrumenter(SpanKindExtractor.alwaysClient());
6876

6977
Logger eventLogger = openTelemetry.getLogsBridge().get(INSTRUMENTATION_NAME);
7078
return new OpenAITelemetry(

instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractChatTest.java

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@
55

66
package 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;
810
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
911
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
12+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE;
13+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE;
14+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE;
1015
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_OPERATION_NAME;
1116
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_PROVIDER_NAME;
1217
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_REQUEST_FREQUENCY_PENALTY;
@@ -61,6 +66,7 @@
6166
import io.opentelemetry.api.common.AttributeKey;
6267
import io.opentelemetry.api.common.KeyValue;
6368
import io.opentelemetry.api.common.Value;
69+
import io.opentelemetry.api.logs.Severity;
6470
import io.opentelemetry.api.trace.Span;
6571
import io.opentelemetry.api.trace.SpanContext;
6672
import io.opentelemetry.context.Context;
@@ -902,7 +908,7 @@ void connectionError() {
902908
trace.hasSpansSatisfyingExactly(
903909
maybeWithTransportSpan(
904910
span ->
905-
span.hasException(thrown)
911+
span.hasException(emitExceptionAsSpanEvents() ? thrown : null)
906912
.hasAttributesSatisfyingExactly(
907913
equalTo(GEN_AI_PROVIDER_NAME, OPENAI),
908914
equalTo(GEN_AI_OPERATION_NAME, CHAT),
@@ -927,14 +933,33 @@ void connectionError() {
927933

928934
SpanContext spanCtx = getTesting().waitForTraces(1).get(0).get(0).getSpanContext();
929935

930-
getTesting()
931-
.waitAndAssertLogRecords(
932-
log ->
933-
log.hasAttributesSatisfyingExactly(
934-
equalTo(GEN_AI_PROVIDER_NAME, OPENAI),
935-
equalTo(EVENT_NAME, "gen_ai.user.message"))
936-
.hasSpanContext(spanCtx)
937-
.hasBody(Value.of(KeyValue.of("content", Value.of(TEST_CHAT_INPUT)))));
936+
if (emitExceptionAsLogs()) {
937+
getTesting()
938+
.waitAndAssertLogRecords(
939+
log ->
940+
log.hasAttributesSatisfyingExactly(
941+
equalTo(GEN_AI_PROVIDER_NAME, OPENAI),
942+
equalTo(EVENT_NAME, "gen_ai.user.message"))
943+
.hasSpanContext(spanCtx)
944+
.hasBody(Value.of(KeyValue.of("content", Value.of(TEST_CHAT_INPUT)))),
945+
log ->
946+
log.hasSpanContext(spanCtx)
947+
.hasSeverity(Severity.WARN)
948+
.hasEventName("gen_ai.client.operation.exception")
949+
.hasAttributesSatisfyingExactly(
950+
equalTo(EXCEPTION_TYPE, thrown.getClass().getName()),
951+
equalTo(EXCEPTION_MESSAGE, thrown.getMessage()),
952+
satisfies(EXCEPTION_STACKTRACE, val -> val.isNotNull())));
953+
} else {
954+
getTesting()
955+
.waitAndAssertLogRecords(
956+
log ->
957+
log.hasAttributesSatisfyingExactly(
958+
equalTo(GEN_AI_PROVIDER_NAME, OPENAI),
959+
equalTo(EVENT_NAME, "gen_ai.user.message"))
960+
.hasSpanContext(spanCtx)
961+
.hasBody(Value.of(KeyValue.of("content", Value.of(TEST_CHAT_INPUT)))));
962+
}
938963
}
939964

940965
@Test
@@ -1609,7 +1634,7 @@ void streamConnectionError() {
16091634
trace.hasSpansSatisfyingExactly(
16101635
maybeWithTransportSpan(
16111636
span ->
1612-
span.hasException(thrown)
1637+
span.hasException(emitExceptionAsSpanEvents() ? thrown : null)
16131638
.hasAttributesSatisfyingExactly(
16141639
equalTo(GEN_AI_PROVIDER_NAME, OPENAI),
16151640
equalTo(GEN_AI_OPERATION_NAME, CHAT),
@@ -1634,14 +1659,33 @@ void streamConnectionError() {
16341659

16351660
SpanContext spanCtx = getTesting().waitForTraces(1).get(0).get(0).getSpanContext();
16361661

1637-
getTesting()
1638-
.waitAndAssertLogRecords(
1639-
log ->
1640-
log.hasAttributesSatisfyingExactly(
1641-
equalTo(GEN_AI_PROVIDER_NAME, OPENAI),
1642-
equalTo(EVENT_NAME, "gen_ai.user.message"))
1643-
.hasSpanContext(spanCtx)
1644-
.hasBody(Value.of(KeyValue.of("content", Value.of(TEST_CHAT_INPUT)))));
1662+
if (emitExceptionAsLogs()) {
1663+
getTesting()
1664+
.waitAndAssertLogRecords(
1665+
log ->
1666+
log.hasAttributesSatisfyingExactly(
1667+
equalTo(GEN_AI_PROVIDER_NAME, OPENAI),
1668+
equalTo(EVENT_NAME, "gen_ai.user.message"))
1669+
.hasSpanContext(spanCtx)
1670+
.hasBody(Value.of(KeyValue.of("content", Value.of(TEST_CHAT_INPUT)))),
1671+
log ->
1672+
log.hasSpanContext(spanCtx)
1673+
.hasSeverity(Severity.WARN)
1674+
.hasEventName("gen_ai.client.operation.exception")
1675+
.hasAttributesSatisfyingExactly(
1676+
equalTo(EXCEPTION_TYPE, thrown.getClass().getName()),
1677+
equalTo(EXCEPTION_MESSAGE, thrown.getMessage()),
1678+
satisfies(EXCEPTION_STACKTRACE, val -> val.isNotNull())));
1679+
} else {
1680+
getTesting()
1681+
.waitAndAssertLogRecords(
1682+
log ->
1683+
log.hasAttributesSatisfyingExactly(
1684+
equalTo(GEN_AI_PROVIDER_NAME, OPENAI),
1685+
equalTo(EVENT_NAME, "gen_ai.user.message"))
1686+
.hasSpanContext(spanCtx)
1687+
.hasBody(Value.of(KeyValue.of("content", Value.of(TEST_CHAT_INPUT)))));
1688+
}
16451689
}
16461690

16471691
protected static ChatCompletionMessageParam createUserMessage(String content) {

instrumentation/openai/openai-java-1.1/testing/src/main/java/io/opentelemetry/instrumentation/openai/v1_1/AbstractEmbeddingsTest.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@
55

66
package 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;
810
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
911
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
12+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE;
13+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE;
14+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE;
1015
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_OPERATION_NAME;
1116
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_PROVIDER_NAME;
1217
import static io.opentelemetry.semconv.incubating.GenAiIncubatingAttributes.GEN_AI_REQUEST_ENCODING_FORMATS;
@@ -28,7 +33,9 @@
2833
import com.openai.errors.OpenAIIoException;
2934
import com.openai.models.embeddings.CreateEmbeddingResponse;
3035
import com.openai.models.embeddings.EmbeddingCreateParams;
36+
import io.opentelemetry.api.logs.Severity;
3137
import io.opentelemetry.api.trace.Span;
38+
import io.opentelemetry.api.trace.SpanContext;
3239
import io.opentelemetry.api.trace.SpanKind;
3340
import io.opentelemetry.context.Context;
3441
import java.util.concurrent.CompletionException;
@@ -241,7 +248,7 @@ void connectionError() {
241248
span ->
242249
span.hasName("embeddings text-embedding-3-small")
243250
.hasKind(SpanKind.CLIENT)
244-
.hasException(thrown)
251+
.hasException(emitExceptionAsSpanEvents() ? thrown : null)
245252
.hasAttributesSatisfyingExactly(
246253
equalTo(GEN_AI_PROVIDER_NAME, OPENAI),
247254
equalTo(GEN_AI_OPERATION_NAME, EMBEDDINGS),
@@ -256,6 +263,19 @@ void connectionError() {
256263
v ->
257264
assertThat(v)
258265
.isEqualTo(singletonList("base64"))))))));
266+
if (emitExceptionAsLogs()) {
267+
SpanContext spanCtx = getTesting().spans().get(0).getSpanContext();
268+
getTesting()
269+
.waitAndAssertLogRecords(
270+
log ->
271+
log.hasSpanContext(spanCtx)
272+
.hasSeverity(Severity.WARN)
273+
.hasEventName("gen_ai.client.operation.exception")
274+
.hasAttributesSatisfyingExactly(
275+
equalTo(EXCEPTION_TYPE, thrown.getClass().getName()),
276+
equalTo(EXCEPTION_MESSAGE, thrown.getMessage()),
277+
satisfies(EXCEPTION_STACKTRACE, val -> val.isNotNull())));
278+
}
259279

260280
getTesting()
261281
.waitAndAssertMetrics(

0 commit comments

Comments
 (0)