Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Emitting Application Insights Custom Events #44262

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

### Features Added

* Enabled support for emitting Application Insights Custom Events from
OpenTelemetry LogRecords when the `microsoft.custom_event.name` attribute is
present.
([#44262](https://github.com/Azure/azure-sdk-for-net/pull/44262))

### Breaking Changes

### Bugs Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
<suppress files="com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.ResourceParser.java" checks="IllegalImportCheck" />
<suppress files="com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.ResourceParserTest.java" checks="IllegalImportCheck" />
<suppress files="com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.TestUtils.java" checks="IllegalImportCheck" />
<suppress files="com.azure.monitor.opentelemetry.autoconfigure.implementation.logging.LogDataMapperTest.java" checks="IllegalImportCheck" />
<suppress files="com.azure.monitor.opentelemetry.autoconfigure.implementation.AttributeKeyTemplate.java" checks="JavadocMethodCheck" />
<suppress files="com.azure.monitor.opentelemetry.autoconfigure.implementation.SamplingScoreGeneratorV2.java" checks="JavadocMethodCheck" />
<suppress files="com.azure.monitor.opentelemetry.autoconfigure.implementation.heartbeat.HeartBeatPropertyPayload.java" checks="JavadocMethodCheck" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

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

private static final String CUSTOM_EVENT_NAME = "microsoft.custom_event.name";

private static final Mappings MAPPINGS;

static {
Expand Down Expand Up @@ -86,14 +89,44 @@ public TelemetryItem map(LogRecordData log, @Nullable String stack, @Nullable Do
if (sampleRate == null) {
sampleRate = getSampleRate(log);
}
if (stack == null) {
return createMessageTelemetryItem(log, sampleRate);
} else {

if (stack != null) {
return createExceptionTelemetryItem(log, stack, sampleRate);
}

Attributes attributes = log.getAttributes();
String customEventName = attributes.get(AttributeKey.stringKey(CUSTOM_EVENT_NAME));
if (customEventName != null) {
return createEventTelemetryItem(log, attributes, customEventName, sampleRate);
}

return createMessageTelemetryItem(log, attributes, sampleRate);
}

public TelemetryItem createEventTelemetryItem(LogRecordData log, Attributes attributes, String eventName,
@Nullable Double sampleRate) {
EventTelemetryBuilder telemetryBuilder = EventTelemetryBuilder.create();
telemetryInitializer.accept(telemetryBuilder, log.getResource());

// set standard properties
setOperationTags(telemetryBuilder, log);
setTime(telemetryBuilder, log);
setSampleRate(telemetryBuilder, sampleRate);

// update tags
if (captureAzureFunctionsAttributes) {
setFunctionExtraTraceAttributes(telemetryBuilder, attributes);
}
MAPPINGS.map(attributes, telemetryBuilder);

// set event-specific properties
telemetryBuilder.setName(eventName);

return telemetryBuilder.build();
}

private TelemetryItem createMessageTelemetryItem(LogRecordData log, @Nullable Double sampleRate) {
private TelemetryItem createMessageTelemetryItem(LogRecordData log, Attributes attributes,
@Nullable Double sampleRate) {
MessageTelemetryBuilder telemetryBuilder = MessageTelemetryBuilder.create();
telemetryInitializer.accept(telemetryBuilder, log.getResource());

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

// update tags
Attributes attributes = log.getAttributes();
if (captureAzureFunctionsAttributes) {
setFunctionExtraTraceAttributes(telemetryBuilder, attributes);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.MessageData;
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.MetricsData;
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.RemoteDependencyData;
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.utils.TestUtils;
import io.opentelemetry.api.OpenTelemetry;
Expand Down Expand Up @@ -119,6 +120,33 @@ public void testBuildLogExporter() throws Exception {
validateLog(logTelemetryItem);
}

@Test
public void testBuildLogExporterWithCustomEvent() throws Exception {
// create the OpenTelemetry SDK
CountDownLatch countDownLatch = new CountDownLatch(1);
CustomValidationPolicy customValidationPolicy = new CustomValidationPolicy(countDownLatch);
OpenTelemetry openTelemetry
= TestUtils.createOpenTelemetrySdk(getHttpPipeline(customValidationPolicy), getConfiguration());

// generate a log
generateEvent(openTelemetry);

// wait for export
countDownLatch.await(10, SECONDS);
assertThat(customValidationPolicy.getUrl())
.isEqualTo(new URL("https://test.in.applicationinsights.azure.com/v2.1/track"));
assertThat(customValidationPolicy.getActualTelemetryItems().size()).isEqualTo(1);

// validate log
TelemetryItem eventTelemetryItem = customValidationPolicy.getActualTelemetryItems()
.stream()
.filter(item -> item.getName().equals("Event"))
.findFirst()
.get();

validateEvent(eventTelemetryItem);
}

@Test
public void testBuildTraceMetricLogExportersConsecutively() throws Exception {
// create the OpenTelemetry SDK
Expand Down Expand Up @@ -191,6 +219,15 @@ private static void generateLog(OpenTelemetry openTelemetry) {
.emit();
}

private static void generateEvent(OpenTelemetry openTelemetry) {
Logger logger = openTelemetry.getLogsBridge().get("Sample");
logger.logRecordBuilder()
.setBody("TestEvent")
.setAttribute(AttributeKey.stringKey("microsoft.custom_event.name"), "TestEvent")
.setAttribute(AttributeKey.stringKey("name"), "apple")
.emit();
}

private static void validateSpan(TelemetryItem telemetryItem) {
assertThat(telemetryItem.getName()).isEqualTo("RemoteDependency");
assertThat(telemetryItem.getInstrumentationKey()).isEqualTo(INSTRUMENTATION_KEY);
Expand Down Expand Up @@ -232,6 +269,20 @@ private static void validateLog(TelemetryItem telemetryItem) {
entry("SourceType", "Logger"), entry("color", "red"), entry("name", "apple"));
}

private static void validateEvent(TelemetryItem telemetryItem) {
assertThat(telemetryItem.getName()).isEqualTo("Event");
assertThat(telemetryItem.getInstrumentationKey()).isEqualTo(INSTRUMENTATION_KEY);
assertThat(telemetryItem.getTags()).containsEntry("ai.cloud.role", "unknown_service:java");
assertThat(telemetryItem.getTags()).hasEntrySatisfying("ai.internal.sdkVersion",
v -> assertThat(v).contains("otel"));
assertThat(telemetryItem.getData().getBaseType()).isEqualTo("TelemetryEventData");

TelemetryEventData eventData = TestUtils.toTelemetryEventData(telemetryItem.getData().getBaseData());
assertThat(eventData.getName()).isEqualTo("TestEvent");
assertThat(eventData.getProperties()).containsOnly(entry("LoggerName", "Sample"), entry("SourceType", "Logger"),
entry("color", "red"));
}

private static Map<String, String> getConfiguration() {
return Collections.singletonMap("APPLICATIONINSIGHTS_CONNECTION_STRING", CONNECTION_STRING_ENV);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.monitor.opentelemetry.autoconfigure.implementation.logging;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.time.Instant;

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.TelemetryEventData;
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryItem;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.logs.Severity;
import io.opentelemetry.sdk.resources.Resource;

@SuppressWarnings("checkstyle:IllegalImport")
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;
}
};

LogDataMapper logDataMapper = new LogDataMapper(true, true, (b, r) -> {
// Initialize telemetry builder with resource
});

TelemetryItem result = logDataMapper.map(logRecordData, null, null);

assertNotNull(result);
assertEquals("Event", result.getName());

// Convert result.getData().getBaseData() to TelemetryEventData and get the name property and validate against "TestEvent".
TelemetryEventData eventData = (TelemetryEventData) result.getData().getBaseData();
assertEquals("TestEvent", eventData.getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.MonitorBase;
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.MonitorDomain;
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.RemoteDependencyData;
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryEventData;
import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryItem;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
Expand Down Expand Up @@ -123,6 +124,15 @@ public static MessageData toMessageData(MonitorDomain baseData) {
}
}

// azure-json doesn't deserialize subtypes yet, so need to convert the abstract MonitorDomain to MessageData
public static TelemetryEventData toTelemetryEventData(MonitorDomain baseData) {
try (JsonReader jsonReader = JsonProviders.createReader(baseData.toJsonString())) {
return TelemetryEventData.fromJson(jsonReader);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

// deserialize multiple TelemetryItem raw bytes with newline delimiters to a list of TelemetryItems
public static List<TelemetryItem> deserialize(byte[] rawBytes) {
try (JsonReader jsonReader = JsonProviders.createReader(rawBytes)) {
Expand Down