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

Extended jinja template to generate template-type semantic attributes. #24

Merged
merged 6 commits into from
Nov 21, 2023
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
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ dependencies {

testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.junit.jupiter:junit-jupiter-params")
testImplementation("org.junit.jupiter:junit-jupiter-engine")

testImplementation(platform("org.assertj:assertj-bom:3.24.2"))
Expand Down
25 changes: 25 additions & 0 deletions buildscripts/templates/SemanticAttributes.java.j2
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ import static io.opentelemetry.api.common.AttributeKey.longKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;

import static io.opentelemetry.semconv.AttributeKeyTemplate.stringArrayKeyTemplate;
import static io.opentelemetry.semconv.AttributeKeyTemplate.stringKeyTemplate;

import io.opentelemetry.api.common.AttributeKey;
import java.util.List;

Expand Down Expand Up @@ -84,6 +87,28 @@ public final class {{class}} {
{%- endif %}
public static final AttributeKey<{{upFirst(to_java_return_type(attribute.attr_type | string))}}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.attr_type | string)}}("{{attribute.fqn}}");
{%- endfor %}
{%- for attribute_template in attribute_templates if attribute_template.is_local and not attribute_template.ref %}

/**
* {{attribute_template.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}}
{%- if attribute_template.note %}
*
* <p>Notes:
{# NOTE: replace("> ", "") removes the following problematic characters which produce mangled javadoc: #}
{# https://github.com/open-telemetry/semantic-conventions/blob/c83a10a9c33c18a769835e959200d0e24dc708fe/model/resource/k8s.yaml#L34-L38 #}
<ul> {{attribute_template.note | replace("> ", "") | render_markdown(code="{{@code {0}}}", paragraph="<li>{0}</li>", list="{0}")}} </ul>

{%- endif %}
{%- if (attribute_template.stability | string()) == "StabilityLevel.DEPRECATED" %}
*
* @deprecated {{attribute_template.brief | to_doc_brief}}.
{%- endif %}
*/
{%- if (attribute_template.stability | string()) == "StabilityLevel.DEPRECATED" %}
@Deprecated
{%- endif %}
public static final AttributeKeyTemplate<{{upFirst(to_java_return_type(attribute_template.instantiated_type | string))}}> {{attribute_template.fqn | to_const_name}} = {{to_java_key_type(attribute_template.instantiated_type | string)}}Template("{{attribute_template.fqn}}");
{%- endfor %}

// Enum definitions
{%- for attribute in attributes if attribute.is_local and not attribute.ref %}
Expand Down
120 changes: 120 additions & 0 deletions src/main/java/io/opentelemetry/semconv/AttributeKeyTemplate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.semconv;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.AttributeType;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;

