diff --git a/.github/patches/opentelemetry-java-instrumentation.patch b/.github/patches/opentelemetry-java-instrumentation.patch index 6eda0e8374..3ac952a166 100644 --- a/.github/patches/opentelemetry-java-instrumentation.patch +++ b/.github/patches/opentelemetry-java-instrumentation.patch @@ -250,10 +250,10 @@ index 0000000000..e890cb3c0f + } +} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsExperimentalAttributes.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsExperimentalAttributes.java -index 3e8fddec5c..c0c2bb7de5 100644 +index 3e8fddec5c..6c8e476c0a 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsExperimentalAttributes.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsExperimentalAttributes.java -@@ -19,5 +19,37 @@ final class AwsExperimentalAttributes { +@@ -19,5 +19,54 @@ final class AwsExperimentalAttributes { static final AttributeKey AWS_TABLE_NAME = stringKey("aws.table.name"); static final AttributeKey AWS_REQUEST_ID = stringKey("aws.requestId"); @@ -274,6 +274,23 @@ index 3e8fddec5c..c0c2bb7de5 100644 + + static final AttributeKey AWS_BEDROCK_SYSTEM = stringKey("gen_ai.system"); + ++ static final AttributeKey GEN_AI_REQUEST_MAX_TOKENS = ++ stringKey("gen_ai.request.max_tokens"); ++ ++ static final AttributeKey GEN_AI_REQUEST_TEMPERATURE = ++ stringKey("gen_ai.request.temperature"); ++ ++ static final AttributeKey GEN_AI_REQUEST_TOP_P = stringKey("gen_ai.request.top_p"); ++ ++ static final AttributeKey GEN_AI_RESPONSE_FINISH_REASONS = ++ stringKey("gen_ai.response.finish_reasons"); ++ ++ static final AttributeKey GEN_AI_USAGE_INPUT_TOKENS = ++ stringKey("gen_ai.usage.input_tokens"); ++ ++ static final AttributeKey GEN_AI_USAGE_OUTPUT_TOKENS = ++ stringKey("gen_ai.usage.output_tokens"); ++ + static final AttributeKey AWS_STATE_MACHINE_ARN = + stringKey("aws.stepfunctions.state_machine.arn"); + @@ -292,10 +309,10 @@ index 3e8fddec5c..c0c2bb7de5 100644 private AwsExperimentalAttributes() {} } diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkExperimentalAttributesExtractor.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkExperimentalAttributesExtractor.java -index 245f09a5d8..d2bdf1c987 100644 +index 245f09a5d8..a76a179748 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkExperimentalAttributesExtractor.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/AwsSdkExperimentalAttributesExtractor.java -@@ -6,11 +6,23 @@ +@@ -6,13 +6,31 @@ package io.opentelemetry.instrumentation.awssdk.v1_11; import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_AGENT; @@ -318,8 +335,16 @@ index 245f09a5d8..d2bdf1c987 100644 +import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_STEP_FUNCTIONS_ACTIVITY_ARN; import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_STREAM_NAME; import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.AWS_TABLE_NAME; ++import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.GEN_AI_REQUEST_MAX_TOKENS; ++import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.GEN_AI_REQUEST_TEMPERATURE; ++import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.GEN_AI_REQUEST_TOP_P; ++import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.GEN_AI_RESPONSE_FINISH_REASONS; ++import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.GEN_AI_USAGE_INPUT_TOKENS; ++import static io.opentelemetry.instrumentation.awssdk.v1_11.AwsExperimentalAttributes.GEN_AI_USAGE_OUTPUT_TOKENS; -@@ -21,12 +33,17 @@ import io.opentelemetry.api.common.AttributeKey; + import com.amazonaws.AmazonWebServiceResponse; + import com.amazonaws.Request; +@@ -21,12 +39,17 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; @@ -337,7 +362,7 @@ index 245f09a5d8..d2bdf1c987 100644 @Override public void onStart(AttributesBuilder attributes, Context parentContext, Request request) { -@@ -34,21 +51,29 @@ class AwsSdkExperimentalAttributesExtractor +@@ -34,21 +57,29 @@ class AwsSdkExperimentalAttributesExtractor attributes.put(AWS_ENDPOINT, request.getEndpoint().toString()); Object originalRequest = request.getOriginalRequest(); @@ -382,7 +407,7 @@ index 245f09a5d8..d2bdf1c987 100644 } } -@@ -59,12 +84,117 @@ class AwsSdkExperimentalAttributesExtractor +@@ -59,12 +90,136 @@ class AwsSdkExperimentalAttributesExtractor Request request, @Nullable Response response, @Nullable Throwable error) { @@ -446,10 +471,18 @@ index 245f09a5d8..d2bdf1c987 100644 + if (!Objects.equals(requestClassName, "InvokeModelRequest")) { + break; + } -+ attributes.put(AWS_BEDROCK_SYSTEM, "aws_bedrock"); ++ attributes.put(AWS_BEDROCK_SYSTEM, "aws.bedrock"); + Function getter = RequestAccess::getModelId; + String modelId = getter.apply(originalRequest); + attributes.put(AWS_BEDROCK_RUNTIME_MODEL_ID, modelId); ++ ++ setAttribute( ++ attributes, GEN_AI_REQUEST_MAX_TOKENS, originalRequest, RequestAccess::getMaxTokens); ++ setAttribute( ++ attributes, GEN_AI_REQUEST_TEMPERATURE, originalRequest, RequestAccess::getTemperature); ++ setAttribute(attributes, GEN_AI_REQUEST_TOP_P, originalRequest, RequestAccess::getTopP); ++ setAttribute( ++ attributes, GEN_AI_USAGE_INPUT_TOKENS, originalRequest, RequestAccess::getInputTokens); + break; + default: + break; @@ -479,6 +512,17 @@ index 245f09a5d8..d2bdf1c987 100644 + setAttribute(attributes, AWS_AGENT_ID, awsResp, RequestAccess::getAgentId); + setAttribute(attributes, AWS_KNOWLEDGE_BASE_ID, awsResp, RequestAccess::getKnowledgeBaseId); + break; ++ case BEDROCK_RUNTIME_SERVICE: ++ if (!Objects.equals(awsResp.getClass().getSimpleName(), "InvokeModelResult")) { ++ break; ++ } ++ ++ setAttribute(attributes, GEN_AI_USAGE_INPUT_TOKENS, awsResp, RequestAccess::getInputTokens); ++ setAttribute( ++ attributes, GEN_AI_USAGE_OUTPUT_TOKENS, awsResp, RequestAccess::getOutputTokens); ++ setAttribute( ++ attributes, GEN_AI_RESPONSE_FINISH_REASONS, awsResp, RequestAccess::getFinishReasons); ++ break; + default: + break; + } @@ -505,22 +549,516 @@ index 245f09a5d8..d2bdf1c987 100644 + } + } } +diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/BedrockJsonParser.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/BedrockJsonParser.java +new file mode 100644 +index 0000000000..d1acc5768a +--- /dev/null ++++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/BedrockJsonParser.java +@@ -0,0 +1,267 @@ ++/* ++ * Copyright The OpenTelemetry Authors ++ * SPDX-License-Identifier: Apache-2.0 ++ */ ++ ++package io.opentelemetry.instrumentation.awssdk.v1_11; ++ ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++ ++public class BedrockJsonParser { ++ ++ // Prevent instantiation ++ private BedrockJsonParser() { ++ throw new UnsupportedOperationException("Utility class"); ++ } ++ ++ public static LlmJson parse(String jsonString) { ++ JsonParser parser = new JsonParser(jsonString); ++ Map jsonBody = parser.parse(); ++ return new LlmJson(jsonBody); ++ } ++ ++ static class JsonParser { ++ private final String json; ++ private int position; ++ ++ public JsonParser(String json) { ++ this.json = json.trim(); ++ this.position = 0; ++ } ++ ++ private void skipWhitespace() { ++ while (position < json.length() && Character.isWhitespace(json.charAt(position))) { ++ position++; ++ } ++ } ++ ++ private char currentChar() { ++ return json.charAt(position); ++ } ++ ++ private static boolean isHexDigit(char c) { ++ return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); ++ } ++ ++ private void expect(char c) { ++ skipWhitespace(); ++ if (currentChar() != c) { ++ throw new IllegalArgumentException( ++ "Expected '" + c + "' but found '" + currentChar() + "'"); ++ } ++ position++; ++ } ++ ++ private String readString() { ++ skipWhitespace(); ++ expect('"'); // Ensure the string starts with a quote ++ StringBuilder result = new StringBuilder(); ++ while (currentChar() != '"') { ++ // Handle escape sequences ++ if (currentChar() == '\\') { ++ position++; // Move past the backslash ++ if (position >= json.length()) { ++ throw new IllegalArgumentException("Unexpected end of input in string escape sequence"); ++ } ++ char escapeChar = currentChar(); ++ switch (escapeChar) { ++ case '"': ++ case '\\': ++ case '/': ++ result.append(escapeChar); ++ break; ++ case 'b': ++ result.append('\b'); ++ break; ++ case 'f': ++ result.append('\f'); ++ break; ++ case 'n': ++ result.append('\n'); ++ break; ++ case 'r': ++ result.append('\r'); ++ break; ++ case 't': ++ result.append('\t'); ++ break; ++ case 'u': // Unicode escape sequence ++ if (position + 4 >= json.length()) { ++ throw new IllegalArgumentException("Invalid unicode escape sequence in string"); ++ } ++ char[] hexChars = new char[4]; ++ for (int i = 0; i < 4; i++) { ++ position++; // Move to the next character ++ char hexChar = json.charAt(position); ++ if (!isHexDigit(hexChar)) { ++ throw new IllegalArgumentException( ++ "Invalid hexadecimal digit in unicode escape sequence"); ++ } ++ hexChars[i] = hexChar; ++ } ++ int unicodeValue = Integer.parseInt(new String(hexChars), 16); ++ result.append((char) unicodeValue); ++ break; ++ default: ++ throw new IllegalArgumentException("Invalid escape character: \\" + escapeChar); ++ } ++ position++; ++ } else { ++ result.append(currentChar()); ++ position++; ++ } ++ } ++ position++; // Skip closing quote ++ return result.toString(); ++ } ++ ++ private Object readValue() { ++ skipWhitespace(); ++ char c = currentChar(); ++ ++ if (c == '"') { ++ return readString(); ++ } else if (Character.isDigit(c)) { ++ return readScopedNumber(); ++ } else if (c == '{') { ++ return readObject(); // JSON Objects ++ } else if (c == '[') { ++ return readArray(); // JSON Arrays ++ } else if (json.startsWith("true", position)) { ++ position += 4; ++ return true; ++ } else if (json.startsWith("false", position)) { ++ position += 5; ++ return false; ++ } else if (json.startsWith("null", position)) { ++ position += 4; ++ return null; // JSON null ++ } else { ++ throw new IllegalArgumentException("Unexpected character: " + c); ++ } ++ } ++ ++ private Number readScopedNumber() { ++ int start = position; ++ ++ // Consume digits and the optional decimal point ++ while (position < json.length() ++ && (Character.isDigit(json.charAt(position)) || json.charAt(position) == '.')) { ++ position++; ++ } ++ ++ String number = json.substring(start, position); ++ ++ if (number.contains(".")) { ++ double value = Double.parseDouble(number); ++ if (value < 0.0 || value > 1.0) { ++ throw new IllegalArgumentException( ++ "Value out of bounds for Bedrock Floating Point Attribute: " + number); ++ } ++ return value; ++ } else { ++ return Integer.parseInt(number); ++ } ++ } ++ ++ private Map readObject() { ++ Map map = new HashMap<>(); ++ expect('{'); ++ skipWhitespace(); ++ while (currentChar() != '}') { ++ String key = readString(); ++ expect(':'); ++ Object value = readValue(); ++ map.put(key, value); ++ skipWhitespace(); ++ if (currentChar() == ',') { ++ position++; ++ } ++ } ++ position++; // Skip closing brace ++ return map; ++ } ++ ++ private List readArray() { ++ List list = new ArrayList<>(); ++ expect('['); ++ skipWhitespace(); ++ while (currentChar() != ']') { ++ list.add(readValue()); ++ skipWhitespace(); ++ if (currentChar() == ',') { ++ position++; ++ } ++ } ++ position++; ++ return list; ++ } ++ ++ public Map parse() { ++ return readObject(); ++ } ++ } ++ ++ // Resolves paths in a JSON structure ++ static class JsonPathResolver { ++ ++ // Private constructor to prevent instantiation ++ private JsonPathResolver() { ++ throw new UnsupportedOperationException("Utility class"); ++ } ++ ++ public static Object resolvePath(LlmJson llmJson, String... paths) { ++ for (String path : paths) { ++ Object value = resolvePath(llmJson.getJsonBody(), path); ++ if (value != null) { ++ return value; ++ } ++ } ++ return null; ++ } ++ ++ private static Object resolvePath(Map json, String path) { ++ String[] keys = path.split("/"); ++ Object current = json; ++ ++ for (String key : keys) { ++ if (key.isEmpty()) { ++ continue; ++ } ++ ++ if (current instanceof Map) { ++ current = ((Map) current).get(key); ++ } else if (current instanceof List) { ++ try { ++ int index = Integer.parseInt(key); ++ current = ((List) current).get(index); ++ } catch (NumberFormatException | IndexOutOfBoundsException e) { ++ return null; ++ } ++ } else { ++ return null; ++ } ++ ++ if (current == null) { ++ return null; ++ } ++ } ++ return current; ++ } ++ } ++ ++ public static class LlmJson { ++ private final Map jsonBody; ++ ++ public LlmJson(Map jsonBody) { ++ this.jsonBody = jsonBody; ++ } ++ ++ public Map getJsonBody() { ++ return jsonBody; ++ } ++ } ++} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/RequestAccess.java b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/RequestAccess.java -index bb2ae9266c..36e216047f 100644 +index bb2ae9266c..8bcde62272 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/RequestAccess.java +++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v1_11/RequestAccess.java -@@ -8,6 +8,7 @@ package io.opentelemetry.instrumentation.awssdk.v1_11; +@@ -8,6 +8,12 @@ package io.opentelemetry.instrumentation.awssdk.v1_11; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.lang.reflect.Method; ++import java.nio.ByteBuffer; ++import java.nio.charset.StandardCharsets; ++import java.util.Arrays; ++import java.util.Objects; ++import java.util.stream.Stream; import javax.annotation.Nullable; final class RequestAccess { -@@ -20,36 +21,158 @@ final class RequestAccess { +@@ -20,36 +26,374 @@ final class RequestAccess { } }; ++ @Nullable ++ private static BedrockJsonParser.LlmJson parseTargetBody(ByteBuffer buffer) { ++ try { ++ byte[] bytes; ++ // Create duplicate to avoid mutating the original buffer position ++ ByteBuffer duplicate = buffer.duplicate(); ++ if (buffer.hasArray()) { ++ bytes = ++ Arrays.copyOfRange( ++ duplicate.array(), ++ duplicate.arrayOffset(), ++ duplicate.arrayOffset() + duplicate.remaining()); ++ } else { ++ bytes = new byte[buffer.remaining()]; ++ buffer.get(bytes); ++ } ++ String jsonString = new String(bytes, StandardCharsets.UTF_8); // Convert to String ++ return BedrockJsonParser.parse(jsonString); ++ } catch (RuntimeException e) { ++ return null; ++ } ++ } ++ ++ @Nullable ++ private static BedrockJsonParser.LlmJson getJsonBody(Object target) { ++ if (target == null) { ++ return null; ++ } ++ ++ RequestAccess access = REQUEST_ACCESSORS.get(target.getClass()); ++ ByteBuffer bodyBuffer = invokeOrNullGeneric(access.getBody, target, ByteBuffer.class); ++ if (bodyBuffer == null) { ++ return null; ++ } ++ ++ return parseTargetBody(bodyBuffer); ++ } ++ ++ @Nullable ++ private static String findFirstMatchingPath(BedrockJsonParser.LlmJson jsonBody, String... paths) { ++ if (jsonBody == null) { ++ return null; ++ } ++ ++ return Stream.of(paths) ++ .map(path -> BedrockJsonParser.JsonPathResolver.resolvePath(jsonBody, path)) ++ .filter(Objects::nonNull) ++ .map(Object::toString) ++ .findFirst() ++ .orElse(null); ++ } ++ ++ @Nullable ++ private static String approximateTokenCount( ++ BedrockJsonParser.LlmJson jsonBody, String... textPaths) { ++ if (jsonBody == null) { ++ return null; ++ } ++ ++ return Stream.of(textPaths) ++ .map(path -> BedrockJsonParser.JsonPathResolver.resolvePath(jsonBody, path)) ++ .filter(value -> value instanceof String) ++ .map(value -> Integer.toString((int) Math.ceil(((String) value).length() / 6.0))) ++ .findFirst() ++ .orElse(null); ++ } ++ ++ // Model -> Path Mapping: ++ // Amazon Nova -> "/inferenceConfig/max_new_tokens" ++ // Amazon Titan -> "/textGenerationConfig/maxTokenCount" ++ // Anthropic Claude -> "/max_tokens" ++ // Cohere Command -> "/max_tokens" ++ // Cohere Command R -> "/max_tokens" ++ // AI21 Jamba -> "/max_tokens" ++ // Meta Llama -> "/max_gen_len" ++ // Mistral AI -> "/max_tokens" ++ @Nullable ++ static String getMaxTokens(Object target) { ++ BedrockJsonParser.LlmJson jsonBody = getJsonBody(target); ++ return findFirstMatchingPath( ++ jsonBody, ++ "/max_tokens", ++ "/max_gen_len", ++ "/textGenerationConfig/maxTokenCount", ++ "/inferenceConfig/max_new_tokens"); ++ } ++ ++ // Model -> Path Mapping: ++ // Amazon Nova -> "/inferenceConfig/temperature" ++ // Amazon Titan -> "/textGenerationConfig/temperature" ++ // Anthropic Claude -> "/temperature" ++ // Cohere Command -> "/temperature" ++ // Cohere Command R -> "/temperature" ++ // AI21 Jamba -> "/temperature" ++ // Meta Llama -> "/temperature" ++ // Mistral AI -> "/temperature" ++ @Nullable ++ static String getTemperature(Object target) { ++ BedrockJsonParser.LlmJson jsonBody = getJsonBody(target); ++ return findFirstMatchingPath( ++ jsonBody, ++ "/temperature", ++ "/textGenerationConfig/temperature", ++ "inferenceConfig/temperature"); ++ } ++ ++ // Model -> Path Mapping: ++ // Amazon Nova -> "/inferenceConfig/top_p" ++ // Amazon Titan -> "/textGenerationConfig/topP" ++ // Anthropic Claude -> "/top_p" ++ // Cohere Command -> "/p" ++ // Cohere Command R -> "/p" ++ // AI21 Jamba -> "/top_p" ++ // Meta Llama -> "/top_p" ++ // Mistral AI -> "/top_p" ++ @Nullable ++ static String getTopP(Object target) { ++ BedrockJsonParser.LlmJson jsonBody = getJsonBody(target); ++ return findFirstMatchingPath( ++ jsonBody, "/top_p", "/p", "/textGenerationConfig/topP", "/inferenceConfig/top_p"); ++ } ++ ++ // Model -> Path Mapping: ++ // Amazon Nova -> "/usage/inputTokens" ++ // Amazon Titan -> "/inputTextTokenCount" ++ // Anthropic Claude -> "/usage/input_tokens" ++ // Cohere Command -> "/prompt" ++ // Cohere Command R -> "/message" ++ // AI21 Jamba -> "/usage/prompt_tokens" ++ // Meta Llama -> "/prompt_token_count" ++ // Mistral AI -> "/prompt" ++ @Nullable ++ static String getInputTokens(Object target) { ++ BedrockJsonParser.LlmJson jsonBody = getJsonBody(target); ++ if (jsonBody == null) { ++ return null; ++ } ++ ++ // Try direct token counts first ++ String directCount = ++ findFirstMatchingPath( ++ jsonBody, ++ "/inputTextTokenCount", ++ "/prompt_token_count", ++ "/usage/input_tokens", ++ "/usage/prompt_tokens", ++ "/usage/inputTokens"); ++ ++ if (directCount != null && !directCount.equals("null")) { ++ return directCount; ++ } ++ ++ // Fall back to token approximation ++ return approximateTokenCount(jsonBody, "/prompt", "/message"); ++ } ++ ++ // Model -> Path Mapping: ++ // Amazon Nova -> "/usage/outputTokens" ++ // Amazon Titan -> "/results/0/tokenCount" ++ // Anthropic Claude -> "/usage/output_tokens" ++ // Cohere Command -> "/generations/0/text" ++ // Cohere Command R -> "/text" ++ // AI21 Jamba -> "/usage/completion_tokens" ++ // Meta Llama -> "/generation_token_count" ++ // Mistral AI -> "/outputs/0/text" ++ @Nullable ++ static String getOutputTokens(Object target) { ++ BedrockJsonParser.LlmJson jsonBody = getJsonBody(target); ++ if (jsonBody == null) { ++ return null; ++ } ++ ++ // Try direct token counts first ++ String directCount = ++ findFirstMatchingPath( ++ jsonBody, ++ "/generation_token_count", ++ "/results/0/tokenCount", ++ "/usage/output_tokens", ++ "/usage/completion_tokens", ++ "/usage/outputTokens"); ++ ++ if (directCount != null && !directCount.equals("null")) { ++ return directCount; ++ } ++ ++ // Fall back to token approximation ++ return approximateTokenCount(jsonBody, "/text", "/outputs/0/text"); ++ } ++ ++ // Model -> Path Mapping: ++ // Amazon Nova -> "/stopReason" ++ // Amazon Titan -> "/results/0/completionReason" ++ // Anthropic Claude -> "/stop_reason" ++ // Cohere Command -> "/generations/0/finish_reason" ++ // Cohere Command R -> "/finish_reason" ++ // AI21 Jamba -> "/choices/0/finish_reason" ++ // Meta Llama -> "/stop_reason" ++ // Mistral AI -> "/outputs/0/stop_reason" ++ @Nullable ++ static String getFinishReasons(Object target) { ++ BedrockJsonParser.LlmJson jsonBody = getJsonBody(target); ++ String finishReason = ++ findFirstMatchingPath( ++ jsonBody, ++ "/stopReason", ++ "/finish_reason", ++ "/stop_reason", ++ "/results/0/completionReason", ++ "/generations/0/finish_reason", ++ "/choices/0/finish_reason", ++ "/outputs/0/stop_reason"); ++ ++ return finishReason != null ? "[" + finishReason + "]" : null; ++ } ++ + @Nullable + static String getLambdaName(Object request) { + if (request == null) { @@ -676,7 +1214,25 @@ index bb2ae9266c..36e216047f 100644 @Nullable private static String invokeOrNull(@Nullable MethodHandle method, Object obj) { if (method == null) { -@@ -67,6 +190,17 @@ final class RequestAccess { +@@ -62,27 +406,82 @@ final class RequestAccess { + } + } + ++ @Nullable ++ private static T invokeOrNullGeneric( ++ @Nullable MethodHandle method, Object obj, Class returnType) { ++ if (method == null) { ++ return null; ++ } ++ try { ++ return returnType.cast(method.invoke(obj)); ++ } catch (Throwable e) { ++ return null; ++ } ++ } ++ + @Nullable private final MethodHandle getBucketName; + @Nullable private final MethodHandle getQueueUrl; @Nullable private final MethodHandle getQueueName; @Nullable private final MethodHandle getStreamName; @Nullable private final MethodHandle getTableName; @@ -685,6 +1241,7 @@ index bb2ae9266c..36e216047f 100644 + @Nullable private final MethodHandle getDataSourceId; + @Nullable private final MethodHandle getGuardrailId; + @Nullable private final MethodHandle getModelId; ++ @Nullable private final MethodHandle getBody; + @Nullable private final MethodHandle getStateMachineArn; + @Nullable private final MethodHandle getStepFunctionsActivityArn; + @Nullable private final MethodHandle getSnsTopicArn; @@ -693,26 +1250,39 @@ index bb2ae9266c..36e216047f 100644 + @Nullable private final MethodHandle getLambdaResourceId; private RequestAccess(Class clz) { - getBucketName = findAccessorOrNull(clz, "getBucketName"); -@@ -74,6 +208,17 @@ final class RequestAccess { - getQueueName = findAccessorOrNull(clz, "getQueueName"); - getStreamName = findAccessorOrNull(clz, "getStreamName"); - getTableName = findAccessorOrNull(clz, "getTableName"); -+ getAgentId = findAccessorOrNull(clz, "getAgentId"); -+ getKnowledgeBaseId = findAccessorOrNull(clz, "getKnowledgeBaseId"); -+ getDataSourceId = findAccessorOrNull(clz, "getDataSourceId"); -+ getGuardrailId = findAccessorOrNull(clz, "getGuardrailId"); -+ getModelId = findAccessorOrNull(clz, "getModelId"); -+ getStateMachineArn = findAccessorOrNull(clz, "getStateMachineArn"); -+ getStepFunctionsActivityArn = findAccessorOrNull(clz, "getActivityArn"); -+ getSnsTopicArn = findAccessorOrNull(clz, "getTopicArn"); -+ getSecretArn = findAccessorOrNull(clz, "getARN"); -+ getLambdaName = findAccessorOrNull(clz, "getFunctionName"); -+ getLambdaResourceId = findAccessorOrNull(clz, "getUUID"); +- getBucketName = findAccessorOrNull(clz, "getBucketName"); +- getQueueUrl = findAccessorOrNull(clz, "getQueueUrl"); +- getQueueName = findAccessorOrNull(clz, "getQueueName"); +- getStreamName = findAccessorOrNull(clz, "getStreamName"); +- getTableName = findAccessorOrNull(clz, "getTableName"); ++ getBucketName = findAccessorOrNull(clz, "getBucketName", String.class); ++ getQueueUrl = findAccessorOrNull(clz, "getQueueUrl", String.class); ++ getQueueName = findAccessorOrNull(clz, "getQueueName", String.class); ++ getStreamName = findAccessorOrNull(clz, "getStreamName", String.class); ++ getTableName = findAccessorOrNull(clz, "getTableName", String.class); ++ getAgentId = findAccessorOrNull(clz, "getAgentId", String.class); ++ getKnowledgeBaseId = findAccessorOrNull(clz, "getKnowledgeBaseId", String.class); ++ getDataSourceId = findAccessorOrNull(clz, "getDataSourceId", String.class); ++ getGuardrailId = findAccessorOrNull(clz, "getGuardrailId", String.class); ++ getModelId = findAccessorOrNull(clz, "getModelId", String.class); ++ getBody = findAccessorOrNull(clz, "getBody", ByteBuffer.class); ++ getStateMachineArn = findAccessorOrNull(clz, "getStateMachineArn", String.class); ++ getStepFunctionsActivityArn = findAccessorOrNull(clz, "getActivityArn", String.class); ++ getSnsTopicArn = findAccessorOrNull(clz, "getTopicArn", String.class); ++ getSecretArn = findAccessorOrNull(clz, "getARN", String.class); ++ getLambdaName = findAccessorOrNull(clz, "getFunctionName", String.class); ++ getLambdaResourceId = findAccessorOrNull(clz, "getUUID", String.class); } @Nullable -@@ -85,4 +230,21 @@ final class RequestAccess { +- private static MethodHandle findAccessorOrNull(Class clz, String methodName) { ++ private static MethodHandle findAccessorOrNull( ++ Class clz, String methodName, Class returnType) { + try { + return MethodHandles.publicLookup() +- .findVirtual(clz, methodName, MethodType.methodType(String.class)); ++ .findVirtual(clz, methodName, MethodType.methodType(returnType)); + } catch (Throwable t) { return null; } } @@ -734,6 +1304,119 @@ index bb2ae9266c..36e216047f 100644 + return (current instanceof String) ? (String) current : null; + } } +diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/BedrockJsonParserTest.groovy b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/BedrockJsonParserTest.groovy +new file mode 100644 +index 0000000000..03563b1d5b +--- /dev/null ++++ b/instrumentation/aws-sdk/aws-sdk-1.11/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/BedrockJsonParserTest.groovy +@@ -0,0 +1,107 @@ ++/* ++ * Copyright The OpenTelemetry Authors ++ * SPDX-License-Identifier: Apache-2.0 ++ */ ++ ++package io.opentelemetry.instrumentation.awssdk.v1_11 ++ ++import spock.lang.Specification ++ ++class BedrockJsonParserTest extends Specification { ++ def "should parse simple JSON object"() { ++ given: ++ String json = '{"key":"value"}' ++ ++ when: ++ def parsedJson = BedrockJsonParser.parse(json) ++ ++ then: ++ parsedJson.getJsonBody() == [key: "value"] ++ } ++ ++ def "should parse nested JSON object"() { ++ given: ++ String json = '{"parent":{"child":"value"}}' ++ ++ when: ++ def parsedJson = BedrockJsonParser.parse(json) ++ ++ then: ++ def parent = parsedJson.getJsonBody().get("parent") ++ parent instanceof Map ++ parent["child"] == "value" ++ } ++ ++ def "should parse JSON array"() { ++ given: ++ String json = '{"array":[1, "two", 1.0]}' ++ ++ when: ++ def parsedJson = BedrockJsonParser.parse(json) ++ ++ then: ++ def array = parsedJson.getJsonBody().get("array") ++ array instanceof List ++ array == [1, "two", 1.0] ++ } ++ ++ def "should parse escape sequences"() { ++ given: ++ String json = '{"escaped":"Line1\\nLine2\\tTabbed\\\"Quoted\\\"\\bBackspace\\fFormfeed\\rCarriageReturn\\\\Backslash\\/Slash\\u0041"}' ++ ++ when: ++ def parsedJson = BedrockJsonParser.parse(json) ++ ++ then: ++ parsedJson.getJsonBody().get("escaped") == ++ "Line1\nLine2\tTabbed\"Quoted\"\bBackspace\fFormfeed\rCarriageReturn\\Backslash/SlashA" ++ } ++ ++ def "should throw exception for malformed JSON"() { ++ given: ++ String malformedJson = '{"key":value}' ++ ++ when: ++ BedrockJsonParser.parse(malformedJson) ++ ++ then: ++ def ex = thrown(IllegalArgumentException) ++ ex.message.contains("Unexpected character") ++ } ++ ++ def "should resolve path in JSON object"() { ++ given: ++ String json = '{"parent":{"child":{"key":"value"}}}' ++ ++ when: ++ def parsedJson = BedrockJsonParser.parse(json) ++ def resolvedValue = BedrockJsonParser.JsonPathResolver.resolvePath(parsedJson, "/parent/child/key") ++ ++ then: ++ resolvedValue == "value" ++ } ++ ++ def "should resolve path in JSON array"() { ++ given: ++ String json = '{"array":[{"key":"value1"}, {"key":"value2"}]}' ++ ++ when: ++ def parsedJson = BedrockJsonParser.parse(json) ++ def resolvedValue = BedrockJsonParser.JsonPathResolver.resolvePath(parsedJson, "/array/1/key") ++ ++ then: ++ resolvedValue == "value2" ++ } ++ ++ def "should return null for invalid path resolution"() { ++ given: ++ String json = '{"parent":{"child":{"key":"value"}}}' ++ ++ when: ++ def parsedJson = BedrockJsonParser.parse(json) ++ def resolvedValue = BedrockJsonParser.JsonPathResolver.resolvePath(parsedJson, "/invalid/path") ++ ++ then: ++ resolvedValue == null ++ } ++} diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/testing/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-1.11/testing/build.gradle.kts index 548631e9f1..51483839a7 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/testing/build.gradle.kts @@ -754,7 +1437,7 @@ index 548631e9f1..51483839a7 100644 // needed for SQS - using emq directly as localstack references emq v0.15.7 ie WITHOUT AWS trace header propagation implementation("org.elasticmq:elasticmq-rest-sqs_2.12:1.0.0") diff --git a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractAws1ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractAws1ClientTest.groovy -index 95e6ed8985..48fb10624e 100644 +index 95e6ed8985..1c83fa3f9b 100644 --- a/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractAws1ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-1.11/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v1_11/AbstractAws1ClientTest.groovy @@ -27,6 +27,24 @@ import com.amazonaws.services.rds.AmazonRDSClientBuilder @@ -790,7 +1473,7 @@ index 95e6ed8985..48fb10624e 100644 import static io.opentelemetry.api.trace.SpanKind.CLIENT import static io.opentelemetry.api.trace.SpanKind.PRODUCER -@@ -156,6 +175,85 @@ abstract class AbstractAws1ClientTest extends InstrumentationSpecification { +@@ -156,6 +175,296 @@ abstract class AbstractAws1ClientTest extends InstrumentationSpecification { """ @@ -826,14 +1509,225 @@ index 95e6ed8985..48fb10624e 100644 + "AWSBedrockAgent" | "GetAgent" | "GET" | "/" | AWSBedrockAgentClientBuilder.standard() | { c -> c.getAgent(new GetAgentRequest().withAgentId("agentId")) } | ["aws.bedrock.agent.id": "agentId"] | "" + "AWSBedrockAgent" | "GetKnowledgeBase" | "GET" | "/" | AWSBedrockAgentClientBuilder.standard() | { c -> c.getKnowledgeBase(new GetKnowledgeBaseRequest().withKnowledgeBaseId("knowledgeBaseId")) } | ["aws.bedrock.knowledge_base.id": "knowledgeBaseId"] | "" + "AWSBedrockAgent" | "GetDataSource" | "GET" | "/" | AWSBedrockAgentClientBuilder.standard() | { c -> c.getDataSource(new GetDataSourceRequest().withDataSourceId("datasourceId").withKnowledgeBaseId("knowledgeBaseId")) } | ["aws.bedrock.data_source.id": "datasourceId"] | "" -+ "BedrockRuntime" | "InvokeModel" | "POST" | "/" | AmazonBedrockRuntimeClientBuilder.standard() | -+ { c -> c.invokeModel(new InvokeModelRequest().withModelId("anthropic.claude-v2").withBody(StandardCharsets.UTF_8.encode("{\"prompt\":\"Hello, world!\",\"temperature\":0.7,\"top_p\":0.9,\"max_tokens_to_sample\":100}\n"))) } | ["gen_ai.request.model": "anthropic.claude-v2", "gen_ai.system": "aws_bedrock"] | """ ++ "BedrockRuntime" | "InvokeModel" | "POST" | "/" | ++ AmazonBedrockRuntimeClientBuilder.standard() | ++ { c -> ++ c.invokeModel( ++ new InvokeModelRequest() ++ .withModelId("ai21.jamba-1-5-mini-v1:0") ++ .withBody(StandardCharsets.UTF_8.encode(''' ++ { ++ "messages": [{ ++ "role": "user", ++ "message": "Which LLM are you?" ++ }], ++ "max_tokens": 1000, ++ "top_p": 0.8, ++ "temperature": 0.7 ++ } ++ ''')) ++ ) ++ } | ++ [ ++ "gen_ai.request.model": "ai21.jamba-1-5-mini-v1:0", ++ "gen_ai.system": "aws.bedrock", ++ "gen_ai.request.max_tokens": "1000", ++ "gen_ai.request.temperature": "0.7", ++ "gen_ai.request.top_p": "0.8", ++ "gen_ai.response.finish_reasons": "[stop]", ++ "gen_ai.usage.input_tokens": "5", ++ "gen_ai.usage.output_tokens": "42" ++ ] | ++ ''' ++ { ++ "choices": [{ ++ "finish_reason": "stop" ++ }], ++ "usage": { ++ "prompt_tokens": 5, ++ "completion_tokens": 42 ++ } ++ } ++ ''' ++ "BedrockRuntime" | "InvokeModel" | "POST" | "/" | ++ AmazonBedrockRuntimeClientBuilder.standard() | ++ { c -> ++ c.invokeModel( ++ new InvokeModelRequest() ++ .withModelId("amazon.titan-text-premier-v1:0") ++ .withBody(StandardCharsets.UTF_8.encode(''' ++ { ++ "inputText": "Hello, world!", ++ "textGenerationConfig": { ++ "temperature": 0.7, ++ "topP": 0.9, ++ "maxTokenCount": 100, ++ "stopSequences": ["END"] ++ } ++ } ++ ''')) ++ ) ++ } | ++ [ ++ "gen_ai.request.model": "amazon.titan-text-premier-v1:0", ++ "gen_ai.system": "aws.bedrock", ++ "gen_ai.request.max_tokens": "100", ++ "gen_ai.request.temperature": "0.7", ++ "gen_ai.request.top_p": "0.9", ++ "gen_ai.response.finish_reasons": "[stop]", ++ "gen_ai.usage.input_tokens": "5", ++ "gen_ai.usage.output_tokens": "42" ++ ] | ++ ''' ++ { ++ "inputTextTokenCount": 5, ++ "results": [ + { -+ "completion": " Here is a simple explanation of black ", -+ "stop_reason": "length", -+ "stop": "holes" ++ "tokenCount": 42, ++ "outputText": "Hi! I'm Titan, an AI assistant. How can I help you today?", ++ "completionReason": "stop" + } -+ """ ++ ] ++ } ++ ''' ++ "BedrockRuntime" | "InvokeModel" | "POST" | "/" | ++ AmazonBedrockRuntimeClientBuilder.standard() | ++ { c -> ++ c.invokeModel( ++ new InvokeModelRequest() ++ .withModelId("anthropic.claude-3-5-sonnet-20241022-v2:0") ++ .withBody(StandardCharsets.UTF_8.encode(''' ++ { ++ "anthropic_version": "bedrock-2023-05-31", ++ "messages": [{ ++ "role": "user", ++ "content": "Hello, world" ++ }], ++ "max_tokens": 100, ++ "temperature": 0.7, ++ "top_p": 0.9 ++ } ++ ''')) ++ ) ++ } | ++ [ ++ "gen_ai.request.model": "anthropic.claude-3-5-sonnet-20241022-v2:0", ++ "gen_ai.system": "aws.bedrock", ++ "gen_ai.request.max_tokens": "100", ++ "gen_ai.request.temperature": "0.7", ++ "gen_ai.request.top_p": "0.9", ++ "gen_ai.response.finish_reasons": "[end_turn]", ++ "gen_ai.usage.input_tokens": "2095", ++ "gen_ai.usage.output_tokens": "503" ++ ] | ++ ''' ++ { ++ "stop_reason": "end_turn", ++ "usage": { ++ "input_tokens": 2095, ++ "output_tokens": 503 ++ } ++ } ++ ''' ++ "BedrockRuntime" | "InvokeModel" | "POST" | "/" | ++ AmazonBedrockRuntimeClientBuilder.standard() | ++ { c -> ++ c.invokeModel( ++ new InvokeModelRequest() ++ .withModelId("meta.llama3-70b-instruct-v1:0") ++ .withBody(StandardCharsets.UTF_8.encode(''' ++ { ++ "prompt": "<|begin_of_text|><|start_header_id|>user<|end_header_id|>\\\\nDescribe the purpose of a 'hello world' program in one line. <|eot_id|>\\\\n<|start_header_id|>assistant<|end_header_id|>\\\\n", ++ "max_gen_len": 128, ++ "temperature": 0.1, ++ "top_p": 0.9 ++ } ++ ''')) ++ ) ++ } | ++ [ ++ "gen_ai.request.model": "meta.llama3-70b-instruct-v1:0", ++ "gen_ai.system": "aws.bedrock", ++ "gen_ai.request.max_tokens": "128", ++ "gen_ai.request.temperature": "0.1", ++ "gen_ai.request.top_p": "0.9", ++ "gen_ai.response.finish_reasons": "[stop]", ++ "gen_ai.usage.input_tokens": "2095", ++ "gen_ai.usage.output_tokens": "503" ++ ] | ++ ''' ++ { ++ "prompt_token_count": 2095, ++ "generation_token_count": 503, ++ "stop_reason": "stop" ++ } ++ ''' ++ "BedrockRuntime" | "InvokeModel" | "POST" | "/" | ++ AmazonBedrockRuntimeClientBuilder.standard() | ++ { c -> ++ c.invokeModel( ++ new InvokeModelRequest() ++ .withModelId("cohere.command-r-v1:0") ++ .withBody(StandardCharsets.UTF_8.encode(''' ++ { ++ "message": "Convince me to write a LISP interpreter in one line.", ++ "temperature": 0.8, ++ "max_tokens": 4096, ++ "p": 0.45 ++ } ++ ''')) ++ ) ++ } | ++ [ ++ "gen_ai.request.model": "cohere.command-r-v1:0", ++ "gen_ai.system": "aws.bedrock", ++ "gen_ai.request.max_tokens": "4096", ++ "gen_ai.request.temperature": "0.8", ++ "gen_ai.request.top_p": "0.45", ++ "gen_ai.response.finish_reasons": "[COMPLETE]", ++ "gen_ai.usage.input_tokens": "9", ++ "gen_ai.usage.output_tokens": "2" ++ ] | ++ ''' ++ { ++ "text": "test-output", ++ "finish_reason": "COMPLETE" ++ } ++ ''' ++ "BedrockRuntime" | "InvokeModel" | "POST" | "/" | ++ AmazonBedrockRuntimeClientBuilder.standard() | ++ { c -> ++ c.invokeModel( ++ new InvokeModelRequest() ++ .withModelId("mistral.mistral-large-2402-v1:0") ++ .withBody(StandardCharsets.UTF_8.encode(''' ++ { ++ "prompt": "[INST] Describe the difference between a compiler and interpreter in one line. [/INST]\\\\n", ++ "max_tokens": 4096, ++ "temperature": 0.75, ++ "top_p": 0.25 ++ } ++ ''')) ++ ) ++ } | ++ [ ++ "gen_ai.request.model": "mistral.mistral-large-2402-v1:0", ++ "gen_ai.system": "aws.bedrock", ++ "gen_ai.request.max_tokens": "4096", ++ "gen_ai.request.temperature": "0.75", ++ "gen_ai.request.top_p": "0.25", ++ "gen_ai.response.finish_reasons": "[stop]", ++ "gen_ai.usage.input_tokens": "16", ++ "gen_ai.usage.output_tokens": "2" ++ ] | ++ ''' ++ { ++ "outputs": [{ ++ "text": "test-output", ++ "stop_reason": "stop" ++ }] ++ } ++ ''' + "AWSStepFunctions" | "DescribeStateMachine" | "POST" | "/" | AWSStepFunctionsClientBuilder.standard() + | { c -> c.describeStateMachine(new DescribeStateMachineRequest().withStateMachineArn("stateMachineArn")) } + | ["aws.stepfunctions.state_machine.arn": "stateMachineArn"] @@ -920,10 +1814,10 @@ index 862df156ae..2ed33d6f03 100644 testing { diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsExperimentalAttributes.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsExperimentalAttributes.java new file mode 100644 -index 0000000000..e1cb180d75 +index 0000000000..9e9f9cf59f --- /dev/null +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsExperimentalAttributes.java -@@ -0,0 +1,47 @@ +@@ -0,0 +1,73 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 @@ -952,6 +1846,23 @@ index 0000000000..e1cb180d75 + static final AttributeKey GEN_AI_MODEL = stringKey("gen_ai.request.model"); + static final AttributeKey GEN_AI_SYSTEM = stringKey("gen_ai.system"); + ++ static final AttributeKey GEN_AI_REQUEST_MAX_TOKENS = ++ stringKey("gen_ai.request.max_tokens"); ++ ++ static final AttributeKey GEN_AI_REQUEST_TEMPERATURE = ++ stringKey("gen_ai.request.temperature"); ++ ++ static final AttributeKey GEN_AI_REQUEST_TOP_P = stringKey("gen_ai.request.top_p"); ++ ++ static final AttributeKey GEN_AI_RESPONSE_FINISH_REASONS = ++ stringKey("gen_ai.response.finish_reasons"); ++ ++ static final AttributeKey GEN_AI_USAGE_INPUT_TOKENS = ++ stringKey("gen_ai.usage.input_tokens"); ++ ++ static final AttributeKey GEN_AI_USAGE_OUTPUT_TOKENS = ++ stringKey("gen_ai.usage.output_tokens"); ++ + static final AttributeKey AWS_STATE_MACHINE_ARN = + stringKey("aws.stepfunctions.state_machine.arn"); + @@ -969,6 +1880,15 @@ index 0000000000..e1cb180d75 + static final AttributeKey AWS_LAMBDA_RESOURCE_ID = + stringKey("aws.lambda.resource_mapping.id"); + ++ static boolean isGenAiAttribute(String attributeKey) { ++ return attributeKey.equals(GEN_AI_REQUEST_MAX_TOKENS.getKey()) ++ || attributeKey.equals(GEN_AI_REQUEST_TEMPERATURE.getKey()) ++ || attributeKey.equals(GEN_AI_REQUEST_TOP_P.getKey()) ++ || attributeKey.equals(GEN_AI_RESPONSE_FINISH_REASONS.getKey()) ++ || attributeKey.equals(GEN_AI_USAGE_INPUT_TOKENS.getKey()) ++ || attributeKey.equals(GEN_AI_USAGE_OUTPUT_TOKENS.getKey()); ++ } ++ + private AwsExperimentalAttributes() {} +} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkRequest.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkRequest.java @@ -1052,10 +1972,10 @@ index 54253d0f7b..2374bd4a52 100644 BatchGetItem( DYNAMODB, diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkRequestType.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkRequestType.java -index 9062f2aa17..1c34035588 100644 +index 9062f2aa17..aa77d8d227 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkRequestType.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/AwsSdkRequestType.java -@@ -5,17 +5,69 @@ +@@ -5,17 +5,84 @@ package io.opentelemetry.instrumentation.awssdk.v2_2; @@ -1077,6 +1997,12 @@ index 9062f2aa17..1c34035588 100644 +import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.AWS_STREAM_NAME; +import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.AWS_TABLE_NAME; +import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.GEN_AI_MODEL; ++import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.GEN_AI_REQUEST_MAX_TOKENS; ++import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.GEN_AI_REQUEST_TEMPERATURE; ++import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.GEN_AI_REQUEST_TOP_P; ++import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.GEN_AI_RESPONSE_FINISH_REASONS; ++import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.GEN_AI_USAGE_INPUT_TOKENS; ++import static io.opentelemetry.instrumentation.awssdk.v2_2.AwsExperimentalAttributes.GEN_AI_USAGE_OUTPUT_TOKENS; import static io.opentelemetry.instrumentation.awssdk.v2_2.FieldMapping.request; +import static io.opentelemetry.instrumentation.awssdk.v2_2.FieldMapping.response; @@ -1113,7 +2039,16 @@ index 9062f2aa17..1c34035588 100644 + BEDROCKKNOWLEDGEBASEOPERATION( + request(AWS_KNOWLEDGE_BASE_ID.getKey(), "knowledgeBaseId"), + response(AWS_KNOWLEDGE_BASE_ID.getKey(), "knowledgeBaseId")), -+ BEDROCKRUNTIME(request(GEN_AI_MODEL.getKey(), "modelId")), ++ BEDROCKRUNTIME( ++ request(GEN_AI_MODEL.getKey(), "modelId"), ++ request(GEN_AI_REQUEST_MAX_TOKENS.getKey(), "body"), ++ request(GEN_AI_REQUEST_TEMPERATURE.getKey(), "body"), ++ request(GEN_AI_REQUEST_TOP_P.getKey(), "body"), ++ request(GEN_AI_USAGE_INPUT_TOKENS.getKey(), "body"), ++ response(GEN_AI_RESPONSE_FINISH_REASONS.getKey(), "body"), ++ response(GEN_AI_USAGE_INPUT_TOKENS.getKey(), "body"), ++ response(GEN_AI_USAGE_OUTPUT_TOKENS.getKey(), "body")), ++ + STEPFUNCTION( + request(AWS_STATE_MACHINE_ARN.getKey(), "stateMachineArn"), + request(AWS_STEP_FUNCTIONS_ACTIVITY_ARN.getKey(), "activityArn")), @@ -1129,8 +2064,533 @@ index 9062f2aa17..1c34035588 100644 // Wrapping in unmodifiableMap @SuppressWarnings("ImmutableEnumChecker") +diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/BedrockJsonParser.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/BedrockJsonParser.java +new file mode 100644 +index 0000000000..bf14047fb0 +--- /dev/null ++++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/BedrockJsonParser.java +@@ -0,0 +1,267 @@ ++/* ++ * Copyright The OpenTelemetry Authors ++ * SPDX-License-Identifier: Apache-2.0 ++ */ ++ ++package io.opentelemetry.instrumentation.awssdk.v2_2; ++ ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++ ++public class BedrockJsonParser { ++ ++ // Prevent instantiation ++ private BedrockJsonParser() { ++ throw new UnsupportedOperationException("Utility class"); ++ } ++ ++ public static LlmJson parse(String jsonString) { ++ JsonParser parser = new JsonParser(jsonString); ++ Map jsonBody = parser.parse(); ++ return new LlmJson(jsonBody); ++ } ++ ++ static class JsonParser { ++ private final String json; ++ private int position; ++ ++ public JsonParser(String json) { ++ this.json = json.trim(); ++ this.position = 0; ++ } ++ ++ private void skipWhitespace() { ++ while (position < json.length() && Character.isWhitespace(json.charAt(position))) { ++ position++; ++ } ++ } ++ ++ private char currentChar() { ++ return json.charAt(position); ++ } ++ ++ private static boolean isHexDigit(char c) { ++ return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); ++ } ++ ++ private void expect(char c) { ++ skipWhitespace(); ++ if (currentChar() != c) { ++ throw new IllegalArgumentException( ++ "Expected '" + c + "' but found '" + currentChar() + "'"); ++ } ++ position++; ++ } ++ ++ private String readString() { ++ skipWhitespace(); ++ expect('"'); // Ensure the string starts with a quote ++ StringBuilder result = new StringBuilder(); ++ while (currentChar() != '"') { ++ // Handle escape sequences ++ if (currentChar() == '\\') { ++ position++; // Move past the backslash ++ if (position >= json.length()) { ++ throw new IllegalArgumentException("Unexpected end of input in string escape sequence"); ++ } ++ char escapeChar = currentChar(); ++ switch (escapeChar) { ++ case '"': ++ case '\\': ++ case '/': ++ result.append(escapeChar); ++ break; ++ case 'b': ++ result.append('\b'); ++ break; ++ case 'f': ++ result.append('\f'); ++ break; ++ case 'n': ++ result.append('\n'); ++ break; ++ case 'r': ++ result.append('\r'); ++ break; ++ case 't': ++ result.append('\t'); ++ break; ++ case 'u': // Unicode escape sequence ++ if (position + 4 >= json.length()) { ++ throw new IllegalArgumentException("Invalid unicode escape sequence in string"); ++ } ++ char[] hexChars = new char[4]; ++ for (int i = 0; i < 4; i++) { ++ position++; // Move to the next character ++ char hexChar = json.charAt(position); ++ if (!isHexDigit(hexChar)) { ++ throw new IllegalArgumentException( ++ "Invalid hexadecimal digit in unicode escape sequence"); ++ } ++ hexChars[i] = hexChar; ++ } ++ int unicodeValue = Integer.parseInt(new String(hexChars), 16); ++ result.append((char) unicodeValue); ++ break; ++ default: ++ throw new IllegalArgumentException("Invalid escape character: \\" + escapeChar); ++ } ++ position++; ++ } else { ++ result.append(currentChar()); ++ position++; ++ } ++ } ++ position++; // Skip closing quote ++ return result.toString(); ++ } ++ ++ private Object readValue() { ++ skipWhitespace(); ++ char c = currentChar(); ++ ++ if (c == '"') { ++ return readString(); ++ } else if (Character.isDigit(c)) { ++ return readScopedNumber(); ++ } else if (c == '{') { ++ return readObject(); // JSON Objects ++ } else if (c == '[') { ++ return readArray(); // JSON Arrays ++ } else if (json.startsWith("true", position)) { ++ position += 4; ++ return true; ++ } else if (json.startsWith("false", position)) { ++ position += 5; ++ return false; ++ } else if (json.startsWith("null", position)) { ++ position += 4; ++ return null; // JSON null ++ } else { ++ throw new IllegalArgumentException("Unexpected character: " + c); ++ } ++ } ++ ++ private Number readScopedNumber() { ++ int start = position; ++ ++ // Consume digits and the optional decimal point ++ while (position < json.length() ++ && (Character.isDigit(json.charAt(position)) || json.charAt(position) == '.')) { ++ position++; ++ } ++ ++ String number = json.substring(start, position); ++ ++ if (number.contains(".")) { ++ double value = Double.parseDouble(number); ++ if (value < 0.0 || value > 1.0) { ++ throw new IllegalArgumentException( ++ "Value out of bounds for Bedrock Floating Point Attribute: " + number); ++ } ++ return value; ++ } else { ++ return Integer.parseInt(number); ++ } ++ } ++ ++ private Map readObject() { ++ Map map = new HashMap<>(); ++ expect('{'); ++ skipWhitespace(); ++ while (currentChar() != '}') { ++ String key = readString(); ++ expect(':'); ++ Object value = readValue(); ++ map.put(key, value); ++ skipWhitespace(); ++ if (currentChar() == ',') { ++ position++; ++ } ++ } ++ position++; // Skip closing brace ++ return map; ++ } ++ ++ private List readArray() { ++ List list = new ArrayList<>(); ++ expect('['); ++ skipWhitespace(); ++ while (currentChar() != ']') { ++ list.add(readValue()); ++ skipWhitespace(); ++ if (currentChar() == ',') { ++ position++; ++ } ++ } ++ position++; ++ return list; ++ } ++ ++ public Map parse() { ++ return readObject(); ++ } ++ } ++ ++ // Resolves paths in a JSON structure ++ static class JsonPathResolver { ++ ++ // Private constructor to prevent instantiation ++ private JsonPathResolver() { ++ throw new UnsupportedOperationException("Utility class"); ++ } ++ ++ public static Object resolvePath(LlmJson llmJson, String... paths) { ++ for (String path : paths) { ++ Object value = resolvePath(llmJson.getJsonBody(), path); ++ if (value != null) { ++ return value; ++ } ++ } ++ return null; ++ } ++ ++ private static Object resolvePath(Map json, String path) { ++ String[] keys = path.split("/"); ++ Object current = json; ++ ++ for (String key : keys) { ++ if (key.isEmpty()) { ++ continue; ++ } ++ ++ if (current instanceof Map) { ++ current = ((Map) current).get(key); ++ } else if (current instanceof List) { ++ try { ++ int index = Integer.parseInt(key); ++ current = ((List) current).get(index); ++ } catch (NumberFormatException | IndexOutOfBoundsException e) { ++ return null; ++ } ++ } else { ++ return null; ++ } ++ ++ if (current == null) { ++ return null; ++ } ++ } ++ return current; ++ } ++ } ++ ++ public static class LlmJson { ++ private final Map jsonBody; ++ ++ public LlmJson(Map jsonBody) { ++ this.jsonBody = jsonBody; ++ } ++ ++ public Map getJsonBody() { ++ return jsonBody; ++ } ++ } ++} +diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/FieldMapper.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/FieldMapper.java +index 569d0eb5ae..8f2d463237 100644 +--- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/FieldMapper.java ++++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/FieldMapper.java +@@ -65,8 +65,13 @@ class FieldMapper { + for (int i = 1; i < path.size() && target != null; i++) { + target = next(target, path.get(i)); + } ++ String value; + if (target != null) { +- String value = serializer.serialize(target); ++ if (AwsExperimentalAttributes.isGenAiAttribute(fieldMapping.getAttribute())) { ++ value = serializer.serialize(fieldMapping.getAttribute(), target); ++ } else { ++ value = serializer.serialize(target); ++ } + if (!StringUtils.isEmpty(value)) { + span.setAttribute(fieldMapping.getAttribute(), value); + } +diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/Serializer.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/Serializer.java +index 979ecb08e8..1d2c7a5386 100644 +--- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/Serializer.java ++++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/Serializer.java +@@ -7,11 +7,14 @@ package io.opentelemetry.instrumentation.awssdk.v2_2; + + import java.io.IOException; + import java.io.InputStream; ++import java.util.Arrays; + import java.util.Collection; + import java.util.Map; ++import java.util.Objects; + import java.util.Optional; + import java.util.stream.Collectors; + import javax.annotation.Nullable; ++import software.amazon.awssdk.core.SdkBytes; + import software.amazon.awssdk.core.SdkPojo; + import software.amazon.awssdk.http.ContentStreamProvider; + import software.amazon.awssdk.http.SdkHttpFullRequest; +@@ -41,6 +44,45 @@ class Serializer { + return target.toString(); + } + ++ @Nullable ++ String serialize(String attributeName, Object target) { ++ try { ++ // Extract JSON string from target if it is a Bedrock Runtime JSON blob ++ String jsonString; ++ if (target instanceof SdkBytes) { ++ jsonString = ((SdkBytes) target).asUtf8String(); ++ } else { ++ if (target != null) { ++ return target.toString(); ++ } ++ return null; ++ } ++ ++ // Parse the LLM JSON string into a Map ++ BedrockJsonParser.LlmJson llmJson = BedrockJsonParser.parse(jsonString); ++ ++ // Use attribute name to extract the corresponding value ++ switch (attributeName) { ++ case "gen_ai.request.max_tokens": ++ return getMaxTokens(llmJson); ++ case "gen_ai.request.temperature": ++ return getTemperature(llmJson); ++ case "gen_ai.request.top_p": ++ return getTopP(llmJson); ++ case "gen_ai.response.finish_reasons": ++ return getFinishReasons(llmJson); ++ case "gen_ai.usage.input_tokens": ++ return getInputTokens(llmJson); ++ case "gen_ai.usage.output_tokens": ++ return getOutputTokens(llmJson); ++ default: ++ return null; ++ } ++ } catch (RuntimeException e) { ++ return null; ++ } ++ } ++ + @Nullable + private static String serialize(SdkPojo sdkPojo) { + ProtocolMarshaller marshaller = +@@ -65,4 +107,167 @@ class Serializer { + String serialized = collection.stream().map(this::serialize).collect(Collectors.joining(",")); + return (StringUtils.isEmpty(serialized) ? null : "[" + serialized + "]"); + } ++ ++ @Nullable ++ private static String approximateTokenCount( ++ BedrockJsonParser.LlmJson jsonBody, String... textPaths) { ++ return Arrays.stream(textPaths) ++ .map( ++ path -> { ++ Object value = BedrockJsonParser.JsonPathResolver.resolvePath(jsonBody, path); ++ if (value instanceof String) { ++ int tokenEstimate = (int) Math.ceil(((String) value).length() / 6.0); ++ return Integer.toString(tokenEstimate); ++ } ++ return null; ++ }) ++ .filter(Objects::nonNull) ++ .findFirst() ++ .orElse(null); ++ } ++ ++ // Model -> Path Mapping: ++ // Amazon Nova -> "/inferenceConfig/max_new_tokens" ++ // Amazon Titan -> "/textGenerationConfig/maxTokenCount" ++ // Anthropic Claude -> "/max_tokens" ++ // Cohere Command -> "/max_tokens" ++ // Cohere Command R -> "/max_tokens" ++ // AI21 Jamba -> "/max_tokens" ++ // Meta Llama -> "/max_gen_len" ++ // Mistral AI -> "/max_tokens" ++ @Nullable ++ private static String getMaxTokens(BedrockJsonParser.LlmJson jsonBody) { ++ Object value = ++ BedrockJsonParser.JsonPathResolver.resolvePath( ++ jsonBody, ++ "/max_tokens", ++ "/max_gen_len", ++ "/textGenerationConfig/maxTokenCount", ++ "inferenceConfig/max_new_tokens"); ++ return value != null ? String.valueOf(value) : null; ++ } ++ ++ // Model -> Path Mapping: ++ // Amazon Nova -> "/inferenceConfig/temperature" ++ // Amazon Titan -> "/textGenerationConfig/temperature" ++ // Anthropic Claude -> "/temperature" ++ // Cohere Command -> "/temperature" ++ // Cohere Command R -> "/temperature" ++ // AI21 Jamba -> "/temperature" ++ // Meta Llama -> "/temperature" ++ // Mistral AI -> "/temperature" ++ @Nullable ++ private static String getTemperature(BedrockJsonParser.LlmJson jsonBody) { ++ Object value = ++ BedrockJsonParser.JsonPathResolver.resolvePath( ++ jsonBody, ++ "/temperature", ++ "/textGenerationConfig/temperature", ++ "/inferenceConfig/temperature"); ++ return value != null ? String.valueOf(value) : null; ++ } ++ ++ // Model -> Path Mapping: ++ // Amazon Nova -> "/inferenceConfig/top_p" ++ // Amazon Titan -> "/textGenerationConfig/topP" ++ // Anthropic Claude -> "/top_p" ++ // Cohere Command -> "/p" ++ // Cohere Command R -> "/p" ++ // AI21 Jamba -> "/top_p" ++ // Meta Llama -> "/top_p" ++ // Mistral AI -> "/top_p" ++ @Nullable ++ private static String getTopP(BedrockJsonParser.LlmJson jsonBody) { ++ Object value = ++ BedrockJsonParser.JsonPathResolver.resolvePath( ++ jsonBody, "/top_p", "/p", "/textGenerationConfig/topP", "/inferenceConfig/top_p"); ++ return value != null ? String.valueOf(value) : null; ++ } ++ ++ // Model -> Path Mapping: ++ // Amazon Nova -> "/stopReason" ++ // Amazon Titan -> "/results/0/completionReason" ++ // Anthropic Claude -> "/stop_reason" ++ // Cohere Command -> "/generations/0/finish_reason" ++ // Cohere Command R -> "/finish_reason" ++ // AI21 Jamba -> "/choices/0/finish_reason" ++ // Meta Llama -> "/stop_reason" ++ // Mistral AI -> "/outputs/0/stop_reason" ++ @Nullable ++ private static String getFinishReasons(BedrockJsonParser.LlmJson jsonBody) { ++ Object value = ++ BedrockJsonParser.JsonPathResolver.resolvePath( ++ jsonBody, ++ "/stopReason", ++ "/finish_reason", ++ "/stop_reason", ++ "/results/0/completionReason", ++ "/generations/0/finish_reason", ++ "/choices/0/finish_reason", ++ "/outputs/0/stop_reason"); ++ ++ return value != null ? "[" + value + "]" : null; ++ } ++ ++ // Model -> Path Mapping: ++ // Amazon Nova -> "/usage/inputTokens" ++ // Amazon Titan -> "/inputTextTokenCount" ++ // Anthropic Claude -> "/usage/input_tokens" ++ // Cohere Command -> "/prompt" ++ // Cohere Command R -> "/message" ++ // AI21 Jamba -> "/usage/prompt_tokens" ++ // Meta Llama -> "/prompt_token_count" ++ // Mistral AI -> "/prompt" ++ @Nullable ++ private static String getInputTokens(BedrockJsonParser.LlmJson jsonBody) { ++ // Try direct tokens counts first ++ Object directCount = ++ BedrockJsonParser.JsonPathResolver.resolvePath( ++ jsonBody, ++ "/inputTextTokenCount", ++ "/prompt_token_count", ++ "/usage/input_tokens", ++ "/usage/prompt_tokens", ++ "/usage/inputTokens"); ++ ++ if (directCount != null) { ++ return String.valueOf(directCount); ++ } ++ ++ // Fall back to token approximation ++ Object approxTokenCount = approximateTokenCount(jsonBody, "/prompt", "/message"); ++ ++ return approxTokenCount != null ? String.valueOf(approxTokenCount) : null; ++ } ++ ++ // Model -> Path Mapping: ++ // Amazon Nova -> "/usage/outputTokens" ++ // Amazon Titan -> "/results/0/tokenCount" ++ // Anthropic Claude -> "/usage/output_tokens" ++ // Cohere Command -> "/generations/0/text" ++ // Cohere Command R -> "/text" ++ // AI21 Jamba -> "/usage/completion_tokens" ++ // Meta Llama -> "/generation_token_count" ++ // Mistral AI -> "/outputs/0/text" ++ @Nullable ++ private static String getOutputTokens(BedrockJsonParser.LlmJson jsonBody) { ++ // Try direct token counts first ++ Object directCount = ++ BedrockJsonParser.JsonPathResolver.resolvePath( ++ jsonBody, ++ "/generation_token_count", ++ "/results/0/tokenCount", ++ "/usage/output_tokens", ++ "/usage/completion_tokens", ++ "/usage/outputTokens"); ++ ++ if (directCount != null) { ++ return String.valueOf(directCount); ++ } ++ ++ // Fall back to token approximation ++ Object approxTokenCount = approximateTokenCount(jsonBody, "/text", "/outputs/0/text"); ++ ++ return approxTokenCount != null ? String.valueOf(approxTokenCount) : null; ++ } + } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java -index f717b1efc4..352b02093e 100644 +index f717b1efc4..5721dbdfa5 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/main/java/io/opentelemetry/instrumentation/awssdk/v2_2/TracingExecutionInterceptor.java @@ -5,6 +5,8 @@ @@ -1146,7 +2606,7 @@ index f717b1efc4..352b02093e 100644 /** AWS request execution interceptor. */ final class TracingExecutionInterceptor implements ExecutionInterceptor { -+ private static final String GEN_AI_SYSTEM_BEDROCK = "aws_bedrock"; ++ private static final String GEN_AI_SYSTEM_BEDROCK = "aws.bedrock"; // the class name is part of the attribute name, so that it will be shaded when used in javaagent // instrumentation, and won't conflict with usage outside javaagent instrumentation @@ -1161,6 +2621,119 @@ index f717b1efc4..352b02093e 100644 } @Override +diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/BedrockJsonParserTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/BedrockJsonParserTest.groovy +new file mode 100644 +index 0000000000..82f3622785 +--- /dev/null ++++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/src/test/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/BedrockJsonParserTest.groovy +@@ -0,0 +1,107 @@ ++/* ++ * Copyright The OpenTelemetry Authors ++ * SPDX-License-Identifier: Apache-2.0 ++ */ ++ ++package io.opentelemetry.instrumentation.awssdk.v2_2 ++ ++import spock.lang.Specification ++ ++class BedrockJsonParserTest extends Specification { ++ def "should parse simple JSON object"() { ++ given: ++ String json = '{"key":"value"}' ++ ++ when: ++ def parsedJson = BedrockJsonParser.parse(json) ++ ++ then: ++ parsedJson.getJsonBody() == [key: "value"] ++ } ++ ++ def "should parse nested JSON object"() { ++ given: ++ String json = '{"parent":{"child":"value"}}' ++ ++ when: ++ def parsedJson = BedrockJsonParser.parse(json) ++ ++ then: ++ def parent = parsedJson.getJsonBody().get("parent") ++ parent instanceof Map ++ parent["child"] == "value" ++ } ++ ++ def "should parse JSON array"() { ++ given: ++ String json = '{"array":[1, "two", 1.0]}' ++ ++ when: ++ def parsedJson = BedrockJsonParser.parse(json) ++ ++ then: ++ def array = parsedJson.getJsonBody().get("array") ++ array instanceof List ++ array == [1, "two", 1.0] ++ } ++ ++ def "should parse escape sequences"() { ++ given: ++ String json = '{"escaped":"Line1\\nLine2\\tTabbed\\\"Quoted\\\"\\bBackspace\\fFormfeed\\rCarriageReturn\\\\Backslash\\/Slash\\u0041"}' ++ ++ when: ++ def parsedJson = BedrockJsonParser.parse(json) ++ ++ then: ++ parsedJson.getJsonBody().get("escaped") == ++ "Line1\nLine2\tTabbed\"Quoted\"\bBackspace\fFormfeed\rCarriageReturn\\Backslash/SlashA" ++ } ++ ++ def "should throw exception for malformed JSON"() { ++ given: ++ String malformedJson = '{"key":value}' ++ ++ when: ++ BedrockJsonParser.parse(malformedJson) ++ ++ then: ++ def ex = thrown(IllegalArgumentException) ++ ex.message.contains("Unexpected character") ++ } ++ ++ def "should resolve path in JSON object"() { ++ given: ++ String json = '{"parent":{"child":{"key":"value"}}}' ++ ++ when: ++ def parsedJson = BedrockJsonParser.parse(json) ++ def resolvedValue = BedrockJsonParser.JsonPathResolver.resolvePath(parsedJson, "/parent/child/key") ++ ++ then: ++ resolvedValue == "value" ++ } ++ ++ def "should resolve path in JSON array"() { ++ given: ++ String json = '{"array":[{"key":"value1"}, {"key":"value2"}]}' ++ ++ when: ++ def parsedJson = BedrockJsonParser.parse(json) ++ def resolvedValue = BedrockJsonParser.JsonPathResolver.resolvePath(parsedJson, "/array/1/key") ++ ++ then: ++ resolvedValue == "value2" ++ } ++ ++ def "should return null for invalid path resolution"() { ++ given: ++ String json = '{"parent":{"child":{"key":"value"}}}' ++ ++ when: ++ def parsedJson = BedrockJsonParser.parse(json) ++ def resolvedValue = BedrockJsonParser.JsonPathResolver.resolvePath(parsedJson, "/invalid/path") ++ ++ then: ++ resolvedValue == null ++ } ++} diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts b/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts index 9981aa9a19..1caed55e62 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/build.gradle.kts @@ -1176,7 +2749,7 @@ index 9981aa9a19..1caed55e62 100644 // needed for SQS - using emq directly as localstack references emq v0.15.7 ie WITHOUT AWS trace header propagation implementation("org.elasticmq:elasticmq-rest-sqs_2.13:1.5.1") diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy -index 7c152bb91b..b30b8fa2c7 100644 +index 7c152bb91b..4fa7a220be 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy +++ b/instrumentation/aws-sdk/aws-sdk-2.2/testing/src/main/groovy/io/opentelemetry/instrumentation/awssdk/v2_2/AbstractAws2ClientTest.groovy @@ -35,6 +35,16 @@ import software.amazon.awssdk.services.sqs.SqsAsyncClient @@ -1210,7 +2783,7 @@ index 7c152bb91b..b30b8fa2c7 100644 + "aws.bedrock.data_source.id" "datasourceId" + } else if (service == "BedrockRuntime" && operation == "InvokeModel") { + "gen_ai.request.model" "meta.llama2-13b-chat-v1" -+ "gen_ai.system" "aws_bedrock" ++ "gen_ai.system" "aws.bedrock" + } else if (service == "Sfn" && operation == "DescribeStateMachine") { + "aws.stepfunctions.state_machine.arn" "stateMachineArn" + } else if (service == "Sfn" && operation == "DescribeActivity") { diff --git a/licenses/licenses.md b/licenses/licenses.md index 1f6d57d996..18cec08c1c 100644 --- a/licenses/licenses.md +++ b/licenses/licenses.md @@ -1,7 +1,7 @@ # aws-otel-java-instrumentation ## Dependency License Report -_2024-12-10 18:19:03 UTC_ +_2025-01-08 19:17:10 UTC_ ## Apache 2 **1** **Group:** `joda-time` **Name:** `joda-time` **Version:** `2.8.1` @@ -1980,281 +1980,285 @@ _2024-12-10 18:19:03 UTC_ > - **POM Project URL**: [https://armeria.dev/](https://armeria.dev/) > - **POM License**: The Apache License, Version 2.0 - [https://www.apache.org/license/LICENSE-2.0.txt](https://www.apache.org/license/LICENSE-2.0.txt) -**347** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api` **Version:** `1.34.1` +**347** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api` **Version:** `1.41.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**348** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api-events` **Version:** `1.34.1-alpha` +**348** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api` **Version:** `1.42.1` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**349** **Group:** `io.opentelemetry` **Name:** `opentelemetry-context` **Version:** `1.34.1` +**349** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api-incubator` **Version:** `1.42.1-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**350** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-aws` **Version:** `1.20.1` +**350** **Group:** `io.opentelemetry` **Name:** `opentelemetry-context` **Version:** `1.41.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**351** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-incubator` **Version:** `1.34.1-alpha` +**351** **Group:** `io.opentelemetry` **Name:** `opentelemetry-context` **Version:** `1.42.1` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**352** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-trace-propagators` **Version:** `1.34.1` +**352** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-aws` **Version:** `1.20.1` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**353** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk` **Version:** `1.34.1` +**353** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-trace-propagators` **Version:** `1.41.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**354** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-common` **Version:** `1.34.1` +**354** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk` **Version:** `1.42.1` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**355** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-logs` **Version:** `1.34.1` +**355** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-common` **Version:** `1.42.1` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**356** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-metrics` **Version:** `1.34.1` +**356** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-logs` **Version:** `1.42.1` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**357** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-trace` **Version:** `1.34.1` +**357** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-metrics` **Version:** `1.42.1` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**358** **Group:** `io.opentelemetry` **Name:** `opentelemetry-semconv` **Version:** `1.28.0-alpha` +**358** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-trace` **Version:** `1.42.1` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**359** **Group:** `io.opentelemetry.contrib` **Name:** `opentelemetry-aws-resources` **Version:** `1.32.0-alpha` +**359** **Group:** `io.opentelemetry` **Name:** `opentelemetry-semconv` **Version:** `1.28.0-alpha` +> - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) +> - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) + +**360** **Group:** `io.opentelemetry.contrib` **Name:** `opentelemetry-aws-resources` **Version:** `1.39.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java-contrib](https://github.com/open-telemetry/opentelemetry-java-contrib) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**360** **Group:** `io.opentelemetry.contrib` **Name:** `opentelemetry-aws-xray` **Version:** `1.32.0` +**361** **Group:** `io.opentelemetry.contrib` **Name:** `opentelemetry-aws-xray` **Version:** `1.39.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java-contrib](https://github.com/open-telemetry/opentelemetry-java-contrib) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**361** **Group:** `io.opentelemetry.proto` **Name:** `opentelemetry-proto` **Version:** `1.0.0-alpha` +**362** **Group:** `io.opentelemetry.proto` **Name:** `opentelemetry-proto` **Version:** `1.0.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-proto-java](https://github.com/open-telemetry/opentelemetry-proto-java) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**362** **Group:** `io.opentelemetry.semconv` **Name:** `opentelemetry-semconv` **Version:** `1.21.0-alpha` +**363** **Group:** `io.opentelemetry.semconv` **Name:** `opentelemetry-semconv` **Version:** `1.25.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/semantic-conventions-java](https://github.com/open-telemetry/semantic-conventions-java) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**363** **Group:** `org.apache.kafka` **Name:** `kafka-clients` **Version:** `3.6.1` +**364** **Group:** `org.apache.kafka` **Name:** `kafka-clients` **Version:** `3.6.1` > - **POM Project URL**: [https://kafka.apache.org](https://kafka.apache.org) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [kafka-clients-3.6.1.jar/LICENSE](kafka-clients-3.6.1.jar/LICENSE) - [kafka-clients-3.6.1.jar/NOTICE](kafka-clients-3.6.1.jar/NOTICE) - [kafka-clients-3.6.1.jar/common/message/README.md](kafka-clients-3.6.1.jar/common/message/README.md) -**364** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib` **Version:** `1.8.22` +**365** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib` **Version:** `1.8.22` > - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**365** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib` **Version:** `1.9.10` +**366** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib` **Version:** `1.9.10` > - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**366** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-common` **Version:** `1.8.22` +**367** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-common` **Version:** `1.8.22` > - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**367** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-common` **Version:** `1.9.10` +**368** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-common` **Version:** `1.9.10` > - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**368** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-jdk7` **Version:** `1.8.22` +**369** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-jdk7` **Version:** `1.8.22` > - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**369** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-jdk7` **Version:** `1.9.10` +**370** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-jdk7` **Version:** `1.9.10` > - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**370** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-jdk8` **Version:** `1.8.22` +**371** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-jdk8` **Version:** `1.8.22` > - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**371** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-jdk8` **Version:** `1.9.10` +**372** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-stdlib-jdk8` **Version:** `1.9.10` > - **POM Project URL**: [https://kotlinlang.org/](https://kotlinlang.org/) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**372** **Group:** `software.amazon.ion` **Name:** `ion-java` **Version:** `1.0.2` +**373** **Group:** `software.amazon.ion` **Name:** `ion-java` **Version:** `1.0.2` > - **POM Project URL**: [https://github.com/amznlabs/ion-java/](https://github.com/amznlabs/ion-java/) > - **POM License**: The Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) ## The Apache Software License, Version 2.0 -**373** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-annotations` **Version:** `2.16.0` +**374** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-annotations` **Version:** `2.16.0` > - **Project URL**: [https://github.com/FasterXML/jackson](https://github.com/FasterXML/jackson) > - **POM License**: The Apache Software License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [jackson-annotations-2.16.0.jar/META-INF/LICENSE](jackson-annotations-2.16.0.jar/META-INF/LICENSE) - [jackson-annotations-2.16.0.jar/META-INF/NOTICE](jackson-annotations-2.16.0.jar/META-INF/NOTICE) -**374** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-annotations` **Version:** `2.17.2` +**375** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-annotations` **Version:** `2.17.2` > - **Project URL**: [https://github.com/FasterXML/jackson](https://github.com/FasterXML/jackson) > - **POM License**: The Apache Software License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [jackson-annotations-2.17.2.jar/META-INF/LICENSE](jackson-annotations-2.17.2.jar/META-INF/LICENSE) - [jackson-annotations-2.17.2.jar/META-INF/NOTICE](jackson-annotations-2.17.2.jar/META-INF/NOTICE) -**375** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-core` **Version:** `2.16.0` +**376** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-core` **Version:** `2.16.0` > - **Project URL**: [https://github.com/FasterXML/jackson-core](https://github.com/FasterXML/jackson-core) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **POM License**: The Apache Software License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [jackson-core-2.16.0.jar/META-INF/LICENSE](jackson-core-2.16.0.jar/META-INF/LICENSE) - [jackson-core-2.16.0.jar/META-INF/NOTICE](jackson-core-2.16.0.jar/META-INF/NOTICE) -**376** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-core` **Version:** `2.17.2` +**377** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-core` **Version:** `2.17.2` > - **Project URL**: [https://github.com/FasterXML/jackson-core](https://github.com/FasterXML/jackson-core) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **POM License**: The Apache Software License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [jackson-core-2.17.2.jar/META-INF/LICENSE](jackson-core-2.17.2.jar/META-INF/LICENSE) - [jackson-core-2.17.2.jar/META-INF/NOTICE](jackson-core-2.17.2.jar/META-INF/NOTICE) -**377** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-databind` **Version:** `2.16.0` +**378** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-databind` **Version:** `2.16.0` > - **Project URL**: [https://github.com/FasterXML/jackson](https://github.com/FasterXML/jackson) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **POM License**: The Apache Software License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [jackson-databind-2.16.0.jar/META-INF/LICENSE](jackson-databind-2.16.0.jar/META-INF/LICENSE) - [jackson-databind-2.16.0.jar/META-INF/NOTICE](jackson-databind-2.16.0.jar/META-INF/NOTICE) -**378** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-databind` **Version:** `2.17.2` +**379** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-databind` **Version:** `2.17.2` > - **Project URL**: [https://github.com/FasterXML/jackson](https://github.com/FasterXML/jackson) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **POM License**: The Apache Software License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [jackson-databind-2.17.2.jar/META-INF/LICENSE](jackson-databind-2.17.2.jar/META-INF/LICENSE) - [jackson-databind-2.17.2.jar/META-INF/NOTICE](jackson-databind-2.17.2.jar/META-INF/NOTICE) -**379** **Group:** `com.fasterxml.jackson.dataformat` **Name:** `jackson-dataformat-cbor` **Version:** `2.16.0` +**380** **Group:** `com.fasterxml.jackson.dataformat` **Name:** `jackson-dataformat-cbor` **Version:** `2.16.0` > - **Project URL**: [https://github.com/FasterXML/jackson-dataformats-binary](https://github.com/FasterXML/jackson-dataformats-binary) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **POM License**: The Apache Software License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [jackson-dataformat-cbor-2.16.0.jar/META-INF/LICENSE](jackson-dataformat-cbor-2.16.0.jar/META-INF/LICENSE) - [jackson-dataformat-cbor-2.16.0.jar/META-INF/NOTICE](jackson-dataformat-cbor-2.16.0.jar/META-INF/NOTICE) -**380** **Group:** `com.fasterxml.jackson.dataformat` **Name:** `jackson-dataformat-cbor` **Version:** `2.17.2` +**381** **Group:** `com.fasterxml.jackson.dataformat` **Name:** `jackson-dataformat-cbor` **Version:** `2.17.2` > - **Project URL**: [https://github.com/FasterXML/jackson-dataformats-binary](https://github.com/FasterXML/jackson-dataformats-binary) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **POM License**: The Apache Software License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [jackson-dataformat-cbor-2.17.2.jar/META-INF/LICENSE](jackson-dataformat-cbor-2.17.2.jar/META-INF/LICENSE) - [jackson-dataformat-cbor-2.17.2.jar/META-INF/NOTICE](jackson-dataformat-cbor-2.17.2.jar/META-INF/NOTICE) -**381** **Group:** `com.fasterxml.jackson.datatype` **Name:** `jackson-datatype-jdk8` **Version:** `2.16.0` +**382** **Group:** `com.fasterxml.jackson.datatype` **Name:** `jackson-datatype-jdk8` **Version:** `2.16.0` > - **Manifest Project URL**: [https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8](https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [jackson-datatype-jdk8-2.16.0.jar/META-INF/LICENSE](jackson-datatype-jdk8-2.16.0.jar/META-INF/LICENSE) - [jackson-datatype-jdk8-2.16.0.jar/META-INF/NOTICE](jackson-datatype-jdk8-2.16.0.jar/META-INF/NOTICE) -**382** **Group:** `com.fasterxml.jackson.datatype` **Name:** `jackson-datatype-jsr310` **Version:** `2.16.0` +**383** **Group:** `com.fasterxml.jackson.datatype` **Name:** `jackson-datatype-jsr310` **Version:** `2.16.0` > - **Manifest Project URL**: [https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310](https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [jackson-datatype-jsr310-2.16.0.jar/META-INF/LICENSE](jackson-datatype-jsr310-2.16.0.jar/META-INF/LICENSE) - [jackson-datatype-jsr310-2.16.0.jar/META-INF/NOTICE](jackson-datatype-jsr310-2.16.0.jar/META-INF/NOTICE) -**383** **Group:** `com.fasterxml.jackson.module` **Name:** `jackson-module-parameter-names` **Version:** `2.16.0` +**384** **Group:** `com.fasterxml.jackson.module` **Name:** `jackson-module-parameter-names` **Version:** `2.16.0` > - **Manifest Project URL**: [https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names](https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [jackson-module-parameter-names-2.16.0.jar/META-INF/LICENSE](jackson-module-parameter-names-2.16.0.jar/META-INF/LICENSE) - [jackson-module-parameter-names-2.16.0.jar/META-INF/NOTICE](jackson-module-parameter-names-2.16.0.jar/META-INF/NOTICE) -**384** **Group:** `com.google.code.findbugs` **Name:** `jsr305` **Version:** `3.0.2` +**385** **Group:** `com.google.code.findbugs` **Name:** `jsr305` **Version:** `3.0.2` > - **POM Project URL**: [http://findbugs.sourceforge.net/](http://findbugs.sourceforge.net/) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**385** **Group:** `com.google.guava` **Name:** `failureaccess` **Version:** `1.0.2` +**386** **Group:** `com.google.guava` **Name:** `failureaccess` **Version:** `1.0.2` > - **Manifest Project URL**: [https://github.com/google/guava/](https://github.com/google/guava/) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**386** **Group:** `com.google.guava` **Name:** `listenablefuture` **Version:** `9999.0-empty-to-avoid-conflict-with-guava` +**387** **Group:** `com.google.guava` **Name:** `listenablefuture` **Version:** `9999.0-empty-to-avoid-conflict-with-guava` > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**387** **Group:** `com.sparkjava` **Name:** `spark-core` **Version:** `2.9.4` +**388** **Group:** `com.sparkjava` **Name:** `spark-core` **Version:** `2.9.4` > - **POM Project URL**: [http://www.sparkjava.com](http://www.sparkjava.com) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**388** **Group:** `com.squareup.okhttp3` **Name:** `okhttp` **Version:** `4.12.0` +**389** **Group:** `com.squareup.okhttp3` **Name:** `okhttp` **Version:** `4.12.0` > - **POM Project URL**: [https://square.github.io/okhttp/](https://square.github.io/okhttp/) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [okhttp-4.12.0.jar/okhttp3/internal/publicsuffix/NOTICE](okhttp-4.12.0.jar/okhttp3/internal/publicsuffix/NOTICE) -**389** **Group:** `com.squareup.okio` **Name:** `okio-jvm` **Version:** `3.6.0` +**390** **Group:** `com.squareup.okio` **Name:** `okio-jvm` **Version:** `3.6.0` > - **POM Project URL**: [https://github.com/square/okio/](https://github.com/square/okio/) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**390** **Group:** `com.zaxxer` **Name:** `HikariCP` **Version:** `5.0.1` +**391** **Group:** `com.zaxxer` **Name:** `HikariCP` **Version:** `5.0.1` > - **Manifest Project URL**: [https://github.com/brettwooldridge](https://github.com/brettwooldridge) > - **POM Project URL**: [https://github.com/brettwooldridge/HikariCP](https://github.com/brettwooldridge/HikariCP) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**391** **Group:** `commons-logging` **Name:** `commons-logging` **Version:** `1.2` +**392** **Group:** `commons-logging` **Name:** `commons-logging` **Version:** `1.2` > - **Project URL**: [http://commons.apache.org/proper/commons-logging/](http://commons.apache.org/proper/commons-logging/) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [commons-logging-1.2.jar/META-INF/LICENSE.txt](commons-logging-1.2.jar/META-INF/LICENSE.txt) - [commons-logging-1.2.jar/META-INF/NOTICE.txt](commons-logging-1.2.jar/META-INF/NOTICE.txt) -**392** **Group:** `io.micrometer` **Name:** `micrometer-commons` **Version:** `1.10.8` +**393** **Group:** `io.micrometer` **Name:** `micrometer-commons` **Version:** `1.10.8` > - **POM Project URL**: [https://github.com/micrometer-metrics/micrometer](https://github.com/micrometer-metrics/micrometer) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [micrometer-commons-1.10.8.jar/META-INF/LICENSE](micrometer-commons-1.10.8.jar/META-INF/LICENSE) - [micrometer-commons-1.10.8.jar/META-INF/NOTICE](micrometer-commons-1.10.8.jar/META-INF/NOTICE) -**393** **Group:** `io.micrometer` **Name:** `micrometer-commons` **Version:** `1.11.5` +**394** **Group:** `io.micrometer` **Name:** `micrometer-commons` **Version:** `1.11.5` > - **POM Project URL**: [https://github.com/micrometer-metrics/micrometer](https://github.com/micrometer-metrics/micrometer) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [micrometer-commons-1.11.5.jar/META-INF/LICENSE](micrometer-commons-1.11.5.jar/META-INF/LICENSE) - [micrometer-commons-1.11.5.jar/META-INF/NOTICE](micrometer-commons-1.11.5.jar/META-INF/NOTICE) -**394** **Group:** `io.micrometer` **Name:** `micrometer-core` **Version:** `1.11.5` +**395** **Group:** `io.micrometer` **Name:** `micrometer-core` **Version:** `1.11.5` > - **POM Project URL**: [https://github.com/micrometer-metrics/micrometer](https://github.com/micrometer-metrics/micrometer) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [micrometer-core-1.11.5.jar/META-INF/LICENSE](micrometer-core-1.11.5.jar/META-INF/LICENSE) - [micrometer-core-1.11.5.jar/META-INF/NOTICE](micrometer-core-1.11.5.jar/META-INF/NOTICE) -**395** **Group:** `io.micrometer` **Name:** `micrometer-observation` **Version:** `1.10.8` +**396** **Group:** `io.micrometer` **Name:** `micrometer-observation` **Version:** `1.10.8` > - **POM Project URL**: [https://github.com/micrometer-metrics/micrometer](https://github.com/micrometer-metrics/micrometer) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [micrometer-observation-1.10.8.jar/META-INF/LICENSE](micrometer-observation-1.10.8.jar/META-INF/LICENSE) - [micrometer-observation-1.10.8.jar/META-INF/NOTICE](micrometer-observation-1.10.8.jar/META-INF/NOTICE) -**396** **Group:** `io.micrometer` **Name:** `micrometer-observation` **Version:** `1.11.5` +**397** **Group:** `io.micrometer` **Name:** `micrometer-observation` **Version:** `1.11.5` > - **POM Project URL**: [https://github.com/micrometer-metrics/micrometer](https://github.com/micrometer-metrics/micrometer) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [micrometer-observation-1.11.5.jar/META-INF/LICENSE](micrometer-observation-1.11.5.jar/META-INF/LICENSE) - [micrometer-observation-1.11.5.jar/META-INF/NOTICE](micrometer-observation-1.11.5.jar/META-INF/NOTICE) -**397** **Group:** `io.netty` **Name:** `netty-tcnative-boringssl-static` **Version:** `2.0.61.Final` +**398** **Group:** `io.netty` **Name:** `netty-tcnative-boringssl-static` **Version:** `2.0.61.Final` > - **Manifest Project URL**: [https://netty.io/](https://netty.io/) > - **POM Project URL**: [https://github.com/netty/netty-tcnative/netty-tcnative-boringssl-static/](https://github.com/netty/netty-tcnative/netty-tcnative-boringssl-static/) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) > - **Embedded license files**: [netty-tcnative-boringssl-static-2.0.61.Final-osx-aarch_64.jar/META-INF/LICENSE.txt](netty-tcnative-boringssl-static-2.0.61.Final-osx-aarch_64.jar/META-INF/LICENSE.txt) - [netty-tcnative-boringssl-static-2.0.61.Final-osx-aarch_64.jar/META-INF/NOTICE.txt](netty-tcnative-boringssl-static-2.0.61.Final-osx-aarch_64.jar/META-INF/NOTICE.txt) -**398** **Group:** `io.netty` **Name:** `netty-tcnative-classes` **Version:** `2.0.61.Final` +**399** **Group:** `io.netty` **Name:** `netty-tcnative-classes` **Version:** `2.0.61.Final` > - **Manifest Project URL**: [https://netty.io/](https://netty.io/) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**399** **Group:** `org.codehaus.mojo` **Name:** `animal-sniffer-annotations` **Version:** `1.23` +**400** **Group:** `org.codehaus.mojo` **Name:** `animal-sniffer-annotations` **Version:** `1.23` > - **POM License**: MIT license - [https://spdx.org/licenses/MIT.txt](https://spdx.org/licenses/MIT.txt) > - **POM License**: The Apache Software License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) -**400** **Group:** `org.jetbrains` **Name:** `annotations` **Version:** `13.0` +**401** **Group:** `org.jetbrains` **Name:** `annotations` **Version:** `13.0` > - **POM Project URL**: [http://www.jetbrains.org](http://www.jetbrains.org) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -**401** **Group:** `org.lz4` **Name:** `lz4-java` **Version:** `1.8.0` +**402** **Group:** `org.lz4` **Name:** `lz4-java` **Version:** `1.8.0` > - **POM Project URL**: [https://github.com/lz4/lz4-java](https://github.com/lz4/lz4-java) > - **POM License**: The Apache Software License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) ## The GNU General Public License, v2 with Universal FOSS Exception, v1.0 -**402** **Group:** `com.mysql` **Name:** `mysql-connector-j` **Version:** `8.4.0` +**403** **Group:** `com.mysql` **Name:** `mysql-connector-j` **Version:** `8.4.0` > - **POM Project URL**: [http://dev.mysql.com/doc/connector-j/en/](http://dev.mysql.com/doc/connector-j/en/) > - **POM License**: The GNU General Public License, v2 with Universal FOSS Exception, v1.0 > - **Embedded license files**: [mysql-connector-j-8.4.0.jar/LICENSE](mysql-connector-j-8.4.0.jar/LICENSE) @@ -2262,13 +2266,13 @@ _2024-12-10 18:19:03 UTC_ ## The MIT License -**403** **Group:** `org.checkerframework` **Name:** `checker-qual` **Version:** `3.41.0` +**404** **Group:** `org.checkerframework` **Name:** `checker-qual` **Version:** `3.41.0` > - **Manifest License**: MIT (Not Packaged) > - **POM Project URL**: [https://checkerframework.org/](https://checkerframework.org/) > - **POM License**: The MIT License - [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT) > - **Embedded license files**: [checker-qual-3.41.0.jar/META-INF/LICENSE.txt](checker-qual-3.41.0.jar/META-INF/LICENSE.txt) -**404** **Group:** `org.checkerframework` **Name:** `checker-qual` **Version:** `3.5.0` +**405** **Group:** `org.checkerframework` **Name:** `checker-qual` **Version:** `3.5.0` > - **Manifest License**: MIT (Not Packaged) > - **POM Project URL**: [https://checkerframework.org](https://checkerframework.org) > - **POM License**: The MIT License - [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT) @@ -2276,12 +2280,12 @@ _2024-12-10 18:19:03 UTC_ ## Unknown -**405** **Group:** `com.squareup.okio` **Name:** `okio` **Version:** `3.6.0` +**406** **Group:** `com.squareup.okio` **Name:** `okio` **Version:** `3.6.0` -**406** **Group:** `io.opentelemetry` **Name:** `opentelemetry-bom` **Version:** `1.34.1` +**407** **Group:** `io.opentelemetry` **Name:** `opentelemetry-bom` **Version:** `1.41.0` -**407** **Group:** `io.opentelemetry` **Name:** `opentelemetry-bom-alpha` **Version:** `1.34.1-alpha` +**408** **Group:** `io.opentelemetry` **Name:** `opentelemetry-bom-alpha` **Version:** `1.41.0-alpha` -**408** **Group:** `io.opentelemetry.instrumentation` **Name:** `opentelemetry-instrumentation-bom` **Version:** `1.32.1-adot2` +**409** **Group:** `io.opentelemetry.instrumentation` **Name:** `opentelemetry-instrumentation-bom` **Version:** `1.33.6-adot1`