Skip to content

Commit 922364f

Browse files
Support Emitting Application Insights Custom Events (Azure#44262)
* Support Emitting Application Insights Custom Events from OpenTelemetry LogRecords * Update changelog + checkstyle suppressions. * Change order of exception check * Fix link in CHANGELOG.md file * Fix test
1 parent 5979313 commit 922364f

File tree

5 files changed

+199
-5
lines changed

5 files changed

+199
-5
lines changed

sdk/monitor/azure-monitor-opentelemetry-autoconfigure/checkstyle-suppressions.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
<suppress files="com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.ResourceParser.java" checks="IllegalImportCheck" />
116116
<suppress files="com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.ResourceParserTest.java" checks="IllegalImportCheck" />
117117
<suppress files="com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.TestUtils.java" checks="IllegalImportCheck" />
118+
<suppress files="com.azure.monitor.opentelemetry.autoconfigure.implementation.logging.LogDataMapperTest.java" checks="IllegalImportCheck" />
118119
<suppress files="com.azure.monitor.opentelemetry.autoconfigure.implementation.AttributeKeyTemplate.java" checks="JavadocMethodCheck" />
119120
<suppress files="com.azure.monitor.opentelemetry.autoconfigure.implementation.SamplingScoreGeneratorV2.java" checks="JavadocMethodCheck" />
120121
<suppress files="com.azure.monitor.opentelemetry.autoconfigure.implementation.heartbeat.HeartBeatPropertyPayload.java" checks="JavadocMethodCheck" />

sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/LogDataMapper.java

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import com.azure.core.util.logging.ClientLogger;
77
import com.azure.monitor.opentelemetry.autoconfigure.implementation.builders.AbstractTelemetryBuilder;
8+
import com.azure.monitor.opentelemetry.autoconfigure.implementation.builders.EventTelemetryBuilder;
89
import com.azure.monitor.opentelemetry.autoconfigure.implementation.builders.ExceptionTelemetryBuilder;
910
import com.azure.monitor.opentelemetry.autoconfigure.implementation.builders.MessageTelemetryBuilder;
1011
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.ContextTagKeys;
@@ -39,6 +40,8 @@ public class LogDataMapper {
3940
private static final AttributeKey<String> LOG4J_MARKER = stringKey("log4j.marker");
4041
private static final AttributeKey<List<String>> LOGBACK_MARKER = stringArrayKey("logback.marker");
4142

43+
private static final String CUSTOM_EVENT_NAME = "microsoft.custom_event.name";
44+
4245
private static final Mappings MAPPINGS;
4346

4447
static {
@@ -86,14 +89,44 @@ public TelemetryItem map(LogRecordData log, @Nullable String stack, @Nullable Do
8689
if (sampleRate == null) {
8790
sampleRate = getSampleRate(log);
8891
}
89-
if (stack == null) {
90-
return createMessageTelemetryItem(log, sampleRate);
91-
} else {
92+
93+
if (stack != null) {
9294
return createExceptionTelemetryItem(log, stack, sampleRate);
9395
}
96+
97+
Attributes attributes = log.getAttributes();
98+
String customEventName = attributes.get(AttributeKey.stringKey(CUSTOM_EVENT_NAME));
99+
if (customEventName != null) {
100+
return createEventTelemetryItem(log, attributes, customEventName, sampleRate);
101+
}
102+
103+
return createMessageTelemetryItem(log, attributes, sampleRate);
104+
}
105+
106+
public TelemetryItem createEventTelemetryItem(LogRecordData log, Attributes attributes, String eventName,
107+
@Nullable Double sampleRate) {
108+
EventTelemetryBuilder telemetryBuilder = EventTelemetryBuilder.create();
109+
telemetryInitializer.accept(telemetryBuilder, log.getResource());
110+
111+
// set standard properties
112+
setOperationTags(telemetryBuilder, log);
113+
setTime(telemetryBuilder, log);
114+
setSampleRate(telemetryBuilder, sampleRate);
115+
116+
// update tags
117+
if (captureAzureFunctionsAttributes) {
118+
setFunctionExtraTraceAttributes(telemetryBuilder, attributes);
119+
}
120+
MAPPINGS.map(attributes, telemetryBuilder);
121+
122+
// set event-specific properties
123+
telemetryBuilder.setName(eventName);
124+
125+
return telemetryBuilder.build();
94126
}
95127

96-
private TelemetryItem createMessageTelemetryItem(LogRecordData log, @Nullable Double sampleRate) {
128+
private TelemetryItem createMessageTelemetryItem(LogRecordData log, Attributes attributes,
129+
@Nullable Double sampleRate) {
97130
MessageTelemetryBuilder telemetryBuilder = MessageTelemetryBuilder.create();
98131
telemetryInitializer.accept(telemetryBuilder, log.getResource());
99132

@@ -103,7 +136,6 @@ private TelemetryItem createMessageTelemetryItem(LogRecordData log, @Nullable Do
103136
setSampleRate(telemetryBuilder, sampleRate);
104137

105138
// update tags
106-
Attributes attributes = log.getAttributes();
107139
if (captureAzureFunctionsAttributes) {
108140
setFunctionExtraTraceAttributes(telemetryBuilder, attributes);
109141
}

sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/test/java/com/azure/monitor/opentelemetry/autoconfigure/AzureMonitorExportersEndToEndTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.MessageData;
1212
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.MetricsData;
1313
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.RemoteDependencyData;
14+
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryEventData;
1415
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryItem;
1516
import com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.TestUtils;
1617
import io.opentelemetry.api.OpenTelemetry;
@@ -139,6 +140,33 @@ public void testBuildLogExporter() throws Exception {
139140
validateLog(logTelemetryItem);
140141
}
141142

143+
@Test
144+
public void testBuildLogExporterWithCustomEvent() throws Exception {
145+
// create the OpenTelemetry SDK
146+
CountDownLatch countDownLatch = new CountDownLatch(1);
147+
CustomValidationPolicy customValidationPolicy = new CustomValidationPolicy(countDownLatch);
148+
OpenTelemetry openTelemetry
149+
= TestUtils.createOpenTelemetrySdk(getHttpPipeline(customValidationPolicy), getConfiguration());
150+
151+
// generate a log
152+
generateEvent(openTelemetry);
153+
154+
// wait for export
155+
countDownLatch.await(10, SECONDS);
156+
assertThat(customValidationPolicy.getUrl())
157+
.isEqualTo(new URL("https://test.in.applicationinsights.azure.com/v2.1/track"));
158+
assertThat(customValidationPolicy.getActualTelemetryItems().size()).isEqualTo(1);
159+
160+
// validate log
161+
TelemetryItem eventTelemetryItem = customValidationPolicy.getActualTelemetryItems()
162+
.stream()
163+
.filter(item -> item.getName().equals("Event"))
164+
.findFirst()
165+
.get();
166+
167+
validateEvent(eventTelemetryItem);
168+
}
169+
142170
@Test
143171
public void testBuildTraceMetricLogExportersConsecutively() throws Exception {
144172
// create the OpenTelemetry SDK
@@ -211,6 +239,15 @@ private static void generateLog(OpenTelemetry openTelemetry) {
211239
.emit();
212240
}
213241

242+
private static void generateEvent(OpenTelemetry openTelemetry) {
243+
Logger logger = openTelemetry.getLogsBridge().get("Sample");
244+
logger.logRecordBuilder()
245+
.setBody("TestEventBody")
246+
.setAttribute(AttributeKey.stringKey("microsoft.custom_event.name"), "TestEvent")
247+
.setAttribute(AttributeKey.stringKey("name"), "apple")
248+
.emit();
249+
}
250+
214251
private static void validateSpan(TelemetryItem telemetryItem) {
215252
assertThat(telemetryItem.getName()).isEqualTo("RemoteDependency");
216253
assertThat(telemetryItem.getInstrumentationKey()).isEqualTo(INSTRUMENTATION_KEY);
@@ -252,6 +289,21 @@ private static void validateLog(TelemetryItem telemetryItem) {
252289
entry("SourceType", "Logger"), entry("color", "red"), entry("name", "apple"));
253290
}
254291

292+
private static void validateEvent(TelemetryItem telemetryItem) {
293+
assertThat(telemetryItem.getName()).isEqualTo("Event");
294+
assertThat(telemetryItem.getInstrumentationKey()).isEqualTo(INSTRUMENTATION_KEY);
295+
assertThat(telemetryItem.getTags()).containsEntry("ai.cloud.role", "unknown_service:java");
296+
assertThat(telemetryItem.getTags()).hasEntrySatisfying("ai.internal.sdkVersion",
297+
v -> assertThat(v).contains("otel"));
298+
assertThat(telemetryItem.getData().getBaseType()).isEqualTo("EventData");
299+
300+
TelemetryEventData eventData = TestUtils.toTelemetryEventData(telemetryItem.getData().getBaseData());
301+
System.out.println("Actual Event Properties: " + eventData.getProperties());
302+
assertThat(eventData.getName()).isEqualTo("TestEvent");
303+
assertThat(eventData.getProperties()).containsOnly(entry("name", "apple"),
304+
entry("microsoft.custom_event.name", "TestEvent"));
305+
}
306+
255307
private static Map<String, String> getConfiguration() {
256308
return Collections.singletonMap("APPLICATIONINSIGHTS_CONNECTION_STRING", CONNECTION_STRING_ENV);
257309
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.monitor.opentelemetry.autoconfigure.implementation.logging;
5+
6+
import static org.junit.jupiter.api.Assertions.assertEquals;
7+
import static org.junit.jupiter.api.Assertions.assertNotNull;
8+
9+
import java.time.Instant;
10+
11+
import io.opentelemetry.api.trace.SpanContext;
12+
import io.opentelemetry.api.trace.SpanId;
13+
import io.opentelemetry.api.trace.TraceFlags;
14+
import io.opentelemetry.api.trace.TraceId;
15+
import io.opentelemetry.api.trace.TraceState;
16+
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
17+
import io.opentelemetry.sdk.logs.data.Body;
18+
import io.opentelemetry.sdk.logs.data.LogRecordData;
19+
import org.junit.jupiter.api.Test;
20+
21+
import com.azure.monitor.opentelemetry.autoconfigure.implementation.LogDataMapper;
22+
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryEventData;
23+
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryItem;
24+
25+
import io.opentelemetry.api.common.Attributes;
26+
import io.opentelemetry.api.logs.Severity;
27+
import io.opentelemetry.sdk.resources.Resource;
28+
29+
@SuppressWarnings("checkstyle:IllegalImport")
30+
class LogDataMapperTest {
31+
@Test
32+
void testCustomEventName() {
33+
LogRecordData logRecordData = new LogRecordData() {
34+
@Override
35+
public Resource getResource() {
36+
return Resource.empty();
37+
}
38+
39+
@Override
40+
public Attributes getAttributes() {
41+
return Attributes.builder().put("microsoft.custom_event.name", "TestEvent").build();
42+
}
43+
44+
@Override
45+
public InstrumentationScopeInfo getInstrumentationScopeInfo() {
46+
return InstrumentationScopeInfo.create("TestScope", null, null);
47+
}
48+
49+
@Override
50+
public long getTimestampEpochNanos() {
51+
return Instant.now().toEpochMilli() * 1_000_000; // Convert millis to nanos
52+
}
53+
54+
@Override
55+
public long getObservedTimestampEpochNanos() {
56+
return Instant.now().toEpochMilli() * 1_000_000;
57+
}
58+
59+
@Override
60+
public SpanContext getSpanContext() {
61+
return SpanContext.create(TraceId.fromLongs(12345L, 67890L), SpanId.fromLong(12345L),
62+
TraceFlags.getDefault(), TraceState.getDefault());
63+
}
64+
65+
@Override
66+
public Severity getSeverity() {
67+
return Severity.INFO;
68+
}
69+
70+
@Override
71+
public String getSeverityText() {
72+
return "INFO";
73+
}
74+
75+
@Override
76+
public Body getBody() {
77+
return Body.string("Test log message");
78+
}
79+
80+
@Override
81+
public int getTotalAttributeCount() {
82+
return 1;
83+
}
84+
};
85+
86+
LogDataMapper logDataMapper = new LogDataMapper(true, true, (b, r) -> {
87+
// Initialize telemetry builder with resource
88+
});
89+
90+
TelemetryItem result = logDataMapper.map(logRecordData, null, null);
91+
92+
assertNotNull(result);
93+
assertEquals("Event", result.getName());
94+
95+
// Convert result.getData().getBaseData() to TelemetryEventData and get the name property and validate against "TestEvent".
96+
TelemetryEventData eventData = (TelemetryEventData) result.getData().getBaseData();
97+
assertEquals("TestEvent", eventData.getName());
98+
}
99+
}

sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/test/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/utils/TestUtils.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.MonitorBase;
1616
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.MonitorDomain;
1717
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.RemoteDependencyData;
18+
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryEventData;
1819
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryItem;
1920
import io.opentelemetry.sdk.OpenTelemetrySdk;
2021
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
@@ -123,6 +124,15 @@ public static MessageData toMessageData(MonitorDomain baseData) {
123124
}
124125
}
125126

127+
// azure-json doesn't deserialize subtypes yet, so need to convert the abstract MonitorDomain to MessageData
128+
public static TelemetryEventData toTelemetryEventData(MonitorDomain baseData) {
129+
try (JsonReader jsonReader = JsonProviders.createReader(baseData.toJsonString())) {
130+
return TelemetryEventData.fromJson(jsonReader);
131+
} catch (IOException e) {
132+
throw new RuntimeException(e);
133+
}
134+
}
135+
126136
// deserialize multiple TelemetryItem raw bytes with newline delimiters to a list of TelemetryItems
127137
public static List<TelemetryItem> deserialize(byte[] rawBytes) {
128138
try (JsonReader jsonReader = JsonProviders.createReader(rawBytes)) {

0 commit comments

Comments
 (0)