Skip to content

Commit

Permalink
Merge pull request #97 from appoptics/release-0.15.4
Browse files Browse the repository at this point in the history
Add trace-options to span attributes
  • Loading branch information
cleverchuk authored Apr 26, 2023
2 parents 7532824 + e558b25 commit f208132
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 68 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ subprojects {
bytebuddy : "1.12.10",
guava : "30.1-jre",
appopticsCore : "7.8.4",
agent : "0.15.3", // the custom distro agent version
agent : "0.15.4", // the custom distro agent version
autoservice : "1.0.1",
]
versions.appopticsMetrics = "${versions.appopticsCore}" // they share the same version now
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import java.util.Map;
import java.util.Set;

import static com.appoptics.opentelemetry.extensions.SamplingUtil.SW_XTRACE_OPTIONS_RESP_KEY;

public class AppOpticsContextPropagator implements TextMapPropagator {
private static final String TRACE_STATE_APPOPTICS_KEY = "sw";
static final String TRACE_PARENT = "traceparent";
Expand Down Expand Up @@ -88,7 +90,7 @@ private String updateTraceState(TraceState traceState, String swTraceStateValue)
for (Map.Entry<String, String> entry : tracestateEntries) {
String key = entry.getKey();
boolean verdict = (TRACE_STATE_APPOPTICS_KEY.equals(key)
|| TraceStateSamplingResult.SW_XTRACE_OPTIONS_RESP_KEY.equals(key));
|| SW_XTRACE_OPTIONS_RESP_KEY.equals(key));
if (!verdict) {
traceStateLength += (key.length());
traceStateLength += (TRACESTATE_KEY_VALUE_DELIMITER.length());
Expand All @@ -102,7 +104,7 @@ private String updateTraceState(TraceState traceState, String swTraceStateValue)
String key = entry.getKey();
String value = entry.getValue();
boolean verdict = (TRACE_STATE_APPOPTICS_KEY.equals(key)
|| TraceStateSamplingResult.SW_XTRACE_OPTIONS_RESP_KEY.equals(key));
|| SW_XTRACE_OPTIONS_RESP_KEY.equals(key));

final int length =
traceStateBuilder.length() + TRACESTATE_ENTRY_DELIMITER.length() +
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.appoptics.opentelemetry.extensions;

import com.appoptics.opentelemetry.core.RootSpan;
import com.tracelytics.joboe.XTraceOption;
import com.tracelytics.joboe.XTraceOptions;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.context.Context;
Expand All @@ -21,7 +19,6 @@ public void onStart(@Nonnull Context parentContext, @Nonnull ReadWriteSpan span)
SpanContext parentSpanContext = Span.fromContext(parentContext).getSpanContext();
if (!parentSpanContext.isValid() || parentSpanContext.isRemote()) { //then a root span of this service
RootSpan.setRootSpan(span);
processXtraceOptions(parentContext, span);
}
}

