Skip to content
Closed
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
13 changes: 6 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,13 +258,12 @@
Use `otel.instrumentation.grpc.client.request-metadata.included` and
`otel.instrumentation.grpc.server.request-metadata.included`.
- Captured messaging header attribute keys now preserve dashes unconditionally; for example,
`messaging.header.Test_Message_Id` is now `messaging.header.Test-Message-Id`. Replace
`otel.instrumentation.messaging.experimental.headers.included=Test-Message-*` with
`otel.instrumentation.common.messaging.headers.included=Test-Message-*`.
The deprecated `otel.instrumentation.messaging.experimental.headers.included`,
`otel.instrumentation.messaging.experimental.headers.excluded`, and
`otel.instrumentation.messaging.experimental.capture-headers` settings are no longer supported;
use `otel.instrumentation.common.messaging.headers.included` / `.excluded` instead.
`messaging.header.Test_Message_Id` is now `messaging.header.Test-Message-Id`.
Replace `otel.instrumentation.common.messaging.headers.included` / `.excluded` and deprecated
messaging header capture settings with `otel.instrumentation.common.messaging.captured-headers`
(`java.common.messaging.captured_headers` in YAML). Only explicitly named headers are captured,
without wildcard expansion. Use `setCapturedHeaders(Collection<String>)` instead of header
selector setters on messaging extractor and telemetry builders.
`otel.instrumentation.messaging.experimental.receive-telemetry.enabled` is also no longer
supported; use `otel.instrumentation.common.messaging.experimental.receive-telemetry.enabled`.
- Disable native Kafka client metrics by default. Enable them with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,10 @@ void testComponentConfigDoesNotUseInstrumentationSpecialCases() {
}

