Skip to content

Commit

Permalink
PeerService Resolver (#9061)
Browse files Browse the repository at this point in the history
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
Co-authored-by: Mateusz Rzeszutek <mrzeszutek@splunk.com>
Co-authored-by: jason plumb <75337021+breedx-splk@users.noreply.github.com>
Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
Co-authored-by: Lauri Tulmin <ltulmin@splunk.com>
  • Loading branch information
5 people committed Oct 12, 2023
1 parent 0511f5f commit 9a1c178
Show file tree
Hide file tree
Showing 47 changed files with 907 additions and 110 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.http;

import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.net.PeerServiceResolver;
import io.opentelemetry.instrumentation.api.instrumenter.url.UrlParser;
import io.opentelemetry.semconv.SemanticAttributes;
import java.util.function.Supplier;
import javax.annotation.Nullable;

/**
* Extractor of the {@code peer.service} span attribute, described in <a
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/span-general.md#general-remote-service-attributes">the
* specification</a>.
*/
public final class HttpClientPeerServiceAttributesExtractor<REQUEST, RESPONSE>
implements AttributesExtractor<REQUEST, RESPONSE> {

private final HttpClientAttributesGetter<REQUEST, RESPONSE> attributesGetter;
private final PeerServiceResolver peerServiceResolver;

// visible for tests
HttpClientPeerServiceAttributesExtractor(
HttpClientAttributesGetter<REQUEST, RESPONSE> attributesGetter,
PeerServiceResolver peerServiceResolver) {
this.attributesGetter = attributesGetter;
this.peerServiceResolver = peerServiceResolver;
}

/**
* Returns a new {@link HttpClientPeerServiceAttributesExtractor} that will use the passed {@code
* attributesGetter} instance to determine the value of the {@code peer.service} attribute.
*/
public static <REQUEST, RESPONSE>
HttpClientPeerServiceAttributesExtractor<REQUEST, RESPONSE> create(
HttpClientAttributesGetter<REQUEST, RESPONSE> attributesGetter,
PeerServiceResolver peerServiceResolver) {
return new HttpClientPeerServiceAttributesExtractor<>(attributesGetter, peerServiceResolver);
}

@Override
public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) {}

@Override
public void onEnd(
AttributesBuilder attributes,
Context context,
REQUEST request,
@Nullable RESPONSE response,
@Nullable Throwable error) {

if (peerServiceResolver.isEmpty()) {
// optimization for common case
return;
}

String serverAddress = attributesGetter.getServerAddress(request);
Integer serverPort = attributesGetter.getServerPort(request);
Supplier<String> pathSupplier = () -> getUrlPath(attributesGetter, request);
String peerService = mapToPeerService(serverAddress, serverPort, pathSupplier);
if (peerService == null) {
String serverSocketDomain = attributesGetter.getServerSocketDomain(request, response);
Integer serverSocketPort = attributesGetter.getServerSocketPort(request, response);
peerService = mapToPeerService(serverSocketDomain, serverSocketPort, null);
}
if (peerService != null) {
attributes.put(SemanticAttributes.PEER_SERVICE, peerService);
}
}

@Nullable
private String mapToPeerService(
@Nullable String host, @Nullable Integer port, @Nullable Supplier<String> pathSupplier) {
if (host == null) {
return null;
}
return peerServiceResolver.resolveService(host, port, pathSupplier);
}

@Nullable
private String getUrlPath(
HttpClientAttributesGetter<REQUEST, RESPONSE> attributesGetter, REQUEST request) {
String urlFull = attributesGetter.getUrlFull(request);
if (urlFull == null) {
return null;
}
return UrlParser.getPath(urlFull);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import io.opentelemetry.instrumentation.api.instrumenter.network.ServerAttributesGetter;
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
import io.opentelemetry.semconv.SemanticAttributes;
import java.util.Map;
import javax.annotation.Nullable;

/**
Expand All @@ -23,24 +22,24 @@ public final class PeerServiceAttributesExtractor<REQUEST, RESPONSE>
implements AttributesExtractor<REQUEST, RESPONSE> {

private final ServerAttributesGetter<REQUEST, RESPONSE> attributesGetter;
private final Map<String, String> peerServiceMapping;
private final PeerServiceResolver peerServiceResolver;

// visible for tests
PeerServiceAttributesExtractor(
ServerAttributesGetter<REQUEST, RESPONSE> attributesGetter,
Map<String, String> peerServiceMapping) {
PeerServiceResolver peerServiceResolver) {
this.attributesGetter = attributesGetter;
this.peerServiceMapping = peerServiceMapping;
this.peerServiceResolver = peerServiceResolver;
}

/**
* Returns a new {@link PeerServiceAttributesExtractor} that will use the passed {@code
* netAttributesExtractor} instance to determine the value of the {@code peer.service} attribute.
* attributesGetter} instance to determine the value of the {@code peer.service} attribute.
*/
public static <REQUEST, RESPONSE> AttributesExtractor<REQUEST, RESPONSE> create(
ServerAttributesGetter<REQUEST, RESPONSE> attributesGetter,
Map<String, String> peerServiceMapping) {
return new PeerServiceAttributesExtractor<>(attributesGetter, peerServiceMapping);
PeerServiceResolver peerServiceResolver) {
return new PeerServiceAttributesExtractor<>(attributesGetter, peerServiceResolver);
}

@Override
Expand All @@ -54,27 +53,29 @@ public void onEnd(
@Nullable RESPONSE response,
@Nullable Throwable error) {

if (peerServiceMapping.isEmpty()) {
if (peerServiceResolver.isEmpty()) {
// optimization for common case
return;
}

String serverAddress = attributesGetter.getServerAddress(request);
String peerService = mapToPeerService(serverAddress);
Integer serverPort = attributesGetter.getServerPort(request);
String peerService = mapToPeerService(serverAddress, serverPort);
if (peerService == null && SemconvStability.emitOldHttpSemconv()) {
String serverSocketDomain = attributesGetter.getServerSocketDomain(request, response);
peerService = mapToPeerService(serverSocketDomain);
Integer serverSocketPort = attributesGetter.getServerSocketPort(request, response);
peerService = mapToPeerService(serverSocketDomain, serverSocketPort);
}
if (peerService != null) {
attributes.put(SemanticAttributes.PEER_SERVICE, peerService);
}
}

@Nullable
private String mapToPeerService(@Nullable String endpoint) {
if (endpoint == null) {
private String mapToPeerService(@Nullable String host, @Nullable Integer port) {
if (host == null) {
return null;
}
return peerServiceMapping.get(endpoint);
return peerServiceResolver.resolveService(host, port, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.net;

import java.util.Map;
import java.util.function.Supplier;
import javax.annotation.Nullable;

public interface PeerServiceResolver {

public boolean isEmpty();

@Nullable
public String resolveService(
String host, @Nullable Integer port, @Nullable Supplier<String> pathSupplier);

static PeerServiceResolver create(Map<String, String> mapping) {
return new PeerServiceResolverImpl(mapping);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.net;

import static java.util.Comparator.comparing;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.nullsFirst;

import com.google.auto.value.AutoValue;
import io.opentelemetry.instrumentation.api.instrumenter.url.UrlParser;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import javax.annotation.Nullable;

class PeerServiceResolverImpl implements PeerServiceResolver {

private static final Comparator<ServiceMatcher> matcherComparator =
nullsFirst(
comparing(ServiceMatcher::getPort, nullsFirst(naturalOrder()))
.thenComparing(comparing(ServiceMatcher::getPath, nullsFirst(naturalOrder()))));

private final Map<String, Map<ServiceMatcher, String>> mapping = new HashMap<>();

PeerServiceResolverImpl(Map<String, String> peerServiceMapping) {
peerServiceMapping.forEach(
(key, serviceName) -> {
String url = "https://" + key;
String host = UrlParser.getHost(url);
Integer port = UrlParser.getPort(url);
String path = UrlParser.getPath(url);
Map<ServiceMatcher, String> matchers =
mapping.computeIfAbsent(host, x -> new HashMap<>());
matchers.putIfAbsent(ServiceMatcher.create(port, path), serviceName);
});
}

@Override
public boolean isEmpty() {
return mapping.isEmpty();
}

@Override
@Nullable
public String resolveService(
String host, @Nullable Integer port, @Nullable Supplier<String> pathSupplier) {
Map<ServiceMatcher, String> matchers = mapping.get(host);
if (matchers == null) {
return null;
}
return matchers.entrySet().stream()
.filter(entry -> entry.getKey().matches(port, pathSupplier))
.max((o1, o2) -> matcherComparator.compare(o1.getKey(), o2.getKey()))
.map(Map.Entry::getValue)
.orElse(null);
}

@AutoValue
abstract static class ServiceMatcher {

static ServiceMatcher create(Integer port, String path) {
return new AutoValue_PeerServiceResolverImpl_ServiceMatcher(port, path);
}

@Nullable
abstract Integer getPort();

@Nullable
abstract String getPath();

public boolean matches(Integer port, Supplier<String> pathSupplier) {
if (this.getPort() != null) {
if (!this.getPort().equals(port)) {
return false;
}
}
if (this.getPath() != null && this.getPath().length() > 0) {
if (pathSupplier == null) {
return false;
}
String path = pathSupplier.get();
if (path == null) {
return false;
}
if (!path.startsWith(this.getPath())) {
return false;
}
if (port != null) {
return port.equals(this.getPort());
}
}
return true;
}
}
}
Loading

0 comments on commit 9a1c178

Please sign in to comment.