Open
Description
Supersedes open-telemetry/opentelemetry-specification#4428 with a simpler approach.
Is your feature request related to a problem? Please describe.
#14014 adds support for declarative config - except for the spring starter.
The spring starter already supports yaml syntax as part of the spring configuration system.
Therefore, we either need to wait for v3 - or give users a way to opt-in to the new declarative config.
Describe the solution you'd like
The entry point into the configuration is the same, but the structure is different.
See https://opentelemetry.io/docs/zero-code/java/spring-boot-starter/sdk-configuration/
Old format:
application.yaml:
otel:
resource:
attributes:
deployment.environment: dev
service:
name: cart
namespace: shop
New format:
application.yaml:
spring:
application:
name: demo-app
otel:
# "file_format" serves as opt-in to the new file format
# it's not a clear indication that this is experimental though
file_format: "0.4"
log_level: debug
resource:
attributes:
- name: foo
value: bar
tracer_provider:
processors:
- simple:
exporter:
console:
Plan for the implementation:
- Read the spring config file in a rather hacky way, parsing a string.
- It seems a bit fragile, but the alternative is to create a spring adapter for every property,
similar to the adapter for the old config file. - Spring Boot is very conservative about everything, so I think it's fine to use string parsing here.
- It seems a bit fragile, but the alternative is to create a spring adapter for every property,
- Extract the
otel
YAML node - Write the contents to a temporary file
- Set the
otel.experimental.config.file
system property to the path of the temporary file (would be great to extend the SDK autoconfig to not need a temp file and setting a system property) - Initialize the OpenTelemetry SDK
- Delete the temporary file after the SDK is initialized
Here's the PoC code that implements this plan:
package com.grafana.demo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
private Pattern pattern = Pattern.compile(
"^Config resource 'class path resource \\[(.+)]' via location 'optional:classpath:/'$"
);
@EventListener
public void on(ApplicationReadyEvent event) throws IOException {
// get the application.yaml file that was used to start the application
ConfigurableEnvironment environment = event.getApplicationContext().getEnvironment();
if (environment.getProperty("otel.file_format") == null) {
System.out.println("otel.file_format is not set, skipping application.yaml extraction.");
return;
}
for (PropertySource<?> propertySource : environment.getPropertySources()) {
if (propertySource instanceof OriginTrackedMapPropertySource source) {
String name = source.getName();
System.out.println("Property Source: " + name);
Matcher matcher = pattern.matcher(name);
if (matcher.matches()) {
String file = matcher.group(1);
System.out.println("Found application.yaml: " + file);
try (InputStream resourceAsStream = event.getClass().getClassLoader().getResourceAsStream(file)) {
// Print the contents of the application.yaml file
if (resourceAsStream != null) {
String content = new String(resourceAsStream.readAllBytes());
System.out.println("Contents of " + file + ":");
System.out.println(content);
extractOtelConfigFile(content);
} else {
System.out.println("Could not find the application.yaml file in the classpath.");
}
}
}
}
}
}
private void extractOtelConfigFile(String content) throws IOException {
// https://github.com/open-telemetry/opentelemetry-configuration/blob/c205770a956713e512eddb056570a99737e3383a/examples/kitchen-sink.yaml#L11
// 1. read to yaml tree in jackson
ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
JsonNode rootNode = yamlMapper.readTree(content);
// 2. find the "otel" node
JsonNode otelNode = rootNode.get("otel");
if (otelNode == null) {
System.out.println("No 'otel' configuration found in the YAML file.");
return;
}
// 3. write the "otel" node to a temp file
File tempFile = File.createTempFile("otel-config", ".yaml");
try (FileWriter writer = new FileWriter(tempFile)) {
writer.write(yamlMapper.writeValueAsString(otelNode));
}
System.out.println("OpenTelemetry configuration extracted to: " + tempFile.getAbsolutePath());
// Set the system property to point to the extracted config file
String key = "otel.experimental.config.file";
System.setProperty(key, tempFile.getAbsolutePath());
System.out.println("Set system property " + key + "=" + tempFile.getAbsolutePath());
AutoConfiguredOpenTelemetrySdk sdk = AutoConfiguredOpenTelemetrySdk.initialize();
System.out.println("OpenTelemetry SDK initialized with configuration from: " + sdk.getOpenTelemetrySdk());
// print the config file
System.out.println("OpenTelemetry configuration file content:");
try (InputStream inputStream = new FileInputStream(tempFile)) {
String configContent = new String(inputStream.readAllBytes());
System.out.println(configContent);
} catch (IOException e) {
System.err.println("Error reading the OpenTelemetry configuration file: " + e.getMessage());
}
tempFile.delete();
}
}
Metadata
Metadata
Assignees
Type
Projects
Status
In Progress