@Test
void testMessagingHeadersSelectorMapping() {
void testMessagingCapturedHeadersMapping() {
Map<String, String> properties = new HashMap<>();
properties.put("otel.instrumentation.common.messaging.headers.included", "a,b");
properties.put("otel.instrumentation.common.messaging.headers.excluded", "c");
properties.put(
"otel.instrumentation.common.messaging.captured-headers", "Test-Header,*,Test-?");

DeclarativeConfigProperties messaging =
DeclarativeConfigBridge.createInstrumentationConfig(
Expand All @@ -205,10 +205,8 @@ void testMessagingHeadersSelectorMapping() {
.getStructured("common")
.getStructured("messaging");

assertThat(messaging.getStructured("headers").getScalarList("included", String.class))
.containsExactly("a", "b");
assertThat(messaging.getStructured("headers").getScalarList("excluded", String.class))
.containsExactly("c");
assertThat(messaging.getScalarList("captured_headers", String.class))
.containsExactly("Test-Header", "*", "Test-?");
}

@Test
Expand Down
20 changes: 5 additions & 15 deletions docs/declarative-configuration-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -158,21 +158,11 @@ instrumentation/development:
# list of attribute keys.
resource_attributes: []
messaging:
headers:
# A comma-separated list of case-sensitive message header name glob patterns to exclude
# from span attributes. Captured attribute keys preserve the header name's original
# spelling, including dashes. `?` matches one character and `*` matches zero or more
# characters. Excluded patterns take precedence over included patterns. An absent or empty
# selector captures no headers; an exclude-only selector captures every header not
# excluded, which may expose sensitive information.
excluded: []

# A comma-separated list of case-sensitive message header name glob patterns to capture as
# span attributes. Captured attribute keys preserve the header name's original spelling,
# including dashes. `?` matches one character and `*` matches zero or more characters.
# Excluded patterns take precedence over included patterns. An absent or empty selector
# captures no headers; an exclude-only selector captures every header not excluded.
included: []
# A comma-separated list of message header names to capture as span attributes. Names are
# matched literally using the messaging library's header lookup semantics, without wildcard
# expansion. Attribute keys preserve the configured name's casing and dashes. No headers are
# captured by default.
captured_headers: []
message_create_spans:
# Provides the default for whether to create a producer span for each eligible message in
# a batch send. An instrumentation-specific setting takes precedence.
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,18 @@
package io.opentelemetry.instrumentation.api.incubator.semconv.messaging;

import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.config.IncludeExclude;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.internal.SpanKey;
import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -97,43 +92,20 @@ public static <REQUEST, RESPONSE> MessagingAttributesExtractorBuilder<REQUEST, R
private final MessagingAttributesGetter<REQUEST, RESPONSE> getter;
private final MessagingOperationType operationType;
private final String operationName;
@Nullable private final IncludeExclude headers;
// exact header names that the selector includes, queried directly so that getters which only
// implement getMessageHeader() keep working
private final List<String> exactHeaderNames;
private final Map<String, AttributeKey<List<String>>> exactHeaderAttributeKeys;
// whether the selector can match header names that are not listed in exactHeaderNames, which
// requires enumerating the header names of each message
private final boolean enumerateHeaderNames;
private final Map<String, AttributeKey<List<String>>> capturedHeaders;

MessagingAttributesExtractor(
MessagingAttributesGetter<REQUEST, RESPONSE> getter,
MessagingOperationType operationType,
String operationName,
@Nullable IncludeExclude headers) {
Collection<String> capturedHeaders) {
this.getter = getter;
this.operationType = operationType;
this.operationName = operationName;
this.headers = headers;

Set<String> exactNames = new LinkedHashSet<>();
boolean enumerate = false;
if (headers != null) {
List<String> included = headers.getIncluded();
// a selector without included patterns matches every header name that is not excluded
enumerate = included.isEmpty();
for (String pattern : included) {
if (pattern.indexOf('*') != -1 || pattern.indexOf('?') != -1) {
enumerate = true;
} else if (headers.matches(pattern)) {
exactNames.add(pattern);
}
}
this.capturedHeaders = new LinkedHashMap<>();
for (String name : capturedHeaders) {
this.capturedHeaders.put(name, AttributeKey.stringArrayKey("messaging.header." + name));
}
this.exactHeaderNames = unmodifiableList(new ArrayList<>(exactNames));
this.exactHeaderAttributeKeys =
CapturedMessageHeadersUtil.createLiteralAttributeKeys(exactHeaderNames);
this.enumerateHeaderNames = enumerate;
}

@Override
Expand Down Expand Up @@ -169,29 +141,12 @@ public void onEnd(
attributes.put(MESSAGING_BATCH_MESSAGE_COUNT, getter.getBatchMessageCount(request, response));
attributes.put(ERROR_TYPE, getter.getErrorType(request, response, error));

for (String name : headerNames(request)) {
List<String> values = getter.getMessageHeader(request, name);
for (Map.Entry<String, AttributeKey<List<String>>> header : capturedHeaders.entrySet()) {
List<String> values = getter.getMessageHeader(request, header.getKey());
if (!values.isEmpty()) {
attributes.put(
CapturedMessageHeadersUtil.attributeKey(name, exactHeaderAttributeKeys), values);
}
}
}

private Collection<String> headerNames(REQUEST request) {
if (headers == null) {
return emptyList();
}
if (!enumerateHeaderNames) {
return exactHeaderNames;
}
Set<String> names = new LinkedHashSet<>(exactHeaderNames);
for (String name : getter.getMessageHeaderNames(request)) {
if (headers.matches(name)) {
names.add(name);
attributes.put(header.getValue(), values);
}
}
return names;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@

package io.opentelemetry.instrumentation.api.incubator.semconv.messaging;

import static java.util.Collections.emptyList;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.instrumentation.api.config.IncludeExclude;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.internal.DeprecatedCaptureNames;
import java.util.ArrayList;
import java.util.Collection;
import javax.annotation.Nullable;

/** A builder of {@link MessagingAttributesExtractor}. */
public final class MessagingAttributesExtractorBuilder<REQUEST, RESPONSE> {

final MessagingAttributesGetter<REQUEST, RESPONSE> getter;
private final MessagingOperationType operationType;
private final String operationName;
@Nullable IncludeExclude headers;
Collection<String> capturedHeaders = emptyList();

MessagingAttributesExtractorBuilder(
MessagingAttributesGetter<REQUEST, RESPONSE> getter,
Expand All @@ -35,53 +35,25 @@ public final class MessagingAttributesExtractorBuilder<REQUEST, RESPONSE> {
* <p>Header values are captured under the {@code messaging.header.<name>} attribute key, where
* {@code <name>} is the header name with its original spelling, including dashes.
*
* <p>Selector patterns are matched case-sensitively. {@code ?} matches one character and {@code
* *} matches any number of characters, including none. Excluded patterns take precedence over
* included patterns. A selector with no included patterns captures every header that is not
* excluded, and an {@linkplain IncludeExclude#isEmpty() empty} selector captures no headers.
* Exact included names are looked up directly, so those lookups follow the underlying messaging
* library's header-name case sensitivity.
*
* <p>Header names that are not listed as exact included names are resolved through {@link
* MessagingAttributesGetter#getMessageHeaderNames(Object)}, so wildcard and exclude-only
* selectors only capture headers when the getter implements that method.
*/
@CanIgnoreReturnValue
public MessagingAttributesExtractorBuilder<REQUEST, RESPONSE> setHeaders(IncludeExclude headers) {
this.headers = headers.isEmpty() ? null : headers;
return this;
}

/**
* Configures the messaging headers that will be captured as span attributes.
*
* <p>The messaging header values will be captured under the {@code messaging.header.<name>}
* attribute key, where {@code <name>} is the header name with its original spelling, including
* dashes.
*
* <p>The header names are matched literally. Names containing {@code *} or {@code ?} are ignored
* and logged, since this setting never supported wildcards.
* <p>Names are looked up literally using the underlying messaging library's header-name case
* sensitivity. {@code *} and {@code ?} are literal characters, not wildcards. By default, no
* headers are captured.
*
* @param capturedHeaders A list of messaging header names.
* @deprecated Use {@link #setHeaders(IncludeExclude)} instead. May be removed in the next minor
* release.
*/
@Deprecated // may be removed in the next minor release
@CanIgnoreReturnValue
public MessagingAttributesExtractorBuilder<REQUEST, RESPONSE> setCapturedHeaders(
Collection<String> capturedHeaders) {
return setHeaders(
DeprecatedCaptureNames.toSelectorOrEmpty(
capturedHeaders,
"MessagingAttributesExtractorBuilder.setCapturedHeaders()",
"setHeaders(IncludeExclude)"));
this.capturedHeaders = new ArrayList<>(capturedHeaders);
return this;
}

/**
* Returns a new {@link MessagingAttributesExtractor} with the settings of this {@link
* MessagingAttributesExtractorBuilder}.
*/
public AttributesExtractor<REQUEST, RESPONSE> build() {
return new MessagingAttributesExtractor<>(getter, operationType, operationName, headers);
return new MessagingAttributesExtractor<>(
getter, operationType, operationName, capturedHeaders);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,20 +83,4 @@ default String getErrorType(
default List<String> getMessageHeader(REQUEST request, String name) {
return emptyList();
}

/**
* Extracts the names of all headers present on the request, or an empty iterable if there were
* none.
*
* <p>This is used to resolve header selectors that cannot be turned into a list of exact header
* names, such as selectors containing wildcard patterns or selectors that only exclude headers.
* Selectors that only list exact header names are resolved with {@link #getMessageHeader(Object,
* String)} alone.
*
* <p>Implementations of this method <b>must not</b> return a null value; an empty iterable should
* be returned instead. The returned iterable is only read, so implementations may return a view
* over the underlying header names as long as that view cannot change while the returned iterable
* is being read.
*/
Iterable<String> getMessageHeaderNames(REQUEST request);
}
Loading
Loading