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

Use upstream micrometer instrumentation #3245

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 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 @@ -7,6 +7,8 @@

import com.azure.monitor.opentelemetry.exporter.implementation.MetricDataMapper;
import com.azure.monitor.opentelemetry.exporter.implementation.logging.OperationLogger;
import com.azure.monitor.opentelemetry.exporter.implementation.models.MetricsData;
import com.azure.monitor.opentelemetry.exporter.implementation.models.MonitorDomain;
import com.azure.monitor.opentelemetry.exporter.implementation.models.TelemetryItem;
import com.azure.monitor.opentelemetry.exporter.implementation.utils.Strings;
import com.microsoft.applicationinsights.agent.internal.telemetry.BatchItemProcessor;
Expand All @@ -22,6 +24,8 @@
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -34,14 +38,17 @@ public class AgentMetricExporter implements MetricExporter {

private final List<MetricFilter> metricFilters;
private final MetricDataMapper mapper;
@Nullable private final String micrometerMetricNamespace;
private final Consumer<TelemetryItem> telemetryItemConsumer;

public AgentMetricExporter(
List<MetricFilter> metricFilters,
MetricDataMapper mapper,
BatchItemProcessor batchItemProcessor) {
BatchItemProcessor batchItemProcessor,
@Nullable String micrometerMetricNamespace) {
this.metricFilters = metricFilters;
this.mapper = mapper;
this.micrometerMetricNamespace = micrometerMetricNamespace;
this.telemetryItemConsumer =
telemetryItem -> {
TelemetryObservers.INSTANCE
Expand All @@ -58,13 +65,43 @@ public CompletableResultCode export(Collection<MetricData> metrics) {
logger.debug("exporter is not active");
return CompletableResultCode.ofSuccess();
}
for (MetricData metricData : metrics) {
List<MetricData> backCompatMetrics =
metrics.stream()
.map(
metricData -> {
if (metricData
.getInstrumentationScopeInfo()
.getName()
.equals("io.opentelemetry.micrometer-1.5")) {
return new BackCompatMetricData(metricData);
}
return metricData;
})
.collect(Collectors.toList());

for (MetricData metricData : backCompatMetrics) {
if (MetricFilter.shouldSkip(metricData.getName(), metricFilters)) {
continue;
}
logger.debug("exporting metric: {}", metricData);
try {
mapper.map(metricData, telemetryItemConsumer);
mapper.map(
metricData,
telemetryItem -> {
if (micrometerMetricNamespace != null
&& metricData
.getInstrumentationScopeInfo()
.getName()
.equals("io.opentelemetry.micrometer-1.5")) {
MonitorDomain baseData = telemetryItem.getData().getBaseData();
if (baseData instanceof MetricsData) {
((MetricsData) baseData)
.getMetrics()
.forEach(md -> md.setNamespace(micrometerMetricNamespace));
}
}
telemetryItemConsumer.accept(telemetryItem);
});
exportingMetricLogger.recordSuccess();
} catch (Throwable t) {
exportingMetricLogger.recordFailure(t.getMessage(), t, EXPORTER_MAPPING_ERROR);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.microsoft.applicationinsights.agent.internal.exporter;

import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.metrics.data.Data;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.data.MetricDataType;
import io.opentelemetry.sdk.resources.Resource;
import java.util.Arrays;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

class BackCompatMetricData implements MetricData {

private static final Pattern NAME_AND_TAG_KEY_PATTERN = Pattern.compile("[^a-zA-Z0-9\\-]");

private final MetricData delegate;
private final String name;

BackCompatMetricData(MetricData delegate) {
this.delegate = delegate;
this.name = backCompatName(delegate.getName());
}

private static String backCompatName(String name) {
return NAME_AND_TAG_KEY_PATTERN.matcher(toSnakeCase(name)).replaceAll("_");
}

private static String toSnakeCase(String value) {
heyams marked this conversation as resolved.
Show resolved Hide resolved
// same logic as micrometer's NamingConvention.snakeCase
return Arrays.stream(value.split("\\."))
.filter(Objects::nonNull)
.collect(Collectors.joining("_"));
}

@Override
public Resource getResource() {
return delegate.getResource();
}

@Override
public InstrumentationScopeInfo getInstrumentationScopeInfo() {
return delegate.getInstrumentationScopeInfo();
}

@Override
public String getName() {
return name;
}

@Override
public String getDescription() {
return delegate.getDescription();
}

@Override
public String getUnit() {
return delegate.getUnit();
}

@Override
public MetricDataType getType() {
return delegate.getType();
}

@Override
public Data<?> getData() {
return delegate.getData();
}

@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,13 @@ private static void enableInstrumentations(
properties.put("otel.instrumentation.undertow.enabled", "true");

if (config.instrumentation.micrometer.enabled) {
// TODO (heya) replace with below when updating to upstream micrometer
// upstream micrometer instrumentation only supports micrometer 1.5 and later
// so ai micrometer instrumentation is (still) used for micrometer versions prior to 1.5
properties.put("otel.instrumentation.ai-micrometer.enabled", "true");
properties.put("otel.instrumentation.ai-actuator-metrics.enabled", "true");
// properties.put("otel.instrumentation.micrometer.enabled", "true");
// properties.put("otel.instrumentation.spring-boot-actuator-autoconfigure.enabled", "true");
// upstream micrometer instrumentation is used for micrometer versions 1.5+
properties.put("otel.instrumentation.micrometer.enabled", "true");
properties.put("otel.instrumentation.spring-boot-actuator-autoconfigure.enabled", "true");
}
String namespace = config.instrumentation.micrometer.namespace;
if (namespace != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,10 @@ private static SdkMeterProviderBuilder configureMetrics(
PeriodicMetricReaderBuilder readerBuilder =
PeriodicMetricReader.builder(
new AgentMetricExporter(
metricFilters, mapper, telemetryClient.getMetricsBatchItemProcessor()));
metricFilters,
mapper,
telemetryClient.getMetricsBatchItemProcessor(),
configuration.instrumentation.micrometer.namespace));
int intervalMillis =
Integer.getInteger(
"applicationinsights.testing.metric-reader-interval-millis",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static java.util.Collections.singletonList;
import static net.bytebuddy.matcher.ElementMatchers.not;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.HelperResourceBuilder;
Expand All @@ -24,7 +25,10 @@ public ActuatorInstrumentationModule() {

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
return hasClassesNamed("io.micrometer.core.instrument.Metrics");
return hasClassesNamed("io.micrometer.core.instrument.Metrics")
.and(
// added in micrometer 1.5
not(hasClassesNamed("io.micrometer.core.instrument.config.validate.Validated")));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@

package io.opentelemetry.javaagent.instrumentation.micrometer.ai;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static net.bytebuddy.matcher.ElementMatchers.not;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.Arrays;
import java.util.List;
import net.bytebuddy.matcher.ElementMatcher;

@AutoService(InstrumentationModule.class)
public class MicrometerInstrumentationModule extends InstrumentationModule {
Expand All @@ -17,6 +21,12 @@ public MicrometerInstrumentationModule() {
super("ai-micrometer");
}

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
// added in 1.5
return not(hasClassesNamed("io.micrometer.core.instrument.config.validate.Validated"));
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Arrays.asList(new MetricsInstrumentation(), new CompositeMeterRegistryInstrumentation());
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ hideFromDependabot(":smoke-tests:apps:Log4j1")
hideFromDependabot(":smoke-tests:apps:Log4j2")
hideFromDependabot(":smoke-tests:apps:Logback")
hideFromDependabot(":smoke-tests:apps:Micrometer")
hideFromDependabot(":smoke-tests:apps:MicrometerOldVersion")
hideFromDependabot(":smoke-tests:apps:MongoDB")
hideFromDependabot(":smoke-tests:apps:NonDaemonThreads")
hideFromDependabot(":smoke-tests:apps:OpenTelemetryApiSupport")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ void doMostBasicTest() throws Exception {
assertThat(point.getCount()).isEqualTo(1);
assertThat(point.getName()).isEqualTo("http_server_requests");

// this isn't desired, but see https://github.com/micrometer-metrics/micrometer/issues/457
assertThat(point.getMin()).isNull();
// running with micrometer 1.5+ and so using upstream micrometer instrumentation
assertThat(point.getMin()).isNotNull();

assertThat(point.getMax()).isNotNull();
assertThat(point.getStdDev()).isNull();
Expand Down
2 changes: 1 addition & 1 deletion smoke-tests/apps/Micrometer/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ plugins {

dependencies {
implementation("org.springframework.boot:spring-boot-starter-web:2.5.12")
implementation("io.micrometer:micrometer-core:1.4.1")
implementation("io.micrometer:micrometer-core:1.5.0")
}
8 changes: 8 additions & 0 deletions smoke-tests/apps/MicrometerOldVersion/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
plugins {
trask marked this conversation as resolved.
Show resolved Hide resolved
id("ai.smoke-test-jar")
}

dependencies {
implementation("org.springframework.boot:spring-boot-starter-web:2.5.12")
implementation("io.micrometer:micrometer-core:1.4.1")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.microsoft.applicationinsights.smoketestapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootApp {

public static void main(String[] args) {

SpringApplication.run(SpringBootApp.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.microsoft.applicationinsights.smoketestapp;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Metrics;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

private final Counter counter = Metrics.counter("test.counter", "tag1", "value1");
private final Counter excludedCounter = Metrics.counter("test.counter.exclude.me");
private final Counter anotherExcludedCounter = Metrics.counter("exclude.me.test.counter");

@GetMapping("/")
public String root() {
return "OK";
}

@GetMapping("/test")
public String test() {
excludedCounter.increment();
anotherExcludedCounter.increment();
counter.increment();
return "OK!";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
management.metrics.export.azuremonitor.instrumentation-key=00000000-0000-0000-0000-000000000000
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.microsoft.applicationinsights.smoketest;

import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.JAVA_8;
import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

@Environment(JAVA_8)
@UseAgent("disabled_applicationinsights.json")
class MicrometerDisabledTest {

@RegisterExtension static final SmokeTestExtension testing = SmokeTestExtension.create();

@Test
@TargetUri("/test")
void doMostBasicTest() throws Exception {
Telemetry telemetry = testing.getTelemetry(0);

assertThat(telemetry.rd.getName()).isEqualTo("GET /test");
assertThat(telemetry.rd.getSuccess()).isTrue();

// sleep a bit and make sure no micrometer metrics are reported
Thread.sleep(10000);
assertThat(testing.mockedIngestion.getItemsEnvelopeDataType("MetricData"))
.noneMatch(MicrometerTest::isMicrometerMetricWithValueOne);
}
}
Loading
Loading