diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/LogDataMapper.java b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/LogDataMapper.java index 7a1f7a3521e6..3522461903a1 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/LogDataMapper.java +++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/LogDataMapper.java @@ -11,6 +11,7 @@ import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.ContextTagKeys; import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.SeverityLevel; import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryItem; +import com.azure.monitor.opentelemetry.autoconfigure.implementation.semconv.CodeAttributes; import com.azure.monitor.opentelemetry.autoconfigure.implementation.semconv.incubating.CodeIncubatingAttributes; import com.azure.monitor.opentelemetry.autoconfigure.implementation.semconv.incubating.ThreadIncubatingAttributes; import com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.FormattedTime; @@ -67,6 +68,13 @@ public class LogDataMapper { .exactString(CodeIncubatingAttributes.CODE_NAMESPACE, "ClassName") .exactString(CodeIncubatingAttributes.CODE_FUNCTION, "MethodName") .exactLong(CodeIncubatingAttributes.CODE_LINENO, "LineNumber") + .exactString(CodeAttributes.CODE_FILE_PATH, "FileName") + .exactLong(CodeAttributes.CODE_LINE_NUMBER, "LineNumber") + .exact(CodeAttributes.CODE_FUNCTION_NAME.getKey(), (telemetryBuilder, value) -> { + if (value instanceof String) { + addFunctionNameProperties(telemetryBuilder, (String) value); + } + }) .exactString(LOG4J_MARKER, "Marker") .exactStringArray(LOGBACK_MARKER, "Marker"); @@ -196,6 +204,25 @@ private static void setOperationName(AbstractTelemetryBuilder telemetryBuilder, } } + private static void addFunctionNameProperties(AbstractTelemetryBuilder telemetryBuilder, String functionName) { + int separatorIndex = functionName.lastIndexOf('.'); + + if (separatorIndex == -1) { + telemetryBuilder.addProperty("MethodName", functionName); + return; + } + + String methodName = functionName.substring(separatorIndex + 1); + if (!methodName.isEmpty()) { + telemetryBuilder.addProperty("MethodName", methodName); + } + + String className = functionName.substring(0, separatorIndex); + if (!className.isEmpty()) { + telemetryBuilder.addProperty("ClassName", className); + } + } + private static void setTime(AbstractTelemetryBuilder telemetryBuilder, LogRecordData log) { telemetryBuilder.setTime(FormattedTime.offSetDateTimeFromEpochNanos(getTimestampEpochNanosWithFallback(log))); } diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/test/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/logging/LogDataMapperTest.java b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/test/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/logging/LogDataMapperTest.java index 355163e72dd9..6d791686893f 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/test/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/logging/LogDataMapperTest.java +++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/test/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/logging/LogDataMapperTest.java @@ -5,22 +5,22 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; -import java.time.Instant; +import java.util.Map; import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.SpanId; -import io.opentelemetry.api.trace.TraceFlags; -import io.opentelemetry.api.trace.TraceId; -import io.opentelemetry.api.trace.TraceState; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.Body; import io.opentelemetry.sdk.logs.data.LogRecordData; import org.junit.jupiter.api.Test; import com.azure.monitor.opentelemetry.autoconfigure.implementation.LogDataMapper; +import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.MessageData; import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryEventData; import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryItem; +import com.azure.monitor.opentelemetry.autoconfigure.implementation.semconv.CodeAttributes; +import com.azure.monitor.opentelemetry.autoconfigure.implementation.semconv.incubating.CodeIncubatingAttributes; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.logs.Severity; @@ -30,58 +30,9 @@ class LogDataMapperTest { @Test void testCustomEventName() { - LogRecordData logRecordData = new LogRecordData() { - @Override - public Resource getResource() { - return Resource.empty(); - } - - @Override - public Attributes getAttributes() { - return Attributes.builder().put("microsoft.custom_event.name", "TestEvent").build(); - } - - @Override - public InstrumentationScopeInfo getInstrumentationScopeInfo() { - return InstrumentationScopeInfo.create("TestScope", null, null); - } - - @Override - public long getTimestampEpochNanos() { - return Instant.now().toEpochMilli() * 1_000_000; // Convert millis to nanos - } - - @Override - public long getObservedTimestampEpochNanos() { - return Instant.now().toEpochMilli() * 1_000_000; - } - - @Override - public SpanContext getSpanContext() { - return SpanContext.create(TraceId.fromLongs(12345L, 67890L), SpanId.fromLong(12345L), - TraceFlags.getDefault(), TraceState.getDefault()); - } - - @Override - public Severity getSeverity() { - return Severity.INFO; - } - - @Override - public String getSeverityText() { - return "INFO"; - } - - @Override - public Body getBody() { - return Body.string("Test log message"); - } - - @Override - public int getTotalAttributeCount() { - return 1; - } - }; + Attributes attributes = Attributes.builder().put("microsoft.custom_event.name", "TestEvent").build(); + + LogRecordData logRecordData = new SimpleLogRecordData(attributes); LogDataMapper logDataMapper = new LogDataMapper(true, true, (b, r) -> { // Initialize telemetry builder with resource @@ -96,4 +47,127 @@ public int getTotalAttributeCount() { TelemetryEventData eventData = (TelemetryEventData) result.getData().getBaseData(); assertEquals("TestEvent", eventData.getName()); } + + @Test + void testStableCodeAttributesMappedToProperties() { + Attributes attributes = Attributes.builder() + .put(CodeAttributes.CODE_FILE_PATH, "/src/main/java/com/example/App.java") + .put(CodeAttributes.CODE_FUNCTION_NAME, "com.example.App.process") + .put(CodeAttributes.CODE_LINE_NUMBER, 42L) + .build(); + + LogDataMapper logDataMapper = new LogDataMapper(false, false, (b, r) -> { + }); + + TelemetryItem telemetryItem = logDataMapper.map(new SimpleLogRecordData(attributes), null, null); + + MessageData messageData = (MessageData) telemetryItem.getData().getBaseData(); + Map properties = messageData.getProperties(); + + assertNotNull(properties); + assertEquals("/src/main/java/com/example/App.java", properties.get("FileName")); + assertEquals("com.example.App", properties.get("ClassName")); + assertEquals("process", properties.get("MethodName")); + assertEquals("42", properties.get("LineNumber")); + } + + @Test + void testIncubatingCodeAttributesStillMapped() { + Attributes attributes = Attributes.builder() + .put(CodeIncubatingAttributes.CODE_FILEPATH, "/src/main/java/com/example/App.java") + .put(CodeIncubatingAttributes.CODE_NAMESPACE, "com.example.App") + .put(CodeIncubatingAttributes.CODE_FUNCTION, "process") + .put(CodeIncubatingAttributes.CODE_LINENO, 42L) + .build(); + + LogDataMapper logDataMapper = new LogDataMapper(false, false, (b, r) -> { + }); + + TelemetryItem telemetryItem = logDataMapper.map(new SimpleLogRecordData(attributes), null, null); + + MessageData messageData = (MessageData) telemetryItem.getData().getBaseData(); + Map properties = messageData.getProperties(); + + assertNotNull(properties); + assertEquals("/src/main/java/com/example/App.java", properties.get("FileName")); + assertEquals("com.example.App", properties.get("ClassName")); + assertEquals("process", properties.get("MethodName")); + assertEquals("42", properties.get("LineNumber")); + } + + @Test + void testStableCodeFunctionWithoutNamespace() { + Attributes attributes = Attributes.builder().put(CodeAttributes.CODE_FUNCTION_NAME, "fopen").build(); + + LogDataMapper logDataMapper = new LogDataMapper(false, false, (b, r) -> { + }); + + TelemetryItem telemetryItem = logDataMapper.map(new SimpleLogRecordData(attributes), null, null); + + MessageData messageData = (MessageData) telemetryItem.getData().getBaseData(); + Map properties = messageData.getProperties(); + + assertNotNull(properties); + assertEquals("fopen", properties.get("MethodName")); + assertNull(properties.get("ClassName")); + } + + private static final class SimpleLogRecordData implements LogRecordData { + + private final Attributes attributes; + + private SimpleLogRecordData(Attributes attributes) { + this.attributes = attributes; + } + + @Override + public Resource getResource() { + return Resource.empty(); + } + + @Override + public Attributes getAttributes() { + return attributes; + } + + @Override + public InstrumentationScopeInfo getInstrumentationScopeInfo() { + return InstrumentationScopeInfo.create("TestScope", null, null); + } + + @Override + public long getTimestampEpochNanos() { + return 0; + } + + @Override + public long getObservedTimestampEpochNanos() { + return 0; + } + + @Override + public SpanContext getSpanContext() { + return SpanContext.getInvalid(); + } + + @Override + public Severity getSeverity() { + return Severity.INFO; + } + + @Override + public String getSeverityText() { + return Severity.INFO.name(); + } + + @Override + public Body getBody() { + return Body.empty(); + } + + @Override + public int getTotalAttributeCount() { + return attributes.size(); + } + } }