Skip to content

Commit be93978

Browse files
authored
Added OpenTelemetry Metrics API bridge and instrumentation (#3014)
1 parent eebaa27 commit be93978

123 files changed

Lines changed: 4727 additions & 85 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ endif::[]
2828
* Add the <<config-disable-outgoing-tracecontext-headers>> config option to disable injection of `tracecontext` on outgoing
2929
communication - {pull}2996[#2996]
3030
* Add the <<config-profiling-inferred-spans-logging-enabled>> config option to suppress async profiler warning messages - {pull}3002[#3002]
31-
* Added support for OpenTelemetry metrics - {pull}2968[#2968]
31+
* Added support for OpenTelemetry metrics - {pull}2968[#2968], {pull}3014[#3014]
3232
* Added agent.activation_method telemetry - {pull}2926[#2926]
3333
3434
[float]

apm-agent-core/src/main/java/co/elastic/apm/agent/bci/classloading/DiscriminatingMultiParentClassLoader.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ class DiscriminatingMultiParentClassLoader extends ClassLoader {
4242
registerAsParallelCapable();
4343
}
4444

45+
DiscriminatingMultiParentClassLoader(ClassLoader singleParent, ElementMatcher<String> classesToLoadFromParent) throws NullPointerException {
46+
super(singleParent);
47+
//noinspection ConstantConditions
48+
if (singleParent == null) {
49+
throw new NullPointerException("The bootstrap class loader cannot be used as one of this class loader parents. " +
50+
"Use a single-parent class loader instead.");
51+
}
52+
this.parents = Arrays.asList(singleParent);
53+
this.discriminators = Arrays.asList(classesToLoadFromParent);
54+
}
55+
4556
DiscriminatingMultiParentClassLoader(ClassLoader agentClassLoader, ElementMatcher<String> classesToLoadFromAgentClassLoader,
4657
ClassLoader targetClassLoader, ElementMatcher<String> classesToLoadFromTargetClassLoader) throws NullPointerException {
4758
// We should not delegate class loading to super but to one of the parents, however this is preferable over using

apm-agent-core/src/main/java/co/elastic/apm/agent/bci/classloading/ExternalPluginClassLoader.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
import java.util.jar.JarEntry;
3535
import java.util.jar.JarFile;
3636

37+
import static co.elastic.apm.agent.bci.classloading.IndyPluginClassLoader.startsWith;
38+
import static net.bytebuddy.matcher.ElementMatchers.not;
39+
3740
/**
3841
* Loads plugins from {@link CoreConfiguration#getPluginsDir() plugins_dir}
3942
*
@@ -44,7 +47,7 @@ public class ExternalPluginClassLoader extends URLClassLoader {
4447
private final File pluginJar;
4548

4649
public ExternalPluginClassLoader(File pluginJar, ClassLoader parent) throws IOException {
47-
super(new URL[]{pluginJar.toURI().toURL()}, parent);
50+
super(new URL[]{pluginJar.toURI().toURL()}, createFilteringClassLoader(parent));
4851
this.pluginJar = pluginJar;
4952
classNames = Collections.unmodifiableList(scanForClasses(pluginJar));
5053
if (classNames.contains(ElasticApmInstrumentation.class.getName())) {
@@ -53,6 +56,12 @@ public ExternalPluginClassLoader(File pluginJar, ClassLoader parent) throws IOEx
5356
}
5457
}
5558

59+
private static ClassLoader createFilteringClassLoader(ClassLoader parent) {
60+
// The agent classloader contains "hidden" Otel dependencies due to the embedded metric SDK
61+
// We force plugins to provide their own Otel version
62+
return new DiscriminatingMultiParentClassLoader(parent, not(startsWith("io.opentelemetry.")));
63+
}
64+
5665
private List<String> scanForClasses(File pluginJar) throws IOException {
5766
List<String> tempClassNames = new ArrayList<>();
5867
try (JarFile jarFile = new JarFile(pluginJar)) {

apm-agent-core/src/main/java/co/elastic/apm/agent/bci/classloading/IndyPluginClassLoader.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,13 @@ agentClassLoader, startsWith("co.elastic.apm.agent").or(startsWith("net.bytebudd
7575
// Within the context of an instrumentation plugin, referencing log4j2 should always reference the instrumented types, not the ones shipped with the agent.
7676
// The list of packages not to load should correspond with matching dependency exclusions from the apm-agent-core in apm-agent-plugins/pom.xml
7777
// As we're using a custom logging facade, plugins don't need to refer to the agent-bundled log4j2 or slf4j.
78+
// io.opentelemetry is blocked to hide the embedded OpenTelemetry metrics SDK from instrumentation plugins
7879
return new DiscriminatingMultiParentClassLoader(
79-
agentClassLoader, not(startsWith("org.apache.logging.log4j")).and(not(startsWith("org.slf4j"))).and(not(startsWith("co.elastic.logging.log4j2"))),
80+
agentClassLoader,
81+
not(startsWith("org.apache.logging.log4j"))
82+
.and(not(startsWith("org.slf4j")))
83+
.and(not(startsWith("co.elastic.logging.log4j2")))
84+
.and(not(startsWith("io.opentelemetry."))),
8085
targetClassLoader, ElementMatchers.<String>any());
8186
}
8287
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>co.elastic.apm</groupId>
7+
<artifactId>apm-opentelemetry</artifactId>
8+
<version>1.36.1-SNAPSHOT</version>
9+
</parent>
10+
11+
<artifactId>apm-opentelemetry-embedded-metrics-sdk</artifactId>
12+
<name>${project.groupId}:${project.artifactId}</name>
13+
14+
<properties>
15+
<apm-agent-parent.base.dir>${project.basedir}/../../..</apm-agent-parent.base.dir>
16+
</properties>
17+
18+
<dependencies>
19+
<dependency>
20+
<groupId>io.opentelemetry</groupId>
21+
<artifactId>opentelemetry-sdk-metrics</artifactId>
22+
<version>${version.opentelemetry}</version>
23+
</dependency>
24+
</dependencies>
25+
26+
<build>
27+
<plugins>
28+
<plugin>
29+
<groupId>org.apache.maven.plugins</groupId>
30+
<artifactId>maven-shade-plugin</artifactId>
31+
<executions>
32+
<execution>
33+
<phase>package</phase>
34+
<goals>
35+
<goal>shade</goal>
36+
</goals>
37+
<configuration>
38+
<artifactSet>
39+
<includes>
40+
<include>io.opentelemetry:*</include>
41+
</includes>
42+
</artifactSet>
43+
<relocations>
44+
<relocation>
45+
<pattern>java.util.logging.Logger</pattern>
46+
<shadedPattern>co.elastic.apm.agent.embeddedotel.logging.Logger</shadedPattern>
47+
</relocation>
48+
</relocations>
49+
</configuration>
50+
</execution>
51+
</executions>
52+
</plugin>
53+
</plugins>
54+
</build>
55+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.apm.agent.embeddedotel;
20+
21+
import co.elastic.apm.agent.context.AbstractLifecycleListener;
22+
import co.elastic.apm.agent.embeddedotel.proxy.ProxyMeterProvider;
23+
import co.elastic.apm.agent.impl.ElasticApmTracer;
24+
import co.elastic.apm.agent.sdk.logging.Logger;
25+
import co.elastic.apm.agent.sdk.logging.LoggerFactory;
26+
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
27+
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
28+
29+
import javax.annotation.Nullable;
30+
import java.util.concurrent.TimeUnit;
31+
32+
/**
33+
* This class is loaded by the agent-classloader.
34+
* The actual embedded SDK is not directly accessible for instrumentations: io.opentelemetry.* is
35+
* blocked by the {@link co.elastic.apm.agent.bci.classloading.IndyPluginClassLoader}.
36+
* The SDK needs to be accessed via the proxy classes (e.g. {@link ProxyMeterProvider}) instead.
37+
*/
38+
public class EmbeddedSdkManager extends AbstractLifecycleListener {
39+
40+
private static final Logger logger = LoggerFactory.getLogger(EmbeddedSdkManager.class);
41+
42+
@Nullable
43+
private ElasticApmTracer tracer;
44+
@Nullable
45+
private volatile SdkMeterProvider sdkInstance;
46+
47+
private boolean isShutdown = false;
48+
49+
@Override
50+
public synchronized void start(ElasticApmTracer tracer) throws Exception {
51+
this.tracer = tracer;
52+
}
53+
54+
@Override
55+
public synchronized void stop() {
56+
isShutdown = true;
57+
if (sdkInstance != null) {
58+
logger.debug("Shutting down embedded OpenTelemetry metrics SDK");
59+
sdkInstance.shutdown().join(5, TimeUnit.SECONDS);
60+
}
61+
}
62+
63+
@Nullable
64+
public ProxyMeterProvider getMeterProvider() {
65+
if (sdkInstance == null) {
66+
startSdk();
67+
}
68+
return new ProxyMeterProvider(sdkInstance);
69+
}
70+
71+
/**
72+
* The agent does prevent the accidental startup of a new SDK after it has been shutdown.
73+
* However, in tests this is required, therefore tests can call this method after stop() to allow reinitialization.
74+
*/
75+
synchronized void reset() {
76+
sdkInstance = null;
77+
isShutdown = false;
78+
}
79+
80+
private synchronized void startSdk() {
81+
if (isShutdown || sdkInstance != null || tracer == null) {
82+
return;
83+
}
84+
logger.debug("Starting embedded OpenTelemetry metrics SDK");
85+
SdkMeterProviderBuilder sdkBuilder = SdkMeterProvider.builder();
86+
//No need to register an exporter -> our metricsdk instrumentation will do that
87+
sdkInstance = sdkBuilder.build();
88+
}
89+
90+
}

0 commit comments

Comments
 (0)