001package io.prometheus.otelagent; 002 003import static java.nio.file.Files.createTempDirectory; 004 005import java.io.File; 006import java.io.InputStream; 007import java.lang.reflect.Field; 008import java.lang.reflect.Method; 009import java.net.URL; 010import java.net.URLClassLoader; 011import java.nio.file.Files; 012import java.nio.file.Path; 013import java.nio.file.StandardCopyOption; 014import java.util.Collections; 015import java.util.HashMap; 016import java.util.Map; 017 018public class ResourceAttributesFromOtelAgent { 019 020 private static final String[] OTEL_JARS = 021 new String[] {"opentelemetry-api-1.29.0.jar", "opentelemetry-context-1.29.0.jar"}; 022 023 /** 024 * This grabs resource attributes like {@code service.name} and {@code service.instance.id} from 025 * the OTel Java agent (if present) and adds them to {@code result}. 026 * 027 * <p>The way this works is as follows: If the OTel Java agent is attached, it modifies the {@code 028 * GlobalOpenTelemetry.get()} method to return an agent-specific object. From that agent-specific 029 * object we can get the resource attributes via reflection. 030 * 031 * <p>So we load the {@code GlobalOpenTelemetry} class (in a separate class loader from the JAR 032 * files that are bundled with this module), call {@code .get()}, and inspect the returned object. 033 * 034 * <p>After that we discard the class loader so that all OTel specific classes are unloaded. No 035 * runtime dependency on any OTel version remains. 036 * 037 * <p>The test for this class is in 038 * examples/example-exporter-opentelemetry/oats-tests/agent/service-instance-id-check.py 039 */ 040 public static Map<String, String> getResourceAttributes(String instrumentationScopeName) { 041 try { 042 Path tmpDir = createTempDirectory(instrumentationScopeName + "-"); 043 try { 044 URL[] otelJars = copyOtelJarsToTempDir(tmpDir, instrumentationScopeName); 045 046 try (URLClassLoader classLoader = new URLClassLoader(otelJars)) { 047 Class<?> globalOpenTelemetryClass = 048 classLoader.loadClass("io.opentelemetry.api.GlobalOpenTelemetry"); 049 Object globalOpenTelemetry = globalOpenTelemetryClass.getMethod("get").invoke(null); 050 if (globalOpenTelemetry.getClass().getSimpleName().contains("ApplicationOpenTelemetry")) { 051 // GlobalOpenTelemetry is injected by the OTel Java agent 052 Object applicationMeterProvider = callMethod("getMeterProvider", globalOpenTelemetry); 053 Object agentMeterProvider = getField("agentMeterProvider", applicationMeterProvider); 054 Object sdkMeterProvider = getField("delegate", agentMeterProvider); 055 Object sharedState = getField("sharedState", sdkMeterProvider); 056 Object resource = callMethod("getResource", sharedState); 057 Object attributes = callMethod("getAttributes", resource); 058 Map<?, ?> attributeMap = (Map<?, ?>) callMethod("asMap", attributes); 059 060 Map<String, String> result = new HashMap<>(); 061 for (Map.Entry<?, ?> entry : attributeMap.entrySet()) { 062 if (entry.getKey() != null && entry.getValue() != null) { 063 result.put(entry.getKey().toString(), entry.getValue().toString()); 064 } 065 } 066 return Collections.unmodifiableMap(result); 067 } 068 } 069 } finally { 070 deleteTempDir(tmpDir.toFile()); 071 } 072 } catch (Exception ignored) { 073 // ignore 074 } 075 return Collections.emptyMap(); 076 } 077 078 private static Object getField(String name, Object obj) throws Exception { 079 Field field = obj.getClass().getDeclaredField(name); 080 field.setAccessible(true); 081 return field.get(obj); 082 } 083 084 private static Object callMethod(String name, Object obj) throws Exception { 085 Method method = obj.getClass().getMethod(name); 086 method.setAccessible(true); 087 return method.invoke(obj); 088 } 089 090 private static URL[] copyOtelJarsToTempDir(Path tmpDir, String instrumentationScopeName) 091 throws Exception { 092 URL[] result = new URL[OTEL_JARS.length]; 093 for (int i = 0; i < OTEL_JARS.length; i++) { 094 InputStream inputStream = 095 Thread.currentThread().getContextClassLoader().getResourceAsStream("lib/" + OTEL_JARS[i]); 096 if (inputStream == null) { 097 throw new IllegalStateException( 098 "Error initializing " 099 + instrumentationScopeName 100 + ": lib/" 101 + OTEL_JARS[i] 102 + " not found in classpath."); 103 } 104 File outputFile = tmpDir.resolve(OTEL_JARS[i]).toFile(); 105 Files.copy(inputStream, outputFile.toPath(), StandardCopyOption.REPLACE_EXISTING); 106 inputStream.close(); 107 result[i] = outputFile.toURI().toURL(); 108 } 109 return result; 110 } 111 112 private static void deleteTempDir(File tmpDir) { 113 // We don't have subdirectories, so this simple implementation should work. 114 for (File file : tmpDir.listFiles()) { 115 file.delete(); 116 } 117 tmpDir.delete(); 118 } 119}