Skip to content
Merged
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
9 changes: 7 additions & 2 deletions instrumentation-docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,13 @@ public class SpringWebInstrumentationModule extends InstrumentationModule
* Short description of what the instrumentation does
* target_versions
* List of supported versions by the module, broken down by `library` or `javaagent` support
* scope
* Name: The scope name of the instrumentation, `io.opentelemetry.{instrumentation name}`
* scope (See [instrumentation-scope](https://opentelemetry.io/docs/specs/otel/common/instrumentation-scope/)
docs)
* name: The scope name of the instrumentation, `io.opentelemetry.{instrumentation name}`
* schema_url: Location of the telemetry schema that the instrumentation’s emitted telemetry
conforms to. (See [telemetry schema docs](https://opentelemetry.io/docs/specs/otel/schemas/#schema-url))
* attributes: The instrumentation scope’s optional attributes provide additional information
about the scope.
* configuration settings
* List of settings that are available for the instrumentation module
* Each setting has a name, description, type, and default value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationType;
import io.opentelemetry.instrumentation.docs.internal.TelemetryMerger;
import io.opentelemetry.instrumentation.docs.parsers.EmittedScopeParser;
import io.opentelemetry.instrumentation.docs.parsers.GradleParser;
import io.opentelemetry.instrumentation.docs.parsers.MetricParser;
import io.opentelemetry.instrumentation.docs.parsers.ModuleParser;
import io.opentelemetry.instrumentation.docs.parsers.SpanParser;
import io.opentelemetry.instrumentation.docs.utils.FileManager;
import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath;
import io.opentelemetry.instrumentation.docs.utils.YamlHelper;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import java.io.IOException;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -70,6 +72,11 @@ private void enrichModule(InstrumentationModule module) throws IOException {

// Handle telemetry merging (manual + emitted)
setMergedTelemetry(module, metaData);

InstrumentationScopeInfo scopeInfo = EmittedScopeParser.getScope(fileManager, module);
if (scopeInfo != null) {
module.setScopeInfo(scopeInfo);
}
}

@Nullable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.docs.internal;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nullable;

/**
* Representation of Scopes emitted the tests in a module. This class is internal and is hence not
* for public use. Its APIs are unstable and can change at any time.
*/
public class EmittedScope {
@Nullable private List<Scope> scopes;

public EmittedScope() {}

public EmittedScope(List<Scope> scopes) {
this.scopes = scopes;
}

@Nullable
public List<Scope> getScopes() {
return scopes;
}

public void setScopes(List<Scope> scopes) {
this.scopes = scopes;
}

/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public static class Scope {
@Nullable private String name;
@Nullable private String version;
@Nullable private String schemaUrl;
@Nullable private Map<String, Object> attributes;

public Scope() {}

public Scope(String name, String version, String schemaUrl, Map<String, Object> attributes) {
this.name = name;
this.version = version;
this.schemaUrl = schemaUrl;
this.attributes = attributes;
}

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

public void setName(String name) {
this.name = name;
}

@Nullable
public String getVersion() {
return version;
}

public void setVersion(String version) {
this.version = version;
}

@Nullable
public String getSchemaUrl() {
return schemaUrl;
}

public void setSchemaUrl(String schemaUrl) {
this.schemaUrl = schemaUrl;
}

@Nullable
public Map<String, Object> getAttributes() {
return attributes;
}

public void setAttributes(Map<String, Object> attributes) {
this.attributes = attributes;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Scope scope)) {
return false;
}

if (!Objects.equals(name, scope.name)) {
return false;
}
if (!Objects.equals(version, scope.version)) {
return false;
}
if (!Objects.equals(schemaUrl, scope.schemaUrl)) {
return false;
}
return Objects.equals(attributes, scope.attributes);
}

@Override
public int hashCode() {
return Objects.hash(name, version, schemaUrl, attributes);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class InstrumentationModule {
private final String instrumentationName;
private final String namespace;
private final String group;
private final InstrumentationScopeInfo scopeInfo;
private InstrumentationScopeInfo scopeInfo;
private Map<String, List<EmittedMetrics.Metric>> metrics;
private Map<String, List<EmittedSpans.Span>> spans;

Expand Down Expand Up @@ -56,7 +56,10 @@ public InstrumentationModule(Builder builder) {
this.metadata = builder.metadata;
this.targetVersions = builder.targetVersions;
this.minJavaVersion = builder.minJavaVersion;
this.scopeInfo = InstrumentationScopeInfo.create("io.opentelemetry." + instrumentationName);
this.scopeInfo =
Objects.requireNonNullElseGet(
builder.scopeInfo,
() -> InstrumentationScopeInfo.create("io.opentelemetry." + instrumentationName));
}

public String getSrcPath() {
Expand Down Expand Up @@ -109,6 +112,10 @@ public void setTargetVersions(Map<InstrumentationType, Set<String>> targetVersio
this.targetVersions = targetVersions;
}

public void setScopeInfo(InstrumentationScopeInfo scopeInfo) {
this.scopeInfo = scopeInfo;
}

public void setMetadata(InstrumentationMetadata metadata) {
this.metadata = metadata;
}
Expand Down Expand Up @@ -136,6 +143,7 @@ public static class Builder {
@Nullable private String group;
@Nullable private Integer minJavaVersion;
@Nullable private InstrumentationMetadata metadata;
@Nullable private InstrumentationScopeInfo scopeInfo;
@Nullable private Map<InstrumentationType, Set<String>> targetVersions;
@Nullable private Map<String, List<EmittedMetrics.Metric>> metrics;
@Nullable private Map<String, List<EmittedSpans.Span>> spans;
Expand All @@ -158,6 +166,12 @@ public Builder namespace(String namespace) {
return this;
}

@CanIgnoreReturnValue
public Builder scope(InstrumentationScopeInfo scope) {
this.scopeInfo = scope;
return this;
}

@CanIgnoreReturnValue
public Builder minJavaVersion(Integer minJavaVersion) {
this.minJavaVersion = minJavaVersion;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.docs.parsers;

import static io.opentelemetry.api.common.AttributeKey.booleanKey;
import static io.opentelemetry.api.common.AttributeKey.doubleKey;
import static io.opentelemetry.api.common.AttributeKey.longKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.instrumentation.docs.internal.EmittedScope;
import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule;
import io.opentelemetry.instrumentation.docs.utils.FileManager;
import io.opentelemetry.instrumentation.docs.utils.YamlHelper;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.common.InstrumentationScopeInfoBuilder;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Stream;
import javax.annotation.Nullable;

/**
* This class is responsible for parsing scope files from the `.telemetry` directory of an
* instrumentation module and collecting all unique scopes.
*/
public class EmittedScopeParser {
private static final Logger logger = Logger.getLogger(EmittedScopeParser.class.getName());

@Nullable
public static InstrumentationScopeInfo getScope(
FileManager fileManager, InstrumentationModule module) {
Set<EmittedScope.Scope> scopes =
EmittedScopeParser.getScopesFromFiles(fileManager.rootDir(), module.getSrcPath());
if (scopes.isEmpty()) {
return null;
}

EmittedScope.Scope scope =
scopes.stream()
.filter(
item ->
item.getName() != null
&& item.getName().contains(module.getInstrumentationName()))
.findFirst()
.orElse(null);
if (scope == null) {
return null;
}

String instrumentationName = "io.opentelemetry." + module.getInstrumentationName();
InstrumentationScopeInfoBuilder builder = InstrumentationScopeInfo.builder(instrumentationName);

// This will identify any module that might deviate from the standard naming convention
if (scope.getName() != null && !scope.getName().equals(instrumentationName)) {
logger.severe(
"Scope name mismatch. Expected: " + instrumentationName + ", got: " + scope.getName());
}

if (scope.getSchemaUrl() != null) {
builder.setSchemaUrl(scope.getSchemaUrl());
}
if (scope.getAttributes() != null) {
builder.setAttributes(convertMapToAttributes(scope.getAttributes()));
}

return builder.build();
}

/**
* Converts a {@code Map<String, Object>} to Attributes.
*
* @param attributeMap the map of attributes from YAML
* @return Attributes
*/
private static Attributes convertMapToAttributes(Map<String, Object> attributeMap) {
AttributesBuilder builder = Attributes.builder();
for (Map.Entry<String, Object> entry : attributeMap.entrySet()) {
Object value = entry.getValue();
if (value instanceof String string) {
builder.put(stringKey(entry.getKey()), string);
} else if (value instanceof Long longValue) {
builder.put(longKey(entry.getKey()), longValue);
} else if (value instanceof Integer intValue) {
builder.put(longKey(entry.getKey()), intValue.longValue());
} else if (value instanceof Double doubleValue) {
builder.put(doubleKey(entry.getKey()), doubleValue);
} else if (value instanceof Boolean boolValue) {
builder.put(booleanKey(entry.getKey()), boolValue);
} else {
// Fallback to string representation for unknown types
builder.put(stringKey(entry.getKey()), String.valueOf(value));
}
}
return builder.build();
}

/**
* Looks for scope files in the .telemetry directory and collects all unique scopes.
*
* @param rootDir the root directory
* @param instrumentationDirectory the instrumentation directory relative to root
* @return set of all unique scopes found in scope files
*/
public static Set<EmittedScope.Scope> getScopesFromFiles(
String rootDir, String instrumentationDirectory) {
Path telemetryDir = Paths.get(rootDir + "/" + instrumentationDirectory, ".telemetry");

Set<EmittedScope.Scope> allScopes = new HashSet<>();

if (Files.exists(telemetryDir) && Files.isDirectory(telemetryDir)) {
try (Stream<Path> files = Files.list(telemetryDir)) {
files
.filter(path -> path.getFileName().toString().startsWith("scope-"))
.forEach(
path -> {
String content = FileManager.readFileToString(path.toString());
if (content != null) {
EmittedScope parsed;
try {
parsed = YamlHelper.emittedScopeParser(content);
} catch (RuntimeException e) {
logger.severe("Error parsing scope file (" + path + "): " + e.getMessage());
return;
}

List<EmittedScope.Scope> scopes = parsed.getScopes();
if (scopes != null) {
allScopes.addAll(scopes);
}
}
});
} catch (IOException e) {
logger.severe("Error reading scope files: " + e.getMessage());
}
}
return allScopes;
}

private EmittedScopeParser() {}
}
Loading
Loading