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

Add AwsAttributePropagatingSpanProcessor component to AWS X-Ray #777

Closed
wants to merge 2 commits into from
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
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.contrib.awsxray;

import io.opentelemetry.api.common.AttributeKey;

/** Utility class holding attribute keys with special meaning to AWS components */
final class AwsAttributeKeys {

private AwsAttributeKeys() {}

static final AttributeKey<String> AWS_LOCAL_OPERATION =
AttributeKey.stringKey("aws.local.operation");

static final AttributeKey<String> AWS_REMOTE_APPLICATION =
AttributeKey.stringKey("aws.remote.application");

static final AttributeKey<String> AWS_REMOTE_OPERATION =
AttributeKey.stringKey("aws.remote.operation");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.awsxray;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.trace.ReadWriteSpan;
import io.opentelemetry.sdk.trace.ReadableSpan;
import io.opentelemetry.sdk.trace.SpanProcessor;

/**
* AwsAttributePropagatingSpanProcessor handles the creation of the aws.local.operation attribute,
* and the inheritance of the {@link AwsAttributeKeys#AWS_LOCAL_OPERATION}, {@link
* AwsAttributeKeys#AWS_REMOTE_APPLICATION} and {@link AwsAttributeKeys#AWS_REMOTE_OPERATION}.
*
* <p>The {@link AwsAttributeKeys#AWS_LOCAL_OPERATION} is created when a local root span of SERVER
* or CONSUMER type is found. The span name will be used as the attribute value. The {@link
* AwsAttributeKeys#AWS_REMOTE_APPLICATION} and {@link AwsAttributeKeys#AWS_REMOTE_OPERATION}
* attributes must be created by manual instrumentation. These attributes will be copied from the
* parent span to children after receiving them, and later be used in SpanMetricsProcessor to help
* generate metric attributes.
*/
public final class AwsAttributePropagatingSpanProcessor implements SpanProcessor {

private AwsAttributePropagatingSpanProcessor() {}

public static AwsAttributePropagatingSpanProcessor create() {
return new AwsAttributePropagatingSpanProcessor();
}

@Override
public void onStart(Context parentContext, ReadWriteSpan span) {
Span parentSpan = Span.fromContextOrNull(parentContext);
if (!(parentSpan instanceof ReadableSpan)) {
return;
}
ReadableSpan parentReadableSpan = (ReadableSpan) parentSpan;

String localOperation;
if (isLocalRoot(parentReadableSpan.getParentSpanContext())
&& isServerOrConsumer(parentReadableSpan)) {
localOperation = parentReadableSpan.getName();
} else {
localOperation = parentReadableSpan.getAttribute(AwsAttributeKeys.AWS_LOCAL_OPERATION);
}
if (localOperation != null) {
span.setAttribute(AwsAttributeKeys.AWS_LOCAL_OPERATION, localOperation);
}

String remoteApplication =
parentReadableSpan.getAttribute(AwsAttributeKeys.AWS_REMOTE_APPLICATION);
if (remoteApplication != null) {
span.setAttribute(AwsAttributeKeys.AWS_REMOTE_APPLICATION, remoteApplication);
}

String remoteOperation = parentReadableSpan.getAttribute(AwsAttributeKeys.AWS_REMOTE_OPERATION);
if (remoteOperation != null) {
span.setAttribute(AwsAttributeKeys.AWS_REMOTE_OPERATION, remoteOperation);
}
}

private static boolean isLocalRoot(SpanContext parentSpanContext) {
return !parentSpanContext.isValid() || parentSpanContext.isRemote();
}

private static boolean isServerOrConsumer(ReadableSpan span) {
return span.getKind() == SpanKind.SERVER || span.getKind() == SpanKind.CONSUMER;
}

@Override
public boolean isStartRequired() {
return true;
}

@Override
public void onEnd(ReadableSpan span) {}

@Override
public boolean isEndRequired() {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.awsxray;

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

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.TraceFlags;
import io.opentelemetry.api.trace.TraceState;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.trace.ReadableSpan;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class AwsAttributePropagatingSpanProcessorTest {
private Tracer tracer;

@BeforeEach
public void setup() {
tracer =
SdkTracerProvider.builder()
.addSpanProcessor(AwsAttributePropagatingSpanProcessor.create())
.build()
.get("awsxray");
}

@Test
public void testRemoteAttributesInheritance() {
Span spanWithAppOnly = tracer.spanBuilder("parent").startSpan();
spanWithAppOnly.setAttribute(AwsAttributeKeys.AWS_REMOTE_APPLICATION, "testApplication");
validateSpanAttributesInheritance(spanWithAppOnly, null, "testApplication", null);

Span spanWithOpOnly = tracer.spanBuilder("parent").startSpan();
spanWithOpOnly.setAttribute(AwsAttributeKeys.AWS_REMOTE_OPERATION, "testOperation");
validateSpanAttributesInheritance(spanWithOpOnly, null, null, "testOperation");

Span spanWithAppAndOp = tracer.spanBuilder("parent").startSpan();
spanWithAppAndOp.setAttribute(AwsAttributeKeys.AWS_REMOTE_APPLICATION, "testApplication");
spanWithAppAndOp.setAttribute(AwsAttributeKeys.AWS_REMOTE_OPERATION, "testOperation");
validateSpanAttributesInheritance(spanWithAppAndOp, null, "testApplication", "testOperation");
}

@Test
public void testOverrideRemoteAttributes() {
Span parentSpan = tracer.spanBuilder("parent").startSpan();
parentSpan.setAttribute(AwsAttributeKeys.AWS_REMOTE_APPLICATION, "testApplication");
parentSpan.setAttribute(AwsAttributeKeys.AWS_REMOTE_OPERATION, "testOperation");

Span transmitSpans1 = createNestedSpan(parentSpan, 2);

Span childSpan =
tracer.spanBuilder("child:1").setParent(Context.current().with(transmitSpans1)).startSpan();

childSpan.setAttribute(AwsAttributeKeys.AWS_REMOTE_OPERATION, "childOperation");

Span transmitSpans2 = createNestedSpan(childSpan, 2);

assertThat(((ReadableSpan) transmitSpans2).getAttribute(AwsAttributeKeys.AWS_REMOTE_OPERATION))
.isEqualTo("childOperation");
}

@Test
public void testRemoteAttributesNotExists() {
Span span = tracer.spanBuilder("parent").startSpan();
validateSpanAttributesInheritance(span, null, null, null);
}

@Test
public void testLocalAttributeDetectionBySpanKind() {
for (SpanKind value : SpanKind.values()) {
Span span = tracer.spanBuilder("startOperation").setSpanKind(value).startSpan();
if (value == SpanKind.SERVER || value == SpanKind.CONSUMER) {
validateSpanAttributesInheritance(span, "startOperation", null, null);
} else {
validateSpanAttributesInheritance(span, null, null, null);
}
}
}

@Test
public void testLocalAttributeDetectionWithRemoteParentSpan() {
Span remoteParent =
Span.wrap(
SpanContext.createFromRemoteParent(
"00000000000000000000000000000001",
"0000000000000002",
TraceFlags.getSampled(),
TraceState.getDefault()));
Context parentcontext = Context.root().with(remoteParent);
Span span =
tracer
.spanBuilder("startOperation")
.setSpanKind(SpanKind.SERVER)
.setParent(parentcontext)
.startSpan();
validateSpanAttributesInheritance(span, "startOperation", null, null);
}

private Span createNestedSpan(Span parentSpan, int depth) {
if (depth == 0) {
return parentSpan;
}
Span childSpan =
tracer
.spanBuilder("child:" + depth)
.setParent(Context.current().with(parentSpan))
.startSpan();
try {
return createNestedSpan(childSpan, depth - 1);
} finally {
childSpan.end();
}
}

private void validateSpanAttributesInheritance(
Span spanWithAppOnly,
String localOperation,
String remoteApplication,
String remoteOperation) {
ReadableSpan leafSpan = (ReadableSpan) createNestedSpan(spanWithAppOnly, 10);

assertThat(leafSpan.getParentSpanContext()).isNotNull();
assertThat(leafSpan.getName()).isEqualTo("child:1");
if (localOperation != null) {
assertThat(leafSpan.getAttribute(AwsAttributeKeys.AWS_LOCAL_OPERATION))
.isEqualTo(localOperation);
} else {
assertThat(leafSpan.getAttribute(AwsAttributeKeys.AWS_LOCAL_OPERATION)).isNull();
}
if (remoteApplication != null) {
assertThat(leafSpan.getAttribute(AwsAttributeKeys.AWS_REMOTE_APPLICATION))
.isEqualTo(remoteApplication);
} else {
assertThat(leafSpan.getAttribute(AwsAttributeKeys.AWS_REMOTE_APPLICATION)).isNull();
}
if (remoteOperation != null) {
assertThat(leafSpan.getAttribute(AwsAttributeKeys.AWS_REMOTE_OPERATION))
.isEqualTo(remoteOperation);
} else {
assertThat(leafSpan.getAttribute(AwsAttributeKeys.AWS_REMOTE_OPERATION)).isNull();
}
}
}