diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java index 74f3851dd6..489fe68a22 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AiConfigCustomizer.java @@ -8,7 +8,6 @@ import com.microsoft.applicationinsights.agent.internal.configuration.Configuration; import com.microsoft.applicationinsights.agent.internal.legacyheaders.DelegatingPropagatorProvider; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -95,16 +94,6 @@ public Map apply(ConfigProperties otelConfig) { if (tracesExporter == null) { // this overrides the default "otlp" so the exporter can be configured later properties.put("otel.traces.exporter", "none"); - - // TODO (trask) this can go away once new indexer is rolled out to gov clouds - List httpClientResponseHeaders = new ArrayList<>(); - httpClientResponseHeaders.add("request-context"); - httpClientResponseHeaders.addAll( - configuration.preview.captureHttpClientHeaders.responseHeaders); - setHttpHeaderConfiguration( - properties, - "otel.instrumentation.http.capture-headers.client.response", - httpClientResponseHeaders); } String metricsExporter = otelConfig.getString("otel.metrics.exporter"); diff --git a/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/AiSemanticAttributes.java b/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/AiSemanticAttributes.java index fa7e188751..52d645dbda 100644 --- a/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/AiSemanticAttributes.java +++ b/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/AiSemanticAttributes.java @@ -8,7 +8,6 @@ import static io.opentelemetry.api.common.AttributeKey.stringKey; import io.opentelemetry.api.common.AttributeKey; -import java.util.List; public final class AiSemanticAttributes { @@ -79,10 +78,6 @@ public final class AiSemanticAttributes { public static final AttributeKey NET_SOCK_PEER_PORT = AttributeKey.longKey("net.sock.peer.port"); - // TODO (trask) this can go away once new indexer is rolled out to gov clouds - public static final AttributeKey> REQUEST_CONTEXT = - AttributeKey.stringArrayKey("http.response.header.request_context"); - public static final AttributeKey LEGACY_PARENT_ID = AttributeKey.stringKey("applicationinsights.internal.legacy_parent_id"); public static final AttributeKey LEGACY_ROOT_ID = diff --git a/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/LogDataMapper.java b/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/LogDataMapper.java index a24ca53256..57d63c885c 100644 --- a/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/LogDataMapper.java +++ b/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/LogDataMapper.java @@ -3,6 +3,8 @@ package com.azure.monitor.opentelemetry.exporter.implementation; +import static io.opentelemetry.api.common.AttributeKey.stringKey; + import com.azure.core.util.logging.ClientLogger; import com.azure.monitor.opentelemetry.exporter.implementation.builders.AbstractTelemetryBuilder; import com.azure.monitor.opentelemetry.exporter.implementation.builders.ExceptionTelemetryBuilder; @@ -25,16 +27,59 @@ public class LogDataMapper { private static final ClientLogger logger = new ClientLogger(LogDataMapper.class); + private static final String LOG4J_MDC_PREFIX = "log4j.mdc."; // log4j 1.2 + private static final String LOG4J_CONTEXT_DATA_PREFIX = "log4j.context_data."; // log4j 2.x + private static final String LOGBACK_MDC_PREFIX = "logback.mdc."; + private static final String JBOSS_LOGGING_MDC_PREFIX = "jboss-logmanager.mdc."; + + private static final AttributeKey LOG4J_MARKER = stringKey("log4j.marker"); + private static final AttributeKey LOGBACK_MARKER = stringKey("logback.marker"); + + private static final Mappings MAPPINGS; + + static { + MappingsBuilder mappingsBuilder = + new MappingsBuilder() + .prefix( + LOG4J_MDC_PREFIX, + (telemetryBuilder, key, value) -> { + telemetryBuilder.addProperty( + key.substring(LOG4J_MDC_PREFIX.length()), String.valueOf(value)); + }) + .prefix( + LOG4J_CONTEXT_DATA_PREFIX, + (telemetryBuilder, key, value) -> { + telemetryBuilder.addProperty( + key.substring(LOG4J_CONTEXT_DATA_PREFIX.length()), String.valueOf(value)); + }) + .prefix( + LOGBACK_MDC_PREFIX, + (telemetryBuilder, key, value) -> { + telemetryBuilder.addProperty( + key.substring(LOGBACK_MDC_PREFIX.length()), String.valueOf(value)); + }) + .prefix( + JBOSS_LOGGING_MDC_PREFIX, + (telemetryBuilder, key, value) -> { + telemetryBuilder.addProperty( + key.substring(JBOSS_LOGGING_MDC_PREFIX.length()), String.valueOf(value)); + }) + .exactString(SemanticAttributes.CODE_FILEPATH, "FileName") + .exactString(SemanticAttributes.CODE_NAMESPACE, "ClassName") + .exactString(SemanticAttributes.CODE_FUNCTION, "MethodName") + .exactLong(SemanticAttributes.CODE_LINENO, "LineNumber") + .exactString(LOG4J_MARKER, "Marker") + .exactString(LOGBACK_MARKER, "Marker"); + + SpanDataMapper.applyCommonTags(mappingsBuilder); + + MAPPINGS = mappingsBuilder.build(); + } + private final boolean captureLoggingLevelAsCustomDimension; private final boolean captureAzureFunctionsAttributes; private final BiConsumer telemetryInitializer; - private static final AttributeKey OTEL_LOG4J_MARKER = - AttributeKey.stringKey("log4j.marker"); - - private static final AttributeKey OTEL_LOGBACK_MARKER = - AttributeKey.stringKey("logback.marker"); - public LogDataMapper( boolean captureLoggingLevelAsCustomDimension, boolean captureAzureFunctionsAttributes, @@ -64,7 +109,10 @@ private TelemetryItem createMessageTelemetryItem(LogRecordData log, @Nullable Lo // update tags Attributes attributes = log.getAttributes(); - setExtraAttributes(telemetryBuilder, attributes); + if (captureAzureFunctionsAttributes) { + setFunctionExtraTraceAttributes(telemetryBuilder, attributes); + } + MAPPINGS.map(attributes, telemetryBuilder); telemetryBuilder.setSeverityLevel(toSeverityLevel(log.getSeverity())); telemetryBuilder.setMessage(log.getBody().asString()); @@ -91,7 +139,7 @@ private TelemetryItem createExceptionTelemetryItem( // update tags Attributes attributes = log.getAttributes(); - setExtraAttributes(telemetryBuilder, attributes); + MAPPINGS.map(attributes, telemetryBuilder); telemetryBuilder.setExceptions(Exceptions.minimalParse(stack)); telemetryBuilder.setSeverityLevel(toSeverityLevel(log.getSeverity())); @@ -143,79 +191,7 @@ private static void setItemCount( } } - private static final String LOG4J1_2_MDC_PREFIX = "log4j.mdc."; - private static final String LOG4J2_CONTEXT_DATA_PREFIX = "log4j.context_data."; - private static final String LOGBACK_MDC_PREFIX = "logback.mdc."; - private static final String JBOSS_LOGGING_MDC_PREFIX = "jboss-logmanager.mdc."; - - private void setExtraAttributes( - AbstractTelemetryBuilder telemetryBuilder, Attributes attributes) { - if (captureAzureFunctionsAttributes) { - setFunctionExtraAttributes(telemetryBuilder, attributes); - } - attributes.forEach( - (attributeKey, value) -> { - String key = attributeKey.getKey(); - if (key.startsWith(LOG4J2_CONTEXT_DATA_PREFIX)) { - telemetryBuilder.addProperty( - key.substring(LOG4J2_CONTEXT_DATA_PREFIX.length()), String.valueOf(value)); - return; - } - if (key.startsWith(LOGBACK_MDC_PREFIX)) { - telemetryBuilder.addProperty( - key.substring(LOGBACK_MDC_PREFIX.length()), String.valueOf(value)); - return; - } - if (SemanticAttributes.CODE_FILEPATH.getKey().equals(key)) { - telemetryBuilder.addProperty("FileName", String.valueOf(value)); - return; - } - if (SemanticAttributes.CODE_NAMESPACE.getKey().equals(key)) { - telemetryBuilder.addProperty("ClassName", String.valueOf(value)); - return; - } - if (SemanticAttributes.CODE_FUNCTION.getKey().equals(key)) { - telemetryBuilder.addProperty("MethodName", String.valueOf(value)); - return; - } - if (SemanticAttributes.CODE_LINENO.getKey().equals(key)) { - telemetryBuilder.addProperty("LineNumber", String.valueOf(value)); - return; - } - if (OTEL_LOG4J_MARKER.getKey().equals(key) || OTEL_LOGBACK_MARKER.getKey().equals(key)) { - telemetryBuilder.addProperty("Marker", String.valueOf(value)); - return; - } - if (key.startsWith(JBOSS_LOGGING_MDC_PREFIX)) { - telemetryBuilder.addProperty( - key.substring(JBOSS_LOGGING_MDC_PREFIX.length()), String.valueOf(value)); - return; - } - if (key.startsWith(LOG4J1_2_MDC_PREFIX)) { - telemetryBuilder.addProperty( - key.substring(LOG4J1_2_MDC_PREFIX.length()), String.valueOf(value)); - return; - } - if (SpanDataMapper.applyCommonTags(telemetryBuilder, key, value)) { - return; - } - if (key.startsWith("applicationinsights.internal.")) { - return; - } - if (key.startsWith("thread.")) { - return; - } - if (key.startsWith("exception.")) { - return; - } - String val = SpanDataMapper.convertToString(value, attributeKey.getType()); - if (val != null) { - telemetryBuilder.addProperty(attributeKey.getKey(), val); - } - }); - } - - private static void setFunctionExtraAttributes( + private static void setFunctionExtraTraceAttributes( AbstractTelemetryBuilder telemetryBuilder, Attributes attributes) { String invocationId = attributes.get(AiSemanticAttributes.AZ_FN_INVOCATION_ID); if (invocationId != null) { diff --git a/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/Mappings.java b/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/Mappings.java new file mode 100644 index 0000000000..7cc0191f52 --- /dev/null +++ b/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/Mappings.java @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.monitor.opentelemetry.exporter.implementation; + +import com.azure.core.util.logging.ClientLogger; +import com.azure.monitor.opentelemetry.exporter.implementation.builders.AbstractTelemetryBuilder; +import com.azure.monitor.opentelemetry.exporter.implementation.utils.Trie; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributeType; +import io.opentelemetry.api.common.Attributes; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import reactor.util.annotation.Nullable; + +class Mappings { + + private static final ClientLogger logger = new ClientLogger(Mappings.class); + private static final Set unexpectedTypesLogged = ConcurrentHashMap.newKeySet(); + + private final Map exactMappings; + private final Trie prefixMappings; + + Mappings( + Map exactMappings, + Trie prefixMappings) { + this.exactMappings = exactMappings; + this.prefixMappings = prefixMappings; + } + + void map(Attributes attributes, AbstractTelemetryBuilder telemetryBuilder) { + attributes.forEach( + (attributeKey, value) -> { + map(telemetryBuilder, attributeKey, value); + }); + } + + private void map( + AbstractTelemetryBuilder telemetryBuilder, AttributeKey attributeKey, Object value) { + String key = attributeKey.getKey(); + MappingsBuilder.ExactMapping exactMapping = exactMappings.get(key); + if (exactMapping != null) { + exactMapping.map(telemetryBuilder, value); + return; + } + MappingsBuilder.PrefixMapping prefixMapping = prefixMappings.getOrNull(key); + if (prefixMapping != null) { + prefixMapping.map(telemetryBuilder, key, value); + return; + } + String val = convertToString(value, attributeKey.getType()); + if (val != null) { + telemetryBuilder.addProperty(attributeKey.getKey(), val); + } + } + + @Nullable + private static String convertToString(Object value, AttributeType type) { + switch (type) { + case STRING: + case BOOLEAN: + case LONG: + case DOUBLE: + return String.valueOf(value); + case STRING_ARRAY: + case BOOLEAN_ARRAY: + case LONG_ARRAY: + case DOUBLE_ARRAY: + return join((List) value); + } + if (unexpectedTypesLogged.add(type)) { + logger.warning("unexpected attribute type: {}", type); + } + return null; + } + + static String join(List values) { + StringBuilder sb = new StringBuilder(); + for (Object val : values) { + if (sb.length() > 0) { + sb.append(", "); + } + sb.append(val); + } + return sb.toString(); + } +} diff --git a/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/MappingsBuilder.java b/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/MappingsBuilder.java new file mode 100644 index 0000000000..6effff09a6 --- /dev/null +++ b/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/MappingsBuilder.java @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.monitor.opentelemetry.exporter.implementation; + +import static java.util.Arrays.asList; + +import com.azure.monitor.opentelemetry.exporter.implementation.builders.AbstractTelemetryBuilder; +import com.azure.monitor.opentelemetry.exporter.implementation.utils.Trie; +import io.opentelemetry.api.common.AttributeKey; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +class MappingsBuilder { + + // TODO need to keep this list in sync as new semantic conventions are defined + private static final Set STANDARD_ATTRIBUTE_PREFIXES = + new HashSet<>( + asList( + "http.", + "db.", + "message.", + "messaging.", + "rpc.", + "enduser.", + "net.", + "peer.", + "exception.", + "thread.", + "faas.", + "code.", + "job.", // proposed semantic convention which we use for job, + "applicationinsights.internal.")); + + private final Map exactMappings = new HashMap<>(); + private final Trie.Builder prefixMappings = Trie.newBuilder(); + + MappingsBuilder() { + // ignore all standard attribute prefixes + for (String prefix : STANDARD_ATTRIBUTE_PREFIXES) { + prefixMappings.put(prefix, (telemetryBuilder, key, value) -> {}); + } + } + + MappingsBuilder ignoreExact(String key) { + exactMappings.put(key, (telemetryBuilder, value) -> {}); + return this; + } + + MappingsBuilder ignorePrefix(String prefix) { + prefixMappings.put(prefix, (telemetryBuilder, key, value) -> {}); + return this; + } + + MappingsBuilder exact(String key, ExactMapping mapping) { + exactMappings.put(key, mapping); + return this; + } + + MappingsBuilder prefix(String prefix, PrefixMapping mapping) { + prefixMappings.put(prefix, mapping); + return this; + } + + MappingsBuilder exactString(AttributeKey attributeKey, String propertyName) { + exactMappings.put( + attributeKey.getKey(), + (telemetryBuilder, value) -> { + if (value instanceof String) { + telemetryBuilder.addProperty(propertyName, (String) value); + } + }); + return this; + } + + MappingsBuilder exactLong(AttributeKey attributeKey, String propertyName) { + exactMappings.put( + attributeKey.getKey(), + (telemetryBuilder, value) -> { + if (value instanceof Long) { + telemetryBuilder.addProperty(propertyName, Long.toString((Long) value)); + } + }); + return this; + } + + public Mappings build() { + return new Mappings(exactMappings, prefixMappings.build()); + } + + @FunctionalInterface + interface ExactMapping { + void map(AbstractTelemetryBuilder telemetryBuilder, Object value); + } + + @FunctionalInterface + interface PrefixMapping { + void map(AbstractTelemetryBuilder telemetryBuilder, String key, Object value); + } + + @FunctionalInterface + interface DefaultMapping { + void map(AbstractTelemetryBuilder telemetryBuilder, AttributeKey key, Object value); + } +} diff --git a/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/MetricDataMapper.java b/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/MetricDataMapper.java index a5c49ce5d2..4840a74af2 100644 --- a/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/MetricDataMapper.java +++ b/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/MetricDataMapper.java @@ -15,6 +15,8 @@ import com.azure.monitor.opentelemetry.exporter.implementation.builders.AbstractTelemetryBuilder; import com.azure.monitor.opentelemetry.exporter.implementation.builders.MetricPointBuilder; import com.azure.monitor.opentelemetry.exporter.implementation.builders.MetricTelemetryBuilder; +import com.azure.monitor.opentelemetry.exporter.implementation.configuration.ConnectionString; +import com.azure.monitor.opentelemetry.exporter.implementation.models.ContextTagKeys; import com.azure.monitor.opentelemetry.exporter.implementation.models.TelemetryItem; import com.azure.monitor.opentelemetry.exporter.implementation.preaggregatedmetrics.DependencyExtractor; import com.azure.monitor.opentelemetry.exporter.implementation.preaggregatedmetrics.RequestExtractor; @@ -37,10 +39,12 @@ public class MetricDataMapper { + private static final ClientLogger logger = new ClientLogger(MetricDataMapper.class); + private static final Set OTEL_PRE_AGGREGATED_STANDARD_METRIC_NAMES = new HashSet<>(4); private static final List EXCLUDED_METRIC_NAMES = new ArrayList<>(); - private static final ClientLogger logger = new ClientLogger(MetricDataMapper.class); + private static final Mappings MAPPINGS; private final BiConsumer telemetryInitializer; private final boolean captureHttpServer4xxAsError; @@ -52,6 +56,8 @@ public class MetricDataMapper { OTEL_PRE_AGGREGATED_STANDARD_METRIC_NAMES.add("http.client.duration"); // HttpClient OTEL_PRE_AGGREGATED_STANDARD_METRIC_NAMES.add("rpc.client.duration"); // gRPC OTEL_PRE_AGGREGATED_STANDARD_METRIC_NAMES.add("rpc.server.duration"); // gRPC + + MAPPINGS = new MappingsBuilder().build(); } public MetricDataMapper( @@ -156,7 +162,7 @@ public static void updateMetricPointBuilder( attributes.forEach( (key, value) -> - SpanDataMapper.applyConnectionStringAndRoleNameOverrides( + applyConnectionStringAndRoleNameOverrides( metricTelemetryBuilder, value, key.getKey())); if (metricData.getName().contains(".server.")) { @@ -180,24 +186,23 @@ public static void updateMetricPointBuilder( metricTelemetryBuilder, statusCode, success, dependencyType, target, isSynthetic); } } else { - setExtraAttributes(metricTelemetryBuilder, attributes); + MAPPINGS.map(attributes, metricTelemetryBuilder); } } - private static void setExtraAttributes( - AbstractTelemetryBuilder telemetryBuilder, Attributes attributes) { - attributes.forEach( - (key, value) -> { - String stringKey = key.getKey(); - if (SpanDataMapper.applyConnectionStringAndRoleNameOverrides( - telemetryBuilder, value, stringKey)) { - return; - } - String val = SpanDataMapper.convertToString(value, key.getType()); - if (value != null) { - telemetryBuilder.addProperty(key.getKey(), val); - } - }); + static boolean applyConnectionStringAndRoleNameOverrides( + AbstractTelemetryBuilder telemetryBuilder, Object value, String key) { + if (key.equals(AiSemanticAttributes.INTERNAL_CONNECTION_STRING.getKey()) + && value instanceof String) { + // intentionally letting exceptions from parse bubble up + telemetryBuilder.setConnectionString(ConnectionString.parse((String) value)); + return true; + } + if (key.equals(AiSemanticAttributes.INTERNAL_ROLE_NAME.getKey()) && value instanceof String) { + telemetryBuilder.addTag(ContextTagKeys.AI_CLOUD_ROLE.toString(), (String) value); + return true; + } + return false; } private static int getDefaultPortForHttpScheme(@Nullable String httpScheme) { diff --git a/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/SpanDataMapper.java b/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/SpanDataMapper.java index 663d136edf..61742b2348 100644 --- a/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/SpanDataMapper.java +++ b/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/SpanDataMapper.java @@ -3,10 +3,10 @@ package com.azure.monitor.opentelemetry.exporter.implementation; +import static java.util.Arrays.asList; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; -import com.azure.core.util.logging.ClientLogger; import com.azure.monitor.opentelemetry.exporter.implementation.builders.AbstractTelemetryBuilder; import com.azure.monitor.opentelemetry.exporter.implementation.builders.ExceptionTelemetryBuilder; import com.azure.monitor.opentelemetry.exporter.implementation.builders.Exceptions; @@ -19,9 +19,7 @@ import com.azure.monitor.opentelemetry.exporter.implementation.models.TelemetryItem; import com.azure.monitor.opentelemetry.exporter.implementation.utils.FormattedDuration; import com.azure.monitor.opentelemetry.exporter.implementation.utils.FormattedTime; -import com.azure.monitor.opentelemetry.exporter.implementation.utils.Trie; import com.azure.monitor.opentelemetry.exporter.implementation.utils.UrlParser; -import io.opentelemetry.api.common.AttributeType; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.SpanId; @@ -30,8 +28,6 @@ import io.opentelemetry.sdk.trace.data.EventData; import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.data.SpanData; -import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -45,48 +41,62 @@ public final class SpanDataMapper { // visible for testing public static final String MS_PROCESSED_BY_METRIC_EXTRACTORS = "_MS.ProcessedByMetricExtractors"; - private static final ClientLogger LOGGER = new ClientLogger(SpanDataMapper.class); - - private static final Set SQL_DB_SYSTEMS; - - private static final Trie STANDARD_ATTRIBUTE_PREFIX_TRIE; + private static final Set SQL_DB_SYSTEMS = + new HashSet<>( + asList( + SemanticAttributes.DbSystemValues.DB2, + SemanticAttributes.DbSystemValues.DERBY, + SemanticAttributes.DbSystemValues.MARIADB, + SemanticAttributes.DbSystemValues.MSSQL, + SemanticAttributes.DbSystemValues.MYSQL, + SemanticAttributes.DbSystemValues.ORACLE, + SemanticAttributes.DbSystemValues.POSTGRESQL, + SemanticAttributes.DbSystemValues.SQLITE, + SemanticAttributes.DbSystemValues.OTHER_SQL, + SemanticAttributes.DbSystemValues.HSQLDB, + SemanticAttributes.DbSystemValues.H2)); + + private static final Mappings MAPPINGS; // TODO (trask) add to generated ContextTagKeys class private static final ContextTagKeys AI_DEVICE_OS = ContextTagKeys.fromString("ai.device.os"); static { - Set dbSystems = new HashSet<>(); - dbSystems.add(SemanticAttributes.DbSystemValues.DB2); - dbSystems.add(SemanticAttributes.DbSystemValues.DERBY); - dbSystems.add(SemanticAttributes.DbSystemValues.MARIADB); - dbSystems.add(SemanticAttributes.DbSystemValues.MSSQL); - dbSystems.add(SemanticAttributes.DbSystemValues.MYSQL); - dbSystems.add(SemanticAttributes.DbSystemValues.ORACLE); - dbSystems.add(SemanticAttributes.DbSystemValues.POSTGRESQL); - dbSystems.add(SemanticAttributes.DbSystemValues.SQLITE); - dbSystems.add(SemanticAttributes.DbSystemValues.OTHER_SQL); - dbSystems.add(SemanticAttributes.DbSystemValues.HSQLDB); - dbSystems.add(SemanticAttributes.DbSystemValues.H2); - - SQL_DB_SYSTEMS = Collections.unmodifiableSet(dbSystems); - - // TODO need to keep this list in sync as new semantic conventions are defined - STANDARD_ATTRIBUTE_PREFIX_TRIE = - Trie.newBuilder() - .put("http.", true) - .put("db.", true) - .put("message.", true) - .put("messaging.", true) - .put("rpc.", true) - .put("enduser.", true) - .put("net.", true) - .put("peer.", true) - .put("exception.", true) - .put("thread.", true) - .put("faas.", true) - .put("code.", true) - .put("job.", true) // proposed semantic convention which we use for job.system - .build(); + MappingsBuilder mappingsBuilder = + new MappingsBuilder() + // these are from azure SDK (AZURE_SDK_PEER_ADDRESS gets filtered out automatically + // since it uses the otel "peer." prefix) + .ignoreExact(AiSemanticAttributes.AZURE_SDK_NAMESPACE.getKey()) + .ignoreExact(AiSemanticAttributes.AZURE_SDK_MESSAGE_BUS_DESTINATION.getKey()) + .ignoreExact(AiSemanticAttributes.AZURE_SDK_ENQUEUED_TIME.getKey()) + .ignoreExact(AiSemanticAttributes.KAFKA_RECORD_QUEUE_TIME_MS.getKey()) + .ignoreExact(AiSemanticAttributes.KAFKA_OFFSET.getKey()) + .exact( + SemanticAttributes.HTTP_USER_AGENT.getKey(), + (builder, value) -> { + if (value instanceof String) { + builder.addTag("ai.user.userAgent", (String) value); + } + }) + .ignorePrefix("applicationinsights.internal.") + .prefix( + "http.request.header.", + (telemetryBuilder, key, value) -> { + if (value instanceof List) { + telemetryBuilder.addProperty(key, Mappings.join((List) value)); + } + }) + .prefix( + "http.response.header.", + (telemetryBuilder, key, value) -> { + if (value instanceof List) { + telemetryBuilder.addProperty(key, Mappings.join((List) value)); + } + }); + + applyCommonTags(mappingsBuilder); + + MAPPINGS = mappingsBuilder.build(); } private final boolean captureHttpServer4xxAsError; @@ -142,7 +152,7 @@ private TelemetryItem exportRemoteDependency(SpanData span, boolean inProc, long setItemCount(telemetryBuilder, itemCount); // update tags - setExtraAttributes(telemetryBuilder, span.getAttributes()); + MAPPINGS.map(span.getAttributes(), telemetryBuilder); addLinks(telemetryBuilder, span.getLinks()); @@ -168,7 +178,7 @@ private TelemetryItem exportRemoteDependency(SpanData span, boolean inProc, long private static final Set DEFAULT_HTTP_SPAN_NAMES = new HashSet<>( - Arrays.asList( + asList( "HTTP OPTIONS", "HTTP GET", "HTTP HEAD", @@ -457,7 +467,7 @@ private TelemetryItem exportRequest(SpanData span, long itemCount) { setItemCount(telemetryBuilder, itemCount); // update tags - setExtraAttributes(telemetryBuilder, attributes); + MAPPINGS.map(attributes, telemetryBuilder); addLinks(telemetryBuilder, span.getLinks()); @@ -707,7 +717,7 @@ private void exportEvents( setItemCount(telemetryBuilder, itemCount); // update tags - setExtraAttributes(telemetryBuilder, event.getAttributes()); + MAPPINGS.map(event.getAttributes(), telemetryBuilder); // set message-specific properties telemetryBuilder.setMessage(event.getName()); @@ -732,7 +742,8 @@ private TelemetryItem createExceptionTelemetryItem( } setTime(telemetryBuilder, span.getEndEpochNanos()); setItemCount(telemetryBuilder, itemCount); - setExtraAttributes(telemetryBuilder, span.getAttributes()); + + MAPPINGS.map(span.getAttributes(), telemetryBuilder); // set exception-specific properties telemetryBuilder.setExceptions(Exceptions.minimalParse(errorStack)); @@ -777,63 +788,25 @@ private static void addLinks(AbstractTelemetryBuilder telemetryBuilder, List { - String key = attributeKey.getKey(); - if (key.equals(AiSemanticAttributes.AZURE_SDK_NAMESPACE.getKey()) - || key.equals(AiSemanticAttributes.AZURE_SDK_MESSAGE_BUS_DESTINATION.getKey()) - || key.equals(AiSemanticAttributes.AZURE_SDK_ENQUEUED_TIME.getKey())) { - // these are from azure SDK (AZURE_SDK_PEER_ADDRESS gets filtered out automatically - // since it uses the otel "peer." prefix) - return; - } - if (key.equals(AiSemanticAttributes.KAFKA_RECORD_QUEUE_TIME_MS.getKey()) - || key.equals(AiSemanticAttributes.KAFKA_OFFSET.getKey())) { - return; - } - if (key.equals(AiSemanticAttributes.REQUEST_CONTEXT.getKey())) { - return; - } - if (key.equals(SemanticAttributes.HTTP_USER_AGENT.getKey()) && value instanceof String) { - telemetryBuilder.addTag("ai.user.userAgent", (String) value); - return; - } - if (applyCommonTags(telemetryBuilder, key, value)) { - return; - } - if (key.startsWith("applicationinsights.internal.")) { - return; - } - if (STANDARD_ATTRIBUTE_PREFIX_TRIE.getOrDefault(key, false) - && !key.startsWith("http.request.header.") - && !key.startsWith("http.response.header.")) { - return; - } - String val = convertToString(value, attributeKey.getType()); - if (value != null) { - telemetryBuilder.addProperty(attributeKey.getKey(), val); - } - }); - } - - static boolean applyCommonTags( - AbstractTelemetryBuilder telemetryBuilder, String key, Object value) { + static void applyCommonTags(MappingsBuilder mappingsBuilder) { + mappingsBuilder + .exact( + SemanticAttributes.ENDUSER_ID.getKey(), + (telemetryBuilder, value) -> { + if (value instanceof String) { + telemetryBuilder.addTag(ContextTagKeys.AI_USER_ID.toString(), (String) value); + } + }) + .exact( + AiSemanticAttributes.PREVIEW_APPLICATION_VERSION.getKey(), + (telemetryBuilder, value) -> { + if (value instanceof String) { + telemetryBuilder.addTag( + ContextTagKeys.AI_APPLICATION_VER.toString(), (String) value); + } + }); - if (key.equals(SemanticAttributes.ENDUSER_ID.getKey()) && value instanceof String) { - telemetryBuilder.addTag(ContextTagKeys.AI_USER_ID.toString(), (String) value); - return true; - } - if (applyConnectionStringAndRoleNameOverrides(telemetryBuilder, value, key)) { - return true; - } - if (key.equals(AiSemanticAttributes.PREVIEW_APPLICATION_VERSION.getKey()) - && value instanceof String) { - telemetryBuilder.addTag(ContextTagKeys.AI_APPLICATION_VER.toString(), (String) value); - return true; - } - return false; + applyConnectionStringAndRoleNameOverrides(mappingsBuilder); } private static final WarningLogger connectionStringAttributeNoLongerSupported = @@ -872,64 +845,40 @@ static boolean applyCommonTags( + " https://github.com/microsoft/ApplicationInsights-Java/issues if you have a" + " different use case."); - static boolean applyConnectionStringAndRoleNameOverrides( - AbstractTelemetryBuilder telemetryBuilder, Object value, String key) { - if (key.equals(AiSemanticAttributes.INTERNAL_CONNECTION_STRING.getKey()) - && value instanceof String) { - // intentionally letting exceptions from parse bubble up - telemetryBuilder.setConnectionString(ConnectionString.parse((String) value)); - return true; - } - if (key.equals(AiSemanticAttributes.INTERNAL_ROLE_NAME.getKey()) && value instanceof String) { - telemetryBuilder.addTag(ContextTagKeys.AI_CLOUD_ROLE.toString(), (String) value); - return true; - } - if (key.equals(AiSemanticAttributes.DEPRECATED_CONNECTION_STRING.getKey())) { - connectionStringAttributeNoLongerSupported.recordWarning(); - return true; - } - if (key.equals(AiSemanticAttributes.DEPRECATED_ROLE_NAME.getKey())) { - roleNameAttributeNoLongerSupported.recordWarning(); - return true; - } - if (key.equals(AiSemanticAttributes.DEPRECATED_ROLE_INSTANCE.getKey()) - && value instanceof String) { - roleInstanceAttributeNoLongerSupported.recordWarning(); - return true; - } - if (key.equals(AiSemanticAttributes.DEPRECATED_INSTRUMENTATION_KEY.getKey())) { - instrumentationKeyAttributeNoLongerSupported.recordWarning(); - return true; - } - return false; - } - - @Nullable - public static String convertToString(Object value, AttributeType type) { - switch (type) { - case STRING: - case BOOLEAN: - case LONG: - case DOUBLE: - return String.valueOf(value); - case STRING_ARRAY: - case BOOLEAN_ARRAY: - case LONG_ARRAY: - case DOUBLE_ARRAY: - return join((List) value); - } - LOGGER.warning("unexpected attribute type: {}", type); - return null; - } - - private static String join(List values) { - StringBuilder sb = new StringBuilder(); - for (Object val : values) { - if (sb.length() > 0) { - sb.append(", "); - } - sb.append(val); - } - return sb.toString(); + static void applyConnectionStringAndRoleNameOverrides(MappingsBuilder mappingsBuilder) { + mappingsBuilder + .exact( + AiSemanticAttributes.INTERNAL_CONNECTION_STRING.getKey(), + (telemetryBuilder, value) -> { + // intentionally letting exceptions from parse bubble up + telemetryBuilder.setConnectionString(ConnectionString.parse((String) value)); + }) + .exact( + AiSemanticAttributes.INTERNAL_ROLE_NAME.getKey(), + (telemetryBuilder, value) -> { + if (value instanceof String) { + telemetryBuilder.addTag(ContextTagKeys.AI_CLOUD_ROLE.toString(), (String) value); + } + }) + .exact( + AiSemanticAttributes.DEPRECATED_CONNECTION_STRING.getKey(), + (telemetryBuilder, value) -> { + connectionStringAttributeNoLongerSupported.recordWarning(); + }) + .exact( + AiSemanticAttributes.DEPRECATED_ROLE_NAME.getKey(), + (telemetryBuilder, value) -> { + roleNameAttributeNoLongerSupported.recordWarning(); + }) + .exact( + AiSemanticAttributes.DEPRECATED_ROLE_INSTANCE.getKey(), + (telemetryBuilder, value) -> { + roleInstanceAttributeNoLongerSupported.recordWarning(); + }) + .exact( + AiSemanticAttributes.DEPRECATED_INSTRUMENTATION_KEY.getKey(), + (telemetryBuilder, value) -> { + instrumentationKeyAttributeNoLongerSupported.recordWarning(); + }); } }