/**
* This class provides a handle for creating and caching dynamic / template-type attributes of the
* form <b>&lt;prefix&gt;.&lt;key&gt;</b>. The &lt;prefix&gt; is fixed for a template instance while
* {@link AttributeKey}s can be created and are cached for different values of the &lt;key&gt; part.
*
* <p>An example template-type attribute is the set of attributes for HTTP headers:
* <b>http.request.header.&lt;key&gt;</b>
*
* @param <T> The type of the nested {@link AttributeKey}s.
*/
public final class AttributeKeyTemplate<T> {
AlexanderWert marked this conversation as resolved.
Show resolved Hide resolved
AlexanderWert marked this conversation as resolved.
Show resolved Hide resolved

private final String prefix;
private final Function<String, AttributeKey<T>> keyBuilder;
private final ConcurrentMap<String, AttributeKey<T>> keysCache = new ConcurrentHashMap<>(1);

private AttributeKeyTemplate(String prefix, Function<String, AttributeKey<T>> keyBuilder) {
this.prefix = prefix;
this.keyBuilder = keyBuilder;
}

/**
* Create an {@link AttributeKeyTemplate} with type {@link AttributeType#STRING} and the given
* {@code prefix}.
*/
public static AttributeKeyTemplate<String> stringKeyTemplate(String prefix) {
return new AttributeKeyTemplate<>(prefix, AttributeKey::stringKey);
}

/**
* Create an {@link AttributeKeyTemplate} with type {@link AttributeType#STRING_ARRAY} and the
* given {@code prefix}.
*/
public static AttributeKeyTemplate<List<String>> stringArrayKeyTemplate(String prefix) {
return new AttributeKeyTemplate<>(prefix, AttributeKey::stringArrayKey);
}

/**
* Create an {@link AttributeKeyTemplate} with type {@link AttributeType#BOOLEAN} and the given
* {@code prefix}.
*/
public static AttributeKeyTemplate<Boolean> booleanKeyTemplate(String prefix) {
return new AttributeKeyTemplate<>(prefix, AttributeKey::booleanKey);
}

/**
* Create an {@link AttributeKeyTemplate} with type {@link AttributeType#BOOLEAN_ARRAY} and the
* given {@code prefix}.
*/
public static AttributeKeyTemplate<List<Boolean>> booleanArrayKeyTemplate(String prefix) {
return new AttributeKeyTemplate<>(prefix, AttributeKey::booleanArrayKey);
}

/**
* Create an {@link AttributeKeyTemplate} with type {@link AttributeType#LONG} and the given
* {@code prefix}.
*/
public static AttributeKeyTemplate<Long> longKeyTemplate(String prefix) {
return new AttributeKeyTemplate<>(prefix, AttributeKey::longKey);
}

/**
* Create an {@link AttributeKeyTemplate} with type {@link AttributeType#LONG_ARRAY} and the given
* {@code prefix}.
*/
public static AttributeKeyTemplate<List<Long>> longArrayKeyTemplate(String prefix) {
return new AttributeKeyTemplate<>(prefix, AttributeKey::longArrayKey);
}

/**
* Create an {@link AttributeKeyTemplate} with type {@link AttributeType#DOUBLE} and the given
* {@code prefix}.
*/
public static AttributeKeyTemplate<Double> doubleKeyTemplate(String prefix) {
return new AttributeKeyTemplate<>(prefix, AttributeKey::doubleKey);
}

/**
* Create an {@link AttributeKeyTemplate} with type {@link AttributeType#DOUBLE_ARRAY} and the
* given {@code prefix}.
*/
public static AttributeKeyTemplate<List<Double>> doubleArrayKeyTemplate(String prefix) {
return new AttributeKeyTemplate<>(prefix, AttributeKey::doubleArrayKey);
}

private AttributeKey<T> createAttributeKey(String keyName) {
String key = prefix + "." + keyName;
return keyBuilder.apply(key);
}

/**
* Returns an {@link AttributeKey} object for the given attribute key whereby the key is the
* variable part of the full attribute name in a template-typed attribute, for example
* <b>http.request.header.&lt;key&gt;</b>.
*
* <p>{@link AttributeKey} objets are being created and cached on the first invocation of this
* method for a certain key. Subsequent invocations of this method with the same key return the
* cached object.
*
* @param key The variable part of the template-typed attribute name.
* @return An {@link AttributeKey} object for the given key.
*/
public AttributeKey<T> getAttributeKey(String key) {
return keysCache.computeIfAbsent(key, this::createAttributeKey);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import static io.opentelemetry.api.common.AttributeKey.longKey;
import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.semconv.AttributeKeyTemplate.stringKeyTemplate;

import io.opentelemetry.api.common.AttributeKey;
import java.util.List;
Expand Down Expand Up @@ -918,6 +919,10 @@ public final class ResourceAttributes {
@Deprecated
public static final AttributeKey<String> OTEL_LIBRARY_VERSION = stringKey("otel.library.version");

/** Container labels, {@code <key>} being the label name, the value being the label value. */
public static final AttributeKeyTemplate<String> CONTAINER_LABELS =
stringKeyTemplate("container.labels");

// Enum definitions
public static final class CloudPlatformValues {
/** Alibaba Cloud Elastic Compute Service. */
Expand Down
117 changes: 117 additions & 0 deletions src/main/java/io/opentelemetry/semconv/SemanticAttributes.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import static io.opentelemetry.api.common.AttributeKey.longKey;
import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.semconv.AttributeKeyTemplate.stringArrayKeyTemplate;
import static io.opentelemetry.semconv.AttributeKeyTemplate.stringKeyTemplate;

import io.opentelemetry.api.common.AttributeKey;
import java.util.List;
Expand Down Expand Up @@ -1688,6 +1690,121 @@ public final class SemanticAttributes {
*/
public static final AttributeKey<String> USER_AGENT_ORIGINAL = stringKey("user_agent.original");

/**
* HTTP request headers, {@code <key>} being the normalized HTTP Header name (lowercase, with
* {@code -} characters replaced by {@code _}), the value being the header values.
*
* <p>Notes:
*
* <ul>
* <li>Instrumentations SHOULD require an explicit configuration of which headers are to be
* captured. Including all request headers can be a security risk - explicit configuration
* helps avoid leaking sensitive information. The {@code User-Agent} header is already
* captured in the {@code user_agent.original} attribute. Users MAY explicitly configure
* instrumentations to capture them even though it is not recommended. The attribute value
* MUST consist of either multiple header values as an array of strings or a single-item
* array containing a possibly comma-concatenated string, depending on the way the HTTP
* library provides access to headers.
* </ul>
*/
public static final AttributeKeyTemplate<List<String>> HTTP_REQUEST_HEADER =
stringArrayKeyTemplate("http.request.header");

/**
* HTTP response headers, {@code <key>} being the normalized HTTP Header name (lowercase, with
* {@code -} characters replaced by {@code _}), the value being the header values.
*
* <p>Notes:
*
* <ul>
* <li>Instrumentations SHOULD require an explicit configuration of which headers are to be
* captured. Including all response headers can be a security risk - explicit configuration
* helps avoid leaking sensitive information. Users MAY explicitly configure
* instrumentations to capture them even though it is not recommended. The attribute value
* MUST consist of either multiple header values as an array of strings or a single-item
* array containing a possibly comma-concatenated string, depending on the way the HTTP
* library provides access to headers.
* </ul>
*/
public static final AttributeKeyTemplate<List<String>> HTTP_RESPONSE_HEADER =
stringArrayKeyTemplate("http.response.header");

/**
* A dynamic value in the url path.
*
* <p>Notes:
*
* <ul>
* <li>Many Elasticsearch url paths allow dynamic values. These SHOULD be recorded in span
* attributes in the format {@code db.elasticsearch.path_parts.<key>}, where {@code <key>}
* is the url path part name. The implementation SHOULD reference the <a
* href="https://raw.githubusercontent.com/elastic/elasticsearch-specification/main/output/schema/schema.json">elasticsearch
* schema</a> in order to map the path part values to their names.
* </ul>
*/
public static final AttributeKeyTemplate<String> DB_ELASTICSEARCH_PATH_PARTS =
stringKeyTemplate("db.elasticsearch.path_parts");

/**
* gRPC request metadata, {@code <key>} being the normalized gRPC Metadata key (lowercase, with
* {@code -} characters replaced by {@code _}), the value being the metadata values.
*
* <p>Notes:
*
* <ul>
* <li>Instrumentations SHOULD require an explicit configuration of which metadata values are to
* be captured. Including all request metadata values can be a security risk - explicit
* configuration helps avoid leaking sensitive information.
* </ul>
*/
public static final AttributeKeyTemplate<List<String>> RPC_GRPC_REQUEST_METADATA =
stringArrayKeyTemplate("rpc.grpc.request.metadata");

/**
* gRPC response metadata, {@code <key>} being the normalized gRPC Metadata key (lowercase, with
* {@code -} characters replaced by {@code _}), the value being the metadata values.
*
* <p>Notes:
*
* <ul>
* <li>Instrumentations SHOULD require an explicit configuration of which metadata values are to
* be captured. Including all response metadata values can be a security risk - explicit
* configuration helps avoid leaking sensitive information.
* </ul>
*/
public static final AttributeKeyTemplate<List<String>> RPC_GRPC_RESPONSE_METADATA =
stringArrayKeyTemplate("rpc.grpc.response.metadata");

/**
* Connect request metadata, {@code <key>} being the normalized Connect Metadata key (lowercase,
* with {@code -} characters replaced by {@code _}), the value being the metadata values.
*
* <p>Notes:
*
* <ul>
* <li>Instrumentations SHOULD require an explicit configuration of which metadata values are to
* be captured. Including all request metadata values can be a security risk - explicit
* configuration helps avoid leaking sensitive information.
* </ul>
*/
public static final AttributeKeyTemplate<List<String>> RPC_CONNECT_RPC_REQUEST_METADATA =
stringArrayKeyTemplate("rpc.connect_rpc.request.metadata");

/**
* Connect response metadata, {@code <key>} being the normalized Connect Metadata key (lowercase,
* with {@code -} characters replaced by {@code _}), the value being the metadata values.
*
* <p>Notes:
*
* <ul>
* <li>Instrumentations SHOULD require an explicit configuration of which metadata values are to
* be captured. Including all response metadata values can be a security risk - explicit
* configuration helps avoid leaking sensitive information.
* </ul>
*/
public static final AttributeKeyTemplate<List<String>> RPC_CONNECT_RPC_RESPONSE_METADATA =
stringArrayKeyTemplate("rpc.connect_rpc.response.metadata");

// Enum definitions
public static final class NetSockFamilyValues {
/** IPv4 address. */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.semconv;

import static org.assertj.core.api.Assertions.assertThat;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.AttributeType;
import java.util.function.Function;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

class AttributeKeyTemplateTest {

@ParameterizedTest
@MethodSource("getAttributeKeyArgs")
void getAttributeKey(
Function<String, AttributeKeyTemplate<?>> createTemplate,
AttributeType expectedAttributeType) {
String prefix = "my.prefix";
AttributeKeyTemplate<?> template = createTemplate.apply(prefix);

AttributeKey<?> attributeKey1 = template.getAttributeKey("key1");
AttributeKey<?> attributeKey2 = template.getAttributeKey("key2");

assertThat(attributeKey1.getType()).isEqualTo(expectedAttributeType);
assertThat(attributeKey1.getKey()).isEqualTo("my.prefix.key1");
assertThat(attributeKey1).isSameAs(template.getAttributeKey("key1"));

assertThat(attributeKey2.getType()).isEqualTo(expectedAttributeType);
assertThat(attributeKey2.getKey()).isEqualTo("my.prefix.key2");
}

private static Stream<Arguments> getAttributeKeyArgs() {
return Stream.of(
Arguments.of(asFunction(AttributeKeyTemplate::stringKeyTemplate), AttributeType.STRING),
Arguments.of(
asFunction(AttributeKeyTemplate::stringArrayKeyTemplate), AttributeType.STRING_ARRAY),
Arguments.of(asFunction(AttributeKeyTemplate::booleanKeyTemplate), AttributeType.BOOLEAN),
Arguments.of(
asFunction(AttributeKeyTemplate::booleanArrayKeyTemplate), AttributeType.BOOLEAN_ARRAY),
Arguments.of(asFunction(AttributeKeyTemplate::longKeyTemplate), AttributeType.LONG),
Arguments.of(
asFunction(AttributeKeyTemplate::longArrayKeyTemplate), AttributeType.LONG_ARRAY),
Arguments.of(asFunction(AttributeKeyTemplate::doubleKeyTemplate), AttributeType.DOUBLE),
Arguments.of(
asFunction(AttributeKeyTemplate::doubleArrayKeyTemplate), AttributeType.DOUBLE_ARRAY));
}

private static Function<String, AttributeKeyTemplate<?>> asFunction(
Function<String, AttributeKeyTemplate<?>> function) {
return function;
}
}