Expand All @@ -42,20 +39,4 @@ public void onEnd(ReadableSpan span) {
public boolean isEndRequired() {
return true;
}

private void processXtraceOptions(@Nonnull Context parentContext, @Nonnull ReadWriteSpan span) {
XTraceOptions xTraceOptions = parentContext.get(TriggerTraceContextKey.KEY);
if (xTraceOptions != null) {
xTraceOptions.getCustomKvs().forEach(
((stringXTraceOption, s) -> span.setAttribute(stringXTraceOption.getKey(), s)));
if (xTraceOptions.getOptionValue(XTraceOption.TRIGGER_TRACE)) {
span.setAttribute("TriggeredTrace", true);
}

String swKeys = xTraceOptions.getOptionValue(XTraceOption.SW_KEYS);
if (swKeys != null) {
span.setAttribute("SWKeys", swKeys);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@
import java.util.List;

import static com.appoptics.opentelemetry.extensions.AppOpticsSpanExporter.LAYER_FORMAT;
import static com.appoptics.opentelemetry.extensions.TraceStateSamplingResult.SW_SPAN_PLACEHOLDER;
import static com.appoptics.opentelemetry.extensions.TraceStateSamplingResult.SW_TRACESTATE_KEY;
import static com.appoptics.opentelemetry.extensions.SamplingUtil.SW_TRACESTATE_KEY;
import static com.appoptics.opentelemetry.extensions.SamplingUtil.addXtraceOptionsToAttribute;
import static com.appoptics.opentelemetry.extensions.SamplingUtil.isValidSWTraceState;
import static com.tracelytics.joboe.TraceDecisionUtil.shouldTraceRequest;

/**
Expand Down Expand Up @@ -63,7 +64,7 @@ public class AppOpticsSampler implements Sampler {

private static final Logger logger = LoggerFactory.getLogger();

public AppOpticsSampler(){
public AppOpticsSampler() {
logger.info("Attached Solarwinds' Sampler");
}

Expand All @@ -82,11 +83,12 @@ public SamplingResult shouldSample(@Nonnull Context parentContext,
final XTraceOptions xTraceOptions = parentContext.get(TriggerTraceContextKey.KEY);

String xTraceOptionsResponseStr = null;
List<String> signals = Arrays.asList(constructUrl(attributes), String.format(LAYER_FORMAT, spanKind, name.trim()));
List<String> signals = Arrays.asList(constructUrl(attributes),
String.format(LAYER_FORMAT, spanKind, name.trim()));

if (!parentSpanContext.isValid()) { // no valid traceparent, it is a new trace
TraceDecision traceDecision = shouldTraceRequest(name, null, xTraceOptions, signals);
samplingResult = toOtSamplingResult(traceDecision);
samplingResult = toOtSamplingResult(traceDecision, xTraceOptions, true);
XTraceOptionsResponse xTraceOptionsResponse = XTraceOptionsResponse.computeResponse(xTraceOptions,
traceDecision, true);

Expand All @@ -97,9 +99,9 @@ public SamplingResult shouldSample(@Nonnull Context parentContext,
} else {
final String swVal = traceState.get(SW_TRACESTATE_KEY);
String parentId = null;
if (!isValidSWTraceStateKey(swVal)) { // broken or non-exist sw tracestate, treat it as a new trace
if (!isValidSWTraceState(swVal)) { // broken or non-exist sw tracestate, treat it as a new trace
final TraceDecision traceDecision = shouldTraceRequest(name, null, xTraceOptions, signals);
samplingResult = toOtSamplingResult(traceDecision);
samplingResult = toOtSamplingResult(traceDecision, xTraceOptions, true);
final XTraceOptionsResponse xTraceOptionsResponse = XTraceOptionsResponse.computeResponse(xTraceOptions,
traceDecision, true);
if (xTraceOptionsResponse != null) {
Expand All @@ -110,10 +112,10 @@ public SamplingResult shouldSample(@Nonnull Context parentContext,
if (parentSpanContext.isRemote()) { // root span needs to roll the dice
final String xTraceId = Util.w3CContextToHexString(parentSpanContext);
final TraceDecision traceDecision = shouldTraceRequest(name, xTraceId, xTraceOptions, signals);
samplingResult = toOtSamplingResult(traceDecision);
samplingResult = toOtSamplingResult(traceDecision, xTraceOptions, false);

final XTraceOptionsResponse xTraceOptionsResponse = XTraceOptionsResponse.computeResponse(
xTraceOptions, traceDecision, true);
xTraceOptions, traceDecision, false);
if (xTraceOptionsResponse != null) {
xTraceOptionsResponseStr = xTraceOptionsResponse.toString();
}
Expand Down Expand Up @@ -141,19 +143,6 @@ public SamplingResult shouldSample(@Nonnull Context parentContext,
return result;
}

private boolean isValidSWTraceStateKey(String swVal) {
if (swVal == null || !swVal.contains("-")) {
return false;
}
final String[] swTraceState = swVal.split("-");
if (swTraceState.length != 2) {
return false;
}

return (swTraceState[0].equals(
SW_SPAN_PLACEHOLDER) || swTraceState[0].length() == 16) // 16 is the HEXLENGTH of the Otel trace id
&& (swTraceState[1].equals("00") || swTraceState[1].equals("01"));
}

private String constructUrl(Attributes attributes) {
String scheme = attributes.get(SemanticAttributes.HTTP_SCHEME);
Expand All @@ -170,29 +159,33 @@ public String getDescription() {
return "Solarwinds Observability Sampler";
}

private SamplingResult toOtSamplingResult(TraceDecision aoTraceDecision) {
private SamplingResult toOtSamplingResult(TraceDecision traceDecision, XTraceOptions xTraceOptions,
boolean genesis) {
SamplingResult result = NOT_TRACED;

if (aoTraceDecision.isSampled()) {
if (traceDecision.isSampled()) {
final SamplingDecision samplingDecision = SamplingDecision.RECORD_AND_SAMPLE;
final AttributesBuilder aoAttributesBuilder = Attributes.builder();
aoAttributesBuilder.put(Constants.SW_KEY_PREFIX + "SampleRate",
aoTraceDecision.getTraceConfig().getSampleRate());
aoAttributesBuilder.put(Constants.SW_KEY_PREFIX + "SampleSource",
aoTraceDecision.getTraceConfig().getSampleRateSourceValue());
aoAttributesBuilder.put(Constants.SW_KEY_PREFIX + "BucketRate",
aoTraceDecision.getTraceConfig().getBucketRate(aoTraceDecision.getRequestType().getBucketType()));
aoAttributesBuilder.put(Constants.SW_KEY_PREFIX + "BucketCapacity",
aoTraceDecision.getTraceConfig().getBucketCapacity(
aoTraceDecision.getRequestType().getBucketType()));
aoAttributesBuilder.put(Constants.SW_KEY_PREFIX + "RequestType", aoTraceDecision.getRequestType().name());
aoAttributesBuilder.put(Constants.SW_DETAILED_TRACING, aoTraceDecision.isSampled());
aoAttributesBuilder.put(Constants.SW_METRICS, aoTraceDecision.isReportMetrics());
aoAttributesBuilder.put(Constants.SW_SAMPLER, true); //mark that it has been sampled by us

result = SamplingResult.create(samplingDecision, aoAttributesBuilder.build());
final AttributesBuilder attributesBuilder = Attributes.builder();
attributesBuilder.put(Constants.SW_KEY_PREFIX + "SampleRate",
traceDecision.getTraceConfig().getSampleRate());
attributesBuilder.put(Constants.SW_KEY_PREFIX + "SampleSource",
traceDecision.getTraceConfig().getSampleRateSourceValue());
attributesBuilder.put(Constants.SW_KEY_PREFIX + "BucketRate",
traceDecision.getTraceConfig().getBucketRate(traceDecision.getRequestType().getBucketType()));
attributesBuilder.put(Constants.SW_KEY_PREFIX + "BucketCapacity",
traceDecision.getTraceConfig().getBucketCapacity(
traceDecision.getRequestType().getBucketType()));
attributesBuilder.put(Constants.SW_KEY_PREFIX + "RequestType", traceDecision.getRequestType().name());
attributesBuilder.put(Constants.SW_DETAILED_TRACING, traceDecision.isSampled());
attributesBuilder.put(Constants.SW_METRICS, traceDecision.isReportMetrics());
attributesBuilder.put(Constants.SW_SAMPLER, true); //mark that it has been sampled by us

if (genesis) {
addXtraceOptionsToAttribute(traceDecision, xTraceOptions, attributesBuilder);
}
result = SamplingResult.create(samplingDecision, attributesBuilder.build());
} else {
if (aoTraceDecision.isReportMetrics()) {
if (traceDecision.isReportMetrics()) {
result = METRICS_ONLY;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.appoptics.opentelemetry.extensions;

import com.tracelytics.joboe.TraceDecision;
import com.tracelytics.joboe.TraceDecisionUtil;
import com.tracelytics.joboe.XTraceOption;
import com.tracelytics.joboe.XTraceOptions;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.trace.TraceState;

public class SamplingUtil {
private SamplingUtil(){}
public static final String SW_TRACESTATE_KEY = "sw";
public static final String SW_XTRACE_OPTIONS_RESP_KEY = "xtrace_options_response";
public static final String SW_SPAN_PLACEHOLDER = "SWSpanIdPlaceHolder";
public static final String SW_SPAN_PLACEHOLDER_SAMPLED = SW_SPAN_PLACEHOLDER + "-01";
public static final String SW_SPAN_PLACEHOLDER_NOT_SAMPLED = SW_SPAN_PLACEHOLDER + "-00";


public static boolean isValidSWTraceState(String swVal) {
if (swVal == null || !swVal.contains("-")) {
return false;
}
final String[] swTraceState = swVal.split("-");
if (swTraceState.length != 2) {
return false;
}

// 16 is the hex length of the Otel trace id
return (swTraceState[0].equals(SW_SPAN_PLACEHOLDER) || swTraceState[0].length() == 16)
&& (swTraceState[1].equals("00") || swTraceState[1].equals("01"));
}

public static boolean isSwSpanPlaceHolder(TraceState traceState){
String swTracestate = traceState.get(SW_TRACESTATE_KEY);
return swTracestate != null && SW_SPAN_PLACEHOLDER.equals(swTracestate.split("-")[0]);
}

public static boolean isValidSWTraceState(TraceState traceState) {
return isValidSWTraceState(traceState.get(SW_TRACESTATE_KEY));
}

public static void addXtraceOptionsToAttribute(TraceDecision traceDecision, XTraceOptions xTraceOptions,
AttributesBuilder attributesBuilder) {
if (xTraceOptions != null) {
xTraceOptions.getCustomKvs().forEach(
((stringXTraceOption, s) -> attributesBuilder.put(stringXTraceOption.getKey(), s)));

if (traceDecision.getRequestType() == TraceDecisionUtil.RequestType.AUTHENTICATED_TRIGGER_TRACE ||
traceDecision.getRequestType() == TraceDecisionUtil.RequestType.UNAUTHENTICATED_TRIGGER_TRACE) {
attributesBuilder.put("TriggeredTrace", true);
}

String swKeys = xTraceOptions.getOptionValue(XTraceOption.SW_KEYS);
if (swKeys != null) {
attributesBuilder.put("SWKeys", swKeys);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;

import static com.appoptics.opentelemetry.extensions.SamplingUtil.SW_SPAN_PLACEHOLDER_NOT_SAMPLED;
import static com.appoptics.opentelemetry.extensions.SamplingUtil.SW_SPAN_PLACEHOLDER_SAMPLED;
import static com.appoptics.opentelemetry.extensions.SamplingUtil.SW_TRACESTATE_KEY;
import static com.appoptics.opentelemetry.extensions.SamplingUtil.SW_XTRACE_OPTIONS_RESP_KEY;

/**
* A SamplingResult wrapper offering the `sw=spanIdPlaceHolder` tracestate and additional attributes
*/
public class TraceStateSamplingResult implements SamplingResult {
public static final String SW_TRACESTATE_KEY = "sw";
public static final String SW_XTRACE_OPTIONS_RESP_KEY = "xtrace_options_response";
public static final String SW_SPAN_PLACEHOLDER = "SWSpanIdPlaceHolder";
public static final String SW_SPAN_PLACEHOLDER_SAMPLED = SW_SPAN_PLACEHOLDER + "-01";
public static final String SW_SPAN_PLACEHOLDER_NOT_SAMPLED = SW_SPAN_PLACEHOLDER + "-00";
private final SamplingResult delegated;
private final String swTraceState;
private final Attributes additionalAttributes;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.appoptics.opentelemetry.extensions;

import com.tracelytics.joboe.TraceDecision;
import com.tracelytics.joboe.TraceDecisionUtil;
import com.tracelytics.joboe.XTraceOptions;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static io.opentelemetry.api.common.AttributeKey.booleanKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class SamplingUtilTest {

@Mock
private TraceDecision traceDecisionMock;

@Test
void verifyThatTriggeredTraceAttributeIsAddedForAuthenticatedTriggerTrace() {
AttributesBuilder builder = Attributes.builder();
XTraceOptions xTraceOptions = XTraceOptions.getXTraceOptions("trigger-trace", null);
when(traceDecisionMock.getRequestType()).thenReturn(TraceDecisionUtil.RequestType.AUTHENTICATED_TRIGGER_TRACE);

SamplingUtil.addXtraceOptionsToAttribute(traceDecisionMock, xTraceOptions, builder);
assertEquals(Boolean.TRUE, builder.build().get(booleanKey("TriggeredTrace")));
}


@Test
void verifyThatTriggeredTraceAttributeIsAddedForUnauthenticatedTriggerTrace() {
AttributesBuilder builder = Attributes.builder();
XTraceOptions xTraceOptions = XTraceOptions.getXTraceOptions("trigger-trace", null);
when(traceDecisionMock.getRequestType()).thenReturn(TraceDecisionUtil.RequestType.UNAUTHENTICATED_TRIGGER_TRACE);

SamplingUtil.addXtraceOptionsToAttribute(traceDecisionMock, xTraceOptions, builder);
assertEquals(Boolean.TRUE, builder.build().get(booleanKey("TriggeredTrace")));
}

@Test
void verifyThatCustomKvAttributesAreAdded() {
AttributesBuilder builder = Attributes.builder();
XTraceOptions xTraceOptions = XTraceOptions.getXTraceOptions("custom-chubi=chubby;", null);
when(traceDecisionMock.getRequestType()).thenReturn(TraceDecisionUtil.RequestType.UNAUTHENTICATED_TRIGGER_TRACE);

SamplingUtil.addXtraceOptionsToAttribute(traceDecisionMock, xTraceOptions, builder);
assertEquals("chubby", builder.build().get(stringKey("custom-chubi")));
}

@Test
void verifyThatSwKeysAttributeIsAdded() {
AttributesBuilder builder = Attributes.builder();
XTraceOptions xTraceOptions = XTraceOptions.getXTraceOptions("sw-keys=lo:se,check-id:123", null);
when(traceDecisionMock.getRequestType()).thenReturn(TraceDecisionUtil.RequestType.AUTHENTICATED_TRIGGER_TRACE);

SamplingUtil.addXtraceOptionsToAttribute(traceDecisionMock, xTraceOptions, builder);
assertEquals("lo:se,check-id:123", builder.build().get(stringKey("SWKeys")));
}
}

0 comments on commit f208132

Please sign in to comment.