diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy deleted file mode 100644 index 2e533583ea2..00000000000 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy +++ /dev/null @@ -1,507 +0,0 @@ -package datadog.trace.core - -import static datadog.trace.api.DDTags.DJM_ENABLED -import static datadog.trace.api.DDTags.DSM_ENABLED -import static datadog.trace.api.DDTags.PROFILING_ENABLED -import static datadog.trace.api.DDTags.SCHEMA_VERSION_TAG_KEY - -import datadog.trace.api.Config -import datadog.trace.api.DDSpanId -import datadog.trace.api.DDTraceId -import datadog.trace.api.TagMap -import datadog.trace.api.gateway.RequestContextSlot -import datadog.trace.api.naming.SpanNaming -import datadog.trace.api.sampling.PrioritySampling -import datadog.trace.bootstrap.instrumentation.api.AgentScope -import datadog.trace.api.datastreams.NoopPathwayContext -import datadog.trace.bootstrap.instrumentation.api.TagContext -import datadog.trace.common.writer.ListWriter -import datadog.trace.core.propagation.PropagationTags -import datadog.trace.core.propagation.ExtractedContext -import datadog.trace.core.test.DDCoreSpecification - -import static datadog.trace.api.DDTags.LANGUAGE_TAG_KEY -import static datadog.trace.api.DDTags.LANGUAGE_TAG_VALUE -import static datadog.trace.api.DDTags.ORIGIN_KEY -import static datadog.trace.api.DDTags.PID_TAG -import static datadog.trace.api.DDTags.RUNTIME_ID_TAG -import static datadog.trace.api.DDTags.THREAD_ID -import static datadog.trace.api.DDTags.THREAD_NAME -import static datadog.trace.api.TracePropagationStyle.DATADOG -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopSpan -import static java.util.concurrent.TimeUnit.MILLISECONDS - -class CoreSpanBuilderTest extends DDCoreSpecification { - - def writer = new ListWriter() - def tracer = tracerBuilder().writer(writer).build() - - def cleanup() { - tracer.close() - } - - def "build simple span"() { - setup: - final DDSpan span = tracer.buildSpan("test", "op name").withServiceName("foo").start() - - expect: - span.operationName == "op name" - } - - def "build complex span"() { - setup: - def expectedName = "fakeName" - def tags = [ - "1": true, - "2": "fakeString", - "3": 42.0, - ] - - CoreTracer.CoreSpanBuilder builder = tracer - .buildSpan(expectedName) - .withServiceName("foo") - tags.each { - builder = builder.withTag(it.key, it.value) - } - - when: - DDSpan span = builder.start() - - then: - span.getOperationName() == expectedName - span.tags.subMap(tags.keySet()) == tags - - - when: - span = tracer.buildSpan("test", expectedName).withServiceName("foo").start() - - then: - span.getTags() == [ - (THREAD_NAME) : Thread.currentThread().getName(), - (THREAD_ID) : Thread.currentThread().getId(), - (RUNTIME_ID_TAG) : Config.get().getRuntimeId(), - (LANGUAGE_TAG_KEY) : LANGUAGE_TAG_VALUE, - (PID_TAG) : Config.get().getProcessId(), - (SCHEMA_VERSION_TAG_KEY) : SpanNaming.instance().version() - ] + productTags() - - when: - // with all custom fields provided - final String expectedResource = "fakeResource" - final String expectedService = "fakeService" - final String expectedType = "fakeType" - - span = - tracer - .buildSpan("test", expectedName) - .withServiceName("foo") - .withResourceName(expectedResource) - .withServiceName(expectedService) - .withErrorFlag() - .withSpanType(expectedType) - .start() - - final DDSpanContext context = span.context() - - then: - context.getResourceName() == expectedResource - context.getErrorFlag() - context.getServiceName() == expectedService - context.getSpanType() == expectedType - - context.getTag(THREAD_NAME) == Thread.currentThread().getName() - context.getTag(THREAD_ID) == Thread.currentThread().getId() - } - - def "setting #name should remove"() { - setup: - final DDSpan span = tracer.buildSpan("test", "op name") - .withTag(name, "tag value") - .withTag(name, value) - .start() - - expect: - span.tags[name] == null - - when: - span.setTag(name, "a tag") - - then: - span.tags[name] == "a tag" - - when: - span.setTag(name, (String) value) - - then: - span.tags[name] == null - - where: - name | value - "null.tag" | null - "empty.tag" | "" - } - - def "should build span timestamp in nano"() { - setup: - // time in micro - final long expectedTimestamp = 487517802L * 1000 * 1000L - final String expectedName = "fakeName" - - DDSpan span = - tracer - .buildSpan("test", expectedName) - .withServiceName("foo") - .withStartTimestamp(expectedTimestamp) - .start() - - expect: - // get return nano time - span.getStartTime() == expectedTimestamp * 1000L - - when: - // auto-timestamp in nanoseconds - def start = System.currentTimeMillis() - span = tracer.buildSpan("test", expectedName).withServiceName("foo").start() - def stop = System.currentTimeMillis() - - then: - // Give a range of +/- 5 millis - span.getStartTime() >= MILLISECONDS.toNanos(start - 1) - span.getStartTime() <= MILLISECONDS.toNanos(stop + 1) - } - - def "should link to parent span"() { - setup: - final long spanId = 1 - final DDTraceId traceId = DDTraceId.ONE - final long expectedParentId = spanId - - final DDSpanContext mockedContext = Mock() - 1 * mockedContext.getTraceId() >> traceId - 1 * mockedContext.getSpanId() >> spanId - _ * mockedContext.getServiceName() >> "foo" - 1 * mockedContext.getBaggageItems() >> [:] - 1 * mockedContext.getTraceCollector() >> tracer.traceCollectorFactory.create(DDTraceId.ONE) - _ * mockedContext.getPathwayContext() >> NoopPathwayContext.INSTANCE - - final String expectedName = "fakeName" - - final DDSpan span = - tracer - .buildSpan("test", expectedName) - .withServiceName("foo") - .asChildOf(mockedContext) - .start() - - final DDSpanContext actualContext = span.context() - - expect: - actualContext.getParentId() == expectedParentId - actualContext.getTraceId() == traceId - } - - def "should link to parent span implicitly"() { - setup: - final AgentScope parent = tracer.activateSpan(noopParent ? - noopSpan() : tracer.buildSpan("test", "parent").withServiceName("service").start()) - - final long expectedParentId = noopParent ? DDSpanId.ZERO : parent.span().context().getSpanId() - - final String expectedName = "fakeName" - - final DDSpan span = tracer - .buildSpan("test", expectedName) - .withServiceName(serviceName) - .start() - - final DDSpanContext actualContext = span.context() - - expect: - actualContext.getParentId() == expectedParentId - span.isTopLevel() == expectTopLevel - - cleanup: - parent.close() - - where: - noopParent | serviceName | expectTopLevel - false | "service" | false - true | "service" | true - false | "another service" | true - true | "another service" | true - } - - def "should inherit the DD parent attributes"() { - setup: - def expectedName = "fakeName" - def expectedParentServiceName = "fakeServiceName" - def expectedParentResourceName = "fakeResourceName" - def expectedParentType = "fakeType" - def expectedChildServiceName = "fakeServiceName-child" - def expectedChildResourceName = "fakeResourceName-child" - def expectedChildType = "fakeType-child" - def expectedBaggageItemKey = "fakeKey" - def expectedBaggageItemValue = "fakeValue" - - final DDSpan parent = - tracer - .buildSpan("test", expectedName) - .withServiceName("foo") - .withResourceName(expectedParentResourceName) - .withSpanType(expectedParentType) - .start() - - parent.setBaggageItem(expectedBaggageItemKey, expectedBaggageItemValue) - - // ServiceName and SpanType are always set by the parent if they are not present in the child - DDSpan span = - tracer - .buildSpan("test", expectedName) - .withServiceName(expectedParentServiceName) - .asChildOf(parent) - .start() - - expect: - span.getOperationName() == expectedName - span.getBaggageItem(expectedBaggageItemKey) == expectedBaggageItemValue - span.context().getServiceName() == expectedParentServiceName - span.context().getResourceName() == expectedName - span.context().getSpanType() == null - span.isTopLevel() // service names differ between parent and child - - when: - // ServiceName and SpanType are always overwritten by the child if they are present - span = - tracer - .buildSpan("test", expectedName) - .withServiceName(expectedChildServiceName) - .withResourceName(expectedChildResourceName) - .withSpanType(expectedChildType) - .asChildOf(parent) - .start() - - then: - span.getOperationName() == expectedName - span.getBaggageItem(expectedBaggageItemKey) == expectedBaggageItemValue - span.context().getServiceName() == expectedChildServiceName - span.context().getResourceName() == expectedChildResourceName - span.context().getSpanType() == expectedChildType - } - - def "should track all spans in trace"() { - setup: - List spans = [] - final int nbSamples = 10 - - // root (aka spans[0]) is the parent - // others are just for fun - - def root = tracer.buildSpan("test", "fake_O").withServiceName("foo").start() - - def lastSpan = root - - for (int i = 1; i <= 10; i++) { - lastSpan = tracer - .buildSpan("test", "fake_" + i) - .withServiceName("foo") - .asChildOf(lastSpan) - .start() - spans.add(lastSpan) - lastSpan.finish() - } - - expect: - root.context().getTraceCollector().rootSpan == root - root.context().getTraceCollector().size() == nbSamples - root.context().getTraceCollector().spans.containsAll(spans) - spans[(int) (Math.random() * nbSamples)].context.traceCollector.spans.containsAll(spans) - } - - def "ExtractedContext should populate new span details"() { - setup: - def thread = Thread.currentThread() - final DDSpan span = tracer.buildSpan("test", "op name") - .asChildOf(extractedContext).start() - - expect: - span.traceId == extractedContext.traceId - span.parentId == extractedContext.spanId - span.samplingPriority == extractedContext.samplingPriority - span.context().origin == extractedContext.origin - span.context().baggageItems == extractedContext.baggage - // check the extracted context has been copied into the span tags - for (Map.Entry tag : extractedContext.tags) { - span.context().tags.get(tag.getKey()) == tag.getValue() - } - span.getTag(THREAD_ID) == thread.id - span.getTag(THREAD_NAME) == thread.name - span.context().propagationTags.headerValue(PropagationTags.HeaderType.DATADOG) == extractedContext.propagationTags.headerValue(PropagationTags.HeaderType.DATADOG) - - where: - extractedContext | _ - new ExtractedContext(DDTraceId.ONE, 2, PrioritySampling.SAMPLER_DROP, null, 0, [:], [:], null, PropagationTags.factory().fromHeaderValue(PropagationTags.HeaderType.DATADOG, "_dd.p.dm=934086a686-4,_dd.p.anytag=value"), null, DATADOG) | _ - new ExtractedContext(DDTraceId.from(3), 4, PrioritySampling.SAMPLER_KEEP, "some-origin", 0, ["asdf": "qwer"], [(ORIGIN_KEY): "some-origin", "zxcv": "1234"], null, PropagationTags.factory().empty(), null, DATADOG) | _ - } - - def "build context from ExtractedContext with TRACE_PROPAGATION_BEHAVIOR_EXTRACT=restart"() { - setup: - injectSysConfig("trace.propagation.behavior.extract", "restart") - def extractedContext = new ExtractedContext(DDTraceId.ONE, 2, PrioritySampling.SAMPLER_DROP, null, 0, [:], [:], null, PropagationTags.factory().fromHeaderValue(PropagationTags.HeaderType.DATADOG, "_dd.p.dm=934086a686-4,_dd.p.anytag=value"), null, DATADOG) - final DDSpan span = tracer.buildSpan("test", "op name") - .asChildOf(extractedContext).start() - - expect: - span.traceId != extractedContext.traceId - span.parentId != extractedContext.spanId - span.samplingPriority() == PrioritySampling.UNSET - - def spanLinks = span.links - - assert spanLinks.size() == 1 - def link = spanLinks[0] - link.traceId() == extractedContext.traceId - link.spanId() == extractedContext.spanId - link.traceState() == extractedContext.propagationTags.headerValue(PropagationTags.HeaderType.W3C) - } - - def "build context from ExtractedContext with TRACE_PROPAGATION_BEHAVIOR_EXTRACT=ignore"() { - setup: - injectSysConfig("trace.propagation.behavior.extract", "ignore") - def extractedContext = new ExtractedContext(DDTraceId.ONE, 2, PrioritySampling.SAMPLER_DROP, null, 0, [:], [:], null, PropagationTags.factory().fromHeaderValue(PropagationTags.HeaderType.DATADOG, "_dd.p.dm=934086a686-4,_dd.p.anytag=value"), null, DATADOG) - final DDSpan span = tracer.buildSpan("test", "op name") - .asChildOf(extractedContext).start() - - expect: - span.traceId != extractedContext.traceId - span.parentId != extractedContext.spanId - span.samplingPriority() == PrioritySampling.UNSET - span.links.empty - } - - def "TagContext should populate default span details"() { - setup: - def thread = Thread.currentThread() - final DDSpan span = tracer.buildSpan("test", "op name").asChildOf(tagContext).start() - - expect: - span.traceId != DDTraceId.ZERO - span.parentId == DDSpanId.ZERO - span.samplingPriority == null - span.context().origin == tagContext.origin - span.context().baggageItems == [:] - span.context().tags == tagContext.tags + [ - (RUNTIME_ID_TAG) : Config.get().getRuntimeId(), - (LANGUAGE_TAG_KEY) : LANGUAGE_TAG_VALUE, - (THREAD_NAME) : thread.name, (THREAD_ID): thread.id, (PID_TAG): Config.get().getProcessId(), - (SCHEMA_VERSION_TAG_KEY) : SpanNaming.instance().version() - ] + productTags() - - where: - tagContext | _ - new TagContext(null, TagMap.fromMap([:])) | _ - new TagContext("some-origin", TagMap.fromMap(["asdf": "qwer"])) | _ - } - - def "global span tags populated on each span"() { - setup: - injectSysConfig("dd.trace.span.tags", tagString) - def customTracer = tracerBuilder().writer(writer).build() - def span = customTracer.buildSpan("test", "op name").withServiceName("foo").start() - - expect: - span.tags == tags + [ - (THREAD_NAME) : Thread.currentThread().getName(), - (THREAD_ID) : Thread.currentThread().getId(), - (RUNTIME_ID_TAG) : Config.get().getRuntimeId(), - (LANGUAGE_TAG_KEY) : LANGUAGE_TAG_VALUE, - (PID_TAG) : Config.get().getProcessId(), - (SCHEMA_VERSION_TAG_KEY) : SpanNaming.instance().version() - ] + productTags() - - cleanup: - customTracer.close() - - where: - tagString | tags - "" | [:] - "is:val:id" | [is: "val:id"] - "a:x" | [a: "x"] - "a:a,a:b,a:c" | [a: "c"] - "a:1,b-c:d" | [a: "1", "b-c": "d"] - } - - def "can overwrite RequestContext data with builder from empty"() { - when: - def span1 = tracer.startSpan("test", "span1") - - then: - span1.getRequestContext().getData(RequestContextSlot.APPSEC) == null - span1.getRequestContext().getData(RequestContextSlot.CI_VISIBILITY) == null - span1.getRequestContext().getData(RequestContextSlot.IAST) == null - - when: - def span2 = tracer.buildSpan("test", "span2") - .asChildOf(span1.context()) - .withRequestContextData(RequestContextSlot.APPSEC, "override") - .withRequestContextData(RequestContextSlot.CI_VISIBILITY, "override") - .withRequestContextData(RequestContextSlot.IAST, "override") - .start() - - then: - span2.getRequestContext().getData(RequestContextSlot.APPSEC) == "override" - span2.getRequestContext().getData(RequestContextSlot.CI_VISIBILITY) == "override" - span2.getRequestContext().getData(RequestContextSlot.IAST) == "override" - - cleanup: - span2.finish() - span1.finish() - } - - def "can overwrite RequestContext data with builder"() { - setup: - TagContext context = new TagContext() - .withCiVisibilityContextData("value") - .withRequestContextDataIast("value") - .withRequestContextDataAppSec("value") - def span1 = tracer.buildSpan("test", "span1").asChildOf(context).start() - - when: - def span2 = tracer.buildSpan("test", "span2").asChildOf(span1.context()).start() - - then: - span2.getRequestContext().getData(RequestContextSlot.APPSEC) == "value" - span2.getRequestContext().getData(RequestContextSlot.CI_VISIBILITY) == "value" - span2.getRequestContext().getData(RequestContextSlot.IAST) == "value" - - when: - def span3 = tracer.buildSpan("test", "span3") - .asChildOf(span2.context()) - .withRequestContextData(RequestContextSlot.APPSEC, "override") - .withRequestContextData(RequestContextSlot.CI_VISIBILITY, "override") - .withRequestContextData(RequestContextSlot.IAST, "override") - .start() - - then: - span3.getRequestContext().getData(RequestContextSlot.APPSEC) == "override" - span3.getRequestContext().getData(RequestContextSlot.CI_VISIBILITY) == "override" - span3.getRequestContext().getData(RequestContextSlot.IAST) == "override" - - cleanup: - span3.finish() - span2.finish() - span1.finish() - } - - def productTags() { - def productTags = [ - (PROFILING_ENABLED) : Config.get().isProfilingEnabled() ? 1 : 0 - ] - if (Config.get().isDataStreamsEnabled()) { - productTags[DSM_ENABLED] = 1 - } - if (Config.get().isDataJobsEnabled()) { - productTags[DJM_ENABLED] = 1 - } - return productTags - } -} diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreTracerTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreTracerTest.groovy deleted file mode 100644 index 97eac1fce6a..00000000000 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreTracerTest.groovy +++ /dev/null @@ -1,670 +0,0 @@ -package datadog.trace.core - -import datadog.communication.ddagent.DDAgentFeaturesDiscovery -import datadog.communication.ddagent.SharedCommunicationObjects -import datadog.metrics.api.Monitoring -import datadog.remoteconfig.ConfigurationPoller -import datadog.remoteconfig.Product -import datadog.remoteconfig.state.ParsedConfigKey -import datadog.remoteconfig.state.ProductListener -import datadog.trace.api.Config -import datadog.trace.api.DDTags -import datadog.trace.api.remoteconfig.ServiceNameCollector -import datadog.trace.api.sampling.PrioritySampling -import datadog.trace.api.sampling.SamplingMechanism -import datadog.trace.bootstrap.instrumentation.api.ServiceNameSources -import datadog.trace.common.sampling.AllSampler -import datadog.trace.common.sampling.PrioritySampler -import datadog.trace.common.sampling.RateByServiceTraceSampler -import datadog.trace.common.sampling.Sampler -import datadog.trace.common.writer.DDAgentWriter -import datadog.trace.common.writer.ListWriter -import datadog.trace.common.writer.LoggingWriter -import datadog.trace.core.tagprocessor.TagsPostProcessorFactory -import datadog.trace.core.test.DDCoreSpecification -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import spock.lang.Timeout - -import java.nio.charset.StandardCharsets -import java.util.concurrent.CopyOnWriteArrayList - -import static datadog.trace.api.config.GeneralConfig.ENV -import static datadog.trace.api.config.GeneralConfig.SERVICE_NAME -import static datadog.trace.api.config.GeneralConfig.VERSION -import static datadog.trace.api.config.TracerConfig.AGENT_UNIX_DOMAIN_SOCKET -import static datadog.trace.api.config.TracerConfig.BAGGAGE_MAPPING -import static datadog.trace.api.config.TracerConfig.HEADER_TAGS -import static datadog.trace.api.config.TracerConfig.PRIORITY_SAMPLING -import static datadog.trace.api.config.TracerConfig.SERVICE_MAPPING -import static datadog.trace.api.config.TracerConfig.SPAN_TAGS -import static datadog.trace.api.config.TracerConfig.WRITER_TYPE - -@Timeout(10) -class CoreTracerTest extends DDCoreSpecification { - - def "verify defaults on tracer"() { - when: - def tracer = CoreTracer.builder().build() - - then: - tracer.serviceName != "" - tracer.initialSampler instanceof RateByServiceTraceSampler - tracer.writer instanceof DDAgentWriter - - cleanup: - tracer.close() - } - - - - def "verify overriding sampler"() { - setup: - injectSysConfig(PRIORITY_SAMPLING, "false") - - when: - def tracer = tracerBuilder().build() - - then: - tracer.initialSampler instanceof AllSampler - - cleanup: - tracer.close() - } - - def "verify overriding writer"() { - setup: - injectSysConfig(WRITER_TYPE, "LoggingWriter") - - when: - def tracer = tracerBuilder().build() - - then: - tracer.writer instanceof LoggingWriter - - cleanup: - tracer.close() - } - - def "verify uds+windows"() { - setup: - System.setProperty("os.name", "Windows ME") - - when: - injectSysConfig(AGENT_UNIX_DOMAIN_SOCKET, uds) - - then: - Config.get().getAgentUnixDomainSocket() == uds - - where: - uds = "asdf" - } - - def "verify mapping configs on tracer"() { - setup: - injectSysConfig(SERVICE_MAPPING, mapString) - injectSysConfig(SPAN_TAGS, mapString) - injectSysConfig(HEADER_TAGS, mapString) - - when: - def tracer = tracerBuilder().build() - - then: - tracer.defaultSpanTags == map - tracer.captureTraceConfig().serviceMapping == map - - cleanup: - tracer.close() - - where: - mapString | map - "a:one, a:two, a:three" | [a: "three"] - "a:b,c:d,e:" | [a: "b", c: "d"] - } - - def "verify baggage mapping configs on tracer"() { - setup: - injectSysConfig(BAGGAGE_MAPPING, mapString) - - when: - def tracer = tracerBuilder().build() - - then: - tracer.captureTraceConfig().baggageMapping == map - - cleanup: - tracer.close() - - where: - mapString | map - "a:one, a:two, a:three" | [a: "three"] - "a:b,c:d,e:" | [a: "b", c: "d"] - } - - def "verify overriding host"() { - when: - injectSysConfig(key, value) - - then: - Config.get().getAgentHost() == value - - where: - key | value - "agent.host" | "somethingelse" - } - - def "verify overriding port"() { - when: - injectSysConfig(key, value) - - then: - Config.get().getAgentPort() == Integer.valueOf(value) - - where: - key | value - "agent.port" | "777" - "trace.agent.port" | "9999" - } - - def "Writer is instance of LoggingWriter when property set"() { - when: - injectSysConfig("writer.type", "LoggingWriter") - def tracer = tracerBuilder().build() - - then: - tracer.writer instanceof LoggingWriter - - cleanup: - tracer.close() - } - - def "Shares TraceCount with DDApi with #key = #value"() { - setup: - injectSysConfig(key, value) - final CoreTracer tracer = tracerBuilder().build() - - expect: - tracer.writer instanceof DDAgentWriter - - cleanup: - tracer.close() - - where: - key | value - PRIORITY_SAMPLING | "true" - PRIORITY_SAMPLING | "false" - } - - def "root tags are applied only to root spans"() { - setup: - def tracer = tracerBuilder().localRootSpanTags(['only_root': 'value']).build() - def root = tracer.buildSpan('my_root').start() - def child = tracer.buildSpan('my_child').asChildOf(root).start() - - expect: - root.context().tags.containsKey('only_root') - !child.context().tags.containsKey('only_root') - - cleanup: - child.finish() - root.finish() - tracer.close() - } - - def "priority sampling when span finishes"() { - given: - def writer = new ListWriter() - def tracer = tracerBuilder().writer(writer).build() - - when: - def span = tracer.buildSpan("operation").start() - span.finish() - writer.waitForTraces(1) - - then: - span.getSamplingPriority() == PrioritySampling.SAMPLER_KEEP - - cleanup: - tracer.close() - } - - def "priority sampling set when child span complete"() { - given: - def writer = new ListWriter() - def tracer = tracerBuilder().writer(writer).build() - - when: - def root = tracer.buildSpan("operation").start() - def child = tracer.buildSpan('my_child').asChildOf(root).start() - root.finish() - - then: - root.getSamplingPriority() == null - - when: - child.finish() - writer.waitForTraces(1) - - then: - root.getSamplingPriority() == PrioritySampling.SAMPLER_KEEP - child.getSamplingPriority() == root.getSamplingPriority() - - cleanup: - tracer.close() - } - - def "verify configuration polling"() { - setup: - def key = ParsedConfigKey.parse("datadog/2/APM_TRACING/config_overrides/config") - def poller = Mock(ConfigurationPoller) - def sco = new SharedCommunicationObjects( - agentHttpClient: Mock(OkHttpClient), - monitoring: Mock(Monitoring), - agentUrl: HttpUrl.get('https://example.com'), - featuresDiscovery: Mock(DDAgentFeaturesDiscovery), - configurationPoller: poller - ) - - def updater - - when: - def tracer = CoreTracer.builder() - .sharedCommunicationObjects(sco) - .pollForTracingConfiguration() - .build() - - then: - 1 * poller.addListener(Product.APM_TRACING, _ as ProductListener) >> { - updater = it[1] // capture config updater for further testing - } - and: - tracer.captureTraceConfig().serviceMapping == [:] - tracer.captureTraceConfig().requestHeaderTags == [:] - tracer.captureTraceConfig().responseHeaderTags == [:] - tracer.captureTraceConfig().traceSampleRate == null - - when: - updater.accept(key, ''' - { - "lib_config": - { - "tracing_service_mapping": - [{ - "from_key": "foobar", - "to_name": "bar" - }, { - "from_key": "snafu", - "to_name": "foo" - }] - , - "tracing_header_tags": - [{ - "header": "Cookie", - "tag_name": "" - }, { - "header": "Referer", - "tag_name": "http.referer" - }, { - "header": " Some.Header ", - "tag_name": "" - }, { - "header": "C!!!ont_____ent----tYp!/!e", - "tag_name": "" - }, { - "header": "this.header", - "tag_name": "whatever.the.user.wants.this.header" - }] - , - "tracing_sampling_rate": 0.5 - } - } - '''.getBytes(StandardCharsets.UTF_8), null) - updater.commit() - - then: - tracer.captureTraceConfig().serviceMapping == ['foobar':'bar', 'snafu':'foo'] - tracer.captureTraceConfig().requestHeaderTags == [ - 'cookie':'http.request.headers.cookie', - 'referer':'http.referer', - 'some.header':'http.request.headers.some_header', - 'c!!!ont_____ent----typ!/!e':'http.request.headers.c___ont_____ent----typ_/_e', - 'this.header':'whatever.the.user.wants.this.header' - ] - tracer.captureTraceConfig().responseHeaderTags == [ - 'cookie':'http.response.headers.cookie', - 'referer':'http.referer', - 'some.header':'http.response.headers.some_header', - 'c!!!ont_____ent----typ!/!e':'http.response.headers.c___ont_____ent----typ_/_e', - 'this.header':'whatever.the.user.wants.this.header' - ] - tracer.captureTraceConfig().traceSampleRate == 0.5 - - when: - updater.remove(key, null) - updater.commit() - - then: - tracer.captureTraceConfig().serviceMapping == [:] - tracer.captureTraceConfig().requestHeaderTags == [:] - tracer.captureTraceConfig().responseHeaderTags == [:] - tracer.captureTraceConfig().traceSampleRate == null - - cleanup: - tracer?.close() - } - - def "verify configuration polling with custom tags"() { - setup: - def key = ParsedConfigKey.parse("datadog/2/APM_TRACING/config_overrides/config") - def poller = Mock(ConfigurationPoller) - def sco = new SharedCommunicationObjects( - agentHttpClient: Mock(OkHttpClient), - monitoring: Mock(Monitoring), - agentUrl: HttpUrl.get('https://example.com'), - featuresDiscovery: Mock(DDAgentFeaturesDiscovery), - configurationPoller: poller - ) - - def updater - - when: - def tracer = CoreTracer.builder() - .sharedCommunicationObjects(sco) - .pollForTracingConfiguration() - .build() - - then: - 1 * poller.addListener(Product.APM_TRACING, _ as ProductListener) >> { - updater = it[1] // capture config updater for further testing - } - and: - tracer.captureTraceConfig().tracingTags == [:] - - when: - updater.accept(key, value.getBytes(StandardCharsets.UTF_8), null) - updater.commit() - - then: - tracer.captureTraceConfig().tracingTags == expectedValue - tracer.captureTraceConfig().mergedTracerTags == expectedValue - when: - updater.remove(key, null) - updater.commit() - - then: - tracer.captureTraceConfig().tracingTags == [:] - - cleanup: - tracer?.close() - - where: - value | expectedValue - """{"lib_config":{"tracing_tags": ["a:b", "c:d", "e:f"]}}""" | ["a":"b", "c":"d", "e":"f"] - """{"lib_config":{"tracing_tags": ["", "c:d", ""]}}""" | [ "c":"d"] - """{"lib_config":{"tracing_tags": [":b", "c:", "e:f"]}}""" | ["e":"f"] - """{"lib_config":{"tracing_tags": [":", "c:", "e:f"]}}""" | ["e":"f"] - """{"lib_config":{"tracing_tags": [":", "c:", ""]}}""" | [:] - """{"lib_config":{"tracing_tags": []}}""" | [:] - } - - def "verify configuration polling with tracing_enabled"() { - setup: - def key = ParsedConfigKey.parse("datadog/2/APM_TRACING/config_overrides/config") - def poller = Mock(ConfigurationPoller) - def sco = new SharedCommunicationObjects( - agentHttpClient: Mock(OkHttpClient), - monitoring: Mock(Monitoring), - agentUrl: HttpUrl.get('https://example.com'), - featuresDiscovery: Mock(DDAgentFeaturesDiscovery), - configurationPoller: poller - ) - - def updater - - when: - def tracer = CoreTracer.builder() - .sharedCommunicationObjects(sco) - .pollForTracingConfiguration() - .build() - - then: - - 1 * poller.addListener(Product.APM_TRACING, _ as ProductListener) >> { - updater = it[1] // capture config updater for further testing - } - and: - tracer.captureTraceConfig().traceEnabled == true - - when: - updater.accept(key, value.getBytes(StandardCharsets.UTF_8), null) - updater.commit() - - then: - tracer.captureTraceConfig().traceEnabled == expectedValue - - cleanup: - tracer?.close() - - where: - value | expectedValue - """{"lib_config":{"tracing_enabled": false } } """ | false - """{"lib_config":{"tracing_enabled": true } } """ | true - """{"action": "enable", "lib_config": {"tracing_sampling_rate": null, "log_injection_enabled": null, "tracing_header_tags": null, "runtime_metrics_enabled": null, "tracing_debug": null, "tracing_service_mapping": null, "tracing_sampling_rules": null, "span_sampling_rules": null, "data_streams_enabled": null, "tracing_enabled": false}}""" | false - } - - def "test local root service name override"() { - setup: - def tracer = tracerBuilder().writer(new ListWriter()).serviceName("test").build() - tracer.updatePreferredServiceName(preferred, preferred) - when: - def span = tracer.startSpan("", "test") - span.finish() - then: - span.serviceName == expected - and: - if (preferred != null) { - ServiceNameCollector.get().getServices().contains(preferred) - } - cleanup: - tracer?.close() - where: - preferred | expected - null | "test" - "some" | "some" - } - - def "test dd_version exists only if service == dd_service"() { - setup: - injectSysConfig(SERVICE_NAME, "dd_service_name") - injectSysConfig(VERSION, "1.0.0") - TagsPostProcessorFactory.withAddInternalTags(true) - def tracer = tracerBuilder().writer(new ListWriter()).build() - - when: - def span = tracer.buildSpan("def").withTag(SERVICE_NAME,"foo").start() - span.finish() - then: - span.getServiceName() == "foo" - span.getTags().containsKey("version") == false - - when: - def span2 = tracer.buildSpan("abc").start() - span2.finish() - then: - span2.getServiceName() == "dd_service_name" - span2.getTags()["version"]?.toString() == "1.0.0" - - cleanup: - tracer?.close() - } - - def "flushes on tracer close if configured to do so"() { - given: - def writer = new WriterWithExplicitFlush() - def tracer = tracerBuilder().writer(writer).flushOnClose(true).build() - - when: - tracer.buildSpan('my_span').start().finish() - tracer.close() - - then: - !writer.flushedTraces.empty - } - - def "verify no filtering of service/env when mismatched with DD_SERVICE/DD_ENV"() { - setup: - injectSysConfig(SERVICE_NAME, service) - injectSysConfig(ENV, env) - - def key = ParsedConfigKey.parse("datadog/2/APM_TRACING/config_overrides/config") - def poller = Mock(ConfigurationPoller) - def sco = new SharedCommunicationObjects( - agentHttpClient: Mock(OkHttpClient), - monitoring: Mock(Monitoring), - agentUrl: HttpUrl.get('https://example.com'), - featuresDiscovery: Mock(DDAgentFeaturesDiscovery), - configurationPoller: poller - ) - - def updater - - when: - def tracer = CoreTracer.builder() - .sharedCommunicationObjects(sco) - .pollForTracingConfiguration() - .build() - - then: - 1 * poller.addListener(Product.APM_TRACING, _ as ProductListener) >> { - updater = it[1] // capture config updater for further testing - } - and: - tracer.captureTraceConfig().serviceMapping == [:] - - when: - updater.accept(key, """ - { - "service_target": { - "service": "${targetService}", - "env": "${targetEnv}" - }, - "lib_config": - { - "tracing_service_mapping": - [{ - "from_key": "foobar", - "to_name": "bar" - }] - } - } - """.getBytes(StandardCharsets.UTF_8), null) - updater.commit() - - then: "configuration should be applied" - tracer.captureTraceConfig().serviceMapping == ["foobar":"bar"] - - cleanup: - tracer?.close() - - where: - service | env | targetService | targetEnv - "service" | "env" | "service_1" | "env" - "service" | "env" | "service" | "env_1" - "service" | "env" | "service_2" | "env_2" - } - - def "service name source is recorded when using two-parameter setServiceName"() { - setup: - def tracer = tracerBuilder().writer(new ListWriter()).build() - - when: - def span = tracer.buildSpan("operation").start() - span.setServiceName("custom-service", "my-integration") - def child = tracer.buildSpan("child").start() - child.finish() - span.finish() - - then: - [span, child].each { - assert span.getServiceName() == "custom-service" - assert span.getTag(DDTags.DD_SVC_SRC) == "my-integration" - } - cleanup: - tracer?.close() - } - - def "service name source is marked as manual when using one-parameter setServiceName"() { - setup: - def tracer = tracerBuilder().writer(new ListWriter()).build() - - when: - def span = tracer.buildSpan("operation").start() - span.setServiceName("custom-service", "my-integration") - span.setServiceName("another") - span.finish() - - then: - span.getServiceName() == "another" - span.getTag(DDTags.DD_SVC_SRC) == ServiceNameSources.MANUAL - cleanup: - tracer?.close() - } - - def "service name source is missing when not explicitly setting the service name"() { - setup: - def tracer = tracerBuilder().writer(new ListWriter()).build() - - when: - def span = tracer.buildSpan("operation").start() - span.finish() - - then: - span.getServiceName() == tracer.serviceName - span.getTag(DDTags.DD_SVC_SRC) == null - cleanup: - tracer?.close() - } -} - -class WriterWithExplicitFlush implements datadog.trace.common.writer.Writer { - final List> writtenTraces = new CopyOnWriteArrayList<>() - final List> flushedTraces = new CopyOnWriteArrayList<>() - - @Override - void write(List trace) { - writtenTraces.add(trace) - } - - @Override - void start() { - } - - @Override - boolean flush() { - flushedTraces.addAll(writtenTraces) - writtenTraces.clear() - return true - } - - @Override - void close() { - } - - @Override - void incrementDropCounts(int spanCount) { - } -} - -class ControllableSampler implements Sampler, PrioritySampler { - protected int nextSamplingPriority = PrioritySampling.SAMPLER_KEEP - - @Override - > void setSamplingPriority(T span) { - span.setSamplingPriority(nextSamplingPriority, SamplingMechanism.DEFAULT) - } - - @Override - > boolean sample(T span) { - return true - } -} diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanContextPropagationTagsTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanContextPropagationTagsTest.groovy deleted file mode 100644 index 1fc5dc342c9..00000000000 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanContextPropagationTagsTest.groovy +++ /dev/null @@ -1,134 +0,0 @@ -package datadog.trace.core - -import datadog.trace.api.DDTraceId -import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext -import datadog.trace.common.writer.ListWriter -import datadog.trace.core.propagation.ExtractedContext -import datadog.trace.core.propagation.PropagationTags -import datadog.trace.core.test.DDCoreSpecification - -import static datadog.trace.api.TracePropagationStyle.DATADOG -import static datadog.trace.api.sampling.PrioritySampling.* -import static datadog.trace.api.sampling.SamplingMechanism.* - -class DDSpanContextPropagationTagsTest extends DDCoreSpecification { - - def writer = new ListWriter() - def tracer - - def cleanup() { - tracer.close() - } - - def "update span PropagationTags #priority #header"() { - setup: - tracer = tracerBuilder().writer(writer).build() - def propagationTags = tracer.propagationTagsFactory.fromHeaderValue(PropagationTags.HeaderType.DATADOG, header) - def extracted = new ExtractedContext(DDTraceId.from(123), 456, priority, "789", propagationTags, DATADOG) - .withRequestContextDataAppSec("dummy") - def span = (DDSpan) tracer.buildSpan("top") - .asChildOf((AgentSpanContext) extracted) - .start() - def dd = span.context().getPropagationTags() - - when: - span.setSamplingPriority(newPriority, newMechanism) - span.getSamplingPriority() == newPriority - - then: - dd.headerValue(PropagationTags.HeaderType.DATADOG) == newHeader - dd.createTagMap() == tagMap - - where: - priority | header | newPriority | newMechanism | newHeader | tagMap - UNSET | "_dd.p.usr=123" | USER_KEEP | MANUAL | "_dd.p.dm=-4,_dd.p.usr=123" | ["_dd.p.dm": "-4", "_dd.p.usr": "123"] - UNSET | "_dd.p.usr=123" | SAMPLER_DROP | DEFAULT | "_dd.p.usr=123" | ["_dd.p.usr": "123"] - // decision has already been made, propagate as-is - SAMPLER_KEEP | "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123" | USER_KEEP | MANUAL | "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123" | ["_dd.p.dm": "9bf3439f2f-1", "_dd.p.usr": "123"] - SAMPLER_KEEP | "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123" | USER_DROP | MANUAL | "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123" | ["_dd.p.dm": "9bf3439f2f-1", "_dd.p.usr": "123"] - SAMPLER_KEEP | "_dd.p.usr=123" | USER_KEEP | MANUAL | "_dd.p.usr=123" | ["_dd.p.usr": "123"] - } - - def "update trace PropagationTags #priority #header"() { - setup: - tracer = tracerBuilder().writer(writer).build() - def propagationTags = tracer.propagationTagsFactory.fromHeaderValue(PropagationTags.HeaderType.DATADOG, header) - def extracted = new ExtractedContext(DDTraceId.from(123), 456, priority, "789", propagationTags, DATADOG) - .withRequestContextDataAppSec("dummy") - def rootSpan = (DDSpan) tracer.buildSpan("top") - .asChildOf((AgentSpanContext) extracted) - .start() - def ddRoot = rootSpan.context().getPropagationTags() - def span = (DDSpan) tracer.buildSpan("current").asChildOf(rootSpan).start() - - when: - span.setSamplingPriority(newPriority, newMechanism) - span.getSamplingPriority() == newPriority - - then: - ddRoot.headerValue(PropagationTags.HeaderType.DATADOG) == rootHeader - ddRoot.createTagMap() == rootTagMap - - where: - priority | header | newPriority | newMechanism | rootHeader | rootTagMap - UNSET | "_dd.p.usr=123" | USER_KEEP | MANUAL | "_dd.p.dm=-4,_dd.p.usr=123" | ["_dd.p.dm": "-4", "_dd.p.usr": "123"] - UNSET | "_dd.p.usr=123" | SAMPLER_DROP | DEFAULT | "_dd.p.usr=123" | ["_dd.p.usr": "123"] - // decision has already been made, propagate as-is - SAMPLER_KEEP | "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123" | USER_KEEP | MANUAL | "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123" | ["_dd.p.dm": "9bf3439f2f-1", "_dd.p.usr": "123"] - SAMPLER_KEEP | "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123" | USER_DROP | MANUAL | "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123" | ["_dd.p.dm": "9bf3439f2f-1", "_dd.p.usr": "123"] - SAMPLER_KEEP | "_dd.p.usr=123" | USER_KEEP | MANUAL | "_dd.p.usr=123" | ["_dd.p.usr": "123"] - } - - def "forceKeep span PropagationTags #priority #header"() { - setup: - tracer = tracerBuilder().writer(writer).build() - def propagationTags = tracer.propagationTagsFactory.fromHeaderValue(PropagationTags.HeaderType.DATADOG, header) - def extracted = new ExtractedContext(DDTraceId.from(123), 456, priority, "789", propagationTags, DATADOG) - .withRequestContextDataAppSec("dummy") - def span = (DDSpan) tracer.buildSpan("top") - .asChildOf((AgentSpanContext) extracted) - .start() - def dd = span.context().getPropagationTags() - - when: - span.context().forceKeep() - span.getSamplingPriority() == USER_KEEP - - then: - dd.headerValue(PropagationTags.HeaderType.DATADOG) == newHeader - dd.createTagMap() == tagMap - - where: - priority | header | newHeader | tagMap - UNSET | "_dd.p.usr=123" | "_dd.p.dm=-4,_dd.p.usr=123" | ["_dd.p.dm": "-4", "_dd.p.usr": "123"] - SAMPLER_KEEP | "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123" | "_dd.p.dm=-4,_dd.p.usr=123" | ["_dd.p.dm": "-4", "_dd.p.usr": "123"] - SAMPLER_KEEP | "_dd.p.usr=123" | "_dd.p.dm=-4,_dd.p.usr=123" | ["_dd.p.dm": "-4", "_dd.p.usr": "123"] - } - - def "forceKeep trace PropagationTags #priority #header"() { - setup: - tracer = tracerBuilder().writer(writer).build() - def propagationTags = tracer.propagationTagsFactory.fromHeaderValue(PropagationTags.HeaderType.DATADOG, header) - def extracted = new ExtractedContext(DDTraceId.from(123), 456, priority, "789", propagationTags, DATADOG) - .withRequestContextDataAppSec("dummy") - def rootSpan = (DDSpan) tracer.buildSpan("top") - .asChildOf((AgentSpanContext) extracted) - .start() - def ddRoot = rootSpan.context().getPropagationTags() - def span = (DDSpan) tracer.buildSpan("current").asChildOf(rootSpan).start() - - when: - span.context().forceKeep() - span.getSamplingPriority() == USER_KEEP - - then: - ddRoot.headerValue(PropagationTags.HeaderType.DATADOG) == rootHeader - ddRoot.createTagMap() == rootTagMap - - where: - priority | header | rootHeader | rootTagMap - UNSET | "_dd.p.usr=123" | "_dd.p.dm=-4,_dd.p.usr=123" | ["_dd.p.dm": "-4", "_dd.p.usr": "123"] - SAMPLER_KEEP | "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123" | "_dd.p.dm=-4,_dd.p.usr=123" | ["_dd.p.dm": "-4", "_dd.p.usr": "123"] - SAMPLER_KEEP | "_dd.p.usr=123" | "_dd.p.dm=-4,_dd.p.usr=123" | ["_dd.p.dm": "-4", "_dd.p.usr": "123"] - } -} diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/taginterceptor/TagInterceptorTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/taginterceptor/TagInterceptorTest.groovy index 075ac57a188..0bb65ac4919 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/taginterceptor/TagInterceptorTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/taginterceptor/TagInterceptorTest.groovy @@ -696,6 +696,7 @@ class TagInterceptorTest extends DDCoreSpecification { } void "when interceptServiceName extraServiceProvider is called"() { + def origServiceNameCollector = ServiceNameCollector.INSTANCE setup: final extraServiceProvider = Mock(ServiceNameCollector) ServiceNameCollector.INSTANCE = extraServiceProvider @@ -708,9 +709,13 @@ class TagInterceptorTest extends DDCoreSpecification { then: 1 * extraServiceProvider.addService("some-service") + + cleanup: + ServiceNameCollector.INSTANCE = origServiceNameCollector } void "when interceptServletContext extraServiceProvider is called"() { + def origServiceNameCollector = ServiceNameCollector.INSTANCE setup: final extraServiceProvider = Mock(ServiceNameCollector) ServiceNameCollector.INSTANCE = extraServiceProvider @@ -724,6 +729,9 @@ class TagInterceptorTest extends DDCoreSpecification { then: 1 * extraServiceProvider.addService(expected) + cleanup: + ServiceNameCollector.INSTANCE = origServiceNameCollector + where: value | expected "/" | "root-servlet" diff --git a/dd-trace-core/src/test/java/datadog/trace/core/ControllableSampler.java b/dd-trace-core/src/test/java/datadog/trace/core/ControllableSampler.java new file mode 100644 index 00000000000..4941fc0894a --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/ControllableSampler.java @@ -0,0 +1,20 @@ +package datadog.trace.core; + +import datadog.trace.api.sampling.PrioritySampling; +import datadog.trace.api.sampling.SamplingMechanism; +import datadog.trace.common.sampling.PrioritySampler; +import datadog.trace.common.sampling.Sampler; + +public class ControllableSampler implements Sampler, PrioritySampler { + protected int nextSamplingPriority = PrioritySampling.SAMPLER_KEEP; + + @Override + public > void setSamplingPriority(T span) { + span.setSamplingPriority(nextSamplingPriority, SamplingMechanism.DEFAULT); + } + + @Override + public > boolean sample(T span) { + return true; + } +} diff --git a/dd-trace-core/src/test/java/datadog/trace/core/CoreSpanBuilderTest.java b/dd-trace-core/src/test/java/datadog/trace/core/CoreSpanBuilderTest.java new file mode 100644 index 00000000000..05c9cd87222 --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/CoreSpanBuilderTest.java @@ -0,0 +1,571 @@ +package datadog.trace.core; + +import static datadog.trace.api.DDTags.DJM_ENABLED; +import static datadog.trace.api.DDTags.DSM_ENABLED; +import static datadog.trace.api.DDTags.LANGUAGE_TAG_KEY; +import static datadog.trace.api.DDTags.LANGUAGE_TAG_VALUE; +import static datadog.trace.api.DDTags.ORIGIN_KEY; +import static datadog.trace.api.DDTags.PID_TAG; +import static datadog.trace.api.DDTags.PROFILING_ENABLED; +import static datadog.trace.api.DDTags.RUNTIME_ID_TAG; +import static datadog.trace.api.DDTags.SCHEMA_VERSION_TAG_KEY; +import static datadog.trace.api.DDTags.THREAD_ID; +import static datadog.trace.api.DDTags.THREAD_NAME; +import static datadog.trace.api.TracePropagationStyle.DATADOG; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopSpan; +import static datadog.trace.junit.utils.config.WithConfigExtension.injectSysConfig; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import datadog.trace.api.Config; +import datadog.trace.api.DDSpanId; +import datadog.trace.api.DDTraceId; +import datadog.trace.api.TagMap; +import datadog.trace.api.datastreams.NoopPathwayContext; +import datadog.trace.api.gateway.RequestContextSlot; +import datadog.trace.api.naming.SpanNaming; +import datadog.trace.api.sampling.PrioritySampling; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.bootstrap.instrumentation.api.TagContext; +import datadog.trace.common.writer.ListWriter; +import datadog.trace.core.propagation.ExtractedContext; +import datadog.trace.core.propagation.PropagationTags; +import datadog.trace.junit.utils.config.WithConfig; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.tabletest.junit.TableTest; + +public class CoreSpanBuilderTest extends DDCoreJavaSpecification { + + private ListWriter writer; + private CoreTracer tracer; + + @BeforeEach + void setup() { + writer = new ListWriter(); + tracer = tracerBuilder().writer(writer).build(); + } + + @Test + void buildSimpleSpan() { + DDSpan span = (DDSpan) tracer.buildSpan("test", "op name").withServiceName("foo").start(); + assertEquals("op name", span.getOperationName()); + } + + @Test + void buildComplexSpan() { + String expectedName = "fakeName"; + Map tags = new HashMap<>(); + tags.put("1", true); + tags.put("2", "fakeString"); + tags.put("3", 42.0); + + AgentTracer.SpanBuilder builder = tracer.buildSpan(expectedName).withServiceName("foo"); + for (Map.Entry entry : tags.entrySet()) { + builder = builder.withTag(entry.getKey(), entry.getValue()); + } + + DDSpan span = (DDSpan) builder.start(); + + assertEquals(expectedName, span.getOperationName()); + for (Map.Entry entry : tags.entrySet()) { + assertEquals(entry.getValue(), span.getTags().get(entry.getKey())); + } + + span = (DDSpan) tracer.buildSpan("test", expectedName).withServiceName("foo").start(); + + Map expectedTags = new HashMap<>(); + expectedTags.put(THREAD_NAME, Thread.currentThread().getName()); + expectedTags.put(THREAD_ID, Thread.currentThread().getId()); + expectedTags.put(RUNTIME_ID_TAG, Config.get().getRuntimeId()); + expectedTags.put(LANGUAGE_TAG_KEY, LANGUAGE_TAG_VALUE); + expectedTags.put(PID_TAG, Config.get().getProcessId()); + expectedTags.put(SCHEMA_VERSION_TAG_KEY, SpanNaming.instance().version()); + expectedTags.putAll(productTags()); + assertEquals(expectedTags, span.getTags()); + + String expectedResource = "fakeResource"; + String expectedService = "fakeService"; + String expectedType = "fakeType"; + + span = + (DDSpan) + tracer + .buildSpan("test", expectedName) + .withServiceName("foo") + .withResourceName(expectedResource) + .withServiceName(expectedService) + .withErrorFlag() + .withSpanType(expectedType) + .start(); + + DDSpanContext context = span.context(); + + assertEquals(expectedResource, context.getResourceName()); + assertTrue(context.getErrorFlag()); + assertEquals(expectedService, context.getServiceName()); + assertEquals(expectedType, context.getSpanType()); + assertEquals(Thread.currentThread().getName(), context.getTag(THREAD_NAME)); + assertEquals(Thread.currentThread().getId(), context.getTag(THREAD_ID)); + } + + @TableTest({ + "scenario | name | value", + "null tag | null.tag | ", + "empty tag | empty.tag | '' " + }) + void settingNameShouldRemove(String name, String value) { + DDSpan span = + (DDSpan) + tracer + .buildSpan("test", "op name") + .withTag(name, "tag value") + .withTag(name, value) + .start(); + + assertNull(span.getTags().get(name)); + + span.setTag(name, "a tag"); + assertEquals("a tag", span.getTags().get(name)); + + span.setTag(name, value); + assertNull(span.getTags().get(name)); + } + + @Test + void shouldBuildSpanTimestampInNano() { + long expectedTimestamp = 487517802L * 1000 * 1000L; + String expectedName = "fakeName"; + + DDSpan span = + (DDSpan) + tracer + .buildSpan("test", expectedName) + .withServiceName("foo") + .withStartTimestamp(expectedTimestamp) + .start(); + + assertEquals(expectedTimestamp * 1000L, span.getStartTime()); + + long start = System.currentTimeMillis(); + span = (DDSpan) tracer.buildSpan("test", expectedName).withServiceName("foo").start(); + long stop = System.currentTimeMillis(); + + assertTrue(span.getStartTime() >= MILLISECONDS.toNanos(start - 1)); + assertTrue(span.getStartTime() <= MILLISECONDS.toNanos(stop + 1)); + } + + @Test + void shouldLinkToParentSpan() { + long spanId = 1L; + DDTraceId traceId = DDTraceId.ONE; + long expectedParentId = spanId; + + DDSpanContext mockedContext = mock(DDSpanContext.class); + when(mockedContext.getTraceId()).thenReturn(traceId); + when(mockedContext.getSpanId()).thenReturn(spanId); + when(mockedContext.getServiceName()).thenReturn("foo"); + when(mockedContext.getBaggageItems()).thenReturn(Collections.emptyMap()); + when(mockedContext.getTraceCollector()).thenReturn(tracer.createTraceCollector(DDTraceId.ONE)); + when(mockedContext.getPathwayContext()).thenReturn(NoopPathwayContext.INSTANCE); + + DDSpan span = + (DDSpan) + tracer + .buildSpan("test", "fakeName") + .withServiceName("foo") + .asChildOf(mockedContext) + .start(); + + DDSpanContext actualContext = span.context(); + assertEquals(expectedParentId, actualContext.getParentId()); + assertEquals(traceId, actualContext.getTraceId()); + } + + @TableTest({ + "scenario | noopParent | serviceName | expectTopLevel", + "same service, no noop | false | service | false ", + "noop parent, same service | true | service | true ", + "diff service, no noop | false | another service | true ", + "noop parent, diff service | true | another service | true " + }) + void shouldLinkToParentSpanImplicitly( + boolean noopParent, String serviceName, boolean expectTopLevel) { + try (AgentScope parent = + tracer.activateSpan( + noopParent + ? noopSpan() + : tracer.buildSpan("test", "parent").withServiceName("service").start())) { + long expectedParentId = noopParent ? DDSpanId.ZERO : parent.span().context().getSpanId(); + + DDSpan span = + (DDSpan) tracer.buildSpan("test", "fakeName").withServiceName(serviceName).start(); + + DDSpanContext actualContext = span.context(); + assertEquals(expectedParentId, actualContext.getParentId()); + assertEquals(expectTopLevel, span.isTopLevel()); + } + } + + @Test + void shouldInheritTheDDParentAttributes() { + String expectedName = "fakeName"; + String expectedParentResourceName = "fakeResourceName"; + String expectedParentType = "fakeType"; + String expectedParentServiceName = "fakeServiceName"; + String expectedChildServiceName = "fakeServiceName-child"; + String expectedChildResourceName = "fakeResourceName-child"; + String expectedChildType = "fakeType-child"; + String expectedBaggageItemKey = "fakeKey"; + String expectedBaggageItemValue = "fakeValue"; + + DDSpan parent = + (DDSpan) + tracer + .buildSpan("test", expectedName) + .withServiceName("foo") + .withResourceName(expectedParentResourceName) + .withSpanType(expectedParentType) + .start(); + + parent.setBaggageItem(expectedBaggageItemKey, expectedBaggageItemValue); + + // ServiceName and SpanType are always set by the parent if they are not present in the child + DDSpan span = + (DDSpan) + tracer + .buildSpan("test", expectedName) + .withServiceName(expectedParentServiceName) + .asChildOf(parent) + .start(); + + assertEquals(expectedName, span.getOperationName()); + assertEquals(expectedBaggageItemValue, span.getBaggageItem(expectedBaggageItemKey)); + assertEquals(expectedParentServiceName, span.context().getServiceName()); + assertEquals(expectedName, span.context().getResourceName()); + assertNull(span.context().getSpanType()); + assertTrue(span.isTopLevel()); // service names differ between parent and child + + // ServiceName and SpanType are always overwritten by the child if they are present + span = + (DDSpan) + tracer + .buildSpan("test", expectedName) + .withServiceName(expectedChildServiceName) + .withResourceName(expectedChildResourceName) + .withSpanType(expectedChildType) + .asChildOf(parent) + .start(); + + assertEquals(expectedName, span.getOperationName()); + assertEquals(expectedBaggageItemValue, span.getBaggageItem(expectedBaggageItemKey)); + assertEquals(expectedChildServiceName, span.context().getServiceName()); + assertEquals(expectedChildResourceName, span.context().getResourceName()); + assertEquals(expectedChildType, span.context().getSpanType()); + } + + @Test + void shouldTrackAllSpansInTrace() { + int nbSamples = 10; + List spans = new ArrayList<>(); + + DDSpan root = (DDSpan) tracer.buildSpan("test", "fake_O").withServiceName("foo").start(); + DDSpan lastSpan = root; + + for (int i = 1; i <= 10; i++) { + lastSpan = + (DDSpan) + tracer + .buildSpan("test", "fake_" + i) + .withServiceName("foo") + .asChildOf(lastSpan) + .start(); + spans.add(lastSpan); + lastSpan.finish(); + } + + PendingTrace traceCollector = (PendingTrace) root.context().getTraceCollector(); + assertEquals(root, traceCollector.getRootSpan()); + assertEquals(nbSamples, traceCollector.size()); + assertTrue(traceCollector.getSpans().containsAll(spans)); + DDSpan randomSpan = spans.get((int) (Math.random() * nbSamples)); + assertTrue( + ((PendingTrace) randomSpan.context().getTraceCollector()).getSpans().containsAll(spans)); + } + + static Stream extractedContextShouldPopulateNewSpanDetailsArguments() { + return Stream.of( + new ExtractedContext( + DDTraceId.ONE, + 2, + PrioritySampling.SAMPLER_DROP, + null, + 0, + Collections.emptyMap(), + Collections.emptyMap(), + null, + PropagationTags.factory() + .fromHeaderValue( + PropagationTags.HeaderType.DATADOG, "_dd.p.dm=934086a686-4,_dd.p.anytag=value"), + null, + DATADOG), + new ExtractedContext( + DDTraceId.from(3), + 4, + PrioritySampling.SAMPLER_KEEP, + "some-origin", + 0, + Collections.singletonMap("asdf", "qwer"), + buildTagsMap(ORIGIN_KEY, "some-origin", "zxcv", "1234"), + null, + PropagationTags.factory().empty(), + null, + DATADOG)); + } + + @ParameterizedTest + @MethodSource("extractedContextShouldPopulateNewSpanDetailsArguments") + void extractedContextShouldPopulateNewSpanDetails(ExtractedContext extractedContext) { + Thread thread = Thread.currentThread(); + DDSpan span = (DDSpan) tracer.buildSpan("test", "op name").asChildOf(extractedContext).start(); + + assertEquals(extractedContext.getTraceId(), span.getTraceId()); + assertEquals(extractedContext.getSpanId(), span.getParentId()); + assertEquals(extractedContext.getSamplingPriority(), (int) span.getSamplingPriority()); + assertEquals(extractedContext.getOrigin(), span.context().getOrigin()); + assertEquals(extractedContext.getBaggage(), span.context().getBaggageItems()); + assertEquals(thread.getId(), span.getTag(THREAD_ID)); + assertEquals(thread.getName(), span.getTag(THREAD_NAME)); + assertEquals( + extractedContext.getPropagationTags().headerValue(PropagationTags.HeaderType.DATADOG), + span.context().getPropagationTags().headerValue(PropagationTags.HeaderType.DATADOG)); + } + + @Test + @WithConfig(key = "trace.propagation.behavior.extract", value = "restart") + void buildContextFromExtractedContextWithRestartBehavior() { + ExtractedContext extractedContext = + new ExtractedContext( + DDTraceId.ONE, + 2, + PrioritySampling.SAMPLER_DROP, + null, + 0, + Collections.emptyMap(), + Collections.emptyMap(), + null, + PropagationTags.factory() + .fromHeaderValue( + PropagationTags.HeaderType.DATADOG, "_dd.p.dm=934086a686-4,_dd.p.anytag=value"), + null, + DATADOG); + DDSpan span = (DDSpan) tracer.buildSpan("test", "op name").asChildOf(extractedContext).start(); + + assertNotEquals(extractedContext.getTraceId(), span.getTraceId()); + assertNotEquals(extractedContext.getSpanId(), span.getParentId()); + assertEquals(PrioritySampling.UNSET, span.samplingPriority()); + + List spanLinks = span.getLinks(); + assertEquals(1, spanLinks.size()); + AgentSpanLink link = spanLinks.get(0); + assertEquals(extractedContext.getTraceId(), link.traceId()); + assertEquals(extractedContext.getSpanId(), link.spanId()); + assertEquals( + extractedContext.getPropagationTags().headerValue(PropagationTags.HeaderType.W3C), + link.traceState()); + } + + @Test + @WithConfig(key = "trace.propagation.behavior.extract", value = "ignore") + void buildContextFromExtractedContextWithIgnoreBehavior() { + ExtractedContext extractedContext = + new ExtractedContext( + DDTraceId.ONE, + 2, + PrioritySampling.SAMPLER_DROP, + null, + 0, + Collections.emptyMap(), + Collections.emptyMap(), + null, + PropagationTags.factory() + .fromHeaderValue( + PropagationTags.HeaderType.DATADOG, "_dd.p.dm=934086a686-4,_dd.p.anytag=value"), + null, + DATADOG); + DDSpan span = (DDSpan) tracer.buildSpan("test", "op name").asChildOf(extractedContext).start(); + + assertNotEquals(extractedContext.getTraceId(), span.getTraceId()); + assertNotEquals(extractedContext.getSpanId(), span.getParentId()); + assertEquals(PrioritySampling.UNSET, span.samplingPriority()); + assertTrue(span.getLinks().isEmpty()); + } + + @TableTest({ + "scenario | origin | tagMap ", + "empty tag map | | [:] ", + "some origin | some-origin | [asdf: qwer]" + }) + void tagContextShouldPopulateDefaultSpanDetails( + String scenario, String origin, Map tagMap) { + Thread thread = Thread.currentThread(); + TagContext tagContext = new TagContext(origin, TagMap.fromMap(tagMap)); + DDSpan span = (DDSpan) tracer.buildSpan("test", "op name").asChildOf(tagContext).start(); + + assertNotEquals(DDTraceId.ZERO, span.getTraceId()); + assertEquals(DDSpanId.ZERO, span.getParentId()); + assertNull(span.getSamplingPriority()); + assertEquals(tagContext.getOrigin(), span.context().getOrigin()); + assertEquals(Collections.emptyMap(), span.context().getBaggageItems()); + + Map expectedTags = new HashMap<>(); + if (tagContext.getTags() != null) { + expectedTags.putAll(tagContext.getTags()); + } + expectedTags.put(RUNTIME_ID_TAG, Config.get().getRuntimeId()); + expectedTags.put(LANGUAGE_TAG_KEY, LANGUAGE_TAG_VALUE); + expectedTags.put(THREAD_NAME, thread.getName()); + expectedTags.put(THREAD_ID, thread.getId()); + expectedTags.put(PID_TAG, Config.get().getProcessId()); + expectedTags.put(SCHEMA_VERSION_TAG_KEY, SpanNaming.instance().version()); + expectedTags.putAll(productTags()); + assertEquals(expectedTags, span.context().getTags()); + } + + static Stream globalSpanTagsPopulatedOnEachSpanArguments() { + return Stream.of( + arguments("", Collections.emptyMap()), + arguments("is:val:id", Collections.singletonMap("is", "val:id")), + arguments("a:x", Collections.singletonMap("a", "x")), + arguments("a:a,a:b,a:c", Collections.singletonMap("a", "c")), + arguments("a:1,b-c:d", buildStringMap("a", "1", "b-c", "d"))); + } + + @TableTest({ + "scenario | tagString | tags ", + "empty | '' | [:] ", + "column | is:val:id | [is: 'val:id']", + "single | a:x | [a: x] ", + "same multi-values | a:a,a:b,a:c | [a: c] ", + "multi values | a:1,b-c:d | [a: 1, b-c: d]" + }) + void globalSpanTagsPopulatedOnEachSpan(String tagString, Map tags) { + injectSysConfig("dd.trace.span.tags", tagString); + CoreTracer customTracer = tracerBuilder().writer(writer).build(); + DDSpan span = (DDSpan) customTracer.buildSpan("test", "op name").withServiceName("foo").start(); + + Map expectedTags = new HashMap<>(tags); + expectedTags.put(THREAD_NAME, Thread.currentThread().getName()); + expectedTags.put(THREAD_ID, Thread.currentThread().getId()); + expectedTags.put(RUNTIME_ID_TAG, Config.get().getRuntimeId()); + expectedTags.put(LANGUAGE_TAG_KEY, LANGUAGE_TAG_VALUE); + expectedTags.put(PID_TAG, Config.get().getProcessId()); + expectedTags.put(SCHEMA_VERSION_TAG_KEY, SpanNaming.instance().version()); + expectedTags.putAll(productTags()); + assertEquals(expectedTags, span.getTags()); + } + + @Test + void canOverwriteRequestContextDataWithBuilderFromEmpty() { + AgentSpan span1 = tracer.startSpan("test", "span1"); + + assertNull(span1.getRequestContext().getData(RequestContextSlot.APPSEC)); + assertNull(span1.getRequestContext().getData(RequestContextSlot.CI_VISIBILITY)); + assertNull(span1.getRequestContext().getData(RequestContextSlot.IAST)); + + AgentSpan span2 = + tracer + .buildSpan("test", "span2") + .asChildOf(span1.context()) + .withRequestContextData(RequestContextSlot.APPSEC, "override") + .withRequestContextData(RequestContextSlot.CI_VISIBILITY, "override") + .withRequestContextData(RequestContextSlot.IAST, "override") + .start(); + + assertEquals("override", span2.getRequestContext().getData(RequestContextSlot.APPSEC)); + assertEquals("override", span2.getRequestContext().getData(RequestContextSlot.CI_VISIBILITY)); + assertEquals("override", span2.getRequestContext().getData(RequestContextSlot.IAST)); + + span2.finish(); + span1.finish(); + } + + @Test + void canOverwriteRequestContextDataWithBuilder() { + TagContext context = + new TagContext() + .withCiVisibilityContextData("value") + .withRequestContextDataIast("value") + .withRequestContextDataAppSec("value"); + AgentSpan span1 = tracer.buildSpan("test", "span1").asChildOf(context).start(); + + AgentSpan span2 = tracer.buildSpan("test", "span2").asChildOf(span1.context()).start(); + + assertEquals("value", span2.getRequestContext().getData(RequestContextSlot.APPSEC)); + assertEquals("value", span2.getRequestContext().getData(RequestContextSlot.CI_VISIBILITY)); + assertEquals("value", span2.getRequestContext().getData(RequestContextSlot.IAST)); + + AgentSpan span3 = + tracer + .buildSpan("test", "span3") + .asChildOf(span2.context()) + .withRequestContextData(RequestContextSlot.APPSEC, "override") + .withRequestContextData(RequestContextSlot.CI_VISIBILITY, "override") + .withRequestContextData(RequestContextSlot.IAST, "override") + .start(); + + assertEquals("override", span3.getRequestContext().getData(RequestContextSlot.APPSEC)); + assertEquals("override", span3.getRequestContext().getData(RequestContextSlot.CI_VISIBILITY)); + assertEquals("override", span3.getRequestContext().getData(RequestContextSlot.IAST)); + + span3.finish(); + span2.finish(); + span1.finish(); + } + + private Map productTags() { + Map productTags = new HashMap<>(); + productTags.put(PROFILING_ENABLED, Config.get().isProfilingEnabled() ? 1 : 0); + if (Config.get().isDataStreamsEnabled()) { + productTags.put(DSM_ENABLED, 1); + } + if (Config.get().isDataJobsEnabled()) { + productTags.put(DJM_ENABLED, 1); + } + return productTags; + } + + private static Map buildTagsMap(String... keyValues) { + Map map = new HashMap<>(); + for (int i = 0; i < keyValues.length; i += 2) { + map.put(keyValues[i], keyValues[i + 1]); + } + return map; + } + + private static Map buildStringMap(String... keyValues) { + Map map = new HashMap<>(); + for (int i = 0; i < keyValues.length; i += 2) { + map.put(keyValues[i], keyValues[i + 1]); + } + return map; + } +} diff --git a/dd-trace-core/src/test/java/datadog/trace/core/CoreTracerTest.java b/dd-trace-core/src/test/java/datadog/trace/core/CoreTracerTest.java new file mode 100644 index 00000000000..451fab2e418 --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/CoreTracerTest.java @@ -0,0 +1,643 @@ +package datadog.trace.core; + +import static datadog.trace.junit.utils.config.WithConfigExtension.injectSysConfig; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import datadog.communication.ddagent.DDAgentFeaturesDiscovery; +import datadog.communication.ddagent.SharedCommunicationObjects; +import datadog.environment.JavaVirtualMachine; +import datadog.metrics.api.Monitoring; +import datadog.remoteconfig.ConfigurationPoller; +import datadog.remoteconfig.Product; +import datadog.remoteconfig.state.ParsedConfigKey; +import datadog.remoteconfig.state.ProductListener; +import datadog.trace.api.Config; +import datadog.trace.api.DDTags; +import datadog.trace.api.config.GeneralConfig; +import datadog.trace.api.config.TracerConfig; +import datadog.trace.api.remoteconfig.ServiceNameCollector; +import datadog.trace.api.sampling.PrioritySampling; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.ServiceNameSources; +import datadog.trace.common.sampling.AllSampler; +import datadog.trace.common.sampling.RateByServiceTraceSampler; +import datadog.trace.common.writer.DDAgentWriter; +import datadog.trace.common.writer.ListWriter; +import datadog.trace.common.writer.LoggingWriter; +import datadog.trace.core.CoreTracer.ConfigSnapshot; +import datadog.trace.core.tagprocessor.TagsPostProcessorFactory; +import datadog.trace.junit.utils.config.WithConfig; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.tabletest.junit.TableTest; + +@Timeout(value = 10, unit = TimeUnit.SECONDS) +public class CoreTracerTest extends DDCoreJavaSpecification { + + @BeforeAll + static void checkJvm() { + assumeFalse( + JavaVirtualMachine.isOracleJDK8(), + "Oracle JDK 1.8 did not merge the fix in JDK-8058322, leading to the JVM failing to" + + " correctly extract method parameters without args, when the code is compiled on a" + + " later JDK (targeting 8). This can manifest when creating mocks."); + } + + @Test + void verifyDefaultsOnTracer() { + CoreTracer tracer = CoreTracer.builder().build(); + try { + assertFalse(tracer.serviceName.isEmpty()); + assertInstanceOf(RateByServiceTraceSampler.class, tracer.initialSampler); + assertInstanceOf(DDAgentWriter.class, tracer.writer); + } finally { + tracer.close(); + } + } + + @Test + @WithConfig(key = TracerConfig.PRIORITY_SAMPLING, value = "false") + void verifyOverridingSampler() { + CoreTracer tracer = tracerBuilder().build(); + try { + assertInstanceOf(AllSampler.class, tracer.initialSampler); + } finally { + tracer.close(); + } + } + + @Test + @WithConfig(key = TracerConfig.WRITER_TYPE, value = "LoggingWriter") + void verifyOverridingWriter() { + CoreTracer tracer = tracerBuilder().build(); + try { + assertInstanceOf(LoggingWriter.class, tracer.writer); + } finally { + tracer.close(); + } + } + + @Test + @WithConfig(key = TracerConfig.AGENT_UNIX_DOMAIN_SOCKET, value = "asdf") + void verifyUdsWindows() { + String originalOsName = System.getProperty("os.name"); + try { + System.setProperty("os.name", "Windows ME"); + assertEquals("asdf", Config.get().getAgentUnixDomainSocket()); + } finally { + if (originalOsName != null) { + System.setProperty("os.name", originalOsName); + } else { + System.clearProperty("os.name"); + } + } + } + + @TableTest({ + "scenario | mapString | map ", + "duplicate keys | a:one, a:two, a:three | [a: three] ", + "empty value | a:b,c:d,e: | [a: b, c: d]" + }) + void verifyMappingConfigsOnTracer(String scenario, String mapString, Map map) { + injectSysConfig(TracerConfig.SERVICE_MAPPING, mapString); + injectSysConfig(TracerConfig.SPAN_TAGS, mapString); + injectSysConfig(TracerConfig.HEADER_TAGS, mapString); + CoreTracer tracer = tracerBuilder().build(); + try { + ConfigSnapshot config = tracer.captureTraceConfig(); + assertEquals(map, config.mergedTracerTags); + assertEquals(map, config.getServiceMapping()); + } finally { + tracer.close(); + } + } + + @TableTest({ + "scenario | mapString | map ", + "duplicate keys | a:one, a:two, a:three | [a: three] ", + "empty value | a:b,c:d,e: | [a: b, c: d]" + }) + void verifyBaggageMappingConfigsOnTracer( + String scenario, String mapString, Map map) { + injectSysConfig(TracerConfig.BAGGAGE_MAPPING, mapString); + CoreTracer tracer = tracerBuilder().build(); + try { + assertEquals(map, tracer.captureTraceConfig().getBaggageMapping()); + } finally { + tracer.close(); + } + } + + @Test + @WithConfig(key = "agent.host", value = "somethingelse") + void verifyOverridingHost() { + assertEquals("somethingelse", Config.get().getAgentHost()); + } + + @TableTest({ + "scenario | key | value", + "agent port | agent.port | 777 ", + "trace port | trace.agent.port | 9999 " + }) + void verifyOverridingPort(String key, String value) { + injectSysConfig(key, value); + assertEquals(Integer.valueOf(value), Config.get().getAgentPort()); + } + + @Test + @WithConfig(key = "writer.type", value = "LoggingWriter") + void writerIsLoggingWriterWhenPropertySet() { + CoreTracer tracer = tracerBuilder().build(); + try { + assertInstanceOf(LoggingWriter.class, tracer.writer); + } finally { + tracer.close(); + } + } + + @TableTest({ + "scenario | key | value", + "priority true | priority.sampling | true ", + "priority false | priority.sampling | false" + }) + void sharesTraceCountWithDDApiWithKeyValue(String key, String value) { + injectSysConfig(key, value); + CoreTracer tracer = tracerBuilder().build(); + try { + assertInstanceOf(DDAgentWriter.class, tracer.writer); + } finally { + tracer.close(); + } + } + + @Test + void rootTagsAppliedOnlyToRootSpans() { + Map localRootSpanTags = new LinkedHashMap<>(); + localRootSpanTags.put("only_root", "value"); + CoreTracer tracer = tracerBuilder().localRootSpanTags(localRootSpanTags).build(); + AgentSpan root = tracer.buildSpan("my_root").start(); + AgentSpan child = tracer.buildSpan("my_child").asChildOf(root.context()).start(); + try { + assertTrue(root.getTags().containsKey("only_root")); + assertFalse(child.getTags().containsKey("only_root")); + } finally { + child.finish(); + root.finish(); + tracer.close(); + } + } + + @Test + void prioritySamplingWhenSpanFinishes() throws Exception { + ListWriter writer = new ListWriter(); + CoreTracer tracer = tracerBuilder().writer(writer).build(); + try { + DDSpan span = (DDSpan) tracer.buildSpan("operation").start(); + span.finish(); + writer.waitForTraces(1); + assertEquals(PrioritySampling.SAMPLER_KEEP, (int) span.getSamplingPriority()); + } finally { + tracer.close(); + } + } + + @Test + void prioritySamplingSetWhenChildSpanComplete() throws Exception { + ListWriter writer = new ListWriter(); + CoreTracer tracer = tracerBuilder().writer(writer).build(); + try { + DDSpan root = (DDSpan) tracer.buildSpan("operation").start(); + DDSpan child = (DDSpan) tracer.buildSpan("my_child").asChildOf(root.context()).start(); + root.finish(); + + assertNull(root.getSamplingPriority()); + + child.finish(); + writer.waitForTraces(1); + + assertEquals(PrioritySampling.SAMPLER_KEEP, (int) root.getSamplingPriority()); + assertEquals(root.getSamplingPriority(), child.getSamplingPriority()); + } finally { + tracer.close(); + } + } + + @Test + void verifyConfigurationPolling() throws Exception { + ParsedConfigKey key = ParsedConfigKey.parse("datadog/2/APM_TRACING/config_overrides/config"); + ConfigurationPoller poller = mock(ConfigurationPoller.class); + SharedCommunicationObjects sco = createScoWithPoller(poller); + + ProductListener[] capturedUpdater = {null}; + doAnswer( + inv -> { + capturedUpdater[0] = inv.getArgument(1, ProductListener.class); + return null; + }) + .when(poller) + .addListener(eq(Product.APM_TRACING), any(ProductListener.class)); + + CoreTracer tracer = + CoreTracer.builder().sharedCommunicationObjects(sco).pollForTracingConfiguration().build(); + unclosedTracers.add(tracer); + + try { + verify(poller).addListener(eq(Product.APM_TRACING), any(ProductListener.class)); + assertNotNull(capturedUpdater[0]); + assertEquals(Collections.emptyMap(), tracer.captureTraceConfig().getServiceMapping()); + assertEquals(Collections.emptyMap(), tracer.captureTraceConfig().getRequestHeaderTags()); + assertEquals(Collections.emptyMap(), tracer.captureTraceConfig().getResponseHeaderTags()); + assertNull(tracer.captureTraceConfig().getTraceSampleRate()); + + String json = + "{\n" + + " \"lib_config\":\n" + + " {\n" + + " \"tracing_service_mapping\":\n" + + " [{\n" + + " \"from_key\": \"foobar\",\n" + + " \"to_name\": \"bar\"\n" + + " }, {\n" + + " \"from_key\": \"snafu\",\n" + + " \"to_name\": \"foo\"\n" + + " }]\n" + + " ,\n" + + " \"tracing_header_tags\":\n" + + " [{\n" + + " \"header\": \"Cookie\",\n" + + " \"tag_name\": \"\"\n" + + " }, {\n" + + " \"header\": \"Referer\",\n" + + " \"tag_name\": \"http.referer\"\n" + + " }, {\n" + + " \"header\": \" Some.Header \",\n" + + " \"tag_name\": \"\"\n" + + " }, {\n" + + " \"header\": \"C!!!ont_____ent----tYp!/!e\",\n" + + " \"tag_name\": \"\"\n" + + " }, {\n" + + " \"header\": \"this.header\",\n" + + " \"tag_name\": \"whatever.the.user.wants.this.header\"\n" + + " }]\n" + + " ,\n" + + " \"tracing_sampling_rate\": 0.5\n" + + " }\n" + + "}"; + + capturedUpdater[0].accept(key, json.getBytes(StandardCharsets.UTF_8), null); + capturedUpdater[0].commit(null); + + Map expectedServiceMapping = buildStringMap("foobar", "bar", "snafu", "foo"); + assertEquals(expectedServiceMapping, tracer.captureTraceConfig().getServiceMapping()); + + Map expectedRequestHeaderTags = + buildStringMap( + "cookie", "http.request.headers.cookie", + "referer", "http.referer", + "some.header", "http.request.headers.some_header", + "c!!!ont_____ent----typ!/!e", "http.request.headers.c___ont_____ent----typ_/_e", + "this.header", "whatever.the.user.wants.this.header"); + assertEquals(expectedRequestHeaderTags, tracer.captureTraceConfig().getRequestHeaderTags()); + + Map expectedResponseHeaderTags = + buildStringMap( + "cookie", "http.response.headers.cookie", + "referer", "http.referer", + "some.header", "http.response.headers.some_header", + "c!!!ont_____ent----typ!/!e", "http.response.headers.c___ont_____ent----typ_/_e", + "this.header", "whatever.the.user.wants.this.header"); + assertEquals(expectedResponseHeaderTags, tracer.captureTraceConfig().getResponseHeaderTags()); + + assertEquals(0.5, tracer.captureTraceConfig().getTraceSampleRate(), 0.0001); + + capturedUpdater[0].remove(key, null); + capturedUpdater[0].commit(null); + + assertEquals(Collections.emptyMap(), tracer.captureTraceConfig().getServiceMapping()); + assertEquals(Collections.emptyMap(), tracer.captureTraceConfig().getRequestHeaderTags()); + assertEquals(Collections.emptyMap(), tracer.captureTraceConfig().getResponseHeaderTags()); + assertNull(tracer.captureTraceConfig().getTraceSampleRate()); + } finally { + tracer.close(); + } + } + + @TableTest({ + "scenario | json | expectedValue ", + "a:b c:d e:f | '{\"lib_config\":{\"tracing_tags\": [\"a:b\", \"c:d\", \"e:f\"]}}' | [a: b, c: d, e: f]", + "empty and c:d | '{\"lib_config\":{\"tracing_tags\": [\"\", \"c:d\", \"\"]}}' | [c: d] ", + ":b c: e:f | '{\"lib_config\":{\"tracing_tags\": [\":b\", \"c:\", \"e:f\"]}}' | [e: f] ", + ": c: e:f | '{\"lib_config\":{\"tracing_tags\": [\":\", \"c:\", \"e:f\"]}}' | [e: f] ", + ": c: empty | '{\"lib_config\":{\"tracing_tags\": [\":\", \"c:\", \"\"]}}' | [:] ", + "empty array | '{\"lib_config\":{\"tracing_tags\": []}}' | [:] " + }) + void verifyConfigurationPollingWithCustomTags( + String scenario, String json, Map expectedValue) throws Exception { + ParsedConfigKey key = ParsedConfigKey.parse("datadog/2/APM_TRACING/config_overrides/config"); + ConfigurationPoller poller = mock(ConfigurationPoller.class); + SharedCommunicationObjects sco = createScoWithPoller(poller); + + ProductListener[] capturedUpdater = {null}; + doAnswer( + inv -> { + capturedUpdater[0] = inv.getArgument(1, ProductListener.class); + return null; + }) + .when(poller) + .addListener(eq(Product.APM_TRACING), any(ProductListener.class)); + + CoreTracer tracer = + CoreTracer.builder().sharedCommunicationObjects(sco).pollForTracingConfiguration().build(); + unclosedTracers.add(tracer); + + try { + verify(poller).addListener(eq(Product.APM_TRACING), any(ProductListener.class)); + assertNotNull(capturedUpdater[0]); + assertEquals(Collections.emptyMap(), tracer.captureTraceConfig().getTracingTags()); + + capturedUpdater[0].accept(key, json.getBytes(StandardCharsets.UTF_8), null); + capturedUpdater[0].commit(null); + + ConfigSnapshot config = tracer.captureTraceConfig(); + assertEquals(expectedValue, config.getTracingTags()); + assertEquals(expectedValue, config.mergedTracerTags); + + capturedUpdater[0].remove(key, null); + capturedUpdater[0].commit(null); + + assertEquals(Collections.emptyMap(), tracer.captureTraceConfig().getTracingTags()); + } finally { + tracer.close(); + } + } + + @TableTest({ + "scenario | json | expectedValue ", + "tracing disabled | '{\"lib_config\":{\"tracing_enabled\": false}}' | false ", + "tracing enabled | '{\"lib_config\":{\"tracing_enabled\": true}}' | true ", + "action with tracing disabled | '{\"action\": \"enable\", \"lib_config\": ", + "{\"tracing_sampling_rate\": null, ", + " \"log_injection_enabled\": null, ", + "\"tracing_header_tags\": null, ", + " \"runtime_metrics_enabled\": null, ", + "\"tracing_debug\": null, ", + " \"tracing_service_mapping\": null, ", + "\"tracing_sampling_rules\": null, ", + " \"span_sampling_rules\": null, ", + "\"data_streams_enabled\": null, ", + " \"tracing_enabled\": false}}' | false " + }) + void verifyConfigurationPollingWithTracingEnabled( + String scenario, String json, boolean expectedValue) throws Exception { + ParsedConfigKey key = ParsedConfigKey.parse("datadog/2/APM_TRACING/config_overrides/config"); + ConfigurationPoller poller = mock(ConfigurationPoller.class); + SharedCommunicationObjects sco = createScoWithPoller(poller); + + ProductListener[] capturedUpdater = {null}; + doAnswer( + inv -> { + capturedUpdater[0] = inv.getArgument(1, ProductListener.class); + return null; + }) + .when(poller) + .addListener(eq(Product.APM_TRACING), any(ProductListener.class)); + + CoreTracer tracer = + CoreTracer.builder().sharedCommunicationObjects(sco).pollForTracingConfiguration().build(); + unclosedTracers.add(tracer); + + try { + verify(poller).addListener(eq(Product.APM_TRACING), any(ProductListener.class)); + assertNotNull(capturedUpdater[0]); + assertTrue(tracer.captureTraceConfig().isTraceEnabled()); + + capturedUpdater[0].accept(key, json.getBytes(StandardCharsets.UTF_8), null); + capturedUpdater[0].commit(null); + + assertEquals(expectedValue, tracer.captureTraceConfig().isTraceEnabled()); + } finally { + tracer.close(); + } + } + + @TableTest({ + "scenario | preferred | expected", + "no pref | | test ", + "with pref | some | some " + }) + void testLocalRootServiceNameOverride(String preferred, String expected) { + CoreTracer tracer = tracerBuilder().writer(new ListWriter()).serviceName("test").build(); + tracer.updatePreferredServiceName(preferred, preferred); + try { + DDSpan span = (DDSpan) tracer.startSpan("", "test"); + span.finish(); + assertEquals(expected, span.getServiceName()); + if (preferred != null) { + assertTrue(ServiceNameCollector.get().getServices().contains(preferred)); + } + } finally { + tracer.close(); + } + } + + @Test + @WithConfig(key = GeneralConfig.SERVICE_NAME, value = "dd_service_name") + @WithConfig(key = GeneralConfig.VERSION, value = "1.0.0") + void testDdVersionExistsOnlyIfServiceEqDdService() { + TagsPostProcessorFactory.withAddInternalTags(true); + CoreTracer tracer = tracerBuilder().writer(new ListWriter()).build(); + try { + DDSpan span = + (DDSpan) tracer.buildSpan("def").withTag(GeneralConfig.SERVICE_NAME, "foo").start(); + span.finish(); + assertEquals("foo", span.getServiceName()); + assertFalse(span.getTags().containsKey("version")); + + DDSpan span2 = (DDSpan) tracer.buildSpan("abc").start(); + span2.finish(); + assertEquals("dd_service_name", span2.getServiceName()); + assertEquals("1.0.0", String.valueOf(span2.getTags().get("version"))); + } finally { + tracer.close(); + } + } + + @Test + void flushesOnTracerCloseIfConfiguredToDoSo() { + WriterWithExplicitFlush writer = new WriterWithExplicitFlush(); + CoreTracer tracer = tracerBuilder().writer(writer).flushOnClose(true).build(); + tracer.buildSpan("my_span").start().finish(); + tracer.close(); + assertFalse(writer.flushedTraces.isEmpty()); + } + + @TableTest({ + "scenario | service | env | targetService | targetEnv", + "diff target service | service | env | service_1 | env ", + "diff target env | service | env | service | env_1 ", + "diff target service and env | service | env | service_2 | env_2 " + }) + void verifyNoFilteringOfServiceEnvWhenMismatchedWithDdServiceDdEnv( + String service, String env, String targetService, String targetEnv) throws Exception { + injectSysConfig(GeneralConfig.SERVICE_NAME, service); + injectSysConfig(GeneralConfig.ENV, env); + + ParsedConfigKey key = ParsedConfigKey.parse("datadog/2/APM_TRACING/config_overrides/config"); + ConfigurationPoller poller = mock(ConfigurationPoller.class); + SharedCommunicationObjects sco = createScoWithPoller(poller); + + ProductListener[] capturedUpdater = {null}; + doAnswer( + inv -> { + capturedUpdater[0] = inv.getArgument(1, ProductListener.class); + return null; + }) + .when(poller) + .addListener(eq(Product.APM_TRACING), any(ProductListener.class)); + + CoreTracer tracer = + CoreTracer.builder().sharedCommunicationObjects(sco).pollForTracingConfiguration().build(); + unclosedTracers.add(tracer); + + try { + verify(poller).addListener(eq(Product.APM_TRACING), any(ProductListener.class)); + assertNotNull(capturedUpdater[0]); + assertEquals(Collections.emptyMap(), tracer.captureTraceConfig().getServiceMapping()); + + String json = + String.format( + "{\"service_target\":{\"service\":\"%s\",\"env\":\"%s\"}," + + "\"lib_config\":{\"tracing_service_mapping\":" + + "[{\"from_key\":\"foobar\",\"to_name\":\"bar\"}]}}", + targetService, targetEnv); + + capturedUpdater[0].accept(key, json.getBytes(StandardCharsets.UTF_8), null); + capturedUpdater[0].commit(null); + + assertEquals( + buildStringMap("foobar", "bar"), tracer.captureTraceConfig().getServiceMapping()); + } finally { + tracer.close(); + } + } + + @Test + void serviceNameSourceIsRecordedWhenUsingTwoParameterSetServiceName() { + CoreTracer tracer = tracerBuilder().writer(new ListWriter()).build(); + try { + DDSpan span = (DDSpan) tracer.buildSpan("operation").start(); + span.setServiceName("custom-service", "my-integration"); + DDSpan child = (DDSpan) tracer.buildSpan("child").start(); + child.finish(); + span.finish(); + + assertEquals("custom-service", span.getServiceName()); + assertEquals("my-integration", span.getTag(DDTags.DD_SVC_SRC)); + } finally { + tracer.close(); + } + } + + @Test + void serviceNameSourceIsMarkedAsManualWhenUsingOneParameterSetServiceName() { + CoreTracer tracer = tracerBuilder().writer(new ListWriter()).build(); + try { + DDSpan span = (DDSpan) tracer.buildSpan("operation").start(); + span.setServiceName("custom-service", "my-integration"); + span.setServiceName("another"); + span.finish(); + + assertEquals("another", span.getServiceName()); + assertEquals(ServiceNameSources.MANUAL, span.getTag(DDTags.DD_SVC_SRC)); + } finally { + tracer.close(); + } + } + + @Test + void serviceNameSourceIsMissingWhenNotExplicitlySettingServiceName() { + CoreTracer tracer = tracerBuilder().writer(new ListWriter()).build(); + try { + DDSpan span = (DDSpan) tracer.buildSpan("operation").start(); + span.finish(); + + assertEquals(tracer.serviceName, span.getServiceName()); + assertNull(span.getTag(DDTags.DD_SVC_SRC)); + } finally { + tracer.close(); + } + } + + // --- helpers --- + + private SharedCommunicationObjects createScoWithPoller(ConfigurationPoller poller) + throws Exception { + SharedCommunicationObjects sco = new SharedCommunicationObjects(); + sco.agentHttpClient = mock(OkHttpClient.class); + sco.monitoring = mock(Monitoring.class); + sco.agentUrl = HttpUrl.get("https://example.com"); + sco.setFeaturesDiscovery(mock(DDAgentFeaturesDiscovery.class)); + Field pollerField = SharedCommunicationObjects.class.getDeclaredField("configurationPoller"); + pollerField.setAccessible(true); + pollerField.set(sco, poller); + return sco; + } + + private static Map buildStringMap(String... keyValues) { + Map map = new LinkedHashMap<>(); + for (int i = 0; i < keyValues.length; i += 2) { + map.put(keyValues[i], keyValues[i + 1]); + } + return map; + } + + // --- inner classes --- + + static class WriterWithExplicitFlush implements datadog.trace.common.writer.Writer { + final List> writtenTraces = new CopyOnWriteArrayList<>(); + final List> flushedTraces = new CopyOnWriteArrayList<>(); + + @Override + public void write(List trace) { + writtenTraces.add(trace); + } + + @Override + public void start() {} + + @Override + public boolean flush() { + flushedTraces.addAll(writtenTraces); + writtenTraces.clear(); + return true; + } + + @Override + public void close() {} + + @Override + public void incrementDropCounts(int spanCount) {} + } +} diff --git a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanContextPropagationTagsTest.java b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanContextPropagationTagsTest.java new file mode 100644 index 00000000000..ae2f424069f --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanContextPropagationTagsTest.java @@ -0,0 +1,277 @@ +package datadog.trace.core; + +import static datadog.trace.api.TracePropagationStyle.DATADOG; +import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_DROP; +import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_KEEP; +import static datadog.trace.api.sampling.PrioritySampling.UNSET; +import static datadog.trace.api.sampling.PrioritySampling.USER_DROP; +import static datadog.trace.api.sampling.PrioritySampling.USER_KEEP; +import static datadog.trace.api.sampling.SamplingMechanism.DEFAULT; +import static datadog.trace.api.sampling.SamplingMechanism.MANUAL; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import datadog.trace.api.DDTraceId; +import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; +import datadog.trace.common.writer.ListWriter; +import datadog.trace.core.propagation.ExtractedContext; +import datadog.trace.core.propagation.PropagationTags; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class DDSpanContextPropagationTagsTest extends DDCoreJavaSpecification { + + private ListWriter writer; + private CoreTracer tracer; + + @BeforeEach + void setup() { + writer = new ListWriter(); + tracer = tracerBuilder().writer(writer).build(); + } + + @ParameterizedTest() + @MethodSource("updateSpanPropagationTagsArguments") + void updateSpanPropagationTags( + String scenario, + int priority, + String header, + int newPriority, + int newMechanism, + String newHeader, + Map tagMap) { + PropagationTags propagationTags = + tracer + .getPropagationTagsFactory() + .fromHeaderValue(PropagationTags.HeaderType.DATADOG, header); + AgentSpanContext extracted = + new ExtractedContext(DDTraceId.from(123), 456, priority, "789", propagationTags, DATADOG) + .withRequestContextDataAppSec("dummy"); + DDSpan span = (DDSpan) tracer.buildSpan("top").asChildOf(extracted).start(); + PropagationTags dd = span.context().getPropagationTags(); + + span.setSamplingPriority(newPriority, newMechanism); + + assertEquals(newHeader, dd.headerValue(PropagationTags.HeaderType.DATADOG)); + assertEquals(tagMap, dd.createTagMap()); + } + + static Stream updateSpanPropagationTagsArguments() { + return Stream.of( + arguments( + "UNSET->USER_KEEP", + UNSET, + "_dd.p.usr=123", + USER_KEEP, + MANUAL, + "_dd.p.dm=-4,_dd.p.usr=123", + buildMap("_dd.p.dm", "-4", "_dd.p.usr", "123")), + arguments( + "UNSET->SAMPLER_DROP", + UNSET, + "_dd.p.usr=123", + SAMPLER_DROP, + DEFAULT, + "_dd.p.usr=123", + buildMap("_dd.p.usr", "123")), + // decision has already been made, propagate as-is + arguments( + "SAMPLER_KEEP->USER_KEEP with dm", + SAMPLER_KEEP, + "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123", + USER_KEEP, + MANUAL, + "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123", + buildMap("_dd.p.dm", "9bf3439f2f-1", "_dd.p.usr", "123")), + arguments( + "SAMPLER_KEEP->USER_DROP with dm", + SAMPLER_KEEP, + "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123", + USER_DROP, + MANUAL, + "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123", + buildMap("_dd.p.dm", "9bf3439f2f-1", "_dd.p.usr", "123")), + arguments( + "SAMPLER_KEEP->USER_KEEP no dm", + SAMPLER_KEEP, + "_dd.p.usr=123", + USER_KEEP, + MANUAL, + "_dd.p.usr=123", + buildMap("_dd.p.usr", "123"))); + } + + @ParameterizedTest + @MethodSource("updateTracePropagationTagsArguments") + void updateTracePropagationTags( + String scenario, + int priority, + String header, + int newPriority, + int newMechanism, + String rootHeader, + Map rootTagMap) { + PropagationTags propagationTags = + tracer + .getPropagationTagsFactory() + .fromHeaderValue(PropagationTags.HeaderType.DATADOG, header); + AgentSpanContext extracted = + new ExtractedContext(DDTraceId.from(123), 456, priority, "789", propagationTags, DATADOG) + .withRequestContextDataAppSec("dummy"); + DDSpan rootSpan = (DDSpan) tracer.buildSpan("top").asChildOf(extracted).start(); + PropagationTags ddRoot = rootSpan.context().getPropagationTags(); + DDSpan span = (DDSpan) tracer.buildSpan("current").asChildOf(rootSpan.context()).start(); + + span.setSamplingPriority(newPriority, newMechanism); + + assertEquals(rootHeader, ddRoot.headerValue(PropagationTags.HeaderType.DATADOG)); + assertEquals(rootTagMap, ddRoot.createTagMap()); + } + + static Stream updateTracePropagationTagsArguments() { + return Stream.of( + arguments( + "UNSET->USER_KEEP", + UNSET, + "_dd.p.usr=123", + USER_KEEP, + MANUAL, + "_dd.p.dm=-4,_dd.p.usr=123", + buildMap("_dd.p.dm", "-4", "_dd.p.usr", "123")), + arguments( + "UNSET->SAMPLER_DROP", + UNSET, + "_dd.p.usr=123", + SAMPLER_DROP, + DEFAULT, + "_dd.p.usr=123", + buildMap("_dd.p.usr", "123")), + // decision has already been made, propagate as-is + arguments( + "SAMPLER_KEEP->USER_KEEP with dm", + SAMPLER_KEEP, + "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123", + USER_KEEP, + MANUAL, + "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123", + buildMap("_dd.p.dm", "9bf3439f2f-1", "_dd.p.usr", "123")), + arguments( + "SAMPLER_KEEP->USER_DROP with dm", + SAMPLER_KEEP, + "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123", + USER_DROP, + MANUAL, + "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123", + buildMap("_dd.p.dm", "9bf3439f2f-1", "_dd.p.usr", "123")), + arguments( + "SAMPLER_KEEP->USER_KEEP no dm", + SAMPLER_KEEP, + "_dd.p.usr=123", + USER_KEEP, + MANUAL, + "_dd.p.usr=123", + buildMap("_dd.p.usr", "123"))); + } + + @ParameterizedTest + @MethodSource("forceKeepSpanPropagationTagsArguments") + void forceKeepSpanPropagationTags( + String scenario, int priority, String header, String newHeader, Map tagMap) { + PropagationTags propagationTags = + tracer + .getPropagationTagsFactory() + .fromHeaderValue(PropagationTags.HeaderType.DATADOG, header); + AgentSpanContext extracted = + new ExtractedContext(DDTraceId.from(123), 456, priority, "789", propagationTags, DATADOG) + .withRequestContextDataAppSec("dummy"); + DDSpan span = (DDSpan) tracer.buildSpan("top").asChildOf(extracted).start(); + PropagationTags dd = span.context().getPropagationTags(); + + span.context().forceKeep(); + + assertEquals(newHeader, dd.headerValue(PropagationTags.HeaderType.DATADOG)); + assertEquals(tagMap, dd.createTagMap()); + } + + static Stream forceKeepSpanPropagationTagsArguments() { + return Stream.of( + arguments( + "UNSET", + UNSET, + "_dd.p.usr=123", + "_dd.p.dm=-4,_dd.p.usr=123", + buildMap("_dd.p.dm", "-4", "_dd.p.usr", "123")), + arguments( + "SAMPLER_KEEP with dm", + SAMPLER_KEEP, + "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123", + "_dd.p.dm=-4,_dd.p.usr=123", + buildMap("_dd.p.dm", "-4", "_dd.p.usr", "123")), + arguments( + "SAMPLER_KEEP no dm", + SAMPLER_KEEP, + "_dd.p.usr=123", + "_dd.p.dm=-4,_dd.p.usr=123", + buildMap("_dd.p.dm", "-4", "_dd.p.usr", "123"))); + } + + @ParameterizedTest + @MethodSource("forceKeepTracePropagationTagsArguments") + void forceKeepTracePropagationTags( + String scenario, + int priority, + String header, + String rootHeader, + Map rootTagMap) { + PropagationTags propagationTags = + tracer + .getPropagationTagsFactory() + .fromHeaderValue(PropagationTags.HeaderType.DATADOG, header); + AgentSpanContext extracted = + new ExtractedContext(DDTraceId.from(123), 456, priority, "789", propagationTags, DATADOG) + .withRequestContextDataAppSec("dummy"); + DDSpan rootSpan = (DDSpan) tracer.buildSpan("top").asChildOf(extracted).start(); + PropagationTags ddRoot = rootSpan.context().getPropagationTags(); + DDSpan span = (DDSpan) tracer.buildSpan("current").asChildOf(rootSpan.context()).start(); + + span.context().forceKeep(); + + assertEquals(rootHeader, ddRoot.headerValue(PropagationTags.HeaderType.DATADOG)); + assertEquals(rootTagMap, ddRoot.createTagMap()); + } + + static Stream forceKeepTracePropagationTagsArguments() { + return Stream.of( + arguments( + "UNSET", + UNSET, + "_dd.p.usr=123", + "_dd.p.dm=-4,_dd.p.usr=123", + buildMap("_dd.p.dm", "-4", "_dd.p.usr", "123")), + arguments( + "SAMPLER_KEEP with dm", + SAMPLER_KEEP, + "_dd.p.dm=9bf3439f2f-1,_dd.p.usr=123", + "_dd.p.dm=-4,_dd.p.usr=123", + buildMap("_dd.p.dm", "-4", "_dd.p.usr", "123")), + arguments( + "SAMPLER_KEEP no dm", + SAMPLER_KEEP, + "_dd.p.usr=123", + "_dd.p.dm=-4,_dd.p.usr=123", + buildMap("_dd.p.dm", "-4", "_dd.p.usr", "123"))); + } + + private static Map buildMap(String... keyValues) { + Map map = new HashMap<>(); + for (int i = 0; i < keyValues.length; i += 2) { + map.put(keyValues[i], keyValues[i + 1]); + } + return map; + } +} diff --git a/internal-api/src/test/groovy/datadog/trace/api/naming/ExtraServiceProviderNamingV0ForkedTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/naming/ExtraServiceProviderNamingV0ForkedTest.groovy index fe203df0b2e..1a936615591 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/naming/ExtraServiceProviderNamingV0ForkedTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/naming/ExtraServiceProviderNamingV0ForkedTest.groovy @@ -7,6 +7,7 @@ import datadog.trace.api.remoteconfig.ServiceNameCollector class ExtraServiceProviderNamingV0ForkedTest extends DDSpecification { void "Naming schema calls ExtraServicesProvider if provides a service name"() { + def origServiceNameCollector = ServiceNameCollector.INSTANCE setup: final extraServiceProvider = Mock(ServiceNameCollector) ServiceNameCollector.INSTANCE = extraServiceProvider @@ -67,5 +68,8 @@ class ExtraServiceProviderNamingV0ForkedTest extends DDSpecification { then: 1 * extraServiceProvider.addService("anything") + + cleanup: + ServiceNameCollector.INSTANCE = origServiceNameCollector } }