diff --git a/brave-bom/pom.xml b/brave-bom/pom.xml index 1414d05942..225fbfd4a4 100644 --- a/brave-bom/pom.xml +++ b/brave-bom/pom.xml @@ -32,6 +32,10 @@ UTF-8 ${project.basedir}/.. + + + 2.27.0 + 3.0.0 @@ -81,6 +85,18 @@ + + io.zipkin.reporter2 + zipkin-reporter-bom + ${zipkin-reporter.version} + pom + import + + + io.zipkin.zipkin2 + zipkin + ${zipkin.version} + ${project.groupId} brave @@ -111,6 +127,11 @@ brave-context-slf4j ${project.version} + + ${project.groupId} + brave-context-rxjava2 + ${project.version} + ${project.groupId} brave-instrumentation-benchmarks @@ -121,6 +142,11 @@ brave-instrumentation-dubbo ${project.version} + + ${project.groupId} + brave-instrumentation-dubbo-rpc + ${project.version} + ${project.groupId} brave-instrumentation-grpc @@ -226,6 +252,11 @@ brave-instrumentation-okhttp3 ${project.version} + + ${project.groupId} + brave-instrumentation-p6spy + ${project.version} + ${project.groupId} brave-instrumentation-rpc @@ -241,6 +272,11 @@ brave-instrumentation-servlet-jakarta ${project.version} + + ${project.groupId} + brave-instrumentation-sparkjava + ${project.version} + ${project.groupId} brave-instrumentation-spring-rabbit diff --git a/brave-tests/src/main/java/brave/test/propagation/CurrentTraceContextTest.java b/brave-tests/src/main/java/brave/test/propagation/CurrentTraceContextTest.java index f2fddbb01c..05641e61d1 100644 --- a/brave-tests/src/main/java/brave/test/propagation/CurrentTraceContextTest.java +++ b/brave-tests/src/main/java/brave/test/propagation/CurrentTraceContextTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -311,7 +311,7 @@ protected void is_inheritable(CurrentTraceContext inheritableCurrentTraceContext } @Test void restoresSpanAfterCallable() throws Exception { - try (Scope scope = currentTraceContext.newScope(context)) { + try (Scope scope0 = currentTraceContext.newScope(context)) { attachesSpanInCallable(); assertThat(currentTraceContext.get()) .isEqualTo(context); @@ -341,7 +341,7 @@ protected void is_inheritable(CurrentTraceContext inheritableCurrentTraceContext @Test void restoresSpanAfterRunnable() throws Exception { TraceContext context0 = TraceContext.newBuilder().traceId(3L).spanId(3L).build(); - try (Scope scope = currentTraceContext.newScope(context0)) { + try (Scope scope0 = currentTraceContext.newScope(context0)) { attachesSpanInRunnable(); assertThat(currentTraceContext.get()) .isEqualTo(context0); @@ -366,7 +366,7 @@ static class Unused extends ClassLoaders.ConsumerRunnable { @Override public void accept(CurrentTraceContext.Builder builder) { CurrentTraceContext current = builder.build(); - try (Scope scope = current.newScope(TraceContext.newBuilder().traceId(1L).spanId(2L).build())) { + try (Scope ws = current.newScope(TraceContext.newBuilder().traceId(1L).spanId(2L).build())) { } } } diff --git a/brave-tests/src/main/java/brave/test/propagation/PropagationSetterTest.java b/brave-tests/src/main/java/brave/test/propagation/PropagationSetterTest.java index 7a35ce5ffa..cdafadb5e4 100644 --- a/brave-tests/src/main/java/brave/test/propagation/PropagationSetterTest.java +++ b/brave-tests/src/main/java/brave/test/propagation/PropagationSetterTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/brave-tests/src/main/java/brave/test/propagation/PropagationTest.java b/brave-tests/src/main/java/brave/test/propagation/PropagationTest.java index 9e8e0e00b4..9d83463c2a 100644 --- a/brave-tests/src/main/java/brave/test/propagation/PropagationTest.java +++ b/brave-tests/src/main/java/brave/test/propagation/PropagationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,8 +13,8 @@ */ package brave.test.propagation; -import brave.internal.Nullable; import brave.internal.codec.HexCodec; +import brave.internal.Nullable; import brave.propagation.Propagation; import brave.propagation.Propagation.Getter; import brave.propagation.Propagation.Setter; diff --git a/brave-tests/src/main/java/brave/test/util/AssertableCallback.java b/brave-tests/src/main/java/brave/test/util/AssertableCallback.java index da385bbdc4..c24a55a025 100644 --- a/brave-tests/src/main/java/brave/test/util/AssertableCallback.java +++ b/brave-tests/src/main/java/brave/test/util/AssertableCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -18,6 +18,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; +import org.assertj.core.api.AbstractThrowableAssert; +import org.assertj.core.api.ObjectAssert; import static org.assertj.core.api.Assertions.assertThat; @@ -102,6 +104,31 @@ public void onError(Throwable throwable) { throw new AssertionError("unexpected state"); } + // TODO: not actually used as we don't need to verify http status code or otherwise yet + public ObjectAssert assertThatSuccess() { + return assertThat(join()); + } + + // TODO: not actually used as we have no async error tests, yet + public AbstractThrowableAssert assertThatError() { + awaitUninterruptably(); + + if (onErrorCount.get() > 0) { + assertThat(onErrorCount) + .withFailMessage("onError signaled multiple times") + .hasValueLessThan(2); + + assertThat(onSuccessCount) + .withFailMessage("Both onSuccess and onError were signaled") + .hasValue(0); + + return assertThat(result == NULL_SENTINEL ? null : (Throwable) result); + } else if (onSuccessCount.get() > 0) { + throw new AssertionError("expected onError, but received onSuccess(" + result + ")"); + } + throw new AssertionError(); // unexpected as we only have two callbacks to handle! + } + void awaitUninterruptably() { try { await(3, TimeUnit.SECONDS); diff --git a/brave-tests/src/test/java/brave/propagation/B3PropagationTest.java b/brave-tests/src/test/java/brave/propagation/B3PropagationTest.java index 01ea553bdd..9bbc61a967 100644 --- a/brave-tests/src/test/java/brave/propagation/B3PropagationTest.java +++ b/brave-tests/src/test/java/brave/propagation/B3PropagationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/brave-tests/src/test/java/brave/propagation/B3SinglePropagationTest.java b/brave-tests/src/test/java/brave/propagation/B3SinglePropagationTest.java new file mode 100644 index 0000000000..1af71e6d4a --- /dev/null +++ b/brave-tests/src/test/java/brave/propagation/B3SinglePropagationTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.propagation; + +import brave.internal.Nullable; +import brave.test.propagation.PropagationTest; +import java.util.Map; +import java.util.function.Supplier; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class B3SinglePropagationTest extends PropagationTest { + @Override protected Class>> propagationSupplier() { + return PropagationSupplier.class; + } + + static class PropagationSupplier implements Supplier> { + @Override public Propagation get() { + return Propagation.B3_SINGLE_STRING; + } + } + + @Override protected void inject(Map map, @Nullable String traceId, + @Nullable String parentId, @Nullable String spanId, @Nullable Boolean sampled, + @Nullable Boolean debug) { + StringBuilder builder = new StringBuilder(); + char sampledChar = sampledChar(sampled, debug); + if (traceId == null) { + if (sampledChar != 0) builder.append(sampledChar); + } else { + builder.append(traceId).append('-').append(spanId); + if (sampledChar != 0) builder.append('-').append(sampledChar); + if (parentId != null) builder.append('-').append(parentId); + } + if (builder.length() != 0) map.put("b3", builder.toString()); + } + + /** returns 0 if there's no sampling status */ + static char sampledChar(@Nullable Boolean sampled, @Nullable Boolean debug) { + if (Boolean.TRUE.equals(debug)) return 'd'; + if (sampled != null) return sampled ? '1' : '0'; + return 0; + } + + @Override protected void inject(Map request, SamplingFlags flags) { + char sampledChar = sampledChar(flags.sampled(), flags.debug()); + if (sampledChar != 0) request.put("b3", String.valueOf(sampledChar)); + } + + @Test void extractTraceContext_sampledFalse() { + MapEntry mapEntry = new MapEntry(); + map.put("b3", "0"); + + SamplingFlags result = propagation.extractor(mapEntry).extract(map).samplingFlags(); + + assertThat(result) + .isEqualTo(SamplingFlags.NOT_SAMPLED); + } + + @Test void extractTraceContext_malformed() { + MapEntry mapEntry = new MapEntry(); + map.put("b3", "not-a-tumor"); + + SamplingFlags result = propagation.extractor(mapEntry).extract(map).samplingFlags(); + + assertThat(result) + .isEqualTo(SamplingFlags.EMPTY); + } + + @Test void extractTraceContext_malformed_uuid() { + MapEntry mapEntry = new MapEntry(); + map.put("b3", "b970dafd-0d95-40aa-95d8-1d8725aebe40"); + + SamplingFlags result = propagation.extractor(mapEntry).extract(map).samplingFlags(); + + assertThat(result) + .isEqualTo(SamplingFlags.EMPTY); + } + + @Test void extractTraceContext_debug_with_ids() { + MapEntry mapEntry = new MapEntry(); + + map.put("b3", "4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-d"); + + TraceContext result = propagation.extractor(mapEntry).extract(map).context(); + + assertThat(result.debug()) + .isTrue(); + } +} diff --git a/brave/README.md b/brave/README.md index 3d1c5918fd..ba84e3e4f4 100644 --- a/brave/README.md +++ b/brave/README.md @@ -87,7 +87,7 @@ When you need more features, or finer control, use the `Span` type: // Start a new trace or a span within an existing trace representing an operation Span span = tracer.nextSpan().name("encode").start(); // Put the span in "scope" so that downstream code such as loggers can see trace IDs -try (SpanInScope scope = tracer.withSpanInScope(span)) { +try (SpanInScope ws = tracer.withSpanInScope(span)) { return encoder.encode(); } catch (RuntimeException | Error e) { span.error(e); // Unless you handle exceptions, you might not know the operation failed! @@ -192,7 +192,7 @@ tracing.propagation().injector(ClientRequestWrapper::addHeader) span.kind(request.spanKind()); span.name("Report"); span.start(); -try (Scope scope = currentTraceContext.newScope(span.context())) { // 2. +try (Scope ws = currentTraceContext.newScope(span.context())) { // 2. return invoke(request); // 3. } catch (Throwable e) { span.error(error); // 4. @@ -721,7 +721,7 @@ external code might be invoked (such as proceeding an interceptor or otherwise), place the span in scope like this. ```java -try (SpanInScope scope = tracer.withSpanInScope(span)) { +try (SpanInScope ws = tracer.withSpanInScope(span)) { return inboundRequest.invoke(); } catch (RuntimeException | Error e) { span.error(e); diff --git a/brave/pom.xml b/brave/pom.xml index 6ca8bd9af4..63930405e8 100644 --- a/brave/pom.xml +++ b/brave/pom.xml @@ -37,6 +37,19 @@ + + + io.zipkin.reporter2 + zipkin-reporter-brave + ${zipkin-reporter.version} + + + io.zipkin.zipkin2 + zipkin + ${zipkin.version} + + org.junit.jupiter @@ -98,6 +111,12 @@ ${spring5.version} test + + io.zipkin.reporter2 + zipkin-sender-okhttp3 + ${zipkin-reporter.version} + test + diff --git a/brave/src/main/java/brave/ErrorParser.java b/brave/src/main/java/brave/ErrorParser.java new file mode 100644 index 0000000000..9caaeab9b7 --- /dev/null +++ b/brave/src/main/java/brave/ErrorParser.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave; + +import brave.handler.MutableSpan; +import brave.propagation.TraceContext; + +/** @deprecated Since 5.12 Use Tags#ERROR or defer to {@link zipkin2.reporter.brave.ZipkinSpanHandler} */ +public class ErrorParser extends Tag { + public ErrorParser() { + super("error"); + } + + /** Adds no tags to the span representing the operation in error. */ + public static final ErrorParser NOOP = new ErrorParser() { + @Override protected void error(Throwable error, Object customizer) { + } + }; + + /** Used to parse errors on a subtype of {@linkplain SpanCustomizer} */ + public final void error(Throwable error, SpanCustomizer customizer) { + error(error, (Object) customizer); + } + + /** Used to parse errors on a subtype of {@linkplain MutableSpan} */ + public final void error(Throwable error, MutableSpan span) { + error(error, (Object) span); + } + + /** + * Override to change what data from the error are parsed into the span modeling it. By default, + * this tags "error" as the message or simple name of the type. + */ + protected void error(Throwable error, Object span) { + Tags.ERROR.tag(span, error, null); + } + + /** Prefers {@link Throwable#getMessage()} over the {@link Class#getSimpleName()}. */ + static String parse(Throwable error) { + if (error == null) throw new NullPointerException("error == null"); + String message = error.getMessage(); + if (message != null) return message; + if (error.getClass().isAnonymousClass()) { // avoids "" + return error.getClass().getSuperclass().getSimpleName(); + } + return error.getClass().getSimpleName(); + } + + /** Same behaviour as {@link brave.SpanCustomizer#annotate(String)} */ + protected final void annotate(Object span, String value) { + if (span instanceof SpanCustomizer) { + ((SpanCustomizer) span).annotate(value); + } + } + + /** Same behaviour as {@link brave.SpanCustomizer#tag(String, String)} */ + protected final void tag(Object span, String key, String message) { + if (span instanceof SpanCustomizer) { + ((SpanCustomizer) span).tag(key, message); + } else if (span instanceof MutableSpan) { + ((MutableSpan) span).tag(key, message); + } else if (span instanceof KeyValueAdapter) { + KeyValueAdapter keyValueAdapter = (KeyValueAdapter) span; + keyValueAdapter.key = key; + keyValueAdapter.value = message; + } + } + + static final class KeyValueAdapter { + String key, value; + } + + @Override protected final String key(Throwable input) { + if (getClass() == ErrorParser.class) return Tags.ERROR.key(); + KeyValueAdapter kv = new KeyValueAdapter(); + error(input, kv); + return kv.key; + } + + @Override protected final String parseValue(Throwable input, TraceContext context) { + if (getClass() == ErrorParser.class) return parse(input); + KeyValueAdapter kv = new KeyValueAdapter(); + error(input, kv); + return kv.value; + } +} diff --git a/brave/src/main/java/brave/Response.java b/brave/src/main/java/brave/Response.java index 92487282b2..69c58266b0 100644 --- a/brave/src/main/java/brave/Response.java +++ b/brave/src/main/java/brave/Response.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,8 +13,8 @@ */ package brave; -import brave.handler.MutableSpan; import brave.handler.SpanHandler; +import brave.handler.MutableSpan; import brave.internal.Nullable; /** diff --git a/brave/src/main/java/brave/Span.java b/brave/src/main/java/brave/Span.java index 8cbf0d3d76..fa16a9e138 100644 --- a/brave/src/main/java/brave/Span.java +++ b/brave/src/main/java/brave/Span.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -15,6 +15,7 @@ import brave.internal.Nullable; import brave.propagation.TraceContext; +import zipkin2.Endpoint; /** * Subtype of {@link SpanCustomizer} which can capture latency and remote context of an operation. @@ -24,7 +25,7 @@ * // Note span methods chain. Explicitly start the span when ready. * Span span = tracer.nextSpan().name("encode").start(); * // A span is not responsible for making itself current (scoped); the tracer is - * try (SpanInScope scope = tracer.withSpanInScope(span)) { + * try (SpanInScope ws = tracer.withSpanInScope(span)) { * return encoder.encode(); * } catch (RuntimeException | Error e) { * span.error(e); // Unless you handle exceptions, you might not know the operation failed! @@ -52,7 +53,7 @@ public enum Kind { /** * When present, {@link #start()} is the moment a producer sent a message to a destination. A * duration between {@link #start()} and {@link #finish()} may imply batching delay. {@link - * #remoteServiceName(String)} indicates the destination, such as a broker. + * #remoteEndpoint(Endpoint)} indicates the destination, such as a broker. * *

Unlike {@link #CLIENT}, messaging spans never share a span ID. For example, the {@link * #CONSUMER} of the same message has {@link TraceContext#parentId()} set to this span's {@link @@ -62,7 +63,7 @@ public enum Kind { /** * When present, {@link #start()} is the moment a consumer received a message from an origin. A * duration between {@link #start()} and {@link #finish()} may imply a processing backlog. while - * {@link #remoteServiceName(String)} indicates the origin, such as a broker. + * {@link #remoteEndpoint(Endpoint)} indicates the origin, such as a broker. * *

Unlike {@link #SERVER}, messaging spans never share a span ID. For example, the {@link * #PRODUCER} of this message is the {@link TraceContext#parentId()} of this span. @@ -136,6 +137,18 @@ public enum Kind { // multi-catch. In practice, you should always at least catch RuntimeException and Error. public abstract Span error(Throwable throwable); + /** + * @deprecated Since 5.0, use {@link #remoteServiceName(String)} {@link #remoteIpAndPort(String, + * int)}. + */ + @Deprecated public Span remoteEndpoint(Endpoint endpoint) { + if (endpoint == null) return this; + if (endpoint.serviceName() != null) remoteServiceName(endpoint.serviceName()); + String ip = endpoint.ipv6() != null ? endpoint.ipv6() : endpoint.ipv4(); + remoteIpAndPort(ip, endpoint.portAsInt()); + return this; + } + /** * Lower-case label of the remote node in the service graph, such as "favstar". Do not set if * unknown. Avoid names with variables or unique identifiers embedded. diff --git a/brave/src/main/java/brave/Tags.java b/brave/src/main/java/brave/Tags.java index 1936c31601..f89ecde5ba 100644 --- a/brave/src/main/java/brave/Tags.java +++ b/brave/src/main/java/brave/Tags.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -33,13 +33,7 @@ public final class Tags { */ public static final Tag ERROR = new Tag("error") { @Override protected String parseValue(Throwable input, TraceContext context) { - if (input == null) throw new NullPointerException("input == null"); - String message = input.getMessage(); - if (message != null) return message; - if (input.getClass().isAnonymousClass()) { // avoids "" - return input.getClass().getSuperclass().getSimpleName(); - } - return input.getClass().getSimpleName(); + return ErrorParser.parse(input); } }; diff --git a/brave/src/main/java/brave/Tracer.java b/brave/src/main/java/brave/Tracer.java index 64cf1fc9df..73de17b9a9 100644 --- a/brave/src/main/java/brave/Tracer.java +++ b/brave/src/main/java/brave/Tracer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -69,7 +69,7 @@ * // Start a new trace or a span within an existing trace representing an operation * Span span = tracer.nextSpan().name("encode").start(); * // Put the span in "scope" so that downstream code such as loggers can see trace IDs - * try (SpanInScope scope = tracer.withSpanInScope(span)) { + * try (SpanInScope ws = tracer.withSpanInScope(span)) { * return encoder.encode(); * } catch (RuntimeException | Error e) { * span.error(e); // Unless you handle exceptions, you might not know the operation failed! @@ -86,6 +86,7 @@ * @see Propagation */ public class Tracer { + final Clock clock; final Propagation.Factory propagationFactory; final SpanHandler spanHandler; // only for toString final PendingSpans pendingSpans; @@ -95,6 +96,7 @@ public class Tracer { final AtomicBoolean noop; Tracer( + Clock clock, Propagation.Factory propagationFactory, SpanHandler spanHandler, PendingSpans pendingSpans, @@ -105,6 +107,7 @@ public class Tracer { boolean alwaysSampleLocal, AtomicBoolean noop ) { + this.clock = clock; this.propagationFactory = propagationFactory; this.spanHandler = spanHandler; this.pendingSpans = pendingSpans; @@ -116,6 +119,27 @@ public class Tracer { this.noop = noop; } + /** + * @since 4.19 + * @deprecated Since 5.8, use {@link #nextSpan(SamplerFunction, Object)} or {@link + * #startScopedSpan(String, SamplerFunction, Object)} + */ + @Deprecated public Tracer withSampler(Sampler sampler) { + if (sampler == null) throw new NullPointerException("sampler == null"); + return new Tracer( + clock, + propagationFactory, + spanHandler, + pendingSpans, + sampler, + currentTraceContext, + traceId128Bit, + supportsJoin, + alwaysSampleLocal, + noop + ); + } + /** * Explicitly creates a new trace. The result will be a root span (no parent span ID). * @@ -401,7 +425,7 @@ Span _toSpan(@Nullable TraceContext parent, TraceContext context) { * Ex. *

{@code
    * // Assume a framework interceptor uses this method to set the inbound span as current
-   * try (SpanInScope scope = tracer.withSpanInScope(span)) {
+   * try (SpanInScope ws = tracer.withSpanInScope(span)) {
    *   return inboundRequest.invoke();
    * // note: try-with-resources closes the scope *before* the catch block
    * } catch (RuntimeException | Error e) {
@@ -414,7 +438,7 @@ Span _toSpan(@Nullable TraceContext parent, TraceContext context) {
    * // An unrelated framework interceptor can now lookup the correct parent for outbound requests
    * Span parent = tracer.currentSpan()
    * Span span = tracer.nextSpan().name("outbound").start(); // parent is implicitly looked up
-   * try (SpanInScope scope = tracer.withSpanInScope(span)) {
+   * try (SpanInScope ws = tracer.withSpanInScope(span)) {
    *   return outboundRequest.invoke();
    * // note: try-with-resources closes the scope *before* the catch block
    * } catch (RuntimeException | Error e) {
diff --git a/brave/src/main/java/brave/Tracing.java b/brave/src/main/java/brave/Tracing.java
index a92c0b1117..cad13e7127 100644
--- a/brave/src/main/java/brave/Tracing.java
+++ b/brave/src/main/java/brave/Tracing.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013-2024 The OpenZipkin Authors
+ * Copyright 2013-2023 The OpenZipkin Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
  * in compliance with the License. You may obtain a copy of the License at
@@ -14,6 +14,7 @@
 package brave;
 
 import brave.baggage.BaggageField;
+import brave.handler.FinishedSpanHandler;
 import brave.handler.MutableSpan;
 import brave.handler.SpanHandler;
 import brave.internal.Nullable;
@@ -36,6 +37,8 @@
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import zipkin2.reporter.Reporter;
+import zipkin2.reporter.brave.ZipkinSpanHandler;
 
 /**
  * This provides utilities needed for trace instrumentation. For example, a {@link Tracer}.
@@ -62,6 +65,9 @@ public static Builder newBuilder() {
    */
   public abstract Propagation propagation();
 
+  /** @deprecated Since 5.12 use {@link #propagation()} as non-string keys are unsupported. */
+  @Deprecated public abstract Propagation.Factory propagationFactory();
+
   /**
    * Sampler is responsible for deciding if a particular trace should be "sampled", i.e. whether the
    * overhead of tracing will occur and/or if a trace will be reported to Zipkin.
@@ -88,6 +94,12 @@ public final Clock clock(TraceContext context) {
     return tracer().pendingSpans.getOrCreate(null, context, false).clock();
   }
 
+  /**
+   * @deprecated This is only used in Zipkin reporting. Since 5.12, use {@link
+   * zipkin2.reporter.brave.ZipkinSpanHandler.Builder#errorTag(Tag)}
+   */
+  @Deprecated public abstract ErrorParser errorParser();
+
   /**
    * Returns the most recently created tracing component iff it hasn't been closed. null otherwise.
    *
@@ -128,12 +140,14 @@ public final Clock clock(TraceContext context) {
 
   public static final class Builder {
     final MutableSpan defaultSpan = new MutableSpan();
+    Object zipkinSpanReporter; // avoid Zipkin type
     Clock clock;
     Sampler sampler = Sampler.ALWAYS_SAMPLE;
     CurrentTraceContext currentTraceContext = CurrentTraceContext.Default.inheritable();
     boolean traceId128Bit = false, supportsJoin = true;
-    boolean alwaysSampleLocal = false, trackOrphans = false;
+    boolean alwaysSampleLocal = false, alwaysReportSpans = false, trackOrphans = false;
     Propagation.Factory propagationFactory = B3Propagation.FACTORY;
+    ErrorParser errorParser = new ErrorParser();
     Set spanHandlers = new LinkedHashSet(); // dupes not ok
 
     Builder() {
@@ -209,6 +223,52 @@ public Builder localPort(int localPort) {
       return this;
     }
 
+    /**
+     * Sets the {@link zipkin2.Span#localEndpoint() Endpoint of the local service} being traced.
+     *
+     * @deprecated Use {@link #localServiceName(String)} {@link #localIp(String)} and {@link
+     * #localPort(int)}. Will be removed in Brave v6.
+     */
+    @Deprecated
+    public Builder endpoint(zipkin2.Endpoint endpoint) {
+      if (endpoint == null) throw new NullPointerException("endpoint == null");
+      this.defaultSpan.localServiceName(endpoint.serviceName());
+      this.defaultSpan.localIp(endpoint.ipv6() != null ? endpoint.ipv6() : endpoint.ipv4());
+      this.defaultSpan.localPort(endpoint.portAsInt());
+      return this;
+    }
+
+    /***
+     * 

Since 5.12, this is deprecated for using {@link zipkin2.reporter.brave.ZipkinSpanHandler} + * in the io.zipkin.reporter2:zipkin-reporter-brave + * library. + * + *

For example, here's how to batch send spans via HTTP to a Zipkin-compatible endpoint: + *

{@code
+     * // Configure a reporter, which controls how often spans are sent
+     * //   (this dependency is io.zipkin.reporter2:zipkin-sender-okhttp3)
+     * sender = OkHttpSender.create("http://127.0.0.1:9411/api/v2/spans");
+     * //   (this dependency is io.zipkin.reporter2:zipkin-reporter-brave)
+     * zipkinSpanHandler = AsyncZipkinSpanHandler.create(sender); // don't forget to close!
+     *
+     * // Create a tracing component with the service name you want to see in Zipkin.
+     * tracing = Tracing.newBuilder()
+     *                  .localServiceName("my-service")
+     *                  .addSpanHandler(zipkinSpanHandler)
+     *                  .build();
+     * }
+ * + * @see #addSpanHandler(SpanHandler) + * @deprecated Since 5.12, use {@link #addSpanHandler(SpanHandler)} with a + * {@link zipkin2.reporter.brave.ZipkinSpanHandler} + */ + @Deprecated public Builder spanReporter(Reporter spanReporter) { + if (spanReporter == Reporter.NOOP) return this; + if (spanReporter == null) throw new NullPointerException("spanReporter == null"); + this.zipkinSpanReporter = spanReporter; + return this; + } + /** * Assigns microsecond-resolution timestamp source for operations like {@link Span#start()}. * Defaults to JRE-specific platform time. @@ -286,6 +346,27 @@ public Builder supportsJoin(boolean supportsJoin) { return this; } + /** + * @deprecated This is only used in Zipkin reporting. Since 5.12, use {@link + * zipkin2.reporter.brave.ZipkinSpanHandler.Builder#errorTag(Tag)} + */ + @Deprecated public Builder errorParser(ErrorParser errorParser) { + this.errorParser = errorParser; + return this; + } + + /** + * @since 5.4 + * @deprecated Since 5.12 {@linkplain #addSpanHandler(SpanHandler) add a span handler} that + * implements {@link SpanHandler#end(TraceContext, MutableSpan, SpanHandler.Cause)} with {@link + * SpanHandler.Cause#FINISHED} + */ + public Builder addFinishedSpanHandler(FinishedSpanHandler handler) { + // Some configuration can coerce to no-op, ignore in this case. + if (handler == FinishedSpanHandler.NOOP) return this; + return addSpanHandler(handler); + } + /** * Inputs receive {code (context, span)} pairs for every {@linkplain TraceContext#sampledLocal() * locally sampled} span. The span is mutable for customization or redaction purposes. Span @@ -331,6 +412,15 @@ public Builder alwaysSampleLocal() { return this; } + /** + * @since 5.8 + * @deprecated Since 5.12, use {@link ZipkinSpanHandler.Builder#alwaysReportSpans(boolean)} + */ + public Builder alwaysReportSpans() { + this.alwaysReportSpans = true; + return this; + } + /** * When true, a {@link SpanHandler} is added that logs the caller which orphaned a span to the * category "brave.Tracer" at {@link Level#FINE}. Defaults to false. @@ -372,10 +462,12 @@ static final class Default extends Tracing { final CurrentTraceContext currentTraceContext; final Sampler sampler; final Clock clock; + final ErrorParser errorParser; final AtomicBoolean noop; Default(Builder builder) { this.clock = builder.clock != null ? builder.clock : Platform.get().clock(); + this.errorParser = builder.errorParser; this.propagationFactory = builder.propagationFactory; this.stringPropagation = builder.propagationFactory.get(); this.currentTraceContext = builder.currentTraceContext; @@ -389,6 +481,14 @@ static final class Default extends Tracing { } Set spanHandlers = new LinkedHashSet(builder.spanHandlers); + // When present, the Zipkin handler is invoked after the user-supplied ones. + if (builder.zipkinSpanReporter != null) { + spanHandlers.add( + ZipkinSpanHandler.newBuilder((Reporter) builder.zipkinSpanReporter) + .errorTag(errorParser) + .alwaysReportSpans(builder.alwaysReportSpans) + .build()); + } if (spanHandlers.isEmpty()) spanHandlers.add(new LogSpanHandler()); if (builder.trackOrphans) { spanHandlers.add(OrphanTracker.newBuilder().defaultSpan(defaultSpan).clock(clock).build()); @@ -398,7 +498,16 @@ static final class Default extends Tracing { SpanHandler spanHandler = NoopAwareSpanHandler.create(spanHandlers.toArray(new SpanHandler[0]), noop); + boolean alwaysSampleLocal = builder.alwaysSampleLocal; + for (SpanHandler handler : spanHandlers) { + if (handler instanceof FinishedSpanHandler) { + // Handle deprecated FinishedSpanHandler.alwaysSampleLocal + if (((FinishedSpanHandler)handler).alwaysSampleLocal()) alwaysSampleLocal = true; + } + } + this.tracer = new Tracer( + builder.clock, builder.propagationFactory, spanHandler, new PendingSpans(defaultSpan, clock, spanHandler, noop), @@ -406,7 +515,7 @@ static final class Default extends Tracing { builder.currentTraceContext, builder.traceId128Bit || propagationFactory.requires128BitTraceId(), builder.supportsJoin && propagationFactory.supportsJoin(), - builder.alwaysSampleLocal, + alwaysSampleLocal, noop ); // assign current IFF there's no instance already current @@ -421,6 +530,10 @@ static final class Default extends Tracing { return stringPropagation; } + @Override public Propagation.Factory propagationFactory() { + return propagationFactory; + } + @Override public Sampler sampler() { return sampler; } @@ -429,6 +542,10 @@ static final class Default extends Tracing { return currentTraceContext; } + @Deprecated @Override public ErrorParser errorParser() { + return errorParser; + } + @Override public boolean isNoop() { return noop.get(); } @@ -442,7 +559,7 @@ static final class Default extends Tracing { } @Override public void close() { - // only set null if we are the outermost instance + // only set null if we are the outer-most instance CURRENT.compareAndSet(this, null); } } diff --git a/brave/src/main/java/brave/baggage/BaggageField.java b/brave/src/main/java/brave/baggage/BaggageField.java index 3e5327ca07..c657d5f1d7 100644 --- a/brave/src/main/java/brave/baggage/BaggageField.java +++ b/brave/src/main/java/brave/baggage/BaggageField.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -20,6 +20,7 @@ import brave.propagation.TraceContext; import brave.propagation.TraceContextOrSamplingFlags; import java.util.Collections; +import java.util.List; import java.util.Locale; import java.util.Map; @@ -141,6 +142,23 @@ public static BaggageField create(String name) { return new BaggageField(name, ExtraBaggageContext.get()); } + /** @deprecated Since 5.12 use {@link #getAllValues(TraceContext)} */ + @Deprecated public static List getAll(@Nullable TraceContext context) { + if (context == null) return Collections.emptyList(); + return ExtraBaggageContext.getAllFields(context); + } + + /** @deprecated Since 5.12 use {@link #getAllValues(TraceContext)} */ + @Deprecated public static List getAll(TraceContextOrSamplingFlags extracted) { + if (extracted == null) throw new NullPointerException("extracted == null"); + return ExtraBaggageContext.getAllFields(extracted); + } + + /** @deprecated Since 5.12 use {@link #getAllValues(TraceContext)} */ + @Deprecated @Nullable public static List getAll() { + return getAll(currentTraceContext()); + } + /** * Returns a map of all {@linkplain BaggageField#name() name} to {@linkplain * BaggageField#getValue(TraceContext) non-{@code null} value} pairs in the {@linkplain @@ -233,7 +251,7 @@ public static Map getAllValues(TraceContextOrSamplingFlags extra * @see CorrelationScopeConfig.SingleCorrelationField#name() * @since 5.11 */ - public String name() { + public final String name() { return name; } diff --git a/brave/src/main/java/brave/baggage/BaggagePropagation.java b/brave/src/main/java/brave/baggage/BaggagePropagation.java index 7bbc861015..276aaeebdd 100644 --- a/brave/src/main/java/brave/baggage/BaggagePropagation.java +++ b/brave/src/main/java/brave/baggage/BaggagePropagation.java @@ -18,6 +18,7 @@ import brave.internal.baggage.BaggageCodec; import brave.internal.baggage.BaggageFields; import brave.internal.collect.Lists; +import brave.propagation.ExtraFieldPropagation; import brave.propagation.Propagation; import brave.propagation.TraceContext; import brave.propagation.TraceContext.Extractor; @@ -35,7 +36,7 @@ /** * This implements in-process and remote {@linkplain BaggageField baggage} propagation. * - *

For example, if you have a need to know the specific request's country code, you can + *

For example, if you have a need to know the a specific request's country code, you can * propagate it through the trace as HTTP headers. *

{@code
  * import brave.baggage.BaggagePropagationConfig.SingleBaggageField;
@@ -83,7 +84,6 @@
  * );
  * }
* - * @param Retained for compatibility with pre Brave v6, but always String. * @see BaggageField * @see BaggagePropagationConfig * @see BaggagePropagationCustomizer @@ -256,7 +256,11 @@ static final class Factory extends Propagation.Factory implements PropagationDetails * The {@code propagation} parameter is required because there may be multiple tracers with * different baggage configuration. Also, {@link Propagation} instances can be wrapped, so you - * cannot use {@code instanceof} to identify if baggage is internally supported. + * cannot use {@code instanceof} to identify if baggage is internally supported. For example, + * {@link ExtraFieldPropagation} internally wraps {@link BaggagePropagation}. + * + *

This is different than {@link BaggageField#getAll(TraceContext)}, as propagation keys may be + * different than {@linkplain BaggageField#name() baggage field names}. * * @param propagation used to extract configuration * @return a list of remote propagation key names used for trace context and baggage. @@ -323,7 +327,7 @@ static final class BaggageInjector implements Injector { for (BaggagePropagationConfig config : factory.configs) { if (config.baggageCodec == BaggageCodec.NOOP) continue; // local field - String value = config.baggageCodec.encode(values, context); + String value = config.baggageCodec.encode(values, context, request); if (value == null) continue; List keys = config.baggageCodec.injectKeyNames(); @@ -358,7 +362,7 @@ static final class BaggageExtractor implements Extractor { List keys = config.baggageCodec.injectKeyNames(); for (int i = 0, length = keys.size(); i < length; i++) { String value = getter.get(request, keys.get(i)); - if (value != null && config.baggageCodec.decode(extra, value)) { + if (value != null && config.baggageCodec.decode(extra, request, value)) { break; // accept the first match } } diff --git a/brave/src/main/java/brave/handler/FinishedSpanHandler.java b/brave/src/main/java/brave/handler/FinishedSpanHandler.java new file mode 100644 index 0000000000..3e3952a259 --- /dev/null +++ b/brave/src/main/java/brave/handler/FinishedSpanHandler.java @@ -0,0 +1,80 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.handler; + +import brave.Tracing; +import brave.TracingCustomizer; +import brave.propagation.TraceContext; + +/** + * @since 5.4 + * @deprecated Since 5.12 use {@link SpanHandler#end(TraceContext, MutableSpan, Cause)} with {@link + * Cause#FINISHED} + */ +@Deprecated public abstract class FinishedSpanHandler extends SpanHandler { + /** + * Use to avoid comparing against {@code null} references. + * + * @since 5.4 + * @deprecated Since 5.12 use {@link SpanHandler#NOOP} + */ + public static final FinishedSpanHandler NOOP = new FinishedSpanHandler() { + @Override public boolean handle(TraceContext context, MutableSpan span) { + return true; + } + + @Override public String toString() { + return "NoopFinishedSpanHandler{}"; + } + }; + + /** + * @since 5.4 + * @deprecated Since 5.12 use {@link SpanHandler#end(TraceContext, MutableSpan, Cause)} with + * {@link Cause#FINISHED} + */ + public abstract boolean handle(TraceContext context, MutableSpan span); + + /** + * @since 5.7 + * @deprecated Since 5.12 use {@link SpanHandler#end(TraceContext, MutableSpan, Cause)} with + * {@link Cause#ORPHANED} + */ + public boolean supportsOrphans() { + return false; + } + + /** + * @since 5.4 + * @deprecated Since 5.12, set {@link Tracing.Builder#alwaysSampleLocal()}. Tip: the same {@link + * TracingCustomizer} that {@linkplain Tracing.Builder#addSpanHandler(SpanHandler) adds this + * handler} can also also set {@link Tracing.Builder#alwaysSampleLocal()}. + */ + @Deprecated public boolean alwaysSampleLocal() { + return false; + } + + @Override public boolean end(TraceContext context, MutableSpan span, Cause cause) { + switch (cause) { + case FLUSHED: + case FINISHED: + return handle(context, span); + case ORPHANED: + return !supportsOrphans() || handle(context, span); + default: + assert false : "Bug!: missing state handling for " + cause; + return true; + } + } +} diff --git a/brave/src/main/java/brave/handler/MutableSpan.java b/brave/src/main/java/brave/handler/MutableSpan.java index d48b3dbd60..6e6bbeb981 100644 --- a/brave/src/main/java/brave/handler/MutableSpan.java +++ b/brave/src/main/java/brave/handler/MutableSpan.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -197,6 +197,14 @@ public MutableSpan(MutableSpan toCopy) { error = toCopy.error; } + /** + * @since 5.4 + * @deprecated Since 5.12 use {@link #equals(Object)} against a base value. + */ + @Deprecated public boolean isEmpty() { + return equals(EMPTY); + } + /** * Returns the {@linkplain TraceContext#traceIdString() trace ID} * diff --git a/brave/src/main/java/brave/handler/MutableSpanBytesEncoder.java b/brave/src/main/java/brave/handler/MutableSpanBytesEncoder.java index b8bfdba81c..2c71423a4c 100644 --- a/brave/src/main/java/brave/handler/MutableSpanBytesEncoder.java +++ b/brave/src/main/java/brave/handler/MutableSpanBytesEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -15,8 +15,8 @@ import brave.Tag; import brave.internal.codec.JsonWriter; -import brave.internal.codec.WriteBuffer; import brave.internal.codec.ZipkinV2JsonWriter; +import brave.internal.codec.WriteBuffer; import java.util.List; /** Similar to {@code zipkin2.MutableSpan.SpanBytesEncoder} except no Zipkin dependency. */ diff --git a/brave/src/main/java/brave/internal/Platform.java b/brave/src/main/java/brave/internal/Platform.java index 2f1744e614..479a001f77 100644 --- a/brave/src/main/java/brave/internal/Platform.java +++ b/brave/src/main/java/brave/internal/Platform.java @@ -15,6 +15,7 @@ import brave.Clock; import brave.Tracer; + import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; diff --git a/brave/src/main/java/brave/internal/baggage/BaggageCodec.java b/brave/src/main/java/brave/internal/baggage/BaggageCodec.java index 5aa2774618..119bd9e2e9 100644 --- a/brave/src/main/java/brave/internal/baggage/BaggageCodec.java +++ b/brave/src/main/java/brave/internal/baggage/BaggageCodec.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -38,11 +38,13 @@ public interface BaggageCodec { return Collections.emptyList(); } - @Override public boolean decode(ValueUpdater valueUpdater, String value) { + @Override + public boolean decode(ValueUpdater valueUpdater, Object request, String value) { return false; } - @Override public String encode(Map values, TraceContext context) { + @Override + public String encode(Map values, TraceContext context, Object request) { return null; } @@ -74,11 +76,16 @@ public interface BaggageCodec { * Called on the first non-{@code null} value from an {@link #extractKeyNames() extract key}. * Decodes any field state from an extracted value or returns {@code null} if there were none. * + *

Ex. When the state is a simple string, this will just use the request value directly. + * {@linkplain BaggageFields#isDynamic() dynamic values} will need to perform some decoding, + * such as splitting on comma and equals. + * * @param valueUpdater used to assign {@link BaggageField} values. + * @param request the parameter of {@link Extractor#extract(Object)} * @param value a non-{@code null} result of {@link Getter#get(Object, Object)} * @see #extractKeyNames() */ - boolean decode(ValueUpdater valueUpdater, String value); + boolean decode(ValueUpdater valueUpdater, Object request, String value); /** * Encodes any state to a request value used by {@link Setter#put(Object, Object, String)}. When @@ -91,5 +98,5 @@ public interface BaggageCodec { * @return an input to {@link Setter#put(Object, Object, String)} * @see #injectKeyNames() */ - @Nullable String encode(Map values, TraceContext context); + @Nullable String encode(Map values, TraceContext context, Object request); } diff --git a/brave/src/main/java/brave/internal/baggage/ExtraBaggageContext.java b/brave/src/main/java/brave/internal/baggage/ExtraBaggageContext.java index 5c8de54613..01546e3ab8 100644 --- a/brave/src/main/java/brave/internal/baggage/ExtraBaggageContext.java +++ b/brave/src/main/java/brave/internal/baggage/ExtraBaggageContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -33,6 +33,15 @@ public static BaggageContext get() { return INSTANCE; } + public static List getAllFields(TraceContextOrSamplingFlags extracted) { + if (extracted.context() != null) return getAllFields(extracted.context()); + return getAllFields(extracted.extra()); + } + + public static List getAllFields(TraceContext context) { + return getAllFields(context.extra()); + } + public static Map getAllValues(TraceContextOrSamplingFlags extracted) { if (extracted.context() != null) return getAllValues(extracted.context()); return getAllValues(extracted.extra()); diff --git a/brave/src/main/java/brave/internal/baggage/SingleFieldBaggageCodec.java b/brave/src/main/java/brave/internal/baggage/SingleFieldBaggageCodec.java index 33ce011600..8918ebe01e 100644 --- a/brave/src/main/java/brave/internal/baggage/SingleFieldBaggageCodec.java +++ b/brave/src/main/java/brave/internal/baggage/SingleFieldBaggageCodec.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -44,11 +44,12 @@ public static SingleFieldBaggageCodec single(BaggageField field, Collection values, TraceContext context) { + @Override public String encode(Map values, TraceContext context, Object request) { return field.getValue(context); } } diff --git a/brave/src/main/java/brave/internal/codec/WriteBuffer.java b/brave/src/main/java/brave/internal/codec/WriteBuffer.java index 7873e541a5..0d897748a5 100644 --- a/brave/src/main/java/brave/internal/codec/WriteBuffer.java +++ b/brave/src/main/java/brave/internal/codec/WriteBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -49,6 +49,11 @@ public void writeByte(int v) { buf[pos++] = (byte) (v & 0xff); } + public void write(byte[] v) { + System.arraycopy(v, 0, buf, pos, v.length); + pos += v.length; + } + void writeBackwards(long v) { int lastPos = pos + asciiSizeInBytes(v); // We write backwards from right to left. pos = lastPos; diff --git a/brave/src/main/java/brave/internal/collect/LongBitSet.java b/brave/src/main/java/brave/internal/collect/LongBitSet.java index 2eb1cc30ee..7aa5833326 100644 --- a/brave/src/main/java/brave/internal/collect/LongBitSet.java +++ b/brave/src/main/java/brave/internal/collect/LongBitSet.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -29,6 +29,10 @@ public static boolean isSet(long bitset, long i) { return (bitset & (1 << i)) != 0; } + public static long unsetBit(long bitset, long i) { + return bitset & ~(1 << i); + } + public static long setBit(long bitset, long i) { return bitset | (1 << i); } diff --git a/brave/src/main/java/brave/internal/extra/MapExtra.java b/brave/src/main/java/brave/internal/extra/MapExtra.java index d1659c1cb0..6244678d3b 100644 --- a/brave/src/main/java/brave/internal/extra/MapExtra.java +++ b/brave/src/main/java/brave/internal/extra/MapExtra.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -63,7 +63,6 @@ protected Set keySet() { } /** Returns a possibly empty map of all key to non-{@code null} values. */ - // Exposed for zipkin-secondary-sampling protected Map asReadOnlyMap() { return UnsafeArrayMap.newBuilder().build(state()); } diff --git a/brave/src/main/java/brave/internal/propagation/StringPropagationAdapter.java b/brave/src/main/java/brave/internal/propagation/StringPropagationAdapter.java new file mode 100644 index 0000000000..2396857eef --- /dev/null +++ b/brave/src/main/java/brave/internal/propagation/StringPropagationAdapter.java @@ -0,0 +1,209 @@ +/* + * Copyright 2013-2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.internal.propagation; + +import brave.propagation.Propagation; +import brave.propagation.TraceContext.Extractor; +import brave.propagation.TraceContext.Injector; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * This supports the deprecated {@link Propagation.Factory#create(KeyFactory)}. + * + *

Here's how to integrate: + * + *

First, override {@link Propagation.Factory#get()} with your actual implementation. Then, + * dispatch {@link Propagation.Factory#create(KeyFactory)} to your implementation via {@link + * #create(Propagation, KeyFactory)}: + * + *

{@code
+ * @Override public Propagation get() {
+ *   return new YourPropagation(this);
+ * }
+ *
+ * @Deprecated public  Propagation create(KeyFactory keyFactory) {
+ *   return StringPropagationAdapter.create(get(), keyFactory);
+ * }
+ * }
+ * + *

This is internal, and will be removed in Brave 6. However, it won't be removed + * before Brave 6. If you wish to use this, use "maven-shade-plugin" with the following + * configuration, or something similar. + * + *

{@code
+ * 
+ * false
+ * 
+ *   
+ *     io.zipkin.brave:brave
+ *   
+ * 
+ * 
+ *   
+ *     io.zipkin.brave:brave
+ *     
+ *       brave/internal/propagation/StringPropagationAdapter*.class
+ *     
+ *   
+ * 
+ * 
+ *   
+ *     brave.internal.propagation
+ *     YOUR_PACKAGE.brave_internal
+ *   
+ * 
+ * }
+ * + * @since 5.12 + * @deprecated As of Brave 5.18, throw an {@link UnsupportedOperationException} in + * {@link #create(Propagation, KeyFactory)} instead of implementing it. + */ +@Deprecated +public final class StringPropagationAdapter implements Propagation { + public static Propagation create(Propagation delegate, KeyFactory keyFactory) { + if (delegate == null) throw new NullPointerException("delegate == null"); + if (keyFactory == null) throw new NullPointerException("keyFactory == null"); + if (keyFactory == KeyFactory.STRING) return (Propagation) delegate; + return new StringPropagationAdapter(delegate, keyFactory); + } + + final Propagation delegate; + final KeyFactory keyFactory; + final Map map; + final List keysList; + + StringPropagationAdapter(Propagation delegate, KeyFactory keyFactory) { + this.delegate = delegate; + this.keyFactory = keyFactory; + this.map = new LinkedHashMap(); + this.keysList = toKeyList(delegate.keys(), keyFactory); + } + + List toKeyList(List keyNames, KeyFactory keyFactory) { + int length = keyNames.size(); + K[] keys = (K[]) new Object[length]; + for (int i = 0; i < length; i++) { + String keyName = keyNames.get(i); + K key = keyFactory.create(keyName); + keys[i] = key; + map.put(keyName, key); + } + return Collections.unmodifiableList(Arrays.asList(keys)); + } + + @Override public List keys() { + return keysList; + } + + @Override public Injector injector(Setter setter) { + // No check if Setter is a RemoteSetter because this instance cannot have String keys while + // RemoteSetter must have String keys + return delegate.injector(new SetterAdapter(setter, map)); + } + + @Override public Extractor extractor(Getter getter) { + // No check if Setter is a RemoteGetter because this instance cannot have String keys while + // RemoteGetter must have String keys + return delegate.extractor(new GetterAdapter(getter, map)); + } + + @Override public int hashCode() { + return delegate.hashCode(); + } + + @Override public String toString() { + return delegate.toString(); + } + + @Override public boolean equals(Object obj) { + if (obj instanceof StringPropagationAdapter) { + return delegate.equals(((StringPropagationAdapter) obj).delegate); + } else if (obj instanceof Propagation) { + return delegate.equals(obj); + } + return false; + } + + static final class GetterAdapter implements Getter { + final Getter getter; + final Map map; + + GetterAdapter(Getter getter, Map map) { + if (getter == null) throw new NullPointerException("getter == null"); + this.getter = getter; + this.map = map; + } + + @Override public String get(R request, String keyName) { + K key = map.get(keyName); + if (key == null) return null; + return getter.get(request, key); + } + + @Override public int hashCode() { + return getter.hashCode(); + } + + @Override public String toString() { + return getter.toString(); + } + + @Override public boolean equals(Object obj) { + if (obj instanceof GetterAdapter) { + return getter.equals(((GetterAdapter) obj).getter); + } else if (obj instanceof Getter) { + return getter.equals(obj); + } + return false; + } + } + + static final class SetterAdapter implements Setter { + final Setter setter; + final Map map; + + SetterAdapter(Setter setter, Map map) { + if (setter == null) throw new NullPointerException("setter == null"); + this.setter = setter; + this.map = map; + } + + @Override public void put(R request, String keyName, String value) { + K key = map.get(keyName); + if (key == null) return; + setter.put(request, key, value); + } + + @Override public int hashCode() { + return setter.hashCode(); + } + + @Override public String toString() { + return setter.toString(); + } + + @Override public boolean equals(Object obj) { + if (obj instanceof SetterAdapter) { + return setter.equals(((SetterAdapter) obj).setter); + } else if (obj instanceof Setter) { + return setter.equals(obj); + } + return false; + } + } +} diff --git a/brave/src/main/java/brave/propagation/B3Propagation.java b/brave/src/main/java/brave/propagation/B3Propagation.java index 9862c174c8..04ad6a5262 100644 --- a/brave/src/main/java/brave/propagation/B3Propagation.java +++ b/brave/src/main/java/brave/propagation/B3Propagation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -18,6 +18,7 @@ import brave.internal.Platform; import brave.internal.propagation.InjectorFactory; import brave.internal.propagation.InjectorFactory.InjectorFunction; +import brave.internal.propagation.StringPropagationAdapter; import brave.propagation.TraceContext.Extractor; import brave.propagation.TraceContext.Injector; import java.util.Collections; @@ -221,6 +222,10 @@ static final class Factory extends Propagation.Factory implements Propagation Propagation create(KeyFactory keyFactory) { + return StringPropagationAdapter.create(this, keyFactory); + } + @Override public boolean supportsJoin() { return true; } @@ -231,7 +236,7 @@ static final class Factory extends Propagation.Factory implements Propagation Extractor extractor(Getter getter) { if (getter == null) throw new NullPointerException("getter == null"); - return new B3Extractor(getter); + return new B3Extractor(this, getter); } @Override public int hashCode() { @@ -252,9 +257,11 @@ static final class Factory extends Propagation.Factory implements Propagation implements Extractor { + final Factory factory; final Getter getter; - B3Extractor(Getter getter) { + B3Extractor(Factory factory, Getter getter) { + this.factory = factory; this.getter = getter; } diff --git a/brave/src/main/java/brave/propagation/ExtraFieldCustomizer.java b/brave/src/main/java/brave/propagation/ExtraFieldCustomizer.java new file mode 100644 index 0000000000..12616c56fe --- /dev/null +++ b/brave/src/main/java/brave/propagation/ExtraFieldCustomizer.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.propagation; + +import brave.baggage.BaggagePropagationCustomizer; + +/** + * @since 5.7 + * @deprecated Since 5.11 use {@link BaggagePropagationCustomizer} + */ +@Deprecated public interface ExtraFieldCustomizer { + /** Use to avoid comparing against null references */ + ExtraFieldCustomizer NOOP = new ExtraFieldCustomizer() { + @Override public void customize(ExtraFieldPropagation.FactoryBuilder builder) { + } + + @Override public String toString() { + return "NoopExtraFieldCustomizer{}"; + } + }; + + void customize(ExtraFieldPropagation.FactoryBuilder builder); +} diff --git a/brave/src/main/java/brave/propagation/ExtraFieldPropagation.java b/brave/src/main/java/brave/propagation/ExtraFieldPropagation.java new file mode 100644 index 0000000000..e1651b2d12 --- /dev/null +++ b/brave/src/main/java/brave/propagation/ExtraFieldPropagation.java @@ -0,0 +1,253 @@ +/* + * Copyright 2013-2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.propagation; + +import brave.baggage.BaggageField; +import brave.baggage.BaggagePropagation; +import brave.baggage.BaggagePropagationConfig.SingleBaggageField; +import brave.internal.Nullable; +import brave.propagation.TraceContext.Extractor; +import brave.propagation.TraceContext.Injector; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import static java.util.Collections.unmodifiableList; + +/** @deprecated Since 5.11 use {@link BaggagePropagation} */ +@Deprecated public class ExtraFieldPropagation implements Propagation { + /** @deprecated Since 5.11 use {@link BaggagePropagation#newFactoryBuilder(Propagation.Factory)} */ + @Deprecated public static Factory newFactory(Propagation.Factory delegate, String... names) { + if (delegate == null) throw new NullPointerException("delegate == null"); + if (names == null) throw new NullPointerException("names == null"); + return newFactory(delegate, Arrays.asList(names)); + } + + /** @deprecated Since 5.11 use {@link BaggagePropagation#newFactoryBuilder(Propagation.Factory)} */ + @Deprecated public static Factory newFactory(Propagation.Factory delegate, + Collection names) { + if (delegate == null) throw new NullPointerException("delegate == null"); + if (names == null) throw new NullPointerException("field names == null"); + if (names.isEmpty()) throw new IllegalArgumentException("no field names"); + FactoryBuilder builder = new FactoryBuilder(delegate); + for (String name : names) builder.addField(name); + return builder.build(); + } + + /** @deprecated Since 5.11 use {@link BaggagePropagation#newFactoryBuilder(Propagation.Factory)} */ + @Deprecated public static FactoryBuilder newFactoryBuilder(Propagation.Factory delegate) { + return new FactoryBuilder(delegate); + } + + /** @deprecated Since 5.11 use {@link BaggagePropagation.FactoryBuilder} */ + @Deprecated public static final class FactoryBuilder { + final Propagation.Factory delegate; + final BaggagePropagation.FactoryBuilder baggageFactory; + // Updates could be out-of-order in the old impl, so we track everything until build() + final Set redactedNames = new LinkedHashSet(); + final Map> nameToKeyNames = new LinkedHashMap>(); + + FactoryBuilder(Propagation.Factory delegate) { + this.delegate = delegate; + this.baggageFactory = BaggagePropagation.newFactoryBuilder(delegate); + } + + /** @deprecated Since 5.11, use {@link SingleBaggageField#local(BaggageField)} */ + @Deprecated public FactoryBuilder addRedactedField(String fieldName) { + fieldName = validateFieldName(fieldName); + redactedNames.add(fieldName); + nameToKeyNames.put(fieldName, Collections.emptySet()); + return this; + } + + /** @deprecated Since 5.11, use {@link SingleBaggageField#remote(BaggageField)} */ + @Deprecated public FactoryBuilder addField(String fieldName) { + fieldName = validateFieldName(fieldName); + addKeyName(fieldName, fieldName); + return this; + } + + /** @deprecated Since 5.11, use {@link SingleBaggageField.Builder#addKeyName(String)} */ + @Deprecated public FactoryBuilder addPrefixedFields(String prefix, Collection names) { + if (prefix == null) throw new NullPointerException("prefix == null"); + prefix = validateFieldName(prefix); + if (names == null) throw new NullPointerException("names == null"); + for (String name : names) { + name = validateFieldName(name); + addKeyName(name, prefix + name); + } + return this; + } + + void addKeyName(String name, String keyName) { + Set keyNames = nameToKeyNames.get(name); + if (keyNames == null) nameToKeyNames.put(name, keyNames = new LinkedHashSet()); + keyNames.add(keyName); + } + + /** Returns a wrapper of the delegate if there are no fields to propagate. */ + public Factory build() { + Set extraKeyNames = new LinkedHashSet(); + for (Map.Entry> entry : nameToKeyNames.entrySet()) { + BaggageField field = BaggageField.create(entry.getKey()); + if (redactedNames.contains(field.name())) { + baggageFactory.add(SingleBaggageField.local(field)); + } else { + extraKeyNames.addAll(entry.getValue()); + SingleBaggageField.Builder builder = SingleBaggageField.newBuilder(field); + for (String keyName : entry.getValue()) { + builder.addKeyName(keyName); + } + baggageFactory.add(builder.build()); + } + } + return new Factory(baggageFactory.build(), extraKeyNames.toArray(new String[0])); + } + } + + /** + * @deprecated Since 5.11 use {@link BaggageField#getByName(String)} and {@link + * BaggageField#getValue()} + */ + @Deprecated @Nullable public static String current(String name) { + return get(name); + } + + /** + * @deprecated Since 5.11 use {@link BaggageField#getByName(String)} and {@link + * BaggageField#getValue()} + */ + @Deprecated @Nullable public static String get(String name) { + BaggageField field = BaggageField.getByName(validateFieldName(name)); + if (field == null) return null; + return field.getValue(); + } + + /** + * @deprecated Since 5.11 use {@link BaggageField#getByName(String)} and {@link + * BaggageField#updateValue(String)} + */ + @Deprecated public static void set(String name, String value) { + BaggageField field = BaggageField.getByName(validateFieldName(name)); + if (field == null) return; + field.updateValue(value); + } + + /** @deprecated Since 5.11 use {@link BaggageField#getAll()} */ + @Deprecated public static Map getAll() { + return BaggageField.getAllValues(); + } + + /** @deprecated Since 5.11 use {@link BaggageField#getAll(TraceContextOrSamplingFlags)} */ + @Deprecated public static Map getAll(TraceContextOrSamplingFlags extracted) { + if (extracted.context() != null) return getAll(extracted.context()); + return BaggageField.getAllValues(extracted); + } + + /** @deprecated Since 5.11 use {@link BaggageField#getAll(TraceContext)} */ + @Deprecated public static Map getAll(TraceContext context) { + return BaggageField.getAllValues(context); + } + + /** + * @deprecated Since 5.11 use {@link BaggageField#getByName(TraceContext, String)} and {@link + * BaggageField#getValue(TraceContext)} + */ + @Deprecated @Nullable public static String get(TraceContext context, String name) { + BaggageField field = BaggageField.getByName(context, validateFieldName(name)); + if (field == null) return null; + return field.getValue(context); + } + + /** + * @deprecated Since 5.11 use {@link BaggageField#getByName(TraceContext, String)} and {@link + * BaggageField#updateValue(String)} + */ + @Deprecated public static void set(TraceContext context, String name, String value) { + BaggageField field = BaggageField.getByName(context, validateFieldName(name)); + if (field == null) return; + field.updateValue(context, value); + } + + /** @deprecated Since 5.11 use {@link Propagation.Factory} */ + public static class Factory extends Propagation.Factory { + final Propagation.Factory delegate; + final List extraKeyNames; + + Factory(Propagation.Factory delegate, String[] extraKeyNames) { + this.delegate = delegate; + this.extraKeyNames = unmodifiableList(Arrays.asList(extraKeyNames)); + } + + /** {@inheritDoc} */ + @Override public ExtraFieldPropagation get() { + return new ExtraFieldPropagation(delegate.get(), extraKeyNames); + } + + @Override public boolean supportsJoin() { + return delegate.supportsJoin(); + } + + @Override public boolean requires128BitTraceId() { + return delegate.requires128BitTraceId(); + } + + @Override public TraceContext decorate(TraceContext context) { + return delegate.decorate(context); + } + } + + final Propagation delegate; + final List extraKeys; + + ExtraFieldPropagation(Propagation delegate, List extraKeys) { + this.delegate = delegate; + this.extraKeys = extraKeys; + } + + /** @deprecated Since 5.12 use {@link BaggagePropagation#allKeyNames(Propagation)} instead. */ + @Deprecated public List extraKeys() { + return extraKeys; + } + + /** + * Only returns trace context keys. Extra field names are not returned to ensure tools don't + * delete them. This is to support users accessing extra fields without Brave apis (ex via + * headers). + */ + @Override public List keys() { + return delegate.keys(); + } + + @Override public Injector injector(Setter setter) { + return delegate.injector(setter); + } + + @Override public Extractor extractor(Getter getter) { + return delegate.extractor(getter); + } + + static String validateFieldName(String fieldName) { + if (fieldName == null) throw new NullPointerException("fieldName == null"); + fieldName = fieldName.toLowerCase(Locale.ROOT).trim(); + if (fieldName.isEmpty()) throw new IllegalArgumentException("fieldName is empty"); + return fieldName; + } +} diff --git a/brave/src/main/java/brave/propagation/SamplingFlags.java b/brave/src/main/java/brave/propagation/SamplingFlags.java index b362893554..aba3539038 100644 --- a/brave/src/main/java/brave/propagation/SamplingFlags.java +++ b/brave/src/main/java/brave/propagation/SamplingFlags.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -131,6 +131,46 @@ static String toString(int flags) { return result.toString(); } + /** @deprecated prefer using constants. This will be removed in Brave v6 */ + @Deprecated + public static final class Builder { + int flags = 0; // bit field for sampled and debug + + public Builder() { + // public constructor instead of static newBuilder which would clash with TraceContext's + } + + /** @see SamplingFlags#sampled() */ + public Builder sampled(@Nullable Boolean sampled) { + if (sampled == null) { + flags &= ~(FLAG_SAMPLED_SET | FLAG_SAMPLED); + return this; + } + flags = InternalPropagation.sampled(sampled, flags); + return this; + } + + /** + * Setting debug to true also sets sampled to true. + * + * @see SamplingFlags#debug() + */ + public Builder debug(boolean debug) { + flags = SamplingFlags.debug(debug, flags); + return this; + } + + /** Allows you to create flags from a boolean value without allocating a builder instance */ + public static SamplingFlags build(@Nullable Boolean sampled) { + if (sampled != null) return sampled ? SAMPLED : NOT_SAMPLED; + return EMPTY; + } + + public SamplingFlags build() { + return toSamplingFlags(flags); + } + } + static boolean debug(int flags) { return (flags & FLAG_DEBUG) == FLAG_DEBUG; } diff --git a/brave/src/main/java/brave/propagation/TraceContext.java b/brave/src/main/java/brave/propagation/TraceContext.java index 3c1c537ed9..2ea1214b82 100644 --- a/brave/src/main/java/brave/propagation/TraceContext.java +++ b/brave/src/main/java/brave/propagation/TraceContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -21,14 +21,14 @@ import java.util.Collections; import java.util.List; +import static brave.internal.codec.HexCodec.lenientLowerHexToUnsignedLong; +import static brave.internal.codec.HexCodec.toLowerHex; +import static brave.internal.codec.HexCodec.writeHexLong; import static brave.internal.InternalPropagation.FLAG_LOCAL_ROOT; import static brave.internal.InternalPropagation.FLAG_SAMPLED; import static brave.internal.InternalPropagation.FLAG_SAMPLED_LOCAL; import static brave.internal.InternalPropagation.FLAG_SAMPLED_SET; import static brave.internal.InternalPropagation.FLAG_SHARED; -import static brave.internal.codec.HexCodec.lenientLowerHexToUnsignedLong; -import static brave.internal.codec.HexCodec.toLowerHex; -import static brave.internal.codec.HexCodec.writeHexLong; import static brave.internal.collect.Lists.ensureImmutable; import static brave.internal.collect.Lists.ensureMutable; import static brave.propagation.TraceIdContext.toTraceIdString; @@ -361,6 +361,18 @@ public Builder shared(boolean shared) { return this; } + /** + * @since 4.9 + * @deprecated Since 5.12, use {@link #addExtra(Object)} + */ + @Deprecated public Builder extra(List extraList) { + if (extraList == null) throw new NullPointerException("extraList == null"); + for (Object extra : extraList) { + addExtra(extra); + } + return this; + } + /** * Allows you to control {@link #extra()} explicitly. * diff --git a/brave/src/main/java/brave/propagation/TraceContextOrSamplingFlags.java b/brave/src/main/java/brave/propagation/TraceContextOrSamplingFlags.java index 8e21405e83..b2316c08db 100644 --- a/brave/src/main/java/brave/propagation/TraceContextOrSamplingFlags.java +++ b/brave/src/main/java/brave/propagation/TraceContextOrSamplingFlags.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -18,9 +18,12 @@ import brave.internal.Nullable; import brave.propagation.TraceContext.Extractor; import brave.sampler.SamplerFunction; +import java.util.ArrayList; import java.util.List; +import static brave.internal.InternalPropagation.FLAG_SAMPLED; import static brave.internal.InternalPropagation.FLAG_SAMPLED_LOCAL; +import static brave.internal.InternalPropagation.FLAG_SAMPLED_SET; import static brave.internal.collect.Lists.ensureImmutable; import static brave.propagation.TraceContext.ensureExtraAdded; import static java.util.Collections.emptyList; @@ -144,16 +147,35 @@ public static Builder newBuilder(SamplingFlags flags) { return new Builder(3, flags, emptyList()); } + /** + * @deprecated Since 5.12, use {@link #newBuilder(TraceContext)}, {@link + * #newBuilder(TraceIdContext)} or {@link #newBuilder(SamplingFlags)}. + */ + @Deprecated public static Builder newBuilder() { + return new Builder(0, null, emptyList()); + } + /** Returns {@link SamplingFlags#sampled()}, regardless of subtype. */ @Nullable public Boolean sampled() { return value.sampled(); } /** Returns {@link SamplingFlags#sampledLocal()}, regardless of subtype. */ - public boolean sampledLocal() { + public final boolean sampledLocal() { return (value.flags & FLAG_SAMPLED_LOCAL) == FLAG_SAMPLED_LOCAL; } + /** @deprecated do not use object variant.. only set when you have a sampling decision */ + @Deprecated + public TraceContextOrSamplingFlags sampled(@Nullable Boolean sampled) { + if (sampled != null) return sampled(sampled.booleanValue()); + int flags = value.flags; + flags &= ~FLAG_SAMPLED_SET; + flags &= ~FLAG_SAMPLED; + if (flags == value.flags) return this; // save effort if no change + return withFlags(flags); + } + /** * This is used to apply a {@link SamplerFunction} decision with least overhead. * @@ -278,6 +300,14 @@ public Builder toBuilder() { return result.append('}').toString(); } + /** @deprecated Since 5.12, use constants defined on this type as needed. */ + @Deprecated + public static TraceContextOrSamplingFlags create(@Nullable Boolean sampled, boolean debug) { + if (debug) return DEBUG; + if (sampled == null) return EMPTY; + return sampled ? SAMPLED : NOT_SAMPLED; + } + final int type; final SamplingFlags value; final List extraList; @@ -302,12 +332,41 @@ public static final class Builder { this.extraList = extraList; } + /** @deprecated Since 5.12, use {@link #newBuilder(TraceIdContext)} */ + @Deprecated public Builder context(TraceContext context) { + return copyStateTo(newBuilder(context)); + } + + /** @deprecated Since 5.12, use {@link #newBuilder(TraceIdContext)} */ + @Deprecated public Builder traceIdContext(TraceIdContext traceIdContext) { + return copyStateTo(newBuilder(traceIdContext)); + } + + /** @deprecated Since 5.12, use {@link #newBuilder(SamplingFlags)} */ + @Deprecated public Builder samplingFlags(SamplingFlags samplingFlags) { + return copyStateTo(newBuilder(samplingFlags)); + } + + Builder copyStateTo(Builder builder) { + if (sampledLocal) builder.sampledLocal(); + for (Object extra : extraList) builder.addExtra(extra); + return builder; + } + /** @see TraceContext#sampledLocal() */ public Builder sampledLocal() { this.sampledLocal = true; return this; } + /** @deprecated Since 5.12, use {@link #addExtra(Object)} */ + @Deprecated public Builder extra(List extraList) { + if (extraList == null) throw new NullPointerException("extraList == null"); + this.extraList = new ArrayList(); + for (Object extra : extraList) addExtra(extra); + return this; + } + /** * @see TraceContextOrSamplingFlags#extra() * @since 4.9 @@ -318,7 +377,11 @@ public Builder addExtra(Object extra) { } /** Returns an immutable result from the values currently in the builder */ - public TraceContextOrSamplingFlags build() { + public final TraceContextOrSamplingFlags build() { + if (value == null) { + throw new IllegalArgumentException( + "Value unset. Use a non-deprecated newBuilder method instead."); + } final TraceContextOrSamplingFlags result; if (!extraList.isEmpty() && type == 1) { // move extra to the trace context TraceContext context = (TraceContext) value; diff --git a/brave/src/main/java/brave/propagation/TraceIdContext.java b/brave/src/main/java/brave/propagation/TraceIdContext.java index df7d0fe05f..3d2306f993 100644 --- a/brave/src/main/java/brave/propagation/TraceIdContext.java +++ b/brave/src/main/java/brave/propagation/TraceIdContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -17,10 +17,10 @@ import brave.internal.Nullable; import brave.internal.RecyclableBuffers; -import static brave.internal.InternalPropagation.FLAG_SAMPLED; -import static brave.internal.InternalPropagation.FLAG_SAMPLED_SET; import static brave.internal.codec.HexCodec.toLowerHex; import static brave.internal.codec.HexCodec.writeHexLong; +import static brave.internal.InternalPropagation.FLAG_SAMPLED; +import static brave.internal.InternalPropagation.FLAG_SAMPLED_SET; /** * Contains inbound trace ID and sampling flags, used when users control the root trace ID, but not diff --git a/brave/src/main/java/brave/sampler/DeclarativeSampler.java b/brave/src/main/java/brave/sampler/DeclarativeSampler.java index 46ceedac3c..249da0afb1 100644 --- a/brave/src/main/java/brave/sampler/DeclarativeSampler.java +++ b/brave/src/main/java/brave/sampler/DeclarativeSampler.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,7 +13,9 @@ */ package brave.sampler; +import brave.Tracer; import brave.internal.Nullable; +import brave.propagation.SamplingFlags; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -141,4 +143,51 @@ static final class DeclarativeRateLimitingSampler extends DeclarativeSampler< DeclarativeSampler() { } + /** + * @since 4.4 + * @deprecated since 5.8, use {@link #createWithProbability(ProbabilityOfMethod)} + */ + public static DeclarativeSampler create(RateForMethod rateForMethod) { + return createWithProbability(rateForMethod); + } + + /** + * @since 4.4 + * @deprecated since 5.8, use {@link ProbabilityOfMethod} + */ + public interface RateForMethod extends ProbabilityOfMethod { + } + + /** + * @since 4.4 + * @deprecated Since 5.8, use {@link Tracer#startScopedSpan(String, SamplerFunction, Object)} + */ + @Deprecated public Sampler toSampler(M method) { + return toSampler(method, Sampler.NEVER_SAMPLE); + } + + /** + * @since 4.19 + * @deprecated Since 5.8, use {@link Tracer#startScopedSpan(String, SamplerFunction, Object)} + */ + @Deprecated public Sampler toSampler(final M method, final Sampler fallback) { + if (fallback == null) throw new NullPointerException("fallback == null"); + if (method == null) return fallback; + return new Sampler() { + @Override public boolean isSampled(long traceId) { + Boolean decision = trySample(method); + if (decision == null) return fallback.isSampled(traceId); + return decision; + } + }; + } + + /** + * @since 4.4 + * @deprecated Since 5.8, use {@link #trySample(Object)} + */ + @Deprecated public SamplingFlags sample(@Nullable M method) { + if (method == null) return SamplingFlags.EMPTY; + return SamplingFlags.Builder.build(trySample(method)); + } } diff --git a/brave/src/main/java/brave/sampler/ParameterizedSampler.java b/brave/src/main/java/brave/sampler/ParameterizedSampler.java index e994ee424f..0206adb540 100644 --- a/brave/src/main/java/brave/sampler/ParameterizedSampler.java +++ b/brave/src/main/java/brave/sampler/ParameterizedSampler.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,7 +14,9 @@ package brave.sampler; import brave.internal.Nullable; +import brave.propagation.SamplingFlags; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; /** @@ -106,4 +108,37 @@ static class R

{ } return null; } + + /** + * @since 4.4 + * @deprecated Since 5.8, use {@link #trySample(Object)} + */ + @Deprecated public SamplingFlags sample(@Nullable P parameters) { + return SamplingFlags.Builder.build(trySample(parameters)); + } + + /** + * @since 4.4 + * @deprecated since 5.8, use {@link #newBuilder()} + */ + @Deprecated public static

ParameterizedSampler

create(List> rules) { + if (rules == null) throw new NullPointerException("rules == null"); + Builder

builder = newBuilder(); + for (Rule

rule : rules) { + builder.putRule(rule.matcher, rule.sampler); + } + return builder.build(); + } + + /** + * @since 4.4 + * @deprecated Since 5.8, use {@link Builder#putRule(Matcher, Sampler)} + */ + @Deprecated public static abstract class Rule

extends R

implements Matcher

{ + protected Rule(float probability) { + super(null, CountingSampler.create(probability)); + } + + @Override public abstract boolean matches(P parameters); + } } diff --git a/brave/src/test/java/brave/CurrentSpanCustomizerTest.java b/brave/src/test/java/brave/CurrentSpanCustomizerTest.java index afae627573..a164246dfd 100644 --- a/brave/src/test/java/brave/CurrentSpanCustomizerTest.java +++ b/brave/src/test/java/brave/CurrentSpanCustomizerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -35,7 +35,7 @@ class CurrentSpanCustomizerTest { @Test void name() { span.start(); - try (SpanInScope scope = tracing.tracer().withSpanInScope(span)) { + try (SpanInScope ws = tracing.tracer().withSpanInScope(span)) { spanCustomizer.name("newname"); } span.flush(); @@ -50,7 +50,7 @@ class CurrentSpanCustomizerTest { @Test void tag() { span.start(); - try (SpanInScope scope = tracing.tracer().withSpanInScope(span)) { + try (SpanInScope ws = tracing.tracer().withSpanInScope(span)) { spanCustomizer.tag("foo", "bar"); } span.flush(); @@ -65,7 +65,7 @@ class CurrentSpanCustomizerTest { @Test void annotate() { span.start(); - try (SpanInScope scope = tracing.tracer().withSpanInScope(span)) { + try (SpanInScope ws = tracing.tracer().withSpanInScope(span)) { spanCustomizer.annotate("foo"); } span.flush(); diff --git a/brave/src/test/java/brave/CurrentTraceContextExecutorServiceTest.java b/brave/src/test/java/brave/CurrentTraceContextExecutorServiceTest.java index 5487db9294..986d9b8417 100644 --- a/brave/src/test/java/brave/CurrentTraceContextExecutorServiceTest.java +++ b/brave/src/test/java/brave/CurrentTraceContextExecutorServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -113,14 +113,14 @@ void eachTaskHasCorrectSpanAttached(Callable scheduleTwoTasks) throws Excepti // First task should block the queue, forcing the latter to not be scheduled immediately // Both should have the same parent, as the parent applies to the task creation time, not // execution time. - try (CurrentTraceContext.Scope scope = currentTraceContext.newScope(context)) { + try (CurrentTraceContext.Scope ws = currentTraceContext.newScope(context)) { scheduleTwoTasks.call(); } // switch the current span to something else. If there's a bug, when the // second runnable starts, it will have this span as opposed to the one it was // invoked with - try (CurrentTraceContext.Scope scope = currentTraceContext.newScope(context2)) { + try (CurrentTraceContext.Scope ws = currentTraceContext.newScope(context2)) { latch.countDown(); shutdownExecutor(); assertThat(threadValues) diff --git a/brave/src/test/java/brave/CurrentTraceContextExecutorTest.java b/brave/src/test/java/brave/CurrentTraceContextExecutorTest.java index a86184e2c2..a8a88f18a3 100644 --- a/brave/src/test/java/brave/CurrentTraceContextExecutorTest.java +++ b/brave/src/test/java/brave/CurrentTraceContextExecutorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -49,7 +49,7 @@ class CurrentTraceContextExecutorTest { // First task should block the queue, forcing the latter to not be scheduled immediately // Both should have the same parent, as the parent applies to the task creation time, not // execution time. - try (CurrentTraceContext.Scope scope = currentTraceContext.newScope(context)) { + try (CurrentTraceContext.Scope ws = currentTraceContext.newScope(context)) { executor.execute(() -> { threadValues[0] = currentTraceContext.get(); try { @@ -66,7 +66,7 @@ class CurrentTraceContextExecutorTest { // switch the current span to something else. If there's a bug, when the // second runnable starts, it will have this span as opposed to the one it was // invoked with - try (CurrentTraceContext.Scope scope = currentTraceContext.newScope(context2)) { + try (CurrentTraceContext.Scope ws = currentTraceContext.newScope(context2)) { latch.countDown(); shutdownExecutor(); assertThat(threadValues) diff --git a/brave/src/test/java/brave/ErrorParserTest.java b/brave/src/test/java/brave/ErrorParserTest.java new file mode 100644 index 0000000000..9e5887265b --- /dev/null +++ b/brave/src/test/java/brave/ErrorParserTest.java @@ -0,0 +1,101 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +@Deprecated +@ExtendWith(MockitoExtension.class) +class ErrorParserTest { + @Mock SpanCustomizer customizer; + @Mock ScopedSpan scopedSpan; + ErrorParser parser = new ErrorParser(); + + @Test void error_customizer() { + parser.error(new RuntimeException("this cake is a lie"), customizer); + + verify(customizer).tag("error", "this cake is a lie"); + } + + @Test void error_customizer_asTag() { + parser.tag(new RuntimeException("this cake is a lie"), customizer); + + verify(customizer).tag("error", "this cake is a lie"); + } + + @Test void error_scopedSpan() { + parser.error(new RuntimeException("this cake is a lie"), scopedSpan); + + verify(scopedSpan).tag("error", "this cake is a lie"); + } + + @Test void error_noMessage_customizer() { + parser.error(new RuntimeException(), customizer); + + verify(customizer).tag("error", "RuntimeException"); + } + + @Test void error_noMessage_scopedSpan() { + parser.error(new RuntimeException(), scopedSpan); + + verify(scopedSpan).tag("error", "RuntimeException"); + } + + @Test void error_noop_customizer() { + ErrorParser.NOOP.error(new RuntimeException("this cake is a lie"), customizer); + + verifyNoMoreInteractions(customizer); + } + + @Test void error_noop_scopedSpan() { + ErrorParser.NOOP.error(new RuntimeException("this cake is a lie"), scopedSpan); + + verifyNoMoreInteractions(scopedSpan); + } + + @Test void parse_anonymous() { + assertThat(ErrorParser.parse(new RuntimeException() { + })).isEqualTo("RuntimeException"); + } + + @Test void parse_anonymous_message() { + assertThat(ErrorParser.parse(new RuntimeException("this cake is a lie") { + })).isEqualTo("this cake is a lie"); + } + + ErrorParser subclassErrorParser = new ErrorParser() { + @Override protected void error(Throwable error, Object span) { + tag(span, "noterror", "the cake is fine"); + } + }; + + @Test void subclass() { + subclassErrorParser.error(new RuntimeException("this cake is a lie"), customizer); + + verify(customizer).tag("noterror", "the cake is fine"); + } + + @Test void subclass_asTag() { + subclassErrorParser.tag(new RuntimeException("this cake is a lie"), customizer); + + verify(customizer).tag("noterror", "the cake is fine"); + } +} diff --git a/brave/src/test/java/brave/LazySpanTest.java b/brave/src/test/java/brave/LazySpanTest.java index bf4c0b2b65..ee1bdd6c5c 100644 --- a/brave/src/test/java/brave/LazySpanTest.java +++ b/brave/src/test/java/brave/LazySpanTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -39,7 +39,7 @@ class LazySpanTest { @Test void equals_sameContext() { Span current1, current2; - try (Scope scope = tracing.currentTraceContext().newScope(context)) { + try (Scope ws = tracing.currentTraceContext().newScope(context)) { current1 = tracing.tracer().currentSpan(); current2 = tracing.tracer().currentSpan(); } @@ -52,10 +52,10 @@ class LazySpanTest { @Test void equals_notSameContext() { Span current1, current2; - try (Scope scope = tracing.currentTraceContext().newScope(context)) { + try (Scope ws = tracing.currentTraceContext().newScope(context)) { current1 = tracing.tracer().currentSpan(); } - try (Scope scope = tracing.currentTraceContext().newScope(context2)) { + try (Scope ws = tracing.currentTraceContext().newScope(context2)) { current2 = tracing.tracer().currentSpan(); } @@ -64,7 +64,7 @@ class LazySpanTest { @Test void equals_realSpan_sameContext() { Span current; - try (Scope scope = tracing.currentTraceContext().newScope(context)) { + try (Scope ws = tracing.currentTraceContext().newScope(context)) { current = tracing.tracer().currentSpan(); } @@ -73,7 +73,7 @@ class LazySpanTest { @Test void equals_realSpan_notSameContext() { Span current; - try (Scope scope = tracing.currentTraceContext().newScope(context)) { + try (Scope ws = tracing.currentTraceContext().newScope(context)) { current = tracing.tracer().currentSpan(); } @@ -82,7 +82,7 @@ class LazySpanTest { @Test void equals_noopSpan_sameContext() { Span current; - try (Scope scope = tracing.currentTraceContext().newScope(unsampledContext)) { + try (Scope ws = tracing.currentTraceContext().newScope(unsampledContext)) { current = tracing.tracer().currentSpan(); } @@ -91,7 +91,7 @@ class LazySpanTest { @Test void equals_noopSpan_notSameContext() { Span current; - try (Scope scope = tracing.currentTraceContext().newScope(unsampledContext)) { + try (Scope ws = tracing.currentTraceContext().newScope(unsampledContext)) { current = tracing.tracer().currentSpan(); } diff --git a/brave/src/test/java/brave/NoopSpanTest.java b/brave/src/test/java/brave/NoopSpanTest.java index 9a3f42c80c..8ed213df94 100644 --- a/brave/src/test/java/brave/NoopSpanTest.java +++ b/brave/src/test/java/brave/NoopSpanTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -69,7 +69,7 @@ class NoopSpanTest { @Test void equals_lazySpan_sameContext() { Span current; - try (SpanInScope scope = tracer.withSpanInScope(span)) { + try (SpanInScope ws = tracer.withSpanInScope(span)) { current = tracer.currentSpan(); } @@ -78,7 +78,7 @@ class NoopSpanTest { @Test void equals_lazySpan_notSameContext() { Span current; - try (SpanInScope scope = tracer.withSpanInScope(tracer.newTrace())) { + try (SpanInScope ws = tracer.withSpanInScope(tracer.newTrace())) { current = tracer.currentSpan(); } diff --git a/brave/src/test/java/brave/RealSpanTest.java b/brave/src/test/java/brave/RealSpanTest.java index 9ee3a54d11..4abf72ee2d 100644 --- a/brave/src/test/java/brave/RealSpanTest.java +++ b/brave/src/test/java/brave/RealSpanTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -20,6 +20,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import zipkin2.Endpoint; + import static brave.Span.Kind; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; @@ -98,6 +100,16 @@ class RealSpanTest { .isTrue(); } + @Deprecated + @Test void remoteEndpoint_nulls() { + span.remoteEndpoint(Endpoint.newBuilder().build()); + span.flush(); + + assertThat(spans.get(0).remoteServiceName()).isNull(); + assertThat(spans.get(0).remoteIp()).isNull(); + assertThat(spans.get(0).remotePort()).isZero(); + } + @Test void annotate_timestamp() { span.annotate(2, "foo"); span.flush(); @@ -187,7 +199,7 @@ private void finish(String start, String end, Kind span2Kind) { @Test void equals_lazySpan_sameContext() { Span current; - try (Scope scope = tracing.currentTraceContext().newScope(context)) { + try (Scope ws = tracing.currentTraceContext().newScope(context)) { current = tracing.tracer().currentSpan(); } @@ -196,7 +208,7 @@ private void finish(String start, String end, Kind span2Kind) { @Test void equals_lazySpan_notSameContext() { Span current; - try (Scope scope = tracing.currentTraceContext().newScope(context2)) { + try (Scope ws = tracing.currentTraceContext().newScope(context2)) { current = tracing.tracer().currentSpan(); } diff --git a/brave/src/test/java/brave/TagTest.java b/brave/src/test/java/brave/TagTest.java index 7bf6846340..6bad30ab6d 100644 --- a/brave/src/test/java/brave/TagTest.java +++ b/brave/src/test/java/brave/TagTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -300,6 +300,6 @@ class TagTest { verify(parseValue).apply(input, context); verifyNoMoreInteractions(parseValue); // doesn't parse twice - assertThat(mutableSpan.error()).isNull(); + assertThat(mutableSpan.isEmpty()).isTrue(); } } diff --git a/brave/src/test/java/brave/TagsTest.java b/brave/src/test/java/brave/TagsTest.java index 3557cdee4c..e7229cbdef 100644 --- a/brave/src/test/java/brave/TagsTest.java +++ b/brave/src/test/java/brave/TagsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -40,18 +40,6 @@ class TagsTest { verify(span).tag("error", "RuntimeException"); } - @Test void error_anonymous() { - Tags.ERROR.tag(new RuntimeException() {}, span); - - verify(span).tag("error", "RuntimeException"); - } - - @Test void error_anonymous_message() { - Tags.ERROR.tag(new RuntimeException("this cake is a lie") {}, span); - - verify(span).tag("error", "this cake is a lie"); - } - TraceContext context = TraceContext.newBuilder().traceId(1).spanId(2).build(); /** These are not good examples of actual baggage.. just to test the types. */ diff --git a/brave/src/test/java/brave/TracerTest.java b/brave/src/test/java/brave/TracerTest.java index baa45fa59c..0b238539b0 100644 --- a/brave/src/test/java/brave/TracerTest.java +++ b/brave/src/test/java/brave/TracerTest.java @@ -40,6 +40,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import zipkin2.Endpoint; +import zipkin2.reporter.Reporter; import static brave.Span.Kind.CLIENT; import static brave.Span.Kind.SERVER; @@ -107,6 +109,19 @@ public class TracerTest { .isSameAs(sampler); } + @Test void withSampler() { + Sampler sampler = new Sampler() { + @Override public boolean isSampled(long traceId) { + return false; + } + }; + + tracer = tracer.withSampler(sampler); + + assertThat(tracer.sampler) + .isSameAs(sampler); + } + @Test void localServiceName() { tracer = Tracing.newBuilder().localServiceName("my-foo").build().tracer(); @@ -121,6 +136,16 @@ public class TracerTest { .isEqualTo("unknown"); } + @Test void localServiceName_ignoredWhenGivenLocalEndpoint() { + Endpoint endpoint = Endpoint.newBuilder().ip("1.2.3.4").serviceName("my-bar").build(); + tracer = Tracing.newBuilder().localServiceName("my-foo").endpoint(endpoint).build().tracer(); + + MutableSpan defaultSpan = new MutableSpan(); + defaultSpan.localServiceName("my-bar"); + defaultSpan.localIp("1.2.3.4"); + assertThat(tracer).extracting("pendingSpans.defaultSpan").isEqualTo(defaultSpan); + } + @Test void newTrace_isRootSpan() { assertThat(tracer.newTrace()) .satisfies(s -> assertThat(s.context().parentId()).isNull()) @@ -134,6 +159,13 @@ public class TracerTest { .isNotZero(); } + @Test void newTrace_notSampled_tracer() { + tracer = tracer.withSampler(Sampler.NEVER_SAMPLE); + + assertThat(tracer.newTrace()) + .isInstanceOf(NoopSpan.class); + } + /** When we join a sampled request, we are sharing the same trace identifiers. */ @Test void join_setsShared() { TraceContext fromIncomingRequest = tracer.newTrace().context(); @@ -331,6 +363,16 @@ public class TracerTest { .isSameAs(NoopSpanCustomizer.INSTANCE); } + @Test void currentSpanCustomizer_noop_when_notSampled() { + ScopedSpan parent = tracer.withSampler(Sampler.NEVER_SAMPLE).startScopedSpan("parent"); + try { + assertThat(tracer.currentSpanCustomizer()) + .isSameAs(NoopSpanCustomizer.INSTANCE); + } finally { + parent.finish(); + } + } + @Test void currentSpanCustomizer_notSampled() { tracer = Tracing.newBuilder().sampler(Sampler.NEVER_SAMPLE).build().tracer(); @@ -359,7 +401,7 @@ public class TracerTest { } @Test void currentSpan_decoratesExternalContext() { - try (Scope scope = currentTraceContext.newScope(context)) { + try (Scope ws = currentTraceContext.newScope(context)) { assertThat(tracer.currentSpan().context()) .isNotSameAs(context) .extracting(TraceContext::localRootId) @@ -371,13 +413,13 @@ public class TracerTest { TraceContext context = TraceContext.newBuilder().traceId(1L).spanId(2L).shared(true).sampled(true).build(); - try (Scope scope = currentTraceContext.newScope(context)) { + try (Scope ws = currentTraceContext.newScope(context)) { assertThat(tracer.currentSpan().context().shared()).isTrue(); } } @Test void currentSpan_doesntSetSharedFlag() { - try (Scope scope = currentTraceContext.newScope(context)) { + try (Scope ws = currentTraceContext.newScope(context)) { assertThat(tracer.currentSpan().context().shared()).isFalse(); } } @@ -385,7 +427,7 @@ public class TracerTest { @Test void currentSpan_doesntRedundantlyDecorateContext() { TraceContext context = tracer.newTrace().context(); - try (Scope scope = currentTraceContext.newScope(context)) { + try (Scope ws = currentTraceContext.newScope(context)) { assertThat(tracer.currentSpan().context()) .isSameAs(context) .extracting(TraceContext::localRootId) @@ -411,7 +453,7 @@ public class TracerTest { @Test void nextSpanWithParent_overrideToMakeNewTrace() { Span span; - try (Scope scope = currentTraceContext.newScope(context)) { + try (Scope ws = currentTraceContext.newScope(context)) { span = tracer.nextSpanWithParent(deferDecision(), false, null); } @@ -421,7 +463,7 @@ public class TracerTest { @Test void nextSpan_extractedNothing_makesChildOfCurrent() { Span parent = tracer.newTrace(); - try (SpanInScope scope = tracer.withSpanInScope(parent)) { + try (SpanInScope ws = tracer.withSpanInScope(parent)) { Span nextSpan = tracer.nextSpan(TraceContextOrSamplingFlags.create(EMPTY)); assertThat(nextSpan.context().parentId()) .isEqualTo(parent.context().spanId()); @@ -438,7 +480,7 @@ public class TracerTest { @Test void nextSpan_makesChildOfCurrent() { Span parent = tracer.newTrace(); - try (SpanInScope scope = tracer.withSpanInScope(parent)) { + try (SpanInScope ws = tracer.withSpanInScope(parent)) { assertThat(tracer.nextSpan().context().parentId()) .isEqualTo(parent.context().spanId()); } @@ -458,7 +500,7 @@ public class TracerTest { TraceContextOrSamplingFlags extracted = TraceContextOrSamplingFlags.newBuilder(EMPTY).addExtra(1L).build(); - try (SpanInScope scope = tracer.withSpanInScope(parent)) { + try (SpanInScope ws = tracer.withSpanInScope(parent)) { assertThat(tracer.nextSpan(extracted).context().extra()) .containsExactly(1L); } @@ -474,7 +516,7 @@ public class TracerTest { .addExtra(1F) .build(); - try (SpanInScope scope = tracer.withSpanInScope(parent)) { + try (SpanInScope ws = tracer.withSpanInScope(parent)) { assertThat(tracer.nextSpan(extracted).context().extra()) .containsExactlyInAnyOrder(1L, 1F); } @@ -553,7 +595,7 @@ void assertRealRoot(ScopedSpan current) { @Test void withSpanInScope() { Span current = tracer.newTrace(); - try (SpanInScope scope = tracer.withSpanInScope(current)) { + try (SpanInScope ws = tracer.withSpanInScope(current)) { assertThat(tracer.currentSpan()) .isEqualTo(current); assertThat(tracer.currentSpanCustomizer()) @@ -565,9 +607,24 @@ void assertRealRoot(ScopedSpan current) { assertThat(tracer.currentSpan()).isNull(); } + @Test void withNoopSpanInScope() { + Span current = tracer.withSampler(Sampler.NEVER_SAMPLE).nextSpan(); + + try (SpanInScope ws = tracer.withSpanInScope(current)) { + assertThat(tracer.currentSpan()) + .isEqualTo(current); + assertThat(tracer.currentSpanCustomizer()) + .isNotEqualTo(current) + .isEqualTo(NoopSpanCustomizer.INSTANCE); + } + + // context was cleared + assertThat(tracer.currentSpan()).isNull(); + } + @Test void toString_withSpanInScope() { TraceContext context = TraceContext.newBuilder().traceId(1L).spanId(10L).sampled(true).build(); - try (SpanInScope scope = tracer.withSpanInScope(tracer.toSpan(context))) { + try (SpanInScope ws = tracer.withSpanInScope(tracer.toSpan(context))) { assertThat(tracer.toString()).hasToString( "Tracer{currentSpan=0000000000000001/000000000000000a, spanHandler=[TestSpanHandler{[]}, OrphanTracker{}]}" ); @@ -582,6 +639,29 @@ void assertRealRoot(ScopedSpan current) { ); } + @Test void toString_withSpanReporter() { + tracer = Tracing.newBuilder().addSpanHandler(new SpanHandler() { + @Override public boolean end(TraceContext context, MutableSpan span, Cause cause) { + return true; + } + + @Override public String toString() { + return "MyHandler"; + } + }).spanReporter(new Reporter() { + @Override public void report(zipkin2.Span span) { + } + + @Override public String toString() { + return "MyReporter"; + } + }).build().tracer(); + + assertThat(tracer).hasToString( + "Tracer{spanHandler=[MyHandler, MyReporter]}" + ); + } + @Test void withSpanInScope_nested() { Span parent = tracer.newTrace(); @@ -715,8 +795,8 @@ void assertRealRoot(ScopedSpan current) { } @Test void useSpanAfterFinishedInOtherTracer_doesNotCauseBraveFlush() { - Tracer noOpTracer = Tracing.newBuilder().addSpanHandler(new SpanHandler() { - // intentionally avoid any special-casing of SpanHandler.NOOP + Tracer noOpTracer = Tracing.newBuilder().spanReporter(s -> { + // intentionally avoid any special-casing of Reporter.NOOP }).build().tracer(); simulateInProcessPropagation(noOpTracer, tracer); GarbageCollectors.blockOnGC(); @@ -1030,7 +1110,7 @@ private static void simulateInProcessPropagation(Tracer tracer1, Tracer tracer2) @Test void currentSpan_sameContextReference() { Span span = tracer.newTrace(); - try (SpanInScope scope = tracer.withSpanInScope(span)) { + try (SpanInScope ws = tracer.withSpanInScope(span)) { assertThat(tracer.currentSpan().context()) .isSameAs(span.context()); } diff --git a/brave/src/test/java/brave/TracingTest.java b/brave/src/test/java/brave/TracingTest.java index 21ae8afc0c..00a540c5b2 100644 --- a/brave/src/test/java/brave/TracingTest.java +++ b/brave/src/test/java/brave/TracingTest.java @@ -13,22 +13,28 @@ */ package brave; +import brave.handler.FinishedSpanHandler; import brave.handler.MutableSpan; import brave.handler.SpanHandler; import brave.internal.Platform; -import brave.propagation.B3Propagation; +import brave.propagation.B3SinglePropagation; import brave.propagation.Propagation; import brave.propagation.StrictCurrentTraceContext; import brave.propagation.TraceContext; import brave.sampler.Sampler; import brave.test.TestSpanHandler; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; +import zipkin2.reporter.Reporter; + import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class TracingTest { TestSpanHandler spans = new TestSpanHandler(); @@ -82,6 +88,30 @@ class TracingTest { .isEqualTo(expectedLocalServiceName); } + @Test void spanReporter_getsLocalEndpointInfo() { + String expectedLocalServiceName = "favistar", expectedLocalIp = "1.2.3.4"; + int expectedLocalPort = 80; + + List zipkinSpans = new ArrayList<>(); + Reporter spanReporter = span -> { + assertThat(span.localServiceName()).isEqualTo(expectedLocalServiceName); + assertThat(span.localEndpoint().ipv4()).isEqualTo(expectedLocalIp); + assertThat(span.localEndpoint().portAsInt()).isEqualTo(expectedLocalPort); + zipkinSpans.add(span); + }; + + try (Tracing tracing = Tracing.newBuilder() + .localServiceName(expectedLocalServiceName) + .localIp(expectedLocalIp) + .localPort(expectedLocalPort) + .spanReporter(spanReporter) + .build()) { + tracing.tracer().newTrace().start().finish(); + } + + assertThat(zipkinSpans).isNotEmpty(); // ensures the assertions passed. + } + @Test void spanHandler_loggingByDefault() { try (Tracing tracing = Tracing.newBuilder().build()) { assertThat((Object) tracing.tracer().pendingSpans).extracting("spanHandler.delegate") @@ -143,6 +173,7 @@ class TracingTest { try (Tracing tracing = Tracing.newBuilder() .addSpanHandler(spans) .sampler(Sampler.NEVER_SAMPLE) + .alwaysReportSpans() .build()) { tracing.tracer().toSpan(sampledLocal).start().finish(); } @@ -150,6 +181,27 @@ class TracingTest { assertThat(spans).isNotEmpty(); } + @Test void spanHandler_dataChangesVisibleToZipkin() { + String serviceNameOverride = "favistar"; + + SpanHandler spanHandler = new SpanHandler() { + @Override public boolean end(TraceContext context, MutableSpan span, Cause cause) { + span.localServiceName(serviceNameOverride); + return true; + } + }; + + List zipkinSpans = new ArrayList<>(); + try (Tracing tracing = Tracing.newBuilder() + .spanReporter(zipkinSpans::add) + .addSpanHandler(spanHandler) + .build()) { + tracing.tracer().newTrace().start().finish(); + } + + assertThat(zipkinSpans.get(0).localServiceName()).isEqualTo(serviceNameOverride); + } + @Test void spanHandler_recordsWhenSampled() { try (Tracing tracing = Tracing.newBuilder() .addSpanHandler(spans) @@ -191,8 +243,8 @@ class TracingTest { AtomicBoolean sampledLocal = new AtomicBoolean(); try (Tracing tracing = Tracing.newBuilder() .propagationFactory(new Propagation.Factory() { - public Propagation get() { - return B3Propagation.FACTORY.get(); + @Override public Propagation get() { + return B3SinglePropagation.FACTORY.get(); } @Override public TraceContext decorate(TraceContext context) { @@ -211,6 +263,25 @@ public Propagation get() { assertThat(spans.get(0).name()).isEqualTo("one"); } + /** + * This ensures deprecated overloads of {@link FinishedSpanHandler#alwaysSampleLocal()} are + * considered. + */ + @Test void finishedSpanHandler_affectsAlwaysSampleLocal() { + FinishedSpanHandler one = mock(FinishedSpanHandler.class); + when(one.alwaysSampleLocal()).thenReturn(false); + FinishedSpanHandler two = mock(FinishedSpanHandler.class); + when(two.alwaysSampleLocal()).thenReturn(true); + + try (Tracing tracing = Tracing.newBuilder() + .addSpanHandler(one) + .addSpanHandler(two) + .build()) { + + assertThat(tracing.tracer().alwaysSampleLocal).isTrue(); + } + } + @Test void spanHandlers_clearAndAdd() { SpanHandler one = mock(SpanHandler.class); SpanHandler two = mock(SpanHandler.class); diff --git a/brave/src/test/java/brave/baggage/BaggageFieldTest.java b/brave/src/test/java/brave/baggage/BaggageFieldTest.java index 95cc9b9a5e..69baa38511 100644 --- a/brave/src/test/java/brave/baggage/BaggageFieldTest.java +++ b/brave/src/test/java/brave/baggage/BaggageFieldTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -16,8 +16,8 @@ import brave.Tracing; import brave.baggage.BaggagePropagationConfig.SingleBaggageField; import brave.internal.baggage.BaggageContext; -import brave.internal.baggage.BaggageFields; import brave.internal.baggage.ExtraBaggageContext; +import brave.internal.baggage.BaggageFields; import brave.propagation.B3Propagation; import brave.propagation.CurrentTraceContext.Scope; import brave.propagation.Propagation; @@ -62,12 +62,40 @@ class BaggageFieldTest { .isSameAs(context); } + @Test void getAll_extracted() { + assertThat(BaggageField.getAll(emptyExtraction)) + .containsExactly(REQUEST_ID, AMZN_TRACE_ID) + .containsExactlyElementsOf(BaggageField.getAll(extraction)); + } + + @Test void getAll() { + assertThat(BaggageField.getAll(emptyContext)) + .containsExactly(REQUEST_ID, AMZN_TRACE_ID); + + try (Tracing tracing = Tracing.newBuilder().build(); + Scope ws = tracing.currentTraceContext().newScope(emptyContext)) { + assertThat(BaggageField.getAll()) + .containsExactly(REQUEST_ID, AMZN_TRACE_ID); + } + } + + @Test void getAll_doesntExist() { + assertThat(BaggageField.getAll(TraceContextOrSamplingFlags.EMPTY)).isEmpty(); + assertThat(BaggageField.getAll(context)).isEmpty(); + assertThat(BaggageField.getAll()).isEmpty(); + + try (Tracing tracing = Tracing.newBuilder().build(); + Scope ws = tracing.currentTraceContext().newScope(null)) { + assertThat(BaggageField.getAll()).isEmpty(); + } + } + @Test void getByName_doesntExist() { assertThat(BaggageField.getByName(emptyContext, "robots")).isNull(); assertThat(BaggageField.getByName("robots")).isNull(); try (Tracing tracing = Tracing.newBuilder().build(); - Scope scope = tracing.currentTraceContext().newScope(null)) { + Scope ws = tracing.currentTraceContext().newScope(null)) { assertThat(BaggageField.getByName(REQUEST_ID.name())).isNull(); } } @@ -77,7 +105,7 @@ class BaggageFieldTest { .isSameAs(REQUEST_ID); try (Tracing tracing = Tracing.newBuilder().build(); - Scope scope = tracing.currentTraceContext().newScope(emptyContext)) { + Scope ws = tracing.currentTraceContext().newScope(emptyContext)) { assertThat(BaggageField.getByName(REQUEST_ID.name())) .isSameAs(REQUEST_ID); } diff --git a/brave/src/test/java/brave/baggage/BaggagePropagationTest.java b/brave/src/test/java/brave/baggage/BaggagePropagationTest.java index 744eabaae2..76c1f9d8f8 100644 --- a/brave/src/test/java/brave/baggage/BaggagePropagationTest.java +++ b/brave/src/test/java/brave/baggage/BaggagePropagationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -19,6 +19,7 @@ import brave.propagation.B3Propagation; import brave.propagation.B3SingleFormat; import brave.propagation.B3SinglePropagation; +import brave.propagation.ExtraFieldPropagation; import brave.propagation.Propagation; import brave.propagation.TraceContext; import brave.propagation.TraceContext.Extractor; @@ -322,4 +323,12 @@ public class BaggagePropagationTest { assertThat(BaggagePropagation.allKeyNames(factory.get())) .containsExactly("b3"); } + + @Test void allKeyNames_extraFieldPropagation() { + ExtraFieldPropagation.Factory factory = + ExtraFieldPropagation.newFactory(B3SinglePropagation.FACTORY, "user-id", "session-id"); + + assertThat(BaggagePropagation.allKeyNames(factory.get())) + .containsExactly("b3", "user-id", "session-id"); + } } diff --git a/brave/src/test/java/brave/baggage/CorrelationScopeDecoratorTest.java b/brave/src/test/java/brave/baggage/CorrelationScopeDecoratorTest.java index 8cced6c32e..bca97a4741 100644 --- a/brave/src/test/java/brave/baggage/CorrelationScopeDecoratorTest.java +++ b/brave/src/test/java/brave/baggage/CorrelationScopeDecoratorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -24,6 +24,7 @@ import brave.propagation.Propagation; import brave.propagation.TraceContext; import java.util.BitSet; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -251,26 +252,26 @@ void decoratesNoop_dirtyField() { FIELD.baggageField().updateValue(contextWithBaggage, "romeo"); map.put(FIELD.name(), "romeo"); - try (Scope scope = onlyScopeDecorator.decorateScope(null, Scope.NOOP)) { + try (Scope s = onlyScopeDecorator.decorateScope(null, Scope.NOOP)) { assertThat(map).isEmpty(); - scope.close(); + s.close(); assertThat(map).isNotEmpty(); map.clear(); - scope.close(); + s.close(); assertThat(map).isEmpty(); // didn't revert again } map.put("flushed", "excel"); FLUSH_FIELD.baggageField().updateValue(contextWithBaggage, "excel"); - try (Scope scope = onlyFlushOnUpdateScopeDecorator.decorateScope(null, mock(Scope.class))) { + try (Scope s = onlyFlushOnUpdateScopeDecorator.decorateScope(null, mock(Scope.class))) { assertThat(map).isEmpty(); - scope.close(); + s.close(); assertThat(map).isNotEmpty(); map.clear(); - scope.close(); + s.close(); assertThat(map).isEmpty(); // didn't revert again } } @@ -283,26 +284,26 @@ void decoratesNoop_dirtyField() { map.put(LOCAL_FIELD.name(), "abcd"); map.put(FIELD.name(), "romeo"); - try (Scope scope = withBaggageFieldsDecorator.decorateScope(null, Scope.NOOP)) { + try (Scope s = withBaggageFieldsDecorator.decorateScope(null, Scope.NOOP)) { assertThat(map).isEmpty(); - scope.close(); + s.close(); assertThat(map).isNotEmpty(); map.clear(); - scope.close(); + s.close(); assertThat(map).isEmpty(); // didn't revert again } map.put("flushed", "excel"); FLUSH_FIELD.baggageField().updateValue(contextWithBaggage, "excel"); - try (Scope scope = withFlushOnUpdateScopeDecorator.decorateScope(null, mock(Scope.class))) { + try (Scope s = withFlushOnUpdateScopeDecorator.decorateScope(null, mock(Scope.class))) { assertThat(map).isEmpty(); - scope.close(); + s.close(); assertThat(map).isNotEmpty(); map.clear(); - scope.close(); + s.close(); assertThat(map).isEmpty(); // didn't revert again } } @@ -316,29 +317,29 @@ void decoratesNoop_dirtyField() { map.put(FIELD_2.name(), "FO"); map.put(LOCAL_FIELD.name(), "abcd"); - try (Scope scope = withBaggageFieldsDecorator.decorateScope(null, Scope.NOOP)) { + try (Scope s = withBaggageFieldsDecorator.decorateScope(null, Scope.NOOP)) { assertThat(map).isEmpty(); } - try (Scope scope = onlyScopeDecorator.decorateScope(null, Scope.NOOP)) { + try (Scope s = onlyScopeDecorator.decorateScope(null, Scope.NOOP)) { assertThat(map).doesNotContainKey(FIELD.name()); } FLUSH_FIELD.baggageField().updateValue(contextWithBaggage, "excel"); map.put("flushed", "excel"); - try (Scope scope = withFlushOnUpdateScopeDecorator.decorateScope(null, Scope.NOOP)) { + try (Scope s = withFlushOnUpdateScopeDecorator.decorateScope(null, Scope.NOOP)) { assertThat(map).isEmpty(); } - try (Scope scope = onlyFlushOnUpdateScopeDecorator.decorateScope(null, Scope.NOOP)) { + try (Scope s = onlyFlushOnUpdateScopeDecorator.decorateScope(null, Scope.NOOP)) { assertThat(map).doesNotContainKey("flushed"); } map.clear(); } @Test void addsAndRemoves() { - try (Scope scope = decorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + try (Scope s = decorator.decorateScope(contextWithBaggage, mock(Scope.class))) { assertThat(map).containsOnly( entry("traceId", "0000000000000001"), entry("spanId", "0000000000000003") @@ -348,7 +349,7 @@ void decoratesNoop_dirtyField() { } @Test void addsAndRemoves_onlyTraceId() { - try (Scope scope = onlyTraceIdDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + try (Scope s = onlyTraceIdDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { assertThat(map).containsOnly(entry("X-B3-TraceId", "0000000000000001")); } assertThat(map.isEmpty()); @@ -356,14 +357,14 @@ void decoratesNoop_dirtyField() { @Test void addsAndRemoves_onlyBaggageField() { FIELD.baggageField().updateValue(contextWithBaggage, "romeo"); - try (Scope scope = onlyScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + try (Scope s = onlyScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { assertThat(map).containsOnly(entry(FIELD.name(), "romeo")); } assertThat(map.isEmpty()); FLUSH_FIELD.baggageField().updateValue(contextWithBaggage, "excel"); try ( - Scope scope = onlyFlushOnUpdateScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + Scope s = onlyFlushOnUpdateScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { assertThat(map).containsOnly(entry("flushed", "excel")); } @@ -375,7 +376,7 @@ void decoratesNoop_dirtyField() { FIELD_2.baggageField().updateValue(contextWithBaggage, "FO"); LOCAL_FIELD.baggageField().updateValue(contextWithBaggage, "abcd"); - try (Scope scope = withBaggageFieldsDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + try (Scope s = withBaggageFieldsDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { assertThat(map).containsOnly( entry("X-B3-TraceId", "0000000000000001"), entry(FIELD.name(), "romeo"), @@ -387,7 +388,7 @@ void decoratesNoop_dirtyField() { FLUSH_FIELD.baggageField().updateValue(contextWithBaggage, "excel"); try ( - Scope scope = withFlushOnUpdateScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + Scope s = withFlushOnUpdateScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { assertThat(map).containsOnly( entry("traceId", "0000000000000001"), entry("spanId", "0000000000000003"), @@ -405,7 +406,7 @@ void decoratesNoop_dirtyField() { map.put("spanId", "000000000000000c"); Map snapshot = new LinkedHashMap<>(map); - try (Scope scope = decorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + try (Scope s = decorator.decorateScope(contextWithBaggage, mock(Scope.class))) { assertThat(map).containsOnly( entry("traceId", "0000000000000001"), entry("spanId", "0000000000000003") @@ -420,7 +421,7 @@ void decoratesNoop_dirtyField() { map.put("X-B3-TraceId", "000000000000000a"); Map snapshot = new LinkedHashMap<>(map); - try (Scope scope = onlyTraceIdDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + try (Scope s = onlyTraceIdDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { assertThat(map).containsOnly(entry("X-B3-TraceId", "0000000000000001")); } @@ -433,7 +434,7 @@ void decoratesNoop_dirtyField() { Map snapshot = new LinkedHashMap<>(map); FIELD.baggageField().updateValue(contextWithBaggage, "romeo"); - try (Scope scope = onlyScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + try (Scope s = onlyScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { assertThat(map).containsOnly(entry(FIELD.name(), "romeo")); } @@ -452,7 +453,7 @@ void decoratesNoop_dirtyField() { FIELD_2.baggageField().updateValue(contextWithBaggage, "FO"); LOCAL_FIELD.baggageField().updateValue(contextWithBaggage, "abcd"); - try (Scope scope = withBaggageFieldsDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + try (Scope s = withBaggageFieldsDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { assertThat(map).containsOnly( entry("X-B3-TraceId", "0000000000000001"), entry(FIELD.name(), "romeo"), @@ -478,7 +479,7 @@ void decoratesNoop_dirtyField() { LOCAL_FIELD.baggageField().updateValue(contextWithBaggage, "abcd"); FLUSH_FIELD.baggageField().updateValue(contextWithBaggage, "excel"); try ( - Scope scope = withFlushOnUpdateScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + Scope s = withFlushOnUpdateScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { assertThat(map).containsOnly( entry("traceId", "0000000000000001"), entry("spanId", "0000000000000003"), @@ -494,7 +495,7 @@ void decoratesNoop_dirtyField() { } @Test void revertsLateChanges() { - try (Scope scope = decorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + try (Scope s = decorator.decorateScope(contextWithBaggage, mock(Scope.class))) { // late changes map.put("traceId", "000000000000000a"); map.put("spanId", "000000000000000c"); @@ -502,7 +503,7 @@ void decoratesNoop_dirtyField() { } @Test void revertsLateChanges_onlyTraceId() { - try (Scope scope = onlyTraceIdDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + try (Scope s = onlyTraceIdDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { // late changes map.put("X-B3-TraceId", "000000000000000a"); } @@ -510,7 +511,7 @@ void decoratesNoop_dirtyField() { @Test void revertsLateChanges_onlyBaggageField() { FIELD.baggageField().updateValue(contextWithBaggage, "romeo"); - try (Scope scope = onlyScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + try (Scope s = onlyScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { // late changes map.put(FIELD.name(), "bob"); } @@ -518,7 +519,7 @@ void decoratesNoop_dirtyField() { FLUSH_FIELD.baggageField().updateValue(contextWithBaggage, "excel"); try ( - Scope scope = onlyFlushOnUpdateScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + Scope s = onlyFlushOnUpdateScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { // late changes map.put("flushed", "word"); } @@ -530,7 +531,7 @@ void decoratesNoop_dirtyField() { FIELD_2.baggageField().updateValue(contextWithBaggage, "FO"); LOCAL_FIELD.baggageField().updateValue(contextWithBaggage, "abcd"); - try (Scope scope = withBaggageFieldsDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + try (Scope s = withBaggageFieldsDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { // late changes map.put("X-B3-TraceId", "000000000000000a"); map.put(FIELD.name(), "bob"); @@ -539,7 +540,7 @@ void decoratesNoop_dirtyField() { FLUSH_FIELD.baggageField().updateValue(contextWithBaggage, "excel"); try ( - Scope scope = withFlushOnUpdateScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + Scope s = withFlushOnUpdateScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { // late changes map.put("flushed", "word"); } @@ -548,7 +549,7 @@ void decoratesNoop_dirtyField() { @Test void ignoresUpdate_onlyBaggageField() { FIELD.baggageField().updateValue(contextWithBaggage, "romeo"); - try (Scope scope = onlyScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + try (Scope s = onlyScopeDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { FIELD.baggageField().updateValue(contextWithBaggage, "bob"); assertThat(map).containsEntry(FIELD.name(), "romeo"); } @@ -566,7 +567,7 @@ void decoratesNoop_dirtyField() { FIELD_2.baggageField().updateValue(contextWithBaggage, "FO"); LOCAL_FIELD.baggageField().updateValue(contextWithBaggage, "abcd"); - try (Scope scope = withBaggageFieldsDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + try (Scope s = withBaggageFieldsDecorator.decorateScope(contextWithBaggage, mock(Scope.class))) { Map snapshot = new LinkedHashMap<>(map); FIELD.baggageField().updateValue(contextWithBaggage, "bob"); assertThat(map).isEqualTo(snapshot); @@ -585,7 +586,7 @@ void decoratesNoop_dirtyField() { } void assertNestedUpdatesCoherent(ScopeDecorator decorator) { - try (Scope scope = decorator.decorateScope(contextWithBaggage, mock(Scope.class))) { + try (Scope s = decorator.decorateScope(contextWithBaggage, mock(Scope.class))) { FLUSH_FIELD.baggageField().updateValue(contextWithBaggage, "word"); try (Scope s1 = decorator.decorateScope(contextWithBaggage, mock(Scope.class))) { assertThat(map).containsEntry("flushed", "word"); diff --git a/brave/src/test/java/brave/features/baggage/SingleHeaderCodec.java b/brave/src/test/java/brave/features/baggage/SingleHeaderCodec.java index 648917cd00..1307534b1e 100644 --- a/brave/src/test/java/brave/features/baggage/SingleHeaderCodec.java +++ b/brave/src/test/java/brave/features/baggage/SingleHeaderCodec.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -46,7 +46,7 @@ static BaggageCodec get() { return keyNames; } - @Override public boolean decode(ValueUpdater valueUpdater, String value) { + @Override public boolean decode(ValueUpdater valueUpdater, Object request, String value) { return ENTRY_SPLITTER.parse(this, valueUpdater, value); } @@ -57,7 +57,7 @@ static BaggageCodec get() { return target.updateValue(field, value); } - @Override public String encode(Map values, TraceContext context) { + @Override public String encode(Map values, TraceContext context, Object request) { StringBuilder result = new StringBuilder(); for (Map.Entry entry : values.entrySet()) { if (result.length() > 0) result.append(','); diff --git a/brave/src/test/java/brave/features/handler/MutableSpanAsyncReporterTest.java b/brave/src/test/java/brave/features/handler/MutableSpanAsyncReporterTest.java index 0679f1a381..eb504aef08 100644 --- a/brave/src/test/java/brave/features/handler/MutableSpanAsyncReporterTest.java +++ b/brave/src/test/java/brave/features/handler/MutableSpanAsyncReporterTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -25,7 +25,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import static java.nio.charset.StandardCharsets.UTF_8; +import zipkin2.codec.SpanBytesDecoder; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -69,15 +70,15 @@ class MutableSpanAsyncReporterTest { .finish(3L); assertThat(messages).hasSize(1).first() - .extracting(b -> new String(b, UTF_8)) - .isEqualTo( + .extracting(SpanBytesDecoder.JSON_V2::decodeOne) + .hasToString( "{\"traceId\":\"50d980fffa300f29\"," + "\"id\":\"86154a4ba6e91385\"," + "\"name\":\"test\"," + "\"timestamp\":1," + "\"duration\":2," + "\"localEndpoint\":{" - + "\"serviceName\":\"Aa\"," + + "\"serviceName\":\"aa\"," + "\"ipv4\":\"1.2.3.4\"," + "\"port\":80}," + "\"tags\":{\"error\":\"this cake is a lie\"}}" diff --git a/brave/src/test/java/brave/features/handler/SpanMetricsCustomizer.java b/brave/src/test/java/brave/features/handler/SpanMetricsCustomizer.java index 39ece16ce3..d618ae13cd 100644 --- a/brave/src/test/java/brave/features/handler/SpanMetricsCustomizer.java +++ b/brave/src/test/java/brave/features/handler/SpanMetricsCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -15,8 +15,8 @@ import brave.Tracing; import brave.TracingCustomizer; -import brave.handler.MutableSpan; import brave.handler.SpanHandler; +import brave.handler.MutableSpan; import brave.propagation.TraceContext; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; diff --git a/brave/src/test/java/brave/features/propagation/CustomTraceIdPropagation.java b/brave/src/test/java/brave/features/propagation/CustomTraceIdPropagation.java index bea2854dc1..2164582627 100644 --- a/brave/src/test/java/brave/features/propagation/CustomTraceIdPropagation.java +++ b/brave/src/test/java/brave/features/propagation/CustomTraceIdPropagation.java @@ -13,6 +13,7 @@ */ package brave.features.propagation; +import brave.internal.propagation.StringPropagationAdapter; import brave.propagation.B3Propagation; import brave.propagation.Propagation; import brave.propagation.TraceContext; @@ -77,6 +78,10 @@ public static Propagation.Factory create(Propagation.Factory delegate, String cu return this; } + @Override public Propagation create(KeyFactory keyFactory) { + return StringPropagationAdapter.create(this, keyFactory); + } + @Override public List keys() { return allKeys; } diff --git a/brave/src/test/java/brave/features/propagation/NonStringPropagationKeysTest.java b/brave/src/test/java/brave/features/propagation/NonStringPropagationKeysTest.java new file mode 100644 index 0000000000..6fd4c00829 --- /dev/null +++ b/brave/src/test/java/brave/features/propagation/NonStringPropagationKeysTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013-2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.features.propagation; + +import brave.propagation.B3Propagation; +import brave.propagation.Propagation; +import brave.propagation.TraceContext; +import io.grpc.Metadata; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** This shows propagation keys don't need to be Strings. For example, we can propagate over gRPC */ +class NonStringPropagationKeysTest { + TraceContext context = TraceContext.newBuilder().traceId(1).spanId(2).sampled(true).build(); + + // Note: This class will be removed in Brave 6.0, but B3Propagation implements create meanwhile. + Propagation> grpcPropagation = B3Propagation.FACTORY.create( + name -> Metadata.Key.of(name, Metadata.ASCII_STRING_MARSHALLER) + ); + TraceContext.Extractor extractor = grpcPropagation.extractor(Metadata::get); + TraceContext.Injector injector = grpcPropagation.injector(Metadata::put); + + @Test void injectExtractTraceContext() { + Metadata metadata = new Metadata(); + injector.inject(context, metadata); + + assertThat(metadata.keys()) + .containsExactly("x-b3-traceid", "x-b3-spanid", "x-b3-sampled"); + + assertThat(extractor.extract(metadata).context()) + .isEqualTo(context); + } +} diff --git a/brave/src/test/java/brave/handler/MutableSpanTest.java b/brave/src/test/java/brave/handler/MutableSpanTest.java index 59b5d8be18..a780e4d67c 100644 --- a/brave/src/test/java/brave/handler/MutableSpanTest.java +++ b/brave/src/test/java/brave/handler/MutableSpanTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -313,6 +313,14 @@ class Tag { assertThat(span.kind()).isNull(); } + @Test void isEmpty() { + assertThat(PERMUTATIONS.get(0).get().isEmpty()).isTrue(); + + for (int i = 1, length = PERMUTATIONS.size(); i < length; i++) { + assertThat(PERMUTATIONS.get(i).get().isEmpty()).isFalse(); + } + } + static final Exception EX1 = new Exception(), EX2 = new Exception(); @Test void equalsOnHashCodeClash() { diff --git a/brave/src/test/java/brave/internal/InternalPropagationTest.java b/brave/src/test/java/brave/internal/InternalPropagationTest.java index 9d87c031e6..fa4ee2dbbc 100644 --- a/brave/src/test/java/brave/internal/InternalPropagationTest.java +++ b/brave/src/test/java/brave/internal/InternalPropagationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -15,6 +15,7 @@ import brave.propagation.SamplingFlags; import brave.propagation.TraceContext; +import java.util.Collections; import org.junit.jupiter.api.Test; import static brave.internal.InternalPropagation.FLAG_SAMPLED; @@ -39,7 +40,7 @@ class InternalPropagationTest { @Test void shallowCopy() { TraceContext context = TraceContext.newBuilder().traceId(1).spanId(2).debug(true) - .addExtra(1L).build(); + .extra(Collections.singletonList(1L)).build(); assertThat(InternalPropagation.instance.shallowCopy(context)) .isNotSameAs(context) diff --git a/brave/src/test/java/brave/internal/propagation/StringPropagationAdapterTest.java b/brave/src/test/java/brave/internal/propagation/StringPropagationAdapterTest.java new file mode 100644 index 0000000000..1b247771a8 --- /dev/null +++ b/brave/src/test/java/brave/internal/propagation/StringPropagationAdapterTest.java @@ -0,0 +1,157 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.internal.propagation; + +import brave.internal.propagation.StringPropagationAdapter.GetterAdapter; +import brave.internal.propagation.StringPropagationAdapter.SetterAdapter; +import brave.propagation.Propagation; +import brave.propagation.Propagation.Getter; +import brave.propagation.Propagation.KeyFactory; +import brave.propagation.Propagation.Setter; +import brave.propagation.TraceContext.Extractor; +import brave.propagation.TraceContext.Injector; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Supplier; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class StringPropagationAdapterTest { + @Mock Propagation delegate; + @Mock Injector> delegateInjector; + @Mock Extractor> delegateExtractor; + + KeyFactory keyFactory = Integer::parseInt; + Setter, Integer> setter = (m, k, v) -> m.put(k, v); + Getter, Integer> getter = (m, k) -> m.get(k); + + @Test void propagation() { + when(delegate.keys()).thenReturn(asList("1", "2")); + + Propagation propagation = StringPropagationAdapter.create(delegate, keyFactory); + + verify(delegate).keys(); + assertThat(propagation.keys()).containsExactly(1, 2); + + Map map = new LinkedHashMap<>(); + map.put("1", 1); + map.put("2", 2); + + assertThat(propagation) + .asInstanceOf(InstanceOfAssertFactories.type(StringPropagationAdapter.class)) + .extracting(spa -> spa.map) + .isEqualTo(map); + + when(delegate.injector(isA(SetterAdapter.class))) + .thenReturn(delegateInjector); + + propagation.injector(setter); + verify(delegate).injector(new SetterAdapter<>(setter, map)); + + when(delegate.extractor(isA(GetterAdapter.class))) + .thenReturn(delegateExtractor); + + propagation.extractor(getter); + verify(delegate).extractor(new GetterAdapter<>(getter, map)); + } + + @Test void getterAdapter() { + Map map = new LinkedHashMap<>(); + map.put("1", 1); + map.put("2", 2); + + Getter, String> wrappedGetter = new GetterAdapter<>(getter, map); + + Map request = new LinkedHashMap<>(); + request.put(1, "one"); + request.put(2, "two"); + + assertThat(wrappedGetter.get(request, "1")).isEqualTo("one"); + assertThat(wrappedGetter.get(request, "2")).isEqualTo("two"); + assertThat(wrappedGetter.get(request, "3")).isNull(); + } + + @Test void setterAdapter() { + Map map = new LinkedHashMap<>(); + map.put("1", 1); + map.put("2", 2); + + Setter, String> wrappedSetter = new SetterAdapter<>(setter, map); + + Map request = new LinkedHashMap<>(); + + wrappedSetter.put(request, "1", "one"); + wrappedSetter.put(request, "2", "two"); + wrappedSetter.put(request, "3", "three"); + + assertThat(request) + .hasSize(2) + .containsEntry(1, "one") + .containsEntry(2, "two"); + } + + @Test void propagation_equalsHashCodeString() { + assertDelegates( + () -> mock(Propagation.class), + p -> StringPropagationAdapter.create(p, keyFactory) + ); + } + + @Test void getter_equalsHashCodeString() { + assertDelegates( + () -> mock(Getter.class), + g -> new StringPropagationAdapter.GetterAdapter<>(g, Collections.emptyMap()) + ); + } + + @Test void setter_equalsHashCodeString() { + assertDelegates( + () -> mock(Setter.class), + g -> new SetterAdapter<>(g, Collections.emptyMap()) + ); + } + + void assertDelegates(Supplier factory, Function wrapperFactory) { + T delegate = factory.get(), wrapper = wrapperFactory.apply(delegate); + + assertThat(wrapper).isEqualTo(delegate); + assertThat(wrapper).hasSameHashCodeAs(delegate); + assertThat(wrapper).hasToString(delegate.toString()); + + T sameDelegate = wrapperFactory.apply(delegate); + assertThat(wrapper).isEqualTo(sameDelegate); + assertThat(sameDelegate).isEqualTo(wrapper); + assertThat(wrapper).hasSameHashCodeAs(sameDelegate); + assertThat(wrapper).hasToString(delegate.toString()); + + T different = wrapperFactory.apply(factory.get()); + assertThat(wrapper).isNotEqualTo(different); + assertThat(different).isNotEqualTo(wrapper); + assertThat(wrapper.hashCode()).isNotEqualTo(different.hashCode()); + assertThat(wrapper.toString()).isNotEqualTo(different.toString()); + } +} diff --git a/brave/src/test/java/brave/internal/recorder/PendingSpansTest.java b/brave/src/test/java/brave/internal/recorder/PendingSpansTest.java index a7ca9f8607..54229c0a4b 100644 --- a/brave/src/test/java/brave/internal/recorder/PendingSpansTest.java +++ b/brave/src/test/java/brave/internal/recorder/PendingSpansTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -34,6 +34,7 @@ import static brave.internal.InternalPropagation.FLAG_LOCAL_ROOT; import static brave.internal.InternalPropagation.FLAG_SAMPLED; import static brave.internal.InternalPropagation.FLAG_SAMPLED_SET; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; class PendingSpansTest { @@ -194,7 +195,7 @@ public boolean begin(TraceContext ctx, MutableSpan span, @Nullable TraceContext } @Test void orphanContext_dropsExtra() { - TraceContext context1 = context.toBuilder().addExtra(1).addExtra(true).build(); + TraceContext context1 = context.toBuilder().extra(asList(1, true)).build(); TraceContext context = this.context.toBuilder().build(); pendingSpans.getOrCreate(null, context, false).state().tag("foo", "bar"); // We drop the reference to the context, which means the next GC should attempt to flush it diff --git a/brave/src/test/java/brave/propagation/B3PropagationTest.java b/brave/src/test/java/brave/propagation/B3PropagationTest.java index 0f55c00e51..cf03b0db88 100644 --- a/brave/src/test/java/brave/propagation/B3PropagationTest.java +++ b/brave/src/test/java/brave/propagation/B3PropagationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -23,6 +23,7 @@ import java.util.Map; import java.util.stream.Stream; import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; diff --git a/brave/src/test/java/brave/propagation/ExtraFieldPropagationTest.java b/brave/src/test/java/brave/propagation/ExtraFieldPropagationTest.java new file mode 100644 index 0000000000..f8da9c17ae --- /dev/null +++ b/brave/src/test/java/brave/propagation/ExtraFieldPropagationTest.java @@ -0,0 +1,447 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.propagation; + +import brave.Tracing; +import brave.baggage.BaggageField; +import brave.baggage.BaggagePropagationTest; +import brave.propagation.CurrentTraceContext.Scope; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static brave.propagation.ExtraFieldPropagation.newFactoryBuilder; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.entry; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * This has a lot of repetition with {@link BaggagePropagationTest} to ensure we don't break old + * signatures + */ +class ExtraFieldPropagationTest { + String awsTraceId = + "Root=1-67891233-abcdef012345678912345678;Parent=463ac35c9f6413ad;Sampled=1"; + String uuid = "f4308d05-2228-4468-80f6-92a8377ba193"; + ExtraFieldPropagation.Factory factory = ExtraFieldPropagation.newFactory( + B3SinglePropagation.FACTORY, "x-vcap-request-id", "x-amzn-trace-id" + ); + + Map request = new LinkedHashMap<>(); + TraceContext.Injector> injector; + TraceContext.Extractor> extractor; + TraceContext context; + + @BeforeEach void initialize() { + injector = factory.get().injector(Map::put); + extractor = factory.get().extractor(Map::get); + context = factory.decorate(TraceContext.newBuilder() + .traceId(1L) + .spanId(2L) + .sampled(true) + .build()); + } + + /** + * Ensure extra fields aren't leaked. This prevents tools from deleting entries when clearing a + * trace. + */ + @Test void keysDontIncludeExtra() { + assertThat(factory.get().keys()) + .isEqualTo(Propagation.B3_SINGLE_STRING.keys()); + } + + /** + * Ensures OpenTracing 0.31 can read the extra keys, as its TextMap has no get by name function. + */ + @Test void extraKeysDontIncludeTraceContextKeys() { + assertThat(factory.get().extraKeys()) + .containsExactly("x-vcap-request-id", "x-amzn-trace-id"); + } + + @Test void downcasesNames() { + ExtraFieldPropagation.Factory factory = + ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "X-FOO"); + assertThat(factory.extraKeyNames).containsExactly("x-foo"); + } + + @Test void trimsNames() { + ExtraFieldPropagation.Factory factory = + ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, " x-foo "); + assertThat(factory.extraKeyNames).containsExactly("x-foo"); + } + + @Test void rejectsNull() { + assertThrows(NullPointerException.class, () -> { + ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "x-me", null); + }); + } + + @Test void rejectsEmpty() { + assertThrows(IllegalArgumentException.class, () -> { + ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "x-me", " "); + }); + } + + @Test void get() { + TraceContext context = extractWithAmazonTraceId(); + + assertThat(ExtraFieldPropagation.get(context, "x-amzn-trace-id")) + .isEqualTo(awsTraceId); + } + + @Test void get_null_if_not_extraField() { + assertThat(ExtraFieldPropagation.get(context, "x-amzn-trace-id")) + .isNull(); + } + + @Test void current_get() { + TraceContext context = extractWithAmazonTraceId(); + + try (Tracing t = Tracing.newBuilder().propagationFactory(factory).build(); + Scope scope = t.currentTraceContext().newScope(context)) { + assertThat(ExtraFieldPropagation.get("x-amzn-trace-id")) + .isEqualTo(awsTraceId); + } + } + + @Test void emptyFields_disallowed() { + assertThatThrownBy(() -> ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "")) + .hasMessage("fieldName is empty"); + + assertThatThrownBy(() -> ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, asList(""))) + .hasMessage("fieldName is empty"); + + assertThatThrownBy(() -> newFactoryBuilder(B3Propagation.FACTORY).addField("").build()) + .hasMessage("fieldName is empty"); + + assertThatThrownBy(() -> newFactoryBuilder(B3Propagation.FACTORY).addRedactedField("").build()) + .hasMessage("fieldName is empty"); + + assertThatThrownBy( + () -> newFactoryBuilder(B3Propagation.FACTORY).addPrefixedFields("foo", asList("")).build()) + .hasMessage("fieldName is empty"); + } + + // We formerly enforced presence of field names in the factory's factory method + @Test void noFields_newFactory_disallowed() { + assertThatThrownBy(() -> ExtraFieldPropagation.newFactory(B3Propagation.FACTORY)) + .hasMessage("no field names"); + + assertThatThrownBy(() -> ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, asList())) + .hasMessage("no field names"); + } + + // We formerly accepted .build() when no fields were present + @Test void noFields_newFactoryBuilder_wrapsDelegate() { + factory = newFactoryBuilder(B3Propagation.FACTORY).build(); + initialize(); + + // check nothing throws on no-op + ExtraFieldPropagation.set(context, "userid", "bob"); + assertThat(ExtraFieldPropagation.get(context, "userid")).isNull(); + + assertThat(extractor.extract(Collections.emptyMap()).extra()) + .isEmpty(); + + injector.inject(context, request); + TraceContext extractedContext = extractor.extract(request).context(); + assertThat(extractedContext) + .usingRecursiveComparison() + .ignoringFields("spanIdString", "traceIdString") + .isEqualTo(context); + } + + @Test void current_get_null_if_no_current_context() { + try (Tracing t = Tracing.newBuilder().propagationFactory(factory).build()) { + assertThat(ExtraFieldPropagation.get("x-amzn-trace-id")) + .isNull(); + } + } + + @Test void current_get_null_if_nothing_current() { + assertThat(ExtraFieldPropagation.get("x-amzn-trace-id")) + .isNull(); + } + + @Test void current_set() { + try (Tracing t = Tracing.newBuilder().propagationFactory(factory).build(); + Scope scope = t.currentTraceContext().newScope(context)) { + ExtraFieldPropagation.set("x-amzn-trace-id", awsTraceId); + + assertThat(ExtraFieldPropagation.get("x-amzn-trace-id")) + .isEqualTo(awsTraceId); + } + } + + @Test void current_set_noop_if_no_current_context() { + try (Tracing t = Tracing.newBuilder().propagationFactory(factory).build()) { + ExtraFieldPropagation.set("x-amzn-trace-id", awsTraceId); // doesn't throw + } + } + + @Test void current_set_noop_if_nothing_current() { + ExtraFieldPropagation.set("x-amzn-trace-id", awsTraceId); // doesn't throw + } + + @Test void inject_extra() { + BaggageField.getByName(context, "x-vcap-request-id").updateValue(context, uuid); + + injector.inject(context, request); + + assertThat(request).containsEntry("x-vcap-request-id", uuid); + } + + @Test void inject_two() { + BaggageField.getByName(context, "x-vcap-request-id").updateValue(context, uuid); + BaggageField.getByName(context, "x-amzn-trace-id").updateValue(context, awsTraceId); + + injector.inject(context, request); + + assertThat(request) + .containsEntry("x-amzn-trace-id", awsTraceId) + .containsEntry("x-vcap-request-id", uuid); + } + + @Test void inject_prefixed() { + factory = newFactoryBuilder(B3Propagation.FACTORY) + .addField("x-vcap-request-id") + .addPrefixedFields("baggage-", asList("country-code")) + .build(); + initialize(); + + BaggageField.getByName(context, "x-vcap-request-id").updateValue(context, uuid); + BaggageField.getByName(context, "country-code").updateValue(context, "FO"); + + injector.inject(context, request); + + assertThat(request) + .containsEntry("baggage-country-code", "FO") + .containsEntry("x-vcap-request-id", uuid); + } + + @Test void extract_extra() { + injector.inject(context, request); + request.put("x-amzn-trace-id", awsTraceId); + + TraceContextOrSamplingFlags extracted = extractor.extract(request); + assertThat(extracted.context().toBuilder().extra(Collections.emptyList()).build()) + .isEqualTo(context); + assertThat(extracted.context().extra()) + .hasSize(2); + + assertThat(BaggageField.getByName(extracted, "x-amzn-trace-id").getValue(extracted)) + .isEqualTo(awsTraceId); + } + + @Test void extract_two() { + injector.inject(context, request); + request.put("x-amzn-trace-id", awsTraceId); + request.put("x-vcap-request-id", uuid); + + TraceContextOrSamplingFlags extracted = extractor.extract(request); + assertThat(extracted.context().toBuilder().extra(Collections.emptyList()).build()) + .isEqualTo(context); + assertThat(extracted.context().extra()) + .hasSize(2); + + assertThat(BaggageField.getByName(extracted, "x-amzn-trace-id").getValue(extracted)) + .isEqualTo(awsTraceId); + assertThat(BaggageField.getByName(extracted, "x-vcap-request-id").getValue(extracted)) + .isEqualTo(uuid); + } + + @Test void extract_prefixed() { + factory = newFactoryBuilder(B3Propagation.FACTORY) + .addField("x-vcap-request-id") + .addPrefixedFields("baggage-", asList("country-code")) + .build(); + initialize(); + + injector.inject(context, request); + request.put("baggage-country-code", "FO"); + request.put("x-vcap-request-id", uuid); + + TraceContextOrSamplingFlags extracted = extractor.extract(request); + assertThat(extracted.context().toBuilder().extra(Collections.emptyList()).build()) + .isEqualTo(context); + assertThat(extracted.context().extra()) + .hasSize(2); + + assertThat(BaggageField.getByName(extracted, "country-code").getValue(extracted)) + .isEqualTo("FO"); + assertThat(BaggageField.getByName(extracted, "x-vcap-request-id").getValue(extracted)) + .isEqualTo(uuid); + } + + @Test void getAll() { + TraceContext context = extractWithAmazonTraceId(); + + assertThat(ExtraFieldPropagation.getAll(context)) + .hasSize(1) + .containsEntry("x-amzn-trace-id", awsTraceId); + } + + @Test void getAll_extracted() { + injector.inject(context, request); + request.put("x-amzn-trace-id", awsTraceId); + + TraceContextOrSamplingFlags extracted = extractor.extract(request); + + assertThat(ExtraFieldPropagation.getAll(extracted)) + .hasSize(1) + .containsEntry("x-amzn-trace-id", awsTraceId); + } + + @Test void getAll_extractedWithContext() { + request.put("x-amzn-trace-id", awsTraceId); + + TraceContextOrSamplingFlags extracted = extractor.extract(request); + + assertThat(ExtraFieldPropagation.getAll(extracted)) + .hasSize(1) + .containsEntry("x-amzn-trace-id", awsTraceId); + } + + @Test void getAll_two() { + injector.inject(context, request); + request.put("x-amzn-trace-id", awsTraceId); + request.put("x-vcap-request-id", uuid); + + context = extractor.extract(request).context(); + + assertThat(ExtraFieldPropagation.getAll(context)) + .hasSize(2) + .containsEntry("x-amzn-trace-id", awsTraceId) + .containsEntry("x-vcap-request-id", uuid); + } + + @Test void getAll_empty_if_no_extraField() { + assertThat(ExtraFieldPropagation.getAll(context)) + .isEmpty(); + } + + @Test void extract_field_multiple_prefixes() { + // switch to case insensitive as this example is about http :P + request = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + factory = newFactoryBuilder(B3Propagation.FACTORY) + .addField("userId") + .addField("sessionId") + .addPrefixedFields("baggage-", asList("userId", "sessionId")) + .addPrefixedFields("baggage_", asList("userId", "sessionId")) + .build(); + initialize(); + + injector.inject(context, request); + request.put("baggage-userId", "bob"); + request.put("baggage-sessionId", "12345"); + + context = extractor.extract(request).context(); + + assertThat(ExtraFieldPropagation.get(context, "userId")) + .isEqualTo("bob"); + assertThat(ExtraFieldPropagation.get(context, "sessionId")) + .isEqualTo("12345"); + } + + @Test void extract_redactedField() { + factory = newFactoryBuilder(B3Propagation.FACTORY) + .addRedactedField("userid") + .addField("sessionid") + .build(); + initialize(); + + injector.inject(context, request); + request.put("userid", "bob"); + request.put("sessionid", "12345"); + + context = extractor.extract(request).context(); + + // Redaction also effects inbound propagation + assertThat(ExtraFieldPropagation.get(context, "userid")) + .isNull(); + assertThat(ExtraFieldPropagation.get(context, "sessionid")) + .isEqualTo("12345"); + } + + /** Redaction prevents named fields from being written downstream. */ + @Test void inject_redactedField() { + factory = newFactoryBuilder(B3Propagation.FACTORY) + .addRedactedField("userid") + .addField("sessionid") + .build(); + initialize(); + + ExtraFieldPropagation.set(context, "userid", "bob"); + ExtraFieldPropagation.set(context, "sessionid", "12345"); + + injector.inject(context, request); + + assertThat(request) + .doesNotContainKey("userid") + .containsEntry("sessionid", "12345"); + } + + @Test void inject_field_multiple_prefixes() { + factory = newFactoryBuilder(B3SinglePropagation.FACTORY) + .addField("userId") + .addField("sessionId") + .addPrefixedFields("baggage-", asList("userId", "sessionId")) + .addPrefixedFields("baggage_", asList("userId", "sessionId")) + .build(); + initialize(); + + ExtraFieldPropagation.set(context, "userId", "bob"); + ExtraFieldPropagation.set(context, "sessionId", "12345"); + + injector.inject(context, request); + + // NOTE: the labels are downcased + assertThat(request).containsOnly( + entry("b3", B3SingleFormat.writeB3SingleFormat(context)), + entry("userid", "bob"), + entry("sessionid", "12345"), + entry("baggage-userid", "bob"), + entry("baggage-sessionid", "12345"), + entry("baggage_userid", "bob"), + entry("baggage_sessionid", "12345") + ); + } + + @Test void deduplicates() { + assertThat(newFactoryBuilder(B3SinglePropagation.FACTORY) + .addField("country-code") + .addPrefixedFields("baggage-", asList("country-code")) + .addPrefixedFields("baggage_", asList("country-code")) + .build()) + .usingRecursiveComparison().isEqualTo( + newFactoryBuilder(B3SinglePropagation.FACTORY) + .addField("country-code").addField("country-code") + .addPrefixedFields("baggage-", asList("country-code", "country-code")) + .addPrefixedFields("baggage_", asList("country-code", "country-code")) + .build() + ); + } + + TraceContext extractWithAmazonTraceId() { + injector.inject(context, request); + request.put("x-amzn-trace-id", awsTraceId); + return extractor.extract(request).context(); + } +} diff --git a/brave/src/test/java/brave/propagation/PropagationConstantsTest.java b/brave/src/test/java/brave/propagation/PropagationConstantsTest.java index e52115dccb..c5e3338c4b 100644 --- a/brave/src/test/java/brave/propagation/PropagationConstantsTest.java +++ b/brave/src/test/java/brave/propagation/PropagationConstantsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/brave/src/test/java/brave/propagation/PropagationFactoryTest.java b/brave/src/test/java/brave/propagation/PropagationFactoryTest.java index 24fed7445e..bf5ec4d2f6 100644 --- a/brave/src/test/java/brave/propagation/PropagationFactoryTest.java +++ b/brave/src/test/java/brave/propagation/PropagationFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -19,7 +19,7 @@ class PropagationFactoryTest { Propagation.Factory factory = new Propagation.Factory() { - @Override public Propagation get() { + @Deprecated @Override public Propagation create(Propagation.KeyFactory keyFactory) { return null; } }; diff --git a/brave/src/test/java/brave/propagation/SamplingFlagsTest.java b/brave/src/test/java/brave/propagation/SamplingFlagsTest.java index 084b7af64f..1cd3c0eb34 100644 --- a/brave/src/test/java/brave/propagation/SamplingFlagsTest.java +++ b/brave/src/test/java/brave/propagation/SamplingFlagsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -23,6 +23,47 @@ import static org.assertj.core.api.Assertions.assertThat; class SamplingFlagsTest { + + @Test void builder_defaultIsEmpty() { + SamplingFlags flags = new SamplingFlags.Builder().build(); + + assertThat(flags).isSameAs(SamplingFlags.EMPTY); + assertThat(flags.sampled()).isNull(); + assertThat(flags.debug()).isFalse(); + } + + @Test void builder_debugImpliesSampled() { + SamplingFlags flags = new SamplingFlags.Builder().debug(true).build(); + + assertThat(flags).isSameAs(SamplingFlags.DEBUG); + assertThat(flags.sampled()).isTrue(); + assertThat(flags.debug()).isTrue(); + } + + @Test void builder_sampled() { + SamplingFlags flags = new SamplingFlags.Builder().sampled(true).build(); + + assertThat(flags).isSameAs(SamplingFlags.SAMPLED); + assertThat(flags.sampled()).isTrue(); + assertThat(flags.debug()).isFalse(); + } + + @Test void builder_notSampled() { + SamplingFlags flags = new SamplingFlags.Builder().sampled(false).build(); + + assertThat(flags).isSameAs(SamplingFlags.NOT_SAMPLED); + assertThat(flags.sampled()).isFalse(); + assertThat(flags.debug()).isFalse(); + } + + @Test void builder_nullSampled() { + SamplingFlags flags = new SamplingFlags.Builder().sampled(true).sampled(null).build(); + + assertThat(flags).isSameAs(SamplingFlags.EMPTY); + assertThat(flags.sampled()).isNull(); + assertThat(flags.debug()).isFalse(); + } + @Test void debug_set_true() { assertThat(SamplingFlags.debug(true, SamplingFlags.EMPTY.flags)) .isEqualTo(SamplingFlags.DEBUG.flags) @@ -62,4 +103,14 @@ class SamplingFlagsTest { .isSameAs(SamplingFlags.DEBUG_SAMPLED_LOCAL) .hasToString("DEBUG|SAMPLED_LOCAL"); } + + @Test void sampledLocal() { + SamplingFlags.Builder flagsBuilder = new SamplingFlags.Builder(); + flagsBuilder.flags |= FLAG_SAMPLED_LOCAL; + SamplingFlags flags = flagsBuilder.build(); + + assertThat(flags).isSameAs(SamplingFlags.EMPTY_SAMPLED_LOCAL); + assertThat(flags.sampledLocal()).isTrue(); + assertThat(flags.sampled()).isNull(); + } } diff --git a/brave/src/test/java/brave/propagation/StrictScopeDecoratorTest.java b/brave/src/test/java/brave/propagation/StrictScopeDecoratorTest.java index a277f3719c..e23f7571ac 100644 --- a/brave/src/test/java/brave/propagation/StrictScopeDecoratorTest.java +++ b/brave/src/test/java/brave/propagation/StrictScopeDecoratorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -46,8 +46,8 @@ class StrictScopeDecoratorTest { } @Test void decorator_close_afterCorrectUsage() { - try (Scope scope = currentTraceContext.newScope(null)) { - try (Scope scope2 = currentTraceContext.newScope(context)) { + try (Scope ws = currentTraceContext.newScope(null)) { + try (Scope ws2 = currentTraceContext.newScope(context)) { } } diff --git a/brave/src/test/java/brave/propagation/TraceContextOrSamplingFlagsTest.java b/brave/src/test/java/brave/propagation/TraceContextOrSamplingFlagsTest.java index 0d333223ce..b164e6c299 100644 --- a/brave/src/test/java/brave/propagation/TraceContextOrSamplingFlagsTest.java +++ b/brave/src/test/java/brave/propagation/TraceContextOrSamplingFlagsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -19,6 +19,7 @@ import static brave.propagation.SamplingFlags.EMPTY; import static brave.propagation.SamplingFlags.NOT_SAMPLED; import static brave.propagation.SamplingFlags.SAMPLED; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -360,4 +361,211 @@ void toString( assertThat(extracted).hasToString(toStringWithExtra); } + @Deprecated + @Test void deprecated_create_sampledNullDebugFalse() { + extracted = TraceContextOrSamplingFlags.create(null, false); + assertThat(extracted).isSameAs(TraceContextOrSamplingFlags.EMPTY); + assertThat(extracted.sampled()).isNull(); + assertThat(extracted.samplingFlags()).isSameAs(SamplingFlags.EMPTY); + } + + @Deprecated + @Test void deprecated_create_sampledNullDebugTrue() { + extracted = TraceContextOrSamplingFlags.create(null, true); + assertThat(extracted).isSameAs(TraceContextOrSamplingFlags.DEBUG); + assertThat(extracted.sampled()).isTrue(); + assertThat(extracted.samplingFlags()).isSameAs(SamplingFlags.DEBUG); + } + + @Deprecated + @Test void deprecated_create_sampledTrueDebugFalse() { + extracted = TraceContextOrSamplingFlags.create(true, false); + assertThat(extracted).isSameAs(TraceContextOrSamplingFlags.SAMPLED); + assertThat(extracted.sampled()).isTrue(); + assertThat(extracted.samplingFlags()).isSameAs(SAMPLED); + } + + @Deprecated + @Test void deprecated_create_sampledFalseDebugFalse() { + extracted = TraceContextOrSamplingFlags.create(false, false); + assertThat(extracted).isSameAs(TraceContextOrSamplingFlags.NOT_SAMPLED); + assertThat(extracted.sampled()).isFalse(); + assertThat(extracted.samplingFlags()).isSameAs(SamplingFlags.NOT_SAMPLED); + } + + @Deprecated + @Test void deprecated_sampled_set_null_context() { + extracted = TraceContextOrSamplingFlags.create(context.toBuilder().sampled(null).build()); + assertThat(extracted.sampled(null)).isSameAs(extracted); + + extracted = TraceContextOrSamplingFlags.create(context); + assertThat(extracted.sampled(null).sampled()).isNull(); + assertThat(extracted.sampled(null).context().sampled()).isNull(); + } + + @Deprecated + @Test void deprecated_sampled_set_null_samplingFlags() { + extracted = TraceContextOrSamplingFlags.create(EMPTY); + assertThat(extracted.sampled(null)).isSameAs(extracted); + + extracted = TraceContextOrSamplingFlags.create(SAMPLED); + assertThat(extracted.sampled(null).sampled()).isNull(); + } + + @Deprecated + @Test void deprecated_sampled_set_null_traceIdContext() { + extracted = TraceContextOrSamplingFlags.create(idContext.toBuilder().sampled(null).build()); + assertThat(extracted.sampled(null)).isSameAs(extracted); + + extracted = TraceContextOrSamplingFlags.create(idContext); + assertThat(extracted.sampled(null).sampled()).isNull(); + assertThat(extracted.sampled(null).traceIdContext().sampled()).isNull(); + } + + @Deprecated + @Test void deprecated_sampled_set_null_keepsExtra_context() { + extracted = TraceContextOrSamplingFlags.newBuilder(context).addExtra(1L).build(); + assertThat(extracted.sampled(null).context().extra()).contains(1L); + } + + @Deprecated + @Test void deprecated_sampled_set_null_keepsExtra_samplingFlags() { + extracted = TraceContextOrSamplingFlags.newBuilder(SAMPLED).addExtra(1L).build(); + assertThat(extracted.sampled(null).extra()).contains(1L); + } + + @Deprecated + @Test void deprecated_sampled_set_null_keepsExtra_traceIdContext() { + extracted = TraceContextOrSamplingFlags.newBuilder(idContext).addExtra(1L).build(); + assertThat(extracted.sampled(null).extra()).contains(1L); + } + + @Deprecated + @Test void deprecated_sampled_set_null_keepsSampledLocal_context() { + extracted = TraceContextOrSamplingFlags.newBuilder(context).sampledLocal().build(); + + extracted = extracted.sampled(null); + assertThat(extracted.sampled()).isNull(); + assertThat(extracted.sampledLocal()).isTrue(); + } + + @Deprecated + @Test void deprecated_sampled_set_null_keepsSampledLocal_samplingFlags() { + extracted = TraceContextOrSamplingFlags.newBuilder(SAMPLED).sampledLocal().build(); + + extracted = extracted.sampled(null); + assertThat(extracted.sampled()).isNull(); + assertThat(extracted.sampledLocal()).isTrue(); + } + + @Deprecated + @Test void deprecated_sampled_set_null_keepsSampledLocal_traceIdContext() { + extracted = TraceContextOrSamplingFlags.newBuilder(idContext).sampledLocal().build(); + + extracted = extracted.sampled(null); + assertThat(extracted.sampled()).isNull(); + assertThat(extracted.sampledLocal()).isTrue(); + } + + @Deprecated + @Test void deprecated_builder_invalid() { + TraceContextOrSamplingFlags.Builder builder = TraceContextOrSamplingFlags.newBuilder(); + assertThatThrownBy(builder::build) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Value unset. Use a non-deprecated newBuilder method instead."); + } + + @Deprecated + @Test void deprecated_builder_extraList_context() { + extracted = TraceContextOrSamplingFlags.newBuilder(context).addExtra(3L).build(); + + extracted = extracted.toBuilder().extra(asList(1L, 2L)).build(); + assertThat(extracted.context().extra()).containsExactly(1L, 2L); + assertThat(extracted.extra()).isEmpty(); + + assertThatThrownBy(() -> extracted.context().extra().add(3L)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> extracted.extra().add(3L)) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Deprecated + @Test void deprecated_builder_extraList_samplingFlags() { + extracted = TraceContextOrSamplingFlags.newBuilder(SAMPLED).addExtra(3L).build(); + + extracted = extracted.toBuilder().extra(asList(1L, 2L)).build(); + assertThat(extracted.extra()).containsExactly(1L, 2L); + + assertThatThrownBy(() -> extracted.extra().add(3L)) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Deprecated + @Test void deprecated_builder_extraList_traceIdContext() { + extracted = TraceContextOrSamplingFlags.newBuilder(idContext).addExtra(3L).build(); + + extracted = extracted.toBuilder().extra(asList(1L, 2L)).build(); + assertThat(extracted.extra()).containsExactly(1L, 2L); + + assertThatThrownBy(() -> extracted.extra().add(3L)) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Deprecated + @Test void deprecated_builder_context() { + extracted = TraceContextOrSamplingFlags.newBuilder().context(context).build(); + assertThat(extracted.context()).isSameAs(context); + assertThat(extracted.traceIdContext()).isNull(); + assertThat(extracted.samplingFlags()).isNull(); + } + + @Deprecated + @Test void deprecated_builder_samplingFlags() { + extracted = TraceContextOrSamplingFlags.newBuilder().samplingFlags(SAMPLED).build(); + assertThat(extracted.context()).isNull(); + assertThat(extracted.traceIdContext()).isNull(); + assertThat(extracted.samplingFlags()).isSameAs(SAMPLED); + } + + @Deprecated + @Test void deprecated_builder_traceIdContext() { + extracted = TraceContextOrSamplingFlags.newBuilder().traceIdContext(idContext).build(); + assertThat(extracted.context()).isNull(); + assertThat(extracted.traceIdContext()).isSameAs(idContext); + assertThat(extracted.samplingFlags()).isNull(); + } + + @Deprecated + @Test void deprecated_builder_late_context() { + extracted = TraceContextOrSamplingFlags.newBuilder() + .sampledLocal() + .addExtra(1L) + .context(context).build(); + + assertThat(extracted.sampledLocal()).isTrue(); + assertThat(extracted.context().extra()).containsExactly(1L); + assertThat(extracted.extra()).isEmpty(); + } + + @Deprecated + @Test void deprecated_builder_late_samplingFlags() { + extracted = TraceContextOrSamplingFlags.newBuilder() + .sampledLocal() + .addExtra(1L) + .samplingFlags(EMPTY).build(); + + assertThat(extracted.sampledLocal()).isTrue(); + assertThat(extracted.extra()).containsExactly(1L); + } + + @Deprecated + @Test void deprecated_builder_late_traceIdContext() { + extracted = TraceContextOrSamplingFlags.newBuilder() + .sampledLocal() + .addExtra(1L) + .traceIdContext(idContext).build(); + + assertThat(extracted.sampledLocal()).isTrue(); + assertThat(extracted.extra()).containsExactly(1L); + } } diff --git a/brave/src/test/java/brave/propagation/TraceContextTest.java b/brave/src/test/java/brave/propagation/TraceContextTest.java index 7b0bdfff1e..e9845818aa 100644 --- a/brave/src/test/java/brave/propagation/TraceContextTest.java +++ b/brave/src/test/java/brave/propagation/TraceContextTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -372,7 +372,7 @@ void parseBadTraceId(String traceIdString) { } @Test void withExtra_empty() { - assertThat(context.toBuilder().addExtra(1L).build().withExtra(emptyList())) + assertThat(context.toBuilder().extra(Arrays.asList(1L)).build().withExtra(emptyList())) .extracting("extraList") .isEqualTo(emptyList()); } diff --git a/brave/src/test/java/brave/sampler/DeclarativeSamplerTest.java b/brave/src/test/java/brave/sampler/DeclarativeSamplerTest.java index 27bec869b8..041a69618e 100644 --- a/brave/src/test/java/brave/sampler/DeclarativeSamplerTest.java +++ b/brave/src/test/java/brave/sampler/DeclarativeSamplerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,6 +13,7 @@ */ package brave.sampler; +import brave.propagation.SamplingFlags; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -34,21 +35,21 @@ class DeclarativeSamplerTest { @Test void honorsSampleRate() { declarativeSampler = DeclarativeSampler.createWithRate(Traced::sampleRate); - assertThat(declarativeSampler.trySample(traced(0.0f, 1, true))) - .isTrue(); + assertThat(declarativeSampler.sample(traced(0.0f, 1, true))) + .isEqualTo(SamplingFlags.SAMPLED); - assertThat(declarativeSampler.trySample(traced(0.0f, 0, true))) - .isFalse(); + assertThat(declarativeSampler.sample(traced(0.0f, 0, true))) + .isEqualTo(SamplingFlags.NOT_SAMPLED); } @Test void honorsSampleProbability() { declarativeSampler = DeclarativeSampler.createWithProbability(Traced::sampleProbability); - assertThat(declarativeSampler.trySample(traced(1.0f, 0, true))) - .isTrue(); + assertThat(declarativeSampler.sample(traced(1.0f, 0, true))) + .isEqualTo(SamplingFlags.SAMPLED); - assertThat(declarativeSampler.trySample(traced(0.0f, 0, true))) - .isFalse(); + assertThat(declarativeSampler.sample(traced(0.0f, 0, true))) + .isEqualTo(SamplingFlags.NOT_SAMPLED); } @Test void nullOnNull() { @@ -59,8 +60,8 @@ class DeclarativeSamplerTest { @Test void unmatched() { DeclarativeSampler declarativeSampler = DeclarativeSampler.createWithRate(o -> null); - assertThat(declarativeSampler.trySample(new Object())) - .isNull(); + assertThat(declarativeSampler.sample(new Object())) + .isEqualTo(SamplingFlags.EMPTY); // this decision is cached assertThat(declarativeSampler.methodToSamplers) @@ -68,20 +69,48 @@ class DeclarativeSamplerTest { } @Test void acceptsFallback() { - assertThat(declarativeSampler.trySample(traced(1.0f, 0, false))) - .isNull(); + assertThat(declarativeSampler.sample(traced(1.0f, 0, false))) + .isEqualTo(SamplingFlags.EMPTY); + } + + @Test void toSampler() { + assertThat(declarativeSampler.toSampler(traced(1.0f, 0, true)).isSampled(0L)) + .isTrue(); + + assertThat(declarativeSampler.toSampler(traced(0.0f, 0, true)).isSampled(0L)) + .isFalse(); + + // check not enabled is false + assertThat(declarativeSampler.toSampler(traced(1.0f, 0, false)).isSampled(0L)) + .isFalse(); + } + + @Test void toSampler_fallback() { + Sampler withFallback = + declarativeSampler.toSampler(traced(0.0f, 0, false), Sampler.ALWAYS_SAMPLE); + + assertThat(withFallback.isSampled(0L)) + .isTrue(); + } + + @Test void toSampler_fallback_notUsed() { + Sampler withFallback = + declarativeSampler.toSampler(traced(1.0f, 0, true), Sampler.NEVER_SAMPLE); + + assertThat(withFallback.isSampled(0L)) + .isTrue(); } @Test void samplerLoadsLazy() { assertThat(declarativeSampler.methodToSamplers) .isEmpty(); - declarativeSampler.trySample(traced(1.0f, 0, true)); + declarativeSampler.sample(traced(1.0f, 0, true)); assertThat(declarativeSampler.methodToSamplers) .hasSize(1); - declarativeSampler.trySample(traced(0.0f, 0, true)); + declarativeSampler.sample(traced(0.0f, 0, true)); assertThat(declarativeSampler.methodToSamplers) .hasSize(2); @@ -90,9 +119,9 @@ class DeclarativeSamplerTest { @Test void cardinalityIsPerAnnotationNotInvocation() { Traced traced = traced(1.0f, 0, true); - declarativeSampler.trySample(traced); - declarativeSampler.trySample(traced); - declarativeSampler.trySample(traced); + declarativeSampler.sample(traced); + declarativeSampler.sample(traced); + declarativeSampler.sample(traced); assertThat(declarativeSampler.methodToSamplers) .hasSize(1); diff --git a/brave/src/test/java/brave/sampler/ParameterizedSamplerTest.java b/brave/src/test/java/brave/sampler/ParameterizedSamplerTest.java index 0015139bbe..03dedd3aa4 100644 --- a/brave/src/test/java/brave/sampler/ParameterizedSamplerTest.java +++ b/brave/src/test/java/brave/sampler/ParameterizedSamplerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,6 +13,7 @@ */ package brave.sampler; +import brave.propagation.SamplingFlags; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -24,8 +25,8 @@ class ParameterizedSamplerTest { .putRule(Boolean::booleanValue, Sampler.ALWAYS_SAMPLE) .build(); - assertThat(sampler.trySample(true)) - .isTrue(); + assertThat(sampler.sample(true)) + .isEqualTo(SamplingFlags.SAMPLED); } @Test void emptyOnNoMatch() { @@ -33,8 +34,8 @@ class ParameterizedSamplerTest { .putRule(Boolean::booleanValue, Sampler.ALWAYS_SAMPLE) .build(); - assertThat(sampler.trySample(false)) - .isNull(); + assertThat(sampler.sample(false)) + .isEqualTo(SamplingFlags.EMPTY); } @Test void emptyOnNull() { @@ -42,8 +43,8 @@ class ParameterizedSamplerTest { .putRule(v -> true, Sampler.ALWAYS_SAMPLE) .build(); - assertThat(sampler.trySample(null)) - .isNull(); + assertThat(sampler.sample(null)) + .isEqualTo(SamplingFlags.EMPTY); } @Test void nullOnNull() { @@ -61,8 +62,8 @@ class ParameterizedSamplerTest { .putRule(v -> true, Sampler.NEVER_SAMPLE) // match .build(); - assertThat(sampler.trySample(true)) - .isFalse(); + assertThat(sampler.sample(true)) + .isEqualTo(SamplingFlags.NOT_SAMPLED); } @Test void putAllRules() { diff --git a/context/jfr/src/main/java/brave/context/jfr/JfrScopeDecorator.java b/context/jfr/src/main/java/brave/context/jfr/JfrScopeDecorator.java index 28e02ad965..45e65afba4 100644 --- a/context/jfr/src/main/java/brave/context/jfr/JfrScopeDecorator.java +++ b/context/jfr/src/main/java/brave/context/jfr/JfrScopeDecorator.java @@ -61,6 +61,11 @@ static final class ScopeEvent extends Event { @Label("Span Id") String spanId; } + /** @deprecated since 5.11 use {@link #get()} */ + @Deprecated public static ScopeDecorator create() { + return new JfrScopeDecorator(); + } + @Override public Scope decorateScope(@Nullable TraceContext context, Scope scope) { if (scope == Scope.NOOP) return scope; // we only scope fields constant in the context diff --git a/context/jfr/src/test/java/brave/context/jfr/JfrScopeDecoratorTest.java b/context/jfr/src/test/java/brave/context/jfr/JfrScopeDecoratorTest.java index 2c34f57725..b91525251e 100644 --- a/context/jfr/src/test/java/brave/context/jfr/JfrScopeDecoratorTest.java +++ b/context/jfr/src/test/java/brave/context/jfr/JfrScopeDecoratorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -98,7 +98,7 @@ class JfrScopeDecoratorTest { void makeFiveScopes() throws InterruptedException { CountDownLatch latch = new CountDownLatch(1); - try (Scope scope = currentTraceContext.newScope(context)) { + try (Scope ws = currentTraceContext.newScope(context)) { executor.execute(() -> { try (Scope clear = currentTraceContext.newScope(null)) { } @@ -108,7 +108,7 @@ void makeFiveScopes() throws InterruptedException { }); } - try (Scope scope = currentTraceContext.newScope(context3)) { + try (Scope ws = currentTraceContext.newScope(context3)) { latch.countDown(); shutdownExecutor(); } diff --git a/context/log4j12/src/main/java/brave/context/log4j12/MDCCurrentTraceContext.java b/context/log4j12/src/main/java/brave/context/log4j12/MDCCurrentTraceContext.java new file mode 100644 index 0000000000..2f18e32a51 --- /dev/null +++ b/context/log4j12/src/main/java/brave/context/log4j12/MDCCurrentTraceContext.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.log4j12; + +import brave.internal.Nullable; +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import org.apache.log4j.MDC; + +/** + * Adds {@linkplain MDC} properties "traceId", "parentId" and "spanId" when a {@link + * brave.Tracer#currentSpan() span is current}. These can be used in log correlation. + * + * @deprecated use {@linkplain MDCScopeDecorator}. This will be removed in Brave v6. + */ +@Deprecated +public final class MDCCurrentTraceContext extends CurrentTraceContext { + public static MDCCurrentTraceContext create() { + return create(CurrentTraceContext.Default.inheritable()); + } + + public static MDCCurrentTraceContext create(CurrentTraceContext delegate) { + if (delegate == null) throw new NullPointerException("delegate == null"); + return new Builder(delegate).build(); + } + + static final class Builder extends CurrentTraceContext.Builder { + final CurrentTraceContext delegate; + + Builder(CurrentTraceContext delegate) { + this.delegate = delegate; + addScopeDecorator(MDCScopeDecorator.create()); + } + + @Override public MDCCurrentTraceContext build() { + return new MDCCurrentTraceContext(this); + } + } + + final CurrentTraceContext delegate; + + MDCCurrentTraceContext(Builder builder) { + super(builder); + delegate = builder.delegate; + } + + @Override public TraceContext get() { + return delegate.get(); + } + + @Override public Scope newScope(@Nullable TraceContext context) { + return decorateScope(context, delegate.newScope(context)); + } + + @Override public Scope maybeScope(TraceContext context) { + return decorateScope(context, delegate.maybeScope(context)); + } +} diff --git a/context/log4j12/src/main/java/brave/context/log4j12/MDCScopeDecorator.java b/context/log4j12/src/main/java/brave/context/log4j12/MDCScopeDecorator.java index 6a20745ea6..6f17fb9269 100644 --- a/context/log4j12/src/main/java/brave/context/log4j12/MDCScopeDecorator.java +++ b/context/log4j12/src/main/java/brave/context/log4j12/MDCScopeDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,6 +14,7 @@ package brave.context.log4j12; import brave.baggage.BaggageFields; +import brave.baggage.CorrelationScopeConfig.SingleCorrelationField; import brave.baggage.CorrelationScopeDecorator; import brave.internal.CorrelationContext; import brave.internal.Nullable; @@ -60,6 +61,23 @@ public static CorrelationScopeDecorator.Builder newBuilder() { return new Builder(); } + /** + * Returns a scope decorator that configures {@link BaggageFields#TRACE_ID}, {@link + * BaggageFields#PARENT_ID}, {@link BaggageFields#SPAN_ID} and {@link BaggageFields#SAMPLED} + * + * @since 5.2 + * @deprecated since 5.11 use {@link #get()} or {@link #newBuilder()} + */ + @Deprecated public static CurrentTraceContext.ScopeDecorator create() { + return new Builder() + .clear() + .add(SingleCorrelationField.create(BaggageFields.TRACE_ID)) + .add(SingleCorrelationField.create(BaggageFields.PARENT_ID)) + .add(SingleCorrelationField.create(BaggageFields.SPAN_ID)) + .add(SingleCorrelationField.create(BaggageFields.SAMPLED)) + .build(); + } + static final class Builder extends CorrelationScopeDecorator.Builder { Builder() { super(MDCContext.INSTANCE); diff --git a/context/log4j12/src/test/java/brave/context/log4j12/MDCCurrentTraceContextTest.java b/context/log4j12/src/test/java/brave/context/log4j12/MDCCurrentTraceContextTest.java new file mode 100644 index 0000000000..99cc59f65f --- /dev/null +++ b/context/log4j12/src/test/java/brave/context/log4j12/MDCCurrentTraceContextTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.log4j12; + +import brave.internal.Nullable; +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import brave.test.propagation.CurrentTraceContextTest; +import java.util.function.Supplier; +import org.apache.log4j.MDC; +import org.junit.jupiter.api.Test; + +import static brave.context.log4j12.MDCScopeDecoratorTest.assumeMDCWorks; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class MDCCurrentTraceContextTest extends CurrentTraceContextTest { + public MDCCurrentTraceContextTest() { + assumeMDCWorks(); + } + + @Override protected Class> builderSupplier() { + return BuilderSupplier.class; + } + + static class BuilderSupplier implements Supplier { + @Override public CurrentTraceContext.Builder get() { + return new MDCCurrentTraceContext.Builder(CurrentTraceContext.Default.inheritable()); + } + } + + @Test // Log4J 1.2.x MDC is inheritable by default + public void isnt_inheritable() throws Exception { + assertThrows(AssertionError.class, () -> { + super.isnt_inheritable(); + }); + } + + @Test void is_inheritable() throws Exception { + super.is_inheritable(currentTraceContext); + } + + @Override protected void verifyImplicitContext(@Nullable TraceContext context) { + if (context != null) { + assertThat(MDC.get("traceId")).isEqualTo(context.traceIdString()); + assertThat(MDC.get("spanId")).isEqualTo(context.spanIdString()); + } else { + assertThat(MDC.get("traceId")).isNull(); + assertThat(MDC.get("spanId")).isNull(); + } + } +} + diff --git a/context/log4j2/src/main/java/brave/context/log4j2/ThreadContextCurrentTraceContext.java b/context/log4j2/src/main/java/brave/context/log4j2/ThreadContextCurrentTraceContext.java new file mode 100644 index 0000000000..bdcc9cff5a --- /dev/null +++ b/context/log4j2/src/main/java/brave/context/log4j2/ThreadContextCurrentTraceContext.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.log4j2; + +import brave.internal.Nullable; +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import org.apache.logging.log4j.ThreadContext; + +/** + * Adds {@linkplain ThreadContext} properties "traceId", "parentId" and "spanId" when a {@link + * brave.Tracer#currentSpan() span is current}. These can be used in log correlation. + * + * @deprecated use {@linkplain ThreadContextScopeDecorator}. This will be removed in Brave v6. + */ +@Deprecated +public final class ThreadContextCurrentTraceContext extends CurrentTraceContext { + public static ThreadContextCurrentTraceContext create() { + return create(CurrentTraceContext.Default.inheritable()); + } + + public static ThreadContextCurrentTraceContext create(CurrentTraceContext delegate) { + if (delegate == null) throw new NullPointerException("delegate == null"); + return new Builder(delegate).build(); + } + + static final class Builder extends CurrentTraceContext.Builder { + final CurrentTraceContext delegate; + + Builder(CurrentTraceContext delegate) { + this.delegate = delegate; + addScopeDecorator(ThreadContextScopeDecorator.get()); + } + + @Override public ThreadContextCurrentTraceContext build() { + return new ThreadContextCurrentTraceContext(this); + } + } + + final CurrentTraceContext delegate; + + ThreadContextCurrentTraceContext(Builder builder) { + super(builder); + delegate = builder.delegate; + } + + @Override public TraceContext get() { + return delegate.get(); + } + + @Override public Scope newScope(@Nullable TraceContext context) { + return decorateScope(context, delegate.newScope(context)); + } + + @Override public Scope maybeScope(TraceContext context) { + return decorateScope(context, delegate.maybeScope(context)); + } +} diff --git a/context/log4j2/src/main/java/brave/context/log4j2/ThreadContextScopeDecorator.java b/context/log4j2/src/main/java/brave/context/log4j2/ThreadContextScopeDecorator.java index 2ff7f3b44b..3078717365 100644 --- a/context/log4j2/src/main/java/brave/context/log4j2/ThreadContextScopeDecorator.java +++ b/context/log4j2/src/main/java/brave/context/log4j2/ThreadContextScopeDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,6 +14,7 @@ package brave.context.log4j2; import brave.baggage.BaggageFields; +import brave.baggage.CorrelationScopeConfig.SingleCorrelationField; import brave.baggage.CorrelationScopeDecorator; import brave.internal.CorrelationContext; import brave.internal.Nullable; @@ -60,6 +61,23 @@ public static CorrelationScopeDecorator.Builder newBuilder() { return new Builder(); } + /** + * Returns a scope decorator that configures {@link BaggageFields#TRACE_ID}, {@link + * BaggageFields#PARENT_ID}, {@link BaggageFields#SPAN_ID} and {@link BaggageFields#SAMPLED} + * + * @since 5.2 + * @deprecated since 5.11 use {@link #get()} or {@link #newBuilder()} + */ + @Deprecated public static CurrentTraceContext.ScopeDecorator create() { + return new Builder() + .clear() + .add(SingleCorrelationField.create(BaggageFields.TRACE_ID)) + .add(SingleCorrelationField.create(BaggageFields.PARENT_ID)) + .add(SingleCorrelationField.create(BaggageFields.SPAN_ID)) + .add(SingleCorrelationField.create(BaggageFields.SAMPLED)) + .build(); + } + static final class Builder extends CorrelationScopeDecorator.Builder { Builder() { super(ThreadContextCorrelationContext.INSTANCE); diff --git a/context/log4j2/src/test/java/brave/context/log4j2/ThreadContextCurrentTraceContextTest.java b/context/log4j2/src/test/java/brave/context/log4j2/ThreadContextCurrentTraceContextTest.java new file mode 100644 index 0000000000..c243da3fb0 --- /dev/null +++ b/context/log4j2/src/test/java/brave/context/log4j2/ThreadContextCurrentTraceContextTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.log4j2; + +import brave.internal.Nullable; +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import brave.test.propagation.CurrentTraceContextTest; +import java.util.function.Supplier; +import org.apache.logging.log4j.ThreadContext; + +import static org.assertj.core.api.Assertions.assertThat; + +class ThreadContextCurrentTraceContextTest extends CurrentTraceContextTest { + @Override protected Class> builderSupplier() { + return BuilderSupplier.class; + } + + static class BuilderSupplier implements Supplier { + @Override public CurrentTraceContext.Builder get() { + return new ThreadContextCurrentTraceContext.Builder(CurrentTraceContext.Default.create()); + } + } + + @Override protected void verifyImplicitContext(@Nullable TraceContext context) { + if (context != null) { + assertThat(ThreadContext.get("traceId")).isEqualTo(context.traceIdString()); + assertThat(ThreadContext.get("spanId")).isEqualTo(context.spanIdString()); + } else { + assertThat(ThreadContext.get("traceId")).isNull(); + assertThat(ThreadContext.get("spanId")).isNull(); + } + } +} diff --git a/context/pom.xml b/context/pom.xml index 9c5361a490..cb134b77b7 100644 --- a/context/pom.xml +++ b/context/pom.xml @@ -36,6 +36,7 @@ slf4j log4j12 log4j2 + rxjava2 diff --git a/context/rxjava2/README.md b/context/rxjava2/README.md new file mode 100644 index 0000000000..05823b2003 --- /dev/null +++ b/context/rxjava2/README.md @@ -0,0 +1,45 @@ +# brave-context-rxjava2 + +## Deprecated + +RxJava 2 hasn't been released since Feb 2021. Tracing support will be removed +in Brave v6. + +## Overview + +`CurrentTraceContextAssemblyTracking` prevents traces from breaking +during RxJava operations by scoping trace context that existed +at assembly time around callbacks or computation of new values. + +The design of this library borrows heavily from https://github.com/akaita/RxJava2Debug and https://github.com/akarnokd/RxJava2Extensions + +To set this up, create `CurrentTraceContextAssemblyTracking` using the +current trace context provided by your `Tracing` component. + +```java +contextTracking = CurrentTraceContextAssemblyTracking.create( + tracing.currentTraceContext() +); +``` + +After your application-specific changes to `RxJavaPlugins`, enable trace +context tracking like so: + +```java +contextTracking.enable(); +``` + +Or, if you want to be able to restore any preceding hooks, enable like +this: +```java +SavedHooks hooks = contextTracking.enableAndChain(); + +// then, later you can restore like this +hooks.restore(); +``` + +## Notes on Fusion +Fuseable types, such as `ConditionalSubscriber` and `ScalarCallable` are +not currently supported. Use of these hooks will mask that functionality. + +This is done because the types are internal and subject to code drift. diff --git a/context/rxjava2/bnd.bnd b/context/rxjava2/bnd.bnd new file mode 100644 index 0000000000..6a5cf47b4f --- /dev/null +++ b/context/rxjava2/bnd.bnd @@ -0,0 +1,4 @@ +Import-Package: \ + * +Export-Package: \ + brave.context.rxjava2 diff --git a/context/rxjava2/pom.xml b/context/rxjava2/pom.xml new file mode 100644 index 0000000000..fde283394f --- /dev/null +++ b/context/rxjava2/pom.xml @@ -0,0 +1,99 @@ + + + + + io.zipkin.brave + brave-context-parent + 5.18.1-SNAPSHOT + + 4.0.0 + + brave-context-rxjava2 + Brave Context: RxJava 2 + + + + brave.context.rxjava2 + + ${project.basedir}/../.. + + + + + io.reactivex.rxjava2 + rxjava + 2.2.21 + provided + + + + com.squareup.retrofit2 + adapter-rxjava2 + 2.9.0 + test + + + com.github.akarnokd + rxjava2-extensions + 0.20.10 + test + + + com.squareup.okhttp3 + mockwebserver + ${okhttp.version} + test + + + + + + release + + + 1.6 + 1.6 + 6 + + + + + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + enforce-java + + enforce + + + + + + [11,12) + + + + + + + + + + + diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/CurrentTraceContextAssemblyTracking.java b/context/rxjava2/src/main/java/brave/context/rxjava2/CurrentTraceContextAssemblyTracking.java new file mode 100644 index 0000000000..c0c3b2703c --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/CurrentTraceContextAssemblyTracking.java @@ -0,0 +1,258 @@ +/* + * Copyright 2013-2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2; + +import brave.context.rxjava2.internal.Util; +import brave.context.rxjava2.internal.Wrappers; +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.Completable; +import io.reactivex.Flowable; +import io.reactivex.Maybe; +import io.reactivex.Observable; +import io.reactivex.Single; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.functions.Function; +import io.reactivex.observables.ConnectableObservable; +import io.reactivex.parallel.ParallelFlowable; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Prevents traces from breaking during RxJava operations by scoping trace context that existed at + * assembly time around callbacks or computation of new values. + * + *

The design of this library borrows heavily from https://github.com/akaita/RxJava2Debug and + * https://github.com/akarnokd/RxJava2Extensions + * + * @deprecated RxJava 2 hasn't been released since Feb 2021. Tracing support will be removed in + * Brave v6. + */ +@Deprecated +public final class CurrentTraceContextAssemblyTracking { + public interface SavedHooks { + + /** Restores the previous set of hooks. */ + void restore(); + } + + static volatile boolean enabled; + + final CurrentTraceContext currentTraceContext; + + CurrentTraceContextAssemblyTracking(CurrentTraceContext currentTraceContext) { + if (currentTraceContext == null) throw new NullPointerException("currentTraceContext == null"); + this.currentTraceContext = currentTraceContext; + } + + public static CurrentTraceContextAssemblyTracking create(CurrentTraceContext delegate) { + return new CurrentTraceContextAssemblyTracking(delegate); + } + + /** + * Enable the protocol violation hooks. + * + * @see #enableAndChain() + * @see #disable() + */ + public void enable() { + enable(false); + } + + /** + * Enable the protocol violation hooks by chaining it before any existing hook. + * + * @return the SavedHooks instance that allows restoring the previous assembly hook handlers + * overridden by this method + * @see #enable() + */ + public SavedHooks enableAndChain() { + return enable(true); + } + + @SuppressWarnings("rawtypes") + SavedHooks enable(boolean chain) { + // ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo + + final Function saveC = + RxJavaPlugins.getOnCompletableAssembly(); + Function oldCompletable = saveC; + if (oldCompletable == null || !chain) oldCompletable = Util.identity(); + + RxJavaPlugins.setOnCompletableAssembly( + new ConditionalOnCurrentTraceContextFunction(oldCompletable) { + @Override Completable applyActual(Completable c, TraceContext assembled) { + return Wrappers.wrap(c, currentTraceContext, assembled); + } + }); + + // ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo + + final Function saveM = RxJavaPlugins.getOnMaybeAssembly(); + Function oldMaybe = saveM; + if (oldMaybe == null || !chain) oldMaybe = Util.identity(); + + RxJavaPlugins.setOnMaybeAssembly( + new ConditionalOnCurrentTraceContextFunction(oldMaybe) { + @Override Maybe applyActual(Maybe m, TraceContext assembled) { + return Wrappers.wrap(m, currentTraceContext, assembled); + } + }); + + // ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo + + final Function saveS = RxJavaPlugins.getOnSingleAssembly(); + Function oldSingle = saveS; + if (oldSingle == null || !chain) oldSingle = Util.identity(); + + RxJavaPlugins.setOnSingleAssembly( + new ConditionalOnCurrentTraceContextFunction(oldSingle) { + @Override Single applyActual(Single s, TraceContext assembled) { + return Wrappers.wrap(s, currentTraceContext, assembled); + } + }); + + // ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo + + final Function saveO = + RxJavaPlugins.getOnObservableAssembly(); + Function oldObservable = saveO; + if (oldObservable == null || !chain) oldObservable = Util.identity(); + + RxJavaPlugins.setOnObservableAssembly( + new ConditionalOnCurrentTraceContextFunction(oldObservable) { + @Override Observable applyActual(Observable o, TraceContext assembled) { + return Wrappers.wrap(o, currentTraceContext, assembled); + } + }); + + // ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo + + final Function saveF = + RxJavaPlugins.getOnFlowableAssembly(); + Function oldFlowable = saveF; + if (oldFlowable == null || !chain) oldFlowable = Util.identity(); + + RxJavaPlugins.setOnFlowableAssembly( + new ConditionalOnCurrentTraceContextFunction(oldFlowable) { + @Override Flowable applyActual(Flowable f, TraceContext assembled) { + return Wrappers.wrap(f, currentTraceContext, assembled); + } + }); + + // ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo + + final Function saveCF = + RxJavaPlugins.getOnConnectableFlowableAssembly(); + Function oldConnFlow = saveCF; + if (oldConnFlow == null || !chain) oldConnFlow = Util.identity(); + + RxJavaPlugins.setOnConnectableFlowableAssembly( + new ConditionalOnCurrentTraceContextFunction(oldConnFlow) { + @Override ConnectableFlowable applyActual(ConnectableFlowable cf, + TraceContext assembled) { + return Wrappers.wrap(cf, currentTraceContext, assembled); + } + }); + + // ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo + + final Function saveCO = + RxJavaPlugins.getOnConnectableObservableAssembly(); + Function oldConnObs = saveCO; + if (oldConnObs == null || !chain) oldConnObs = Util.identity(); + + RxJavaPlugins.setOnConnectableObservableAssembly( + new ConditionalOnCurrentTraceContextFunction(oldConnObs) { + @Override ConnectableObservable applyActual(ConnectableObservable co, + TraceContext assembled) { + return Wrappers.wrap(co, currentTraceContext, assembled); + } + }); + + // ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo + + final Function savePF = + RxJavaPlugins.getOnParallelAssembly(); + Function oldParFlow = savePF; + if (oldParFlow == null || !chain) oldParFlow = Util.identity(); + + RxJavaPlugins.setOnParallelAssembly( + new ConditionalOnCurrentTraceContextFunction(oldParFlow) { + @Override ParallelFlowable applyActual(ParallelFlowable pf, TraceContext assembled) { + return Wrappers.wrap(pf, currentTraceContext, assembled); + } + }); + + enabled = true; + + return new SavedHooks() { + @Override + public void restore() { + RxJavaPlugins.setOnCompletableAssembly(saveC); + RxJavaPlugins.setOnSingleAssembly(saveS); + RxJavaPlugins.setOnMaybeAssembly(saveM); + RxJavaPlugins.setOnObservableAssembly(saveO); + RxJavaPlugins.setOnFlowableAssembly(saveF); + + RxJavaPlugins.setOnConnectableObservableAssembly(saveCO); + RxJavaPlugins.setOnConnectableFlowableAssembly(saveCF); + + RxJavaPlugins.setOnParallelAssembly(savePF); + + enabled = false; + } + }; + } + + /** Disables the validation hooks be resetting the assembled hooks to none. */ + public static void disable() { + RxJavaPlugins.setOnCompletableAssembly(null); + RxJavaPlugins.setOnSingleAssembly(null); + RxJavaPlugins.setOnMaybeAssembly(null); + RxJavaPlugins.setOnObservableAssembly(null); + RxJavaPlugins.setOnFlowableAssembly(null); + + RxJavaPlugins.setOnConnectableObservableAssembly(null); + RxJavaPlugins.setOnConnectableFlowableAssembly(null); + + RxJavaPlugins.setOnParallelAssembly(null); + + enabled = false; + } + + /** Returns true if the validation hooks have been installed. */ + public static boolean isEnabled() { + return enabled; + } + + /** + * This is the only code that gets the assembly time trace context. Wrapped code applies this + * assembled context at runtime with {@link CurrentTraceContext#newScope(TraceContext)}. + */ + abstract class ConditionalOnCurrentTraceContextFunction implements Function { + final Function oldFn; + + ConditionalOnCurrentTraceContextFunction(Function oldFn) { + this.oldFn = oldFn; + } + + @Override public final T apply(T t) throws Exception { + TraceContext assembly = currentTraceContext.get(); + if (assembly == null) return oldFn.apply(t); // less overhead when there's no current trace + return applyActual(oldFn.apply(t), assembly); + } + + abstract T applyActual(T t, TraceContext assembled); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCallableCompletable.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCallableCompletable.java new file mode 100644 index 0000000000..74b9119ced --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCallableCompletable.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.Completable; +import io.reactivex.CompletableObserver; +import io.reactivex.CompletableSource; +import java.util.concurrent.Callable; + +final class TraceContextCallableCompletable extends Completable implements Callable { + final CompletableSource source; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + + TraceContextCallableCompletable( + CompletableSource source, CurrentTraceContext contextScoper, TraceContext assembled) { + this.source = source; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + /** + * Wraps the observer so that its callbacks run in the assembly context. This does not affect any + * subscription callbacks. + */ + @Override protected void subscribeActual(CompletableObserver s) { + source.subscribe(Wrappers.wrap(s, contextScoper, assembled)); + } + + /** + * The value in the source is computed synchronously, at subscription time. We don't re-scope this + * call because it would interfere with the subscription context. + * + *

See https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0#callable-and-scalarcallable + */ + @Override @SuppressWarnings("unchecked") public T call() throws Exception { + return ((Callable) source).call(); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCallableFlowable.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCallableFlowable.java new file mode 100644 index 0000000000..d92b6b54bc --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCallableFlowable.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.Flowable; +import java.util.concurrent.Callable; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +final class TraceContextCallableFlowable extends Flowable implements Callable { + final Publisher source; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + + TraceContextCallableFlowable( + Publisher source, CurrentTraceContext contextScoper, TraceContext assembled) { + this.source = source; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + /** + * Wraps the subscriber so that its callbacks run in the assembly context. This does not affect + * any subscription callbacks. + */ + @Override protected void subscribeActual(Subscriber s) { + source.subscribe(Wrappers.wrap(s, contextScoper, assembled)); + } + + /** + * The value in the source is computed synchronously, at subscription time. We don't re-scope this + * call because it would interfere with the subscription context. + * + *

See https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0#callable-and-scalarcallable + */ + @Override @SuppressWarnings("unchecked") public T call() throws Exception { + return ((Callable) source).call(); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCallableMaybe.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCallableMaybe.java new file mode 100644 index 0000000000..959891113d --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCallableMaybe.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.Maybe; +import io.reactivex.MaybeObserver; +import io.reactivex.MaybeSource; +import java.util.concurrent.Callable; + +final class TraceContextCallableMaybe extends Maybe implements Callable { + final MaybeSource source; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + + TraceContextCallableMaybe( + MaybeSource source, CurrentTraceContext contextScoper, TraceContext assembled) { + this.source = source; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + /** + * Wraps the observer so that its callbacks run in the assembly context. This does not affect any + * subscription callbacks. + */ + @Override protected void subscribeActual(MaybeObserver o) { + source.subscribe(Wrappers.wrap(o, contextScoper, assembled)); + } + + /** + * The value in the source is computed synchronously, at subscription time. We don't re-scope this + * call because it would interfere with the subscription context. + * + *

See https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0#callable-and-scalarcallable + */ + @Override @SuppressWarnings("unchecked") public T call() throws Exception { + return ((Callable) source).call(); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCallableObservable.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCallableObservable.java new file mode 100644 index 0000000000..35c363f4fa --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCallableObservable.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.Observable; +import io.reactivex.ObservableSource; +import io.reactivex.Observer; +import java.util.concurrent.Callable; + +final class TraceContextCallableObservable extends Observable implements Callable { + final ObservableSource source; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + + TraceContextCallableObservable( + ObservableSource source, CurrentTraceContext contextScoper, TraceContext assembled) { + this.source = source; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + /** + * Wraps the observer so that its callbacks run in the assembly context. This does not affect any + * subscription callbacks. + */ + @Override protected void subscribeActual(Observer o) { + source.subscribe(Wrappers.wrap(o, contextScoper, assembled)); + } + + /** + * The value in the source is computed synchronously, at subscription time. We don't re-scope this + * call because it would interfere with the subscription context. + * + *

See https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0#callable-and-scalarcallable + */ + @Override @SuppressWarnings("unchecked") public T call() throws Exception { + return ((Callable) source).call(); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCallableSingle.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCallableSingle.java new file mode 100644 index 0000000000..e533dc0078 --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCallableSingle.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.SingleSource; +import java.util.concurrent.Callable; + +final class TraceContextCallableSingle extends Single implements Callable { + final SingleSource source; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + + TraceContextCallableSingle( + SingleSource source, CurrentTraceContext contextScoper, TraceContext assembled) { + this.source = source; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + /** + * Wraps the observer so that its callbacks run in the assembly context. This does not affect any + * subscription callbacks. + */ + @Override protected void subscribeActual(SingleObserver o) { + source.subscribe(Wrappers.wrap(o, contextScoper, assembled)); + } + + /** + * The value in the source is computed synchronously, at subscription time. We don't re-scope this + * call because it would interfere with the subscription context. + * + *

See https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0#callable-and-scalarcallable + */ + @Override @SuppressWarnings("unchecked") public T call() throws Exception { + return ((Callable) source).call(); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCompletable.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCompletable.java new file mode 100644 index 0000000000..3c7e0b6e7a --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCompletable.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.Completable; +import io.reactivex.CompletableObserver; +import io.reactivex.CompletableSource; + +final class TraceContextCompletable extends Completable { + final CompletableSource source; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + + TraceContextCompletable( + CompletableSource source, CurrentTraceContext contextScoper, TraceContext assembled) { + this.source = source; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + /** + * Wraps the observer so that its callbacks run in the assembly context. This does not affect any + * subscription callbacks. + */ + @Override protected void subscribeActual(CompletableObserver o) { + source.subscribe(new TraceContextCompletableObserver(o, contextScoper, assembled)); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCompletableObserver.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCompletableObserver.java new file mode 100644 index 0000000000..ce8a550918 --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextCompletableObserver.java @@ -0,0 +1,66 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.TraceContext; +import io.reactivex.CompletableObserver; +import io.reactivex.disposables.Disposable; + +final class TraceContextCompletableObserver implements CompletableObserver, Disposable { + final CompletableObserver downstream; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + Disposable upstream; + + TraceContextCompletableObserver( + CompletableObserver downstream, CurrentTraceContext contextScoper, TraceContext assembled) { + this.downstream = downstream; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + @Override public void onSubscribe(Disposable d) { + if (!Util.validate(upstream, d)) return; + upstream = d; + downstream.onSubscribe(this); + } + + @Override public void onError(Throwable t) { + Scope scope = contextScoper.maybeScope(assembled); + try { + downstream.onError(t); + } finally { + scope.close(); + } + } + + @Override public void onComplete() { + Scope scope = contextScoper.maybeScope(assembled); + try { + downstream.onComplete(); + } finally { + scope.close(); + } + } + + @Override public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override public void dispose() { + upstream.dispose(); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextConnectableFlowable.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextConnectableFlowable.java new file mode 100644 index 0000000000..e92268f3f2 --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextConnectableFlowable.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.TraceContext; +import io.reactivex.disposables.Disposable; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.functions.Consumer; +import org.reactivestreams.Subscriber; + +final class TraceContextConnectableFlowable extends ConnectableFlowable { + final ConnectableFlowable source; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + + TraceContextConnectableFlowable( + ConnectableFlowable source, CurrentTraceContext contextScoper, TraceContext assembled) { + this.source = source; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + /** + * Wraps the subscriber so that its callbacks run in the assembly context. This does not affect + * any subscription callbacks. + */ + @Override protected void subscribeActual(Subscriber s) { + source.subscribe(Wrappers.wrap(s, contextScoper, assembled)); + } + + @Override public void connect(Consumer connection) { + Scope scope = contextScoper.maybeScope(assembled); + try { + source.connect(connection); + } finally { + scope.close(); + } + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextConnectableObservable.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextConnectableObservable.java new file mode 100644 index 0000000000..eaddfd461a --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextConnectableObservable.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.TraceContext; +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Consumer; +import io.reactivex.observables.ConnectableObservable; + +final class TraceContextConnectableObservable extends ConnectableObservable { + final ConnectableObservable source; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + + TraceContextConnectableObservable( + ConnectableObservable source, CurrentTraceContext contextScoper, TraceContext assembled) { + this.source = source; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + /** + * Wraps the observer so that its callbacks run in the assembly context. This does not affect any + * subscription callbacks. + */ + @Override protected void subscribeActual(Observer o) { + source.subscribe(new TraceContextObserver(o, contextScoper, assembled)); + } + + @Override public void connect(Consumer connection) { + Scope scope = contextScoper.maybeScope(assembled); + try { + source.connect(connection); + } finally { + scope.close(); + } + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextFlowable.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextFlowable.java new file mode 100644 index 0000000000..bcac528456 --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextFlowable.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.Flowable; +import io.reactivex.FlowableSubscriber; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +final class TraceContextFlowable extends Flowable { + final Publisher source; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + + TraceContextFlowable( + Publisher source, CurrentTraceContext contextScoper, TraceContext assembled) { + this.source = source; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + /** + * Wraps the subscriber so that its callbacks run in the assembly context. This does not affect + * any subscription callbacks. + * + *

Note: per {@link #subscribe(Subscriber)} only calls this with a {@link FlowableSubscriber} + */ + @Override protected void subscribeActual(Subscriber s) { + assert s instanceof FlowableSubscriber : "!(s instanceof FlowableSubscriber)"; + source.subscribe(Wrappers.wrap(s, contextScoper, assembled)); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextFlowableSubscriber.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextFlowableSubscriber.java new file mode 100644 index 0000000000..1f54fefe03 --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextFlowableSubscriber.java @@ -0,0 +1,43 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.FlowableSubscriber; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +/** + * We implement {@linkplain FlowableSubscriber}, not {@linkplain Subscriber} as the only call site + * is {@code Flowable#subscribeActual(Subscriber)} which is guaranteed to only take a {@linkplain + * FlowableSubscriber}. + */ +class TraceContextFlowableSubscriber extends TraceContextSubscriber + implements FlowableSubscriber, Subscription { + + TraceContextFlowableSubscriber( + FlowableSubscriber downstream, CurrentTraceContext contextScoper, + TraceContext assembled) { + super(downstream, contextScoper, assembled); + } + + @Override public void request(long n) { + upstream.request(n); + } + + @Override public void cancel() { + upstream.cancel(); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextMaybe.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextMaybe.java new file mode 100644 index 0000000000..cf6580dc07 --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextMaybe.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.Maybe; +import io.reactivex.MaybeObserver; +import io.reactivex.MaybeSource; + +final class TraceContextMaybe extends Maybe { + final MaybeSource source; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + + TraceContextMaybe( + MaybeSource source, CurrentTraceContext contextScoper, TraceContext assembled) { + this.source = source; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + /** + * Wraps the observer so that its callbacks run in the assembly context. This does not affect any + * subscription callbacks. + */ + @Override protected void subscribeActual(MaybeObserver o) { + source.subscribe(new TraceContextMaybeObserver(o, contextScoper, assembled)); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextMaybeObserver.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextMaybeObserver.java new file mode 100644 index 0000000000..6cee2ebd23 --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextMaybeObserver.java @@ -0,0 +1,78 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.TraceContext; +import io.reactivex.MaybeObserver; +import io.reactivex.disposables.Disposable; + +final class TraceContextMaybeObserver implements MaybeObserver, Disposable { + final MaybeObserver downstream; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + Disposable upstream; + + TraceContextMaybeObserver( + MaybeObserver downstream, CurrentTraceContext contextScoper, TraceContext assembled) { + this.downstream = downstream; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + @Override public void onSubscribe(Disposable d) { + if (!Util.validate(upstream, d)) return; + upstream = d; + + // Operators need to detect the fuseable feature of their immediate upstream. We pass "this" + // to ensure downstream don't interface with the wrong operator (s). + downstream.onSubscribe(this); + } + + @Override public void onError(Throwable t) { + Scope scope = contextScoper.maybeScope(assembled); + try { + downstream.onError(t); + } finally { + scope.close(); + } + } + + @Override public void onSuccess(T value) { + Scope scope = contextScoper.maybeScope(assembled); + try { + downstream.onSuccess(value); + } finally { + scope.close(); + } + } + + @Override public void onComplete() { + Scope scope = contextScoper.maybeScope(assembled); + try { + downstream.onComplete(); + } finally { + scope.close(); + } + } + + @Override public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override public void dispose() { + upstream.dispose(); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextObservable.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextObservable.java new file mode 100644 index 0000000000..fd1332754f --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextObservable.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.Observable; +import io.reactivex.ObservableSource; +import io.reactivex.Observer; + +final class TraceContextObservable extends Observable { + final ObservableSource source; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + + TraceContextObservable( + ObservableSource source, CurrentTraceContext contextScoper, TraceContext assembled) { + this.source = source; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + /** + * Wraps the observer so that its callbacks run in the assembly context. This does not affect any + * subscription callbacks. + */ + @Override protected void subscribeActual(Observer o) { + source.subscribe(new TraceContextObserver(o, contextScoper, assembled)); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextObserver.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextObserver.java new file mode 100644 index 0000000000..3c45feab08 --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextObserver.java @@ -0,0 +1,89 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.TraceContext; +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; +import io.reactivex.plugins.RxJavaPlugins; + +final class TraceContextObserver implements Observer, Disposable { + final Observer downstream; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + Disposable upstream; + boolean done; + + TraceContextObserver( + Observer downstream, CurrentTraceContext contextScoper, TraceContext assembled) { + this.downstream = downstream; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + @Override public final void onSubscribe(Disposable d) { + if (!Util.validate(upstream, d)) return; + upstream = d; + + // Operators need to detect the fuseable feature of their immediate upstream. We pass "this" + // to ensure downstream don't interface with the wrong operator (s). + downstream.onSubscribe(this); + } + + @Override public void onNext(T t) { + Scope scope = contextScoper.maybeScope(assembled); + try { + downstream.onNext(t); + } finally { + scope.close(); + } + } + + @Override public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + + Scope scope = contextScoper.maybeScope(assembled); + try { + downstream.onError(t); + } finally { + scope.close(); + } + } + + @Override public void onComplete() { + if (done) return; + done = true; + + Scope scope = contextScoper.maybeScope(assembled); + try { + downstream.onComplete(); + } finally { + scope.close(); + } + } + + @Override public void dispose() { + upstream.dispose(); + } + + @Override public boolean isDisposed() { + return upstream.isDisposed(); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextParallelFlowable.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextParallelFlowable.java new file mode 100644 index 0000000000..2e91f2e343 --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextParallelFlowable.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.parallel.ParallelFlowable; +import org.reactivestreams.Subscriber; + +final class TraceContextParallelFlowable extends ParallelFlowable { + final ParallelFlowable source; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + + TraceContextParallelFlowable( + ParallelFlowable source, CurrentTraceContext contextScoper, TraceContext assembled) { + this.source = source; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + @Override public int parallelism() { + return source.parallelism(); + } + + /** + * Wraps the subscribers so that their callbacks run in the assembly context. This does not affect + * any subscription callbacks. + */ + @Override public void subscribe(Subscriber[] s) { + if (!validate(s)) return; + int n = s.length; + @SuppressWarnings("unchecked") + Subscriber[] parents = new Subscriber[n]; + for (int i = 0; i < n; i++) { + Subscriber z = s[i]; + parents[i] = Wrappers.wrap(z, contextScoper, assembled); + } + source.subscribe(parents); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextSingle.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextSingle.java new file mode 100644 index 0000000000..c03e440773 --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextSingle.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.SingleSource; + +final class TraceContextSingle extends Single { + final SingleSource source; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + + TraceContextSingle( + SingleSource source, CurrentTraceContext contextScoper, TraceContext assembled) { + this.source = source; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + /** + * Wraps the observer so that its callbacks run in the assembly context. This does not affect any + * subscription callbacks. + */ + @Override protected void subscribeActual(SingleObserver o) { + source.subscribe(new TraceContextSingleObserver(o, contextScoper, assembled)); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextSingleObserver.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextSingleObserver.java new file mode 100644 index 0000000000..d06eb9e4d8 --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextSingleObserver.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.TraceContext; +import io.reactivex.SingleObserver; +import io.reactivex.disposables.Disposable; + +final class TraceContextSingleObserver implements SingleObserver, Disposable { + final SingleObserver downstream; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + Disposable upstream; + + TraceContextSingleObserver( + SingleObserver downstream, CurrentTraceContext contextScoper, TraceContext assembled) { + this.downstream = downstream; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + @Override public void onSubscribe(Disposable d) { + if (!Util.validate(upstream, d)) return; + upstream = d; + + // Operators need to detect the fuseable feature of their immediate upstream. We pass "this" + // to ensure downstream don't interface with the wrong operator (s). + downstream.onSubscribe(this); + } + + @Override public void onError(Throwable t) { + Scope scope = contextScoper.maybeScope(assembled); + try { + downstream.onError(t); + } finally { + scope.close(); + } + } + + @Override public void onSuccess(T value) { + Scope scope = contextScoper.maybeScope(assembled); + try { + downstream.onSuccess(value); + } finally { + scope.close(); + } + } + + @Override public boolean isDisposed() { + return upstream.isDisposed(); + } + + @Override public void dispose() { + upstream.dispose(); + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextSubscriber.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextSubscriber.java new file mode 100644 index 0000000000..721503f372 --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/TraceContextSubscriber.java @@ -0,0 +1,82 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.TraceContext; +import io.reactivex.plugins.RxJavaPlugins; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +class TraceContextSubscriber implements Subscriber { + final Subscriber downstream; + final CurrentTraceContext contextScoper; + final TraceContext assembled; + + Subscription upstream; + boolean done; + + TraceContextSubscriber( + Subscriber downstream, CurrentTraceContext contextScoper, TraceContext assembled) { + this.downstream = downstream; + this.contextScoper = contextScoper; + this.assembled = assembled; + } + + @Override public final void onSubscribe(Subscription s) { + if (!Util.validate(upstream, s)) return; + upstream = s; + + // Operators need to detect the fuseable feature of their immediate upstream. We pass "this" + // to ensure downstream don't interface with the wrong operator (s). + downstream.onSubscribe(upstream); + } + + @Override public void onNext(T t) { + Scope scope = contextScoper.maybeScope(assembled); + try { + downstream.onNext(t); + } finally { + scope.close(); + } + } + + @Override public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + return; + } + done = true; + + Scope scope = contextScoper.maybeScope(assembled); + try { + downstream.onError(t); + } finally { + scope.close(); + } + } + + @Override public void onComplete() { + if (done) return; + done = true; + + Scope scope = contextScoper.maybeScope(assembled); + try { + downstream.onComplete(); + } finally { + scope.close(); + } + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/Util.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/Util.java new file mode 100644 index 0000000000..54437e8f09 --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/Util.java @@ -0,0 +1,66 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.ProtocolViolationException; +import io.reactivex.functions.Function; +import io.reactivex.plugins.RxJavaPlugins; +import org.reactivestreams.Subscription; + +/** Junk drawer to avoid dependencies on internal RxJava types. */ +public final class Util { + static final Function IDENTITY = new Function() { + @Override public Object apply(Object v) { + return v; + } + + @Override public String toString() { + return "IdentityFunction"; + } + }; + + // io.reactivex.internal.functions.Functions.identity() + public static Function identity() { + return (Function) IDENTITY; + } + + // io.reactivex.internal.disposables.DisposableHelper.validate(Disposable, Disposable) + public static boolean validate(Disposable current, Disposable next) { + if (next == null) { + RxJavaPlugins.onError(new NullPointerException("next is null")); + return false; + } + if (current != null) { + next.dispose(); + RxJavaPlugins.onError(new ProtocolViolationException("Disposable already set!")); + return false; + } + return true; + } + + // io.reactivex.internal.subscriptions.SubscriptionHelper.validate(Subscription, Subscription) + public static boolean validate(Subscription current, Subscription next) { + if (next == null) { + RxJavaPlugins.onError(new NullPointerException("next is null")); + return false; + } + if (current != null) { + next.cancel(); + RxJavaPlugins.onError(new ProtocolViolationException("Subscription already set!")); + return false; + } + return true; + } +} diff --git a/context/rxjava2/src/main/java/brave/context/rxjava2/internal/Wrappers.java b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/Wrappers.java new file mode 100644 index 0000000000..0fa6f9c260 --- /dev/null +++ b/context/rxjava2/src/main/java/brave/context/rxjava2/internal/Wrappers.java @@ -0,0 +1,128 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.internal; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.Completable; +import io.reactivex.CompletableObserver; +import io.reactivex.CompletableSource; +import io.reactivex.Flowable; +import io.reactivex.FlowableSubscriber; +import io.reactivex.Maybe; +import io.reactivex.MaybeObserver; +import io.reactivex.MaybeSource; +import io.reactivex.Observable; +import io.reactivex.ObservableSource; +import io.reactivex.Observer; +import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.SingleSource; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.observables.ConnectableObservable; +import io.reactivex.parallel.ParallelFlowable; +import java.util.concurrent.Callable; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +public class Wrappers { + + public static Subscriber wrap( + Subscriber downstream, CurrentTraceContext contextScoper, TraceContext assembled) { + if (downstream instanceof FlowableSubscriber) { + return new TraceContextFlowableSubscriber((FlowableSubscriber) downstream, + contextScoper, assembled); + } + return new TraceContextSubscriber(downstream, contextScoper, assembled); + } + + public static Completable wrap( + CompletableSource source, CurrentTraceContext contextScoper, TraceContext assembled) { + if (source instanceof Callable) { + return new TraceContextCallableCompletable(source, contextScoper, assembled); + } + return new TraceContextCompletable(source, contextScoper, assembled); + } + + public static Maybe wrap( + MaybeSource source, CurrentTraceContext contextScoper, TraceContext assembled) { + if (source instanceof Callable) { + return new TraceContextCallableMaybe(source, contextScoper, assembled); + } + return new TraceContextMaybe(source, contextScoper, assembled); + } + + public static Single wrap( + SingleSource source, CurrentTraceContext contextScoper, TraceContext assembled) { + if (source instanceof Callable) { + return new TraceContextCallableSingle(source, contextScoper, assembled); + } + return new TraceContextSingle(source, contextScoper, assembled); + } + + public static Observable wrap( + ObservableSource source, CurrentTraceContext contextScoper, TraceContext assembled) { + if (source instanceof Callable) { + return new TraceContextCallableObservable(source, contextScoper, assembled); + } + return new TraceContextObservable(source, contextScoper, assembled); + } + + public static ConnectableObservable wrap( + ConnectableObservable source, CurrentTraceContext contextScoper, TraceContext assembled) { + return new TraceContextConnectableObservable(source, contextScoper, assembled); + } + + public static Flowable wrap( + Publisher source, CurrentTraceContext contextScoper, TraceContext assembled) { + if (source instanceof Callable) { + return new TraceContextCallableFlowable(source, contextScoper, assembled); + } + return new TraceContextFlowable(source, contextScoper, assembled); + } + + public static ConnectableFlowable wrap( + ConnectableFlowable source, CurrentTraceContext contextScoper, TraceContext assembled) { + return new TraceContextConnectableFlowable(source, contextScoper, assembled); + } + + public static ParallelFlowable wrap( + ParallelFlowable source, CurrentTraceContext contextScoper, TraceContext assembled) { + return new TraceContextParallelFlowable(source, contextScoper, assembled); + } + + public static Observer wrap(Observer downstream, + CurrentTraceContext contextScoper, TraceContext assembled) { + return new TraceContextObserver(downstream, contextScoper, assembled); + } + + public static SingleObserver wrap(SingleObserver downstream, + CurrentTraceContext contextScoper, TraceContext assembled) { + return new TraceContextSingleObserver(downstream, contextScoper, assembled); + } + + public static MaybeObserver wrap(MaybeObserver downstream, + CurrentTraceContext contextScoper, TraceContext assembled) { + return new TraceContextMaybeObserver(downstream, contextScoper, assembled); + } + + public static CompletableObserver wrap(CompletableObserver downstream, + CurrentTraceContext contextScoper, TraceContext assembled) { + return new TraceContextCompletableObserver(downstream, contextScoper, assembled); + } + + Wrappers() { + + } +} diff --git a/context/rxjava2/src/test/java/brave/context/rxjava2/CurrentTraceContextAssemblyTrackingClassLoaderTest.java b/context/rxjava2/src/test/java/brave/context/rxjava2/CurrentTraceContextAssemblyTrackingClassLoaderTest.java new file mode 100644 index 0000000000..f763c9b77a --- /dev/null +++ b/context/rxjava2/src/test/java/brave/context/rxjava2/CurrentTraceContextAssemblyTrackingClassLoaderTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2; + +import brave.context.rxjava2.CurrentTraceContextAssemblyTracking.SavedHooks; +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.ThreadLocalCurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.Observable; +import org.junit.jupiter.api.Test; + +import static brave.test.util.ClassLoaders.assertRunIsUnloadable; + +class CurrentTraceContextAssemblyTrackingClassLoaderTest { + + @Test void noop_unloadable() { + assertRunIsUnloadable(Noop.class, getClass().getClassLoader()); + } + + static class Noop implements Runnable { + @Override public void run() { + new CurrentTraceContextAssemblyTracking( + ThreadLocalCurrentTraceContext.newBuilder().build() + ).enable(); + CurrentTraceContextAssemblyTracking.disable(); + } + } + + /** Proves when code is correct, we can unload our classes. */ + @Test void simpleUsage_unloadable() { + assertRunIsUnloadable(SimpleUsable.class, getClass().getClassLoader()); + } + + static class SimpleUsable implements Runnable { + @Override public void run() { + CurrentTraceContext currentTraceContext = + ThreadLocalCurrentTraceContext.newBuilder().build(); + SavedHooks saved = new CurrentTraceContextAssemblyTracking(currentTraceContext) + .enableAndChain(); + + TraceContext assembly = TraceContext.newBuilder().traceId(1).spanId(1).build(); + + try (Scope scope = currentTraceContext.newScope(assembly)) { + Observable.just(1).map(i -> i).test().assertNoErrors(); + } finally { + saved.restore(); + } + } + } +} diff --git a/context/rxjava2/src/test/java/brave/context/rxjava2/CurrentTraceContextAssemblyTrackingMatrixTest.java b/context/rxjava2/src/test/java/brave/context/rxjava2/CurrentTraceContextAssemblyTrackingMatrixTest.java new file mode 100644 index 0000000000..01ad3cdc1d --- /dev/null +++ b/context/rxjava2/src/test/java/brave/context/rxjava2/CurrentTraceContextAssemblyTrackingMatrixTest.java @@ -0,0 +1,1486 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.StrictCurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.Completable; +import io.reactivex.CompletableObserver; +import io.reactivex.Flowable; +import io.reactivex.Maybe; +import io.reactivex.MaybeObserver; +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.Single; +import io.reactivex.SingleObserver; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.fuseable.ScalarCallable; +import io.reactivex.internal.operators.completable.CompletableEmpty; +import io.reactivex.internal.operators.completable.CompletableFromCallable; +import io.reactivex.internal.operators.flowable.FlowableFilter; +import io.reactivex.internal.operators.flowable.FlowableFromCallable; +import io.reactivex.internal.operators.flowable.FlowablePublish; +import io.reactivex.internal.operators.flowable.FlowableRange; +import io.reactivex.internal.operators.maybe.MaybeFilter; +import io.reactivex.internal.operators.maybe.MaybeFilterSingle; +import io.reactivex.internal.operators.maybe.MaybeFromCallable; +import io.reactivex.internal.operators.maybe.MaybeJust; +import io.reactivex.internal.operators.observable.ObservableFilter; +import io.reactivex.internal.operators.observable.ObservableFromCallable; +import io.reactivex.internal.operators.observable.ObservablePublish; +import io.reactivex.internal.operators.observable.ObservableRange; +import io.reactivex.internal.operators.parallel.ParallelFilter; +import io.reactivex.internal.operators.parallel.ParallelFromPublisher; +import io.reactivex.internal.operators.single.SingleFromCallable; +import io.reactivex.internal.operators.single.SingleJust; +import io.reactivex.observables.ConnectableObservable; +import io.reactivex.observers.TestObserver; +import io.reactivex.parallel.ParallelFlowable; +import io.reactivex.plugins.RxJavaPlugins; +import java.util.concurrent.Callable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Subscriber; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * These test that features that are similar across reactive types act the same way. These are + * largely cut/paste/find/replace with the exception of api differences between the types. When + * behavior needs to be tested across all things, do it here. Keep interesting things in {@link + * CurrentTraceContextAssemblyTrackingTest} so they don't get lost! + */ +class CurrentTraceContextAssemblyTrackingMatrixTest { + CurrentTraceContext currentTraceContext = StrictCurrentTraceContext.create(); + CurrentTraceContext throwingCurrentTraceContext = new CurrentTraceContext() { + @Override public TraceContext get() { + return subscribeContext; + } + + @Override public Scope newScope(TraceContext currentSpan) { + throw new AssertionError(); + } + }; + TraceContext assemblyContext = TraceContext.newBuilder().traceId(1L).spanId(1L).build(); + TraceContext subscribeContext = assemblyContext.toBuilder().parentId(1L).spanId(2L).build(); + Predicate lessThanThreeInAssemblyContext = i -> { + assertInAssemblyContext(); + return i < 3; + }; + Predicate lessThanThreeInSubscribeContext = i -> { + assertInSubscribeContext(); + return i < 3; + }; + + @BeforeEach void setup() { + RxJavaPlugins.reset(); + CurrentTraceContextAssemblyTracking.create(currentTraceContext).enable(); + } + + @AfterEach void tearDown() { + CurrentTraceContextAssemblyTracking.disable(); + } + + @Test void completable_assembleInScope_subscribeNoScope() { + Completable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Completable.complete() + .doOnComplete(this::assertInAssemblyContext); + errorSource = Completable.error(new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInNoContext(source.toObservable(), errorSource.toObservable()).assertResult(); + } + + @Test void completable_assembleInScope_subscribeInScope() { + Completable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Completable.complete() + .doOnComplete(this::assertInAssemblyContext); + errorSource = Completable.error(new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(); + } + + @Test void completable_assembleNoScope_subscribeInScope() { + Completable source = Completable.complete() + .doOnComplete(this::assertInSubscribeContext); + Completable errorSource = Completable.error(new IllegalStateException()) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInAssemblyContext); + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(); + } + + @Test void completable_unwrappedWhenNotInScope() { + assertThat(Completable.complete()).isEqualTo(CompletableEmpty.INSTANCE); + } + + @Test void flowable_assembleInScope_subscribeNoScope() { + Flowable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Flowable.range(1, 3) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = Flowable.error(new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInNoContext(source.toObservable(), errorSource.toObservable()) + .assertResult(1, 2, 3); + } + + @Test void flowable_assembleInScope_subscribeInScope() { + Flowable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Flowable.range(1, 3) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = Flowable.error(new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()) + .assertResult(1, 2, 3); + } + + @Test void flowable_assembleNoScope_subscribeInScope() { + Flowable source = Flowable.range(1, 3) + .doOnNext(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + Flowable errorSource = Flowable.error(new IllegalStateException()) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInAssemblyContext); + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()) + .assertResult(1, 2, 3); + } + + @Test void flowable_unwrappedWhenNotInScope() { + assertThat(Flowable.range(1, 3)).isInstanceOf(FlowableRange.class); + } + + @Test void flowable_conditional_assembleInScope_subscribeNoScope() { + Flowable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Flowable.range(1, 3) + .filter(lessThanThreeInAssemblyContext) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = Flowable.error(new IllegalStateException()) + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInNoContext(source.toObservable(), errorSource.toObservable()) + .assertResult(1, 2); + } + + @Test void flowable_conditional_assembleInScope_subscribeInScope() { + Flowable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Flowable.range(1, 3) + .filter(lessThanThreeInAssemblyContext) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = Flowable.error(new IllegalStateException()) + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()) + .assertResult(1, 2); + } + + @Test void flowable_conditional_assembleNoScope_subscribeInScope() { + Flowable source = Flowable.range(1, 3) + .filter(lessThanThreeInSubscribeContext) + .doOnNext(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + Flowable errorSource = Flowable.error(new IllegalStateException()) + .filter(lessThanThreeInSubscribeContext) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()) + .assertResult(1, 2); + } + + @Test void flowable_conditional_unwrappedWhenNotInScope() { + assertThat(Flowable.range(1, 3).filter(i -> i < 3)) + .isInstanceOf(FlowableFilter.class); + } + + @Test void observable_assembleInScope_subscribeNoScope() { + Observable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Observable.range(1, 3) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = Observable.error(new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInNoContext(source, errorSource).assertResult(1, 2, 3); + } + + @Test void observable_assembleInScope_subscribeInScope() { + Observable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Observable.range(1, 3) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = Observable.error(new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source, errorSource).assertResult(1, 2, 3); + } + + @Test void observable_assembleNoScope_subscribeInScope() { + Observable source = Observable.range(1, 3) + .doOnNext(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + Observable errorSource = Observable.error(new IllegalStateException()) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInAssemblyContext); + + subscribeInDifferentContext(source, errorSource).assertResult(1, 2, 3); + } + + @Test void observable_unwrappedWhenNotInScope() { + assertThat(Observable.range(1, 3)).isInstanceOf(ObservableRange.class); + } + + @Test void observable_conditional_assembleInScope_subscribeNoScope() { + Observable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Observable.range(1, 3) + .filter(lessThanThreeInAssemblyContext) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = Observable.error(new IllegalStateException()) + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInNoContext(source, errorSource).assertResult(1, 2); + } + + @Test void observable_conditional_assembleInScope_subscribeInScope() { + Observable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Observable.range(1, 3) + .filter(lessThanThreeInAssemblyContext) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = Observable.error(new IllegalStateException()) + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source, errorSource).assertResult(1, 2); + } + + @Test void observable_conditional_assembleNoScope_subscribeInScope() { + Observable source = Observable.range(1, 3) + .filter(lessThanThreeInSubscribeContext) + .doOnNext(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + Observable errorSource = Observable.error(new IllegalStateException()) + .filter(lessThanThreeInSubscribeContext) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInAssemblyContext); + + subscribeInDifferentContext(source, errorSource).assertResult(1, 2); + } + + @Test void observable_conditional_unwrappedWhenNotInScope() { + assertThat(Observable.range(1, 3).filter(i -> i < 3)) + .isInstanceOf(ObservableFilter.class); + } + + @Test void single_assembleInScope_subscribeNoScope() { + Single source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Single.just(1) + .doOnSuccess(e -> assertInAssemblyContext()); + errorSource = Single.error(new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()); + } + + subscribeInNoContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void single_assembleInScope_subscribeInScope() { + Single source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Single.just(1) + .doOnSuccess(e -> assertInAssemblyContext()); + errorSource = Single.error(new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()); + } + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void single_assembleNoScope_subscribeInScope() { + Single source = Single.just(1) + .doOnSuccess(e -> assertInSubscribeContext()); + Single errorSource = Single.error(new IllegalStateException()) + .doOnError(t -> assertInSubscribeContext()); + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void single_unwrappedWhenNotInScope() { + assertThat(Single.just(1)).isInstanceOf(SingleJust.class); + } + + @Test void single_conditional_assembleInScope_subscribeNoScope() { + Maybe source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Single.just(1) + .filter(lessThanThreeInAssemblyContext) + .doOnSuccess(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInSubscribeContext); + errorSource = Single.error(new IllegalStateException()) + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()); + } + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void single_conditional_assembleInScope_subscribeInScope() { + Maybe source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Single.just(1) + .filter(lessThanThreeInAssemblyContext) + .doOnSuccess(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInSubscribeContext); + errorSource = Single.error(new IllegalStateException()) + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()); + } + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void single_conditional_assembleNoScope_subscribeInScope() { + Maybe source = Single.just(1) + .filter(lessThanThreeInSubscribeContext) + .doOnSuccess(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + Maybe errorSource = Single.error(new IllegalStateException()) + .filter(lessThanThreeInSubscribeContext) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInAssemblyContext); + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void single_conditionalunwrappedWhenNotInScope() { + assertThat(Single.just(1).filter(i -> i < 3)).isInstanceOf(MaybeFilterSingle.class); + } + + @Test void maybe_assembleInScope_subscribeNoScope() { + Maybe source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Maybe.just(1) + .doOnSuccess(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = Maybe.error(new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInNoContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void maybe_assembleInScope_subscribeInScope() { + Maybe source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Maybe.just(1) + .doOnSuccess(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = Maybe.error(new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void maybe_assembleNoScope_subscribeInScope() { + Maybe source = Maybe.just(1) + .doOnSuccess(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + Maybe errorSource = Maybe.error(new IllegalStateException()) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void maybe_unwrappedWhenNotInScope() { + assertThat(Maybe.just(1)).isInstanceOf(MaybeJust.class); + } + + @Test void maybe_conditional_assembleInScope_subscribeNoScope() { + Maybe source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Maybe.just(1) + .filter(lessThanThreeInAssemblyContext) + .doOnSuccess(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInSubscribeContext); + errorSource = Maybe.error(new IllegalStateException()) + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void maybe_conditional_assembleInScope_subscribeInScope() { + Maybe source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Maybe.just(1) + .filter(lessThanThreeInAssemblyContext) + .doOnSuccess(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInSubscribeContext); + errorSource = Maybe.error(new IllegalStateException()) + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void maybe_conditional_assembleNoScope_subscribeInScope() { + Maybe source = Maybe.just(1) + .filter(lessThanThreeInSubscribeContext) + .doOnSuccess(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + Maybe errorSource = Maybe.error(new IllegalStateException()) + .filter(lessThanThreeInSubscribeContext) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInAssemblyContext); + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void maybe_conditionalunwrappedWhenNotInScope() { + assertThat(Maybe.just(1).filter(i -> i < 3)).isInstanceOf(MaybeFilter.class); + } + + @Test void parallelFlowable_assembleInScope_subscribeNoScope() { + ParallelFlowable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Flowable.range(1, 3).parallel() + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = Flowable.concat( + Flowable.error(new IllegalStateException()), Flowable.error(new IllegalStateException())) + .parallel() + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInNoContext(source, errorSource).assertResult(1, 2, 3); + } + + @Test void parallelFlowable_assembleInScope_subscribeInScope() { + ParallelFlowable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Flowable.range(1, 3).parallel() + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = Flowable.concat( + Flowable.error(new IllegalStateException()), Flowable.error(new IllegalStateException())) + .parallel() + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source, errorSource).assertResult(1, 2, 3); + } + + @Test void parallelFlowable_assembleNoScope_subscribeInScope() { + ParallelFlowable source = Flowable.range(1, 3).parallel() + .doOnNext(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + ParallelFlowable errorSource = Flowable.concat( + Flowable.error(new IllegalStateException()), Flowable.error(new IllegalStateException())) + .parallel() + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + + subscribeInDifferentContext(source, errorSource).assertResult(1, 2, 3); + } + + @Test void parallelFlowable_unwrappedWhenNotInScope() { + assertThat(Flowable.range(1, 3).parallel()).isInstanceOf(ParallelFromPublisher.class); + } + + @Test void parallelFlowable_conditional_assembleInScope_subscribeNoScope() { + ParallelFlowable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Flowable.range(1, 3).parallel() + .filter(lessThanThreeInAssemblyContext) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = Flowable.concat( + Flowable.error(new IllegalStateException()), Flowable.error(new IllegalStateException())) + .parallel() + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInNoContext(source, errorSource).assertResult(1, 2); + } + + @Test void parallelFlowable_conditional_assembleInScope_subscribeInScope() { + ParallelFlowable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Flowable.range(1, 3).parallel() + .filter(lessThanThreeInAssemblyContext) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = Flowable.concat( + Flowable.error(new IllegalStateException()), Flowable.error(new IllegalStateException())) + .parallel() + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source, errorSource).assertResult(1, 2); + } + + @Test void parallelFlowable_conditional_assembleNoScope_subscribeInScope() { + ParallelFlowable source = Flowable.range(1, 3).parallel() + .filter(lessThanThreeInSubscribeContext) + .doOnNext(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + ParallelFlowable errorSource = Flowable.concat( + Flowable.error(new IllegalStateException()), Flowable.error(new IllegalStateException())) + .parallel() + .filter(lessThanThreeInSubscribeContext) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + + subscribeInDifferentContext(source, errorSource).assertResult(1, 2); + } + + @Test void parallelFlowable_conditional_unwrappedWhenNotInScope() { + assertThat(Flowable.range(1, 3).parallel().filter(i -> i < 3)) + .isInstanceOf(ParallelFilter.class); + } + + @Test void connectableFlowable_assembleInScope_subscribeNoScope() { + ConnectableFlowable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Flowable.range(1, 3) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext).publish(); + errorSource = Flowable.error(new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext).publish(); + } + + subscribeInNoContext( + source.autoConnect().toObservable(), errorSource.autoConnect().toObservable() + ).assertResult(1, 2, 3); + } + + @Test void connectableFlowable_assembleInScope_subscribeInScope() { + ConnectableFlowable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Flowable.range(1, 3) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext).publish(); + errorSource = Flowable.error(new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext).publish(); + } + + subscribeInDifferentContext( + source.autoConnect().toObservable(), errorSource.autoConnect().toObservable() + ).assertResult(1, 2, 3); + } + + @Test void connectableFlowable_assembleNoScope_subscribeInScope() { + ConnectableFlowable source = Flowable.range(1, 3) + .doOnNext(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext).publish(); + ConnectableFlowable errorSource = Flowable.error(new IllegalStateException()) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext).publish(); + + subscribeInDifferentContext( + source.autoConnect().toObservable(), errorSource.autoConnect().toObservable() + ).assertResult(1, 2, 3); + } + + @Test void connectableFlowable_unwrappedWhenNotInScope() { + assertThat(Flowable.range(1, 3).publish()).isInstanceOf(FlowablePublish.class); + } + + // NOTE: we aren't doing separate tests for conditional ConnectableFlowable as there are no + // operations on the type that are conditional (ex filter) + + @Test void connectableObservable_assembleInScope_subscribeNoScope() { + ConnectableObservable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Observable.range(1, 3) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext).publish(); + errorSource = Observable.error(new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext).publish(); + } + + subscribeInNoContext(source.autoConnect(), errorSource.autoConnect()).assertResult(1, 2, 3); + } + + @Test void connectableObservable_assembleInScope_subscribeInScope() { + ConnectableObservable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = Observable.range(1, 3) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext).publish(); + errorSource = Observable.error(new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext).publish(); + } + + subscribeInDifferentContext(source.autoConnect(), errorSource.autoConnect()) + .assertResult(1, 2, 3); + } + + @Test void connectableObservable_assembleNoScope_subscribeInScope() { + ConnectableObservable source = Observable.range(1, 3) + .doOnNext(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext).publish(); + ConnectableObservable errorSource = + Observable.error(new IllegalStateException()) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext).publish(); + + subscribeInDifferentContext(source.autoConnect(), errorSource.autoConnect()) + .assertResult(1, 2, 3); + } + + @Test void connectableObservable_unwrappedWhenNotInScope() { + assertThat(Observable.range(1, 3).publish()).isInstanceOf(ObservablePublish.class); + } + + // NOTE: we aren't doing separate tests for conditional ConnectableObservable as there are no + // operations on the type that are conditional (ex filter) + + @Test void callable_completable_assembleInScope_subscribeNoScope() { + Completable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableCompletable(null) + .doOnComplete(this::assertInAssemblyContext); + errorSource = callableCompletable(null, new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInNoContext(source.toObservable(), errorSource.toObservable()).assertResult(); + } + + @Test void callable_completable_assembleInScope_subscribeInScope() { + Completable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableCompletable(subscribeContext) + .doOnComplete(this::assertInAssemblyContext); + errorSource = callableCompletable(subscribeContext, new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(); + } + + @Test void callable_completable_assembleNoScope_subscribeInScope() { + Completable source = callableCompletable(subscribeContext) + .doOnComplete(this::assertInSubscribeContext); + Completable errorSource = callableCompletable(subscribeContext, new IllegalStateException()) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(); + } + + @Test void callable_completable_unwrappedWhenNotInScope() { + assertThat(callableCompletable(null)).isInstanceOf(CallableCompletable.class); + } + + // Callable should inherit the subscribing context. Ensure we don't accidentally instrument it. + @Test void callable_completable_uninstrumented() throws Exception { + currentTraceContext = throwingCurrentTraceContext; + setup(); + + // currentTraceContext.newScope would throw and break the test if call() was instrumented + ((Callable) callableCompletable(subscribeContext)).call(); + } + + @Test void callable_flowable_assembleInScope_subscribeNoScope() { + Flowable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableFlowable(null) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = callableFlowable(null, new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInNoContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_flowable_assembleInScope_subscribeInScope() { + Flowable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableFlowable(subscribeContext) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = callableFlowable(subscribeContext, new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_flowable_assembleNoScope_subscribeInScope() { + Flowable source = callableFlowable(subscribeContext) + .doOnNext(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + Flowable errorSource = callableFlowable(subscribeContext, new IllegalStateException()) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_flowable_unwrappedWhenNotInScope() { + assertThat(callableFlowable(null)).isInstanceOf(CallableFlowable.class); + } + + @Test void callable_flowable_conditional_assembleInScope_subscribeNoScope() { + Flowable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableFlowable(null) + .filter(lessThanThreeInAssemblyContext) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = callableFlowable(null, new IllegalStateException()) + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInNoContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_flowable_conditional_assembleInScope_subscribeInScope() { + Flowable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableFlowable(subscribeContext) + .filter(lessThanThreeInAssemblyContext) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = callableFlowable(subscribeContext, new IllegalStateException()) + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_flowable_conditional_assembleNoScope_subscribeInScope() { + Flowable source = callableFlowable(subscribeContext) + .filter(lessThanThreeInSubscribeContext) + .doOnNext(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + Flowable errorSource = callableFlowable(subscribeContext, new IllegalStateException()) + .filter(lessThanThreeInSubscribeContext) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_flowable_conditional_unwrappedWhenNotInScope() { + assertThat(callableFlowable(null).filter(i -> i < 3)) + .isInstanceOf(FlowableFilter.class); + } + + // Callable should inherit the subscribing context. Ensure we don't accidentally instrument it. + @Test void callable_flowable_uninstrumented() throws Exception { + currentTraceContext = throwingCurrentTraceContext; + setup(); + + // currentTraceContext.newScope would throw and break the test if call() was instrumented + ((Callable) callableFlowable(subscribeContext)).call(); + } + + @Test void callable_observable_assembleInScope_subscribeNoScope() { + Observable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableObservable(null) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = callableObservable(null, new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInNoContext(source, errorSource).assertResult(1); + } + + @Test void callable_observable_assembleInScope_subscribeInScope() { + Observable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableObservable(subscribeContext) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = callableObservable(subscribeContext, new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source, errorSource).assertResult(1); + } + + @Test void callable_observable_assembleNoScope_subscribeInScope() { + Observable source = callableObservable(subscribeContext) + .doOnNext(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + Observable errorSource = + callableObservable(subscribeContext, new IllegalStateException()) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + + subscribeInDifferentContext(source, errorSource).assertResult(1); + } + + @Test void callable_observable_unwrappedWhenNotInScope() { + assertThat(callableObservable(null)).isInstanceOf(CallableObservable.class); + } + + @Test void callable_observable_conditional_assembleInScope_subscribeNoScope() { + Observable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableObservable(null) + .filter(lessThanThreeInAssemblyContext) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = callableObservable(null, new IllegalStateException()) + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInNoContext(source, errorSource).assertResult(1); + } + + @Test void callable_observable_conditional_assembleInScope_subscribeInScope() { + Observable source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableObservable(subscribeContext) + .filter(lessThanThreeInAssemblyContext) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = callableObservable(subscribeContext, new IllegalStateException()) + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source, errorSource).assertResult(1); + } + + @Test void callable_observable_conditional_assembleNoScope_subscribeInScope() { + Observable source = callableObservable(subscribeContext) + .filter(lessThanThreeInSubscribeContext) + .doOnNext(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + Observable errorSource = + callableObservable(subscribeContext, new IllegalStateException()) + .filter(lessThanThreeInSubscribeContext) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + + subscribeInDifferentContext(source, errorSource).assertResult(1); + } + + @Test void callable_observable_conditional_unwrappedWhenNotInScope() { + assertThat(callableObservable(null).filter(i -> i < 3)) + .isInstanceOf(ObservableFilter.class); + } + + // Callable should inherit the subscribing context. Ensure we don't accidentally instrument it. + @Test void callable_observable_uninstrumented() throws Exception { + currentTraceContext = throwingCurrentTraceContext; + setup(); + + // currentTraceContext.newScope would throw and break the test if call() was instrumented + ((Callable) callableObservable(subscribeContext)).call(); + } + + @Test void callable_single_assembleInScope_subscribeNoScope() { + Single source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableSingle(null) + .doOnSuccess(e -> assertInAssemblyContext()); + errorSource = callableSingle(null, new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()); + } + + subscribeInNoContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_single_assembleInScope_subscribeInScope() { + Single source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableSingle(subscribeContext) + .doOnSuccess(e -> assertInAssemblyContext()); + errorSource = callableSingle(subscribeContext, new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()); + } + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_single_assembleNoScope_subscribeInScope() { + Single source = callableSingle(subscribeContext) + .doOnSuccess(e -> assertInSubscribeContext()); + Single errorSource = callableSingle(subscribeContext, new IllegalStateException()) + .doOnError(t -> assertInSubscribeContext()); + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_single_unwrappedWhenNotInScope() { + assertThat(callableSingle(null)).isInstanceOf(CallableSingle.class); + } + + @Test void callable_single_conditional_assembleInScope_subscribeNoScope() { + Maybe source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableSingle(null) + .filter(lessThanThreeInAssemblyContext) + .doOnSuccess(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = callableSingle(null, new IllegalStateException()) + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInNoContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_single_conditional_assembleInScope_subscribeInScope() { + Maybe source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableSingle(subscribeContext) + .filter(lessThanThreeInAssemblyContext) + .doOnSuccess(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = callableSingle(subscribeContext, new IllegalStateException()) + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_single_conditional_assembleNoScope_subscribeInScope() { + Maybe source = callableSingle(subscribeContext) + .filter(lessThanThreeInSubscribeContext) + .doOnSuccess(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + Maybe errorSource = callableSingle(subscribeContext, new IllegalStateException()) + .filter(lessThanThreeInSubscribeContext) + .doOnError(t -> assertInSubscribeContext()); + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_single_conditional_unwrappedWhenNotInScope() { + assertThat(callableSingle(null).filter(i -> i < 3)) + .isInstanceOf(MaybeFilterSingle.class); + } + + // Callable should inherit the subscribing context. Ensure we don't accidentally instrument it. + @Test void callable_single_uninstrumented() throws Exception { + currentTraceContext = throwingCurrentTraceContext; + setup(); + + // currentTraceContext.newScope would throw and break the test if call() was instrumented + ((Callable) callableSingle(subscribeContext)).call(); + } + + @Test void callable_maybe_assembleInScope_subscribeNoScope() { + Maybe source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableMaybe(null) + .doOnSuccess(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = callableMaybe(null, new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInNoContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_maybe_assembleInScope_subscribeInScope() { + Maybe source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableMaybe(subscribeContext) + .doOnSuccess(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = callableMaybe(subscribeContext, new IllegalStateException()) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_maybe_assembleNoScope_subscribeInScope() { + Maybe source = callableMaybe(subscribeContext) + .doOnSuccess(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + Maybe errorSource = callableMaybe(subscribeContext, new IllegalStateException()) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInAssemblyContext); + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_maybe_unwrappedWhenNotInScope() { + assertThat(callableMaybe(null)).isInstanceOf(CallableMaybe.class); + } + + @Test void callable_maybe_conditional_assembleInScope_subscribeNoScope() { + Maybe source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableMaybe(null) + .filter(lessThanThreeInAssemblyContext) + .doOnSuccess(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = callableMaybe(null, new IllegalStateException()) + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInNoContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_maybe_conditional_assembleInScope_subscribeInScope() { + Maybe source, errorSource; + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + source = callableMaybe(subscribeContext) + .filter(lessThanThreeInAssemblyContext) + .doOnSuccess(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + errorSource = callableMaybe(subscribeContext, new IllegalStateException()) + .filter(lessThanThreeInAssemblyContext) + .doOnError(t -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_maybe_conditional_assembleNoScope_subscribeInScope() { + Maybe source = callableMaybe(subscribeContext) + .filter(lessThanThreeInSubscribeContext) + .doOnSuccess(e -> assertInSubscribeContext()) + .doOnComplete(this::assertInSubscribeContext); + Maybe errorSource = callableMaybe(subscribeContext, new IllegalStateException()) + .filter(lessThanThreeInSubscribeContext) + .doOnError(t -> assertInSubscribeContext()) + .doOnComplete(this::assertInAssemblyContext); + + subscribeInDifferentContext(source.toObservable(), errorSource.toObservable()).assertResult(1); + } + + @Test void callable_maybe_conditional_unwrappedWhenNotInScope() { + assertThat(callableMaybe(null).filter(i -> i < 3)) + .isInstanceOf(MaybeFilter.class); + } + + // Callable should inherit the subscribing context. Ensure we don't accidentally instrument it. + @Test void callable_maybe_uninstrumented() throws Exception { + currentTraceContext = throwingCurrentTraceContext; + setup(); + + // currentTraceContext.newScope would throw and break the test if call() was instrumented + ((Callable) callableMaybe(subscribeContext)).call(); + } + + TestObserver subscribeInNoContext(Observable source, + Observable errorSource) { + source = source.doOnSubscribe(s -> assertInNoContext()); + errorSource = errorSource.doOnSubscribe(s -> assertInNoContext()); + + errorSource.test().assertFailure(IllegalStateException.class); + return source.test(); + } + + TestObserver subscribeInNoContext(ParallelFlowable source, + ParallelFlowable errorSource) { + source = source.doOnSubscribe(s -> assertInNoContext()); + errorSource = errorSource.doOnSubscribe(s -> assertInNoContext()); + + errorSource.sequential().test().assertFailure(IllegalStateException.class); + return source.sequential().toObservable().test(); + } + + TestObserver subscribeInDifferentContext(Observable source, + Observable errorSource) { + source = source.doOnSubscribe(s -> assertInSubscribeContext()); + errorSource = errorSource.doOnSubscribe(s -> assertInSubscribeContext()); + + try (Scope scope2 = currentTraceContext.newScope(subscribeContext)) { + errorSource.test().assertFailure(IllegalStateException.class); + return source.test(); + } + } + + TestObserver subscribeInDifferentContext(ParallelFlowable source, + ParallelFlowable errorSource) { + source = source.doOnSubscribe(s -> assertInSubscribeContext()); + errorSource = errorSource.doOnSubscribe(s -> assertInSubscribeContext()); + + try (Scope scope2 = currentTraceContext.newScope(subscribeContext)) { + errorSource.sequential().test().assertFailure(IllegalStateException.class); + return source.sequential().toObservable().test(); + } + } + + void assertInNoContext() { + assertThat(currentTraceContext.get()).isNull(); + } + + void assertInAssemblyContext() { + assertThat(currentTraceContext.get()).isEqualTo(assemblyContext); + } + + void assertInSubscribeContext() { + assertThat(currentTraceContext.get()).isEqualTo(subscribeContext); + } + + Completable callableCompletable(TraceContext expectedCallContext) { + return RxJavaPlugins.onAssembly(new CallableCompletable() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + return 1; + } + }); + } + + Completable callableCompletable(TraceContext expectedCallContext, RuntimeException exception) { + return RxJavaPlugins.onAssembly(new CallableCompletable() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + throw exception; + } + }); + } + + static abstract class CallableCompletable extends Completable implements Callable { + + final CompletableFromCallable delegate = new CompletableFromCallable(this); + + @Override protected void subscribeActual(CompletableObserver o) { + delegate.subscribe(o); + } + } + + Flowable callableFlowable(TraceContext expectedCallContext) { + return RxJavaPlugins.onAssembly(new CallableFlowable() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + return 1; + } + }); + } + + Flowable callableFlowable(TraceContext expectedCallContext, RuntimeException exception) { + return RxJavaPlugins.onAssembly(new CallableFlowable() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + throw exception; + } + }); + } + + abstract class CallableFlowable extends Flowable implements Callable { + final FlowableFromCallable delegate = new FlowableFromCallable<>(this); + + @Override protected void subscribeActual(Subscriber s) { + delegate.subscribe(s); + } + } + + Observable callableObservable(TraceContext expectedCallContext) { + return RxJavaPlugins.onAssembly(new CallableObservable() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + return 1; + } + }); + } + + Observable callableObservable(TraceContext expectedCallContext, + RuntimeException exception) { + return RxJavaPlugins.onAssembly(new CallableObservable() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + throw exception; + } + }); + } + + abstract class CallableObservable extends Observable implements Callable { + final ObservableFromCallable delegate = new ObservableFromCallable<>(this); + + @Override protected void subscribeActual(Observer o) { + delegate.subscribe(o); + } + } + + Single callableSingle(TraceContext expectedCallContext) { + return RxJavaPlugins.onAssembly(new CallableSingle() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + return 1; + } + }); + } + + Single callableSingle(TraceContext expectedCallContext, RuntimeException exception) { + return RxJavaPlugins.onAssembly(new CallableSingle() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + throw exception; + } + }); + } + + abstract class CallableSingle extends Single implements Callable { + final SingleFromCallable delegate = new SingleFromCallable<>(this); + + @Override protected void subscribeActual(SingleObserver o) { + delegate.subscribe(o); + } + } + + Maybe callableMaybe(TraceContext expectedCallContext) { + return RxJavaPlugins.onAssembly(new CallableMaybe() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + return 1; + } + }); + } + + Maybe callableMaybe(TraceContext expectedCallContext, RuntimeException exception) { + return RxJavaPlugins.onAssembly(new CallableMaybe() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + throw exception; + } + }); + } + + abstract class CallableMaybe extends Maybe implements Callable { + final MaybeFromCallable delegate = new MaybeFromCallable<>(this); + + @Override protected void subscribeActual(MaybeObserver o) { + delegate.subscribe(o); + } + } + + Completable scalarCallableCompletable(TraceContext expectedCallContext) { + return RxJavaPlugins.onAssembly(new ScalarCallableCompletable() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + return 1; + } + }); + } + + Completable scalarCallableCompletable(TraceContext expectedCallContext, + RuntimeException exception) { + return RxJavaPlugins.onAssembly(new ScalarCallableCompletable() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + throw exception; + } + }); + } + + static abstract class ScalarCallableCompletable extends Completable + implements ScalarCallable { + + final CompletableFromCallable delegate = new CompletableFromCallable(this); + + @Override protected void subscribeActual(CompletableObserver o) { + delegate.subscribe(o); + } + } + + Flowable scalarCallableFlowable(TraceContext expectedCallContext) { + return RxJavaPlugins.onAssembly(new ScalarCallableFlowable() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + return 1; + } + }); + } + + Flowable scalarCallableFlowable(TraceContext expectedCallContext, + RuntimeException exception) { + return RxJavaPlugins.onAssembly(new ScalarCallableFlowable() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + throw exception; + } + }); + } + + abstract class ScalarCallableFlowable extends Flowable + implements ScalarCallable { + final FlowableFromCallable delegate = new FlowableFromCallable<>(this); + + @Override protected void subscribeActual(Subscriber s) { + delegate.subscribe(s); + } + } + + Observable scalarCallableObservable(TraceContext expectedCallContext) { + return RxJavaPlugins.onAssembly(new ScalarCallableObservable() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + return 1; + } + }); + } + + Observable scalarCallableObservable(TraceContext expectedCallContext, + RuntimeException exception) { + return RxJavaPlugins.onAssembly(new ScalarCallableObservable() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + throw exception; + } + }); + } + + abstract class ScalarCallableObservable extends Observable + implements ScalarCallable { + final ObservableFromCallable delegate = new ObservableFromCallable<>(this); + + @Override protected void subscribeActual(Observer o) { + delegate.subscribe(o); + } + } + + Single scalarCallableSingle(TraceContext expectedCallContext) { + return RxJavaPlugins.onAssembly(new ScalarCallableSingle() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + return 1; + } + }); + } + + Single scalarCallableSingle(TraceContext expectedCallContext, + RuntimeException exception) { + return RxJavaPlugins.onAssembly(new ScalarCallableSingle() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + throw exception; + } + }); + } + + abstract class ScalarCallableSingle extends Single implements ScalarCallable { + final SingleFromCallable delegate = new SingleFromCallable<>(this); + + @Override protected void subscribeActual(SingleObserver o) { + delegate.subscribe(o); + } + } + + Maybe scalarCallableMaybe(TraceContext expectedCallContext) { + return RxJavaPlugins.onAssembly(new ScalarCallableMaybe() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + return 1; + } + }); + } + + Maybe scalarCallableMaybe(TraceContext expectedCallContext, RuntimeException exception) { + return RxJavaPlugins.onAssembly(new ScalarCallableMaybe() { + @Override public Integer call() { + assertThat(currentTraceContext.get()).isEqualTo(expectedCallContext); + throw exception; + } + }); + } + + abstract class ScalarCallableMaybe extends Maybe implements ScalarCallable { + final MaybeFromCallable delegate = new MaybeFromCallable<>(this); + + @Override protected void subscribeActual(MaybeObserver o) { + delegate.subscribe(o); + } + } +} diff --git a/context/rxjava2/src/test/java/brave/context/rxjava2/CurrentTraceContextAssemblyTrackingTest.java b/context/rxjava2/src/test/java/brave/context/rxjava2/CurrentTraceContextAssemblyTrackingTest.java new file mode 100644 index 0000000000..e63008d149 --- /dev/null +++ b/context/rxjava2/src/test/java/brave/context/rxjava2/CurrentTraceContextAssemblyTrackingTest.java @@ -0,0 +1,147 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2; + +import brave.context.rxjava2.CurrentTraceContextAssemblyTracking.SavedHooks; +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.StrictCurrentTraceContext; +import brave.propagation.TraceContext; +import hu.akarnokd.rxjava2.debug.RxJavaAssemblyException; +import hu.akarnokd.rxjava2.debug.RxJavaAssemblyTracking; +import io.reactivex.Observable; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import java.io.IOException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class CurrentTraceContextAssemblyTrackingTest { + CurrentTraceContext currentTraceContext = StrictCurrentTraceContext.create(); + TraceContext assemblyContext = TraceContext.newBuilder().traceId(1L).spanId(1L).build(); + + @BeforeEach void setup() { + RxJavaPlugins.reset(); + CurrentTraceContextAssemblyTracking.create(currentTraceContext).enable(); + } + + @AfterEach void tearDown() { + CurrentTraceContextAssemblyTracking.disable(); + } + + /** This spot-checks Observable to ensure that our chaining works properly. */ + @Test void enableAndChain_runsOldHooks() { + RxJavaPlugins.reset(); + + // Sanity check that RxJavaAssemblyTracking is not already enabled + TestObserver to = newObservableThatErrs().test() + .assertFailure(IOException.class, 1, 2, 3, 4, 5); + + assertThat(RxJavaAssemblyException.find(to.errors().get(0))).isNull(); + + RxJavaAssemblyTracking.enable(); + try { + + SavedHooks h = + CurrentTraceContextAssemblyTracking.create(currentTraceContext).enableAndChain(); + + to = newObservableThatErrs().test().assertFailure(IOException.class, 1, 2, 3, 4, 5); + + // Old hooks run when there is no current trace context. + Throwable error = to.errors().get(0); + assertThat(error).hasMessage(null); // our hook didn't run as there was no trace context + assertThat(RxJavaAssemblyException.find(error)).isNotNull(); // old hook ran + + try (Scope scope = currentTraceContext.newScope(assemblyContext)) { + to = newObservableThatErrs().test().assertFailure(IOException.class, 1, 2, 3, 4, 5); + } + + // Our hook and the old hooks run when there is a current trace context. + error = to.errors().get(0); + assertThat(error).hasMessage(assemblyContext.traceIdString()); // our hook ran + assertThat(RxJavaAssemblyException.find(error)).isNotNull(); // old hook ran + + h.restore(); + } finally { + RxJavaAssemblyTracking.disable(); + } + } + + /** If we have a span in scope, the message will be the current trace ID */ + Observable newObservableThatErrs() { + return Observable.range(1, 5).concatWith(Observable.fromCallable(() -> { + TraceContext ctx = currentTraceContext.get(); + String message = ctx != null ? ctx.traceIdString() : null; + throw new IOException(message); + })); + } + + @Test void enableAndChain_restoresSavedHooks() { + RxJavaPlugins.reset(); + + RxJavaAssemblyTracking.enable(); + try { + Object o1 = RxJavaPlugins.getOnCompletableAssembly(); + Object o2 = RxJavaPlugins.getOnSingleAssembly(); + Object o3 = RxJavaPlugins.getOnMaybeAssembly(); + Object o4 = RxJavaPlugins.getOnObservableAssembly(); + Object o5 = RxJavaPlugins.getOnFlowableAssembly(); + Object o6 = RxJavaPlugins.getOnConnectableFlowableAssembly(); + Object o7 = RxJavaPlugins.getOnConnectableObservableAssembly(); + Object o8 = RxJavaPlugins.getOnParallelAssembly(); + + SavedHooks h = + CurrentTraceContextAssemblyTracking.create(currentTraceContext).enableAndChain(); + + h.restore(); + + Assertions.assertSame(o1, RxJavaPlugins.getOnCompletableAssembly()); + Assertions.assertSame(o2, RxJavaPlugins.getOnSingleAssembly()); + Assertions.assertSame(o3, RxJavaPlugins.getOnMaybeAssembly()); + Assertions.assertSame(o4, RxJavaPlugins.getOnObservableAssembly()); + Assertions.assertSame(o5, RxJavaPlugins.getOnFlowableAssembly()); + Assertions.assertSame(o6, RxJavaPlugins.getOnConnectableFlowableAssembly()); + Assertions.assertSame(o7, RxJavaPlugins.getOnConnectableObservableAssembly()); + Assertions.assertSame(o8, RxJavaPlugins.getOnParallelAssembly()); + } finally { + RxJavaAssemblyTracking.disable(); + } + } + + @Test void enable_restoresSavedHooks() { + RxJavaPlugins.reset(); + + RxJavaAssemblyTracking.enable(); + try { + CurrentTraceContextAssemblyTracking.create(currentTraceContext).enable(); + + CurrentTraceContextAssemblyTracking.disable(); + + Assertions.assertNull(RxJavaPlugins.getOnCompletableAssembly()); + Assertions.assertNull(RxJavaPlugins.getOnSingleAssembly()); + Assertions.assertNull(RxJavaPlugins.getOnMaybeAssembly()); + Assertions.assertNull(RxJavaPlugins.getOnObservableAssembly()); + Assertions.assertNull(RxJavaPlugins.getOnFlowableAssembly()); + Assertions.assertNull(RxJavaPlugins.getOnConnectableFlowableAssembly()); + Assertions.assertNull(RxJavaPlugins.getOnConnectableObservableAssembly()); + Assertions.assertNull(RxJavaPlugins.getOnParallelAssembly()); + } finally { + RxJavaAssemblyTracking.disable(); + } + } +} diff --git a/context/rxjava2/src/test/java/brave/context/rxjava2/NotYetSupportedTest.java b/context/rxjava2/src/test/java/brave/context/rxjava2/NotYetSupportedTest.java new file mode 100644 index 0000000000..06e18e4a49 --- /dev/null +++ b/context/rxjava2/src/test/java/brave/context/rxjava2/NotYetSupportedTest.java @@ -0,0 +1,158 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.StrictCurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.Flowable; +import io.reactivex.Observable; +import io.reactivex.functions.Predicate; +import io.reactivex.internal.fuseable.ConditionalSubscriber; +import io.reactivex.internal.fuseable.ScalarCallable; +import io.reactivex.internal.operators.flowable.FlowableScalarXMap; +import io.reactivex.internal.operators.observable.ObservableScalarXMap; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subscribers.TestSubscriber; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Subscriber; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class NotYetSupportedTest { + CurrentTraceContext currentTraceContext = StrictCurrentTraceContext.create(); + TraceContext assemblyContext = TraceContext.newBuilder().traceId(1L).spanId(1L).build(); + TraceContext subscribeContext = assemblyContext.toBuilder().parentId(1L).spanId(2L).build(); + + @BeforeEach void setup() { + RxJavaPlugins.reset(); + CurrentTraceContextAssemblyTracking.create(currentTraceContext).enable(); + } + + @AfterEach void tearDown() { + CurrentTraceContextAssemblyTracking.disable(); + } + + /** + * On XMap (ex {@code just(1).concatMap(..}, the source scalar callable is not passed as an input + * to the subsequent operator like {@code ObservableScalarXMap.ScalarXMapObservable}. What is + * passed is the result of {@link ScalarCallable#call()}. + * + *

Usually, this would result in lost tracking of the assembled context. However, we use a + * thread local to stash the context between {@link ScalarCallable#call()} and the next {@link + * RxJavaPlugins#onAssembly assembly hook}. + * + * @see ObservableScalarXMap#scalarXMap - references to this are operators which require stashing + */ + @Test void observable_scalarCallable_propagatesContextOnXMap() { + assertThrows(AssertionError.class, () -> { + Observable fuseable; + try (Scope scope1 = currentTraceContext.newScope(assemblyContext)) { + fuseable = Observable.just(1); + assertThat(fuseable).isInstanceOf(ScalarCallable.class); + } + + // eventhough upstream is assembled with XMap, we still inherit the fused context. + fuseable = fuseable.concatMap(Observable::just); + + assertXMapFusion(fuseable).test().assertValues(1).assertNoErrors(); + }); + } + + /** + * Same as {@link #observable_scalarCallable_propagatesContextOnXMap()}, except for Flowable. + * + * @see FlowableScalarXMap#scalarXMap - references of this will break when assembly + */ + @Test void flowable_scalarCallable_propagatesContextOnXMap() { + assertThrows(AssertionError.class, () -> { + Observable fuseable; + try (Scope scope1 = currentTraceContext.newScope(assemblyContext)) { + fuseable = Observable.just(1); + assertThat(fuseable).isInstanceOf(ScalarCallable.class); + } + + // eventhough upstream is assembled with XMap, we still inherit the fused context. + fuseable = fuseable.concatMap(Observable::just); + + assertXMapFusion(fuseable).test().assertValues(1).assertNoErrors(); + }); + } + + /** + * This is an example of "conditional micro fusion" where use use a source that supports fusion: + * {@link Flowable#range(int, int)} with an intermediate operator which supports transitive + * fusion: {@link Flowable#filter(Predicate)}. + * + *

We are looking for the assembly trace context to be visible, but specifically inside + * {@link ConditionalSubscriber#tryOnNext(Object)}, as if we wired things correctly, this will be + * called instead of {@link Subscriber#onNext(Object)}. + */ + @Test void conditionalMicroFusion() { + assertThrows(AssertionError.class, () -> { + Flowable fuseable; + try (Scope scope1 = currentTraceContext.newScope(assemblyContext)) { + // we want the fitering to occur in the assembly context + fuseable = Flowable.just(1); + assertThat(fuseable).isInstanceOf(ScalarCallable.class); + } + + // proves the assembly context is retained even after it is no longer in scope + // TODO: this lies as if you debug this you'll notice it isn't fusing with upstream + fuseable = fuseable.filter(i -> { + assertInAssemblyContext(); + return i < 3; + }); + + ConditionalTestSubscriber testSubscriber = new ConditionalTestSubscriber<>(); + try (Scope scope2 = currentTraceContext.newScope(subscribeContext)) { + // subscribing in a different scope shouldn't affect the assembly context + fuseable.subscribe(testSubscriber); + } + + testSubscriber.assertValues(1).assertNoErrors(); + }); + } + + /** This ensures we don't accidentally think we tested tryOnNext */ + class ConditionalTestSubscriber extends TestSubscriber implements ConditionalSubscriber { + + @Override public boolean tryOnNext(T value) { + super.onNext(value); + return true; + } + + @Override public void onNext(T value) { + throw new AssertionError("unexpected call to onNext: check assumptions"); + } + } + + Observable assertXMapFusion(Observable fuseable) { + return fuseable + // prove XMap fusion occurred + .doOnSubscribe(d -> { + assertThat(d).isInstanceOf(ObservableScalarXMap.ScalarDisposable.class); + }) + .doOnNext(e -> assertInAssemblyContext()) + .doOnComplete(this::assertInAssemblyContext); + } + + void assertInAssemblyContext() { + assertThat(currentTraceContext.get()).isEqualTo(assemblyContext); + } +} diff --git a/context/rxjava2/src/test/java/brave/context/rxjava2/features/ITRetrofitRxJava2.java b/context/rxjava2/src/test/java/brave/context/rxjava2/features/ITRetrofitRxJava2.java new file mode 100644 index 0000000000..0c5df3ea99 --- /dev/null +++ b/context/rxjava2/src/test/java/brave/context/rxjava2/features/ITRetrofitRxJava2.java @@ -0,0 +1,197 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.rxjava2.features; + +import brave.context.rxjava2.CurrentTraceContextAssemblyTracking; +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.StrictCurrentTraceContext; +import brave.propagation.TraceContext; +import io.reactivex.Completable; +import io.reactivex.Flowable; +import io.reactivex.Maybe; +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.Single; +import io.reactivex.disposables.Disposable; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.subscribers.TestSubscriber; +import java.io.IOException; +import java.util.function.BiConsumer; +import okhttp3.ResponseBody; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import retrofit2.CallAdapter; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import retrofit2.http.GET; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; + +/** This tests that propagation isn't lost when passing through an untraced system. */ +class ITRetrofitRxJava2 { + TraceContext context1 = TraceContext.newBuilder().traceId(1L).spanId(1L).build(); + CurrentTraceContext currentTraceContext = StrictCurrentTraceContext.create(); + CurrentTraceContextAssemblyTracking contextTracking; + + public MockWebServer server = new MockWebServer(); + CurrentTraceContextObserver currentTraceContextObserver = new CurrentTraceContextObserver(); + + @BeforeEach + public void setup() { + RxJavaPlugins.reset(); + contextTracking = CurrentTraceContextAssemblyTracking.create(currentTraceContext); + contextTracking.enable(); + } + + @AfterEach + public void tearDown() throws IOException { + CurrentTraceContextAssemblyTracking.disable(); + server.close(); + } + + interface Service { + @GET("/") + Completable completable(); + + @GET("/") + Maybe maybe(); + + @GET("/") + Observable observable(); + + @GET("/") + Single single(); + + @GET("/") + Flowable flowable(); + } + + @Test void createAsync_completable_success() { + rxjava_createAsync_success( + (service, observer) -> { + try (Scope scope = currentTraceContext.newScope(context1)) { + service.completable().subscribe(observer); + } + }); + } + + @Test void createAsync_maybe_success() { + rxjava_createAsync_success( + (service, observer) -> { + try (Scope scope = currentTraceContext.newScope(context1)) { + service.maybe().subscribe(observer); + } + }); + } + + @Test void createAsync_observable_success() { + rxjava_createAsync_success( + (service, observer) -> { + try (Scope scope = currentTraceContext.newScope(context1)) { + service.observable().subscribe(observer); + } + }); + } + + @Test void createAsync_single_success() { + rxjava_createAsync_success( + (service, observer) -> { + try (Scope scope = currentTraceContext.newScope(context1)) { + service.single().subscribe(observer); + } + }); + } + + private void rxjava_createAsync_success(BiConsumer> subscriber) { + TestObserver observer = new TestObserver<>(currentTraceContextObserver); + + Service service = service(RxJava2CallAdapterFactory.createAsync()); + subscriber.accept(service, observer); + + // enqueue later + server.enqueue(new MockResponse()); + + observer.awaitTerminalEvent(1, SECONDS); + observer.assertComplete(); + assertThat(currentTraceContextObserver.onComplete).isEqualTo(context1); + } + + @Test void createAsync_flowable_success() { + rx_createAsync_success( + (service, subscriber) -> { + try (Scope scope = currentTraceContext.newScope(context1)) { + service.flowable().subscribe(subscriber); + } + }); + } + + private void rx_createAsync_success(BiConsumer> subscriber) { + TestSubscriber observer = new TestSubscriber<>(currentTraceContextObserver); + + Service service = service(RxJava2CallAdapterFactory.createAsync()); + subscriber.accept(service, observer); + + // enqueue later + server.enqueue(new MockResponse()); + + observer.awaitTerminalEvent(1, SECONDS); + observer.assertComplete(); + assertThat(currentTraceContextObserver.onComplete).isEqualTo(context1); + } + + Service service(CallAdapter.Factory callAdapterFactory) { + return new Retrofit.Builder() + .baseUrl(server.url("/")) + .addCallAdapterFactory(callAdapterFactory) + .build() + .create(Service.class); + } + + class CurrentTraceContextObserver implements Observer, Subscriber { + volatile TraceContext onSubscribe, onNext, onError, onComplete; + + @Override + public void onSubscribe(Disposable d) { + onSubscribe = currentTraceContext.get(); + } + + @Override + public void onSubscribe(Subscription subscription) { + onSubscribe = currentTraceContext.get(); + } + + @Override + public void onNext(Object object) { + onNext = currentTraceContext.get(); + } + + @Override + public void onError(Throwable e) { + onError = currentTraceContext.get(); + } + + @Override + public void onComplete() { + onComplete = currentTraceContext.get(); + } + } +} diff --git a/context/slf4j/src/main/java/brave/context/slf4j/MDCCurrentTraceContext.java b/context/slf4j/src/main/java/brave/context/slf4j/MDCCurrentTraceContext.java new file mode 100644 index 0000000000..c013b1f2cd --- /dev/null +++ b/context/slf4j/src/main/java/brave/context/slf4j/MDCCurrentTraceContext.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.slf4j; + +import brave.internal.Nullable; +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import org.slf4j.MDC; + +/** + * Adds {@linkplain MDC} properties "traceId", "parentId" and "spanId" when a {@link + * brave.Tracer#currentSpan() span is current}. These can be used in log correlation. + * + * @deprecated use {@linkplain MDCScopeDecorator}. This will be removed in Brave v6. + */ +@Deprecated +public final class MDCCurrentTraceContext extends CurrentTraceContext { + public static MDCCurrentTraceContext create() { + return create(CurrentTraceContext.Default.inheritable()); + } + + public static MDCCurrentTraceContext create(CurrentTraceContext delegate) { + if (delegate == null) throw new NullPointerException("delegate == null"); + return new Builder(delegate).build(); + } + + static final class Builder extends CurrentTraceContext.Builder { + final CurrentTraceContext delegate; + + Builder(CurrentTraceContext delegate) { + this.delegate = delegate; + addScopeDecorator(MDCScopeDecorator.create()); + } + + @Override public MDCCurrentTraceContext build() { + return new MDCCurrentTraceContext(this); + } + } + + final CurrentTraceContext delegate; + + MDCCurrentTraceContext(Builder builder) { + super(builder); + delegate = builder.delegate; + } + + @Override public TraceContext get() { + return delegate.get(); + } + + @Override public Scope newScope(@Nullable TraceContext context) { + return decorateScope(context, delegate.newScope(context)); + } + + @Override public Scope maybeScope(TraceContext context) { + return decorateScope(context, delegate.maybeScope(context)); + } +} diff --git a/context/slf4j/src/main/java/brave/context/slf4j/MDCScopeDecorator.java b/context/slf4j/src/main/java/brave/context/slf4j/MDCScopeDecorator.java index 50a928528a..e9d94ed029 100644 --- a/context/slf4j/src/main/java/brave/context/slf4j/MDCScopeDecorator.java +++ b/context/slf4j/src/main/java/brave/context/slf4j/MDCScopeDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,6 +14,7 @@ package brave.context.slf4j; import brave.baggage.BaggageFields; +import brave.baggage.CorrelationScopeConfig.SingleCorrelationField; import brave.baggage.CorrelationScopeDecorator; import brave.internal.CorrelationContext; import brave.internal.Nullable; @@ -61,6 +62,23 @@ public static CorrelationScopeDecorator.Builder newBuilder() { return new Builder(); } + /** + * Returns a scope decorator that configures {@link BaggageFields#TRACE_ID}, {@link + * BaggageFields#PARENT_ID}, {@link BaggageFields#SPAN_ID} and {@link BaggageFields#SAMPLED} + * + * @since 5.2 + * @deprecated since 5.11 use {@link #get()} or {@link #newBuilder()} + */ + @Deprecated public static CurrentTraceContext.ScopeDecorator create() { + return new Builder() + .clear() + .add(SingleCorrelationField.create(BaggageFields.TRACE_ID)) + .add(SingleCorrelationField.create(BaggageFields.PARENT_ID)) + .add(SingleCorrelationField.create(BaggageFields.SPAN_ID)) + .add(SingleCorrelationField.create(BaggageFields.SAMPLED)) + .build(); + } + static final class Builder extends CorrelationScopeDecorator.Builder { Builder() { super(MDCContext.INSTANCE); diff --git a/context/slf4j/src/test/java/brave/context/slf4j/MDCCurrentTraceContextTest.java b/context/slf4j/src/test/java/brave/context/slf4j/MDCCurrentTraceContextTest.java new file mode 100644 index 0000000000..a177bc7807 --- /dev/null +++ b/context/slf4j/src/test/java/brave/context/slf4j/MDCCurrentTraceContextTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.context.slf4j; + +import brave.internal.Nullable; +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import brave.test.propagation.CurrentTraceContextTest; +import java.util.function.Supplier; +import org.slf4j.MDC; + +import static org.assertj.core.api.Assertions.assertThat; + +class MDCCurrentTraceContextTest extends CurrentTraceContextTest { + @Override protected Class> builderSupplier() { + return BuilderSupplier.class; + } + + static class BuilderSupplier implements Supplier { + @Override public CurrentTraceContext.Builder get() { + return new MDCCurrentTraceContext.Builder(CurrentTraceContext.Default.create()); + } + } + + @Override protected void verifyImplicitContext(@Nullable TraceContext context) { + if (context != null) { + assertThat(MDC.get("traceId")) + .isEqualTo(context.traceIdString()); + assertThat(MDC.get("parentId")) + .isEqualTo(context.parentIdString()); + assertThat(MDC.get("spanId")) + .isEqualTo(context.spanIdString()); + assertThat(MDC.get("sampled")) + .isEqualTo(context.sampled() != null ? context.sampled().toString() : null); + } else { + assertThat(MDC.get("traceId")) + .isNull(); + assertThat(MDC.get("parentId")) + .isNull(); + assertThat(MDC.get("spanId")) + .isNull(); + assertThat(MDC.get("sampled")) + .isNull(); + } + } +} diff --git a/instrumentation/README.md b/instrumentation/README.md index 8e76945ad6..a32d6d766c 100644 --- a/instrumentation/README.md +++ b/instrumentation/README.md @@ -6,6 +6,7 @@ example, the directory "servlet" includes the artifact "brave-instrumentation-se Here's a brief overview of what's packaged here: * [dubbo](dubbo/README.md) - Tracing filter for RPC providers and consumers in [Apache Dubbo](http://dubbo.apache.org/en-us/) +* [dubbo-rpc](dubbo-rpc/README.md) - Tracing filter for RPC providers and consumers in [Alibaba Dubbo](http://dubbo.io/books/dubbo-user-book-en/) * [grpc](grpc/README.md) - Tracing client and server interceptors for [grpc](github.com/grpc/grpc-java) * [httpasyncclient](httpasyncclient/README.md) - Tracing decorator for [Apache HttpClient](https://hc.apache.org/httpcomponents-asyncclient-dev/) 4.0+ * [httpclient](httpclient/README.md) - Tracing decorator for [Apache HttpClient](http://hc.apache.org/httpcomponents-client-4.4.x/index.html) 4.3+ @@ -20,7 +21,9 @@ Here's a brief overview of what's packaged here: * [mysql8](mysql8/README.md) - Tracing MySQL v8 statement interceptor * [netty-codec-http](netty-codec-http/README.md) - Tracing handler for [Netty](http://netty.io/) 4.x http servers * [okhttp3](okhttp3/README.md) - Tracing decorators for [OkHttp](https://github.com/square/okhttp) 3.x +* [p6spy](p6spy/README.md) - Tracing event listener for [P6Spy](https://github.com/p6spy/p6spy) (a proxy for calls to your JDBC driver) * [servlet](servlet/README.md) - Tracing filter for Servlet 2.5+ (including Async) +* [sparkjava](sparkjava/README.md) - Tracing filters and exception handlers for [SparkJava](http://sparkjava.com/) * [spring-rabbit](spring-rabbit/README.md) - Tracing MessagePostProcessor and ListenerAdvice for [Spring Rabbit](https://spring.io/guides/gs/messaging-rabbitmq/) * [spring-web](spring-web/README.md) - Tracing interceptor for [Spring RestTemplate](https://spring.io/guides/gs/consuming-rest/) * [spring-webmvc](spring-webmvc/README.md) - Tracing filter and span customizing interceptors for [Spring WebMVC](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html) diff --git a/instrumentation/benchmarks/pom.xml b/instrumentation/benchmarks/pom.xml index 60c43f5a39..3f0afbe6de 100644 --- a/instrumentation/benchmarks/pom.xml +++ b/instrumentation/benchmarks/pom.xml @@ -130,6 +130,17 @@ ${spring5.version} + + ${project.groupId} + brave-instrumentation-sparkjava + ${project.version} + + + com.sparkjava + spark-core + ${sparkjava.version} + + ${project.groupId} brave-instrumentation-jersey-server @@ -222,6 +233,23 @@ brave-context-log4j2 ${project.version} + + + ${project.groupId} + brave-instrumentation-grpc + ${project.version} + + + io.grpc + grpc-testing + ${grpc.version} + + + junit + junit + + + diff --git a/instrumentation/benchmarks/src/main/java/brave/baggage/BaggagePropagationBenchmarks.java b/instrumentation/benchmarks/src/main/java/brave/baggage/BaggagePropagationBenchmarks.java index c2344cd118..0cd0152476 100644 --- a/instrumentation/benchmarks/src/main/java/brave/baggage/BaggagePropagationBenchmarks.java +++ b/instrumentation/benchmarks/src/main/java/brave/baggage/BaggagePropagationBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/instrumentation/benchmarks/src/main/java/brave/grpc/GrpcPropagationBenchmarks.java b/instrumentation/benchmarks/src/main/java/brave/grpc/GrpcPropagationBenchmarks.java new file mode 100644 index 0000000000..3153d0e75e --- /dev/null +++ b/instrumentation/benchmarks/src/main/java/brave/grpc/GrpcPropagationBenchmarks.java @@ -0,0 +1,181 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.grpc; + +import brave.grpc.GrpcPropagation.TagsBin; +import brave.internal.codec.HexCodec; +import brave.propagation.B3Propagation; +import brave.propagation.Propagation; +import brave.propagation.TraceContext; +import brave.propagation.TraceContext.Extractor; +import brave.propagation.TraceContext.Injector; +import brave.propagation.TraceContextOrSamplingFlags; +import io.grpc.CallOptions; +import io.grpc.Metadata; +import io.grpc.Metadata.Key; +import io.grpc.MethodDescriptor; +import io.grpc.internal.NoopClientCall; +import io.grpc.internal.NoopServerCall; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import static brave.grpc.GrpcPropagation.nameToKey; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; + +@Measurement(iterations = 5, time = 1) +@Warmup(iterations = 10, time = 1) +@Fork(3) +@BenchmarkMode(Mode.SampleTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +public class GrpcPropagationBenchmarks { + static final MethodDescriptor methodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("helloworld.Greeter/SayHello") + .setRequestMarshaller(VoidMarshaller.INSTANCE) + .setResponseMarshaller(VoidMarshaller.INSTANCE) + .build(); + + static final Propagation b3 = B3Propagation.FACTORY.get(); + static final Injector b3Injector = + b3.injector(GrpcClientRequest::propagationField); + static final Extractor b3Extractor = + b3.extractor(GrpcServerRequest::propagationField); + + static final Propagation both = GrpcPropagation.create(B3Propagation.get()); + static final Injector bothInjector = + both.injector(GrpcClientRequest::propagationField); + static final Extractor bothExtractor = + both.extractor(GrpcServerRequest::propagationField); + + static final TraceContext context = TraceContext.newBuilder() + .traceIdHigh(HexCodec.lowerHexToUnsignedLong("67891233abcdef01")) + .traceId(HexCodec.lowerHexToUnsignedLong("2345678912345678")) + .spanId(HexCodec.lowerHexToUnsignedLong("463ac35c9f6413ad")) + .sampled(true) + .build(); + static final TraceContext contextWithTags; + + static final Map> + b3NameToKey = nameToKey(b3), + bothNameToKey = nameToKey(both); + + static final GrpcServerRequest + incomingB3 = new GrpcServerRequest(b3NameToKey, new NoopServerCall<>(), new Metadata()), + incomingBoth = new GrpcServerRequest(bothNameToKey, new NoopServerCall<>(), new Metadata()), + incomingBothNoTags = new GrpcServerRequest(b3NameToKey, new NoopServerCall<>(), new Metadata()), + nothingIncoming = new GrpcServerRequest(emptyMap(), new NoopServerCall<>(), new Metadata()); + + static final byte[] tagsBytes; + + static { + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + bytes.write(0); // version + bytes.write(0); // field number + bytes.write("method" .length()); + bytes.write("method" .getBytes(UTF_8)); + bytes.write("helloworld.Greeter/SayHello" .length()); + bytes.write("helloworld.Greeter/SayHello" .getBytes(UTF_8)); + tagsBytes = bytes.toByteArray(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + contextWithTags = context.toBuilder().extra(singletonList(new TagsBin(tagsBytes))).build(); + b3Injector.inject(context, noopRequest(b3NameToKey, incomingB3.headers)); + bothInjector.inject(contextWithTags, noopRequest(bothNameToKey, incomingBoth.headers)); + bothInjector.inject(context, noopRequest(bothNameToKey, incomingBothNoTags.headers)); + } + + static GrpcClientRequest noopRequest(Map> nameToKey, Metadata headers) { + return new GrpcClientRequest(nameToKey, methodDescriptor, CallOptions.DEFAULT, + new NoopClientCall<>(), headers); + } + + @Benchmark public void inject_b3() { + GrpcClientRequest request = noopRequest(b3NameToKey, new Metadata()); + b3Injector.inject(context, request); + } + + @Benchmark public TraceContextOrSamplingFlags extract_b3() { + return b3Extractor.extract(incomingBoth); + } + + @Benchmark public TraceContextOrSamplingFlags extract_b3_nothing() { + return b3Extractor.extract(nothingIncoming); + } + + @Benchmark public void inject_both() { + GrpcClientRequest request = noopRequest(bothNameToKey, new Metadata()); + bothInjector.inject(contextWithTags, request); + } + + @Benchmark public void inject_both_no_tags() { + GrpcClientRequest request = noopRequest(bothNameToKey, new Metadata()); + bothInjector.inject(context, request); + } + + @Benchmark public TraceContextOrSamplingFlags extract_both() { + return bothExtractor.extract(incomingBoth); + } + + @Benchmark public TraceContextOrSamplingFlags extract_both_nothing() { + return bothExtractor.extract(nothingIncoming); + } + + @Benchmark public TraceContextOrSamplingFlags extract_both_no_tags() { + return bothExtractor.extract(incomingBothNoTags); + } + + // Convenience main entry-point + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .addProfiler("gc") + .include(".*" + GrpcPropagationBenchmarks.class.getSimpleName()) + .build(); + + new Runner(opt).run(); + } + + enum VoidMarshaller implements MethodDescriptor.Marshaller { + INSTANCE; + + @Override public InputStream stream(Void value) { + return new ByteArrayInputStream(new byte[0]); + } + + @Override public Void parse(InputStream stream) { + return null; + } + } +} diff --git a/instrumentation/benchmarks/src/main/java/brave/grpc/TraceContextBinaryMarshallerBenchmarks.java b/instrumentation/benchmarks/src/main/java/brave/grpc/TraceContextBinaryMarshallerBenchmarks.java new file mode 100644 index 0000000000..8828cfd619 --- /dev/null +++ b/instrumentation/benchmarks/src/main/java/brave/grpc/TraceContextBinaryMarshallerBenchmarks.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.grpc; + +import brave.internal.codec.HexCodec; +import brave.propagation.TraceContext; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +@Measurement(iterations = 5, time = 1) +@Warmup(iterations = 10, time = 1) +@Fork(3) +@BenchmarkMode(Mode.SampleTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +public class TraceContextBinaryMarshallerBenchmarks { + static final TraceContext context = TraceContext.newBuilder() + .traceIdHigh(HexCodec.lowerHexToUnsignedLong("67891233abcdef01")) + .traceId(HexCodec.lowerHexToUnsignedLong("2345678912345678")) + .spanId(HexCodec.lowerHexToUnsignedLong("463ac35c9f6413ad")) + .sampled(true) + .build(); + + static final byte[] serialized = TraceContextBinaryFormat.toBytes(context); + + @Benchmark public byte[] toBytes() { + return TraceContextBinaryFormat.toBytes(context); + } + + @Benchmark public TraceContext parseBytes() { + return TraceContextBinaryFormat.parseBytes(serialized, null); + } + + // Convenience main entry-point + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .addProfiler("gc") + .include(".*" + TraceContextBinaryMarshallerBenchmarks.class.getSimpleName()) + .build(); + + new Runner(opt).run(); + } +} diff --git a/instrumentation/benchmarks/src/main/java/brave/http/HttpClientBenchmarks.java b/instrumentation/benchmarks/src/main/java/brave/http/HttpClientBenchmarks.java index 4b6bb5e10e..b137582730 100644 --- a/instrumentation/benchmarks/src/main/java/brave/http/HttpClientBenchmarks.java +++ b/instrumentation/benchmarks/src/main/java/brave/http/HttpClientBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2019 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -32,6 +32,7 @@ import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; +import zipkin2.reporter.Reporter; import static io.undertow.util.Headers.CONTENT_TYPE; @@ -76,10 +77,10 @@ protected String baseUrl() { client = newClient(); tracedClient = newClient(HttpTracing.create( - Tracing.newBuilder().build() + Tracing.newBuilder().spanReporter(Reporter.NOOP).build() )); unsampledClient = newClient(HttpTracing.create( - Tracing.newBuilder().sampler(Sampler.NEVER_SAMPLE).build() + Tracing.newBuilder().sampler(Sampler.NEVER_SAMPLE).spanReporter(Reporter.NOOP).build() )); } diff --git a/instrumentation/benchmarks/src/main/java/brave/jersey/server/JerseyServerBenchmarks.java b/instrumentation/benchmarks/src/main/java/brave/jersey/server/JerseyServerBenchmarks.java index e477c65f6b..ea934ab1eb 100644 --- a/instrumentation/benchmarks/src/main/java/brave/jersey/server/JerseyServerBenchmarks.java +++ b/instrumentation/benchmarks/src/main/java/brave/jersey/server/JerseyServerBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -34,6 +34,7 @@ import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; +import zipkin2.reporter.Reporter; import static brave.baggage.BaggagePropagationBenchmarks.BAGGAGE_FIELD; import static io.undertow.servlet.Servlets.servlet; @@ -60,7 +61,10 @@ public static class App extends Application { public static class Unsampled extends Application { @Override public Set getSingletons() { return new LinkedHashSet<>(asList(new Resource(), TracingApplicationEventListener.create( - HttpTracing.create(Tracing.newBuilder().sampler(Sampler.NEVER_SAMPLE).build()) + HttpTracing.create(Tracing.newBuilder() + .sampler(Sampler.NEVER_SAMPLE) + .spanReporter(Reporter.NOOP) + .build()) ))); } } @@ -69,7 +73,7 @@ public static class Unsampled extends Application { public static class TracedApp extends Application { @Override public Set getSingletons() { return new LinkedHashSet<>(asList(new Resource(), TracingApplicationEventListener.create( - HttpTracing.create(Tracing.newBuilder().build()) + HttpTracing.create(Tracing.newBuilder().spanReporter(Reporter.NOOP).build()) ))); } } @@ -81,6 +85,7 @@ public static class TracedBaggageApp extends Application { HttpTracing.create(Tracing.newBuilder() .propagationFactory(BaggagePropagation.newFactoryBuilder(B3Propagation.FACTORY) .add(SingleBaggageField.remote(BAGGAGE_FIELD)).build()) + .spanReporter(Reporter.NOOP) .build()) ))); } @@ -92,6 +97,7 @@ public static class Traced128App extends Application { return new LinkedHashSet<>(asList(new Resource(), TracingApplicationEventListener.create( HttpTracing.create(Tracing.newBuilder() .traceId128Bit(true) + .spanReporter(Reporter.NOOP) .build()) ))); } diff --git a/instrumentation/benchmarks/src/main/java/brave/jms/JmsMessageProducerBenchmarks.java b/instrumentation/benchmarks/src/main/java/brave/jms/JmsMessageProducerBenchmarks.java index 892551ba0f..6c3e26f48a 100644 --- a/instrumentation/benchmarks/src/main/java/brave/jms/JmsMessageProducerBenchmarks.java +++ b/instrumentation/benchmarks/src/main/java/brave/jms/JmsMessageProducerBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -37,6 +37,7 @@ import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; +import zipkin2.reporter.Reporter; @Measurement(iterations = 5, time = 1) @Warmup(iterations = 10, time = 1) @@ -49,7 +50,7 @@ public class JmsMessageProducerBenchmarks { MessageProducer producer, tracingProducer; @Setup(Level.Trial) public void init() throws MessageNotWriteableException { - Tracing tracing = Tracing.newBuilder().build(); + Tracing tracing = Tracing.newBuilder().spanReporter(Reporter.NOOP).build(); producer = new FakeMessageProducer(); message.setText("value"); tracingProducer = TracingMessageProducer.create(producer, JmsTracing.create(tracing)); diff --git a/instrumentation/benchmarks/src/main/java/brave/kafka/clients/TracingProducerBenchmarks.java b/instrumentation/benchmarks/src/main/java/brave/kafka/clients/TracingProducerBenchmarks.java index a88df7b0f8..af45fe0563 100644 --- a/instrumentation/benchmarks/src/main/java/brave/kafka/clients/TracingProducerBenchmarks.java +++ b/instrumentation/benchmarks/src/main/java/brave/kafka/clients/TracingProducerBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2022 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,10 +14,10 @@ package brave.kafka.clients; import brave.Tracing; +import com.google.common.util.concurrent.Futures; import java.time.Duration; import java.util.List; import java.util.Map; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.kafka.clients.consumer.ConsumerGroupMetadata; @@ -47,6 +47,7 @@ import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; +import zipkin2.reporter.Reporter; @Measurement(iterations = 5, time = 1) @Warmup(iterations = 10, time = 1) @@ -56,12 +57,14 @@ @State(Scope.Thread) public class TracingProducerBenchmarks { ProducerRecord record = new ProducerRecord<>("topic", "key", "value"); - Producer producer, tracingProducer; + Producer producer, tracingProducer, tracingB3SingleProducer; @Setup(Level.Trial) public void init() { - Tracing tracing = Tracing.newBuilder().build(); + Tracing tracing = Tracing.newBuilder().spanReporter(Reporter.NOOP).build(); producer = new FakeProducer(); tracingProducer = KafkaTracing.create(tracing).producer(producer); + tracingB3SingleProducer = + KafkaTracing.newBuilder(tracing).writeB3SingleFormat(true).build().producer(producer); } @TearDown(Level.Trial) public void close() { @@ -76,6 +79,10 @@ public class TracingProducerBenchmarks { return tracingProducer.send(record).get(); } + @Benchmark public RecordMetadata send_traced_b3Single() throws Exception { + return tracingB3SingleProducer.send(record).get(); + } + // Convenience main entry-point public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() @@ -117,7 +124,7 @@ public Future send(ProducerRecord record, Callba TopicPartition tp = new TopicPartition(record.topic(), 0); RecordMetadata rm = new RecordMetadata(tp, -1L, -1, 1L, 3, 4); if (callback != null) callback.onCompletion(rm, null); - return CompletableFuture.completedFuture(rm); + return Futures.immediateFuture(rm); } @Override public void flush() { diff --git a/instrumentation/benchmarks/src/main/java/brave/netty/http/NettyHttpServerBenchmarks.java b/instrumentation/benchmarks/src/main/java/brave/netty/http/NettyHttpServerBenchmarks.java index 2fb9cd65e1..437e9a3b42 100644 --- a/instrumentation/benchmarks/src/main/java/brave/netty/http/NettyHttpServerBenchmarks.java +++ b/instrumentation/benchmarks/src/main/java/brave/netty/http/NettyHttpServerBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -41,6 +41,7 @@ import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; +import zipkin2.reporter.Reporter; import static brave.EndToEndBenchmarks.COUNTRY_CODE; import static brave.EndToEndBenchmarks.REQUEST_ID; @@ -59,7 +60,7 @@ static class TracingDispatchHandler extends ChannelDuplexHandler { static final AttributeKey URI_ATTRIBUTE = AttributeKey.valueOf("uri"); final ChannelDuplexHandler unsampled = NettyHttpTracing.create( - Tracing.newBuilder().sampler(Sampler.NEVER_SAMPLE).build() + Tracing.newBuilder().sampler(Sampler.NEVER_SAMPLE).spanReporter(Reporter.NOOP).build() ).serverHandler(); final ChannelDuplexHandler traced = NettyHttpTracing.create( Tracing.newBuilder() @@ -69,13 +70,14 @@ static class TracingDispatchHandler extends ChannelDuplexHandler { SingleBaggageField.newBuilder(COUNTRY_CODE).addKeyName("baggage-country-code").build()) .add(SingleBaggageField.newBuilder(USER_ID).addKeyName("baggage-user-id").build()) .build()) + .spanReporter(Reporter.NOOP) .build() ).serverHandler(); final ChannelDuplexHandler tracedBaggage = NettyHttpTracing.create( - Tracing.newBuilder().build() + Tracing.newBuilder().spanReporter(Reporter.NOOP).build() ).serverHandler(); final ChannelDuplexHandler traced128 = NettyHttpTracing.create( - Tracing.newBuilder().traceId128Bit(true).build() + Tracing.newBuilder().traceId128Bit(true).spanReporter(Reporter.NOOP).build() ).serverHandler(); @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { diff --git a/instrumentation/benchmarks/src/main/java/brave/propagation/B3PropagationBenchmarks.java b/instrumentation/benchmarks/src/main/java/brave/propagation/B3PropagationBenchmarks.java index c0b08102d0..652076cb94 100644 --- a/instrumentation/benchmarks/src/main/java/brave/propagation/B3PropagationBenchmarks.java +++ b/instrumentation/benchmarks/src/main/java/brave/propagation/B3PropagationBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/instrumentation/benchmarks/src/main/java/brave/propagation/CurrentTraceContextBenchmarks.java b/instrumentation/benchmarks/src/main/java/brave/propagation/CurrentTraceContextBenchmarks.java index 1508b4ec31..f7c6241ff0 100644 --- a/instrumentation/benchmarks/src/main/java/brave/propagation/CurrentTraceContextBenchmarks.java +++ b/instrumentation/benchmarks/src/main/java/brave/propagation/CurrentTraceContextBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -86,77 +86,77 @@ public class CurrentTraceContextBenchmarks { } @Benchmark public void newScope_default() { - try (Scope scope = base.newScope(context)) { + try (Scope ws = base.newScope(context)) { } } @Benchmark public void newScope_log4j2() { - try (Scope scope = log4j2.newScope(context)) { + try (Scope ws = log4j2.newScope(context)) { } } @Benchmark public void newScope_log4j2_onlyTraceId() { - try (Scope scope = log4j2OnlyTraceId.newScope(context)) { + try (Scope ws = log4j2OnlyTraceId.newScope(context)) { } } @Benchmark public void newScope_log4j2_onlyBaggage() { - try (Scope scope = log4j2OnlyBaggage.newScope(context)) { + try (Scope ws = log4j2OnlyBaggage.newScope(context)) { } } @Benchmark public void newScope_log4j2_baggage() { - try (Scope scope = log4j2Baggage.newScope(context)) { + try (Scope ws = log4j2Baggage.newScope(context)) { } } @Benchmark public void newScope_redundant_default() { - try (Scope scope = base.newScope(context)) { + try (Scope ws = base.newScope(context)) { } } @Benchmark public void newScope_redundant_log4j2() { - try (Scope scope = log4j2.newScope(context)) { + try (Scope ws = log4j2.newScope(context)) { } } @Benchmark public void newScope_clear_default() { - try (Scope scope = base.newScope(null)) { + try (Scope ws = base.newScope(null)) { } } @Benchmark public void newScope_clear_log4j2() { - try (Scope scope = log4j2.newScope(null)) { + try (Scope ws = log4j2.newScope(null)) { } } @Benchmark public void maybeScope_default() { - try (Scope scope = base.maybeScope(context)) { + try (Scope ws = base.maybeScope(context)) { } } @Benchmark public void maybeScope_log4j2() { - try (Scope scope = log4j2.maybeScope(context)) { + try (Scope ws = log4j2.maybeScope(context)) { } } @Benchmark public void maybeScope_redundant_default() { - try (Scope scope = base.maybeScope(context)) { + try (Scope ws = base.maybeScope(context)) { } } @Benchmark public void maybeScope_redundant_log4j2() { - try (Scope scope = log4j2.maybeScope(context)) { + try (Scope ws = log4j2.maybeScope(context)) { } } @Benchmark public void maybeScope_clear_default() { - try (Scope scope = base.maybeScope(null)) { + try (Scope ws = base.maybeScope(null)) { } } @Benchmark public void maybeScope_clear_log4j2() { - try (Scope scope = log4j2.maybeScope(null)) { + try (Scope ws = log4j2.maybeScope(null)) { } } diff --git a/instrumentation/benchmarks/src/main/java/brave/servlet/ServletBenchmarks.java b/instrumentation/benchmarks/src/main/java/brave/servlet/ServletBenchmarks.java index 4713862e44..852ba574a8 100644 --- a/instrumentation/benchmarks/src/main/java/brave/servlet/ServletBenchmarks.java +++ b/instrumentation/benchmarks/src/main/java/brave/servlet/ServletBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -36,6 +36,7 @@ import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; +import zipkin2.reporter.Reporter; import static brave.baggage.BaggagePropagationBenchmarks.BAGGAGE_FIELD; import static javax.servlet.DispatcherType.REQUEST; @@ -55,14 +56,14 @@ static class HelloServlet extends HttpServlet { public static class Unsampled extends ForwardingTracingFilter { public Unsampled() { super(TracingFilter.create( - Tracing.newBuilder().sampler(Sampler.NEVER_SAMPLE).build() + Tracing.newBuilder().sampler(Sampler.NEVER_SAMPLE).spanReporter(Reporter.NOOP).build() )); } } public static class Traced extends ForwardingTracingFilter { public Traced() { - super(TracingFilter.create(Tracing.newBuilder().build())); + super(TracingFilter.create(Tracing.newBuilder().spanReporter(Reporter.NOOP).build())); } } @@ -71,6 +72,7 @@ public TracedBaggage() { super(TracingFilter.create(Tracing.newBuilder() .propagationFactory(BaggagePropagation.newFactoryBuilder(B3Propagation.FACTORY) .add(SingleBaggageField.remote(BAGGAGE_FIELD)).build()) + .spanReporter(Reporter.NOOP) .build())); } } @@ -78,7 +80,7 @@ public TracedBaggage() { public static class Traced128 extends ForwardingTracingFilter { public Traced128() { super(TracingFilter.create( - Tracing.newBuilder().traceId128Bit(true).build())); + Tracing.newBuilder().traceId128Bit(true).spanReporter(Reporter.NOOP).build())); } } diff --git a/instrumentation/benchmarks/src/main/java/brave/sparkjava/SparkBenchmarks.java b/instrumentation/benchmarks/src/main/java/brave/sparkjava/SparkBenchmarks.java new file mode 100644 index 0000000000..ed5a02c08b --- /dev/null +++ b/instrumentation/benchmarks/src/main/java/brave/sparkjava/SparkBenchmarks.java @@ -0,0 +1,132 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.sparkjava; + +import brave.Tracing; +import brave.baggage.BaggagePropagation; +import brave.baggage.BaggagePropagationConfig; +import brave.http.HttpServerBenchmarks; +import brave.propagation.B3Propagation; +import brave.sampler.Sampler; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.FilterInfo; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import spark.Request; +import spark.Response; +import spark.Spark; +import spark.servlet.SparkApplication; +import spark.servlet.SparkFilter; +import zipkin2.reporter.Reporter; + +import static brave.baggage.BaggagePropagationBenchmarks.BAGGAGE_FIELD; +import static javax.servlet.DispatcherType.REQUEST; + +public class SparkBenchmarks extends HttpServerBenchmarks { + + public static class NotTraced implements SparkApplication { + @Override + public void init() { + Spark.get("/nottraced", (Request request, Response response) -> "hello world"); + } + } + + public static class Unsampled implements SparkApplication { + SparkTracing sparkTracing = SparkTracing.create( + Tracing.newBuilder().sampler(Sampler.NEVER_SAMPLE).spanReporter(Reporter.NOOP).build() + ); + + @Override + public void init() { + Spark.before(sparkTracing.before()); + Spark.get("/unsampled", (Request request, Response response) -> "hello world"); + Spark.afterAfter(sparkTracing.afterAfter()); + } + } + + public static class Traced implements SparkApplication { + SparkTracing sparkTracing = SparkTracing.create( + Tracing.newBuilder().spanReporter(Reporter.NOOP).build() + ); + + @Override + public void init() { + Spark.before(sparkTracing.before()); + Spark.get("/traced", (Request request, Response response) -> "hello world"); + Spark.afterAfter(sparkTracing.afterAfter()); + } + } + + public static class TracedBaggage implements SparkApplication { + SparkTracing sparkTracing = SparkTracing.create( + Tracing.newBuilder() + .propagationFactory(BaggagePropagation.newFactoryBuilder(B3Propagation.FACTORY) + .add(BaggagePropagationConfig.SingleBaggageField.remote(BAGGAGE_FIELD)).build()) + .spanReporter(Reporter.NOOP).build() + ); + + @Override + public void init() { + Spark.before(sparkTracing.before()); + Spark.get("/tracedBaggage", (Request request, Response response) -> { + BAGGAGE_FIELD.updateValue("FO"); + return "hello world"; + }); + Spark.afterAfter(sparkTracing.afterAfter()); + } + } + + public static class Traced128 implements SparkApplication { + SparkTracing sparkTracing = SparkTracing.create( + Tracing.newBuilder().traceId128Bit(true).spanReporter(Reporter.NOOP).build() + ); + + @Override + public void init() { + Spark.before(sparkTracing.before()); + Spark.get("/traced128", (Request request, Response response) -> "hello world"); + Spark.afterAfter(sparkTracing.afterAfter()); + } + } + + @Override protected void init(DeploymentInfo servletBuilder) { + servletBuilder + .addFilter(new FilterInfo("NotTraced", SparkFilter.class) + .addInitParam("applicationClass", NotTraced.class.getName())) + .addFilterUrlMapping("NotTraced", "/*", REQUEST) + .addFilter(new FilterInfo("Unsampled", SparkFilter.class) + .addInitParam("applicationClass", Unsampled.class.getName())) + .addFilterUrlMapping("Unsampled", "/unsampled", REQUEST) + .addFilter(new FilterInfo("Traced", SparkFilter.class) + .addInitParam("applicationClass", Traced.class.getName())) + .addFilterUrlMapping("Traced", "/traced", REQUEST) + .addFilter(new FilterInfo("TracedBaggage", SparkFilter.class) + .addInitParam("applicationClass", TracedBaggage.class.getName())) + .addFilterUrlMapping("TracedBaggage", "/tracedBaggage", REQUEST) + .addFilter(new FilterInfo("Traced128", SparkFilter.class) + .addInitParam("applicationClass", Traced128.class.getName())) + .addFilterUrlMapping("Traced128", "/traced128", REQUEST); + } + + // Convenience main entry-point + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(".*" + SparkBenchmarks.class.getSimpleName() + ".*") + .build(); + + new Runner(opt).run(); + } +} diff --git a/instrumentation/benchmarks/src/main/java/brave/spring/rabbit/TracingMessagePostProcessorBenchmarks.java b/instrumentation/benchmarks/src/main/java/brave/spring/rabbit/TracingMessagePostProcessorBenchmarks.java index faddb579b2..68dba1dcd4 100644 --- a/instrumentation/benchmarks/src/main/java/brave/spring/rabbit/TracingMessagePostProcessorBenchmarks.java +++ b/instrumentation/benchmarks/src/main/java/brave/spring/rabbit/TracingMessagePostProcessorBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2019 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -33,6 +33,7 @@ import org.openjdk.jmh.runner.options.OptionsBuilder; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageBuilder; +import zipkin2.reporter.Reporter; @Measurement(iterations = 5, time = 1) @Warmup(iterations = 10, time = 1) @@ -42,11 +43,14 @@ @State(Scope.Thread) public class TracingMessagePostProcessorBenchmarks { Message message = MessageBuilder.withBody(new byte[0]).build(); - TracingMessagePostProcessor tracingPostProcessor; + TracingMessagePostProcessor tracingPostProcessor, tracingB3SinglePostProcessor; @Setup(Level.Trial) public void init() { - Tracing tracing = Tracing.newBuilder().build(); + Tracing tracing = Tracing.newBuilder().spanReporter(Reporter.NOOP).build(); tracingPostProcessor = new TracingMessagePostProcessor(SpringRabbitTracing.create(tracing)); + tracingB3SinglePostProcessor = new TracingMessagePostProcessor( + SpringRabbitTracing.newBuilder(tracing).writeB3SingleFormat(true).build() + ); } @TearDown(Level.Trial) public void close() { @@ -57,6 +61,10 @@ public class TracingMessagePostProcessorBenchmarks { return tracingPostProcessor.postProcessMessage(message); } + @Benchmark public Message send_traced_b3Single() { + return tracingB3SinglePostProcessor.postProcessMessage(message); + } + // Convenience main entry-point public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() diff --git a/instrumentation/dubbo-rpc/README.md b/instrumentation/dubbo-rpc/README.md new file mode 100644 index 0000000000..673d9e1703 --- /dev/null +++ b/instrumentation/dubbo-rpc/README.md @@ -0,0 +1,134 @@ +# brave-instrumentation-dubbo-rpc + +## Deprecated + +Please use [brave-instrumentation-dubbo](../brave-instrumentation-dubbo) with +Apache Dubbo, as Alibaba Dubbo is no longer maintained. This module will be +removed in Brave v6. + +## Overview + +This is a tracing filter for RPC providers and consumers in [Dubbo 2.6+](http://dubbo.apache.org/en-us/docs/dev/impls/filter.html) + +When used on a consumer, `TracingFilter` adds trace state as attachments +to outgoing requests. When a provider, it extracts trace state from +incoming requests. In either case, the filter reports to Zipkin how long +each request took, along with any error information. + +Note: A Dubbo Provider is a server, and a Dubbo Consumer is a client in +Zipkin terminology. + +## Configuration +The filter "tracing" requires an extension of type `brave.rpc.RpcTracing` named +"rpcTracing" configured. Once that's configured, you assign the filter to your +providers and consumers like so: + +Here's an example of with Spring 2.5+ [XML](http://dubbo.apache.org/en-us/docs/user/references/xml/dubbo-consumer.html) +```xml + + + +``` + +Here's an example with dubbo.properties: +```properties +dubbo.provider.filter=tracing +dubbo.consumer.filter=tracing +``` + +### Registering the `brave.rpc.RpcTracing` extension with Spring +Most typically, the `brave.rpc.RpcTracing` extension is provided by Spring, so +have this in place before proceeding. The bean must be named "rpcTracing" + +Here's an example in [XML](../../spring-beans/README.md). + +### Registering the `brave.rpc.RpcTracing` extension with Java +Dubbo supports custom extensions. You can supply your own instance of +tracing by creating and registering an extension factory: + +#### create an extension factory that returns `brave.rpc.RpcTracing` + +```java +package com.yourcompany.dubbo; + +import brave.Tracing; +import brave.rpc.RpcTracing; +import brave.rpc.RpcRuleSampler; +import com.alibaba.dubbo.common.extension.ExtensionFactory; +import zipkin2.reporter.AsyncReporter; +import zipkin2.reporter.brave.ZipkinSpanHandler; +import brave.Span; + +import static brave.rpc.RpcRequestMatchers.methodEquals; +import static brave.sampler.Matchers.and; + +public class TracingExtensionFactory implements ExtensionFactory { + + @Override public T getExtension(Class type, String name) { + if (type != RpcTracing.class) return null; + + return (T) RpcTracing.newBuilder(tracing()) + .serverSampler(serverSampler()) + .build(); + } + + RpcRuleSampler serverSampler() { + return RpcRuleSampler.newBuilder() + .putRule(methodEquals("sayHello"), Sampler.NEVER_SAMPLE) + .build(); + } + + Tracing tracing() { + return Tracing.newBuilder() + .localServiceName("my-service") + .addSpanHandler(spanHandler()) + .build(); + } + + // NOTE: When async, the spanHandler should be closed with a shutdown hook + ZipkinSpanHandler spanHandler() { + // (this dependency is io.zipkin.reporter2:zipkin-reporter-brave) + return ZipkinSpanHandler.create(AsyncReporter.builder(sender())); +--snip-- +``` + +#### Register that factory using META-INF +Make sure the following line is in `META-INF/dubbo/com.alibaba.dubbo.common.extension.ExtensionFactory` in your classpath: +``` +tracing=com.yourcompany.dubbo.TracingExtensionFactory +``` + +## Sampling and data policy + +Please read the [RPC documentation](../rpc/README.md) before proceeding, as it +covers important topics such as which tags are added to spans, and how traces +are sampled. + +### RPC model mapping + +As mentioned above, the RPC model types `RpcRequest` and `RpcResponse` allow +portable sampling decisions and tag parsing. + +Dubbo maps to this model as follows: +* `RpcRequest.service()` - `Invoker.url.serviceInterface` + * Ex. "GreeterService" for a URL "dubbo://localhost:9090?interface=brave.dubbo.GreeterService" +* `RpcRequest.method()` - `Invocation.methodName` + * When absent, this falls back to the string arg[0] to the "$invoke" method. +* `RpcResponse.errorCode()` - The constant name for `RpcException.code`. + * Ex. "FORBIDDEN_EXCEPTION" when `RpcException.code == 4` + +### Dubbo-specific model + +The `DubboRequest` and `DubboResponse` are available for custom sampling and +tag parsing. + +Here is an example that adds default tags, and if Dubbo, Java arguments: +```java +rpcTracing = rpcTracingBuilder + .clientRequestParser((req, context, span) -> { + RpcRequestParser.DEFAULT.parse(req, context, span); + if (req instanceof DubboRequest) { + tagArguments(((DubboRequest) req).invocation().getArguments()); + } + }).build(); +``` diff --git a/instrumentation/dubbo-rpc/bnd.bnd b/instrumentation/dubbo-rpc/bnd.bnd new file mode 100644 index 0000000000..e33364f23f --- /dev/null +++ b/instrumentation/dubbo-rpc/bnd.bnd @@ -0,0 +1,7 @@ +# We use need to import to support brave.internal.Platform +# brave.internal.Nullable is not used at runtime. +Import-Package: \ + brave.internal;braveinternal=true,\ + * +Export-Package: \ + brave.dubbo.rpc diff --git a/instrumentation/dubbo-rpc/pom.xml b/instrumentation/dubbo-rpc/pom.xml new file mode 100644 index 0000000000..5bc5363255 --- /dev/null +++ b/instrumentation/dubbo-rpc/pom.xml @@ -0,0 +1,114 @@ + + + + io.zipkin.brave + brave-instrumentation-parent + 5.18.1-SNAPSHOT + + 4.0.0 + + brave-instrumentation-dubbo-rpc + Brave Instrumentation: Alibaba Dubbo + + + + brave.dubbo.rpc + + ${project.basedir}/../.. + + 2.6.12 + + + + --add-opens java.base/java.lang=ALL-UNNAMED + + + + ${maven-surefire-plugin.argLine} + --add-opens java.base/java.lang=ALL-UNNAMED + --add-opens java.base/java.lang.reflect=ALL-UNNAMED + --add-opens java.base/java.math=ALL-UNNAMED + + + + + + ${project.groupId} + brave-instrumentation-rpc + ${project.version} + + + com.alibaba + dubbo + ${dubbo.version} + provided + + + + + io.netty + netty-all + ${netty.version} + test + + + ${project.groupId} + brave-tests + ${project.version} + test + + + + + + release + + + 1.6 + 1.6 + 6 + + + + + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + enforce-java + + enforce + + + + + + [11,12) + + + + + + + + + + + diff --git a/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboClientRequest.java b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboClientRequest.java new file mode 100644 index 0000000000..34bce9e22d --- /dev/null +++ b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboClientRequest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.Span; +import brave.rpc.RpcClientRequest; +import com.alibaba.dubbo.common.URL; +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; +import java.util.Map; + +final class DubboClientRequest extends RpcClientRequest implements DubboRequest { + final Invoker invoker; + final Invocation invocation; + final Map attachments; + + DubboClientRequest(Invoker invoker, Invocation invocation, Map attachments) { + if (invoker == null) throw new NullPointerException("invoker == null"); + if (invocation == null) throw new NullPointerException("invocation == null"); + if (attachments == null) throw new NullPointerException("attachments == null"); + this.invoker = invoker; + this.invocation = invocation; + this.attachments = attachments; + } + + @Override public Invoker invoker() { + return invoker; + } + + @Override public Invocation invocation() { + return invocation; + } + + /** Returns the {@link Invocation}. */ + @Override public Invocation unwrap() { + return invocation; + } + + /** + * Returns the method name of the invocation or the first string arg of an "$invoke" method. + */ + @Override public String method() { + return DubboParser.method(invocation); + } + + /** + * Returns the {@link URL#getServiceInterface() service interface} of the invoker. + */ + @Override public String service() { + return DubboParser.service(invoker); + } + + @Override public boolean parseRemoteIpAndPort(Span span) { + return DubboParser.parseRemoteIpAndPort(span); + } + + @Override protected void propagationField(String keyName, String value) { + attachments.put(keyName, value); + } +} diff --git a/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboClientResponse.java b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboClientResponse.java new file mode 100644 index 0000000000..b05da97dce --- /dev/null +++ b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboClientResponse.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.internal.Nullable; +import brave.rpc.RpcClientResponse; +import com.alibaba.dubbo.rpc.Result; +import com.alibaba.dubbo.rpc.RpcException; + +final class DubboClientResponse extends RpcClientResponse implements DubboResponse { + final DubboClientRequest request; + @Nullable final Result result; + @Nullable final Throwable error; + + DubboClientResponse( + DubboClientRequest request, @Nullable Result result, @Nullable Throwable error) { + if (request == null) throw new NullPointerException("request == null"); + this.request = request; + this.result = result; + this.error = error; + } + + @Override public Result result() { + return result; + } + + @Override public Result unwrap() { + return result; + } + + @Override public DubboClientRequest request() { + return request; + } + + @Override public Throwable error() { + return error; + } + + /** Returns the string form of the {@link RpcException#getCode()} */ + @Override public String errorCode() { + return DubboParser.errorCode(error); + } +} diff --git a/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboParser.java b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboParser.java new file mode 100644 index 0000000000..1df35f24db --- /dev/null +++ b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboParser.java @@ -0,0 +1,95 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.Span; +import brave.internal.Nullable; +import brave.internal.Platform; +import com.alibaba.dubbo.common.URL; +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; +import com.alibaba.dubbo.rpc.RpcContext; +import com.alibaba.dubbo.rpc.RpcException; +import com.alibaba.dubbo.rpc.support.RpcUtils; +import java.net.InetSocketAddress; + +final class DubboParser { + /** + * Returns the method name of the invocation or the first string arg of an "$invoke" method. + * + *

Like {@link RpcUtils#getMethodName(Invocation)}, except without re-reading fields or + * returning an unhelpful "$invoke" method name. + */ + static @Nullable String method(Invocation invocation) { + String methodName = invocation.getMethodName(); + if ("$invoke".equals(methodName)) { + Object[] arguments = invocation.getArguments(); + if (arguments != null && arguments.length > 0 && arguments[0] instanceof String) { + methodName = (String) arguments[0]; + } else { + methodName = null; + } + } + return methodName != null && !methodName.isEmpty() ? methodName : null; + } + + /** + * Returns the {@link URL#getServiceInterface() service interface} of the invoker. + * + *

This was chosen as the {@link URL#getServiceName() service name} is deprecated for it. + */ + @Nullable static String service(Invoker invoker) { + URL url = invoker.getUrl(); + if (url == null) return null; + String service = url.getServiceInterface(); + return service != null && !service.isEmpty() ? service : null; + } + + static boolean parseRemoteIpAndPort(Span span) { + RpcContext rpcContext = RpcContext.getContext(); + InetSocketAddress remoteAddress = rpcContext.getRemoteAddress(); + if (remoteAddress == null) return false; + return span.remoteIpAndPort( + Platform.get().getHostString(remoteAddress), + remoteAddress.getPort() + ); + } + + /** + * This library is no-longer being released, so it should not have any maintenance on error codes. + * The error codes here were defined in 2012. + */ + @Nullable static String errorCode(Throwable error) { + if (error instanceof RpcException) { + int code = ((RpcException) error).getCode(); + switch (code) { + case RpcException.UNKNOWN_EXCEPTION: + return "UNKNOWN_EXCEPTION"; + case RpcException.NETWORK_EXCEPTION: + return "NETWORK_EXCEPTION"; + case RpcException.TIMEOUT_EXCEPTION: + return "TIMEOUT_EXCEPTION"; + case RpcException.BIZ_EXCEPTION: + return "BIZ_EXCEPTION"; + case RpcException.FORBIDDEN_EXCEPTION: + return "FORBIDDEN_EXCEPTION"; + case RpcException.SERIALIZATION_EXCEPTION: + return "SERIALIZATION_EXCEPTION"; + default: + return String.valueOf(code); + } + } + return null; + } +} diff --git a/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboRequest.java b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboRequest.java new file mode 100644 index 0000000000..4d0d88fd20 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboRequest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.rpc.RpcClientRequest; +import brave.rpc.RpcServerRequest; +import brave.rpc.RpcTracing; +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; + +/** + * Used to access Dubbo specific aspects of a client or server request. + * + *

Here's an example that adds default tags, and if Dubbo, Java arguments: + *

{@code
+ * rpcTracing = rpcTracingBuilder
+ *   .clientRequestParser((req, context, span) -> {
+ *      RpcRequestParser.DEFAULT.parse(req, context, span);
+ *      if (req instanceof DubboRequest) {
+ *        tagArguments(((DubboRequest) req).invocation().getArguments());
+ *      }
+ *   }).build();
+ * }
+ * + *

Note: Do not implement this type directly. An implementation will be + * either as {@link RpcClientRequest} or an {@link RpcServerRequest}. + * + * @see RpcTracing#clientRequestParser() + * @see RpcTracing#serverRequestParser() + * @see DubboResponse + * @since 5.12 + */ +// Note: Unlike Apache Dubbo, Alibaba Dubbo is Java 1.6+. +// This means we cannot add default methods later. However, Alibaba Dubbo is +// deprecated, so there should not be cause to add methods later. +public interface DubboRequest { + Invoker invoker(); + + Invocation invocation(); +} diff --git a/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboResponse.java b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboResponse.java new file mode 100644 index 0000000000..dfbc064105 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboResponse.java @@ -0,0 +1,51 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.internal.Nullable; +import brave.rpc.RpcClientResponse; +import brave.rpc.RpcServerResponse; +import brave.rpc.RpcTracing; +import com.alibaba.dubbo.rpc.Result; + +/** + * Used to access Dubbo specific aspects of a client or server response. + * + *

Here's an example that adds default tags, and if Dubbo, the Java result: + *

{@code
+ * rpcTracing = rpcTracingBuilder
+ *   .clientResponseParser((res, context, span) -> {
+ *      RpcResponseParser.DEFAULT.parse(res, context, span);
+ *      if (res instanceof DubboResponse) {
+ *        DubboResponse dubboResponse = (DubboResponse) res;
+ *        if (res.result() != null) {
+ *          tagJavaResult(res.result().value());
+ *        }
+ *      }
+ *   }).build();
+ * }
+ * + *

Note: Do not implement this type directly. An implementation will be + * either as {@link RpcClientResponse} or an {@link RpcServerResponse}. + * + * @see RpcTracing#clientResponseParser() + * @see RpcTracing#serverResponseParser() + * @see DubboResponse + * @since 5.12 + */ +public interface DubboResponse { + DubboRequest request(); + + @Nullable Result result(); +} diff --git a/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboServerRequest.java b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboServerRequest.java new file mode 100644 index 0000000000..0679ddc10d --- /dev/null +++ b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboServerRequest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.Span; +import brave.rpc.RpcServerRequest; +import com.alibaba.dubbo.common.URL; +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; + +final class DubboServerRequest extends RpcServerRequest implements DubboRequest { + final Invoker invoker; + final Invocation invocation; + + DubboServerRequest(Invoker invoker, Invocation invocation) { + if (invoker == null) throw new NullPointerException("invoker == null"); + if (invocation == null) throw new NullPointerException("invocation == null"); + this.invoker = invoker; + this.invocation = invocation; + } + + @Override public Invoker invoker() { + return invoker; + } + + @Override public Invocation invocation() { + return invocation; + } + + /** Returns the {@link Invocation}. */ + @Override public Invocation unwrap() { + return invocation; + } + + /** + * Returns the method name of the invocation or the first string arg of an "$invoke" method. + */ + @Override public String method() { + return DubboParser.method(invocation); + } + + /** + * Returns the {@link URL#getServiceInterface() service interface} of the invoker. + */ + @Override public String service() { + return DubboParser.service(invoker); + } + + @Override public boolean parseRemoteIpAndPort(Span span) { + return DubboParser.parseRemoteIpAndPort(span); + } + + @Override protected String propagationField(String keyName) { + return invocation.getAttachment(keyName); + } +} diff --git a/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboServerResponse.java b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboServerResponse.java new file mode 100644 index 0000000000..a7702a0c79 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/DubboServerResponse.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.internal.Nullable; +import brave.rpc.RpcServerResponse; +import com.alibaba.dubbo.rpc.Result; +import com.alibaba.dubbo.rpc.RpcException; + +final class DubboServerResponse extends RpcServerResponse implements DubboResponse { + final DubboServerRequest request; + @Nullable final Result result; + @Nullable final Throwable error; + + DubboServerResponse( + DubboServerRequest request, @Nullable Result result, @Nullable Throwable error) { + if (request == null) throw new NullPointerException("request == null"); + this.request = request; + this.result = result; + this.error = error; + } + + @Override public Result result() { + return result; + } + + @Override public Result unwrap() { + return result; + } + + @Override public DubboServerRequest request() { + return request; + } + + @Override public Throwable error() { + return error; + } + + /** Returns the string form of the {@link RpcException#getCode()} */ + @Override public String errorCode() { + return DubboParser.errorCode(error); + } +} diff --git a/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/FinishSpan.java b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/FinishSpan.java new file mode 100644 index 0000000000..8be1f42f39 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/FinishSpan.java @@ -0,0 +1,89 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.Span; +import brave.internal.Nullable; +import brave.rpc.RpcClientHandler; +import brave.rpc.RpcClientRequest; +import brave.rpc.RpcServerHandler; +import com.alibaba.dubbo.rpc.Result; + +// Intentionally the same as Apache Dubbo, even though we don't use the first arg. +// When the signature is the same, it reduces work porting bug fixes or tests +abstract class FinishSpan { // implements BiConsumer except Java 6 + static void finish(TracingFilter filter, + DubboRequest request, @Nullable Result result, @Nullable Throwable error, Span span) { + if (request instanceof RpcClientRequest) { + filter.clientHandler.handleReceive( + new DubboClientResponse((DubboClientRequest) request, result, error), span); + } else { + filter.serverHandler.handleSend( + new DubboServerResponse((DubboServerRequest) request, result, error), span); + } + } + + static FinishSpan create(TracingFilter filter, DubboRequest request, Result result, Span span) { + if (request instanceof DubboClientRequest) { + return new FinishClientSpan( + span, result, filter.clientHandler, (DubboClientRequest) request); + } + return new FinishServerSpan(span, result, filter.serverHandler, (DubboServerRequest) request); + } + + final Span span; + final Result result; + + FinishSpan(Span span, Result result) { + if (span == null) throw new NullPointerException("span == null"); + if (result == null) throw new NullPointerException("result == null"); + this.span = span; + this.result = result; + } + + /** One, but not both parameters can be {@code null}. */ + public abstract void accept(@Nullable Object unused, @Nullable Throwable error); + + static final class FinishClientSpan extends FinishSpan { + final RpcClientHandler clientHandler; + final DubboClientRequest request; + + FinishClientSpan( + Span span, Result result, RpcClientHandler clientHandler, DubboClientRequest request) { + super(span, result); + this.clientHandler = clientHandler; + this.request = request; + } + + @Override public void accept(@Nullable Object unused, @Nullable Throwable error) { + clientHandler.handleReceive(new DubboClientResponse(request, result, error), span); + } + } + + static final class FinishServerSpan extends FinishSpan { + final RpcServerHandler serverHandler; + final DubboServerRequest request; + + FinishServerSpan( + Span span, Result result, RpcServerHandler serverHandler, DubboServerRequest request) { + super(span, result); + this.serverHandler = serverHandler; + this.request = request; + } + + @Override public void accept(@Nullable Object unused, @Nullable Throwable error) { + serverHandler.handleSend(new DubboServerResponse(request, result, error), span); + } + } +} diff --git a/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/FinishSpanResponseFuture.java b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/FinishSpanResponseFuture.java new file mode 100644 index 0000000000..747775f462 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/FinishSpanResponseFuture.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.Span; +import brave.internal.Nullable; +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import com.alibaba.dubbo.remoting.RemotingException; +import com.alibaba.dubbo.remoting.exchange.ResponseCallback; +import com.alibaba.dubbo.remoting.exchange.ResponseFuture; +import com.alibaba.dubbo.rpc.Result; + +/** Ensures any callbacks finish the span. */ +final class FinishSpanResponseFuture implements ResponseFuture { + final ResponseFuture delegate; + final FinishSpan finishSpan; + final CurrentTraceContext currentTraceContext; + final @Nullable TraceContext callbackContext; + + FinishSpanResponseFuture( + ResponseFuture delegate, TracingFilter filter, DubboRequest request, Result result, Span span, + @Nullable TraceContext callbackContext) { + this.delegate = delegate; + this.finishSpan = FinishSpan.create(filter, request, result, span); + this.currentTraceContext = filter.currentTraceContext; + this.callbackContext = callbackContext; + // Ensures even if no callback added later, for example when a consumer, we finish the span + setCallback(null); + } + + @Override public Object get() throws RemotingException { + return delegate.get(); + } + + @Override public Object get(int timeoutInMillis) throws RemotingException { + return delegate.get(timeoutInMillis); + } + + @Override public void setCallback(ResponseCallback callback) { + delegate.setCallback( + TracingResponseCallback.create(callback, finishSpan, currentTraceContext, callbackContext) + ); + } + + @Override public boolean isDone() { + return delegate.isDone(); + } +} diff --git a/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/TracingFilter.java b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/TracingFilter.java new file mode 100644 index 0000000000..cfd3011a3c --- /dev/null +++ b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/TracingFilter.java @@ -0,0 +1,163 @@ +/* + * Copyright 2013-2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.Span; +import brave.Span.Kind; +import brave.SpanCustomizer; +import brave.Tag; +import brave.Tracing; +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.TraceContext; +import brave.rpc.RpcClientHandler; +import brave.rpc.RpcResponse; +import brave.rpc.RpcResponseParser; +import brave.rpc.RpcServerHandler; +import brave.rpc.RpcTracing; +import com.alibaba.dubbo.common.Constants; +import com.alibaba.dubbo.common.extension.Activate; +import com.alibaba.dubbo.common.extension.ExtensionLoader; +import com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory; +import com.alibaba.dubbo.remoting.exchange.ResponseFuture; +import com.alibaba.dubbo.rpc.Filter; +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; +import com.alibaba.dubbo.rpc.Result; +import com.alibaba.dubbo.rpc.RpcContext; +import com.alibaba.dubbo.rpc.RpcException; +import com.alibaba.dubbo.rpc.protocol.dubbo.FutureAdapter; +import java.util.Map; +import java.util.concurrent.Future; + +import static brave.internal.Throwables.propagateIfFatal; + +/** + * @deprecated please use io.zipkin.brave:brave-instrumentation-dubbo with Apache Dubbo, as Alibaba + * Dubbo is no longer maintained. Tracing support for Alibaba Dubbo will be removed in Brave v6. + */ +@Deprecated +@Activate(group = {Constants.PROVIDER, Constants.CONSUMER}, value = "tracing") +// http://dubbo.apache.org/en-us/docs/dev/impls/filter.html +// public constructor permitted to allow dubbo to instantiate this +public final class TracingFilter implements Filter { + static final Tag DUBBO_ERROR_CODE = new Tag("dubbo.error_code") { + @Override protected String parseValue(Throwable input, TraceContext context) { + if (!(input instanceof RpcException)) return null; + return String.valueOf(((RpcException) input).getCode()); + } + }; + static final RpcResponseParser LEGACY_RESPONSE_PARSER = new RpcResponseParser() { + @Override public void parse(RpcResponse response, TraceContext context, SpanCustomizer span) { + DUBBO_ERROR_CODE.tag(response.error(), span); + } + }; + + CurrentTraceContext currentTraceContext; + RpcClientHandler clientHandler; + RpcServerHandler serverHandler; + volatile boolean isInit = false; + + /** + * {@link ExtensionLoader} supplies the tracing implementation which must be named "tracing". For + * example, if using the {@link SpringExtensionFactory}, only a bean named "tracing" will be + * injected. + * + * @deprecated Since 5.12 only use {@link #setRpcTracing(RpcTracing)} + */ + @Deprecated public void setTracing(Tracing tracing) { + if (tracing == null) throw new NullPointerException("rpcTracing == null"); + setRpcTracing(RpcTracing.newBuilder(tracing) + .clientResponseParser(LEGACY_RESPONSE_PARSER) + .serverResponseParser(LEGACY_RESPONSE_PARSER) + .build()); + } + + /** + * {@link ExtensionLoader} supplies the tracing implementation which must be named "rpcTracing". + * For example, if using the {@link SpringExtensionFactory}, only a bean named "rpcTracing" will + * be injected. + * + *

Custom parsing

+ * Custom parsers, such as {@link RpcTracing#clientRequestParser()}, can use Dubbo-specific types + * {@link DubboRequest} and {@link DubboResponse} to get access such as the Java invocation or + * result. + */ + public void setRpcTracing(RpcTracing rpcTracing) { + if (rpcTracing == null) throw new NullPointerException("rpcTracing == null"); + // we don't guard on init because we intentionally want to overwrite any call to setTracing + currentTraceContext = rpcTracing.tracing().currentTraceContext(); + clientHandler = RpcClientHandler.create(rpcTracing); + serverHandler = RpcServerHandler.create(rpcTracing); + isInit = true; + } + + @Override public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { + if (!isInit) return invoker.invoke(invocation); + TraceContext invocationContext = currentTraceContext.get(); + + RpcContext rpcContext = RpcContext.getContext(); + Kind kind = rpcContext.isProviderSide() ? Kind.SERVER : Kind.CLIENT; + Span span; + DubboRequest request; + if (kind.equals(Kind.CLIENT)) { + // When A service invoke B service, then B service then invoke C service, the parentId of the + // C service span is A when read from invocation.getAttachments(). This is because + // AbstractInvoker adds attachments via RpcContext.getContext(), not the invocation. + // See com.alibaba.dubbo.rpc.protocol.AbstractInvoker(line 138) from v2.6.7 + Map attachments = RpcContext.getContext().getAttachments(); + DubboClientRequest clientRequest = new DubboClientRequest(invoker, invocation, attachments); + request = clientRequest; + span = clientHandler.handleSendWithParent(clientRequest, invocationContext); + } else { + DubboServerRequest serverRequest = new DubboServerRequest(invoker, invocation); + request = serverRequest; + span = serverHandler.handleReceive(serverRequest); + } + + boolean isSynchronous = true; + Scope scope = currentTraceContext.newScope(span.context()); + Result result = null; + Throwable error = null; + try { + result = invoker.invoke(invocation); + error = result.getException(); + Future future = rpcContext.getFuture(); // the case on async client invocation + if (future != null) { + if (!(future instanceof FutureAdapter)) { + assert false : "we can't defer the span finish unless we can access the ResponseFuture"; + return result; + } + isSynchronous = false; + ResponseFuture original = ((FutureAdapter) future).getFuture(); + // See instrumentation/RATIONALE.md for why the below response callbacks are invocation context + TraceContext callbackContext = kind == Kind.CLIENT ? invocationContext : span.context(); + ResponseFuture wrapped = + new FinishSpanResponseFuture(original, this, request, result, span, callbackContext); + RpcContext.getContext().setFuture(new FutureAdapter(wrapped)); + } + return result; + } catch (RuntimeException e) { + error = e; + throw e; + } catch (Error e) { + propagateIfFatal(e); + error = e; + throw e; + } finally { + if (isSynchronous) FinishSpan.finish(this, request, result, error, span); + scope.close(); + } + } +} diff --git a/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/TracingResponseCallback.java b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/TracingResponseCallback.java new file mode 100644 index 0000000000..4363b679c4 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/TracingResponseCallback.java @@ -0,0 +1,82 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.internal.Nullable; +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.TraceContext; +import com.alibaba.dubbo.remoting.exchange.ResponseCallback; + +/** + * Ensures deferred async calls complete a span upon success or failure callback. + * + *

This was originally a copy of {@code brave.kafka.clients.TracingCallback}. + */ +class TracingResponseCallback implements ResponseCallback { + static ResponseCallback create( + @Nullable ResponseCallback delegate, FinishSpan finishSpan, + CurrentTraceContext currentTraceContext, @Nullable TraceContext context) { + if (delegate == null) return new TracingResponseCallback(finishSpan); + return new DelegateAndFinishSpan(finishSpan, delegate, currentTraceContext, context); + } + + final FinishSpan finishSpan; + + TracingResponseCallback(FinishSpan finishSpan) { + this.finishSpan = finishSpan; + } + + @Override public void done(Object response) { + finishSpan.accept(response, null); + } + + @Override public void caught(Throwable exception) { + finishSpan.accept(null, exception); + } + + static final class DelegateAndFinishSpan extends TracingResponseCallback { + final ResponseCallback delegate; + final CurrentTraceContext current; + @Nullable final TraceContext context; + + DelegateAndFinishSpan(FinishSpan finishSpan, ResponseCallback delegate, + CurrentTraceContext currentTraceContext, @Nullable TraceContext context) { + super(finishSpan); + this.delegate = delegate; + this.current = currentTraceContext; + this.context = context; + } + + @Override public void done(Object response) { + Scope ws = current.maybeScope(context); + try { + delegate.done(response); + } finally { + super.done(response); + ws.close(); + } + } + + @Override public void caught(Throwable exception) { + Scope ws = current.maybeScope(context); + try { + delegate.caught(exception); + } finally { + super.caught(exception); + ws.close(); + } + } + } +} diff --git a/instrumentation/dubbo-rpc/src/main/resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter b/instrumentation/dubbo-rpc/src/main/resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter new file mode 100644 index 0000000000..4143040abe --- /dev/null +++ b/instrumentation/dubbo-rpc/src/main/resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter @@ -0,0 +1 @@ +tracing=brave.dubbo.rpc.TracingFilter diff --git a/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/DubboClientRequestTest.java b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/DubboClientRequestTest.java new file mode 100644 index 0000000000..fd6c114526 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/DubboClientRequestTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import com.alibaba.dubbo.common.URL; +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DubboClientRequestTest { + Invoker invoker = mock(Invoker.class); + Invocation invocation = mock(Invocation.class); + URL url = URL.valueOf("dubbo://localhost:6666?scope=remote&interface=brave.dubbo.GreeterService"); + Map attachments = new LinkedHashMap<>(); + DubboClientRequest request = new DubboClientRequest(invoker, invocation, attachments); + + @Test void service() { + when(invocation.getInvoker()).thenReturn(invoker); + when(invoker.getUrl()).thenReturn(url); + + assertThat(request.service()) + .isEqualTo("brave.dubbo.GreeterService"); + } + + @Test void method() { + when(invocation.getMethodName()).thenReturn("sayHello"); + + assertThat(request.method()).isEqualTo("sayHello"); + } + + @Test void unwrap() { + assertThat(request.unwrap()).isSameAs(invocation); + } + + @Test void invoker() { + assertThat(request.invoker()).isSameAs(invoker); + } + + @Test void invocation() { + assertThat(request.invocation()).isSameAs(invocation); + } + + @Test void propagationField() { + request.propagationField("b3", "d"); + + assertThat(attachments).containsEntry("b3", "d"); + } +} diff --git a/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/DubboClientResponseTest.java b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/DubboClientResponseTest.java new file mode 100644 index 0000000000..12ba90a439 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/DubboClientResponseTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; +import com.alibaba.dubbo.rpc.Result; +import com.alibaba.dubbo.rpc.RpcException; +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +import static com.alibaba.dubbo.rpc.RpcException.TIMEOUT_EXCEPTION; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class DubboClientResponseTest { + Invoker invoker = mock(Invoker.class); + Invocation invocation = mock(Invocation.class); + Result result = mock(Result.class); + RpcException error = new RpcException(TIMEOUT_EXCEPTION); + Map attachments = new LinkedHashMap<>(); + DubboClientRequest request = new DubboClientRequest(invoker, invocation, attachments); + DubboClientResponse response = new DubboClientResponse(request, result, error); + + @Test void request() { + assertThat(response.request()).isSameAs(request); + } + + @Test void result() { + assertThat(response.result()).isSameAs(result); + } + + @Test void unwrap() { + assertThat(response.unwrap()).isSameAs(result); + } + + @Test void error() { + assertThat(response.error()).isSameAs(error); + } + + @Test void errorCode() { + assertThat(response.errorCode()).isEqualTo("TIMEOUT_EXCEPTION"); + } +} diff --git a/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/DubboParserTest.java b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/DubboParserTest.java new file mode 100644 index 0000000000..a160a7b16c --- /dev/null +++ b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/DubboParserTest.java @@ -0,0 +1,121 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import com.alibaba.dubbo.common.URL; +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; +import com.alibaba.dubbo.rpc.RpcException; +import java.io.IOException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class DubboParserTest { + @Mock Invocation invocation; + @Mock Invoker invoker; + + @Test void method() { + when(invocation.getMethodName()).thenReturn("sayHello"); + + assertThat(DubboParser.method(invocation)) + .isEqualTo("sayHello"); + } + + @Test void method_malformed() { + when(invocation.getMethodName()).thenReturn(""); + + assertThat(DubboParser.method(invocation)).isNull(); + } + + @Test void method_invoke() { + when(invocation.getMethodName()).thenReturn("$invoke"); + when(invocation.getArguments()).thenReturn(new Object[] {"sayHello"}); + + assertThat(DubboParser.method(invocation)) + .isEqualTo("sayHello"); + } + + @Test void method_invoke_nullArgs() { + when(invocation.getMethodName()).thenReturn("$invoke"); + + assertThat(DubboParser.method(invocation)).isNull(); + } + + @Test void method_invoke_emptyArgs() { + when(invocation.getMethodName()).thenReturn("$invoke"); + when(invocation.getArguments()).thenReturn(new Object[0]); + + assertThat(DubboParser.method(invocation)).isNull(); + } + + @Test void method_invoke_nonStringArg() { + when(invocation.getMethodName()).thenReturn("$invoke"); + when(invocation.getArguments()).thenReturn(new Object[] {new Object()}); + + assertThat(DubboParser.method(invocation)).isNull(); + } + + @Test void service() { + URL url = URL.valueOf("dubbo://localhost:9090?interface=brave.dubbo.GreeterService"); + when(invoker.getUrl()).thenReturn(url); + + assertThat(DubboParser.service(invoker)) + .isEqualTo("brave.dubbo.GreeterService"); + } + + @Test void service_nullUrl() { + assertThat(DubboParser.service(invoker)).isNull(); + } + + @Test void service_nullServiceInterface() { + URL url = URL.valueOf("dubbo://localhost:9090"); + when(invoker.getUrl()).thenReturn(url); + + assertThat(DubboParser.service(invoker)).isNull(); + } + + @Test void service_malformed() { + URL url = URL.valueOf("dubbo://localhost:9090?interface="); + when(invoker.getUrl()).thenReturn(url); + + assertThat(DubboParser.service(invoker)).isNull(); + } + + @Test void errorCodes() { + assertThat(DubboParser.errorCode(null)) + .isEqualTo(DubboParser.errorCode(new IOException("timeout"))) + .isNull(); + + assertThat(DubboParser.errorCode(new RpcException(0))) + .isEqualTo("UNKNOWN_EXCEPTION"); + assertThat(DubboParser.errorCode(new RpcException(1))) + .isEqualTo("NETWORK_EXCEPTION"); + assertThat(DubboParser.errorCode(new RpcException(2))) + .isEqualTo("TIMEOUT_EXCEPTION"); + assertThat(DubboParser.errorCode(new RpcException(3))) + .isEqualTo("BIZ_EXCEPTION"); + assertThat(DubboParser.errorCode(new RpcException(4))) + .isEqualTo("FORBIDDEN_EXCEPTION"); + assertThat(DubboParser.errorCode(new RpcException(5))) + .isEqualTo("SERIALIZATION_EXCEPTION"); + assertThat(DubboParser.errorCode(new RpcException(6))) + .isEqualTo("6"); // this will catch drift if Dubbo adds another code + } +} diff --git a/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/DubboServerRequestTest.java b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/DubboServerRequestTest.java new file mode 100644 index 0000000000..bb45585225 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/DubboServerRequestTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import com.alibaba.dubbo.common.URL; +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DubboServerRequestTest { + Invoker invoker = mock(Invoker.class); + Invocation invocation = mock(Invocation.class); + URL url = URL.valueOf("dubbo://localhost:6666?scope=remote&interface=brave.dubbo.GreeterService"); + DubboServerRequest request = new DubboServerRequest(invoker, invocation); + + @Test void service() { + when(invocation.getInvoker()).thenReturn(invoker); + when(invoker.getUrl()).thenReturn(url); + + assertThat(request.service()) + .isEqualTo("brave.dubbo.GreeterService"); + } + + @Test void method() { + when(invocation.getMethodName()).thenReturn("sayHello"); + + assertThat(request.method()).isEqualTo("sayHello"); + } + + @Test void unwrap() { + assertThat(request.unwrap()).isSameAs(invocation); + } + + @Test void invoker() { + assertThat(request.invoker()).isSameAs(invoker); + } + + @Test void invocation() { + assertThat(request.invocation()).isSameAs(invocation); + } + + @Test void propagationField() { + when(invocation.getAttachment("b3")).thenReturn("d"); + + assertThat(request.propagationField("b3")).isEqualTo("d"); + } +} diff --git a/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/DubboServerResponseTest.java b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/DubboServerResponseTest.java new file mode 100644 index 0000000000..ad7ee956a2 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/DubboServerResponseTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; +import com.alibaba.dubbo.rpc.Result; +import com.alibaba.dubbo.rpc.RpcException; +import org.junit.jupiter.api.Test; + +import static com.alibaba.dubbo.rpc.RpcException.TIMEOUT_EXCEPTION; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class DubboServerResponseTest { + Invoker invoker = mock(Invoker.class); + Invocation invocation = mock(Invocation.class); + Result result = mock(Result.class); + RpcException error = new RpcException(TIMEOUT_EXCEPTION); + DubboServerRequest request = new DubboServerRequest(invoker, invocation); + DubboServerResponse response = new DubboServerResponse(request, result, error); + + @Test void request() { + assertThat(response.request()).isSameAs(request); + } + + @Test void result() { + assertThat(response.result()).isSameAs(result); + } + + @Test void unwrap() { + assertThat(response.unwrap()).isSameAs(result); + } + + @Test void error() { + assertThat(response.error()).isSameAs(error); + } + + @Test void errorCode() { + assertThat(response.errorCode()).isEqualTo("TIMEOUT_EXCEPTION"); + } +} diff --git a/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/FinishSpanTest.java b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/FinishSpanTest.java new file mode 100644 index 0000000000..23379bf0c6 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/FinishSpanTest.java @@ -0,0 +1,143 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.Span; +import brave.Span.Kind; +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; +import com.alibaba.dubbo.rpc.Result; +import java.util.Collections; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.mock; + +public class FinishSpanTest extends ITTracingFilter { + DubboClientRequest clientRequest = + new DubboClientRequest(mock(Invoker.class), mock(Invocation.class), Collections.emptyMap()); + DubboServerRequest serverRequest = + new DubboServerRequest(mock(Invoker.class), mock(Invocation.class)); + TracingFilter filter; + + @BeforeEach void setup() { + filter = init(); + } + + @Test void finish_null_result_and_error_DubboClientRequest() { + Span span = tracing.tracer().nextSpan().kind(Span.Kind.CLIENT).start(); + + FinishSpan.finish(filter, clientRequest, null, null, span); + + testSpanHandler.takeRemoteSpan(Kind.CLIENT); + } + + @Test void finish_null_result_and_error_DubboServerRequest() { + Span span = tracing.tracer().nextSpan().kind(Span.Kind.SERVER).start(); + + FinishSpan.finish(filter, serverRequest, null, null, span); + + testSpanHandler.takeRemoteSpan(Kind.SERVER); + } + + @Test void finish_result_but_null_error_DubboClientRequest() { + Span span = tracing.tracer().nextSpan().kind(Span.Kind.CLIENT).start(); + + FinishSpan.finish(filter, clientRequest, mock(Result.class), null, span); + + testSpanHandler.takeRemoteSpan(Kind.CLIENT); + } + + @Test void finish_result_but_null_error_DubboServerRequest() { + Span span = tracing.tracer().nextSpan().kind(Span.Kind.SERVER).start(); + + FinishSpan.finish(filter, serverRequest, mock(Result.class), null, span); + + testSpanHandler.takeRemoteSpan(Kind.SERVER); + } + + @Test void finish_error_but_null_result_DubboClientRequest() { + Span span = tracing.tracer().nextSpan().kind(Span.Kind.CLIENT).start(); + + Throwable error = new RuntimeException("melted"); + FinishSpan.finish(filter, clientRequest, null, error, span); + + testSpanHandler.takeRemoteSpanWithError(Kind.CLIENT, error); + } + + @Test void finish_error_but_null_result_DubboServerRequest() { + Span span = tracing.tracer().nextSpan().kind(Span.Kind.SERVER).start(); + + Throwable error = new RuntimeException("melted"); + FinishSpan.finish(filter, serverRequest, null, error, span); + + testSpanHandler.takeRemoteSpanWithError(Kind.SERVER, error); + } + + @Test void create_null_result_value_and_error_DubboClientRequest() { + Span span = tracing.tracer().nextSpan().kind(Span.Kind.CLIENT).start(); + + FinishSpan.create(filter, clientRequest, mock(Result.class), span) + .accept(null, null); + + testSpanHandler.takeRemoteSpan(Kind.CLIENT); + } + + @Test void create_null_result_value_and_error_DubboServerRequest() { + Span span = tracing.tracer().nextSpan().kind(Span.Kind.SERVER).start(); + + FinishSpan.create(filter, serverRequest, mock(Result.class), span) + .accept(null, null); + + testSpanHandler.takeRemoteSpan(Kind.SERVER); + } + + @Test void create_result_value_but_null_error_DubboClientRequest() { + Span span = tracing.tracer().nextSpan().kind(Span.Kind.CLIENT).start(); + + FinishSpan.create(filter, clientRequest, mock(Result.class), span) + .accept(new Object(), null); + + testSpanHandler.takeRemoteSpan(Kind.CLIENT); + } + + @Test void create_result_value_but_null_error_DubboServerRequest() { + Span span = tracing.tracer().nextSpan().kind(Span.Kind.SERVER).start(); + + FinishSpan.create(filter, serverRequest, mock(Result.class), span) + .accept(new Object(), null); + + testSpanHandler.takeRemoteSpan(Kind.SERVER); + } + + @Test void create_error_but_null_result_value_DubboClientRequest() { + Span span = tracing.tracer().nextSpan().kind(Span.Kind.CLIENT).start(); + + Throwable error = new RuntimeException("melted"); + FinishSpan.create(filter, clientRequest, mock(Result.class), span) + .accept(null, error); + + testSpanHandler.takeRemoteSpanWithError(Kind.CLIENT, error); + } + + @Test void create_error_but_null_result_value_DubboServerRequest() { + Span span = tracing.tracer().nextSpan().kind(Span.Kind.SERVER).start(); + + Throwable error = new RuntimeException("melted"); + FinishSpan.create(filter, serverRequest, mock(Result.class), span) + .accept(null, error); + + testSpanHandler.takeRemoteSpanWithError(Kind.SERVER, error); + } +} diff --git a/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/GraterService.java b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/GraterService.java new file mode 100644 index 0000000000..06d7b0aee7 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/GraterService.java @@ -0,0 +1,18 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +public interface GraterService { + String sayHello(String name); +} \ No newline at end of file diff --git a/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/GreeterService.java b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/GreeterService.java new file mode 100644 index 0000000000..84ddbe7409 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/GreeterService.java @@ -0,0 +1,20 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +public interface GreeterService { + String sayHello(String name); + + String sayGoodbye(String name); +} diff --git a/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/ITTracingFilter.java b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/ITTracingFilter.java new file mode 100644 index 0000000000..aad0f0c989 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/ITTracingFilter.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013-2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.rpc.RpcTracing; +import brave.test.ITRemote; +import com.alibaba.dubbo.common.extension.ExtensionLoader; +import com.alibaba.dubbo.config.ReferenceConfig; +import com.alibaba.dubbo.rpc.Filter; +import org.junit.jupiter.api.AfterEach; + +public abstract class ITTracingFilter extends ITRemote { + TestServer server = new TestServer(propagationFactory); + ReferenceConfig client; + + @AfterEach void stop() { + if (client != null) client.destroy(); + server.stop(); + } + + /** Call this after updating {@link #tracing} */ + TracingFilter init() { + TracingFilter filter = (TracingFilter) ExtensionLoader.getExtensionLoader(Filter.class) + .getExtension("tracing"); + filter.setRpcTracing(RpcTracing.create(tracing)); + return filter; + } +} diff --git a/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/ITTracingFilter_Consumer.java b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/ITTracingFilter_Consumer.java new file mode 100644 index 0000000000..72bbac68d1 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/ITTracingFilter_Consumer.java @@ -0,0 +1,286 @@ +/* + * Copyright 2013-2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.Clock; +import brave.Tag; +import brave.handler.MutableSpan; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.SamplingFlags; +import brave.propagation.TraceContext; +import brave.rpc.RpcResponseParser; +import brave.rpc.RpcRuleSampler; +import brave.rpc.RpcTracing; +import com.alibaba.dubbo.common.beanutil.JavaBeanDescriptor; +import com.alibaba.dubbo.common.extension.ExtensionLoader; +import com.alibaba.dubbo.config.ApplicationConfig; +import com.alibaba.dubbo.config.ReferenceConfig; +import com.alibaba.dubbo.rpc.Filter; +import com.alibaba.dubbo.rpc.Result; +import com.alibaba.dubbo.rpc.RpcContext; +import com.alibaba.dubbo.rpc.RpcException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static brave.Span.Kind.CLIENT; +import static brave.rpc.RpcRequestMatchers.methodEquals; +import static brave.rpc.RpcRequestMatchers.serviceEquals; +import static brave.sampler.Sampler.ALWAYS_SAMPLE; +import static brave.sampler.Sampler.NEVER_SAMPLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ITTracingFilter_Consumer extends ITTracingFilter { + ReferenceConfig wrongClient; + + @BeforeEach void setup() { + init(); + server.start(); + + String url = "dubbo://" + server.ip() + ":" + server.port() + "?scope=remote&generic=bean"; + client = new ReferenceConfig<>(); + client.setApplication(new ApplicationConfig("bean-consumer")); + client.setFilter("tracing"); + client.setInterface(GreeterService.class); + client.setUrl(url); + + wrongClient = new ReferenceConfig<>(); + wrongClient.setApplication(new ApplicationConfig("bad-consumer")); + wrongClient.setFilter("tracing"); + wrongClient.setInterface(GraterService.class); + wrongClient.setUrl(url); + } + + @Test void propagatesNewTrace() { + client.get().sayHello("jorge"); + + TraceContext extracted = server.takeRequest().context(); + assertThat(extracted.sampled()).isTrue(); + assertThat(extracted.parentIdString()).isNull(); + assertSameIds(testSpanHandler.takeRemoteSpan(CLIENT), extracted); + } + + @Test void propagatesChildOfCurrentSpan() { + TraceContext parent = newTraceContext(SamplingFlags.SAMPLED); + try (Scope scope = currentTraceContext.newScope(parent)) { + client.get().sayHello("jorge"); + } + + TraceContext extracted = server.takeRequest().context(); + assertThat(extracted.sampled()).isTrue(); + assertChildOf(extracted, parent); + assertSameIds(testSpanHandler.takeRemoteSpan(CLIENT), extracted); + } + + /** Unlike Brave 3, Brave 4 propagates trace ids even when unsampled */ + @Test void propagatesUnsampledContext() { + TraceContext parent = newTraceContext(SamplingFlags.NOT_SAMPLED); + try (Scope scope = currentTraceContext.newScope(parent)) { + client.get().sayHello("jorge"); + } + + TraceContext extracted = server.takeRequest().context(); + assertThat(extracted.sampled()).isFalse(); + assertChildOf(extracted, parent); + } + + @Test void propagatesBaggage() { + TraceContext parent = newTraceContext(SamplingFlags.SAMPLED); + try (Scope scope = currentTraceContext.newScope(parent)) { + BAGGAGE_FIELD.updateValue(parent, "joey"); + client.get().sayHello("jorge"); + } + + TraceContext extracted = server.takeRequest().context(); + assertThat(BAGGAGE_FIELD.getValue(extracted)).isEqualTo("joey"); + + testSpanHandler.takeRemoteSpan(CLIENT); + } + + @Test void propagatesBaggage_unsampled() { + TraceContext parent = newTraceContext(SamplingFlags.NOT_SAMPLED); + try (Scope scope = currentTraceContext.newScope(parent)) { + BAGGAGE_FIELD.updateValue(parent, "joey"); + client.get().sayHello("jorge"); + } + + TraceContext extracted = server.takeRequest().context(); + assertThat(BAGGAGE_FIELD.getValue(extracted)).isEqualTo("joey"); + } + + /** This prevents confusion as a blocking client should end before, the start of the next span. */ + @Test void clientTimestampAndDurationEnclosedByParent() { + TraceContext parent = newTraceContext(SamplingFlags.SAMPLED); + Clock clock = tracing.clock(parent); + + long start = clock.currentTimeMicroseconds(); + try (Scope scope = currentTraceContext.newScope(parent)) { + client.get().sayHello("jorge"); + } + long finish = clock.currentTimeMicroseconds(); + + MutableSpan clientSpan = testSpanHandler.takeRemoteSpan(CLIENT); + assertChildOf(clientSpan, parent); + assertSpanInInterval(clientSpan, start, finish); + } + + /** + * This tests that the parent is determined at the time the request was made, not when the request + * was executed. + */ + @Test void usesParentFromInvocationTime() { + TraceContext parent = newTraceContext(SamplingFlags.SAMPLED); + try (Scope scope = currentTraceContext.newScope(parent)) { + RpcContext.getContext().asyncCall(() -> client.get().sayHello("jorge")); + RpcContext.getContext().asyncCall(() -> client.get().sayHello("romeo")); + } + + try (Scope scope = currentTraceContext.newScope(null)) { + // complete within a different scope + for (int i = 0; i < 2; i++) { + TraceContext extracted = server.takeRequest().context(); + assertChildOf(extracted, parent); + } + } + + // The spans may report in a different order than the requests + for (int i = 0; i < 2; i++) { + assertChildOf(testSpanHandler.takeRemoteSpan(CLIENT), parent); + } + } + + @Test void reportsClientKindToZipkin() { + client.get().sayHello("jorge"); + + testSpanHandler.takeRemoteSpan(CLIENT); + } + + @Test void defaultSpanNameIsMethodName() { + client.get().sayHello("jorge"); + + assertThat(testSpanHandler.takeRemoteSpan(CLIENT).name()) + .isEqualTo("brave.dubbo.rpc.GreeterService/sayHello"); + } + + @Test void onTransportException_setsError() { + server.stop(); + + assertThatThrownBy(() -> client.get().sayHello("jorge")) + .isInstanceOf(RpcException.class); + + testSpanHandler.takeRemoteSpanWithErrorMessage(CLIENT, ".*RemotingException.*"); + } + + @Test void onTransportException_setsError_async() { + server.stop(); + + RpcContext.getContext().asyncCall(() -> client.get().sayHello("romeo")); + + testSpanHandler.takeRemoteSpanWithErrorMessage(CLIENT, ".*RemotingException.*"); + } + + @Test void finishesOneWay() { + RpcContext.getContext().asyncCall(() -> { + client.get().sayHello("romeo"); + }); + + testSpanHandler.takeRemoteSpan(CLIENT); + } + + @Test void setsError_onUnimplemented() { + assertThatThrownBy(() -> wrongClient.get().sayHello("jorge")) + .isInstanceOf(RpcException.class); + + MutableSpan span = + testSpanHandler.takeRemoteSpanWithErrorMessage(CLIENT, ".*Not found exported service.*"); + assertThat(span.tags()) + .containsEntry("rpc.error_code", "NETWORK_EXCEPTION"); + } + + /** Shows if you aren't using RpcTracing, the old "dubbo.error_code" works */ + @Test void setsError_onUnimplemented_legacy() { + ((TracingFilter) ExtensionLoader.getExtensionLoader(Filter.class) + .getExtension("tracing")).isInit = false; + + ((TracingFilter) ExtensionLoader.getExtensionLoader(Filter.class) + .getExtension("tracing")) + .setTracing(tracing); + + assertThatThrownBy(() -> wrongClient.get().sayHello("jorge")) + .isInstanceOf(RpcException.class); + + MutableSpan span = + testSpanHandler.takeRemoteSpanWithErrorMessage(CLIENT, ".*Not found exported service.*"); + assertThat(span.tags()) + .containsEntry("dubbo.error_code", "1"); + } + + /** Ensures the span completes on asynchronous invocation. */ + @Test void test_async_invoke() throws Exception { + client.setAsync(true); + String jorge = client.get().sayHello("jorge"); + assertThat(jorge).isNull(); + Object o = RpcContext.getContext().getFuture().get(); + assertThat(o).isNotNull(); + + testSpanHandler.takeRemoteSpan(CLIENT); + } + + /* RpcTracing-specific feature tests */ + + @Test void customSampler() { + RpcTracing rpcTracing = RpcTracing.newBuilder(tracing).clientSampler(RpcRuleSampler.newBuilder() + .putRule(methodEquals("sayGoodbye"), NEVER_SAMPLE) + .putRule(serviceEquals("brave.dubbo"), ALWAYS_SAMPLE) + .build()).build(); + init().setRpcTracing(rpcTracing); + + // unsampled + client.get().sayGoodbye("jorge"); + + // sampled + client.get().sayHello("jorge"); + + assertThat(testSpanHandler.takeRemoteSpan(CLIENT).name()).endsWith("sayHello"); + // @After will also check that sayGoodbye was not sampled + } + + @Test void customParser() { + Tag javaValue = new Tag("dubbo.result_value") { + @Override protected String parseValue(DubboResponse input, TraceContext context) { + Result result = input.result(); + if (result == null) return null; + Object value = result.getValue(); + if (value instanceof JavaBeanDescriptor) { + return String.valueOf(((JavaBeanDescriptor) value).getProperty("value")); + } + return null; + } + }; + + RpcTracing rpcTracing = RpcTracing.newBuilder(tracing) + .clientResponseParser((res, context, span) -> { + RpcResponseParser.DEFAULT.parse(res, context, span); + if (res instanceof DubboResponse) { + javaValue.tag((DubboResponse) res, span); + } + }).build(); + init().setRpcTracing(rpcTracing); + + String javaResult = client.get().sayHello("jorge"); + + assertThat(testSpanHandler.takeRemoteSpan(CLIENT).tags()) + .containsEntry("dubbo.result_value", javaResult); + } +} diff --git a/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/ITTracingFilter_Provider.java b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/ITTracingFilter_Provider.java new file mode 100644 index 0000000000..2c153c2c20 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/ITTracingFilter_Provider.java @@ -0,0 +1,170 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.Tag; +import brave.propagation.B3SingleFormat; +import brave.propagation.SamplingFlags; +import brave.propagation.TraceContext; +import brave.rpc.RpcResponseParser; +import brave.rpc.RpcRuleSampler; +import brave.rpc.RpcTracing; +import com.alibaba.dubbo.common.beanutil.JavaBeanDescriptor; +import com.alibaba.dubbo.config.ApplicationConfig; +import com.alibaba.dubbo.config.ReferenceConfig; +import com.alibaba.dubbo.rpc.Result; +import com.alibaba.dubbo.rpc.RpcContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static brave.Span.Kind.SERVER; +import static brave.rpc.RpcRequestMatchers.methodEquals; +import static brave.rpc.RpcRequestMatchers.serviceEquals; +import static brave.sampler.Sampler.ALWAYS_SAMPLE; +import static brave.sampler.Sampler.NEVER_SAMPLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ITTracingFilter_Provider extends ITTracingFilter { + @BeforeEach void setup() { + server.service.setFilter("tracing"); + server.service.setInterface(GreeterService.class); + server.service.setRef((method, parameterTypes, args) -> { + JavaBeanDescriptor arg = (JavaBeanDescriptor) args[0]; + if (arg.getProperty("value").equals("bad")) { + throw new IllegalArgumentException("bad"); + } + String value = currentTraceContext.get() != null + ? currentTraceContext.get().traceIdString() + : ""; + arg.setProperty("value", value); + return args[0]; + }); + server.start(); + + ReferenceConfig ref = new ReferenceConfig<>(); + ref.setApplication(new ApplicationConfig("bean-consumer")); + ref.setInterface(GreeterService.class); + ref.setUrl("dubbo://" + server.ip() + ":" + server.port() + "?scope=remote&generic=bean"); + client = ref; + + init(); + } + + @Test void reusesPropagatedSpanId() { + TraceContext parent = newTraceContext(SamplingFlags.SAMPLED); + + RpcContext.getContext().getAttachments().put("b3", B3SingleFormat.writeB3SingleFormat(parent)); + client.get().sayHello("jorge"); + + assertSameIds(testSpanHandler.takeRemoteSpan(SERVER), parent); + } + + @Test void createsChildWhenJoinDisabled() { + tracing = tracingBuilder(NEVER_SAMPLE).supportsJoin(false).build(); + init(); + + TraceContext parent = newTraceContext(SamplingFlags.SAMPLED); + + RpcContext.getContext().getAttachments().put("b3", B3SingleFormat.writeB3SingleFormat(parent)); + client.get().sayHello("jorge"); + + assertChildOf(testSpanHandler.takeRemoteSpan(SERVER), parent); + } + + @Test void samplingDisabled() { + tracing = tracingBuilder(NEVER_SAMPLE).build(); + init(); + + client.get().sayHello("jorge"); + + // @After will check that nothing is reported + } + + @Test void currentSpanVisibleToImpl() { + assertThat(client.get().sayHello("jorge")) + .isNotEmpty(); + + testSpanHandler.takeRemoteSpan(SERVER); + } + + @Test void reportsServerKindToZipkin() { + client.get().sayHello("jorge"); + + assertThat(testSpanHandler.takeRemoteSpan(SERVER).kind()) + .isEqualTo(SERVER); + } + + @Test void defaultSpanNameIsMethodName() { + client.get().sayHello("jorge"); + + assertThat(testSpanHandler.takeRemoteSpan(SERVER).name()) + .isEqualTo("brave.dubbo.rpc.GreeterService/sayHello"); + } + + @Test void setsErrorOnException() { + assertThatThrownBy(() -> client.get().sayHello("bad")) + .isInstanceOf(IllegalArgumentException.class); + + testSpanHandler.takeRemoteSpanWithErrorMessage(SERVER, "bad"); + } + + /* RpcTracing-specific feature tests */ + + @Test void customSampler() { + RpcTracing rpcTracing = RpcTracing.newBuilder(tracing).serverSampler(RpcRuleSampler.newBuilder() + .putRule(methodEquals("sayGoodbye"), NEVER_SAMPLE) + .putRule(serviceEquals("brave.dubbo"), ALWAYS_SAMPLE) + .build()).build(); + init().setRpcTracing(rpcTracing); + + // unsampled + client.get().sayGoodbye("jorge"); + + // sampled + client.get().sayHello("jorge"); + + assertThat(testSpanHandler.takeRemoteSpan(SERVER).name()).endsWith("sayHello"); + // @After will also check that sayGoodbye was not sampled + } + + @Test void customParser() { + Tag javaValue = new Tag("dubbo.result_value") { + @Override protected String parseValue(DubboResponse input, TraceContext context) { + Result result = input.result(); + if (result == null) return null; + Object value = result.getValue(); + if (value instanceof JavaBeanDescriptor) { + return String.valueOf(((JavaBeanDescriptor) value).getProperty("value")); + } + return null; + } + }; + + RpcTracing rpcTracing = RpcTracing.newBuilder(tracing) + .serverResponseParser((res, context, span) -> { + RpcResponseParser.DEFAULT.parse(res, context, span); + if (res instanceof DubboResponse) { + javaValue.tag((DubboResponse) res, span); + } + }) + .build(); + init().setRpcTracing(rpcTracing); + + String javaResult = client.get().sayHello("jorge"); + + assertThat(testSpanHandler.takeRemoteSpan(SERVER).tags()) + .containsEntry("dubbo.result_value", javaResult); + } +} diff --git a/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/PickUnusedPort.java b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/PickUnusedPort.java new file mode 100644 index 0000000000..e97889e27a --- /dev/null +++ b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/PickUnusedPort.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import java.io.IOException; +import java.net.ServerSocket; + +class PickUnusedPort { + static int get() { + try { + ServerSocket serverSocket = new ServerSocket(0); + int port = serverSocket.getLocalPort(); + serverSocket.close(); + return port; + } catch (IOException e) { + throw new AssertionError(e); + } + } +} diff --git a/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/TestServer.java b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/TestServer.java new file mode 100644 index 0000000000..e294c11e42 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/TestServer.java @@ -0,0 +1,81 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.internal.Platform; +import brave.propagation.Propagation; +import brave.propagation.TraceContext.Extractor; +import brave.propagation.TraceContextOrSamplingFlags; +import com.alibaba.dubbo.common.Constants; +import com.alibaba.dubbo.config.ApplicationConfig; +import com.alibaba.dubbo.config.ProtocolConfig; +import com.alibaba.dubbo.config.RegistryConfig; +import com.alibaba.dubbo.config.ServiceConfig; +import com.alibaba.dubbo.rpc.RpcContext; +import com.alibaba.dubbo.rpc.service.GenericService; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +class TestServer { + final BlockingQueue requestQueue = new LinkedBlockingQueue<>(); + final Extractor> extractor; + final ServiceConfig service; + final String linkLocalIp; + + TestServer(Propagation.Factory propagationFactory) { + extractor = propagationFactory.get().extractor(Map::get); + linkLocalIp = Platform.get().linkLocalIp(); + if (linkLocalIp != null) { + // avoid dubbo's logic which might pick docker ip + System.setProperty(Constants.DUBBO_IP_TO_BIND, linkLocalIp); + System.setProperty(Constants.DUBBO_IP_TO_REGISTRY, linkLocalIp); + } + service = new ServiceConfig<>(); + service.setApplication(new ApplicationConfig("bean-provider")); + service.setRegistry(new RegistryConfig(RegistryConfig.NO_AVAILABLE)); + service.setProtocol(new ProtocolConfig("dubbo", PickUnusedPort.get())); + service.setInterface(GreeterService.class); + service.setRef((method, parameterTypes, args) -> { + requestQueue.add(extractor.extract(RpcContext.getContext().getAttachments())); + return args[0]; + }); + } + + void start() { + service.export(); + } + + void stop() { + service.unexport(); + } + + int port() { + return service.getProtocol().getPort(); + } + + TraceContextOrSamplingFlags takeRequest() { + try { + return requestQueue.poll(3, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new AssertionError(e); + } + } + + String ip() { + return linkLocalIp != null ? linkLocalIp : "127.0.0.1"; + } +} diff --git a/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/TracingResponseCallbackTest.java b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/TracingResponseCallbackTest.java new file mode 100644 index 0000000000..a5ac20c248 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/test/java/brave/dubbo/rpc/TracingResponseCallbackTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.dubbo.rpc; + +import brave.propagation.CurrentTraceContext; +import brave.propagation.ThreadLocalCurrentTraceContext; +import brave.propagation.TraceContext; +import com.alibaba.dubbo.remoting.exchange.ResponseCallback; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class TracingResponseCallbackTest { + FinishSpan finishSpan = mock(FinishSpan.class); + TraceContext invocationContext = TraceContext.newBuilder().traceId(1).spanId(2).build(); + CurrentTraceContext currentTraceContext = ThreadLocalCurrentTraceContext.create(); + + @Test void done_should_finish_span() { + ResponseCallback callback = + TracingResponseCallback.create(null, finishSpan, currentTraceContext, invocationContext); + + callback.done(null); + + verify(finishSpan).accept(null, null); + } + + @Test void done_should_finish_span_caught() { + ResponseCallback callback = + TracingResponseCallback.create(null, finishSpan, currentTraceContext, invocationContext); + + Throwable error = new Exception("Test exception"); + callback.caught(error); + + verify(finishSpan).accept(null, error); + } + + @Test void done_should_forward_then_finish_span() { + ResponseCallback delegate = mock(ResponseCallback.class); + + ResponseCallback callback = + TracingResponseCallback.create(delegate, finishSpan, currentTraceContext, invocationContext); + + Object result = new Object(); + callback.done(result); + + verify(delegate).done(result); + verify(finishSpan).accept(result, null); + } + + @Test void done_should_have_span_in_scope() { + ResponseCallback delegate = new ResponseCallback() { + @Override public void done(Object response) { + assertThat(currentTraceContext.get()).isSameAs(invocationContext); + } + + @Override public void caught(Throwable exception) { + throw new AssertionError(); + } + }; + + ResponseCallback callback = + TracingResponseCallback.create(delegate, finishSpan, currentTraceContext, invocationContext); + + Object result = new Object(); + callback.done(result); + + verify(finishSpan).accept(result, null); + } + + @Test void done_should_have_span_in_scope_caught() { + ResponseCallback delegate = new ResponseCallback() { + @Override public void done(Object response) { + throw new AssertionError(); + } + + @Override public void caught(Throwable exception) { + assertThat(currentTraceContext.get()).isSameAs(invocationContext); + } + }; + + ResponseCallback callback = + TracingResponseCallback.create(delegate, finishSpan, currentTraceContext, invocationContext); + + Throwable error = new Exception("Test exception"); + callback.caught(error); + + verify(finishSpan).accept(null, error); + } +} diff --git a/instrumentation/dubbo-rpc/src/test/resources/log4j2.properties b/instrumentation/dubbo-rpc/src/test/resources/log4j2.properties new file mode 100755 index 0000000000..330d187d00 --- /dev/null +++ b/instrumentation/dubbo-rpc/src/test/resources/log4j2.properties @@ -0,0 +1,28 @@ +appenders=console +appender.console.type=Console +appender.console.name=STDOUT +appender.console.layout.type=PatternLayout +appender.console.layout.pattern=%d{ABSOLUTE} %-5p [%t] %C{2} (%F:%L) - %m%n +rootLogger.level=warn +rootLogger.appenderRefs=stdout +rootLogger.appenderRef.stdout.ref=STDOUT + +# mute logs that have to do with config re-loading, which is constant during tests +logger.model.name=com.alibaba.dubbo.config.model.ApplicationModel +logger.model.level=off +logger.server.name=com.alibaba.dubbo.remoting.transport.AbstractServer +logger.server.level=off +logger.spring-extension.name=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory +logger.spring-extension.level=off + +# mute dispatcher error as we intentionally dispatch to an unknown route +logger.dispatcher.name=com.alibaba.dubbo.remoting.transport.dispatcher +logger.dispatcher.level=off + +# mute future error as we intentionally timeout a call +logger.future.name=com.alibaba.dubbo.remoting.exchange.support.DefaultFuture +logger.future.level=off + +# mute exception filter as we intentionally test exception paths +logger.exception-filter.name=com.alibaba.dubbo.rpc.filter.ExceptionFilter +logger.exception-filter.level=off diff --git a/instrumentation/dubbo/src/main/java/brave/dubbo/TracingFilter.java b/instrumentation/dubbo/src/main/java/brave/dubbo/TracingFilter.java index 2ee47da6af..ad0f25689f 100644 --- a/instrumentation/dubbo/src/main/java/brave/dubbo/TracingFilter.java +++ b/instrumentation/dubbo/src/main/java/brave/dubbo/TracingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -15,14 +15,18 @@ import brave.Span; import brave.Span.Kind; +import brave.SpanCustomizer; +import brave.Tag; +import brave.Tracing; import brave.propagation.CurrentTraceContext; import brave.propagation.CurrentTraceContext.Scope; import brave.propagation.TraceContext; import brave.rpc.RpcClientHandler; +import brave.rpc.RpcResponse; +import brave.rpc.RpcResponseParser; import brave.rpc.RpcServerHandler; import brave.rpc.RpcTracing; -import java.util.Map; -import java.util.concurrent.CompletableFuture; +import org.apache.dubbo.common.Version; import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.common.extension.ExtensionLoader; @@ -33,18 +37,47 @@ import org.apache.dubbo.rpc.RpcContext; import org.apache.dubbo.rpc.RpcException; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + import static brave.internal.Throwables.propagateIfFatal; @Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER}, value = "tracing") // http://dubbo.apache.org/en-us/docs/dev/impls/filter.html // public constructor permitted to allow dubbo to instantiate this public final class TracingFilter implements Filter { + static final Tag DUBBO_ERROR_CODE = new Tag("dubbo.error_code") { + @Override protected String parseValue(Throwable input, TraceContext context) { + if (!(input instanceof RpcException)) return null; + return String.valueOf(((RpcException) input).getCode()); + } + }; + static final RpcResponseParser LEGACY_RESPONSE_PARSER = new RpcResponseParser() { + @Override public void parse(RpcResponse response, TraceContext context, SpanCustomizer span) { + DUBBO_ERROR_CODE.tag(response.error(), span); + } + }; CurrentTraceContext currentTraceContext; RpcClientHandler clientHandler; RpcServerHandler serverHandler; volatile boolean isInit = false; + /** + * {@link ExtensionLoader} supplies the tracing implementation which must be named "tracing". For + * example, if using the {@link org.apache.dubbo.config.spring.extension.SpringExtensionInjector}, only a bean named "tracing" will be + * injected. + * + * @deprecated Since 5.12 only use {@link #setRpcTracing(RpcTracing)} + */ + @Deprecated public void setTracing(Tracing tracing) { + if (tracing == null) throw new NullPointerException("rpcTracing == null"); + setRpcTracing(RpcTracing.newBuilder(tracing) + .clientResponseParser(LEGACY_RESPONSE_PARSER) + .serverResponseParser(LEGACY_RESPONSE_PARSER) + .build()); + } + /** * {@link ExtensionLoader} supplies the tracing implementation which must be named "rpcTracing". * For example, if using the {@link org.apache.dubbo.config.spring.extension.SpringExtensionInjector}, only a bean named "rpcTracing" will diff --git a/instrumentation/dubbo/src/test/java/brave/dubbo/ITTracingFilter_Consumer.java b/instrumentation/dubbo/src/test/java/brave/dubbo/ITTracingFilter_Consumer.java index 598148f469..23678df61a 100644 --- a/instrumentation/dubbo/src/test/java/brave/dubbo/ITTracingFilter_Consumer.java +++ b/instrumentation/dubbo/src/test/java/brave/dubbo/ITTracingFilter_Consumer.java @@ -25,8 +25,10 @@ import brave.test.util.AssertableCallback; import java.util.Map; import org.apache.dubbo.common.beanutil.JavaBeanDescriptor; +import org.apache.dubbo.common.extension.ExtensionLoader; import org.apache.dubbo.config.ReferenceConfig; import org.apache.dubbo.config.bootstrap.DubboBootstrap; +import org.apache.dubbo.rpc.Filter; import org.apache.dubbo.rpc.Result; import org.apache.dubbo.rpc.RpcContext; import org.apache.dubbo.rpc.RpcException; @@ -237,6 +239,24 @@ class ITTracingFilter_Consumer extends ITTracingFilter { .containsEntry("rpc.error_code", "NETWORK_EXCEPTION"); } + /** Shows if you aren't using RpcTracing, the old "dubbo.error_code" works */ + @Test void setError_onUnimplemented_legacy() { + ((TracingFilter) ExtensionLoader.getExtensionLoader(Filter.class) + .getExtension("tracing")).isInit = false; + + ((TracingFilter) ExtensionLoader.getExtensionLoader(Filter.class) + .getExtension("tracing")) + .setTracing(tracing); + + assertThatThrownBy(() -> wrongClient.get().sayHello("jorge")) + .isInstanceOf(RpcException.class); + + MutableSpan span = + testSpanHandler.takeRemoteSpanWithErrorMessage(CLIENT, ".*Fail to decode request.*"); + assertThat(span.tags()) + .containsEntry("dubbo.error_code", "1"); + } + /* RpcTracing-specific feature tests */ @Test void customSampler() { diff --git a/instrumentation/dubbo/src/test/java/brave/dubbo/ITTracingFilter_Provider.java b/instrumentation/dubbo/src/test/java/brave/dubbo/ITTracingFilter_Provider.java index 44e13c37f1..1caede1513 100644 --- a/instrumentation/dubbo/src/test/java/brave/dubbo/ITTracingFilter_Provider.java +++ b/instrumentation/dubbo/src/test/java/brave/dubbo/ITTracingFilter_Provider.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -21,7 +21,6 @@ import brave.rpc.RpcResponseParser; import brave.rpc.RpcRuleSampler; import brave.rpc.RpcTracing; -import java.util.Map; import org.apache.dubbo.common.beanutil.JavaBeanDescriptor; import org.apache.dubbo.common.beanutil.JavaBeanSerializeUtil; import org.apache.dubbo.config.ReferenceConfig; @@ -29,6 +28,7 @@ import org.apache.dubbo.rpc.RpcContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.util.Map; import static brave.Span.Kind.SERVER; import static brave.rpc.RpcRequestMatchers.methodEquals; diff --git a/instrumentation/dubbo/src/test/java/brave/dubbo/TestServer.java b/instrumentation/dubbo/src/test/java/brave/dubbo/TestServer.java index d8d96ae847..d24c900a0f 100644 --- a/instrumentation/dubbo/src/test/java/brave/dubbo/TestServer.java +++ b/instrumentation/dubbo/src/test/java/brave/dubbo/TestServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -30,6 +30,8 @@ import org.apache.dubbo.rpc.RpcContext; import org.apache.dubbo.rpc.service.GenericService; +import static org.apache.dubbo.common.utils.SerializeCheckStatus.DISABLE; + class TestServer { final BlockingQueue requestQueue = new LinkedBlockingQueue<>(); final Extractor> extractor; diff --git a/instrumentation/grpc/README.md b/instrumentation/grpc/README.md index efee8c2994..e1c8776436 100644 --- a/instrumentation/grpc/README.md +++ b/instrumentation/grpc/README.md @@ -69,6 +69,63 @@ grpcTracing = GrpcTracing.create(RpcTracing.newBuilder(tracing) .serverRequestParser(addMethodType).build()); ``` +## Message processing + +## Message processing callback context +If you need to process messages, streaming or otherwise, you can use normal +gRPC interceptors. The current span will be the following, regardless of +message count per request (streaming): + +* `ClientInterceptor` + * `ClientCall.sendMessage()` - the client trace context + * `ClientCall.Listener.onMessage()` - the invocation trace context (aka parent) + Why is discussed in the main [instrumentation rationale](../RATIONALE.md). +* `ServerInterceptor` + * `ServerCall.Listener.onMessage()` - the server trace context + * `ServerCall.sendMessage()` - the server trace context + Both sides are in the server context, as otherwise would be a new trace. + +Ex. To annotate the time each message sent to a server, you can do this: +```java +SpanCustomizer span = CurrentSpanCustomizer.create(tracing); + +// Make sure this is ordered before the tracing interceptor! Otherwise, it will +// not see the current span. +managedChannelBuilder.intercept(new ClientInterceptor() { + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + return new SimpleForwardingClientCall(next.newCall(method, callOptions)) { + public void sendMessage(ReqT message) { + span.annotate("grpc.send_message"); + delegate().sendMessage(message); + } + }; + } +}, grpcTracing.newClientInterceptor()); +``` + +## gRPC Propagation Format (Census interop) + +gRPC defines a [binary encoded propagation format](https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/BinaryEncoding.md) which is implemented +by [OpenCensus](https://opencensus.io/) instrumentation. When this is +the case, incoming requests will have two metadata keys "grpc-trace-bin" +and "grpc-tags-bin". + +When enabled, this component can extract trace contexts from these +metadata and also write the same keys on outgoing calls. This allows +transparent interop when both census and brave report data to the same +tracing system. + +To enable this feature, set `grpcPropagationFormatEnabled` which is off +by default: +```java +grpcTracing = GrpcTracing.newBuilder(tracing) + .grpcPropagationFormatEnabled(true).build(); +``` + +Warning: the format of both "grpc-trace-bin" and "grpc-tags-bin" are +version 0. As such, consider this feature experimental. + ## Development If you are working on this module, then you need to run `mvn install` to first compile the protos. Once the protos are compiled, then can be found in the directories: diff --git a/instrumentation/grpc/src/main/java/brave/grpc/GrpcClientParser.java b/instrumentation/grpc/src/main/java/brave/grpc/GrpcClientParser.java new file mode 100644 index 0000000000..5421f158d8 --- /dev/null +++ b/instrumentation/grpc/src/main/java/brave/grpc/GrpcClientParser.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.grpc; + +import brave.SpanCustomizer; +import brave.propagation.TraceContext; +import brave.rpc.RpcRequest; +import brave.rpc.RpcRequestParser; +import brave.rpc.RpcTracing; +import io.grpc.CallOptions; +import io.grpc.ClientCall; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; + +/** + * @see GrpcClientRequest + * @see GrpcClientResponse + * @deprecated Since 5.12 use {@link RpcTracing#clientRequestParser()} or {@link + * RpcTracing#clientResponseParser()}. + */ +@Deprecated +public class GrpcClientParser extends GrpcParser implements RpcRequestParser { + @Override public void parse(RpcRequest request, TraceContext context, SpanCustomizer span) { + if (request instanceof GrpcClientRequest) { + GrpcClientRequest grpcRequest = (GrpcClientRequest) request; + onStart(grpcRequest.methodDescriptor, grpcRequest.callOptions, grpcRequest.headers, span); + } else { + assert false : "expected a GrpcClientRequest: " + request; + } + } + + /** Override the customize the span based on the start of a request. */ + protected void onStart(MethodDescriptor method, CallOptions options, + Metadata headers, SpanCustomizer span) { + span.name(spanName(method)); + } + + /** + * @since 4.8 + * @deprecated Since 5.12 use {@link ClientCall#sendMessage(Object)}. + */ + @Deprecated @Override protected void onMessageSent(M message, SpanCustomizer span) { + super.onMessageSent(message, span); + } + + /** + * @since 4.8 + * @deprecated Since 5.12 use {@link ClientCall.Listener#onMessage(Object)}. + */ + @Deprecated @Override protected void onMessageReceived(M message, SpanCustomizer span) { + super.onMessageReceived(message, span); + } +} diff --git a/instrumentation/grpc/src/main/java/brave/grpc/GrpcParser.java b/instrumentation/grpc/src/main/java/brave/grpc/GrpcParser.java index c7cfafecee..b3807639ca 100644 --- a/instrumentation/grpc/src/main/java/brave/grpc/GrpcParser.java +++ b/instrumentation/grpc/src/main/java/brave/grpc/GrpcParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,9 +13,68 @@ */ package brave.grpc; +import brave.ErrorParser; +import brave.Span; +import brave.SpanCustomizer; +import brave.Tag; import brave.internal.Nullable; +import brave.propagation.TraceContext; +import brave.rpc.RpcResponse; +import brave.rpc.RpcResponseParser; +import brave.rpc.RpcTracing; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Status; + +/** @deprecated Since 5.12 use parsers in {@link RpcTracing}. */ +@Deprecated +public class GrpcParser extends MessageProcessor implements RpcResponseParser { + static final ErrorParser ERROR_PARSER = new ErrorParser(); + static final RpcResponseParser LEGACY_RESPONSE_PARSER = new GrpcParser(); + + @Override + public void parse(RpcResponse response, TraceContext context, SpanCustomizer span) { + Status status = null; + Metadata trailers = null; + if (response instanceof GrpcClientResponse) { + status = ((GrpcClientResponse) response).status(); + trailers = ((GrpcClientResponse) response).trailers(); + } else if (response instanceof GrpcServerResponse) { + status = ((GrpcServerResponse) response).status(); + trailers = ((GrpcServerResponse) response).trailers(); + } else { + assert false : "expected a GrpcClientResponse or GrpcServerResponse: " + response; + } + onClose(status, trailers, span); + } + + /** + * @deprecated This is only used in Zipkin reporting. Since 5.12, use {@link + * zipkin2.reporter.brave.ZipkinSpanHandler.Builder#errorTag(Tag)} + */ + @Deprecated protected ErrorParser errorParser() { + return ERROR_PARSER; + } + + /** Returns the span name of the request. Defaults to the full grpc method name. */ + protected String spanName(MethodDescriptor methodDescriptor) { + return methodDescriptor.getFullMethodName(); + } + + /** + * Override to change what data from the status or trailers are parsed into the span modeling it. + * + *

Note: {@link Status#getCause()} will be set as {@link Span#error(Throwable)} by + * default. You don't need to parse it here. + */ + protected void onClose(Status status, Metadata trailers, SpanCustomizer span) { + if (status == null || status.isOk()) return; + + String code = String.valueOf(status.getCode()); + span.tag("grpc.status_code", code); + if (status.getCause() == null) span.tag("error", code); + } -final class GrpcParser { @Nullable static String method(String fullMethodName) { int index = fullMethodName.lastIndexOf('/'); if (index == -1 || index == 0) return null; diff --git a/instrumentation/grpc/src/main/java/brave/grpc/GrpcPropagation.java b/instrumentation/grpc/src/main/java/brave/grpc/GrpcPropagation.java index eebaf77dad..253c6e7a40 100644 --- a/instrumentation/grpc/src/main/java/brave/grpc/GrpcPropagation.java +++ b/instrumentation/grpc/src/main/java/brave/grpc/GrpcPropagation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -15,12 +15,37 @@ import brave.baggage.BaggagePropagation; import brave.propagation.Propagation; +import brave.propagation.TraceContext; +import brave.propagation.TraceContext.Extractor; +import brave.propagation.TraceContext.Injector; +import brave.propagation.TraceContextOrSamplingFlags; import io.grpc.Metadata; import io.grpc.Metadata.Key; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; -final class GrpcPropagation { +/** see {@link GrpcTracing.Builder#grpcPropagationFormatEnabled} for documentation. */ +final class GrpcPropagation implements Propagation { + /** + * This creates a compatible metadata key based on Census, except this extracts a brave trace + * context as opposed to a census span context + */ + static final Key GRPC_TRACE_BIN = + Key.of("grpc-trace-bin", Metadata.BINARY_BYTE_MARSHALLER); + /** This stashes the tag context in "extra" so it isn't lost */ + static final Key GRPC_TAGS_BIN = + Key.of("grpc-tags-bin", new Metadata.BinaryMarshaller() { + @Override public byte[] toBytes(TagsBin value) { + return value.bytes; + } + + @Override public TagsBin parseBytes(byte[] serialized) { + if (serialized == null || serialized.length == 0) return null; + return new TagsBin(serialized); + } + }); + /** Creates constant keys for use in propagating trace identifiers or baggage. */ static Map> nameToKey(Propagation propagation) { Map> result = new LinkedHashMap>(); @@ -32,4 +57,92 @@ static Map> nameToKey(Propagation propagation) { } return result; } + + /** {@linkplain TraceContext#extra() extra field} that passes through bytes metdata. */ + static final class TagsBin { + final byte[] bytes; + + TagsBin(byte[] bytes) { + this.bytes = bytes; + } + } + + static Propagation create(Propagation delegate) { + if (delegate == null) throw new NullPointerException("delegate == null"); + return new GrpcPropagation(delegate); + } + + final Propagation delegate; + + GrpcPropagation(Propagation delegate) { + this.delegate = delegate; + } + + @Override public List keys() { + return delegate.keys(); + } + + @Override public Injector injector(Setter setter) { + return new GrpcInjector(this, setter); + } + + @Override public Extractor extractor(Getter getter) { + return new GrpcExtractor(this, getter); + } + + static final class GrpcInjector implements Injector { + final Injector delegate; + final Setter setter; + + GrpcInjector(GrpcPropagation propagation, Setter setter) { + this.delegate = propagation.delegate.injector(setter); + this.setter = setter; + } + + @Override public void inject(TraceContext context, R request) { + if (request instanceof GrpcRequest) { + byte[] serialized = TraceContextBinaryFormat.toBytes(context); + Metadata metadata = ((GrpcRequest) request).headers(); + metadata.removeAll(GRPC_TRACE_BIN); + metadata.put(GRPC_TRACE_BIN, serialized); + TagsBin tags = context.findExtra(TagsBin.class); + if (tags != null) { + metadata.removeAll(GRPC_TAGS_BIN); + metadata.put(GRPC_TAGS_BIN, tags); + } + } + delegate.inject(context, request); + } + } + + static final class GrpcExtractor implements Extractor { + final Extractor delegate; + final Propagation.Getter getter; + + GrpcExtractor(GrpcPropagation propagation, Getter getter) { + this.delegate = propagation.delegate.extractor(getter); + this.getter = getter; + } + + @Override public TraceContextOrSamplingFlags extract(R request) { + if (!(request instanceof GrpcRequest)) return delegate.extract(request); + + Metadata metadata = ((GrpcRequest) request).headers(); + + // First, check if we are propagating gRPC tags. + TagsBin tagsBin = metadata.get(GRPC_TAGS_BIN); + + // Next, check to see if there is a gRPC formatted trace context: use it if parsable. + byte[] bytes = metadata.get(GRPC_TRACE_BIN); + if (bytes != null) { + TraceContext maybeContext = TraceContextBinaryFormat.parseBytes(bytes, tagsBin); + if (maybeContext != null) return TraceContextOrSamplingFlags.create(maybeContext); + } + + // Finally, try to extract an incoming, non-gRPC trace context. If tags exist, propagate them. + TraceContextOrSamplingFlags result = delegate.extract(request); + if (tagsBin == null) return result; + return result.toBuilder().addExtra(tagsBin).build(); + } + } } diff --git a/instrumentation/grpc/src/main/java/brave/grpc/GrpcServerParser.java b/instrumentation/grpc/src/main/java/brave/grpc/GrpcServerParser.java new file mode 100644 index 0000000000..678ca61c60 --- /dev/null +++ b/instrumentation/grpc/src/main/java/brave/grpc/GrpcServerParser.java @@ -0,0 +1,62 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.grpc; + +import brave.SpanCustomizer; +import brave.propagation.TraceContext; +import brave.rpc.RpcRequest; +import brave.rpc.RpcRequestParser; +import brave.rpc.RpcTracing; +import io.grpc.Metadata; +import io.grpc.ServerCall; + +/** + * @see GrpcServerRequest + * @see GrpcServerResponse + * @deprecated Since 5.12 use {@link RpcTracing#serverRequestParser()} or {@link + * RpcTracing#serverResponseParser()}. + */ +@Deprecated +public class GrpcServerParser extends GrpcParser implements RpcRequestParser { + @Override public void parse(RpcRequest request, TraceContext context, SpanCustomizer span) { + if (request instanceof GrpcServerRequest) { + GrpcServerRequest grpcRequest = (GrpcServerRequest) request; + onStart(grpcRequest.call, grpcRequest.headers, span); + } else { + assert false : "expected a GrpcServerRequest: " + request; + } + } + + /** Override the customize the span based on the start of a request. */ + protected void onStart(ServerCall call, Metadata headers, + SpanCustomizer span) { + span.name(spanName(call.getMethodDescriptor())); + } + + /** + * @since 4.8 + * @deprecated Since 5.12 use {@link ServerCall.Listener#onMessage(Object)}. + */ + @Deprecated @Override protected void onMessageSent(M message, SpanCustomizer span) { + super.onMessageSent(message, span); + } + + /** + * @since 4.8 + * @deprecated Since 5.12 use {@link ServerCall#sendMessage(Object)}. + */ + @Deprecated @Override protected void onMessageReceived(M message, SpanCustomizer span) { + super.onMessageReceived(message, span); + } +} diff --git a/instrumentation/grpc/src/main/java/brave/grpc/GrpcTracing.java b/instrumentation/grpc/src/main/java/brave/grpc/GrpcTracing.java index 4007f09773..026f96b3ae 100644 --- a/instrumentation/grpc/src/main/java/brave/grpc/GrpcTracing.java +++ b/instrumentation/grpc/src/main/java/brave/grpc/GrpcTracing.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,12 +14,16 @@ package brave.grpc; import brave.Tracing; +import brave.rpc.RpcRequestParser; +import brave.rpc.RpcResponseParser; import brave.rpc.RpcTracing; import io.grpc.ClientInterceptor; import io.grpc.Metadata; import io.grpc.ServerInterceptor; import java.util.Map; +import static brave.grpc.GrpcParser.LEGACY_RESPONSE_PARSER; + public final class GrpcTracing { public static GrpcTracing create(Tracing tracing) { return newBuilder(tracing).build(); @@ -30,7 +34,10 @@ public static GrpcTracing create(RpcTracing rpcTracing) { } public static Builder newBuilder(Tracing tracing) { - return newBuilder(RpcTracing.create(tracing)); + return newBuilder(RpcTracing.newBuilder(tracing) + .clientResponseParser(LEGACY_RESPONSE_PARSER) + .serverResponseParser(LEGACY_RESPONSE_PARSER) + .build()); } public static Builder newBuilder(RpcTracing rpcTracing) { @@ -39,6 +46,11 @@ public static Builder newBuilder(RpcTracing rpcTracing) { public static final class Builder { RpcTracing rpcTracing; + boolean grpcPropagationFormatEnabled = false; + + // for interop with old parsers + MessageProcessor clientMessageProcessor = MessageProcessor.NOOP; + MessageProcessor serverMessageProcessor = MessageProcessor.NOOP; Builder(RpcTracing rpcTracing) { if (rpcTracing == null) throw new NullPointerException("rpcTracing == null"); @@ -47,6 +59,62 @@ public static final class Builder { Builder(GrpcTracing grpcTracing) { rpcTracing = grpcTracing.rpcTracing; + clientMessageProcessor = grpcTracing.clientMessageProcessor; + serverMessageProcessor = grpcTracing.serverMessageProcessor; + } + + /** + * @see GrpcClientRequest + * @see GrpcClientResponse + * @deprecated Since 5.12 use {@link RpcTracing.Builder#clientRequestParser(RpcRequestParser)} + * or {@link RpcTracing.Builder#clientResponseParser(RpcResponseParser)}. + */ + @Deprecated + public Builder clientParser(GrpcClientParser clientParser) { + if (clientParser == null) throw new NullPointerException("clientParser == null"); + clientMessageProcessor = clientParser; + rpcTracing = rpcTracing.toBuilder() + .clientRequestParser(clientParser) + .clientResponseParser(clientParser).build(); + return this; + } + + /** + * @see GrpcServerRequest + * @see GrpcServerResponse + * @deprecated Since 5.12 use {@link RpcTracing.Builder#serverRequestParser(RpcRequestParser)} + * or {@link RpcTracing.Builder#serverResponseParser(RpcResponseParser)}. + */ + @Deprecated + public Builder serverParser(GrpcServerParser serverParser) { + if (serverParser == null) throw new NullPointerException("serverParser == null"); + serverMessageProcessor = serverParser; + rpcTracing = rpcTracing.toBuilder() + .serverRequestParser(serverParser) + .serverResponseParser(serverParser).build(); + return this; + } + + /** + * When true, "grpc-trace-bin" is preferred when extracting trace context. This is useful when + * OpenCensus implements tracing upstream or downstream. + * Default is false. + * + *

This wraps an existing propagation implementation, but prefers extracting + * "grpc-trace-bin" when parsing gRPC metadata. Regardless of whether "grpc-trace-bin" was + * parsed, it is speculatively written on outgoing requests. + * + *

When present, "grpc-tags-bin" is propagated pass-through. We do not alter it. + * + *

Warning: the format of both "grpc-trace-bin" is version 0. As such, + * consider this feature experimental. + * + * @deprecated The only user of this format was Census, which was removed from gRPC in version + * v1.27. + */ + @Deprecated public Builder grpcPropagationFormatEnabled(boolean grpcPropagationFormatEnabled) { + this.grpcPropagationFormatEnabled = grpcPropagationFormatEnabled; + return this; } public GrpcTracing build() { @@ -56,10 +124,26 @@ public GrpcTracing build() { final RpcTracing rpcTracing; final Map> nameToKey; + final boolean grpcPropagationFormatEnabled; + + // for toBuilder() + final MessageProcessor clientMessageProcessor, serverMessageProcessor; GrpcTracing(Builder builder) { // intentionally hidden constructor - rpcTracing = builder.rpcTracing; + grpcPropagationFormatEnabled = builder.grpcPropagationFormatEnabled; + + // Decorate so that grpc-specific formats are sent downstream + if (grpcPropagationFormatEnabled) { + rpcTracing = builder.rpcTracing.toBuilder() + .propagation(GrpcPropagation.create(builder.rpcTracing.propagation())) + .build(); + } else { + rpcTracing = builder.rpcTracing; + } + nameToKey = GrpcPropagation.nameToKey(rpcTracing.propagation()); + clientMessageProcessor = builder.clientMessageProcessor; + serverMessageProcessor = builder.serverMessageProcessor; } public Builder toBuilder() { @@ -67,7 +151,7 @@ public Builder toBuilder() { } /** This interceptor traces outbound calls */ - public ClientInterceptor newClientInterceptor() { + public final ClientInterceptor newClientInterceptor() { return new TracingClientInterceptor(this); } diff --git a/instrumentation/grpc/src/main/java/brave/grpc/MessageProcessor.java b/instrumentation/grpc/src/main/java/brave/grpc/MessageProcessor.java new file mode 100644 index 0000000000..f1bdb0dd0c --- /dev/null +++ b/instrumentation/grpc/src/main/java/brave/grpc/MessageProcessor.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.grpc; + +import brave.SpanCustomizer; + +// only here to deal with deprecated methods. +abstract class MessageProcessor { + static final MessageProcessor NOOP = new MessageProcessor() { + @Override public void onMessageSent(Object message, SpanCustomizer span) { + } + + @Override public void onMessageReceived(Object message, SpanCustomizer span) { + } + }; + + void onMessageSent(M message, SpanCustomizer span) { + } + + void onMessageReceived(M message, SpanCustomizer span) { + } +} diff --git a/instrumentation/grpc/src/main/java/brave/grpc/TraceContextBinaryFormat.java b/instrumentation/grpc/src/main/java/brave/grpc/TraceContextBinaryFormat.java new file mode 100644 index 0000000000..5563c0fbc6 --- /dev/null +++ b/instrumentation/grpc/src/main/java/brave/grpc/TraceContextBinaryFormat.java @@ -0,0 +1,127 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.grpc; + +import brave.grpc.GrpcPropagation.TagsBin; +import brave.internal.Nullable; +import brave.internal.Platform; +import brave.propagation.TraceContext; +import java.util.Collections; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * This logs instead of throwing exceptions. + * + *

See + * https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/BinaryEncoding.md + */ +final class TraceContextBinaryFormat { + static final byte VERSION = 0, + TRACE_ID_FIELD_ID = 0, + SPAN_ID_FIELD_ID = 1, + TRACE_OPTION_FIELD_ID = 2; + + static final int FORMAT_LENGTH = + 4 /* version + 3 fields */ + 16 /* trace ID */ + 8 /* span ID */ + 1 /* sampled bit */; + + static byte[] toBytes(TraceContext traceContext) { + checkNotNull(traceContext, "traceContext"); + byte[] bytes = new byte[FORMAT_LENGTH]; + bytes[0] = VERSION; + bytes[1] = TRACE_ID_FIELD_ID; + writeLong(bytes, 2, traceContext.traceIdHigh()); + writeLong(bytes, 10, traceContext.traceId()); + bytes[18] = SPAN_ID_FIELD_ID; + writeLong(bytes, 19, traceContext.spanId()); + bytes[27] = TRACE_OPTION_FIELD_ID; + if (traceContext.sampled() != null && traceContext.sampled()) { + bytes[28] = 1; + } + return bytes; + } + + @Nullable static TraceContext parseBytes(byte[] bytes, @Nullable TagsBin tags) { + if (bytes == null) throw new NullPointerException("bytes == null"); // programming error + if (bytes.length == 0) return null; + if (bytes[0] != VERSION) { + Platform.get().log("Invalid input: unsupported version {0}", bytes[0], null); + return null; + } + if (bytes.length < FORMAT_LENGTH - 2 /* sampled field + bit is optional */) { + Platform.get().log("Invalid input: truncated", null); + return null; + } + long traceIdHigh, traceId, spanId; + int pos = 1; + if (bytes[pos] == TRACE_ID_FIELD_ID) { + pos++; + traceIdHigh = readLong(bytes, pos); + traceId = readLong(bytes, pos + 8); + pos += 16; + } else { + Platform.get().log("Invalid input: expected trace ID at offset {0}", pos, null); + return null; + } + if (bytes[pos] == SPAN_ID_FIELD_ID) { + pos++; + spanId = readLong(bytes, pos); + pos += 8; + } else { + Platform.get().log("Invalid input: expected span ID at offset {0}", pos, null); + return null; + } + // The trace options field is optional. However, when present, it should be valid. + Boolean sampled = null; + if (bytes.length > pos && bytes[pos] == TRACE_OPTION_FIELD_ID) { + pos++; + if (bytes.length < pos + 1) { + Platform.get().log("Invalid input: truncated", null); + return null; + } + sampled = bytes[pos] == 1; + } + TraceContext.Builder builder = TraceContext.newBuilder() + .traceIdHigh(traceIdHigh) + .traceId(traceId) + .spanId(spanId); + if (sampled != null) builder.sampled(sampled.booleanValue()); + if (tags != null) builder.extra(Collections.singletonList(tags)); + return builder.build(); + } + + /** Inspired by {@code okio.Buffer.writeLong} */ + static void writeLong(byte[] data, int pos, long v) { + data[pos + 0] = (byte) ((v >>> 56L) & 0xff); + data[pos + 1] = (byte) ((v >>> 48L) & 0xff); + data[pos + 2] = (byte) ((v >>> 40L) & 0xff); + data[pos + 3] = (byte) ((v >>> 32L) & 0xff); + data[pos + 4] = (byte) ((v >>> 24L) & 0xff); + data[pos + 5] = (byte) ((v >>> 16L) & 0xff); + data[pos + 6] = (byte) ((v >>> 8L) & 0xff); + data[pos + 7] = (byte) (v & 0xff); + } + + /** Inspired by {@code okio.Buffer.readLong} */ + static long readLong(byte[] data, int pos) { + return (data[pos] & 0xffL) << 56 + | (data[pos + 1] & 0xffL) << 48 + | (data[pos + 2] & 0xffL) << 40 + | (data[pos + 3] & 0xffL) << 32 + | (data[pos + 4] & 0xffL) << 24 + | (data[pos + 5] & 0xffL) << 16 + | (data[pos + 6] & 0xffL) << 8 + | (data[pos + 7] & 0xffL); + } +} diff --git a/instrumentation/grpc/src/main/java/brave/grpc/TracingClientInterceptor.java b/instrumentation/grpc/src/main/java/brave/grpc/TracingClientInterceptor.java index f4e48d30f3..905f199bb6 100644 --- a/instrumentation/grpc/src/main/java/brave/grpc/TracingClientInterceptor.java +++ b/instrumentation/grpc/src/main/java/brave/grpc/TracingClientInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,7 +13,9 @@ */ package brave.grpc; +import brave.NoopSpanCustomizer; import brave.Span; +import brave.SpanCustomizer; import brave.internal.Nullable; import brave.propagation.CurrentTraceContext; import brave.propagation.CurrentTraceContext.Scope; @@ -40,11 +42,13 @@ final class TracingClientInterceptor implements ClientInterceptor { final Map> nameToKey; final CurrentTraceContext currentTraceContext; final RpcClientHandler handler; + final MessageProcessor messageProcessor; TracingClientInterceptor(GrpcTracing grpcTracing) { nameToKey = grpcTracing.nameToKey; currentTraceContext = grpcTracing.rpcTracing.tracing().currentTraceContext(); handler = RpcClientHandler.create(grpcTracing.rpcTracing); + messageProcessor = grpcTracing.clientMessageProcessor; } @Override @@ -153,6 +157,9 @@ final class TracingClientCall extends SimpleForwardingClientCall extends SimpleForwardingClientCallL @Override public void onMessage(RespT message) { Scope scope = currentTraceContext.maybeScope(invocationContext); try { + Span span = spanRef.get(); // could be an error + SpanCustomizer customizer = span != null ? span.customizer() : NoopSpanCustomizer.INSTANCE; + messageProcessor.onMessageReceived(message, customizer); delegate().onMessage(message); } finally { scope.close(); diff --git a/instrumentation/grpc/src/main/java/brave/grpc/TracingServerInterceptor.java b/instrumentation/grpc/src/main/java/brave/grpc/TracingServerInterceptor.java index 35e1ad3314..98319a052f 100644 --- a/instrumentation/grpc/src/main/java/brave/grpc/TracingServerInterceptor.java +++ b/instrumentation/grpc/src/main/java/brave/grpc/TracingServerInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,7 +13,9 @@ */ package brave.grpc; +import brave.NoopSpanCustomizer; import brave.Span; +import brave.SpanCustomizer; import brave.propagation.CurrentTraceContext; import brave.propagation.CurrentTraceContext.Scope; import brave.propagation.TraceContext; @@ -37,11 +39,15 @@ final class TracingServerInterceptor implements ServerInterceptor { final Map> nameToKey; final CurrentTraceContext currentTraceContext; final RpcServerHandler handler; + final boolean grpcPropagationFormatEnabled; + final MessageProcessor messageProcessor; TracingServerInterceptor(GrpcTracing grpcTracing) { nameToKey = grpcTracing.nameToKey; currentTraceContext = grpcTracing.rpcTracing.tracing().currentTraceContext(); handler = RpcServerHandler.create(grpcTracing.rpcTracing); + grpcPropagationFormatEnabled = grpcTracing.grpcPropagationFormatEnabled; + messageProcessor = grpcTracing.serverMessageProcessor; } @Override @@ -78,7 +84,7 @@ public Listener interceptCall(ServerCall call, scope.close(); } - return new TracingServerCallListener(result, span, spanRef); + return new TracingServerCallListener(result, span, spanRef, request); } final class TracingServerCall extends SimpleForwardingServerCall { @@ -119,6 +125,9 @@ final class TracingServerCall extends SimpleForwardingServerCall extends SimpleForwardingServerCall extends SimpleForwardingServerCallListener { final TraceContext context; final AtomicReference spanRef; + final GrpcServerRequest request; TracingServerCallListener( Listener delegate, Span span, - AtomicReference spanRef + AtomicReference spanRef, + GrpcServerRequest request ) { super(delegate); this.context = span.context(); this.spanRef = spanRef; + this.request = request; } @Override public void onMessage(RespT message) { Scope scope = currentTraceContext.maybeScope(context); try { delegate().onMessage(message); + Span span = spanRef.get(); // could be an error + SpanCustomizer customizer = span != null ? span.customizer() : NoopSpanCustomizer.INSTANCE; + messageProcessor.onMessageReceived(message, customizer); } finally { scope.close(); } diff --git a/instrumentation/grpc/src/test/java/brave/grpc/BaseITTracingClientInterceptor.java b/instrumentation/grpc/src/test/java/brave/grpc/BaseITTracingClientInterceptor.java index 9846fbf1db..8b403ff957 100644 --- a/instrumentation/grpc/src/test/java/brave/grpc/BaseITTracingClientInterceptor.java +++ b/instrumentation/grpc/src/test/java/brave/grpc/BaseITTracingClientInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -43,6 +43,7 @@ import io.grpc.examples.helloworld.GraterGrpc; import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; import io.grpc.internal.GrpcUtil; import io.grpc.stub.StreamObserver; import java.io.IOException; @@ -57,6 +58,7 @@ import static brave.Span.Kind.CLIENT; import static brave.grpc.GreeterImpl.HELLO_REQUEST; +import static brave.grpc.GrpcPropagation.GRPC_TRACE_BIN; import static brave.rpc.RpcRequestMatchers.methodEquals; import static brave.rpc.RpcRequestMatchers.serviceEquals; import static brave.sampler.Sampler.ALWAYS_SAMPLE; @@ -213,7 +215,7 @@ ManagedChannel newClient(ClientInterceptor... clientInterceptors) { // The error format of the exception message can differ from the span's "error" tag in CI MutableSpan span = testSpanHandler.takeRemoteSpanWithErrorMessage(CLIENT, ".*Connection refused.*"); - assertThat(span.tags()).containsEntry("rpc.error_code", "UNAVAILABLE"); + assertThat(span.tags()).containsEntry("grpc.status_code", "UNAVAILABLE"); } @Test void setsErrorTag_onUnimplemented() { @@ -221,7 +223,7 @@ ManagedChannel newClient(ClientInterceptor... clientInterceptors) { .isInstanceOf(StatusRuntimeException.class); MutableSpan span = testSpanHandler.takeRemoteSpanWithErrorTag(CLIENT, "UNIMPLEMENTED"); - assertThat(span.tags().get("rpc.error_code")).isEqualTo("UNIMPLEMENTED"); + assertThat(span.tags().get("grpc.status_code")).isEqualTo("UNIMPLEMENTED"); } @Test void setsErrorTag_onCanceledFuture() { @@ -231,7 +233,7 @@ ManagedChannel newClient(ClientInterceptor... clientInterceptors) { assumeTrue(resp.cancel(true), "lost race on cancel"); MutableSpan span = testSpanHandler.takeRemoteSpanWithErrorTag(CLIENT, "CANCELLED"); - assertThat(span.tags().get("rpc.error_code")).isEqualTo("CANCELLED"); + assertThat(span.tags().get("grpc.status_code")).isEqualTo("CANCELLED"); } /** @@ -270,6 +272,103 @@ public void start(Listener responseListener, Metadata headers) { .containsOnly("start", "sendMessage"); } + @Test void clientParserTest() { + closeClient(client); + grpcTracing = grpcTracing.toBuilder().clientParser(new GrpcClientParser() { + @Override protected void onMessageSent(M message, SpanCustomizer span) { + span.tag("grpc.message_sent", message.toString()); + if (tracing.currentTraceContext().get() != null) { + span.tag("grpc.message_sent.visible", "true"); + } + } + + @Override protected void onMessageReceived(M message, SpanCustomizer span) { + span.tag("grpc.message_received", message.toString()); + if (tracing.currentTraceContext().get() != null) { + span.tag("grpc.message_received.visible", "true"); + } + } + + @Override + protected String spanName(MethodDescriptor methodDescriptor) { + return methodDescriptor.getType().name(); + } + }).build(); + client = newClient(); + + ScopedSpan parent = tracing.tracer().startScopedSpan("parent"); + try { + GreeterGrpc.newBlockingStub(client).sayHello(HELLO_REQUEST); + } finally { + parent.finish(); + } + + MutableSpan span = testSpanHandler.takeRemoteSpan(CLIENT); + assertThat(span.name()).isEqualTo("UNARY"); + assertThat(span.tags()).containsKeys( + "grpc.message_received", "grpc.message_sent", + "grpc.message_received.visible", "grpc.message_sent.visible" + ); + testSpanHandler.takeLocalSpan(); + } + + @Test void deprecated_grpcPropagationFormatEnabled() { + closeClient(client); + grpcTracing = grpcTracing.toBuilder().grpcPropagationFormatEnabled(true).build(); + client = newClient(); + + GreeterGrpc.newBlockingStub(client).sayHello(HELLO_REQUEST); + + // Check the grpc-trace-bin header was sent and equal to the one encoded with B3 + Metadata headers = server.headers.poll(); + byte[] traceBin = headers.get(GRPC_TRACE_BIN); + assertThat(TraceContextBinaryFormat.parseBytes(traceBin, null)) + .isEqualTo(server.requests.poll().context()); + + testSpanHandler.takeRemoteSpan(CLIENT); + } + + @Test void deprecated_clientParserTestStreamingResponse() { + closeClient(client); + grpcTracing = grpcTracing.toBuilder().clientParser(new GrpcClientParser() { + int receiveCount = 0; + + @Override protected void onMessageReceived(M message, SpanCustomizer span) { + span.tag("grpc.message_received." + receiveCount++, message.toString()); + } + }).build(); + client = newClient(); + + Iterator replies = GreeterGrpc.newBlockingStub(client) + .sayHelloWithManyReplies(HelloRequest.newBuilder().setName("this is dog").build()); + assertThat(replies).toIterable().hasSize(10); + + // all response messages are tagged to the same span + assertThat(testSpanHandler.takeRemoteSpan(CLIENT).tags()).hasSize(10); + } + + // Make sure we work well with bad user interceptors. + + @Test void userInterceptor_throwsOnStart() { + closeClient(client); + client = newClient(new ClientInterceptor() { + @Override public ClientCall interceptCall( + MethodDescriptor methodDescriptor, CallOptions callOptions, + Channel channel) { + ClientCall call = channel.newCall(methodDescriptor, callOptions); + return new SimpleForwardingClientCall(call) { + @Override public void start(Listener responseListener, Metadata headers) { + throw new IllegalStateException("I'm a bad interceptor."); + } + }; + } + }, grpcTracing.newClientInterceptor()); + + assertThatThrownBy(() -> GreeterGrpc.newBlockingStub(client).sayHello(HELLO_REQUEST)) + .isInstanceOf(IllegalStateException.class); + testSpanHandler.takeRemoteSpanWithErrorMessage(CLIENT, "I'm a bad interceptor."); + } + @Test void userInterceptor_throwsOnHalfClose() { closeClient(client); client = newClient(new ClientInterceptor() { diff --git a/instrumentation/grpc/src/test/java/brave/grpc/BaseITTracingServerInterceptor.java b/instrumentation/grpc/src/test/java/brave/grpc/BaseITTracingServerInterceptor.java index 408dd3801a..0696d64c2f 100644 --- a/instrumentation/grpc/src/test/java/brave/grpc/BaseITTracingServerInterceptor.java +++ b/instrumentation/grpc/src/test/java/brave/grpc/BaseITTracingServerInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -62,11 +62,13 @@ import org.junit.jupiter.api.Test; import static brave.grpc.GreeterImpl.HELLO_REQUEST; +import static brave.grpc.GrpcPropagation.GRPC_TRACE_BIN; import static brave.rpc.RpcRequestMatchers.methodEquals; import static brave.rpc.RpcRequestMatchers.serviceEquals; import static brave.sampler.Sampler.ALWAYS_SAMPLE; import static brave.sampler.Sampler.NEVER_SAMPLE; import static io.grpc.Metadata.ASCII_STRING_MARSHALLER; +import static io.grpc.stub.MetadataUtils.attachHeaders; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -192,7 +194,7 @@ public ServerCall.Listener interceptCall(ServerCall ServerCall.Listener interceptCall(ServerCall void onMessageSent(M message, SpanCustomizer span) { + span.tag("grpc.message_sent", message.toString()); + if (tracing.currentTraceContext().get() != null) { + span.tag("grpc.message_sent.visible", "true"); + } + } + + @Override protected void onMessageReceived(M message, SpanCustomizer span) { + span.tag("grpc.message_received", message.toString()); + if (tracing.currentTraceContext().get() != null) { + span.tag("grpc.message_received.visible", "true"); + } + } + + @Override + protected String spanName(MethodDescriptor methodDescriptor) { + return methodDescriptor.getType().name(); + } + }).build(); + init(); + + GreeterGrpc.newBlockingStub(client).sayHello(HELLO_REQUEST); + + MutableSpan span = testSpanHandler.takeRemoteSpan(Span.Kind.SERVER); + assertThat(span.name()).isEqualTo("UNARY"); + assertThat(span.tags().keySet()).containsExactlyInAnyOrder( + "grpc.message_received", "grpc.message_sent", + "grpc.message_received.visible", "grpc.message_sent.visible" + ); + } + + @Test void serverParserTestWithStreamingResponse() throws IOException { + grpcTracing = grpcTracing.toBuilder().serverParser(new GrpcServerParser() { + int responsesSent = 0; + + @Override protected void onMessageSent(M message, SpanCustomizer span) { + span.tag("grpc.message_sent." + responsesSent++, message.toString()); + } + }).build(); + init(); + + Iterator replies = GreeterGrpc.newBlockingStub(client) + .sayHelloWithManyReplies(HELLO_REQUEST); + assertThat(replies).toIterable().hasSize(10); + // all response messages are tagged to the same span + assertThat(testSpanHandler.takeRemoteSpan(Span.Kind.SERVER).tags()).hasSize(10); + } + + @Test void deprecated_grpcPropagationFormatEnabled() throws IOException { + grpcTracing = grpcTracing.toBuilder().grpcPropagationFormatEnabled(true).build(); + init(); + + TraceContext context = newTraceContext(SamplingFlags.SAMPLED); + + // Add gRPC-encoded headers to the request + Metadata headers = new Metadata(); + headers.put(GRPC_TRACE_BIN, TraceContextBinaryFormat.toBytes(context)); + attachHeaders(GreeterGrpc.newBlockingStub(client), headers).sayHello(HELLO_REQUEST); + + // Ensure that trace was continued + assertThat(testSpanHandler.takeRemoteSpan(Span.Kind.SERVER).traceId()) + .isEqualTo(context.traceIdString()); } // Make sure we work well with bad user interceptors. diff --git a/instrumentation/grpc/src/test/java/brave/grpc/GreeterImpl.java b/instrumentation/grpc/src/test/java/brave/grpc/GreeterImpl.java index c35f0d8882..536cbed1fa 100644 --- a/instrumentation/grpc/src/test/java/brave/grpc/GreeterImpl.java +++ b/instrumentation/grpc/src/test/java/brave/grpc/GreeterImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,6 +13,9 @@ */ package brave.grpc; +import brave.CurrentSpanCustomizer; +import brave.NoopSpanCustomizer; +import brave.SpanCustomizer; import brave.Tracing; import brave.internal.Nullable; import brave.propagation.TraceContext; @@ -26,9 +29,12 @@ class GreeterImpl extends GreeterGrpc.GreeterImplBase { static final HelloRequest HELLO_REQUEST = HelloRequest.newBuilder().setName("tracer").build(); @Nullable final Tracing tracing; + final SpanCustomizer spanCustomizer; GreeterImpl(@Nullable GrpcTracing grpcTracing) { tracing = grpcTracing != null ? grpcTracing.rpcTracing.tracing() : null; + spanCustomizer = + tracing != null ? CurrentSpanCustomizer.create(tracing) : NoopSpanCustomizer.INSTANCE; } @Override diff --git a/instrumentation/grpc/src/test/java/brave/grpc/GrpcParserTest.java b/instrumentation/grpc/src/test/java/brave/grpc/GrpcParserTest.java index 59df490c4a..4d5f695b87 100644 --- a/instrumentation/grpc/src/test/java/brave/grpc/GrpcParserTest.java +++ b/instrumentation/grpc/src/test/java/brave/grpc/GrpcParserTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -18,7 +18,7 @@ import static brave.grpc.TestObjects.METHOD_DESCRIPTOR; import static org.assertj.core.api.Assertions.assertThat; -class GrpcParserTest { +public class GrpcParserTest { @Test void method() { assertThat(GrpcParser.method(METHOD_DESCRIPTOR.getFullMethodName())) .isEqualTo("SayHello"); diff --git a/instrumentation/grpc/src/test/java/brave/grpc/TestServer.java b/instrumentation/grpc/src/test/java/brave/grpc/TestServer.java index 9c537a3321..8802c2a43b 100644 --- a/instrumentation/grpc/src/test/java/brave/grpc/TestServer.java +++ b/instrumentation/grpc/src/test/java/brave/grpc/TestServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/instrumentation/grpc/src/test/java/brave/grpc/TraceContextBinaryFormatTest.java b/instrumentation/grpc/src/test/java/brave/grpc/TraceContextBinaryFormatTest.java new file mode 100644 index 0000000000..d270c20dcd --- /dev/null +++ b/instrumentation/grpc/src/test/java/brave/grpc/TraceContextBinaryFormatTest.java @@ -0,0 +1,158 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.grpc; + +import brave.grpc.GrpcPropagation.TagsBin; +import brave.propagation.TraceContext; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests here are based on {@code io.opencensus.implcore.trace.propagation.BinaryFormatImplTest} + */ +public class TraceContextBinaryFormatTest { + TraceContext context = TraceContext.newBuilder() + .traceIdHigh(Long.MAX_VALUE).traceId(Long.MIN_VALUE) + .spanId(-1) + .sampled(true) + .build(); + + byte[] contextBytes = { + 0, // version + 0, 127, -1, -1, -1, -1, -1, -1, -1, -128, 0, 0, 0, 0, 0, 0, 0, // trace ID + 1, -1, -1, -1, -1, -1, -1, -1, -1, // span ID + 2, 1 // sampled + }; + byte[] tagsBytes = { + 0, // version + 0, // field number + 6, 'm', 'e', 't', 'h', 'o', 'd', // + 3, 'f', 'o', 'o', // + 0, // field number + 4, 'u', 's', 'e', 'r', // + 5, 'r', 'o', 'm', 'e', 'o' // + }; + + @Test void roundtrip() { + byte[] serialized = TraceContextBinaryFormat.toBytes(context); + assertThat(serialized) + .containsExactly(contextBytes); + + assertThat(TraceContextBinaryFormat.parseBytes(serialized, null)) + .isEqualTo(context); + } + + @Test void roundtrip_unsampled() { + context = context.toBuilder().sampled(false).build(); + + byte[] serialized = TraceContextBinaryFormat.toBytes(context); + contextBytes[contextBytes.length - 1] = 0; // unsampled + assertThat(serialized) + .containsExactly(contextBytes); + + assertThat(TraceContextBinaryFormat.parseBytes(serialized, null)) + .isEqualTo(context); + } + + @Test void roundtrip_tags() { + TagsBin tags = new TagsBin(tagsBytes); + context = context.toBuilder().extra(Collections.singletonList(tags)).build(); + + byte[] serialized = TraceContextBinaryFormat.toBytes(context); + assertThat(serialized) + .containsExactly(contextBytes); + + assertThat(TraceContextBinaryFormat.parseBytes(serialized, new TagsBin(tagsBytes))) + .isEqualTo(context); + } + + @Test void parseBytes_empty_toNull() { + assertThat(TraceContextBinaryFormat.parseBytes(new byte[0], null)) + .isNull(); + } + + @Test void parseBytes_unsupportedVersionId_toNull() { + assertThat(TraceContextBinaryFormat.parseBytes(new byte[] { + 1, // bad version + 0, 127, -1, -1, -1, -1, -1, -1, -1, -128, 0, 0, 0, 0, 0, 0, 0, + 1, -1, -1, -1, -1, -1, -1, -1, -1, + 2, 1 + }, null)).isNull(); + } + + @Test void parseBytes_unsupportedFieldIdFirst_toNull() { + assertThat(TraceContextBinaryFormat.parseBytes(new byte[] { + 0, + 4, 127, -1, -1, -1, -1, -1, -1, -1, -128, 0, 0, 0, 0, 0, 0, 0, // bad field number + 1, -1, -1, -1, -1, -1, -1, -1, -1, + 2, 1 + }, null)).isNull(); + } + + @Test void parseBytes_unsupportedFieldIdSecond_toNull() { + assertThat(TraceContextBinaryFormat.parseBytes(new byte[] { + 0, + 0, 127, -1, -1, -1, -1, -1, -1, -1, -128, 0, 0, 0, 0, 0, 0, 0, + 4, -1, -1, -1, -1, -1, -1, -1, -1, // bad field number + 2, 1 + }, null)).isNull(); + } + + @Test void parseBytes_unsupportedFieldIdThird_toSampledNull() { + assertThat(TraceContextBinaryFormat.parseBytes(new byte[] { + 0, + 0, 127, -1, -1, -1, -1, -1, -1, -1, -128, 0, 0, 0, 0, 0, 0, 0, + 1, -1, -1, -1, -1, -1, -1, -1, -1, + 4, 1 // bad field number + }, null).sampled()).isNull(); + } + + @Test void parseBytes_64BitTraceId_toNull() { + assertThat(TraceContextBinaryFormat.parseBytes(new byte[] { + 0, + 0, 127, -1, -1, -1, -1, -1, -1, -1, // half a trace ID + 1, -1, -1, -1, -1, -1, -1, -1, -1, + 2, 1 + }, null)).isNull(); + } + + @Test void parseBytes_32BitSpanId_toNull() { + assertThat(TraceContextBinaryFormat.parseBytes(new byte[] { + 0, + 0, 127, -1, -1, -1, -1, -1, -1, -1, -128, 0, 0, 0, 0, 0, 0, 0, + 1, -1, -1, -1, -1, // half a span ID + 2, 1 + }, null)).isNull(); + } + + @Test void parseBytes_truncatedTraceOptions_toNull() { + assertThat(TraceContextBinaryFormat.parseBytes(new byte[] { + 0, + 0, 127, -1, -1, -1, -1, -1, -1, -1, -128, 0, 0, 0, 0, 0, 0, 0, + 1, -1, -1, -1, -1, -1, -1, -1, -1, + 2 // has field ID, but missing sampled bit + }, null)).isNull(); + } + + @Test void parseBytes_missingTraceOptions() { + assertThat(TraceContextBinaryFormat.parseBytes(new byte[] { + 0, + 0, 127, -1, -1, -1, -1, -1, -1, -1, -128, 0, 0, 0, 0, 0, 0, 0, + 1, -1, -1, -1, -1, -1, -1, -1, -1, + // no trace options field + }, null)).isEqualTo(context); + } +} diff --git a/instrumentation/http-tests-jakarta/src/main/java/brave/test/jakarta/http/ITServlet5Container.java b/instrumentation/http-tests-jakarta/src/main/java/brave/test/jakarta/http/ITServlet5Container.java index 38995ef2b7..a90e9a932c 100644 --- a/instrumentation/http-tests-jakarta/src/main/java/brave/test/jakarta/http/ITServlet5Container.java +++ b/instrumentation/http-tests-jakarta/src/main/java/brave/test/jakarta/http/ITServlet5Container.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -18,6 +18,11 @@ import brave.http.HttpTracing; import brave.propagation.TraceContext; import brave.test.http.ITServletContainer; + +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + import jakarta.servlet.AsyncContext; import jakarta.servlet.AsyncEvent; import jakarta.servlet.AsyncListener; @@ -32,9 +37,6 @@ import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import okhttp3.Request; import okhttp3.Response; import org.eclipse.jetty.servlet.ServletContextHandler; diff --git a/instrumentation/http-tests-jakarta/src/main/java/brave/test/jakarta/http/Jetty11ServerController.java b/instrumentation/http-tests-jakarta/src/main/java/brave/test/jakarta/http/Jetty11ServerController.java index 22ab77e77c..adcfa8ad9f 100644 --- a/instrumentation/http-tests-jakarta/src/main/java/brave/test/jakarta/http/Jetty11ServerController.java +++ b/instrumentation/http-tests-jakarta/src/main/java/brave/test/jakarta/http/Jetty11ServerController.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2022 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,6 +13,7 @@ */ package brave.test.jakarta.http; +import brave.test.http.ServletContainer; import brave.test.http.ServletContainer.ServerController; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; diff --git a/instrumentation/http-tests/src/main/java/brave/test/http/ITHttpClient.java b/instrumentation/http-tests/src/main/java/brave/test/http/ITHttpClient.java index 044d9d9360..2b5b75cf73 100644 --- a/instrumentation/http-tests/src/main/java/brave/test/http/ITHttpClient.java +++ b/instrumentation/http-tests/src/main/java/brave/test/http/ITHttpClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,8 +14,11 @@ package brave.test.http; import brave.Clock; +import brave.SpanCustomizer; import brave.handler.MutableSpan; import brave.handler.SpanHandler; +import brave.http.HttpAdapter; +import brave.http.HttpClientParser; import brave.http.HttpRequest; import brave.http.HttpResponseParser; import brave.http.HttpRuleSampler; @@ -255,6 +258,49 @@ public abstract class ITHttpClient extends ITRemote { .containsEntry("response_customizer.is_span", "false"); } + @Deprecated + @Test protected void supportsDeprecatedPortableCustomization() throws IOException { + String uri = "/foo/bar?z=2&yAA=1"; + + closeClient(client); + httpTracing = httpTracing.toBuilder() + .clientParser(new HttpClientParser() { + @Override + public void request(HttpAdapter adapter, Req req, + SpanCustomizer customizer) { + customizer.name(adapter.method(req).toLowerCase() + " " + adapter.path(req)); + customizer.tag("http.url", adapter.url(req)); // just the path is tagged by default + customizer.tag("context.visible", String.valueOf(currentTraceContext.get() != null)); + customizer.tag("request_customizer.is_span", (customizer instanceof brave.Span) + ""); + } + + @Override + public void response(HttpAdapter adapter, Resp res, Throwable error, + SpanCustomizer customizer) { + super.response(adapter, res, error, customizer); + customizer.tag("response_customizer.is_span", (customizer instanceof brave.Span) + ""); + } + }) + .build().clientOf("remote-service"); + + client = newClient(server.getPort()); + server.enqueue(new MockResponse()); + get(client, uri); + + MutableSpan span = testSpanHandler.takeRemoteSpan(CLIENT); + assertThat(span.name()) + .isEqualTo("get /foo/bar"); + + assertThat(span.remoteServiceName()) + .isEqualTo("remote-service"); + + assertThat(span.tags()) + .containsEntry("http.url", url(uri)) + .containsEntry("context.visible", "true") + .containsEntry("request_customizer.is_span", "false") + .containsEntry("response_customizer.is_span", "false"); + } + @Test protected void addsStatusCodeWhenNotOk() throws IOException { server.enqueue(new MockResponse().setResponseCode(400)); diff --git a/instrumentation/http-tests/src/main/java/brave/test/http/ITHttpServer.java b/instrumentation/http-tests/src/main/java/brave/test/http/ITHttpServer.java index 74ed2398c7..00cdfa7dfd 100644 --- a/instrumentation/http-tests/src/main/java/brave/test/http/ITHttpServer.java +++ b/instrumentation/http-tests/src/main/java/brave/test/http/ITHttpServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,13 +13,16 @@ */ package brave.test.http; +import brave.SpanCustomizer; import brave.baggage.BaggageField; import brave.handler.MutableSpan; import brave.handler.SpanHandler; +import brave.http.HttpAdapter; import brave.http.HttpRequest; import brave.http.HttpRequestParser; import brave.http.HttpResponseParser; import brave.http.HttpRuleSampler; +import brave.http.HttpServerParser; import brave.http.HttpTags; import brave.http.HttpTracing; import brave.propagation.B3SingleFormat; @@ -274,6 +277,36 @@ void readsBaggage(Request.Builder builder) throws IOException { .containsEntry("response_customizer.is_span", "false"); } + @Test + @Deprecated + public void supportsPortableCustomizationDeprecated() throws IOException { + httpTracing = httpTracing.toBuilder().serverParser(new HttpServerParser() { + @Override + public void request(HttpAdapter adapter, Req req, SpanCustomizer customizer) { + customizer.tag("http.url", adapter.url(req)); // just the path is tagged by default + customizer.tag("context.visible", String.valueOf(currentTraceContext.get() != null)); + customizer.tag("request_customizer.is_span", (customizer instanceof brave.Span) + ""); + } + + @Override + public void response(HttpAdapter adapter, Resp res, Throwable error, + SpanCustomizer customizer) { + super.response(adapter, res, error, customizer); + customizer.tag("response_customizer.is_span", (customizer instanceof brave.Span) + ""); + } + }).build(); + init(); + + String uri = "/foo?z=2&yAA=1"; + get(uri); + + assertThat(testSpanHandler.takeRemoteSpan(SERVER).tags()) + .containsEntry("http.url", url(uri)) + .containsEntry("context.visible", "true") + .containsEntry("request_customizer.is_span", "false") + .containsEntry("response_customizer.is_span", "false"); + } + /** * The "/items/{itemId}" endpoint should return the itemId in the response body, which proves * templating worked (including that it ignores query parameters). Note the route format is diff --git a/instrumentation/http-tests/src/test/java/brave/http/features/RequestSamplingTest.java b/instrumentation/http-tests/src/test/java/brave/http/features/RequestSamplingTest.java index 2b15de0687..6201804991 100644 --- a/instrumentation/http-tests/src/test/java/brave/http/features/RequestSamplingTest.java +++ b/instrumentation/http-tests/src/test/java/brave/http/features/RequestSamplingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -18,14 +18,14 @@ import brave.propagation.StrictCurrentTraceContext; import brave.test.IntegrationTestSpanHandler; import java.io.IOException; -import okhttp3.Call; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.ResponseBody; import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; +import okhttp3.Call; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.ResponseBody; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/instrumentation/http-tests/src/test/java/brave/http/features/TracingDispatcher.java b/instrumentation/http-tests/src/test/java/brave/http/features/TracingDispatcher.java index 20ecea0bd9..427d7e0eb3 100644 --- a/instrumentation/http-tests/src/test/java/brave/http/features/TracingDispatcher.java +++ b/instrumentation/http-tests/src/test/java/brave/http/features/TracingDispatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -41,7 +41,7 @@ final class TracingDispatcher extends Dispatcher { Span span = handler.handleReceive(request); MockResponse response = null; Throwable error = null; - try (SpanInScope scope = tracer.withSpanInScope(span)) { + try (SpanInScope ws = tracer.withSpanInScope(span)) { return response = delegate.dispatch(req); } catch (Throwable e) { error = e; diff --git a/instrumentation/http-tests/src/test/java/brave/http/features/TracingInterceptor.java b/instrumentation/http-tests/src/test/java/brave/http/features/TracingInterceptor.java index 77b204fd30..5c31e5ffea 100644 --- a/instrumentation/http-tests/src/test/java/brave/http/features/TracingInterceptor.java +++ b/instrumentation/http-tests/src/test/java/brave/http/features/TracingInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -41,7 +41,7 @@ final class TracingInterceptor implements Interceptor { Span span = handler.handleSend(request); Response response = null; Throwable error = null; - try (SpanInScope scope = tracer.withSpanInScope(span)) { + try (SpanInScope ws = tracer.withSpanInScope(span)) { return response = chain.proceed(request.build()); } catch (Throwable e) { error = e; diff --git a/instrumentation/http/README.md b/instrumentation/http/README.md index a5459dcfc6..5dcff90281 100644 --- a/instrumentation/http/README.md +++ b/instrumentation/http/README.md @@ -176,7 +176,7 @@ HttpClientRequestWrapper requestWrapper = new HttpClientRequestWrapper(request); Span span = handler.handleSend(requestWrapper); // 1. HttpClientResponse response = null; Throwable error = null; -try (Scope scope = currentTraceContext.newScope(span.context())) { // 2. +try (Scope ws = currentTraceContext.newScope(span.context())) { // 2. return response = invoke(request); // 3. } catch (Throwable e) { error = e; // 4. @@ -246,7 +246,7 @@ HttpServerRequestWrapper requestWrapper = new HttpServerRequestWrapper(request); Span span = handler.handleReceive(requestWrapper); // 1. HttpServerResponse response = null; Throwable error = null; -try (Scope scope = currentTraceContext.newScope(span.context())) { // 2. +try (Scope ws = currentTraceContext.newScope(span.context())) { // 2. return response = process(request); // 3. } catch (Throwable e) { error = e; // 4. diff --git a/instrumentation/http/pom.xml b/instrumentation/http/pom.xml index 2ff2cf051e..1e5ed123d0 100644 --- a/instrumentation/http/pom.xml +++ b/instrumentation/http/pom.xml @@ -33,6 +33,14 @@ + + + io.zipkin.zipkin2 + zipkin + ${zipkin.version} + true + + ${project.groupId} brave-tests diff --git a/instrumentation/http/src/main/java/brave/http/HttpAdapter.java b/instrumentation/http/src/main/java/brave/http/HttpAdapter.java new file mode 100644 index 0000000000..d9a2c8e455 --- /dev/null +++ b/instrumentation/http/src/main/java/brave/http/HttpAdapter.java @@ -0,0 +1,84 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.internal.Nullable; +import java.net.URI; + +/** @deprecated Since 5.10, use {@link HttpRequest} and {@link HttpResponse} */ +@Deprecated public abstract class HttpAdapter { + /** @see HttpRequest#method() */ + public abstract String method(Req request); + + /** @see HttpRequest#path() */ + @Nullable public String path(Req request) { + String url = url(request); + if (url == null) return null; + return URI.create(url).getPath(); // TODO benchmark + } + + /** @see HttpRequest#url() */ + @Nullable public abstract String url(Req request); + + /** @see HttpRequest#header(String) */ + @Nullable public abstract String requestHeader(Req request, String name); + + /** + * @see HttpRequest#startTimestamp() + * @since 5.7 + */ + public long startTimestamp(Req request) { + return 0L; + } + + /** @see HttpResponse#method() */ + // FromResponse suffix is needed as you can't compile methods that only differ on generic params + @Nullable public String methodFromResponse(Resp resp) { + return null; + } + + /** @see HttpResponse#route() */ + // NOTE: It wasn't possible for a user to easily consume HttpServerAdapter, which is why this + // method, while generally about the server, was pushed up to the HttpAdapter. + @Nullable public String route(Resp response) { + return null; + } + + /** + * @see HttpResponse#statusCode() + * @see #statusCodeAsInt(Object) + * @deprecated Since 5.7, use {@link #statusCodeAsInt(Object)} which prevents boxing. + */ + @Deprecated @Nullable public abstract Integer statusCode(Resp response); + + /** + * @see HttpResponse#statusCode() + * @since 4.16 + */ + public int statusCodeAsInt(Resp response) { + Integer maybeStatus = statusCode(response); + return maybeStatus != null ? maybeStatus : 0; + } + + /** + * @see HttpResponse#finishTimestamp() + * @since 5.7 + */ + public long finishTimestamp(Resp response) { + return 0L; + } + + HttpAdapter() { + } +} diff --git a/instrumentation/http/src/main/java/brave/http/HttpClientAdapter.java b/instrumentation/http/src/main/java/brave/http/HttpClientAdapter.java new file mode 100644 index 0000000000..f9f741aa5f --- /dev/null +++ b/instrumentation/http/src/main/java/brave/http/HttpClientAdapter.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import zipkin2.Endpoint; + +/** @deprecated Since 5.10, use {@link HttpClientRequest} and {@link HttpClientResponse} */ +@Deprecated public abstract class HttpClientAdapter extends HttpAdapter { + /** + * Returns true if an IP representing the client was readable. + * + * @deprecated remote IP information should be added directly by instrumentation. This will be + * removed in Brave v6. + */ + @Deprecated public boolean parseServerIpAndPort(Req req, Endpoint.Builder builder) { + return false; + } +} diff --git a/instrumentation/http/src/main/java/brave/http/HttpClientAdapters.java b/instrumentation/http/src/main/java/brave/http/HttpClientAdapters.java new file mode 100644 index 0000000000..f9941b1af9 --- /dev/null +++ b/instrumentation/http/src/main/java/brave/http/HttpClientAdapters.java @@ -0,0 +1,235 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.internal.Nullable; + +/** @deprecated Intentionally hidden: implemented to support deprecated signatures. */ +@Deprecated final class HttpClientAdapters { + // Void type used to force generics to fail handling the wrong side. + @Deprecated static final class ToRequestAdapter extends HttpClientAdapter { + final HttpClientRequest delegate; + final Object unwrapped; + + ToRequestAdapter(HttpClientRequest delegate, Object unwrapped) { + if (delegate == null) throw new NullPointerException("delegate == null"); + if (unwrapped == null) throw new NullPointerException("unwrapped == null"); + this.delegate = delegate; + this.unwrapped = unwrapped; + } + + @Override public final long startTimestamp(Object request) { + if (request == unwrapped) return delegate.startTimestamp(); + return 0L; + } + + @Override public final String method(Object request) { + if (request == unwrapped) return delegate.method(); + return null; + } + + @Override public final String url(Object request) { + if (request == unwrapped) return delegate.url(); + return null; + } + + @Override public final String requestHeader(Object request, String name) { + if (request == unwrapped) return delegate.header(name); + return null; + } + + @Override public final String path(Object request) { + if (request == unwrapped) return delegate.path(); + return null; + } + + @Override public final String toString() { + return delegate.toString(); + } + + // Skip response adapter methods + + @Override public final String methodFromResponse(Void response) { + return null; + } + + @Override public final String route(Void response) { + return null; + } + + @Override public final int statusCodeAsInt(Void response) { + return 0; + } + + @Override @Nullable public final Integer statusCode(Void response) { + return null; + } + + @Override public final long finishTimestamp(Void response) { + return 0L; + } + } + + @Deprecated static final class FromRequestAdapter extends HttpClientRequest { + final HttpClientAdapter adapter; + final Req request; + + FromRequestAdapter(HttpClientAdapter adapter, Req request) { + if (adapter == null) throw new NullPointerException("adapter == null"); + this.adapter = adapter; + if (request == null) throw new NullPointerException("request == null"); + this.request = request; + } + + @Override public Object unwrap() { + return request; + } + + @Override public long startTimestamp() { + return adapter.startTimestamp(request); + } + + @Override public String method() { + return adapter.method(request); + } + + @Override public String path() { + return adapter.path(request); + } + + @Override public String url() { + return adapter.url(request); + } + + @Override public String header(String name) { + return adapter.requestHeader(request, name); + } + + @Override public void header(String name, String value) { + // not called + } + + @Override public final String toString() { + return request.toString(); + } + } + + // Void type used to force generics to fail handling the wrong side + @Deprecated static final class ToResponseAdapter extends HttpClientAdapter { + final HttpClientResponse delegate; + final Object unwrapped; + + ToResponseAdapter(HttpClientResponse delegate, Object unwrapped) { + if (delegate == null) throw new NullPointerException("delegate == null"); + if (unwrapped == null) throw new NullPointerException("unwrapped == null"); + this.delegate = delegate; + this.unwrapped = unwrapped; + } + + // Skip request adapter methods + @Override public final String method(Void request) { + return null; + } + + @Override public final String path(Void request) { + return null; + } + + @Override public final String url(Void request) { + return null; + } + + @Override public final String requestHeader(Void request, String name) { + return null; + } + + @Override public final long startTimestamp(Void request) { + return 0L; + } + + // Begin response adapter methods + + @Override public final String methodFromResponse(Object response) { + if (response == unwrapped) return delegate.method(); + return null; + } + + @Override public final String route(Object response) { + if (response == unwrapped) return delegate.route(); + return null; + } + + @Override @Nullable public final Integer statusCode(Object response) { + int result = statusCodeAsInt(response); + return result == 0 ? null : result; + } + + @Override public final int statusCodeAsInt(Object response) { + if (response == unwrapped) return delegate.statusCode(); + return 0; + } + + @Override public final long finishTimestamp(Object response) { + if (response == unwrapped) return delegate.finishTimestamp(); + return 0L; + } + + @Override public String toString() { + return delegate.toString(); + } + } + + @Deprecated static final class FromResponseAdapter extends HttpClientResponse { + final HttpClientAdapter adapter; + final Res response; + @Nullable final Throwable error; + + FromResponseAdapter( + HttpClientAdapter adapter, Res response, @Nullable Throwable error) { + if (adapter == null) throw new NullPointerException("adapter == null"); + if (response == null) throw new NullPointerException("response == null"); + this.adapter = adapter; + this.response = response; + this.error = error; + } + + @Override public Throwable error() { + return error; + } + + @Override public Object unwrap() { + return response; + } + + @Override public String method() { + return adapter.methodFromResponse(response); + } + + @Override public String route() { + return adapter.route(response); + } + + @Override public int statusCode() { + return adapter.statusCodeAsInt(response); + } + + @Override public long finishTimestamp() { + return adapter.finishTimestamp(response); + } + + @Override public String toString() { + return response.toString(); + } + } +} diff --git a/instrumentation/http/src/main/java/brave/http/HttpClientHandler.java b/instrumentation/http/src/main/java/brave/http/HttpClientHandler.java index 32fca7eee1..aca15b88ca 100644 --- a/instrumentation/http/src/main/java/brave/http/HttpClientHandler.java +++ b/instrumentation/http/src/main/java/brave/http/HttpClientHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -16,11 +16,15 @@ import brave.Span; import brave.SpanCustomizer; import brave.Tracer; +import brave.http.HttpClientAdapters.FromRequestAdapter; import brave.internal.Nullable; import brave.propagation.TraceContext; import brave.propagation.TraceContext.Injector; +import brave.sampler.Sampler; import brave.sampler.SamplerFunction; +import static brave.http.HttpClientAdapters.FromResponseAdapter; + /** * This standardizes a way to instrument http clients, particularly in a way that encourages use of * portable customizations via {@link HttpRequestParser} and {@link HttpResponseParser}. @@ -41,7 +45,7 @@ * Span span = handler.handleSend(requestWrapper); // 1. * HttpClientResponse response = null; * Throwable error = null; - * try (Scope scope = currentTraceContext.newScope(span.context())) { // 2. + * try (Scope ws = currentTraceContext.newScope(span.context())) { // 2. * return response = invoke(request); // 3. * } catch (Throwable e) { * error = e; // 4. @@ -65,17 +69,33 @@ public final class HttpClientHandler extends HttpHandler { public static HttpClientHandler create( HttpTracing httpTracing) { if (httpTracing == null) throw new NullPointerException("httpTracing == null"); - return new HttpClientHandler(httpTracing); + return new HttpClientHandler(httpTracing, null); + } + + /** + * @since 4.3 + * @deprecated Since 5.7, use {@link #create(HttpTracing)} as it is more portable. + */ + @Deprecated + public static HttpClientHandler create(HttpTracing httpTracing, + HttpClientAdapter adapter) { + if (httpTracing == null) throw new NullPointerException("httpTracing == null"); + if (adapter == null) throw new NullPointerException("adapter == null"); + return new HttpClientHandler(httpTracing, adapter); } final Tracer tracer; + @Deprecated @Nullable final HttpClientAdapter adapter; // null when using default types + final Sampler sampler; final SamplerFunction httpSampler; @Nullable final String serverName; final Injector defaultInjector; - HttpClientHandler(HttpTracing httpTracing) { + HttpClientHandler(HttpTracing httpTracing, @Deprecated HttpClientAdapter adapter) { super(httpTracing.clientRequestParser(), httpTracing.clientResponseParser()); + this.adapter = adapter; this.tracer = httpTracing.tracing().tracer(); + this.sampler = httpTracing.tracing().sampler(); this.httpSampler = httpTracing.clientRequestSampler(); this.serverName = !"".equals(httpTracing.serverName()) ? httpTracing.serverName() : null; // The following allows us to add the method: handleSend(HttpClientRequest request) without @@ -132,6 +152,95 @@ public Span handleSend(HttpClientRequest request, Span span) { super.parseRequest(request, span); } + /** + * @since 4.3 + * @deprecated Since 5.7, use {@link #handleSend(HttpClientRequest)}, as this allows more advanced + * samplers to be used. + */ + @Deprecated public Span handleSend(Injector injector, Req request) { + return handleSend(injector, request, request); + } + + /** + * @since 4.3 + * @deprecated Since 5.7, use {@link #handleSend(HttpClientRequest)}. + */ + @Deprecated public Span handleSend(Injector injector, C carrier, Req request) { + return handleSend(injector, carrier, request, nextSpan(request)); + } + + /** + * @since 4.4 + * @deprecated Since 5.7, use {@link #handleSend(HttpClientRequest, Span)}. + */ + @Deprecated public Span handleSend(Injector injector, Req request, Span span) { + return handleSend(injector, request, request, span); + } + + /** + * @since 4.4 + * @deprecated Since 5.7, use {@link #handleSend(HttpClientRequest)}. + */ + @Deprecated public Span handleSend(Injector injector, C carrier, Req request, Span span) { + if (request == null) throw new NullPointerException("carrier == null"); + if (span == null) throw new NullPointerException("span == null"); + + injector.inject(span.context(), carrier); + HttpClientRequest clientRequest; + if (request instanceof HttpClientRequest) { + clientRequest = (HttpClientRequest) request; + } else { + clientRequest = new HttpClientAdapters.FromRequestAdapter(adapter, request); + } + + return handleStart(clientRequest, span); + } + + /** + * @since 4.4 + * @deprecated since 5.8 use {@link Tracer#nextSpan(SamplerFunction, Object)} + */ + @Deprecated public Span nextSpan(Req request) { + // nextSpan can be called independently when interceptors control lifecycle directly. In these + // cases, it is possible to have HttpClientRequest as an argument. + HttpClientRequest clientRequest; + if (request instanceof HttpClientRequest) { + clientRequest = (HttpClientRequest) request; + } else { + clientRequest = new FromRequestAdapter(adapter, request); + } + return tracer.nextSpan(httpSampler, clientRequest); + } + + /** + * @since 4.3 + * @deprecated since 5.12 use {@link #handleReceive(HttpClientResponse, Span)} + */ + @Deprecated + public void handleReceive(@Nullable Resp response, @Nullable Throwable error, Span span) { + if (span == null) throw new NullPointerException("span == null"); + if (response == null && error == null) { + throw new IllegalArgumentException( + "Either the response or error parameters may be null, but not both"); + } + + if (response == null) { + span.error(error).finish(); + return; + } + + HttpClientResponse clientResponse; + if (response instanceof HttpClientResponse) { + clientResponse = (HttpClientResponse) response; + if (clientResponse.error() == null && error != null) { + span.error(error); + } + } else { + clientResponse = new FromResponseAdapter(adapter, response, error); + } + handleFinish(clientResponse, span); + } + /** * Finishes the client span after assigning it tags according to the response or error. * diff --git a/instrumentation/http/src/main/java/brave/http/HttpClientParser.java b/instrumentation/http/src/main/java/brave/http/HttpClientParser.java new file mode 100644 index 0000000000..69deea92eb --- /dev/null +++ b/instrumentation/http/src/main/java/brave/http/HttpClientParser.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.SpanCustomizer; +import brave.internal.Nullable; + +/** @deprecated Since 5.10, use {@link HttpRequestParser} and {@link HttpResponseParser} */ +@Deprecated public class HttpClientParser extends HttpParser { + /** + * Customizes the span based on the request that will be sent to the server. + * + *

{@inheritDoc} + */ + @Override public void request(HttpAdapter adapter, Req req, + SpanCustomizer customizer) { + super.request(adapter, req, customizer); + } + + /** + * Customizes the span based on the response received from the server. + * + *

{@inheritDoc} + */ + @Override public void response(HttpAdapter adapter, @Nullable Resp res, + @Nullable Throwable error, SpanCustomizer customizer) { + super.response(adapter, res, error, customizer); + } +} diff --git a/instrumentation/http/src/main/java/brave/http/HttpClientParserAdapter.java b/instrumentation/http/src/main/java/brave/http/HttpClientParserAdapter.java new file mode 100644 index 0000000000..20e2c7c297 --- /dev/null +++ b/instrumentation/http/src/main/java/brave/http/HttpClientParserAdapter.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.ErrorParser; +import brave.SpanCustomizer; +import brave.propagation.CurrentTraceContext; + +/** Added to allow us to keep compatibility with deprecated {@link HttpTracing#clientParser()} */ +@Deprecated final class HttpClientParserAdapter extends HttpClientParser { + final HttpRequestParser requestParser; + final HttpResponseParser responseParser; + final CurrentTraceContext currentTraceContext; + final ErrorParser errorParser; + + HttpClientParserAdapter( + HttpRequestParser requestParser, + HttpResponseParser responseParser, + CurrentTraceContext currentTraceContext, + ErrorParser errorParser + ) { + this.requestParser = requestParser; + this.responseParser = responseParser; + this.currentTraceContext = currentTraceContext; + this.errorParser = errorParser; + } + + @Override protected ErrorParser errorParser() { + return errorParser; + } + + @Override + public void request(HttpAdapter adapter, Req req, SpanCustomizer customizer) { + HttpRequest request; + if (req instanceof HttpClientRequest) { + request = new HttpClientAdapters.FromRequestAdapter((HttpClientAdapter) adapter, req); + } else if (adapter instanceof HttpClientAdapters.ToRequestAdapter) { + request = ((HttpClientAdapters.ToRequestAdapter) adapter).delegate; + } else { + throw new AssertionError("programming bug"); + } + requestParser.parse(request, currentTraceContext.get(), customizer); + } + + @Override public void response(HttpAdapter adapter, Resp res, Throwable error, + SpanCustomizer customizer) { + HttpResponse response; + if (res instanceof HttpClientResponse) { + response = + new HttpClientAdapters.FromResponseAdapter((HttpClientAdapter) adapter, res, error); + } else if (adapter instanceof HttpClientAdapters.ToResponseAdapter) { + response = ((HttpClientAdapters.ToResponseAdapter) adapter).delegate; + } else { + throw new AssertionError("programming bug"); + } + responseParser.parse(response, currentTraceContext.get(), customizer); + } +} diff --git a/instrumentation/http/src/main/java/brave/http/HttpHandler.java b/instrumentation/http/src/main/java/brave/http/HttpHandler.java index ebc43c01e2..65fa0a7094 100644 --- a/instrumentation/http/src/main/java/brave/http/HttpHandler.java +++ b/instrumentation/http/src/main/java/brave/http/HttpHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -19,6 +19,12 @@ import static brave.internal.Throwables.propagateIfFatal; abstract class HttpHandler { + /** + * To avoid passing null to signatures that use HttpAdapter, we use a dummy value when {@link + * HttpRequest#unwrap()} or {@link HttpResponse#unwrap()} return null. + */ + static final Object NULL_SENTINEL = new Object(); + final HttpRequestParser requestParser; final HttpResponseParser responseParser; diff --git a/instrumentation/http/src/main/java/brave/http/HttpParser.java b/instrumentation/http/src/main/java/brave/http/HttpParser.java new file mode 100644 index 0000000000..76ee10d295 --- /dev/null +++ b/instrumentation/http/src/main/java/brave/http/HttpParser.java @@ -0,0 +1,153 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.ErrorParser; +import brave.SpanCustomizer; +import brave.Tag; +import brave.internal.Nullable; + +import static brave.http.HttpResponseParser.Default.catchAllName; + +/** @deprecated Since 5.10, use {@link HttpRequestParser} and {@link HttpResponseParser} */ +@Deprecated public class HttpParser { + static final ErrorParser ERROR_PARSER = new ErrorParser(); + + /** + * @deprecated This is only used in Zipkin reporting. Since 5.12, use {@link + * zipkin2.reporter.brave.ZipkinSpanHandler.Builder#errorTag(Tag)} + */ + protected ErrorParser errorParser() { + return ERROR_PARSER; + } + + /** + * Override to change what data from the http request are parsed into the span representing it. By + * default, this sets the span name to the http method and tags "http.method" and "http.path". + * + *

If you only want to change the span name, you can override {@link #spanName(HttpAdapter, + * Object)} instead. + * + * @see #spanName(HttpAdapter, Object) + */ + // Eventhough the default span name is the method, we have no way of knowing that a user hasn't + // overwritten the name to something else. If that occurs during response parsing, it is too late + // to go back and get the http method. Adding http method by default ensures span naming doesn't + // prevent basic HTTP info from being visible. A cost of this is another tag, but it is small with + // very limited cardinality. Moreover, users who care strictly about size can override this. + public void request(HttpAdapter adapter, Req req, SpanCustomizer customizer) { + String name = spanName(adapter, req); + if (name != null) customizer.name(name); + String method = adapter.method(req); + if (method != null) customizer.tag("http.method", method); + String path = adapter.path(req); + if (path != null) customizer.tag("http.path", path); + } + + /** + * Returns the span name of the request or null if the data needed is unavailable. Defaults to the + * http method. + */ + @Nullable protected String spanName(HttpAdapter adapter, Req req) { + return adapter.method(req); + } + + /** + * Override to change what data from the http response or error are parsed into the span modeling + * it. + * + *

By default, this tags "http.status_code" when it is not 2xx. If there's an exception or the + * status code is neither 2xx nor 3xx, it tags "error". This also overrides the span name based on + * the {@link HttpAdapter#methodFromResponse(Object)} and {@link HttpAdapter#route(Object)} where + * possible (ex "get /users/:userId"). + * + *

If routing is supported, and a GET didn't match due to 404, the span name will be + * "get not_found". If it didn't match due to redirect, the span name will be "get redirected". If + * routing is not supported, the span name is left alone. + * + *

If you only want to change how exceptions are parsed, override {@link #error(Integer, + * Throwable, SpanCustomizer)} instead. + * + *

Note: Either the response or error parameters may be null, but not both. + * + * @see #error(Integer, Throwable, SpanCustomizer) + */ + // This accepts response or exception because sometimes http 500 is an exception and sometimes not + // If this were not an abstraction, we'd use separate hooks for response and error. + public void response(HttpAdapter adapter, @Nullable Resp res, + @Nullable Throwable error, SpanCustomizer customizer) { + int statusCode = 0; + if (res != null) { + statusCode = adapter.statusCodeAsInt(res); + String nameFromRoute = spanNameFromRoute(adapter, res, statusCode); + if (nameFromRoute != null) customizer.name(nameFromRoute); + String maybeStatus = maybeStatusAsString(statusCode, 299); + if (maybeStatus != null) customizer.tag("http.status_code", maybeStatus); + } + error(statusCode, error, customizer); + } + + /** The intent of this is to by default add "http.status_code", when not a success code */ + @Nullable String maybeStatusAsString(int statusCode, int upperRange) { + if (statusCode != 0 && (statusCode < 200 || statusCode > upperRange)) { + return String.valueOf(statusCode); + } + return null; + } + + static String spanNameFromRoute(HttpAdapter adapter, Resp res, int statusCode) { + String method = adapter.methodFromResponse(res); + if (method == null) return null; // don't undo a valid name elsewhere + String route = adapter.route(res); + if (route == null) return null; // don't undo a valid name elsewhere + if (!"".equals(route)) return method + " " + route; + return catchAllName(method, statusCode); + } + + /** + * Override to change what data from the http error are parsed into the span modeling it. By + * default, this tags "error" as the exception or the status code if it is below 1xx or above + * 3xx. + * + *

Note: Either the httpStatus or error parameters may be null, but not both + * + *

Conventionally associated with the tag key "error" + */ + // BRAVE6: httpStatus is a Integer, not a int. We can't change this api as users expect this to be + // called by default. Unfortunately, this implies boxing until we can change it. + protected void error(@Nullable Integer httpStatus, @Nullable Throwable error, + SpanCustomizer customizer) { + if (error != null) { + errorParser().error(error, customizer); + return; + } + if (httpStatus == null) return; + // Unlike success path tagging, we only want to indicate something as error if it is not in a + // success range. 1xx-3xx are not errors. It is endpoint-specific if client codes like 404 are + // in fact errors. That's why this is overridable. + int httpStatusInt = httpStatus; + + // Instrumentation error should not make span errors. We don't know the difference between a + // library being unable to get the http status and a bad status (0). We don't classify zero as + // error in case instrumentation cannot read the status. This prevents tagging every response as + // error. + if (httpStatusInt == 0) return; + + // 1xx, 2xx, and 3xx codes are not all valid, but the math is good enough vs drift and opinion + // about individual codes in the range. + if (httpStatusInt < 100 || httpStatusInt > 399) { + customizer.tag("error", String.valueOf(httpStatusInt)); + } + } +} diff --git a/instrumentation/http/src/main/java/brave/http/HttpRequestParserAdapters.java b/instrumentation/http/src/main/java/brave/http/HttpRequestParserAdapters.java new file mode 100644 index 0000000000..bede8ff4b9 --- /dev/null +++ b/instrumentation/http/src/main/java/brave/http/HttpRequestParserAdapters.java @@ -0,0 +1,92 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.SpanCustomizer; +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.TraceContext; + +import static brave.http.HttpHandler.NULL_SENTINEL; + +/** @deprecated Intentionally hidden: implemented to support deprecated signatures. */ +@Deprecated final class HttpRequestParserAdapters { + static final class ClientAdapter extends HttpRequestParserAdapter { + ClientAdapter(CurrentTraceContext currentTraceContext, HttpParser parser) { + super(currentTraceContext, parser); + } + + @Override public void parse(HttpRequest request, TraceContext context, SpanCustomizer span) { + HttpAdapter adapter; + Object req; + if (request instanceof HttpClientAdapters.FromRequestAdapter) { // is a HttpClientRequest + HttpClientAdapters.FromRequestAdapter wrapped = + (HttpClientAdapters.FromRequestAdapter) request; + adapter = wrapped.adapter; + req = wrapped.request; + } else if (request instanceof HttpClientRequest) { + req = request.unwrap(); + if (req == null) req = NULL_SENTINEL; // Ensure adapter methods never see null + adapter = new HttpClientAdapters.ToRequestAdapter((HttpClientRequest) request, req); + } else { + throw new AssertionError("programming bug"); + } + parseInScope(context, adapter, req, span); + } + } + + static final class ServerAdapter extends HttpRequestParserAdapter { + ServerAdapter(CurrentTraceContext currentTraceContext, HttpParser parser) { + super(currentTraceContext, parser); + } + + @Override public void parse(HttpRequest request, TraceContext context, SpanCustomizer span) { + HttpAdapter adapter; + Object req; + if (request instanceof HttpServerAdapters.FromRequestAdapter) { // is a HttpServerRequest + HttpServerAdapters.FromRequestAdapter wrapped = + (HttpServerAdapters.FromRequestAdapter) request; + adapter = wrapped.adapter; + req = wrapped.request; + } else if (request instanceof HttpServerRequest) { + req = request.unwrap(); + if (req == null) req = NULL_SENTINEL; // Ensure adapter methods never see null + adapter = new HttpServerAdapters.ToRequestAdapter((HttpServerRequest) request, req); + } else { + throw new AssertionError("programming bug"); + } + parseInScope(context, adapter, req, span); + } + } + + static abstract class HttpRequestParserAdapter implements HttpRequestParser { + final CurrentTraceContext currentTraceContext; + final HttpParser parser; + + HttpRequestParserAdapter(CurrentTraceContext currentTraceContext, HttpParser parser) { + this.currentTraceContext = currentTraceContext; + this.parser = parser; + } + + void parseInScope(TraceContext context, HttpAdapter adapter, Req req, + SpanCustomizer span) { + Scope ws = currentTraceContext.maybeScope(context); + try { + parser.request(adapter, req, span); + } finally { + ws.close(); + } + } + } +} diff --git a/instrumentation/http/src/main/java/brave/http/HttpResponseParser.java b/instrumentation/http/src/main/java/brave/http/HttpResponseParser.java index ad0d77c19d..5e6c73ac03 100644 --- a/instrumentation/http/src/main/java/brave/http/HttpResponseParser.java +++ b/instrumentation/http/src/main/java/brave/http/HttpResponseParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,6 +13,7 @@ */ package brave.http; +import brave.ErrorParser; import brave.Span; import brave.SpanCustomizer; import brave.Tags; diff --git a/instrumentation/http/src/main/java/brave/http/HttpResponseParserAdapters.java b/instrumentation/http/src/main/java/brave/http/HttpResponseParserAdapters.java new file mode 100644 index 0000000000..1f50bb59fb --- /dev/null +++ b/instrumentation/http/src/main/java/brave/http/HttpResponseParserAdapters.java @@ -0,0 +1,93 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.SpanCustomizer; +import brave.internal.Nullable; +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.propagation.TraceContext; + +import static brave.http.HttpHandler.NULL_SENTINEL; + +/** @deprecated Intentionally hidden: implemented to support deprecated signatures. */ +@Deprecated final class HttpResponseParserAdapters { + static final class ClientAdapter extends HttpResponseParserAdapter { + ClientAdapter(CurrentTraceContext currentTraceContext, HttpParser parser) { + super(currentTraceContext, parser); + } + + @Override public void parse(HttpResponse response, TraceContext context, SpanCustomizer span) { + HttpAdapter adapter; + Object res; + if (response instanceof HttpClientAdapters.FromResponseAdapter) { // is a HttpClientResponse + HttpClientAdapters.FromResponseAdapter wrapped = + (HttpClientAdapters.FromResponseAdapter) response; + adapter = wrapped.adapter; + res = wrapped.response; + } else if (response instanceof HttpClientResponse) { + res = response.unwrap(); + if (res == null) res = NULL_SENTINEL; // Ensure adapter methods never see null + adapter = new HttpClientAdapters.ToResponseAdapter((HttpClientResponse) response, res); + } else { + throw new AssertionError("programming bug"); + } + parseInScope(context, adapter, res, response.error(), span); + } + } + + static final class ServerAdapter extends HttpResponseParserAdapter { + ServerAdapter(CurrentTraceContext currentTraceContext, HttpParser parser) { + super(currentTraceContext, parser); + } + + @Override public void parse(HttpResponse response, TraceContext context, SpanCustomizer span) { + HttpAdapter adapter; + Object res; + if (response instanceof HttpServerAdapters.FromResponseAdapter) { // is a HttpServerResponse + HttpServerAdapters.FromResponseAdapter wrapped = + (HttpServerAdapters.FromResponseAdapter) response; + adapter = wrapped.adapter; + res = wrapped.response; + } else if (response instanceof HttpServerResponse) { + res = response.unwrap(); + if (res == null) res = NULL_SENTINEL; // Ensure adapter methods never see null + adapter = new HttpServerAdapters.ToResponseAdapter((HttpServerResponse) response, res); + } else { + throw new AssertionError("programming bug"); + } + parseInScope(context, adapter, res, response.error(), span); + } + } + + static abstract class HttpResponseParserAdapter implements HttpResponseParser { + final CurrentTraceContext currentTraceContext; + final HttpParser parser; + + HttpResponseParserAdapter(CurrentTraceContext currentTraceContext, HttpParser parser) { + this.currentTraceContext = currentTraceContext; + this.parser = parser; + } + + void parseInScope(TraceContext context, HttpAdapter adapter, @Nullable Resp res, + @Nullable Throwable error, SpanCustomizer customizer) { + Scope ws = currentTraceContext.maybeScope(context); + try { + parser.response(adapter, res, error, customizer); + } finally { + ws.close(); + } + } + } +} diff --git a/instrumentation/http/src/main/java/brave/http/HttpRuleSampler.java b/instrumentation/http/src/main/java/brave/http/HttpRuleSampler.java index 40f70693bf..3d54892774 100644 --- a/instrumentation/http/src/main/java/brave/http/HttpRuleSampler.java +++ b/instrumentation/http/src/main/java/brave/http/HttpRuleSampler.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,11 +14,18 @@ package brave.http; import brave.Tracing; +import brave.internal.Nullable; +import brave.sampler.CountingSampler; import brave.sampler.Matcher; import brave.sampler.ParameterizedSampler; +import brave.sampler.RateLimitingSampler; import brave.sampler.Sampler; import brave.sampler.SamplerFunction; +import static brave.http.HttpRequestMatchers.methodEquals; +import static brave.http.HttpRequestMatchers.pathStartsWith; +import static brave.sampler.Matchers.and; + /** * Assigns sample rates to http routes. * @@ -56,7 +63,7 @@ * * @since 4.4 */ -public final class HttpRuleSampler implements SamplerFunction { +public final class HttpRuleSampler extends HttpSampler implements SamplerFunction { /** @since 4.4 */ public static Builder newBuilder() { return new Builder(); @@ -66,6 +73,21 @@ public static Builder newBuilder() { public static final class Builder { final ParameterizedSampler.Builder delegate = ParameterizedSampler.newBuilder(); + /** + * @since 4.4 + * @deprecated Since 5.8, use {@link #putRule(Matcher, Sampler)} + */ + @Deprecated public Builder addRule(@Nullable String method, String path, float probability) { + if (path == null) throw new NullPointerException("path == null"); + Sampler sampler = CountingSampler.create(probability); + if (method == null) { + delegate.putRule(pathStartsWith(path), RateLimitingSampler.create(10)); + return this; + } + delegate.putRule(and(methodEquals(method), pathStartsWith(path)), sampler); + return this; + } + /** * Adds or replaces all rules in this sampler with those of the input. * @@ -111,4 +133,9 @@ public HttpRuleSampler build() { @Override public Boolean trySample(HttpRequest request) { return delegate.trySample(request); } + + @Override @Deprecated public Boolean trySample(HttpAdapter adapter, Req request) { + if (request == null) return null; + return trySample(new FromHttpAdapter(adapter, request)); + } } diff --git a/instrumentation/http/src/main/java/brave/http/HttpSampler.java b/instrumentation/http/src/main/java/brave/http/HttpSampler.java new file mode 100644 index 0000000000..ed04d36ee0 --- /dev/null +++ b/instrumentation/http/src/main/java/brave/http/HttpSampler.java @@ -0,0 +1,181 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.Span; +import brave.internal.Nullable; +import brave.sampler.SamplerFunction; +import brave.sampler.SamplerFunctions; + +import static brave.http.HttpHandler.NULL_SENTINEL; + +/** + * Decides whether to start a new trace based on http request properties such as path. + * + *

Ex. Here's a sampler that only traces api requests + *

{@code
+ * httpTracingBuilder.serverSampler(new HttpSampler() {
+ *   @Override public  Boolean trySample(HttpAdapter adapter, Req request) {
+ *     return adapter.path(request).startsWith("/api");
+ *   }
+ * });
+ * }
+ * + * @see HttpRuleSampler + * @see SamplerFunction + * @deprecated Since 5.8, use {@code SamplerFunction}. + */ +@Deprecated +// abstract class as you can't lambda generic methods anyway. This lets us make helpers in the future +public abstract class HttpSampler implements SamplerFunction { + /** Ignores the request and uses the {@link brave.sampler.Sampler trace ID instead}. */ + public static final HttpSampler TRACE_ID = new HttpSampler() { + @Override public Boolean trySample(HttpRequest request) { + return null; + } + + @Override @Nullable public Boolean trySample(HttpAdapter adapter, Req request) { + return null; + } + + @Override public String toString() { + return "DeferDecision"; + } + }; + + /** + * Returns false to never start new traces for http requests. For example, you may wish to only + * capture traces if they originated from an inbound server request. Such a policy would filter + * out client requests made during bootstrap. + */ + public static final HttpSampler NEVER_SAMPLE = new HttpSampler() { + @Override public Boolean trySample(HttpRequest request) { + return false; + } + + @Override public Boolean trySample(HttpAdapter adapter, Req request) { + return false; + } + + @Override public String toString() { + return "NeverSample"; + } + }; + + @Override @Nullable public Boolean trySample(HttpRequest request) { + if (request == null) return null; + + Object unwrapped = request.unwrap(); + if (unwrapped == null) unwrapped = NULL_SENTINEL; // Ensure adapter methods never see null + + HttpAdapter adapter; + if (request instanceof HttpClientRequest) { + adapter = new HttpClientAdapters.ToRequestAdapter((HttpClientRequest) request, unwrapped); + } else { + adapter = new HttpServerAdapters.ToRequestAdapter((HttpServerRequest) request, unwrapped); + } + + return trySample(adapter, unwrapped); + } + + /** + * Returns an overriding sampling decision for a new trace. Return null ignore the request and use + * the {@link brave.sampler.Sampler trace ID sampler}. + */ + @Nullable public abstract Boolean trySample(HttpAdapter adapter, Req request); + + static HttpSampler fromHttpRequestSampler(SamplerFunction sampler) { + if (sampler == null) throw new NullPointerException("sampler == null"); + if (sampler.equals(SamplerFunctions.deferDecision())) return HttpSampler.TRACE_ID; + if (sampler.equals(SamplerFunctions.neverSample())) return HttpSampler.NEVER_SAMPLE; + return sampler instanceof HttpSampler ? (HttpSampler) sampler + : new HttpRequestSamplerAdapter(sampler); + } + + static SamplerFunction toHttpRequestSampler(SamplerFunction sampler) { + if (sampler == null) throw new NullPointerException("sampler == null"); + if (sampler == HttpSampler.TRACE_ID) return SamplerFunctions.deferDecision(); + if (sampler == HttpSampler.NEVER_SAMPLE) return SamplerFunctions.neverSample(); + return sampler; + } + + static final class HttpRequestSamplerAdapter extends HttpSampler { + final SamplerFunction delegate; + + HttpRequestSamplerAdapter(SamplerFunction delegate) { + this.delegate = delegate; + } + + @Override public Boolean trySample(HttpRequest request) { + return delegate.trySample(request); + } + + @Override public Boolean trySample(HttpAdapter adapter, Req request) { + if (adapter == null) throw new NullPointerException("adapter == null"); + if (request == null) return null; + // This can be called independently when interceptors control lifecycle directly. In these + // cases, it is possible to have an HttpRequest as an argument. + if (request instanceof HttpRequest) { + return delegate.trySample((HttpRequest) request); + } + return delegate.trySample(new FromHttpAdapter(adapter, request)); + } + + @Override public String toString() { + return delegate.toString(); + } + } + + @Deprecated static final class FromHttpAdapter extends HttpRequest { + final HttpAdapter adapter; + final Req request; + final Span.Kind kind; + + FromHttpAdapter(HttpAdapter adapter, Req request) { + if (adapter == null) throw new NullPointerException("adapter == null"); + this.adapter = adapter; + this.kind = adapter instanceof HttpServerAdapter ? Span.Kind.SERVER : Span.Kind.CLIENT; + if (request == null) throw new NullPointerException("request == null"); + this.request = request; + } + + @Override public Span.Kind spanKind() { + return kind; + } + + @Override public Object unwrap() { + return request; + } + + @Override public String method() { + return adapter.method(request); + } + + @Override public String path() { + return adapter.path(request); + } + + @Override public String url() { + return adapter.url(request); + } + + @Override public String header(String name) { + return adapter.requestHeader(request, name); + } + + @Override public String toString() { + return request.toString(); + } + } +} diff --git a/instrumentation/http/src/main/java/brave/http/HttpServerAdapter.java b/instrumentation/http/src/main/java/brave/http/HttpServerAdapter.java new file mode 100644 index 0000000000..cad59e2217 --- /dev/null +++ b/instrumentation/http/src/main/java/brave/http/HttpServerAdapter.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.Span; + +/** @deprecated Since 5.10, use {@link HttpServerRequest} and {@link HttpServerResponse} */ +@Deprecated public abstract class HttpServerAdapter extends HttpAdapter { + /** + * @deprecated {@link #parseClientIpAndPort} addresses this functionality. This will be removed in + * Brave v6. + */ + @Deprecated public boolean parseClientAddress(Req req, zipkin2.Endpoint.Builder builder) { + return false; + } + + /** @see HttpServerRequest#parseClientIpAndPort(Span) */ + public boolean parseClientIpAndPort(Req req, Span span) { + return parseClientIpFromXForwardedFor(req, span); + } + + /** @see HttpServerRequest#parseClientIpFromXForwardedFor(Span) */ + public boolean parseClientIpFromXForwardedFor(Req req, Span span) { + String forwardedFor = requestHeader(req, "X-Forwarded-For"); + if (forwardedFor == null) return false; + int indexOfComma = forwardedFor.indexOf(','); + if (indexOfComma != -1) forwardedFor = forwardedFor.substring(0, indexOfComma); + return span.remoteIpAndPort(forwardedFor, 0); + } +} diff --git a/instrumentation/http/src/main/java/brave/http/HttpServerAdapters.java b/instrumentation/http/src/main/java/brave/http/HttpServerAdapters.java new file mode 100644 index 0000000000..284b8c238c --- /dev/null +++ b/instrumentation/http/src/main/java/brave/http/HttpServerAdapters.java @@ -0,0 +1,245 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.Span; +import brave.internal.Nullable; + +/** @deprecated Intentionally hidden: implemented to support deprecated signatures. */ +@Deprecated final class HttpServerAdapters { + + // Void type used to force generics to fail handling the wrong side. + @Deprecated static final class ToRequestAdapter extends HttpServerAdapter { + final HttpServerRequest delegate; + final Object unwrapped; + + ToRequestAdapter(HttpServerRequest delegate, Object unwrapped) { + if (delegate == null) throw new NullPointerException("delegate == null"); + if (unwrapped == null) throw new NullPointerException("unwrapped == null"); + this.delegate = delegate; + this.unwrapped = unwrapped; + } + + @Override public final boolean parseClientIpAndPort(Object req, Span span) { + if (req == unwrapped) { + if (parseClientIpFromXForwardedFor(req, span)) return true; + return delegate.parseClientIpAndPort(span); + } + return false; + } + + @Override public final long startTimestamp(Object request) { + if (request == unwrapped) return delegate.startTimestamp(); + return 0L; + } + + @Override public final String method(Object request) { + if (request == unwrapped) return delegate.method(); + return null; + } + + @Override public final String url(Object request) { + if (request == unwrapped) return delegate.url(); + return null; + } + + @Override public final String requestHeader(Object request, String name) { + if (request == unwrapped) return delegate.header(name); + return null; + } + + @Override public final String path(Object request) { + if (request == unwrapped) return delegate.path(); + return null; + } + + @Override public final String toString() { + return delegate.toString(); + } + + // Skip response adapter methods + + @Override public final String methodFromResponse(Void response) { + return null; + } + + @Override public final String route(Void response) { + return null; + } + + @Override public final int statusCodeAsInt(Void response) { + return 0; + } + + @Override @Nullable public final Integer statusCode(Void response) { + return null; + } + + @Override public final long finishTimestamp(Void response) { + return 0L; + } + } + + @Deprecated static final class FromRequestAdapter extends HttpServerRequest { + final HttpServerAdapter adapter; + final Req request; + + FromRequestAdapter(HttpServerAdapter adapter, Req request) { + if (adapter == null) throw new NullPointerException("adapter == null"); + this.adapter = adapter; + if (request == null) throw new NullPointerException("request == null"); + this.request = request; + } + + @Override public Object unwrap() { + return request; + } + + @Override public long startTimestamp() { + return adapter.startTimestamp(request); + } + + @Override public String method() { + return adapter.method(request); + } + + @Override public String path() { + return adapter.path(request); + } + + @Override public String url() { + return adapter.url(request); + } + + @Override public String header(String name) { + return adapter.requestHeader(request, name); + } + + @Override public boolean parseClientIpAndPort(Span span) { + return adapter.parseClientIpAndPort(request, span); + } + + @Override public final String toString() { + return request.toString(); + } + } + + // Void type used to force generics to fail handling the wrong side + @Deprecated static final class ToResponseAdapter extends HttpServerAdapter { + final HttpServerResponse delegate; + final Object unwrapped; + + ToResponseAdapter(HttpServerResponse delegate, Object unwrapped) { + if (delegate == null) throw new NullPointerException("delegate == null"); + if (unwrapped == null) throw new NullPointerException("unwrapped == null"); + this.delegate = delegate; + this.unwrapped = unwrapped; + } + + // Skip request adapter methods + @Override public final String method(Void request) { + return null; + } + + @Override public final String path(Void request) { + return null; + } + + @Override public final String url(Void request) { + return null; + } + + @Override public final String requestHeader(Void request, String name) { + return null; + } + + @Override public final long startTimestamp(Void request) { + return 0L; + } + + // Begin response adapter methods + + @Override public final String methodFromResponse(Object response) { + if (response == unwrapped) return delegate.method(); + return null; + } + + @Override public final String route(Object response) { + if (response == unwrapped) return delegate.route(); + return null; + } + + @Override @Nullable public final Integer statusCode(Object response) { + int result = statusCodeAsInt(response); + return result == 0 ? null : result; + } + + @Override public final int statusCodeAsInt(Object response) { + if (response == unwrapped) return delegate.statusCode(); + return 0; + } + + @Override public final long finishTimestamp(Object response) { + if (response == unwrapped) return delegate.finishTimestamp(); + return 0L; + } + + @Override public String toString() { + return delegate.toString(); + } + } + + @Deprecated static final class FromResponseAdapter extends HttpServerResponse { + final HttpServerAdapter adapter; + final Res response; + @Nullable final Throwable error; + + FromResponseAdapter(HttpServerAdapter adapter, Res response, + @Nullable Throwable error) { + if (adapter == null) throw new NullPointerException("adapter == null"); + if (response == null) throw new NullPointerException("response == null"); + this.adapter = adapter; + this.response = response; + this.error = error; + } + + @Override public Object unwrap() { + return response; + } + + @Override public Throwable error() { + return error; + } + + @Override public String method() { + return adapter.methodFromResponse(response); + } + + @Override public String route() { + return adapter.route(response); + } + + @Override public int statusCode() { + return adapter.statusCodeAsInt(response); + } + + @Override public long finishTimestamp() { + return adapter.finishTimestamp(response); + } + + @Override public String toString() { + return response.toString(); + } + } +} diff --git a/instrumentation/http/src/main/java/brave/http/HttpServerHandler.java b/instrumentation/http/src/main/java/brave/http/HttpServerHandler.java index 68a14ecf76..99ba5c2aac 100644 --- a/instrumentation/http/src/main/java/brave/http/HttpServerHandler.java +++ b/instrumentation/http/src/main/java/brave/http/HttpServerHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -17,6 +17,8 @@ import brave.SpanCustomizer; import brave.Tracer; import brave.Tracer.SpanInScope; +import brave.http.HttpServerAdapters.FromResponseAdapter; +import brave.internal.Nullable; import brave.propagation.TraceContext; import brave.propagation.TraceContext.Extractor; import brave.propagation.TraceContextOrSamplingFlags; @@ -41,7 +43,7 @@ * Span span = handler.handleReceive(requestWrapper); // 1. * HttpServerResponse response = null; * Throwable error = null; - * try (Scope scope = currentTraceContext.newScope(span.context())) { // 2. + * try (Scope ws = currentTraceContext.newScope(span.context())) { // 2. * return response = process(request); // 3. * } catch (Throwable e) { * error = e; // 4. @@ -65,16 +67,29 @@ public final class HttpServerHandler extends HttpHandler { public static HttpServerHandler create( HttpTracing httpTracing) { if (httpTracing == null) throw new NullPointerException("httpTracing == null"); - return new HttpServerHandler(httpTracing); + return new HttpServerHandler(httpTracing, null); } + /** + * @since 4.3 + * @deprecated Since 5.7, use {@link #create(HttpTracing)} as it is more portable. + */ + @Deprecated + public static HttpServerHandler create(HttpTracing httpTracing, + HttpServerAdapter adapter) { + if (httpTracing == null) throw new NullPointerException("httpTracing == null"); + if (adapter == null) throw new NullPointerException("adapter == null"); + return new HttpServerHandler(httpTracing, adapter); + } final Tracer tracer; final SamplerFunction sampler; + @Deprecated @Nullable final HttpServerAdapter adapter; // null when using default types final Extractor defaultExtractor; - HttpServerHandler(HttpTracing httpTracing) { + HttpServerHandler(HttpTracing httpTracing, @Deprecated HttpServerAdapter adapter) { super(httpTracing.serverRequestParser(), httpTracing.serverResponseParser()); + this.adapter = adapter; this.tracer = httpTracing.tracing().tracer(); this.sampler = httpTracing.serverRequestSampler(); // The following allows us to add the method: handleReceive(HttpServerRequest request) without @@ -97,6 +112,32 @@ public Span handleReceive(HttpServerRequest request) { return handleStart(request, span); } + /** + * @since 4.3 + * @deprecated Since 5.7, use {@link #handleReceive(HttpServerRequest)} + */ + @Deprecated public Span handleReceive(Extractor extractor, Req request) { + return handleReceive(extractor, request, request); + } + + /** + * @since 4.3 + * @deprecated Since 5.7, use {@link #handleReceive(HttpServerRequest)} + */ + @Deprecated public Span handleReceive(Extractor extractor, C carrier, Req request) { + if (carrier == null) throw new NullPointerException("request == null"); + + HttpServerRequest serverRequest; + if (request instanceof HttpServerRequest) { + serverRequest = (HttpServerRequest) request; + } else { + serverRequest = new HttpServerAdapters.FromRequestAdapter(adapter, request); + } + + Span span = nextSpan(extractor.extract(carrier), serverRequest); + return handleStart(serverRequest, span); + } + @Override void parseRequest(HttpRequest request, Span span) { ((HttpServerRequest) request).parseClientIpAndPort(span); super.parseRequest(request, span); @@ -114,6 +155,34 @@ Span nextSpan(TraceContextOrSamplingFlags extracted, HttpServerRequest request) : tracer.nextSpan(extracted); } + /** + * @since 4.3 + * @deprecated Since 5.12, use {@link #handleSend(HttpServerResponse, Span)} + */ + public void handleSend(@Nullable Resp response, @Nullable Throwable error, Span span) { + if (span == null) throw new NullPointerException("span == null"); + if (response == null && error == null) { + throw new IllegalArgumentException( + "Either the response or error parameters may be null, but not both"); + } + + if (response == null) { + span.error(error).finish(); + return; + } + + HttpServerResponse serverResponse; + if (response instanceof HttpServerResponse) { + serverResponse = (HttpServerResponse) response; + if (serverResponse.error() == null && error != null) { + span.error(error); + } + } else { + serverResponse = new FromResponseAdapter(adapter, response, error); + } + handleFinish(serverResponse, span); + } + /** * Finishes the server span after assigning it tags according to the response or error. * diff --git a/instrumentation/http/src/main/java/brave/http/HttpServerParser.java b/instrumentation/http/src/main/java/brave/http/HttpServerParser.java new file mode 100644 index 0000000000..488ba247d0 --- /dev/null +++ b/instrumentation/http/src/main/java/brave/http/HttpServerParser.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.SpanCustomizer; +import brave.internal.Nullable; + +/** @deprecated Since 5.10, use {@link HttpRequestParser} and {@link HttpResponseParser} */ +@Deprecated public class HttpServerParser extends HttpParser { + + /** + * Customizes the span based on the request received from the client. + * + *

{@inheritDoc} + */ + @Override public void request(HttpAdapter adapter, Req req, + SpanCustomizer customizer) { + super.request(adapter, req, customizer); + } + + /** + * Customizes the span based on the response sent to the client. + * + *

{@inheritDoc} + */ + @Override public void response(HttpAdapter adapter, @Nullable Resp res, + @Nullable Throwable error, SpanCustomizer customizer) { + super.response(adapter, res, error, customizer); + } +} diff --git a/instrumentation/http/src/main/java/brave/http/HttpServerParserAdapter.java b/instrumentation/http/src/main/java/brave/http/HttpServerParserAdapter.java new file mode 100644 index 0000000000..534dd65cfc --- /dev/null +++ b/instrumentation/http/src/main/java/brave/http/HttpServerParserAdapter.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.ErrorParser; +import brave.SpanCustomizer; +import brave.propagation.CurrentTraceContext; + +/** Added to allow us to keep compatibility with deprecated {@link HttpTracing#serverParser()} */ +@Deprecated final class HttpServerParserAdapter extends HttpServerParser { + final HttpRequestParser requestParser; + final HttpResponseParser responseParser; + final CurrentTraceContext currentTraceContext; + final ErrorParser errorParser; + + HttpServerParserAdapter( + HttpRequestParser requestParser, + HttpResponseParser responseParser, + CurrentTraceContext currentTraceContext, + ErrorParser errorParser + ) { + this.requestParser = requestParser; + this.responseParser = responseParser; + this.currentTraceContext = currentTraceContext; + this.errorParser = errorParser; + } + + @Override protected ErrorParser errorParser() { + return errorParser; + } + + @Override + public void request(HttpAdapter adapter, Req req, SpanCustomizer customizer) { + HttpRequest request; + if (req instanceof HttpServerRequest) { + request = new HttpServerAdapters.FromRequestAdapter((HttpServerAdapter) adapter, req); + } else if (adapter instanceof HttpServerAdapters.ToRequestAdapter) { + request = ((HttpServerAdapters.ToRequestAdapter) adapter).delegate; + } else { + throw new AssertionError("programming bug"); + } + requestParser.parse(request, currentTraceContext.get(), customizer); + } + + @Override public void response(HttpAdapter adapter, Resp res, Throwable error, + SpanCustomizer customizer) { + HttpResponse response; + if (res instanceof HttpServerResponse) { + response = + new HttpServerAdapters.FromResponseAdapter((HttpServerAdapter) adapter, res, error); + } else if (adapter instanceof HttpServerAdapters.ToResponseAdapter) { + response = ((HttpServerAdapters.ToResponseAdapter) adapter).delegate; + } else { + throw new AssertionError("programming bug"); + } + responseParser.parse(response, currentTraceContext.get(), customizer); + } +} diff --git a/instrumentation/http/src/main/java/brave/http/HttpTracing.java b/instrumentation/http/src/main/java/brave/http/HttpTracing.java index b51c5f2fec..2b7ccd9d47 100644 --- a/instrumentation/http/src/main/java/brave/http/HttpTracing.java +++ b/instrumentation/http/src/main/java/brave/http/HttpTracing.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -24,6 +24,9 @@ import java.io.Closeable; import java.util.concurrent.atomic.AtomicReference; +import static brave.http.HttpSampler.fromHttpRequestSampler; +import static brave.http.HttpSampler.toHttpRequestSampler; + /** * Instances built via {@link #create(Tracing)} or {@link #newBuilder(Tracing)} are registered * automatically such that statically configured instrumentation like HTTP clients can use {@link @@ -65,6 +68,21 @@ public HttpResponseParser clientResponseParser() { return clientResponseParser; } + /** + * @deprecated Since 5.10, use {@link #clientRequestParser()} and {@link #clientResponseParser()} + */ + @Deprecated public HttpClientParser clientParser() { + if (clientRequestParser instanceof HttpRequestParserAdapters.ClientAdapter) { + return (HttpClientParser) ((HttpRequestParserAdapters.ClientAdapter) clientRequestParser).parser; + } + return new HttpClientParserAdapter( + clientRequestParser, + clientResponseParser, + tracing.currentTraceContext(), + tracing.errorParser() + ); + } + /** * Used by http clients to indicate the name of the destination service. *

@@ -108,7 +126,7 @@ public HttpRequestParser serverRequestParser() { } /** - * Used by {@link HttpServerHandler#handleSend} to add tags about the + * Used by {@link HttpServerHandler#handleSend(Object, Throwable, Span)} to add tags about the * response sent to the client. * * @since 5.10 @@ -117,6 +135,26 @@ public HttpResponseParser serverResponseParser() { return serverResponseParser; } + /** + * @deprecated Since 5.10, use {@link #serverRequestParser()} and {@link #serverResponseParser()} + */ + @Deprecated public HttpServerParser serverParser() { + if (serverRequestParser instanceof HttpRequestParserAdapters.ServerAdapter) { + return (HttpServerParser) ((HttpRequestParserAdapters.ServerAdapter) serverRequestParser).parser; + } + return new HttpServerParserAdapter( + serverRequestParser, + serverResponseParser, + tracing.currentTraceContext(), + tracing.errorParser() + ); + } + + /** @deprecated Since 5.8, use {@link #clientRequestSampler()} */ + @Deprecated public HttpSampler clientSampler() { + return fromHttpRequestSampler(clientSampler); + } + /** * Returns an overriding sampling decision for a new trace. Defaults to ignore the request and use * the {@link SamplerFunctions#deferDecision() trace ID instead}. @@ -132,6 +170,11 @@ public SamplerFunction clientRequestSampler() { return clientSampler; } + /** @deprecated Since 5.8, use {@link #serverRequestSampler()} */ + @Deprecated public HttpSampler serverSampler() { + return fromHttpRequestSampler(serverSampler); + } + /** * Returns an overriding sampling decision for a new trace. Defaults to ignore the request and use * the {@link SamplerFunctions#deferDecision() trace ID instead}. @@ -266,6 +309,20 @@ public Builder clientResponseParser(HttpResponseParser clientResponseParser) { return this; } + /** + * @deprecated Since 5.10, use {@link #clientRequestParser(HttpRequestParser)} and {@link + * #clientResponseParser(HttpResponseParser)} + */ + @Deprecated public Builder clientParser(HttpClientParser clientParser) { + if (clientParser == null) throw new NullPointerException("clientParser == null"); + this.clientRequestParser = + new HttpRequestParserAdapters.ClientAdapter(tracing.currentTraceContext(), clientParser); + this.clientResponseParser = + new HttpResponseParserAdapters.ClientAdapter(tracing.currentTraceContext(), clientParser); + this.tracing.errorParser(); + return this; + } + Builder serverName(String serverName) { if (serverName == null) throw new NullPointerException("serverName == null"); this.serverName = serverName; @@ -300,6 +357,25 @@ public Builder serverResponseParser(HttpResponseParser serverResponseParser) { return this; } + /** + * @deprecated Since 5.10, use {@link #serverRequestParser(HttpRequestParser)} and {@link + * #serverResponseParser(HttpResponseParser)} + */ + @Deprecated public Builder serverParser(HttpServerParser serverParser) { + if (serverParser == null) throw new NullPointerException("serverParser == null"); + this.serverRequestParser = + new HttpRequestParserAdapters.ServerAdapter(tracing.currentTraceContext(), serverParser); + this.serverResponseParser = + new HttpResponseParserAdapters.ServerAdapter(tracing.currentTraceContext(), serverParser); + return this; + } + + /** @deprecated Since 5.8, use {@link #clientSampler(SamplerFunction)} */ + public Builder clientSampler(HttpSampler clientSampler) { + if (clientSampler == null) throw new NullPointerException("clientSampler == null"); + return clientSampler((SamplerFunction) clientSampler); + } + /** * @see SamplerFunctions * @see HttpTracing#clientRequestSampler() @@ -307,10 +383,15 @@ public Builder serverResponseParser(HttpResponseParser serverResponseParser) { */ public Builder clientSampler(SamplerFunction clientSampler) { if (clientSampler == null) throw new NullPointerException("clientSampler == null"); - this.clientSampler = clientSampler; + this.clientSampler = toHttpRequestSampler(clientSampler); return this; } + /** @deprecated Since 5.8, use {@link #serverSampler(SamplerFunction)} */ + public Builder serverSampler(HttpSampler serverSampler) { + return serverSampler((SamplerFunction) serverSampler); + } + /** * @see SamplerFunctions * @see HttpTracing#serverRequestSampler() @@ -318,7 +399,7 @@ public Builder clientSampler(SamplerFunction clientSampler) { */ public Builder serverSampler(SamplerFunction serverSampler) { if (serverSampler == null) throw new NullPointerException("serverSampler == null"); - this.serverSampler = serverSampler; + this.serverSampler = toHttpRequestSampler(serverSampler); return this; } diff --git a/instrumentation/http/src/test/java/brave/http/DeprecatedHttpClientHandlerTest.java b/instrumentation/http/src/test/java/brave/http/DeprecatedHttpClientHandlerTest.java new file mode 100644 index 0000000000..d8b3ecf71c --- /dev/null +++ b/instrumentation/http/src/test/java/brave/http/DeprecatedHttpClientHandlerTest.java @@ -0,0 +1,158 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.ScopedSpan; +import brave.Tracing; +import brave.http.HttpClientAdapters.FromRequestAdapter; +import brave.propagation.TraceContext; +import brave.test.IntegrationTestSpanHandler; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +import static brave.Span.Kind.CLIENT; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@Deprecated +class DeprecatedHttpClientHandlerTest { + @RegisterExtension IntegrationTestSpanHandler spanHandler = new IntegrationTestSpanHandler(); + + TraceContext context = TraceContext.newBuilder().traceId(1L).spanId(1L).sampled(true).build(); + @Mock HttpSampler sampler; + HttpTracing httpTracing; + HttpClientHandler handler; + + @Spy HttpClientParser parser = new HttpClientParser(); + @Mock HttpClientAdapter adapter; + @Mock TraceContext.Injector injector; + @Mock Object request; + @Mock Object response; + + @BeforeEach void init() { + init(httpTracingBuilder(tracingBuilder())); + } + + void init(HttpTracing.Builder builder) { + close(); + httpTracing = builder.build(); + handler = HttpClientHandler.create(httpTracing, adapter); + } + + HttpTracing.Builder httpTracingBuilder(Tracing.Builder tracingBuilder) { + return HttpTracing.newBuilder(tracingBuilder.build()) + .clientSampler(sampler).clientParser(parser); + } + + Tracing.Builder tracingBuilder() { + return Tracing.newBuilder().addSpanHandler(spanHandler); + } + + @AfterEach void close() { + Tracing current = Tracing.current(); + if (current != null) current.close(); + } + + @Test void handleSend_defaultsToMakeNewTrace() { + when(sampler.trySample(any(FromRequestAdapter.class))).thenReturn(null); + + assertThat(handler.handleSend(injector, request)) + .extracting(brave.Span::isNoop, s -> s.context().parentId()) + .containsExactly(false, null); + } + + @Test void handleSend_makesAChild() { + ScopedSpan parent = httpTracing.tracing().tracer().startScopedSpan("test"); + try { + assertThat(handler.handleSend(injector, request)) + .extracting(brave.Span::isNoop, s -> s.context().parentId()) + .containsExactly(false, parent.context().spanId()); + } finally { + parent.finish(); + } + spanHandler.takeLocalSpan(); + } + + @Test void handleSend_makesRequestBasedSamplingDecision() { + // request sampler says false eventhough trace ID sampler would have said true + when(sampler.trySample(any(FromRequestAdapter.class))).thenReturn(false); + init(httpTracingBuilder(tracingBuilder())); + + assertThat(handler.handleSend(injector, request).isNoop()).isTrue(); + } + + @Test void handleSend_injectsTheTraceContext() { + TraceContext context = handler.handleSend(injector, request).context(); + + verify(injector).inject(context, request); + } + + @Test void handleSend_injectsTheTraceContext_onTheRequest() { + HttpClientRequest customRequest = mock(HttpClientRequest.class); + TraceContext context = handler.handleSend(injector, customRequest, request).context(); + + verify(injector).inject(context, customRequest); + } + + @Test void handleSend_addsClientAddressWhenOnlyServiceName() { + when(sampler.trySample(any(FromRequestAdapter.class))).thenReturn(null); + + httpTracing = httpTracing.clientOf("remote-service"); + + HttpClientHandler.create(httpTracing, adapter).handleSend(injector, request).finish(); + + assertThat(spanHandler.takeRemoteSpan(CLIENT).remoteServiceName()) + .isEqualTo("remote-service"); + } + + @Test void handleSend_skipsClientAddressWhenUnparsed() { + when(sampler.trySample(any(FromRequestAdapter.class))).thenReturn(null); + + handler.handleSend(injector, request).finish(); + + assertThat(spanHandler.takeRemoteSpan(CLIENT).remoteServiceName()) + .isNull(); + } + + @Test void handleReceive() { + brave.Span span = mock(brave.Span.class); + when(span.context()).thenReturn(context); + when(span.customizer()).thenReturn(span); + + handler.handleReceive(response, null, span); + + verify(parser).response(eq(adapter), eq(response), isNull(), eq(span)); + } + + @Test void handleReceive_oneOfResponseError() { + brave.Span span = mock(brave.Span.class); + + assertThatThrownBy(() -> handler.handleReceive(null, null, span)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Either the response or error parameters may be null, but not both"); + } +} diff --git a/instrumentation/http/src/test/java/brave/http/DeprecatedHttpServerHandlerTest.java b/instrumentation/http/src/test/java/brave/http/DeprecatedHttpServerHandlerTest.java new file mode 100644 index 0000000000..7be40a8808 --- /dev/null +++ b/instrumentation/http/src/test/java/brave/http/DeprecatedHttpServerHandlerTest.java @@ -0,0 +1,152 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.http.HttpServerAdapters.FromRequestAdapter; +import brave.propagation.SamplingFlags; +import brave.propagation.TraceContext; +import brave.propagation.TraceContextOrSamplingFlags; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) // TODO: hunt down these +@Deprecated public class DeprecatedHttpServerHandlerTest { + TraceContext context = TraceContext.newBuilder().traceId(1L).spanId(1L).sampled(true).build(); + List spans = new ArrayList<>(); + @Mock HttpSampler sampler; + HttpTracing httpTracing; + HttpServerHandler handler; + + @Spy HttpServerParser parser = new HttpServerParser(); + @Mock HttpServerAdapter adapter; + @Mock TraceContext.Extractor extractor; + @Mock Object request; + @Mock Object response; + + @BeforeEach void init() { + httpTracing = HttpTracing.newBuilder(Tracing.newBuilder().spanReporter(spans::add).build()) + .serverSampler(sampler).serverParser(parser).build(); + handler = HttpServerHandler.create(httpTracing, adapter); + + when(adapter.method(request)).thenReturn("GET"); + doCallRealMethod().when(adapter).parseClientIpAndPort(eq(request), isA(Span.class)); + } + + @AfterEach void close() { + Tracing.current().close(); + } + + @Test void handleReceive_defaultsToMakeNewTrace() { + when(extractor.extract(request)) + .thenReturn(TraceContextOrSamplingFlags.create(SamplingFlags.EMPTY)); + + // request sampler abstains (trace ID sampler will say true) + when(sampler.trySample(any(FromRequestAdapter.class))).thenReturn(null); + + Span newSpan = handler.handleReceive(extractor, request); + assertThat(newSpan.isNoop()).isFalse(); + assertThat(newSpan.context().shared()).isFalse(); + } + + @Test void handleReceive_reusesTraceId() { + httpTracing = HttpTracing.newBuilder( + Tracing.newBuilder().supportsJoin(false).spanReporter(spans::add).build()) + .serverSampler(sampler).serverParser(parser).build(); + + Tracer tracer = httpTracing.tracing().tracer(); + handler = HttpServerHandler.create(httpTracing, adapter); + + TraceContext incomingContext = tracer.nextSpan().context(); + when(extractor.extract(request)).thenReturn( + TraceContextOrSamplingFlags.create(incomingContext)); + + assertThat(handler.handleReceive(extractor, request).context()) + .extracting(TraceContext::traceId, TraceContext::parentId, TraceContext::shared) + .containsOnly(incomingContext.traceId(), incomingContext.spanId(), false); + } + + @Test void handleReceive_reusesSpanIds() { + TraceContext incomingContext = httpTracing.tracing().tracer().nextSpan().context(); + when(extractor.extract(request)) + .thenReturn(TraceContextOrSamplingFlags.create(incomingContext)); + + assertThat(handler.handleReceive(extractor, request).context()) + .isEqualTo(incomingContext.toBuilder().shared(true).build()); + } + + @Test void handleReceive_honorsSamplingFlags() { + when(extractor.extract(request)) + .thenReturn(TraceContextOrSamplingFlags.create(SamplingFlags.NOT_SAMPLED)); + + assertThat(handler.handleReceive(extractor, request).isNoop()) + .isTrue(); + } + + @Test void handleReceive_makesRequestBasedSamplingDecision_flags() { + when(extractor.extract(request)) + .thenReturn(TraceContextOrSamplingFlags.create(SamplingFlags.EMPTY)); + + // request sampler says false eventhough trace ID sampler would have said true + when(sampler.trySample(any(FromRequestAdapter.class))).thenReturn(false); + + assertThat(handler.handleReceive(extractor, request).isNoop()) + .isTrue(); + } + + @Test void handleReceive_makesRequestBasedSamplingDecision_context() { + Tracer tracer = httpTracing.tracing().tracer(); + TraceContext incomingContext = tracer.nextSpan().context().toBuilder().sampled(null).build(); + when(extractor.extract(request)) + .thenReturn(TraceContextOrSamplingFlags.create(incomingContext)); + + // request sampler says false eventhough trace ID sampler would have said true + when(sampler.trySample(any(FromRequestAdapter.class))).thenReturn(false); + + assertThat(handler.handleReceive(extractor, request).isNoop()) + .isTrue(); + } + + @Test void handleSend() { + Span span = mock(Span.class); + when(span.context()).thenReturn(context); + when(span.customizer()).thenReturn(span); + + handler.handleSend(response, null, span); + + verify(parser).response(eq(adapter), eq(response), isNull(), eq(span)); + } +} diff --git a/instrumentation/http/src/test/java/brave/http/HttpAdaptersTest.java b/instrumentation/http/src/test/java/brave/http/HttpAdaptersTest.java new file mode 100644 index 0000000000..4795e74733 --- /dev/null +++ b/instrumentation/http/src/test/java/brave/http/HttpAdaptersTest.java @@ -0,0 +1,348 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +// Due to inheritance, a base type between client and server can't be used. However, we can share +// tests across the implementations, to ensure they are duplicated accurately. +@Deprecated abstract class HttpAdaptersTest< + Req extends HttpRequest, ReqAdapter extends HttpAdapter, + Resp extends HttpResponse, RespAdapter extends HttpAdapter> { + + // mocks + final Req request; + final ReqAdapter requestAdapter; + final Resp response; + final RespAdapter responseAdapter; + + // types under test + ReqAdapter toRequestAdapter; + Req fromRequestAdapter; + RespAdapter toResponseAdapter; + Resp fromResponseAdapter; + + HttpAdaptersTest( + Req request, ReqAdapter requestAdapter, + Resp response, RespAdapter responseAdapter) { + this.request = request; + this.requestAdapter = requestAdapter; + this.response = response; + this.responseAdapter = responseAdapter; + } + + @Test void toRequestAdapter_toString() { + assertThat(toRequestAdapter.toString()) + .isEqualTo(request.toString()); + } + + @Test void toRequestAdapter_startTimestamp_zeroOnNoMatch() { + assertThat(toRequestAdapter.startTimestamp(request)).isZero(); + } + + @Test void toRequestAdapter_startTimestamp_zeroOnWrongRequest() { + assertThat(toRequestAdapter.startTimestamp(null)).isZero(); + assertThat(toRequestAdapter.startTimestamp(request)).isZero(); + } + + @Test void toRequestAdapter_startTimestamp_delegatesToHttpRequest() { + when(request.startTimestamp()).thenReturn(1L); + + assertThat(toRequestAdapter.startTimestamp(request)).isEqualTo(1L); + + verify(request).startTimestamp(); + } + + @Test void toRequestAdapter_method_nullOnNoMatch() { + assertThat(toRequestAdapter.method(request)).isNull(); + } + + @Test void toRequestAdapter_method_nullOnWrongRequest() { + assertThat(toRequestAdapter.method(null)).isNull(); + assertThat(toRequestAdapter.method(request)).isNull(); + } + + @Test void toRequestAdapter_method_delegatesToHttpRequest() { + when(request.method()).thenReturn("GET"); + + assertThat(toRequestAdapter.method(request)).isEqualTo("GET"); + + verify(request).method(); + } + + @Test void toRequestAdapter_url_nullOnNoMatch() { + assertThat(toRequestAdapter.url(request)).isNull(); + } + + @Test void toRequestAdapter_url_nullOnWrongRequest() { + assertThat(toRequestAdapter.url(null)).isNull(); + assertThat(toRequestAdapter.url(request)).isNull(); + } + + @Test void toRequestAdapter_url_delegatesToHttpRequest() { + when(request.url()).thenReturn("https://zipkin.io"); + + assertThat(toRequestAdapter.url(request)).isEqualTo("https://zipkin.io"); + + verify(request).url(); + } + + @Test void toRequestAdapter_requestHeader_nullOnNoMatch() { + assertThat(toRequestAdapter.requestHeader(request, "Content-Type")).isNull(); + } + + @Test void toRequestAdapter_requestHeader_nullOnWrongRequest() { + assertThat(toRequestAdapter.requestHeader(null, "Content-Type")).isNull(); + assertThat(toRequestAdapter.requestHeader(request, "Content-Type")).isNull(); + } + + @Test void toRequestAdapter_requestHeader_delegatesToHttpRequest() { + when(request.header("Content-Type")).thenReturn("text/plain"); + + assertThat(toRequestAdapter.requestHeader(request, "Content-Type")).isEqualTo("text/plain"); + + verify(request).header("Content-Type"); + } + + @Test void toRequestAdapter_path_nullOnNoMatch() { + assertThat(toRequestAdapter.path(request)).isNull(); + } + + @Test void toRequestAdapter_path_nullOnWrongRequest() { + assertThat(toRequestAdapter.path(null)).isNull(); + assertThat(toRequestAdapter.path(request)).isNull(); + } + + @Test void toRequestAdapter_path_delegatesToHttpRequest() { + when(request.path()).thenReturn("/api/v2/traces"); + + assertThat(toRequestAdapter.path(request)).isEqualTo("/api/v2/traces"); + + verify(request).path(); + } + + @Test void fromRequestAdapter_unwrap() { + assertThat(fromRequestAdapter.unwrap()).isSameAs(request); + } + + @Test void fromRequestAdapter_toString() { + assertThat(fromRequestAdapter.toString()) + .isEqualTo(request.toString()); + } + + @Test void fromRequestAdapter_startTimestamp_zeroOnNoMatch() { + assertThat(fromRequestAdapter.startTimestamp()).isZero(); + } + + @Test void fromRequestAdapter_startTimestamp_delegatesToAdapter() { + when(requestAdapter.startTimestamp(request)).thenReturn(1L); + + assertThat(fromRequestAdapter.startTimestamp()).isEqualTo(1L); + + verify(requestAdapter).startTimestamp(request); + } + + @Test void fromRequestAdapter_method_delegatesToAdapter() { + when(requestAdapter.method(request)).thenReturn("GET"); + + assertThat(fromRequestAdapter.method()).isEqualTo("GET"); + + verify(requestAdapter).method(request); + } + + @Test void fromRequestAdapter_url_nullOnNoMatch() { + assertThat(fromRequestAdapter.url()).isNull(); + } + + @Test void fromRequestAdapter_url_delegatesToAdapter() { + when(requestAdapter.url(request)).thenReturn("https://zipkin.io"); + + assertThat(fromRequestAdapter.url()).isEqualTo("https://zipkin.io"); + + verify(requestAdapter).url(request); + } + + @Test void fromRequestAdapter_header_nullOnNoMatch() { + assertThat(fromRequestAdapter.header("Content-Type")).isNull(); + } + + @Test void fromRequestAdapter_header_delegatesToAdapter() { + when(requestAdapter.requestHeader(request, "Content-Type")).thenReturn("text/plain"); + + assertThat(fromRequestAdapter.header("Content-Type")).isEqualTo("text/plain"); + + verify(requestAdapter).requestHeader(request, "Content-Type"); + } + + @Test void fromRequestAdapter_path_nullOnNoMatch() { + assertThat(fromRequestAdapter.path()).isNull(); + } + + @Test void fromRequestAdapter_path_delegatesToAdapter() { + when(requestAdapter.path(request)).thenReturn("/api/v2/traces"); + + assertThat(fromRequestAdapter.path()).isEqualTo("/api/v2/traces"); + + verify(requestAdapter).path(request); + } + + //////////// + + @Test void toResponseAdapter_toString() { + assertThat(toResponseAdapter.toString()) + .isEqualTo(response.toString()); + } + + @Test void toResponseAdapter_finishTimestamp_zeroOnNoMatch() { + assertThat(toResponseAdapter.finishTimestamp(response)).isZero(); + } + + @Test void toResponseAdapter_finishTimestamp_zeroOnWrongResponse() { + assertThat(toResponseAdapter.finishTimestamp(null)).isZero(); + assertThat(toResponseAdapter.finishTimestamp(response)).isZero(); + } + + @Test void toResponseAdapter_finishTimestamp_delegatesToHttpResponse() { + when(response.finishTimestamp()).thenReturn(1L); + + assertThat(toResponseAdapter.finishTimestamp(response)).isEqualTo(1L); + + verify(response).finishTimestamp(); + } + + @Test void toResponseAdapter_methodFromResponse_nullOnNoMatch() { + assertThat(toResponseAdapter.methodFromResponse(response)).isNull(); + } + + @Test void toResponseAdapter_methodFromResponse_nullOnWrongResponse() { + assertThat(toResponseAdapter.methodFromResponse(null)).isNull(); + assertThat(toResponseAdapter.methodFromResponse(response)).isNull(); + } + + @Test void toResponseAdapter_methodFromResponse_delegatesToHttpResponse() { + when(response.method()).thenReturn("GET"); + + assertThat(toResponseAdapter.methodFromResponse(response)).isEqualTo("GET"); + + verify(response).method(); + } + + @Test void toResponseAdapter_route_nullOnNoMatch() { + assertThat(toResponseAdapter.route(response)).isNull(); + } + + @Test void toResponseAdapter_route_nullOnWrongResponse() { + assertThat(toResponseAdapter.route(null)).isNull(); + assertThat(toResponseAdapter.route(response)).isNull(); + } + + @Test void toResponseAdapter_route_delegatesToHttpResponse() { + when(response.route()).thenReturn("https://zipkin.io"); + + assertThat(toResponseAdapter.route(response)).isEqualTo("https://zipkin.io"); + + verify(response).route(); + } + + @Test void toResponseAdapter_statusCode_nullOnNoMatch() { + assertThat(toResponseAdapter.statusCode(response)).isNull(); + } + + @Test void toResponseAdapter_statusCode_nullOnWrongResponse() { + assertThat(toResponseAdapter.statusCode(null)).isNull(); + assertThat(toResponseAdapter.statusCode(response)).isNull(); + } + + @Test void toResponseAdapter_statusCode_delegatesToHttpResponse() { + when(response.statusCode()).thenReturn(200); + + assertThat(toResponseAdapter.statusCode(response)).isEqualTo(200); + } + + @Test void toResponseAdapter_statusCodeAsInt_zeroOnNoMatch() { + assertThat(toResponseAdapter.statusCodeAsInt(response)).isZero(); + } + + @Test void toResponseAdapter_statusCodeAsInt_zeroOnWrongResponse() { + assertThat(toResponseAdapter.statusCodeAsInt(null)).isZero(); + assertThat(toResponseAdapter.statusCodeAsInt(response)).isZero(); + } + + @Test void toResponseAdapter_statusCodeAsInt_delegatesToHttpResponse() { + when(response.statusCode()).thenReturn(200); + + assertThat(toResponseAdapter.statusCodeAsInt(response)).isEqualTo(200); + } + + @Test void fromResponseAdapter_unwrap() { + assertThat(fromResponseAdapter.unwrap()).isSameAs(response); + } + + @Test void fromResponseAdapter_toString() { + assertThat(fromResponseAdapter.toString()) + .isEqualTo(response.toString()); + } + + @Test void fromResponseAdapter_finishTimestamp_zeroOnNoMatch() { + assertThat(fromResponseAdapter.finishTimestamp()).isZero(); + } + + @Test void fromResponseAdapter_finishTimestamp_delegatesToAdapter() { + when(responseAdapter.finishTimestamp(response)).thenReturn(1L); + + assertThat(fromResponseAdapter.finishTimestamp()).isEqualTo(1L); + + verify(responseAdapter).finishTimestamp(response); + } + + @Test void fromResponseAdapter_methodFromResponse_nullOnNoMatch() { + assertThat(fromResponseAdapter.method()).isNull(); + } + + @Test void fromResponseAdapter_methodFromResponse_delegatesToAdapter() { + when(responseAdapter.methodFromResponse(response)).thenReturn("GET"); + + assertThat(fromResponseAdapter.method()).isEqualTo("GET"); + + verify(responseAdapter).methodFromResponse(response); + } + + @Test void fromResponseAdapter_route_nullOnNoMatch() { + assertThat(fromResponseAdapter.route()).isNull(); + } + + @Test void fromResponseAdapter_route_delegatesToAdapter() { + when(responseAdapter.route(response)).thenReturn("https://zipkin.io"); + + assertThat(fromResponseAdapter.route()).isEqualTo("https://zipkin.io"); + + verify(responseAdapter).route(response); + } + + @Test void fromResponseAdapter_statusCode_zeroOnNoMatch() { + assertThat(fromResponseAdapter.statusCode()).isZero(); + } + + @Test void fromResponseAdapter_statusCode_delegatesToAdapterStatusCodeAsInt() { + when(responseAdapter.statusCodeAsInt(response)).thenReturn(200); + + assertThat(fromResponseAdapter.statusCode()).isEqualTo(200); + + verify(responseAdapter).statusCodeAsInt(response); + } +} diff --git a/instrumentation/http/src/test/java/brave/http/HttpClientAdaptersTest.java b/instrumentation/http/src/test/java/brave/http/HttpClientAdaptersTest.java new file mode 100644 index 0000000000..89ba1f3f2d --- /dev/null +++ b/instrumentation/http/src/test/java/brave/http/HttpClientAdaptersTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import org.junit.jupiter.api.BeforeEach; + +import static org.mockito.Mockito.mock; + +@Deprecated public class HttpClientAdaptersTest extends HttpAdaptersTest< + HttpClientRequest, HttpClientAdapter, + HttpClientResponse, HttpClientAdapter> { + + public HttpClientAdaptersTest() { + super( + mock(HttpClientRequest.class), mock(HttpClientAdapter.class), + mock(HttpClientResponse.class), mock(HttpClientAdapter.class) + ); + } + + @BeforeEach void setup() { + toRequestAdapter = new HttpClientAdapters.ToRequestAdapter(request, request); + fromRequestAdapter = new HttpClientAdapters.FromRequestAdapter<>(requestAdapter, request); + toResponseAdapter = new HttpClientAdapters.ToResponseAdapter(response, response); + fromResponseAdapter = + new HttpClientAdapters.FromResponseAdapter<>(responseAdapter, response, null); + } +} diff --git a/instrumentation/http/src/test/java/brave/http/HttpClientHandlerTest.java b/instrumentation/http/src/test/java/brave/http/HttpClientHandlerTest.java index 2deb2f0ab2..be787a8f35 100644 --- a/instrumentation/http/src/test/java/brave/http/HttpClientHandlerTest.java +++ b/instrumentation/http/src/test/java/brave/http/HttpClientHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,6 +13,7 @@ */ package brave.http; +import brave.SpanCustomizer; import brave.Tracing; import brave.handler.MutableSpan; import brave.propagation.CurrentTraceContext; @@ -21,6 +22,7 @@ import brave.sampler.SamplerFunction; import brave.sampler.SamplerFunctions; import brave.test.IntegrationTestSpanHandler; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -32,6 +34,7 @@ import org.mockito.quality.Strictness; import static brave.Span.Kind.CLIENT; +import static brave.http.HttpHandler.NULL_SENTINEL; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Answers.CALLS_REAL_METHODS; @@ -123,7 +126,7 @@ Tracing.Builder tracingBuilder() { @Test void handleSendWithParent_overrideContext() { try ( - CurrentTraceContext.Scope scope = httpTracing.tracing.currentTraceContext().newScope(context)) { + CurrentTraceContext.Scope ws = httpTracing.tracing.currentTraceContext().newScope(context)) { brave.Span span = handler.handleSendWithParent(request, null); // If the overwrite was successful, we have a root span. @@ -132,7 +135,7 @@ Tracing.Builder tracingBuilder() { } @Test void handleSendWithParent_overrideNull() { - try (CurrentTraceContext.Scope scope = httpTracing.tracing.currentTraceContext().newScope(null)) { + try (CurrentTraceContext.Scope ws = httpTracing.tracing.currentTraceContext().newScope(null)) { brave.Span span = handler.handleSendWithParent(request, context); // If the overwrite was successful, we have a child span. @@ -179,4 +182,108 @@ Tracing.Builder tracingBuilder() { .isInstanceOf(NullPointerException.class) .hasMessage("response == null"); } + + @Test void deprecatedHandleReceive_externalTimestamps() { + when(request.startTimestamp()).thenReturn(123000L); + when(response.finishTimestamp()).thenReturn(124000L); + + brave.Span span = handler.handleSend(request); + handler.handleReceive(response, null, span); + + assertThat(spanHandler.takeRemoteSpan(CLIENT)) + .extracting(MutableSpan::startTimestamp, MutableSpan::finishTimestamp) + .containsExactly(123000L, 124000L); + } + + @Test void deprecatedNextSpan_samplerSeesHttpClientRequest() { + SamplerFunction clientSampler = mock(SamplerFunction.class); + init(httpTracingBuilder(tracingBuilder()).clientSampler(clientSampler)); + + handler.nextSpan(request); + + verify(clientSampler).trySample(request); + } + + @Test void deprecatedHandleReceive_finishesSpanEvenIfUnwrappedNull() { + brave.Span span = mock(brave.Span.class); + when(span.context()).thenReturn(context); + when(span.customizer()).thenReturn(span); + + handler.handleReceive(mock(HttpClientResponse.class), null, span); + + verify(span).isNoop(); + verify(span).context(); + verify(span).customizer(); + verify(span).finish(); + verifyNoMoreInteractions(span); + } + + @Test void deprecatedHandleReceive_finishesSpanEvenIfUnwrappedNull_withError() { + brave.Span span = mock(brave.Span.class); + when(span.context()).thenReturn(context); + when(span.customizer()).thenReturn(span); + + Exception error = new RuntimeException("peanuts"); + + handler.handleReceive(mock(HttpClientResponse.class), error, span); + + verify(span).isNoop(); + verify(span).context(); + verify(span).customizer(); + verify(span).error(error); + verify(span).finish(); + verifyNoMoreInteractions(span); + } + + @Test void handleSend_oldSamplerDoesntSeeNullWhenUnwrappedNull() { + AtomicBoolean reachedAssertion = new AtomicBoolean(); + init(httpTracingBuilder(tracingBuilder()) + .clientSampler(new HttpSampler() { + @Override public Boolean trySample(HttpAdapter adapter, Req req) { + assertThat(req).isSameAs(NULL_SENTINEL); + reachedAssertion.set(true); + return true; + } + })); + + handler.handleSend(request); + + assertThat(reachedAssertion).isTrue(); + } + + @Test void handleSend_requestParserDoesntSeeNullWhenUnwrappedNull() { + AtomicBoolean reachedAssertion = new AtomicBoolean(); + init(httpTracingBuilder(tracingBuilder()) + .clientParser(new HttpClientParser() { + @Override + public void request(HttpAdapter adapter, Req req, SpanCustomizer span) { + assertThat(req).isSameAs(NULL_SENTINEL); + reachedAssertion.set(true); + } + })); + + handler.handleSend(request); + + assertThat(reachedAssertion).isTrue(); + } + + @Test void handleReceive_responseParserDoesntSeeNullWhenUnwrappedNull() { + AtomicBoolean reachedAssertion = new AtomicBoolean(); + init(httpTracingBuilder(tracingBuilder()) + .clientParser(new HttpClientParser() { + @Override + public void response(HttpAdapter adapter, Resp resp, Throwable error, + SpanCustomizer span) { + assertThat(resp).isSameAs(NULL_SENTINEL); + reachedAssertion.set(true); + } + })); + + brave.Span span = mock(brave.Span.class); + when(span.isNoop()).thenReturn(false); + + handler.handleReceive(response, null, span); + + assertThat(reachedAssertion).isTrue(); + } } diff --git a/instrumentation/http/src/test/java/brave/http/HttpParserTest.java b/instrumentation/http/src/test/java/brave/http/HttpParserTest.java new file mode 100644 index 0000000000..5cd2e0a3d8 --- /dev/null +++ b/instrumentation/http/src/test/java/brave/http/HttpParserTest.java @@ -0,0 +1,148 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.SpanCustomizer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@Deprecated public class HttpParserTest { + @Mock HttpClientAdapter adapter; + @Mock SpanCustomizer customizer; + Object request = new Object(); + Object response = new Object(); + HttpParser parser = new HttpParser(); + + @Test void spanName_isMethod() { + when(adapter.method(request)).thenReturn("GET"); + + assertThat(parser.spanName(adapter, request)) + .isEqualTo("GET"); // note: in practice this will become lowercase + } + + @Test void request_addsMethodAndPath() { + when(adapter.method(request)).thenReturn("GET"); + when(adapter.path(request)).thenReturn("/foo"); + + parser.request(adapter, request, customizer); + + verify(customizer).tag("http.method", "GET"); + verify(customizer).tag("http.path", "/foo"); + } + + @Test void request_doesntCrashOnNullPath() { + parser.request(adapter, request, customizer); + + verify(customizer, never()).tag("http.path", null); + } + + @Test void response_tagsStatusAndErrorOnResponseCode() { + when(adapter.statusCodeAsInt(response)).thenReturn(400); + + parser.response(adapter, response, null, customizer); + + verify(customizer).tag("http.status_code", "400"); + verify(customizer).tag("error", "400"); + } + + @Test void response_statusZeroIsNotAnError() { + when(adapter.statusCodeAsInt(response)).thenReturn(0); + + parser.response(adapter, response, null, customizer); + + verify(customizer, never()).tag("http.status_code", "0"); + verify(customizer, never()).tag("error", "0"); + } + + // Ensures "HTTP/1.1 101 Switching Protocols" aren't classified as error spans + @Test void response_status101IsNotAnError() { + when(adapter.statusCodeAsInt(response)).thenReturn(101); + + parser.response(adapter, response, null, customizer); + + verify(customizer).tag("http.status_code", "101"); + verify(customizer, never()).tag("error", "101"); + } + + @Test void response_tagsErrorFromException() { + parser.response(adapter, response, new RuntimeException("drat"), customizer); + + verify(customizer).tag("error", "drat"); + } + + @Test void response_tagsErrorPrefersExceptionVsResponseCode() { + when(adapter.statusCodeAsInt(response)).thenReturn(400); + + parser.response(adapter, response, new RuntimeException("drat"), customizer); + + verify(customizer).tag("error", "drat"); + } + + @Test void response_tagsErrorOnExceptionEvenIfStatusOk() { + when(adapter.statusCodeAsInt(response)).thenReturn(200); + + parser.response(adapter, response, new RuntimeException("drat"), customizer); + + verify(customizer).tag("error", "drat"); + } + + @Test void routeBasedName() { + when(adapter.methodFromResponse(response)).thenReturn("GET"); + when(adapter.route(response)).thenReturn("/users/:userId"); + when(adapter.statusCodeAsInt(response)).thenReturn(200); + + parser.response(adapter, response, null, customizer); + + verify(customizer).name("GET /users/:userId"); // zipkin will implicitly lowercase this + } + + @Test void routeBasedName_redirect() { + when(adapter.methodFromResponse(response)).thenReturn("GET"); + when(adapter.route(response)).thenReturn(""); + when(adapter.statusCodeAsInt(response)).thenReturn(307); + + parser.response(adapter, response, null, customizer); + + verify(customizer).name("GET redirected"); // zipkin will implicitly lowercase this + } + + @Test void routeBasedName_notFound() { + when(adapter.methodFromResponse(response)).thenReturn("DELETE"); + when(adapter.route(response)).thenReturn(""); + when(adapter.statusCodeAsInt(response)).thenReturn(404); + + parser.response(adapter, response, null, customizer); + + verify(customizer).name("DELETE not_found"); // zipkin will implicitly lowercase this + } + + @Test void routeBasedName_skipsOnMissingData() { + when(adapter.methodFromResponse(response)).thenReturn("DELETE"); + when(adapter.route(response)).thenReturn(null); // missing! + when(adapter.statusCodeAsInt(response)).thenReturn(404); + + parser.response(adapter, response, null, customizer); + + verify(customizer, never()).name(any(String.class)); + } +} diff --git a/instrumentation/http/src/test/java/brave/http/HttpRequestParserAdaptersTest.java b/instrumentation/http/src/test/java/brave/http/HttpRequestParserAdaptersTest.java new file mode 100644 index 0000000000..6f180a879e --- /dev/null +++ b/instrumentation/http/src/test/java/brave/http/HttpRequestParserAdaptersTest.java @@ -0,0 +1,129 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.SpanCustomizer; +import brave.http.HttpRequestParserAdapters.ClientAdapter; +import brave.http.HttpRequestParserAdapters.ServerAdapter; +import brave.propagation.CurrentTraceContext; +import brave.propagation.ThreadLocalCurrentTraceContext; +import brave.propagation.TraceContext; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.Test; + +import static brave.http.HttpHandler.NULL_SENTINEL; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.refEq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class HttpRequestParserAdaptersTest { + CurrentTraceContext currentTraceContext = ThreadLocalCurrentTraceContext.create(); + TraceContext context = TraceContext.newBuilder().traceId(1L).spanId(10L).build(); + HttpParser parser = mock(HttpParser.class); + SpanCustomizer span = mock(SpanCustomizer.class); + + /** + * The old http handler always parsed in scope because the parser had no argument for a trace + * context. + */ + @Test void parse_parsesInScope() { + AtomicBoolean parsed = new AtomicBoolean(); + ClientAdapter parserAdapter = new ClientAdapter(currentTraceContext, new HttpParser() { + @Override + public void request(HttpAdapter adapter, Req req, SpanCustomizer span) { + parsed.set(true); + assertThat(currentTraceContext.get()).isSameAs(context); + } + }); + + parserAdapter.parse(mock(HttpClientRequest.class), context, span); + + assertThat(parsed).isTrue(); + } + + @Test void parse_HttpClientRequestAdapter() { + ClientAdapter parserAdapter = new ClientAdapter(currentTraceContext, parser); + + HttpClientAdapter adapter = mock(HttpClientAdapter.class); + Object req = new Object(); + + parserAdapter.parse(new HttpClientAdapters.FromRequestAdapter(adapter, req), context, span); + + verify(parser).request(adapter, req, span); + } + + @Test void parse_HttpClientRequest() { + ClientAdapter parserAdapter = new ClientAdapter(currentTraceContext, parser); + + HttpClientRequest request = mock(HttpClientRequest.class); + Object req = new Object(); + when(request.unwrap()).thenReturn(req); + + parserAdapter.parse(request, context, span); + + HttpClientAdapters.ToRequestAdapter a = new HttpClientAdapters.ToRequestAdapter(request, req); + verify(parser).request(refEq(a), eq(req), eq(span)); + } + + @Test void parse_HttpClientParse_unwrapNull() { + ClientAdapter parserAdapter = new ClientAdapter(currentTraceContext, parser); + + HttpClientRequest request = mock(HttpClientRequest.class); + Object req = NULL_SENTINEL; // to avoid old parsers seeing null + + parserAdapter.parse(request, context, span); + + HttpClientAdapters.ToRequestAdapter a = new HttpClientAdapters.ToRequestAdapter(request, req); + verify(parser).request(refEq(a), eq(req), eq(span)); + } + + @Test void parse_HttpServerRequestAdapter() { + ServerAdapter parserAdapter = new ServerAdapter(currentTraceContext, parser); + + HttpServerAdapter adapter = mock(HttpServerAdapter.class); + Object req = new Object(); + + parserAdapter.parse(new HttpServerAdapters.FromRequestAdapter(adapter, req), context, span); + + verify(parser).request(adapter, req, span); + } + + @Test void parse_HttpServerRequest() { + ServerAdapter parserAdapter = new ServerAdapter(currentTraceContext, parser); + + HttpServerRequest request = mock(HttpServerRequest.class); + Object req = new Object(); + when(request.unwrap()).thenReturn(req); + + parserAdapter.parse(request, context, span); + + HttpServerAdapters.ToRequestAdapter a = new HttpServerAdapters.ToRequestAdapter(request, req); + verify(parser).request(refEq(a), eq(req), eq(span)); + } + + @Test void parse_HttpServerParse_unwrapNull() { + ServerAdapter parserAdapter = new ServerAdapter(currentTraceContext, parser); + + HttpServerRequest request = mock(HttpServerRequest.class); + Object req = NULL_SENTINEL; // to avoid old parsers seeing null + + parserAdapter.parse(request, context, span); + + HttpServerAdapters.ToRequestAdapter a = new HttpServerAdapters.ToRequestAdapter(request, req); + verify(parser).request(refEq(a), eq(req), eq(span)); + } +} diff --git a/instrumentation/http/src/test/java/brave/http/HttpResponseParserAdaptersTest.java b/instrumentation/http/src/test/java/brave/http/HttpResponseParserAdaptersTest.java new file mode 100644 index 0000000000..bbc09f12da --- /dev/null +++ b/instrumentation/http/src/test/java/brave/http/HttpResponseParserAdaptersTest.java @@ -0,0 +1,152 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.SpanCustomizer; +import brave.http.HttpResponseParserAdapters.ClientAdapter; +import brave.http.HttpResponseParserAdapters.ServerAdapter; +import brave.propagation.CurrentTraceContext; +import brave.propagation.ThreadLocalCurrentTraceContext; +import brave.propagation.TraceContext; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.Test; + +import static brave.http.HttpHandler.NULL_SENTINEL; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.refEq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class HttpResponseParserAdaptersTest { + CurrentTraceContext currentTraceContext = ThreadLocalCurrentTraceContext.create(); + TraceContext context = TraceContext.newBuilder().traceId(1L).spanId(10L).build(); + HttpParser parser = mock(HttpParser.class); + SpanCustomizer span = mock(SpanCustomizer.class); + + /** + * The old http handler always parsed in scope because the parser had no argument for a trace + * context. + */ + @Test void parse_parsesInScope() { + AtomicBoolean parsed = new AtomicBoolean(); + ClientAdapter parserAdapter = new ClientAdapter(currentTraceContext, new HttpParser() { + @Override + public void response(HttpAdapter adapter, Res res, Throwable error, + SpanCustomizer customizer) { + parsed.set(true); + assertThat(currentTraceContext.get()).isSameAs(context); + } + }); + + parserAdapter.parse(mock(HttpClientResponse.class), context, span); + + assertThat(parsed).isTrue(); + } + + @Test void parse_HttpClientResponseAdapter() { + ClientAdapter parserAdapter = new ClientAdapter(currentTraceContext, parser); + + HttpClientAdapter adapter = mock(HttpClientAdapter.class); + Object res = new Object(); + + parserAdapter.parse(new HttpClientAdapters.FromResponseAdapter(adapter, res, null), context, + span); + + verify(parser).response(adapter, res, null, span); + } + + @Test void parse_HttpClientResponse() { + ClientAdapter parserAdapter = new ClientAdapter(currentTraceContext, parser); + + HttpClientResponse response = mock(HttpClientResponse.class); + Object res = new Object(); + when(response.unwrap()).thenReturn(res); + + parserAdapter.parse(response, context, span); + + HttpClientAdapters.ToResponseAdapter a = + new HttpClientAdapters.ToResponseAdapter(response, res); + verify(parser).response(refEq(a), eq(res), eq(null), eq(span)); + } + + @Test void parse_HttpClientParse_error() { + ClientAdapter parserAdapter = new ClientAdapter(currentTraceContext, parser); + + HttpClientResponse response = mock(HttpClientResponse.class); + Object res = new Object(); + Throwable error = new Throwable(); + when(response.unwrap()).thenReturn(res); + when(response.error()).thenReturn(error); + + parserAdapter.parse(response, context, span); + + HttpClientAdapters.ToResponseAdapter a = + new HttpClientAdapters.ToResponseAdapter(response, res); + verify(parser).response(refEq(a), eq(res), eq(error), eq(span)); + } + + @Test void parse_HttpClientParse_unwrapNull() { + ClientAdapter parserAdapter = new ClientAdapter(currentTraceContext, parser); + + HttpClientResponse response = mock(HttpClientResponse.class); + Object res = NULL_SENTINEL; // to avoid old parsers seeing null + + parserAdapter.parse(response, context, span); + + HttpClientAdapters.ToResponseAdapter a = + new HttpClientAdapters.ToResponseAdapter(response, res); + verify(parser).response(refEq(a), eq(res), eq(null), eq(span)); + } + + @Test void parse_HttpServerResponseAdapter() { + ServerAdapter parserAdapter = new ServerAdapter(currentTraceContext, parser); + + HttpServerAdapter adapter = mock(HttpServerAdapter.class); + Object res = new Object(); + + parserAdapter.parse(new HttpServerAdapters.FromResponseAdapter(adapter, res, null), context, + span); + + verify(parser).response(adapter, res, null, span); + } + + @Test void parse_HttpServerResponse() { + ServerAdapter parserAdapter = new ServerAdapter(currentTraceContext, parser); + + HttpServerResponse response = mock(HttpServerResponse.class); + Object res = new Object(); + when(response.unwrap()).thenReturn(res); + + parserAdapter.parse(response, context, span); + + HttpServerAdapters.ToResponseAdapter a = + new HttpServerAdapters.ToResponseAdapter(response, res); + verify(parser).response(refEq(a), eq(res), eq(null), eq(span)); + } + + @Test void parse_HttpServerParse_unwrapNull() { + ServerAdapter parserAdapter = new ServerAdapter(currentTraceContext, parser); + + HttpServerResponse response = mock(HttpServerResponse.class); + Object res = NULL_SENTINEL; // to avoid old parsers seeing null + + parserAdapter.parse(response, context, span); + + HttpServerAdapters.ToResponseAdapter a = + new HttpServerAdapters.ToResponseAdapter(response, res); + verify(parser).response(refEq(a), eq(res), eq(null), eq(span)); + } +} diff --git a/instrumentation/http/src/test/java/brave/http/HttpRuleSamplerTest.java b/instrumentation/http/src/test/java/brave/http/HttpRuleSamplerTest.java index 162bedb538..82880a75ba 100644 --- a/instrumentation/http/src/test/java/brave/http/HttpRuleSamplerTest.java +++ b/instrumentation/http/src/test/java/brave/http/HttpRuleSamplerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -32,6 +32,9 @@ @ExtendWith(MockitoExtension.class) public class HttpRuleSamplerTest { + @Deprecated @Mock HttpClientAdapter adapter; + Object request = new Object(); + @Mock HttpClientRequest httpClientRequest; @Mock HttpServerRequest httpServerRequest; @@ -45,6 +48,11 @@ public class HttpRuleSamplerTest { .putRule(pathStartsWith("/foo"), sampler) .build(); + when(adapter.path(request)).thenReturn("/foo"); + + assertThat(ruleSampler.trySample(adapter, request)) + .isEqualTo(answer); + when(httpClientRequest.path()).thenReturn("/foo"); assertThat(ruleSampler.trySample(httpClientRequest)) @@ -63,6 +71,8 @@ public class HttpRuleSamplerTest { .putRule(pathStartsWith("/bar"), Sampler.ALWAYS_SAMPLE) .build(); + assertThat(ruleSampler.trySample(adapter, null)) + .isNull(); assertThat(ruleSampler.trySample(null)) .isNull(); } @@ -72,6 +82,11 @@ public class HttpRuleSamplerTest { .putRule(pathStartsWith("/bar"), Sampler.ALWAYS_SAMPLE) .build(); + when(adapter.path(request)).thenReturn("/foo"); + + assertThat(ruleSampler.trySample(adapter, request)) + .isNull(); + when(httpClientRequest.path()).thenReturn("/foo"); assertThat(ruleSampler.trySample(httpClientRequest)) @@ -112,6 +127,24 @@ public class HttpRuleSamplerTest { .isNull(); // unmatched because country isn't ES } + /** Tests deprecated method */ + @Test void addRule() { + HttpRuleSampler sampler = HttpRuleSampler.newBuilder() + .addRule("GET", "/foo", 0.0f) + .build(); + + when(httpServerRequest.method()).thenReturn("POST"); + + assertThat(sampler.trySample(httpServerRequest)) + .isNull(); + + when(httpServerRequest.method()).thenReturn("GET"); + when(httpServerRequest.path()).thenReturn("/foo"); + + assertThat(sampler.trySample(httpServerRequest)) + .isFalse(); + } + @Test void putAllRules() { HttpRuleSampler base = HttpRuleSampler.newBuilder() .putRule(and(methodEquals("GET"), pathStartsWith("/foo")), Sampler.NEVER_SAMPLE) diff --git a/instrumentation/http/src/test/java/brave/http/HttpSamplerTest.java b/instrumentation/http/src/test/java/brave/http/HttpSamplerTest.java new file mode 100644 index 0000000000..1b5835d4a1 --- /dev/null +++ b/instrumentation/http/src/test/java/brave/http/HttpSamplerTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static brave.http.HttpHandler.NULL_SENTINEL; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@Deprecated public class HttpSamplerTest { + @Mock HttpClientRequest httpClientRequest; + @Mock HttpServerRequest httpServerRequest; + Object request = new Object(); + + @Test void trySample_dispatches() { + HttpSampler sampler = new HttpSampler() { + @Override public Boolean trySample(HttpAdapter adapter, Req req) { + return adapter.method(req).equals("POST"); + } + }; + + when(httpClientRequest.method()).thenReturn("POST"); + assertThat(sampler.trySample(httpClientRequest)).isTrue(); + verify(httpClientRequest).method(); + + when(httpServerRequest.method()).thenReturn("POST"); + assertThat(sampler.trySample(httpServerRequest)).isTrue(); + verify(httpServerRequest).method(); + } + + @Test void trySample_seesUnwrappedValue() { + AtomicBoolean reachedAssertion = new AtomicBoolean(); + HttpSampler sampler = new HttpSampler() { + @Override public Boolean trySample(HttpAdapter adapter, Req req) { + assertThat(req).isSameAs(request); + reachedAssertion.set(true); + return true; + } + }; + + when(httpClientRequest.unwrap()).thenReturn(request); + assertThat(sampler.trySample(httpClientRequest)).isTrue(); + assertThat(reachedAssertion.getAndSet(false)).isTrue(); + + when(httpServerRequest.unwrap()).thenReturn(request); + assertThat(sampler.trySample(httpServerRequest)).isTrue(); + assertThat(reachedAssertion.getAndSet(false)).isTrue(); + } + + @Test void trySample_doesntSeeNullWhenUnwrappedNull() { + AtomicBoolean reachedAssertion = new AtomicBoolean(); + HttpSampler sampler = new HttpSampler() { + @Override public Boolean trySample(HttpAdapter adapter, Req req) { + assertThat(req).isSameAs(NULL_SENTINEL); + reachedAssertion.set(true); + return true; + } + }; + + assertThat(sampler.trySample(httpClientRequest)).isTrue(); + assertThat(reachedAssertion.getAndSet(false)).isTrue(); + + assertThat(sampler.trySample(httpServerRequest)).isTrue(); + assertThat(reachedAssertion.getAndSet(false)).isTrue(); + } +} diff --git a/instrumentation/http/src/test/java/brave/http/HttpServerAdapterTest.java b/instrumentation/http/src/test/java/brave/http/HttpServerAdapterTest.java new file mode 100644 index 0000000000..5d0319382c --- /dev/null +++ b/instrumentation/http/src/test/java/brave/http/HttpServerAdapterTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.Span; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) // TODO: hunt down these +@Deprecated +public class HttpServerAdapterTest { + @Mock HttpServerAdapter adapter; + @Mock Span span; + Object request = new Object(); + Object response = new Object(); + + @BeforeEach void callRealMethod() { + doCallRealMethod().when(adapter).parseClientIpAndPort(eq(request), isA(Span.class)); + when(adapter.path(request)).thenCallRealMethod(); + when(adapter.statusCodeAsInt(response)).thenCallRealMethod(); + when(adapter.parseClientIpFromXForwardedFor(request, span)).thenCallRealMethod(); + } + + @Test void path_doesntCrashOnNullUrl() { + assertThat(adapter.path(request)) + .isNull(); + } + + @Test void statusCodeAsInt_callsStatusCodeByDefault() { + when(adapter.statusCode(response)).thenReturn(400); + + assertThat(adapter.statusCodeAsInt(response)) + .isEqualTo(400); + } + + @Test void path_derivedFromUrl() { + when(adapter.url(request)).thenReturn("http://foo:8080/bar?hello=world"); + + assertThat(adapter.path(request)) + .isEqualTo("/bar"); + } + + @Test void parseClientIpAndPort_prefersXForwardedFor() { + when(adapter.requestHeader(request, "X-Forwarded-For")).thenReturn("1.2.3.4"); + + adapter.parseClientIpAndPort(request, span); + + verify(span).remoteIpAndPort("1.2.3.4", 0); + verifyNoMoreInteractions(span); + } + + @Test void parseClientIpAndPort_skipsOnNoIp() { + adapter.parseClientIpAndPort(request, span); + + verifyNoMoreInteractions(span); + } +} diff --git a/instrumentation/http/src/test/java/brave/http/HttpServerAdaptersTest.java b/instrumentation/http/src/test/java/brave/http/HttpServerAdaptersTest.java new file mode 100644 index 0000000000..3e072f2812 --- /dev/null +++ b/instrumentation/http/src/test/java/brave/http/HttpServerAdaptersTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.http; + +import brave.Span; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@Deprecated public class HttpServerAdaptersTest extends HttpAdaptersTest< + HttpServerRequest, HttpServerAdapter, + HttpServerResponse, HttpServerAdapter> { + + public HttpServerAdaptersTest() { + super( + mock(HttpServerRequest.class), mock(HttpServerAdapter.class), + mock(HttpServerResponse.class), mock(HttpServerAdapter.class) + ); + } + + Span span = mock(Span.class); + + @BeforeEach void setup() { + toRequestAdapter = new HttpServerAdapters.ToRequestAdapter(request, request); + fromRequestAdapter = new HttpServerAdapters.FromRequestAdapter<>(requestAdapter, request); + toResponseAdapter = new HttpServerAdapters.ToResponseAdapter(response, response); + fromResponseAdapter = + new HttpServerAdapters.FromResponseAdapter<>(responseAdapter, response, null); + } + + @Test void toRequestAdapter_parseClientIpAndPort_falseOnNoMatch() { + assertThat(toRequestAdapter.parseClientIpAndPort(request, span)).isFalse(); + } + + @Test void toRequestAdapter_parseClientIpAndPort_falseOnWrongRequest() { + assertThat(toRequestAdapter.parseClientIpAndPort(null, span)).isFalse(); + assertThat(toRequestAdapter.parseClientIpAndPort(request, span)).isFalse(); + } + + @Test void toRequestAdapter_parseClientIpAndPort_prioritizesXForwardedFor() { + when(request.header("X-Forwarded-For")).thenReturn("1.2.3.4"); + when(span.remoteIpAndPort("1.2.3.4", 0)).thenReturn(true); + + assertThat(toRequestAdapter.parseClientIpAndPort(request, span)).isTrue(); + } + + @Test void toRequestAdapter_parseClientIpAndPort_delegatesToServerRequest() { + when(request.parseClientIpAndPort(span)).thenReturn(true); + + assertThat(toRequestAdapter.parseClientIpAndPort(request, span)).isTrue(); + + verify(request).parseClientIpAndPort(span); + } + + @Test void fromRequestAdapter_parseClientIpAndPort_falseOnNoMatch() { + assertThat(fromRequestAdapter.parseClientIpAndPort(span)).isFalse(); + } + + @Test void fromRequestAdapter_parseClientIpAndPort_delegatesToAdapter() { + when(requestAdapter.parseClientIpAndPort(eq(request), isA(Span.class))).thenReturn(true); + + assertThat(fromRequestAdapter.parseClientIpAndPort(span)).isTrue(); + } +} diff --git a/instrumentation/http/src/test/java/brave/http/HttpServerHandlerTest.java b/instrumentation/http/src/test/java/brave/http/HttpServerHandlerTest.java index 9f4691d538..9b6e75fe72 100644 --- a/instrumentation/http/src/test/java/brave/http/HttpServerHandlerTest.java +++ b/instrumentation/http/src/test/java/brave/http/HttpServerHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,23 +14,25 @@ package brave.http; import brave.Span; +import brave.SpanCustomizer; import brave.Tracing; -import brave.handler.MutableSpan; import brave.propagation.TraceContext; import brave.sampler.Sampler; import brave.sampler.SamplerFunction; import brave.sampler.SamplerFunctions; -import brave.test.IntegrationTestSpanHandler; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.RegisterExtension; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import static brave.http.HttpHandler.NULL_SENTINEL; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Answers.CALLS_REAL_METHODS; @@ -43,7 +45,7 @@ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) // TODO: hunt down these public class HttpServerHandlerTest { - @RegisterExtension IntegrationTestSpanHandler spanHandler = new IntegrationTestSpanHandler(); + List spans = new ArrayList<>(); TraceContext context = TraceContext.newBuilder().traceId(1L).spanId(1L).sampled(true).build(); HttpTracing httpTracing; @@ -68,7 +70,7 @@ HttpTracing.Builder httpTracingBuilder(Tracing.Builder tracingBuilder) { } Tracing.Builder tracingBuilder() { - return Tracing.newBuilder().addSpanHandler(spanHandler); + return Tracing.newBuilder().spanReporter(spans::add); } @AfterEach void close() { @@ -114,9 +116,7 @@ Tracing.Builder tracingBuilder() { Span span = handler.handleReceive(request); handler.handleSend(response, span); - MutableSpan received = spanHandler.takeRemoteSpan(Span.Kind.SERVER); - assertThat(received.startTimestamp()).isEqualTo(123000L); - assertThat(received.finishTimestamp()).isEqualTo(124000L); + assertThat(spans.get(0).durationAsLong()).isEqualTo(1000L); } @Test void handleSend_finishesSpanEvenIfUnwrappedNull() { @@ -158,4 +158,102 @@ Tracing.Builder tracingBuilder() { .isInstanceOf(NullPointerException.class) .hasMessage("response == null"); } + + @Test void deprecatedHandleSend_externalTimestamps() { + when(request.startTimestamp()).thenReturn(123000L); + when(response.finishTimestamp()).thenReturn(124000L); + + Span span = handler.handleReceive(request); + handler.handleSend(response, null, span); + + assertThat(spans.get(0).durationAsLong()).isEqualTo(1000L); + } + + @Test void deprecatedHandleSend_finishesSpanEvenIfUnwrappedNull() { + brave.Span span = mock(brave.Span.class); + when(span.context()).thenReturn(context); + when(span.customizer()).thenReturn(span); + + handler.handleSend(mock(HttpServerResponse.class), null, span); + + verify(span).isNoop(); + verify(span).context(); + verify(span).customizer(); + verify(span).finish(); + verifyNoMoreInteractions(span); + } + + @Test void deprecatedHandleSend_finishesSpanEvenIfUnwrappedNull_withError() { + brave.Span span = mock(brave.Span.class); + when(span.context()).thenReturn(context); + when(span.customizer()).thenReturn(span); + + Exception error = new RuntimeException("peanuts"); + + handler.handleSend(mock(HttpServerResponse.class), error, span); + + verify(span).isNoop(); + verify(span).context(); + verify(span).customizer(); + verify(span).error(error); + verify(span).finish(); + verifyNoMoreInteractions(span); + } + + @Test void deprecatedHandleSend_oneOfResponseError() { + brave.Span span = mock(brave.Span.class); + + assertThatThrownBy(() -> handler.handleSend(null, null, span)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Either the response or error parameters may be null, but not both"); + } + + @Test void handleReceive_oldSamplerDoesntSeeNullWhenUnwrappedNull() { + AtomicBoolean reachedAssertion = new AtomicBoolean(); + init(httpTracingBuilder(tracingBuilder()) + .serverSampler(new HttpSampler() { + @Override public Boolean trySample(HttpAdapter adapter, Req req) { + assertThat(req).isSameAs(NULL_SENTINEL); + reachedAssertion.set(true); + return true; + } + })); + + handler.handleReceive(request); + + assertThat(reachedAssertion).isTrue(); + } + + @Test void handleReceive_requestParserDoesntSeeNullWhenUnwrappedNull() { + AtomicBoolean reachedAssertion = new AtomicBoolean(); + init(httpTracingBuilder(tracingBuilder()) + .serverParser(new HttpServerParser() { + @Override + public void request(HttpAdapter adapter, Req req, SpanCustomizer span) { + assertThat(req).isSameAs(NULL_SENTINEL); + reachedAssertion.set(true); + } + })); + + handler.handleReceive(request); + + assertThat(reachedAssertion).isTrue(); + } + + @Test void handleSend_responseParserDoesntSeeNullWhenUnwrappedNull() { + AtomicBoolean reachedAssertion = new AtomicBoolean(); + init(httpTracingBuilder(tracingBuilder()) + .serverParser(new HttpServerParser() { + @Override + public void response(HttpAdapter adapter, Resp resp, Throwable error, + SpanCustomizer span) { + assertThat(resp).isSameAs(NULL_SENTINEL); + reachedAssertion.set(true); + } + })); + + handler.handleSend(response, null, mock(brave.Span.class)); + + assertThat(reachedAssertion).isTrue(); + } } diff --git a/instrumentation/http/src/test/java/brave/http/HttpTracingTest.java b/instrumentation/http/src/test/java/brave/http/HttpTracingTest.java index 812fb73e40..e17f27a7fa 100644 --- a/instrumentation/http/src/test/java/brave/http/HttpTracingTest.java +++ b/instrumentation/http/src/test/java/brave/http/HttpTracingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/instrumentation/httpasyncclient/src/main/java/brave/httpasyncclient/TracingHttpAsyncClientBuilder.java b/instrumentation/httpasyncclient/src/main/java/brave/httpasyncclient/TracingHttpAsyncClientBuilder.java index c2f03bd2f4..e1f688c266 100644 --- a/instrumentation/httpasyncclient/src/main/java/brave/httpasyncclient/TracingHttpAsyncClientBuilder.java +++ b/instrumentation/httpasyncclient/src/main/java/brave/httpasyncclient/TracingHttpAsyncClientBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/instrumentation/httpclient/src/main/java/brave/httpclient/TracingMainExec.java b/instrumentation/httpclient/src/main/java/brave/httpclient/TracingMainExec.java index 1b186beb28..7c6e29976f 100644 --- a/instrumentation/httpclient/src/main/java/brave/httpclient/TracingMainExec.java +++ b/instrumentation/httpclient/src/main/java/brave/httpclient/TracingMainExec.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,12 +14,14 @@ package brave.httpclient; import brave.Span; +import brave.Tracer; import brave.http.HttpClientHandler; import brave.http.HttpClientRequest; import brave.http.HttpClientResponse; import brave.http.HttpTracing; import brave.httpclient.TracingProtocolExec.HttpRequestWrapper; import brave.internal.Nullable; +import brave.propagation.CurrentTraceContext; import java.io.IOException; import java.net.InetAddress; import org.apache.http.HttpException; @@ -36,11 +38,15 @@ * request, so this is where the span is started. */ class TracingMainExec implements ClientExecChain { // not final for subclassing + final Tracer tracer; + final CurrentTraceContext currentTraceContext; final HttpClientHandler handler; @Nullable final String serverName; final ClientExecChain mainExec; TracingMainExec(HttpTracing httpTracing, ClientExecChain mainExec) { + this.tracer = httpTracing.tracing().tracer(); + this.currentTraceContext = httpTracing.tracing().currentTraceContext(); this.serverName = "".equals(httpTracing.serverName()) ? null : httpTracing.serverName(); this.handler = HttpClientHandler.create(httpTracing); this.mainExec = mainExec; diff --git a/instrumentation/httpclient/src/main/java/brave/httpclient/TracingProtocolExec.java b/instrumentation/httpclient/src/main/java/brave/httpclient/TracingProtocolExec.java index 8c6d4bc4a5..0fefa5834c 100644 --- a/instrumentation/httpclient/src/main/java/brave/httpclient/TracingProtocolExec.java +++ b/instrumentation/httpclient/src/main/java/brave/httpclient/TracingProtocolExec.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -64,7 +64,7 @@ final class TracingProtocolExec implements ClientExecChain { CloseableHttpResponse response = null; Throwable error = null; - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); try { return response = protocolExec.execute(route, req, context, execAware); } catch (RuntimeException e) { @@ -82,7 +82,7 @@ final class TracingProtocolExec implements ClientExecChain { throw e; } finally { handler.handleReceive(new HttpResponseWrapper(response, context, error), span); - scope.close(); + ws.close(); } } diff --git a/instrumentation/httpclient5/src/main/java/brave/httpclient5/AsyncHandleSendHandler.java b/instrumentation/httpclient5/src/main/java/brave/httpclient5/AsyncHandleSendHandler.java index 43ea151cea..c519f5a1ec 100644 --- a/instrumentation/httpclient5/src/main/java/brave/httpclient5/AsyncHandleSendHandler.java +++ b/instrumentation/httpclient5/src/main/java/brave/httpclient5/AsyncHandleSendHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -11,6 +11,7 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ + package brave.httpclient5; import brave.Span; @@ -29,7 +30,6 @@ import org.apache.hc.core5.http.nio.AsyncEntityProducer; import static brave.httpclient5.HttpClientUtils.parseTargetAddress; -import static brave.internal.Throwables.propagateIfFatal; class AsyncHandleSendHandler implements AsyncExecChainHandler { final HttpClientHandler handler; @@ -57,7 +57,6 @@ public void execute(HttpRequest request, AsyncEntityProducer entityProducer, try { chain.proceed(request, entityProducer, scope, callbackWrapper); } catch (Throwable e) { - propagateIfFatal(e); // Handle if exception is raised before sending. context.removeAttribute(Span.class.getName()); HttpClientUtils.closeScope(context); diff --git a/instrumentation/httpclient5/src/main/java/brave/httpclient5/HandleReceiveHandler.java b/instrumentation/httpclient5/src/main/java/brave/httpclient5/HandleReceiveHandler.java index d3ee39e0c0..274325dd25 100644 --- a/instrumentation/httpclient5/src/main/java/brave/httpclient5/HandleReceiveHandler.java +++ b/instrumentation/httpclient5/src/main/java/brave/httpclient5/HandleReceiveHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -11,6 +11,7 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ + package brave.httpclient5; import brave.Span; @@ -32,8 +33,6 @@ import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; -import static brave.internal.Throwables.propagateIfFatal; - class HandleReceiveHandler implements ExecChainHandler { final Tracer tracer; final SamplerFunction httpSampler; @@ -48,21 +47,20 @@ class HandleReceiveHandler implements ExecChainHandler { @Override public ClassicHttpResponse execute( ClassicHttpRequest classicHttpRequest, - ExecChain.Scope execChainScope, + Scope scope, ExecChain execChain) throws IOException, HttpException { - HttpHost targetHost = execChainScope.route.getTargetHost(); + HttpHost targetHost = scope.route.getTargetHost(); HttpRequestWrapper request = new HttpRequestWrapper(classicHttpRequest, targetHost); Span span = tracer.nextSpan(httpSampler, request); - HttpClientContext clientContext = execChainScope.clientContext; + HttpClientContext clientContext = scope.clientContext; clientContext.setAttribute(Span.class.getName(), span); ClassicHttpResponse response = null; Throwable error = null; - try (SpanInScope scope = tracer.withSpanInScope(span)) { - return response = execChain.proceed(classicHttpRequest, execChainScope); + try (SpanInScope ws = tracer.withSpanInScope(span)) { + return response = execChain.proceed(classicHttpRequest, scope); } catch (Throwable e) { - propagateIfFatal(e); error = e; throw e; } finally { diff --git a/instrumentation/jersey-server/src/main/java/brave/jersey/server/TracingApplicationEventListener.java b/instrumentation/jersey-server/src/main/java/brave/jersey/server/TracingApplicationEventListener.java index 109951412c..bb15be4d40 100644 --- a/instrumentation/jersey-server/src/main/java/brave/jersey/server/TracingApplicationEventListener.java +++ b/instrumentation/jersey-server/src/main/java/brave/jersey/server/TracingApplicationEventListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/instrumentation/jms-jakarta/README.md b/instrumentation/jms-jakarta/README.md index a8c8cd575e..3fa006f850 100644 --- a/instrumentation/jms-jakarta/README.md +++ b/instrumentation/jms-jakarta/README.md @@ -87,7 +87,7 @@ void process(Message message) { Span span = jmsTracing.nextSpan(message).name("process").start(); // Below is the same setup as any synchronous tracing - try (SpanInScope scope = tracer.withSpanInScope(span)) { // so logging can see trace ID + try (SpanInScope ws = tracer.withSpanInScope(span)) { // so logging can see trace ID return doProcess(message); // do the actual work } catch (RuntimeException | Error e) { span.error(e); // make sure any error gets into the span before it is finished @@ -100,7 +100,7 @@ void process(Message message) { ## Compatibility issues -There are known issues with ActiveMQ Artemis Client 2.x: +There are known issues with ActiveMQ Artemis Client 2.x: - the message property names have to be valid Java identifiers, as such properties like `X-B3-TraceId`, `X-B3-SpanId` will be rejected and not propagated - the message property of type `Object` only supports `String`, primitive wrappers (`Integer`, `Byte`, ...) and `byte[]`, other types be rejected and not set diff --git a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/JmsTracing.java b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/JmsTracing.java index 2728d7d3f7..eda3fefbb1 100644 --- a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/JmsTracing.java +++ b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/JmsTracing.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -26,6 +26,11 @@ import brave.propagation.TraceContext.Injector; import brave.propagation.TraceContextOrSamplingFlags; import brave.sampler.SamplerFunction; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; import jakarta.jms.Connection; import jakarta.jms.ConnectionFactory; import jakarta.jms.JMSException; @@ -38,21 +43,13 @@ import jakarta.jms.XAConnectionFactory; import jakarta.jms.XAQueueConnection; import jakarta.jms.XATopicConnection; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.logging.Logger; import static brave.internal.Throwables.propagateIfFatal; import static brave.jakarta.jms.MessageParser.destination; import static brave.jakarta.jms.MessageProperties.getPropertyIfString; -/** - * Use this class to decorate your JMS consumer / producer and enable Tracing. - * - * @since 5.12 - */ +/** Use this class to decorate your JMS consumer / producer and enable Tracing. */ +/** @since 5.12 */ public final class JmsTracing { static final String JMS_QUEUE = "jms.queue"; static final String JMS_TOPIC = "jms.topic"; @@ -296,16 +293,16 @@ void tagQueueOrTopic(MessagingRequest request, SpanCustomizer span) { * try { * return message.getStringProperty(name); * } catch (Throwable t) { - * Throwables.propagateIfFatal(e); + * Call.propagateIfFatal(e); * log(e, "error getting property {0} from message {1}", name, message); * return null; * } * } * * @param thrown the JMS exception that was caught - * @param msg the format string - * @param zero will end up as {@code {0}} in the format string - * @param one if present, will end up as {@code {1}} in the format string + * @param msg the format string + * @param zero will end up as {@code {0}} in the format string + * @param one if present, will end up as {@code {1}} in the format string */ static void log(Throwable thrown, String msg, Object zero, @Nullable Object one) { Logger logger = LoggerHolder.LOG; diff --git a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/PropertyFilter.java b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/PropertyFilter.java index 99ce165701..1d910370d5 100644 --- a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/PropertyFilter.java +++ b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/PropertyFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,11 +13,11 @@ */ package brave.jakarta.jms; -import jakarta.jms.Message; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Set; +import jakarta.jms.Message; import static brave.internal.Throwables.propagateIfFatal; import static brave.jakarta.jms.JmsTracing.log; @@ -31,7 +31,7 @@ final class PropertyFilter { *

See https://docs.oracle.com/javaee/6/api/javax/jms/Message.html */ static void filterProperties(Message message, Set namesToClear) { - List retainedProperties = messagePropertiesBuffer(); + ArrayList retainedProperties = messagePropertiesBuffer(); try { filterProperties(message, namesToClear, retainedProperties); } finally { @@ -86,11 +86,11 @@ static void filterProperties(Message message, Set namesToClear, List> MESSAGE_PROPERTIES_BUFFER = new ThreadLocal<>(); + static final ThreadLocal> MESSAGE_PROPERTIES_BUFFER = new ThreadLocal<>(); /** Also use pair indexing for temporary message properties: (name, value). */ - static List messagePropertiesBuffer() { - List messagePropertiesBuffer = MESSAGE_PROPERTIES_BUFFER.get(); + static ArrayList messagePropertiesBuffer() { + ArrayList messagePropertiesBuffer = MESSAGE_PROPERTIES_BUFFER.get(); if (messagePropertiesBuffer == null) { messagePropertiesBuffer = new ArrayList<>(); MESSAGE_PROPERTIES_BUFFER.set(messagePropertiesBuffer); diff --git a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingCompletionListener.java b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingCompletionListener.java index 3319721a36..69538ec7e9 100644 --- a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingCompletionListener.java +++ b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingCompletionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,9 +14,11 @@ package brave.jakarta.jms; import brave.Span; +import brave.internal.Nullable; import brave.propagation.CurrentTraceContext; import brave.propagation.CurrentTraceContext.Scope; import jakarta.jms.CompletionListener; +import jakarta.jms.Destination; import jakarta.jms.Message; /** @@ -24,23 +26,26 @@ * for send and actual send. */ final class TracingCompletionListener implements CompletionListener { - static CompletionListener create(CompletionListener delegate, Span span, - CurrentTraceContext current) { - return new TracingCompletionListener(delegate, span, current); + static CompletionListener create(CompletionListener delegate, + @Nullable Destination destination, Span span, CurrentTraceContext current) { + return new TracingCompletionListener(delegate, destination, span, current); } final CompletionListener delegate; final CurrentTraceContext current; + @Nullable final Destination destination; final Span span; - TracingCompletionListener(CompletionListener delegate, Span span, CurrentTraceContext current) { + TracingCompletionListener(CompletionListener delegate, Destination destination, Span span, + CurrentTraceContext current) { this.delegate = delegate; + this.destination = destination; this.span = span; this.current = current; } @Override public void onCompletion(Message message) { - try (Scope scope = current.maybeScope(span.context())) { + try (Scope ws = current.maybeScope(span.context())) { delegate.onCompletion(message); } finally { // TODO: in order to tag messageId @@ -50,7 +55,7 @@ static CompletionListener create(CompletionListener delegate, Span span, } @Override public void onException(Message message, Exception exception) { - try (Scope scope = current.maybeScope(span.context())) { + try (Scope ws = current.maybeScope(span.context())) { // TODO: in order to tag messageId // parse(new MessageConsumerRequest(message, destination)) delegate.onException(message, exception); diff --git a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingExceptionListener.java b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingExceptionListener.java index 5e66357f2b..daeb198d36 100644 --- a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingExceptionListener.java +++ b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingExceptionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -57,7 +57,7 @@ static final class DelegateAndTagError extends TagError { delegate.onException(exception); return; } - try (SpanInScope scope = tracer.withSpanInScope(span)) { + try (SpanInScope ws = tracer.withSpanInScope(span)) { delegate.onException(exception); } finally { span.error(exception); diff --git a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingJMSContext.java b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingJMSContext.java index 4bf0b332a6..620f415637 100644 --- a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingJMSContext.java +++ b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingJMSContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,6 +13,7 @@ */ package brave.jakarta.jms; +import java.io.Serializable; import jakarta.jms.BytesMessage; import jakarta.jms.ConnectionMetaData; import jakarta.jms.Destination; @@ -31,7 +32,6 @@ import jakarta.jms.TextMessage; import jakarta.jms.Topic; import jakarta.jms.XAJMSContext; -import java.io.Serializable; class TracingJMSContext implements JMSContext { static JMSContext create(JMSContext delegate, JmsTracing jmsTracing) { diff --git a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingJMSProducer.java b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingJMSProducer.java index b47cba9559..3a7ed1b498 100644 --- a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingJMSProducer.java +++ b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingJMSProducer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -15,13 +15,13 @@ import brave.Span; import brave.propagation.CurrentTraceContext.Scope; +import java.io.Serializable; +import java.util.Map; +import java.util.Set; import jakarta.jms.CompletionListener; import jakarta.jms.Destination; import jakarta.jms.JMSProducer; import jakarta.jms.Message; -import java.io.Serializable; -import java.util.Map; -import java.util.Set; import static brave.internal.Throwables.propagateIfFatal; @@ -97,10 +97,10 @@ enum Send { void send(Send send, Destination destination, Object message) { Span span = createAndStartProducerSpan(new JMSProducerRequest(delegate, destination)); - Scope scope = current.newScope(span.context()); + Scope ws = current.newScope(span.context()); final CompletionListener async = getAsync(); if (async != null) { - delegate.setAsync(TracingCompletionListener.create(async, span, current)); + delegate.setAsync(TracingCompletionListener.create(async, destination, span, current)); } Throwable error = null; try { @@ -117,7 +117,7 @@ void send(Send send, Destination destination, Object message) { } else { span.finish(); // handle success synchronous send } - scope.close(); + ws.close(); } } diff --git a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingMessageListener.java b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingMessageListener.java index dd4139ddba..794ccd2f13 100644 --- a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingMessageListener.java +++ b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingMessageListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -19,6 +19,7 @@ import brave.Tracing; import brave.messaging.MessagingRequest; import brave.propagation.TraceContext.Extractor; +import brave.propagation.TraceContext.Injector; import brave.propagation.TraceContextOrSamplingFlags; import brave.sampler.SamplerFunction; import jakarta.jms.Message; @@ -50,6 +51,7 @@ static MessageListener create(MessageListener delegate, JmsTracing jmsTracing) { final Tracing tracing; final Tracer tracer; final Extractor extractor; + final Injector injector; final SamplerFunction sampler; final String remoteServiceName; final boolean addConsumerSpan; @@ -61,13 +63,14 @@ static MessageListener create(MessageListener delegate, JmsTracing jmsTracing) { this.tracer = jmsTracing.tracer; this.extractor = jmsTracing.messageConsumerExtractor; this.sampler = jmsTracing.consumerSampler; + this.injector = jmsTracing.messageConsumerInjector; this.remoteServiceName = jmsTracing.remoteServiceName; this.addConsumerSpan = addConsumerSpan; } @Override public void onMessage(Message message) { Span listenerSpan = startMessageListenerSpan(message); - SpanInScope scope = tracer.withSpanInScope(listenerSpan); + SpanInScope ws = tracer.withSpanInScope(listenerSpan); Throwable error = null; try { delegate.onMessage(message); @@ -78,7 +81,7 @@ static MessageListener create(MessageListener delegate, JmsTracing jmsTracing) { } finally { if (error != null) listenerSpan.error(error); listenerSpan.finish(); - scope.close(); + ws.close(); } } diff --git a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingMessageProducer.java b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingMessageProducer.java index 307f86fddf..b24e2bb52b 100644 --- a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingMessageProducer.java +++ b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingMessageProducer.java @@ -114,7 +114,7 @@ Span createAndStartProducerSpan(Message message, Destination destination) { @Override public void send(Message message) throws JMSException { Span span = createAndStartProducerSpan(message, destination(message)); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { delegate.send(message); @@ -125,14 +125,14 @@ Span createAndStartProducerSpan(Message message, Destination destination) { } finally { if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } @Override public void send(Message message, int deliveryMode, int priority, long timeToLive) throws JMSException { Span span = createAndStartProducerSpan(message, destination(message)); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { delegate.send(message, deliveryMode, priority, timeToLive); @@ -143,7 +143,7 @@ Span createAndStartProducerSpan(Message message, Destination destination) { } finally { if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } @@ -178,7 +178,7 @@ abstract void apply(MessageProducer producer, Destination destination, Message m void send(SendDestination sendDestination, Destination destination, Message message) throws JMSException { Span span = createAndStartProducerSpan(message, destination); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { sendDestination.apply(delegate, destination, message); @@ -189,7 +189,7 @@ void send(SendDestination sendDestination, Destination destination, Message mess } finally { if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } @@ -197,7 +197,7 @@ void send(SendDestination sendDestination, Destination destination, Message mess public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive) throws JMSException { Span span = createAndStartProducerSpan(message, destination); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { delegate.send(destination, message, deliveryMode, priority, timeToLive); @@ -208,7 +208,7 @@ public void send(Destination destination, Message message, int deliveryMode, int } finally { if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } @@ -216,17 +216,17 @@ public void send(Destination destination, Message message, int deliveryMode, int public void send(Message message, CompletionListener completionListener) throws JMSException { Destination destination = destination(message); Span span = createAndStartProducerSpan(message, destination); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { - delegate.send(message, TracingCompletionListener.create(completionListener, span, current)); + delegate.send(message, TracingCompletionListener.create(completionListener, destination, span, current)); } catch (Throwable t) { propagateIfFatal(t); error = t; throw t; } finally { if (error != null) span.error(error).finish(); - scope.close(); + ws.close(); } } @@ -234,8 +234,8 @@ public void send(Message message, CompletionListener completionListener) throws CompletionListener completionListener) throws JMSException { Destination destination = destination(message); Span span = createAndStartProducerSpan(message, destination); - completionListener = TracingCompletionListener.create(completionListener, span, current); - SpanInScope scope = tracer.withSpanInScope(span); + completionListener = TracingCompletionListener.create(completionListener, destination, span, current); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { delegate.send(message, deliveryMode, priority, timeToLive, completionListener); @@ -245,15 +245,15 @@ public void send(Message message, CompletionListener completionListener) throws throw t; } finally { if (error != null) span.error(error).finish(); - scope.close(); + ws.close(); } } @Override public void send(Destination destination, Message message, CompletionListener completionListener) throws JMSException { Span span = createAndStartProducerSpan(message, destination); - completionListener = TracingCompletionListener.create(completionListener, span, current); - SpanInScope scope = tracer.withSpanInScope(span); + completionListener = TracingCompletionListener.create(completionListener, destination, span, current); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { delegate.send(destination, message, completionListener); @@ -263,15 +263,15 @@ public void send(Message message, CompletionListener completionListener) throws throw t; } finally { if (error != null) span.error(error).finish(); - scope.close(); + ws.close(); } } @Override public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, CompletionListener completionListener) throws JMSException { Span span = createAndStartProducerSpan(message, destination); - completionListener = TracingCompletionListener.create(completionListener, span, current); - SpanInScope scope = tracer.withSpanInScope(span); + completionListener = TracingCompletionListener.create(completionListener, destination, span, current); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { delegate.send(destination, message, deliveryMode, priority, timeToLive, completionListener); @@ -281,7 +281,7 @@ public void send(Message message, CompletionListener completionListener) throws throw t; } finally { if (error != null) span.error(error).finish(); - scope.close(); + ws.close(); } } @@ -303,7 +303,7 @@ public void send(Queue queue, Message message, int deliveryMode, int priority, l checkQueueSender(); QueueSender qs = (QueueSender) delegate; Span span = createAndStartProducerSpan(message, destination(message)); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { qs.send(queue, message, deliveryMode, priority, timeToLive); @@ -314,7 +314,7 @@ public void send(Queue queue, Message message, int deliveryMode, int priority, l } finally { if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } @@ -336,7 +336,7 @@ void checkQueueSender() { TopicPublisher tp = (TopicPublisher) delegate; Span span = createAndStartProducerSpan(message, destination(message)); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { tp.publish(message); @@ -347,7 +347,7 @@ void checkQueueSender() { } finally { if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } @@ -357,7 +357,7 @@ void checkQueueSender() { TopicPublisher tp = (TopicPublisher) delegate; Span span = createAndStartProducerSpan(message, destination(message)); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { tp.publish(message, deliveryMode, priority, timeToLive); @@ -368,7 +368,7 @@ void checkQueueSender() { } finally { if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } @@ -384,7 +384,7 @@ public void publish(Topic topic, Message message, int deliveryMode, int priority TopicPublisher tp = (TopicPublisher) delegate; Span span = createAndStartProducerSpan(message, destination(message)); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { tp.publish(topic, message, deliveryMode, priority, timeToLive); @@ -395,7 +395,7 @@ public void publish(Topic topic, Message message, int deliveryMode, int priority } finally { if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } diff --git a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingSession.java b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingSession.java index 7f60efa6f4..b803302ab5 100644 --- a/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingSession.java +++ b/instrumentation/jms-jakarta/src/main/java/brave/jakarta/jms/TracingSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,6 +13,7 @@ */ package brave.jakarta.jms; +import java.io.Serializable; import jakarta.jms.BytesMessage; import jakarta.jms.Destination; import jakarta.jms.JMSException; @@ -39,7 +40,6 @@ import jakarta.jms.XAQueueSession; import jakarta.jms.XASession; import jakarta.jms.XATopicSession; -import java.io.Serializable; import static brave.jakarta.jms.TracingConnection.TYPE_QUEUE; import static brave.jakarta.jms.TracingConnection.TYPE_TOPIC; diff --git a/instrumentation/jms-jakarta/src/test/java/brave/jakarta/jms/JmsTracingTest.java b/instrumentation/jms-jakarta/src/test/java/brave/jakarta/jms/JmsTracingTest.java index b82fbadbf1..686425eee7 100644 --- a/instrumentation/jms-jakarta/src/test/java/brave/jakarta/jms/JmsTracingTest.java +++ b/instrumentation/jms-jakarta/src/test/java/brave/jakarta/jms/JmsTracingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -53,7 +53,7 @@ public class JmsTracingTest extends ITJms { TraceContext parent = newTraceContext(SamplingFlags.DEBUG); ActiveMQTextMessage message; - @BeforeEach void setup() { + @BeforeEach void setup() throws JMSException { final ClientSession clientSession = mock(ClientSession.class); when(clientSession.createMessage(anyByte(), eq(true), eq(0L), anyLong(), eq((byte) 4))) .thenReturn(new ClientMessageImpl()); @@ -213,7 +213,7 @@ abstract class Both implements XATopicConnection, TopicConnection { setStringProperty(message, "b3", B3SingleFormat.writeB3SingleFormat(incoming)); Span child; - try (Scope scope = tracing.currentTraceContext().newScope(parent)) { + try (Scope ws = tracing.currentTraceContext().newScope(parent)) { child = jmsTracing.nextSpan(message); } assertChildOf(child.context(), incoming); @@ -222,7 +222,7 @@ abstract class Both implements XATopicConnection, TopicConnection { @Test void nextSpan_uses_current_context() { Span child; - try (Scope scope = tracing.currentTraceContext().newScope(parent)) { + try (Scope ws = tracing.currentTraceContext().newScope(parent)) { child = jmsTracing.nextSpan(message); } assertChildOf(child.context(), parent); @@ -263,6 +263,7 @@ abstract class Both implements XATopicConnection, TopicConnection { @Test void nextSpan_should_clear_propagation_headers() { Propagation.B3_STRING.injector(SETTER).inject(parent, message); + Propagation.B3_SINGLE_STRING.injector(SETTER).inject(parent, message); jmsTracing.nextSpan(message); assertThat(ITJms.propertiesToMap(message)) diff --git a/instrumentation/jms-jakarta/src/test/java/brave/jakarta/jms/TracingCompletionListenerTest.java b/instrumentation/jms-jakarta/src/test/java/brave/jakarta/jms/TracingCompletionListenerTest.java index c6fd99cda5..5061642964 100644 --- a/instrumentation/jms-jakarta/src/test/java/brave/jakarta/jms/TracingCompletionListenerTest.java +++ b/instrumentation/jms-jakarta/src/test/java/brave/jakarta/jms/TracingCompletionListenerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -16,6 +16,7 @@ import brave.Span; import brave.propagation.TraceContextOrSamplingFlags; import jakarta.jms.CompletionListener; +import jakarta.jms.Destination; import jakarta.jms.Message; import org.junit.jupiter.api.Test; @@ -25,6 +26,7 @@ public class TracingCompletionListenerTest extends ITJms { Message message = mock(Message.class); + Destination destination = mock(Destination.class); @Test void onCompletion_shouldKeepContext_whenNotSampled() { Span span = tracing.tracer().nextSpan(TraceContextOrSamplingFlags.NOT_SAMPLED); @@ -38,7 +40,7 @@ public class TracingCompletionListenerTest extends ITJms { } }; CompletionListener tracingCompletionListener = - TracingCompletionListener.create(delegate, span, currentTraceContext); + TracingCompletionListener.create(delegate, destination, span, currentTraceContext); tracingCompletionListener.onCompletion(null); @@ -49,7 +51,7 @@ public class TracingCompletionListenerTest extends ITJms { Span span = tracing.tracer().nextSpan().start(); CompletionListener tracingCompletionListener = - TracingCompletionListener.create(mock(CompletionListener.class), span, + TracingCompletionListener.create(mock(CompletionListener.class), destination, span, currentTraceContext); tracingCompletionListener.onCompletion(message); @@ -62,7 +64,7 @@ public class TracingCompletionListenerTest extends ITJms { RuntimeException error = new RuntimeException("Test exception"); CompletionListener tracingCompletionListener = - TracingCompletionListener.create(mock(CompletionListener.class), span, + TracingCompletionListener.create(mock(CompletionListener.class), destination, span, currentTraceContext); tracingCompletionListener.onException(message, error); @@ -74,7 +76,7 @@ public class TracingCompletionListenerTest extends ITJms { CompletionListener delegate = mock(CompletionListener.class); CompletionListener tracingCompletionListener = - TracingCompletionListener.create(delegate, span, currentTraceContext); + TracingCompletionListener.create(delegate, destination, span, currentTraceContext); tracingCompletionListener.onCompletion(message); verify(delegate).onCompletion(message); @@ -95,7 +97,7 @@ public class TracingCompletionListenerTest extends ITJms { } }; - TracingCompletionListener.create(delegate, span, currentTraceContext) + TracingCompletionListener.create(delegate, destination, span, currentTraceContext) .onCompletion(message); testSpanHandler.takeLocalSpan(); @@ -106,7 +108,7 @@ public class TracingCompletionListenerTest extends ITJms { CompletionListener delegate = mock(CompletionListener.class); CompletionListener tracingCompletionListener = - TracingCompletionListener.create(delegate, span, currentTraceContext); + TracingCompletionListener.create(delegate, destination, span, currentTraceContext); RuntimeException error = new RuntimeException("Test exception"); tracingCompletionListener.onException(message, error); diff --git a/instrumentation/jms-jakarta/src/test/java/brave/jakarta/jms/TracingJMSConsumerTest.java b/instrumentation/jms-jakarta/src/test/java/brave/jakarta/jms/TracingJMSConsumerTest.java index f9eceb29f8..5b3f875f4d 100644 --- a/instrumentation/jms-jakarta/src/test/java/brave/jakarta/jms/TracingJMSConsumerTest.java +++ b/instrumentation/jms-jakarta/src/test/java/brave/jakarta/jms/TracingJMSConsumerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -19,6 +19,7 @@ import brave.propagation.SamplingFlags; import brave.propagation.TraceContext; import jakarta.jms.JMSConsumer; +import jakarta.jms.JMSException; import jakarta.jms.Message; import java.util.Collections; import org.apache.activemq.artemis.api.core.client.ClientSession; @@ -90,8 +91,16 @@ public class TracingJMSConsumerTest extends ITJms { testSpanHandler.takeRemoteSpan(CONSUMER); } - void receive(Message message) { + void receive(Message message) throws Exception { when(delegate.receive()).thenReturn(message); tracingJMSConsumer.receive(); } + + void assertNoProperties(ActiveMQTextMessage message) { + try { + assertThat(Collections.list(message.getPropertyNames())).isEmpty(); + } catch (JMSException e) { + throw new AssertionError(e); + } + } } diff --git a/instrumentation/jms/README.md b/instrumentation/jms/README.md index 18ee1786bf..13385c6895 100644 --- a/instrumentation/jms/README.md +++ b/instrumentation/jms/README.md @@ -87,7 +87,7 @@ void process(Message message) { Span span = jmsTracing.nextSpan(message).name("process").start(); // Below is the same setup as any synchronous tracing - try (SpanInScope scope = tracer.withSpanInScope(span)) { // so logging can see trace ID + try (SpanInScope ws = tracer.withSpanInScope(span)) { // so logging can see trace ID return doProcess(message); // do the actual work } catch (RuntimeException | Error e) { span.error(e); // make sure any error gets into the span before it is finished diff --git a/instrumentation/jms/src/main/java/brave/jms/JmsTracing.java b/instrumentation/jms/src/main/java/brave/jms/JmsTracing.java index df492503e8..ad62b0edfa 100644 --- a/instrumentation/jms/src/main/java/brave/jms/JmsTracing.java +++ b/instrumentation/jms/src/main/java/brave/jms/JmsTracing.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -294,7 +294,7 @@ void tagQueueOrTopic(MessagingRequest request, SpanCustomizer span) { * try { * return message.getStringProperty(name); * } catch (Throwable t) { - * Throwables.propagateIfFatal(e); + * Call.propagateIfFatal(e); * log(e, "error getting property {0} from message {1}", name, message); * return null; * } diff --git a/instrumentation/jms/src/main/java/brave/jms/PropertyFilter.java b/instrumentation/jms/src/main/java/brave/jms/PropertyFilter.java index fdab645323..3666385171 100644 --- a/instrumentation/jms/src/main/java/brave/jms/PropertyFilter.java +++ b/instrumentation/jms/src/main/java/brave/jms/PropertyFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -31,7 +31,7 @@ final class PropertyFilter { *

See https://docs.oracle.com/javaee/6/api/javax/jms/Message.html */ static void filterProperties(Message message, Set namesToClear) { - List retainedProperties = messagePropertiesBuffer(); + ArrayList retainedProperties = messagePropertiesBuffer(); try { filterProperties(message, namesToClear, retainedProperties); } finally { @@ -86,12 +86,12 @@ static void filterProperties(Message message, Set namesToClear, List> MESSAGE_PROPERTIES_BUFFER = - new ThreadLocal>(); + static final ThreadLocal> MESSAGE_PROPERTIES_BUFFER = + new ThreadLocal>(); /** Also use pair indexing for temporary message properties: (name, value). */ - static List messagePropertiesBuffer() { - List messagePropertiesBuffer = MESSAGE_PROPERTIES_BUFFER.get(); + static ArrayList messagePropertiesBuffer() { + ArrayList messagePropertiesBuffer = MESSAGE_PROPERTIES_BUFFER.get(); if (messagePropertiesBuffer == null) { messagePropertiesBuffer = new ArrayList(); MESSAGE_PROPERTIES_BUFFER.set(messagePropertiesBuffer); diff --git a/instrumentation/jms/src/main/java/brave/jms/TracingCompletionListener.java b/instrumentation/jms/src/main/java/brave/jms/TracingCompletionListener.java index e54281c5a2..fc84d068d8 100644 --- a/instrumentation/jms/src/main/java/brave/jms/TracingCompletionListener.java +++ b/instrumentation/jms/src/main/java/brave/jms/TracingCompletionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,9 +14,11 @@ package brave.jms; import brave.Span; +import brave.internal.Nullable; import brave.propagation.CurrentTraceContext; import brave.propagation.CurrentTraceContext.Scope; import javax.jms.CompletionListener; +import javax.jms.Destination; import javax.jms.Message; /** @@ -24,35 +26,38 @@ * for send and actual send. */ @JMS2_0 final class TracingCompletionListener implements CompletionListener { - static CompletionListener create(CompletionListener delegate, Span span, - CurrentTraceContext current) { - return new TracingCompletionListener(delegate, span, current); + static CompletionListener create(CompletionListener delegate, + @Nullable Destination destination, Span span, CurrentTraceContext current) { + return new TracingCompletionListener(delegate, destination, span, current); } final CompletionListener delegate; final CurrentTraceContext current; + @Nullable final Destination destination; final Span span; - TracingCompletionListener(CompletionListener delegate, Span span, CurrentTraceContext current) { + TracingCompletionListener(CompletionListener delegate, Destination destination, Span span, + CurrentTraceContext current) { this.delegate = delegate; + this.destination = destination; this.span = span; this.current = current; } @Override public void onCompletion(Message message) { - Scope scope = current.maybeScope(span.context()); + Scope ws = current.maybeScope(span.context()); try { delegate.onCompletion(message); } finally { // TODO: in order to tag messageId // parse(new MessageConsumerRequest(message, destination)) span.finish(); - scope.close(); + ws.close(); } } @Override public void onException(Message message, Exception exception) { - Scope scope = current.maybeScope(span.context()); + Scope ws = current.maybeScope(span.context()); try { // TODO: in order to tag messageId // parse(new MessageConsumerRequest(message, destination)) @@ -60,7 +65,7 @@ static CompletionListener create(CompletionListener delegate, Span span, } finally { span.error(exception); span.finish(); - scope.close(); + ws.close(); } } } diff --git a/instrumentation/jms/src/main/java/brave/jms/TracingExceptionListener.java b/instrumentation/jms/src/main/java/brave/jms/TracingExceptionListener.java index 29b71f14c3..cd57a71ca0 100644 --- a/instrumentation/jms/src/main/java/brave/jms/TracingExceptionListener.java +++ b/instrumentation/jms/src/main/java/brave/jms/TracingExceptionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -57,12 +57,12 @@ static final class DelegateAndTagError extends TagError { delegate.onException(exception); return; } - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); try { delegate.onException(exception); } finally { span.error(exception); - scope.close(); + ws.close(); } } } diff --git a/instrumentation/jms/src/main/java/brave/jms/TracingJMSProducer.java b/instrumentation/jms/src/main/java/brave/jms/TracingJMSProducer.java index 030bfa62ee..53d30d00e0 100644 --- a/instrumentation/jms/src/main/java/brave/jms/TracingJMSProducer.java +++ b/instrumentation/jms/src/main/java/brave/jms/TracingJMSProducer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,6 +14,8 @@ package brave.jms; import brave.Span; +import brave.Tracer.SpanInScope; +import brave.propagation.CurrentTraceContext; import brave.propagation.CurrentTraceContext.Scope; import java.io.Serializable; import java.util.Map; @@ -97,10 +99,10 @@ enum Send { void send(Send send, Destination destination, Object message) { Span span = createAndStartProducerSpan(new JMSProducerRequest(delegate, destination)); - Scope scope = current.newScope(span.context()); + Scope ws = current.newScope(span.context()); final CompletionListener async = getAsync(); if (async != null) { - delegate.setAsync(TracingCompletionListener.create(async, span, current)); + delegate.setAsync(TracingCompletionListener.create(async, destination, span, current)); } Throwable error = null; try { @@ -120,7 +122,7 @@ void send(Send send, Destination destination, Object message) { } else { span.finish(); // handle success synchronous send } - scope.close(); + ws.close(); } } diff --git a/instrumentation/jms/src/main/java/brave/jms/TracingMessageListener.java b/instrumentation/jms/src/main/java/brave/jms/TracingMessageListener.java index 09cc4ecaab..7c68f20312 100644 --- a/instrumentation/jms/src/main/java/brave/jms/TracingMessageListener.java +++ b/instrumentation/jms/src/main/java/brave/jms/TracingMessageListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -19,6 +19,7 @@ import brave.Tracing; import brave.messaging.MessagingRequest; import brave.propagation.TraceContext.Extractor; +import brave.propagation.TraceContext.Injector; import brave.propagation.TraceContextOrSamplingFlags; import brave.sampler.SamplerFunction; import javax.jms.Message; @@ -50,6 +51,7 @@ static MessageListener create(MessageListener delegate, JmsTracing jmsTracing) { final Tracing tracing; final Tracer tracer; final Extractor extractor; + final Injector injector; final SamplerFunction sampler; final String remoteServiceName; final boolean addConsumerSpan; @@ -61,13 +63,14 @@ static MessageListener create(MessageListener delegate, JmsTracing jmsTracing) { this.tracer = jmsTracing.tracer; this.extractor = jmsTracing.messageConsumerExtractor; this.sampler = jmsTracing.consumerSampler; + this.injector = jmsTracing.messageConsumerInjector; this.remoteServiceName = jmsTracing.remoteServiceName; this.addConsumerSpan = addConsumerSpan; } @Override public void onMessage(Message message) { Span listenerSpan = startMessageListenerSpan(message); - SpanInScope scope = tracer.withSpanInScope(listenerSpan); + SpanInScope ws = tracer.withSpanInScope(listenerSpan); Throwable error = null; try { delegate.onMessage(message); @@ -81,7 +84,7 @@ static MessageListener create(MessageListener delegate, JmsTracing jmsTracing) { } finally { if (error != null) listenerSpan.error(error); listenerSpan.finish(); - scope.close(); + ws.close(); } } diff --git a/instrumentation/jms/src/main/java/brave/jms/TracingMessageProducer.java b/instrumentation/jms/src/main/java/brave/jms/TracingMessageProducer.java index 65103ca2da..5bb0fc116a 100644 --- a/instrumentation/jms/src/main/java/brave/jms/TracingMessageProducer.java +++ b/instrumentation/jms/src/main/java/brave/jms/TracingMessageProducer.java @@ -116,7 +116,7 @@ Span createAndStartProducerSpan(Message message, Destination destination) { @Override public void send(Message message) throws JMSException { Span span = createAndStartProducerSpan(message, destination(message)); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { delegate.send(message); @@ -133,14 +133,14 @@ Span createAndStartProducerSpan(Message message, Destination destination) { } finally { if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } @Override public void send(Message message, int deliveryMode, int priority, long timeToLive) throws JMSException { Span span = createAndStartProducerSpan(message, destination(message)); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { delegate.send(message, deliveryMode, priority, timeToLive); @@ -157,7 +157,7 @@ Span createAndStartProducerSpan(Message message, Destination destination) { } finally { if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } @@ -192,7 +192,7 @@ abstract void apply(MessageProducer producer, Destination destination, Message m void send(SendDestination sendDestination, Destination destination, Message message) throws JMSException { Span span = createAndStartProducerSpan(message, destination); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { sendDestination.apply(delegate, destination, message); @@ -209,7 +209,7 @@ void send(SendDestination sendDestination, Destination destination, Message mess } finally { if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } @@ -217,7 +217,7 @@ void send(SendDestination sendDestination, Destination destination, Message mess public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive) throws JMSException { Span span = createAndStartProducerSpan(message, destination); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { delegate.send(destination, message, deliveryMode, priority, timeToLive); @@ -234,7 +234,7 @@ public void send(Destination destination, Message message, int deliveryMode, int } finally { if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } @@ -243,10 +243,10 @@ public void send(Destination destination, Message message, int deliveryMode, int public void send(Message message, CompletionListener completionListener) throws JMSException { Destination destination = destination(message); Span span = createAndStartProducerSpan(message, destination); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { - delegate.send(message, TracingCompletionListener.create(completionListener, span, current)); + delegate.send(message, TracingCompletionListener.create(completionListener, destination, span, current)); } catch (RuntimeException e) { error = e; throw e; @@ -259,7 +259,7 @@ public void send(Message message, CompletionListener completionListener) throws throw e; } finally { if (error != null) span.error(error).finish(); - scope.close(); + ws.close(); } } @@ -268,8 +268,8 @@ public void send(Message message, CompletionListener completionListener) throws CompletionListener completionListener) throws JMSException { Destination destination = destination(message); Span span = createAndStartProducerSpan(message, destination); - completionListener = TracingCompletionListener.create(completionListener, span, current); - SpanInScope scope = tracer.withSpanInScope(span); + completionListener = TracingCompletionListener.create(completionListener, destination, span, current); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { delegate.send(message, deliveryMode, priority, timeToLive, completionListener); @@ -285,7 +285,7 @@ public void send(Message message, CompletionListener completionListener) throws throw e; } finally { if (error != null) span.error(error).finish(); - scope.close(); + ws.close(); } } @@ -293,8 +293,8 @@ public void send(Message message, CompletionListener completionListener) throws @JMS2_0 public void send(Destination destination, Message message, CompletionListener completionListener) throws JMSException { Span span = createAndStartProducerSpan(message, destination); - completionListener = TracingCompletionListener.create(completionListener, span, current); - SpanInScope scope = tracer.withSpanInScope(span); + completionListener = TracingCompletionListener.create(completionListener, destination, span, current); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { delegate.send(destination, message, completionListener); @@ -310,7 +310,7 @@ public void send(Message message, CompletionListener completionListener) throws throw e; } finally { if (error != null) span.error(error).finish(); - scope.close(); + ws.close(); } } @@ -318,8 +318,8 @@ public void send(Message message, CompletionListener completionListener) throws @JMS2_0 public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, CompletionListener completionListener) throws JMSException { Span span = createAndStartProducerSpan(message, destination); - completionListener = TracingCompletionListener.create(completionListener, span, current); - SpanInScope scope = tracer.withSpanInScope(span); + completionListener = TracingCompletionListener.create(completionListener, destination, span, current); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { delegate.send(destination, message, deliveryMode, priority, timeToLive, completionListener); @@ -335,7 +335,7 @@ public void send(Message message, CompletionListener completionListener) throws throw e; } finally { if (error != null) span.error(error).finish(); - scope.close(); + ws.close(); } } @@ -357,7 +357,7 @@ public void send(Queue queue, Message message, int deliveryMode, int priority, l checkQueueSender(); QueueSender qs = (QueueSender) delegate; Span span = createAndStartProducerSpan(message, destination(message)); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { qs.send(queue, message, deliveryMode, priority, timeToLive); @@ -374,7 +374,7 @@ public void send(Queue queue, Message message, int deliveryMode, int priority, l } finally { if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } @@ -396,7 +396,7 @@ void checkQueueSender() { TopicPublisher tp = (TopicPublisher) delegate; Span span = createAndStartProducerSpan(message, destination(message)); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { tp.publish(message); @@ -413,7 +413,7 @@ void checkQueueSender() { } finally { if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } @@ -423,7 +423,7 @@ void checkQueueSender() { TopicPublisher tp = (TopicPublisher) delegate; Span span = createAndStartProducerSpan(message, destination(message)); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { tp.publish(message, deliveryMode, priority, timeToLive); @@ -440,7 +440,7 @@ void checkQueueSender() { } finally { if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } @@ -456,7 +456,7 @@ public void publish(Topic topic, Message message, int deliveryMode, int priority TopicPublisher tp = (TopicPublisher) delegate; Span span = createAndStartProducerSpan(message, destination(message)); - SpanInScope scope = tracer.withSpanInScope(span); + SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { tp.publish(topic, message, deliveryMode, priority, timeToLive); @@ -473,7 +473,7 @@ public void publish(Topic topic, Message message, int deliveryMode, int priority } finally { if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } diff --git a/instrumentation/jms/src/test/java/brave/jms/ITJms_1_1_TracingMessageConsumer.java b/instrumentation/jms/src/test/java/brave/jms/ITJms_1_1_TracingMessageConsumer.java index 200a63b8f0..ed9bc7176d 100644 --- a/instrumentation/jms/src/test/java/brave/jms/ITJms_1_1_TracingMessageConsumer.java +++ b/instrumentation/jms/src/test/java/brave/jms/ITJms_1_1_TracingMessageConsumer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -21,8 +21,10 @@ import brave.propagation.SamplingFlags; import brave.propagation.TraceContext; import brave.sampler.Sampler; +import java.lang.reflect.Method; import java.util.Collections; import java.util.Map; +import java.util.Optional; import javax.jms.BytesMessage; import javax.jms.JMSException; import javax.jms.Message; @@ -39,6 +41,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.extension.RegisterExtension; import static brave.Span.Kind.CONSUMER; diff --git a/instrumentation/jms/src/test/java/brave/jms/ITJms_1_1_TracingMessageProducer.java b/instrumentation/jms/src/test/java/brave/jms/ITJms_1_1_TracingMessageProducer.java index 1af1cec425..01b9f9c7f7 100644 --- a/instrumentation/jms/src/test/java/brave/jms/ITJms_1_1_TracingMessageProducer.java +++ b/instrumentation/jms/src/test/java/brave/jms/ITJms_1_1_TracingMessageProducer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -20,8 +20,10 @@ import brave.propagation.SamplingFlags; import brave.propagation.TraceContext; import brave.sampler.Sampler; +import java.lang.reflect.Method; import java.util.Collections; import java.util.Map; +import java.util.Optional; import javax.jms.BytesMessage; import javax.jms.JMSException; import javax.jms.Message; @@ -38,6 +40,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.extension.RegisterExtension; import static brave.Span.Kind.PRODUCER; diff --git a/instrumentation/jms/src/test/java/brave/jms/ITJms_2_0_TracingMessageConsumer.java b/instrumentation/jms/src/test/java/brave/jms/ITJms_2_0_TracingMessageConsumer.java index 25d4c213a2..1450309161 100644 --- a/instrumentation/jms/src/test/java/brave/jms/ITJms_2_0_TracingMessageConsumer.java +++ b/instrumentation/jms/src/test/java/brave/jms/ITJms_2_0_TracingMessageConsumer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -16,10 +16,14 @@ import brave.messaging.MessagingRuleSampler; import brave.messaging.MessagingTracing; import brave.sampler.Sampler; +import java.lang.reflect.Method; +import java.util.Optional; import javax.jms.JMSConsumer; import javax.jms.JMSContext; import javax.jms.JMSException; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; import static brave.messaging.MessagingRequestMatchers.channelNameEquals; import static org.assertj.core.api.Assertions.assertThat; diff --git a/instrumentation/jms/src/test/java/brave/jms/ITJms_2_0_TracingMessageProducer.java b/instrumentation/jms/src/test/java/brave/jms/ITJms_2_0_TracingMessageProducer.java index 3a8482b5c1..2fea72e3c2 100644 --- a/instrumentation/jms/src/test/java/brave/jms/ITJms_2_0_TracingMessageProducer.java +++ b/instrumentation/jms/src/test/java/brave/jms/ITJms_2_0_TracingMessageProducer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -17,14 +17,18 @@ import brave.messaging.MessagingRuleSampler; import brave.messaging.MessagingTracing; import brave.sampler.Sampler; +import java.lang.reflect.Method; +import java.util.Optional; import java.util.concurrent.CountDownLatch; import javax.jms.CompletionListener; import javax.jms.JMSContext; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageProducer; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; import static brave.messaging.MessagingRequestMatchers.channelNameEquals; import static org.assertj.core.api.Assertions.assertThat; diff --git a/instrumentation/jms/src/test/java/brave/jms/ITTracingJMSConsumer.java b/instrumentation/jms/src/test/java/brave/jms/ITTracingJMSConsumer.java index 6c257cb1bc..21a66ad1fc 100644 --- a/instrumentation/jms/src/test/java/brave/jms/ITTracingJMSConsumer.java +++ b/instrumentation/jms/src/test/java/brave/jms/ITTracingJMSConsumer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -20,6 +20,8 @@ import brave.propagation.SamplingFlags; import brave.propagation.TraceContext; import brave.sampler.Sampler; +import java.lang.reflect.Method; +import java.util.Optional; import javax.jms.JMSConsumer; import javax.jms.JMSContext; import javax.jms.JMSProducer; @@ -27,6 +29,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.extension.RegisterExtension; import static brave.Span.Kind.CONSUMER; diff --git a/instrumentation/jms/src/test/java/brave/jms/ITTracingJMSProducer.java b/instrumentation/jms/src/test/java/brave/jms/ITTracingJMSProducer.java index b09046d6c4..318c5a774c 100644 --- a/instrumentation/jms/src/test/java/brave/jms/ITTracingJMSProducer.java +++ b/instrumentation/jms/src/test/java/brave/jms/ITTracingJMSProducer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -20,8 +20,10 @@ import brave.propagation.SamplingFlags; import brave.propagation.TraceContext; import brave.sampler.Sampler; +import java.lang.reflect.Method; import java.util.Collections; import java.util.Map; +import java.util.Optional; import javax.jms.CompletionListener; import javax.jms.JMSConsumer; import javax.jms.JMSContext; @@ -32,6 +34,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.extension.RegisterExtension; import static brave.Span.Kind.PRODUCER; diff --git a/instrumentation/jms/src/test/java/brave/jms/JmsTracingTest.java b/instrumentation/jms/src/test/java/brave/jms/JmsTracingTest.java index bdf52bab8d..bb729af94f 100644 --- a/instrumentation/jms/src/test/java/brave/jms/JmsTracingTest.java +++ b/instrumentation/jms/src/test/java/brave/jms/JmsTracingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -197,7 +197,7 @@ abstract class Both implements XATopicConnection, TopicConnection { setStringProperty(message, "b3", B3SingleFormat.writeB3SingleFormat(incoming)); Span child; - try (Scope scope = tracing.currentTraceContext().newScope(parent)) { + try (Scope ws = tracing.currentTraceContext().newScope(parent)) { child = jmsTracing.nextSpan(message); } assertChildOf(child.context(), incoming); @@ -206,7 +206,7 @@ abstract class Both implements XATopicConnection, TopicConnection { @Test void nextSpan_uses_current_context() { Span child; - try (Scope scope = tracing.currentTraceContext().newScope(parent)) { + try (Scope ws = tracing.currentTraceContext().newScope(parent)) { child = jmsTracing.nextSpan(message); } assertChildOf(child.context(), parent); @@ -247,6 +247,7 @@ abstract class Both implements XATopicConnection, TopicConnection { @Test void nextSpan_should_clear_propagation_headers() { Propagation.B3_STRING.injector(SETTER).inject(parent, message); + Propagation.B3_SINGLE_STRING.injector(SETTER).inject(parent, message); jmsTracing.nextSpan(message); assertThat(ITJms.propertiesToMap(message)).isEmpty(); diff --git a/instrumentation/jms/src/test/java/brave/jms/TracingCompletionListenerTest.java b/instrumentation/jms/src/test/java/brave/jms/TracingCompletionListenerTest.java index 8b8d3a088e..6ed7ef6355 100644 --- a/instrumentation/jms/src/test/java/brave/jms/TracingCompletionListenerTest.java +++ b/instrumentation/jms/src/test/java/brave/jms/TracingCompletionListenerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -16,6 +16,7 @@ import brave.Span; import brave.propagation.TraceContextOrSamplingFlags; import javax.jms.CompletionListener; +import javax.jms.Destination; import javax.jms.Message; import org.junit.jupiter.api.Test; @@ -25,6 +26,7 @@ public class TracingCompletionListenerTest extends ITJms { Message message = mock(Message.class); + Destination destination = mock(Destination.class); @Test void onCompletion_shouldKeepContext_whenNotSampled() { Span span = tracing.tracer().nextSpan(TraceContextOrSamplingFlags.NOT_SAMPLED); @@ -38,7 +40,7 @@ public class TracingCompletionListenerTest extends ITJms { } }; CompletionListener tracingCompletionListener = - TracingCompletionListener.create(delegate, span, currentTraceContext); + TracingCompletionListener.create(delegate, destination, span, currentTraceContext); tracingCompletionListener.onCompletion(null); @@ -49,7 +51,7 @@ public class TracingCompletionListenerTest extends ITJms { Span span = tracing.tracer().nextSpan().start(); CompletionListener tracingCompletionListener = - TracingCompletionListener.create(mock(CompletionListener.class), span, currentTraceContext); + TracingCompletionListener.create(mock(CompletionListener.class), destination, span, currentTraceContext); tracingCompletionListener.onCompletion(message); testSpanHandler.takeLocalSpan(); @@ -61,7 +63,7 @@ public class TracingCompletionListenerTest extends ITJms { RuntimeException error = new RuntimeException("Test exception"); CompletionListener tracingCompletionListener = - TracingCompletionListener.create(mock(CompletionListener.class), span, currentTraceContext); + TracingCompletionListener.create(mock(CompletionListener.class), destination, span, currentTraceContext); tracingCompletionListener.onException(message, error); assertThat(testSpanHandler.takeLocalSpan().error()).isEqualTo(error); @@ -72,7 +74,7 @@ public class TracingCompletionListenerTest extends ITJms { CompletionListener delegate = mock(CompletionListener.class); CompletionListener tracingCompletionListener = - TracingCompletionListener.create(delegate, span, currentTraceContext); + TracingCompletionListener.create(delegate, destination, span, currentTraceContext); tracingCompletionListener.onCompletion(message); verify(delegate).onCompletion(message); @@ -93,7 +95,7 @@ public class TracingCompletionListenerTest extends ITJms { } }; - TracingCompletionListener.create(delegate, span, currentTraceContext).onCompletion(message); + TracingCompletionListener.create(delegate, destination, span, currentTraceContext).onCompletion(message); testSpanHandler.takeLocalSpan(); } @@ -103,7 +105,7 @@ public class TracingCompletionListenerTest extends ITJms { CompletionListener delegate = mock(CompletionListener.class); CompletionListener tracingCompletionListener = - TracingCompletionListener.create(delegate, span, currentTraceContext); + TracingCompletionListener.create(delegate, destination, span, currentTraceContext); RuntimeException error = new RuntimeException("Test exception"); tracingCompletionListener.onException(message, error); diff --git a/instrumentation/jms/src/test/java/brave/jms/TracingJMSConsumerTest.java b/instrumentation/jms/src/test/java/brave/jms/TracingJMSConsumerTest.java index f52a892e34..4db0649424 100644 --- a/instrumentation/jms/src/test/java/brave/jms/TracingJMSConsumerTest.java +++ b/instrumentation/jms/src/test/java/brave/jms/TracingJMSConsumerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -18,6 +18,7 @@ import brave.propagation.B3SingleFormat; import brave.propagation.SamplingFlags; import brave.propagation.TraceContext; +import java.io.IOException; import javax.jms.JMSConsumer; import javax.jms.Message; import org.apache.activemq.command.ActiveMQTextMessage; @@ -77,8 +78,16 @@ public class TracingJMSConsumerTest extends ITJms { testSpanHandler.takeRemoteSpan(CONSUMER); } - void receive(Message message) { + void receive(Message message) throws Exception { when(delegate.receive()).thenReturn(message); tracingJMSConsumer.receive(); } + + void assertNoProperties(ActiveMQTextMessage message) { + try { + assertThat(message.getProperties()).isEmpty(); + } catch (IOException e) { + throw new AssertionError(e); + } + } } diff --git a/instrumentation/kafka-clients/README.md b/instrumentation/kafka-clients/README.md index 40ba7403ec..af398dcbd1 100644 --- a/instrumentation/kafka-clients/README.md +++ b/instrumentation/kafka-clients/README.md @@ -96,7 +96,7 @@ to trace manually or you can do similar via automatic instrumentation like Aspec Span span = kafkaTracing.nextSpan(record).name("process").start(); // Below is the same setup as any synchronous tracing - try (Tracer.SpanInScope scope = tracer.withSpanInScope(span)) { // so logging can see trace ID + try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) { // so logging can see trace ID return doProcess(record); // do the actual work } catch (RuntimeException | Error e) { span.error(e); // make sure any error gets into the span before it is finished diff --git a/instrumentation/kafka-clients/src/main/java/brave/kafka/clients/KafkaTracing.java b/instrumentation/kafka-clients/src/main/java/brave/kafka/clients/KafkaTracing.java index f40607c93e..db89b810d0 100644 --- a/instrumentation/kafka-clients/src/main/java/brave/kafka/clients/KafkaTracing.java +++ b/instrumentation/kafka-clients/src/main/java/brave/kafka/clients/KafkaTracing.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -20,6 +20,7 @@ import brave.internal.Nullable; import brave.messaging.MessagingRequest; import brave.messaging.MessagingTracing; +import brave.propagation.B3Propagation; import brave.propagation.Propagation; import brave.propagation.Propagation.Getter; import brave.propagation.TraceContext.Extractor; @@ -32,6 +33,7 @@ import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; + import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.producer.Producer; @@ -120,6 +122,14 @@ public Builder singleRootSpanOnReceiveBatch(boolean singleRootSpanOnReceiveBatch return this; } + /** + * @deprecated as of v5.9, this is ignored because single format is default for messaging. Use + * {@link B3Propagation#newFactoryBuilder()} to change the default. + */ + @Deprecated public Builder writeB3SingleFormat(boolean writeB3SingleFormat) { + return this; + } + public KafkaTracing build() { return new KafkaTracing(this); } @@ -254,7 +264,7 @@ static void addTags(ConsumerRecord record, SpanCustomizer result) { * try { * tracePropagationThatMayThrow(record); * } catch (Throwable e) { - * Throwables.propagateIfFatal(e); + * Call.propagateIfFatal(e); * log(e, "error adding propagation information to {0}", record, null); * return null; * } diff --git a/instrumentation/kafka-clients/src/main/java/brave/kafka/clients/TracingCallback.java b/instrumentation/kafka-clients/src/main/java/brave/kafka/clients/TracingCallback.java index 0a1099f92c..f300044730 100644 --- a/instrumentation/kafka-clients/src/main/java/brave/kafka/clients/TracingCallback.java +++ b/instrumentation/kafka-clients/src/main/java/brave/kafka/clients/TracingCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2019 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -54,7 +54,7 @@ static final class DelegateAndFinishSpan extends FinishSpan { } @Override public void onCompletion(RecordMetadata metadata, @Nullable Exception exception) { - try (Scope scope = current.maybeScope(span.context())) { + try (Scope ws = current.maybeScope(span.context())) { delegate.onCompletion(metadata, exception); } finally { super.onCompletion(metadata, exception); diff --git a/instrumentation/kafka-clients/src/main/java/brave/kafka/clients/TracingProducer.java b/instrumentation/kafka-clients/src/main/java/brave/kafka/clients/TracingProducer.java index 82a38ea032..6c554a08b2 100644 --- a/instrumentation/kafka-clients/src/main/java/brave/kafka/clients/TracingProducer.java +++ b/instrumentation/kafka-clients/src/main/java/brave/kafka/clients/TracingProducer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2022 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -123,7 +123,7 @@ public Future send(ProducerRecord record, @Nullable Callba injector.inject(span.context(), request); - Tracer.SpanInScope scope = tracer.withSpanInScope(span); + Tracer.SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { return delegate.send(record, TracingCallback.create(callback, span, currentTraceContext)); @@ -133,7 +133,7 @@ public Future send(ProducerRecord record, @Nullable Callba } finally { // finish as an exception means the callback won't finish the span if (error != null) span.error(error).finish(); - scope.close(); + ws.close(); } } diff --git a/instrumentation/kafka-clients/src/test/java/brave/kafka/clients/KafkaTracingTest.java b/instrumentation/kafka-clients/src/test/java/brave/kafka/clients/KafkaTracingTest.java index d21249d259..6ce1d476da 100644 --- a/instrumentation/kafka-clients/src/test/java/brave/kafka/clients/KafkaTracingTest.java +++ b/instrumentation/kafka-clients/src/test/java/brave/kafka/clients/KafkaTracingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -29,7 +29,7 @@ public class KafkaTracingTest extends KafkaTest { consumerRecord.headers().add("b3", B3SingleFormat.writeB3SingleFormatAsBytes(incoming)); Span child; - try (Scope scope = tracing.currentTraceContext().newScope(parent)) { + try (Scope ws = tracing.currentTraceContext().newScope(parent)) { child = kafkaTracing.nextSpan(consumerRecord); } child.finish(); @@ -40,7 +40,7 @@ public class KafkaTracingTest extends KafkaTest { @Test void nextSpan_uses_current_context() { Span child; - try (Scope scope = tracing.currentTraceContext().newScope(parent)) { + try (Scope ws = tracing.currentTraceContext().newScope(parent)) { child = kafkaTracing.nextSpan(consumerRecord); } child.finish(); diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/AbstractTracingTransformer.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/AbstractTracingTransformer.java new file mode 100644 index 0000000000..f6aa8c0726 --- /dev/null +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/AbstractTracingTransformer.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.kafka.streams; + +import org.apache.kafka.streams.kstream.Transformer; +import org.apache.kafka.streams.processor.ProcessorContext; + +abstract class AbstractTracingTransformer implements + Transformer { + + @Override public void init(ProcessorContext context) { + } + + @Override public void close() { + } +} diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/AbstractTracingValueTransformer.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/AbstractTracingValueTransformer.java new file mode 100644 index 0000000000..ee24e48b5d --- /dev/null +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/AbstractTracingValueTransformer.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.kafka.streams; + +import org.apache.kafka.streams.kstream.ValueTransformer; +import org.apache.kafka.streams.processor.ProcessorContext; + +abstract class AbstractTracingValueTransformer implements + ValueTransformer { + + @Override public void init(ProcessorContext context) { + } + + @Override public void close() { + } +} diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/AbstractTracingValueTransformerWithKey.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/AbstractTracingValueTransformerWithKey.java new file mode 100644 index 0000000000..73e6eed2a2 --- /dev/null +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/AbstractTracingValueTransformerWithKey.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.kafka.streams; + +import org.apache.kafka.streams.kstream.ValueTransformerWithKey; +import org.apache.kafka.streams.processor.ProcessorContext; + +abstract class AbstractTracingValueTransformerWithKey implements + ValueTransformerWithKey { + + @Override public void init(ProcessorContext context) { + } + + @Override public void close() { + } +} diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/KafkaStreamsTags.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/KafkaStreamsTags.java index 13e81c7836..420c0d51de 100644 --- a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/KafkaStreamsTags.java +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/KafkaStreamsTags.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2019 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,6 +13,7 @@ */ package brave.kafka.streams; +import org.apache.kafka.streams.kstream.Predicate; import org.apache.kafka.streams.processor.ProcessorContext; /** @@ -25,4 +26,10 @@ class KafkaStreamsTags { */ static final String KAFKA_STREAMS_APPLICATION_ID_TAG = "kafka.streams.application.id"; static final String KAFKA_STREAMS_TASK_ID_TAG = "kafka.streams.task.id"; + /** + * Added on {@link KafkaStreamsTracing#nextSpan(ProcessorContext)} by the {@link + * KafkaStreamsTracing#filter(String, Predicate)} transformer. The tag value is true + * when the message is filtered out, false otherwise. + */ + static final String KAFKA_STREAMS_FILTERED_TAG = "kafka.streams.filtered"; } diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/KafkaStreamsTracing.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/KafkaStreamsTracing.java index 8afa696f52..6e14987d0c 100644 --- a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/KafkaStreamsTracing.java +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/KafkaStreamsTracing.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -31,8 +31,21 @@ import org.apache.kafka.common.header.Headers; import org.apache.kafka.streams.KafkaClientSupplier; import org.apache.kafka.streams.KafkaStreams; +import org.apache.kafka.streams.KeyValue; import org.apache.kafka.streams.Topology; +import org.apache.kafka.streams.kstream.ForeachAction; +import org.apache.kafka.streams.kstream.KStream; +import org.apache.kafka.streams.kstream.KeyValueMapper; +import org.apache.kafka.streams.kstream.Predicate; +import org.apache.kafka.streams.kstream.TransformerSupplier; +import org.apache.kafka.streams.kstream.ValueMapper; +import org.apache.kafka.streams.kstream.ValueMapperWithKey; +import org.apache.kafka.streams.kstream.ValueTransformerSupplier; +import org.apache.kafka.streams.kstream.ValueTransformerWithKey; +import org.apache.kafka.streams.kstream.ValueTransformerWithKeySupplier; +import org.apache.kafka.streams.processor.AbstractProcessor; import org.apache.kafka.streams.processor.ProcessorContext; +import org.apache.kafka.streams.processor.ProcessorSupplier; import org.apache.kafka.streams.processor.api.ProcessingContext; /** Use this class to decorate Kafka Stream Topologies and enable Tracing. */ @@ -112,6 +125,372 @@ public KafkaStreams kafkaStreams(Topology topology, Properties streamsConfig) { return new KafkaStreams(topology, streamsConfig, kafkaClientSupplier()); } + /** + * Create a tracing-decorated {@link ProcessorSupplier} + * + *

Simple example using Kafka Streams DSL: + *

{@code
+   * StreamsBuilder builder = new StreamsBuilder();
+   * builder.stream(inputTopic)
+   *        .process(kafkaStreamsTracing.processor("my-processor", myProcessorSupplier);
+   * }
+ * + * @see TracingKafkaClientSupplier + * @deprecated Use {@link KafkaStreamsTracing#process(String, org.apache.kafka.streams.processor.api.ProcessorSupplier)} or + * {@link KafkaStreamsTracing#processValues(String, org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier)} instead. + */ + @Deprecated + public ProcessorSupplier processor(String spanName, + ProcessorSupplier processorSupplier) { + return new TracingProcessorSupplier<>(this, spanName, processorSupplier); + } + + /** + * Create a tracing-decorated {@link TransformerSupplier} + * + *

Simple example using Kafka Streams DSL: + *

{@code
+   * StreamsBuilder builder = new StreamsBuilder();
+   * builder.stream(inputTopic)
+   *        .transform(kafkaStreamsTracing.transformer("my-transformer", myTransformerSupplier)
+   *        .to(outputTopic);
+   * }
+ * @deprecated Use {@link KafkaStreamsTracing#process(String, org.apache.kafka.streams.processor.api.ProcessorSupplier)} or + * {@link KafkaStreamsTracing#processValues(String, org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier)} instead. + */ + @Deprecated + public TransformerSupplier transformer(String spanName, + TransformerSupplier transformerSupplier) { + return new TracingTransformerSupplier<>(this, spanName, transformerSupplier); + } + + /** + * Create a tracing-decorated {@link ValueTransformerSupplier} + * + *

Simple example using Kafka Streams DSL: + *

{@code
+   * StreamsBuilder builder = new StreamsBuilder();
+   * builder.stream(inputTopic)
+   *        .transformValues(kafkaStreamsTracing.valueTransformer("my-transformer", myTransformerSupplier)
+   *        .to(outputTopic);
+   * }
+ * @deprecated Use {@link KafkaStreamsTracing#process(String, org.apache.kafka.streams.processor.api.ProcessorSupplier)} or + * {@link KafkaStreamsTracing#processValues(String, org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier)} instead. + */ + @Deprecated + public ValueTransformerSupplier valueTransformer(String spanName, + ValueTransformerSupplier valueTransformerSupplier) { + return new TracingValueTransformerSupplier<>(this, spanName, valueTransformerSupplier); + } + + /** + * Create a tracing-decorated {@link ValueTransformerWithKeySupplier} + * + *

Simple example using Kafka Streams DSL: + *

{@code
+   * StreamsBuilder builder = new StreamsBuilder();
+   * builder.stream(inputTopic)
+   *        .transformValues(kafkaStreamsTracing.valueTransformerWithKey("my-transformer", myTransformerSupplier)
+   *        .to(outputTopic);
+   * }
+ * @deprecated Use {@link KafkaStreamsTracing#process(String, org.apache.kafka.streams.processor.api.ProcessorSupplier)} or + * {@link KafkaStreamsTracing#processValues(String, org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier)} instead. + */ + @Deprecated + public ValueTransformerWithKeySupplier valueTransformerWithKey( + String spanName, + ValueTransformerWithKeySupplier valueTransformerWithKeySupplier) { + return new TracingValueTransformerWithKeySupplier<>(this, spanName, + valueTransformerWithKeySupplier); + } + + /** + * Create a foreach processor, similar to {@link KStream#foreach(ForeachAction)}, where its action + * will be recorded in a new span with the indicated name. + * + *

Simple example using Kafka Streams DSL: + *

{@code
+   * StreamsBuilder builder = new StreamsBuilder();
+   * builder.stream(inputTopic)
+   *        .process(kafkaStreamsTracing.foreach("myForeach", (k, v) -> ...);
+   * }
+ * @deprecated Use {@link KafkaStreamsTracing#process(String, org.apache.kafka.streams.processor.api.ProcessorSupplier)} or + * {@link KafkaStreamsTracing#processValues(String, org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier)} instead. + */ + @Deprecated + public ProcessorSupplier foreach(String spanName, ForeachAction action) { + return new TracingProcessorSupplier<>(this, spanName, () -> + new AbstractProcessor() { + @Override public void process(K key, V value) { + action.apply(key, value); + } + }); + } + + /** + * Create a peek transformer, similar to {@link KStream#peek(ForeachAction)}, where its action + * will be recorded in a new span with the indicated name. + * + *

Simple example using Kafka Streams DSL: + *

{@code
+   * StreamsBuilder builder = new StreamsBuilder();
+   * builder.stream(inputTopic)
+   *        .transformValues(kafkaStreamsTracing.peek("myPeek", (k, v) -> ...)
+   *        .to(outputTopic);
+   * }
+ * @deprecated Use {@link KafkaStreamsTracing#process(String, org.apache.kafka.streams.processor.api.ProcessorSupplier)} or + * {@link KafkaStreamsTracing#processValues(String, org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier)} instead. + */ + @Deprecated + public ValueTransformerWithKeySupplier peek(String spanName, + ForeachAction action) { + return new TracingValueTransformerWithKeySupplier<>(this, spanName, () -> + new AbstractTracingValueTransformerWithKey() { + @Override public V transform(K key, V value) { + action.apply(key, value); + return value; + } + }); + } + + /** + * Create a mark transformer, similar to {@link KStream#peek(ForeachAction)}, but no action is + * executed. Instead, only a span is created to represent an event as part of the stream process. + *

+ * A common scenario for this transformer is to mark the beginning and end of a step (or set of + * steps) in a stream process. + * + *

Simple example using Kafka Streams DSL: + *

{@code
+   * StreamsBuilder builder = new StreamsBuilder();
+   * builder.stream(inputTopic)
+   *        .transformValues(kafkaStreamsTracing.mark("beginning-complex-map")
+   *        .map(complexTransformation1)
+   *        .filter(predicate)
+   *        .mapValues(complexTransformation2)
+   *        .transform(kafkaStreamsTracing.mark("end-complex-transformation")
+   *        .to(outputTopic);
+   * }
+ * @deprecated Use {@link KafkaStreamsTracing#process(String, org.apache.kafka.streams.processor.api.ProcessorSupplier)} or + * {@link KafkaStreamsTracing#processValues(String, org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier)} instead. + */ + @Deprecated + public ValueTransformerWithKeySupplier mark(String spanName) { + return new TracingValueTransformerWithKeySupplier<>(this, spanName, () -> + new AbstractTracingValueTransformerWithKey() { + @Override public V transform(K key, V value) { + return value; + } + }); + } + + /** + * Create a map transformer, similar to {@link KStream#map(KeyValueMapper)}, where its mapper + * action will be recorded in a new span with the indicated name. + * + *

Simple example using Kafka Streams DSL: + *

{@code
+   * StreamsBuilder builder = new StreamsBuilder();
+   * builder.stream(inputTopic)
+   *        .transform(kafkaStreamsTracing.map("myMap", (k, v) -> ...)
+   *        .to(outputTopic);
+   * }
+ * @deprecated Use {@link KafkaStreamsTracing#process(String, org.apache.kafka.streams.processor.api.ProcessorSupplier)} or + * {@link KafkaStreamsTracing#processValues(String, org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier)} instead. + */ + @Deprecated + public TransformerSupplier> map(String spanName, + KeyValueMapper> mapper) { + return new TracingTransformerSupplier<>(this, spanName, () -> + new AbstractTracingTransformer>() { + @Override public KeyValue transform(K key, V value) { + return mapper.apply(key, value); + } + }); + } + + /** + * Create a flatMap transformer, similar to {@link KStream#flatMap(KeyValueMapper)}, where its + * mapper action will be recorded in a new span with the indicated name. + * + *

Simple example using Kafka Streams DSL: + *

{@code
+   * StreamsBuilder builder = new StreamsBuilder();
+   * builder.stream(inputTopic)
+   *        .flatTransform(kafkaStreamsTracing.flatMap("myflatMap", (k, v) -> ...)
+   *        .to(outputTopic);
+   * }
+ * @deprecated Use {@link KafkaStreamsTracing#process(String, org.apache.kafka.streams.processor.api.ProcessorSupplier)} or + * {@link KafkaStreamsTracing#processValues(String, org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier)} instead. + */ + @Deprecated + public TransformerSupplier>> flatMap( + String spanName, + KeyValueMapper>> mapper) { + return new TracingTransformerSupplier<>(this, spanName, () -> + new AbstractTracingTransformer>>() { + @Override public Iterable> transform(K key, V value) { + return mapper.apply(key, value); + } + }); + } + + /** + * Create a filter transformer. + *

+ * WARNING: this filter implementation uses the Streams transform API, meaning that + * re-partitioning can occur if a key modifying operation like grouping or joining operation is + * applied after this filter. + *

+ * In that case, consider using {@link #markAsFiltered(String, Predicate)} instead which uses + * {@link ValueTransformerWithKey} API instead. + * + *

Simple example using Kafka Streams DSL: + *

{@code
+   * StreamsBuilder builder = new StreamsBuilder();
+   * builder.stream(inputTopic)
+   *       .transform(kafkaStreamsTracing.filter("myFilter", (k, v) -> ...)
+   *       .to(outputTopic);
+   * }
+ * @deprecated Use {@link KafkaStreamsTracing#process(String, org.apache.kafka.streams.processor.api.ProcessorSupplier)} or + * {@link KafkaStreamsTracing#processValues(String, org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier)} instead. + */ + @Deprecated + public TransformerSupplier> filter(String spanName, + Predicate predicate) { + return new TracingFilterTransformerSupplier<>(this, spanName, predicate, false); + } + + /** + * Create a filterNot transformer. + *

+ * WARNING: this filter implementation uses the Streams transform API, meaning that + * re-partitioning can occur if a key modifying operation like grouping or joining operation is + * applied after this filter. In that case, consider using {@link #markAsNotFiltered(String, + * Predicate)} instead which uses {@link ValueTransformerWithKey} API instead. + * + *

Simple example using Kafka Streams DSL: + *

{@code
+   * StreamsBuilder builder = new StreamsBuilder();
+   * builder.stream(inputTopic)
+   *       .transform(kafkaStreamsTracing.filterNot("myFilter", (k, v) -> ...)
+   *       .to(outputTopic);
+   * }
+ * @deprecated Use {@link KafkaStreamsTracing#process(String, org.apache.kafka.streams.processor.api.ProcessorSupplier)} or + * {@link KafkaStreamsTracing#processValues(String, org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier)} instead. + */ + @Deprecated + public TransformerSupplier> filterNot(String spanName, + Predicate predicate) { + return new TracingFilterTransformerSupplier<>(this, spanName, predicate, true); + } + + /** + * Create a markAsFiltered valueTransformer. + *

+ * Instead of filtering, and not emitting values downstream as {@code filter} does; {@code + * markAsFiltered} creates a span, marking it as filtered or not. If filtered, value returned will + * be {@code null} and will require an additional non-null value filter to complete the + * filtering. + *

+ * This operation is offered as lack of a processor that allows to continue conditionally with the + * processing without risk of accidental re-partitioning. + * + *

Simple example using Kafka Streams DSL: + *

{@code
+   * StreamsBuilder builder = new StreamsBuilder();
+   * builder.stream(inputTopic)
+   *       .transformValues(kafkaStreamsTracing.markAsFiltered("myFilter", (k, v) -> ...)
+   *       .filterNot((k, v) -> Objects.isNull(v))
+   *       .to(outputTopic);
+   * }
+ * @deprecated Use {@link KafkaStreamsTracing#process(String, org.apache.kafka.streams.processor.api.ProcessorSupplier)} or + * {@link KafkaStreamsTracing#processValues(String, org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier)} instead. + */ + @Deprecated + public ValueTransformerWithKeySupplier markAsFiltered(String spanName, + Predicate predicate) { + return new TracingFilterValueTransformerWithKeySupplier<>(this, spanName, predicate, false); + } + + /** + * Create a markAsNotFiltered valueTransformer. + *

+ * Instead of filtering, and not emitting values downstream as {@code filterNot} does; {@code + * markAsNotFiltered} creates a span, marking it as filtered or not. If filtered, value returned + * will be {@code null} and will require an additional non-null value filter to complete the + * filtering. + *

+ * This operation is offered as lack of a processor that allows to continue conditionally with the + * processing without risk of accidental re-partitioning. + * + *

Simple example using Kafka Streams DSL: + *

{@code
+   * StreamsBuilder builder = new StreamsBuilder();
+   * builder.stream(inputTopic)
+   *       .transformValues(kafkaStreamsTracing.markAsNotFiltered("myFilter", (k, v) -> ...)
+   *       .filterNot((k, v) -> Objects.isNull(v))
+   *       .to(outputTopic);
+   * }
+ * @deprecated Use {@link KafkaStreamsTracing#process(String, org.apache.kafka.streams.processor.api.ProcessorSupplier)} or + * {@link KafkaStreamsTracing#processValues(String, org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier)} instead. + */ + @Deprecated + public ValueTransformerWithKeySupplier markAsNotFiltered(String spanName, + Predicate predicate) { + return new TracingFilterValueTransformerWithKeySupplier<>(this, spanName, predicate, true); + } + + /** + * Create a mapValues transformer, similar to {@link KStream#mapValues(ValueMapperWithKey)}, where + * its mapper action will be recorded in a new span with the indicated name. + * + *

Simple example using Kafka Streams DSL: + *

{@code
+   * StreamsBuilder builder = new StreamsBuilder();
+   * builder.stream(inputTopic)
+   *        .transformValues(kafkaStreamsTracing.mapValues("myMapValues", (k, v) -> ...)
+   *        .to(outputTopic);
+   * }
+ * @deprecated Use {@link KafkaStreamsTracing#process(String, org.apache.kafka.streams.processor.api.ProcessorSupplier)} or + * {@link KafkaStreamsTracing#processValues(String, org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier)} instead. + */ + @Deprecated + public ValueTransformerWithKeySupplier mapValues(String spanName, + ValueMapperWithKey mapper) { + return new TracingValueTransformerWithKeySupplier<>(this, spanName, () -> + new AbstractTracingValueTransformerWithKey() { + @Override public VR transform(K readOnlyKey, V value) { + return mapper.apply(readOnlyKey, value); + } + }); + } + + /** + * Create a mapValues transformer, similar to {@link KStream#mapValues(ValueMapper)}, where its + * mapper action will be recorded in a new span with the indicated name. + * + *

Simple example using Kafka Streams DSL: + *

{@code
+   * StreamsBuilder builder = new StreamsBuilder();
+   * builder.stream(inputTopic)
+   *        .transformValues(kafkaStreamsTracing.mapValues("myMapValues", v -> ...)
+   *        .to(outputTopic);
+   * }
+ * @deprecated Use {@link KafkaStreamsTracing#process(String, org.apache.kafka.streams.processor.api.ProcessorSupplier)} or + * {@link KafkaStreamsTracing#processValues(String, org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier)} instead. + */ + @Deprecated + public ValueTransformerSupplier mapValues(String spanName, + ValueMapper mapper) { + return new TracingValueTransformerSupplier<>(this, spanName, () -> + new AbstractTracingValueTransformer() { + @Override public VR transform(V value) { + return mapper.apply(value); + } + }); + } + /** * Create a tracing-decorated {@link org.apache.kafka.streams.processor.api.ProcessorSupplier} * diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingFilter.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingFilter.java new file mode 100644 index 0000000000..6c6285e150 --- /dev/null +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingFilter.java @@ -0,0 +1,76 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.kafka.streams; + +import brave.Span; +import brave.Tracer; +import org.apache.kafka.streams.kstream.Predicate; +import org.apache.kafka.streams.processor.ProcessorContext; + +import static brave.internal.Throwables.propagateIfFatal; +import static brave.kafka.streams.KafkaStreamsTags.KAFKA_STREAMS_FILTERED_TAG; + +abstract class TracingFilter { + final KafkaStreamsTracing kafkaStreamsTracing; + final String spanName; + final Predicate delegatePredicate; + final Tracer tracer; + final boolean filterNot; + ProcessorContext processorContext; + + TracingFilter(KafkaStreamsTracing kafkaStreamsTracing, String spanName, + Predicate delegatePredicate, boolean filterNot) { + this.kafkaStreamsTracing = kafkaStreamsTracing; + this.tracer = kafkaStreamsTracing.tracer; + this.spanName = spanName; + this.delegatePredicate = delegatePredicate; + this.filterNot = filterNot; + } + + public void init(ProcessorContext context) { + processorContext = context; + } + + public R transform(K key, V value) { + Span span = kafkaStreamsTracing.nextSpan(processorContext); + if (!span.isNoop()) { + span.name(spanName); + span.start(); + } + + Tracer.SpanInScope ws = tracer.withSpanInScope(span); + Throwable error = null; + try { + if (filterNot ^ delegatePredicate.test(key, value)) { + span.tag(KAFKA_STREAMS_FILTERED_TAG, "false"); + return result(key, value); + } else { + span.tag(KAFKA_STREAMS_FILTERED_TAG, "true"); + return null; // meaning KV pair will not be forwarded thus effectively filtered + } + } catch (Throwable e) { + error = e; + propagateIfFatal(e); + throw e; + } finally { + // Inject this span so that the next stage uses it as a parent + kafkaStreamsTracing.injector.inject(span.context(), processorContext.headers()); + if (error != null) span.error(error); + span.finish(); + ws.close(); + } + } + + abstract R result(K key, V value); +} diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingFilterTransformer.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingFilterTransformer.java new file mode 100644 index 0000000000..32380904e2 --- /dev/null +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingFilterTransformer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.kafka.streams; + +import org.apache.kafka.streams.KeyValue; +import org.apache.kafka.streams.kstream.Predicate; +import org.apache.kafka.streams.kstream.Transformer; +import org.apache.kafka.streams.processor.ProcessorContext; + +class TracingFilterTransformer extends TracingFilter> + implements Transformer> { + + TracingFilterTransformer(KafkaStreamsTracing tracing, String spanName, + Predicate delegatePredicate, boolean filterNot) { + super(tracing, spanName, delegatePredicate, filterNot); + } + + @Override + public void init(ProcessorContext context) { + super.init(context); + } + + @Override + public KeyValue transform(K key, V value) { + return super.transform(key, value); + } + + @Override public void close() { + } + + @Override KeyValue result(K key, V value) { + return KeyValue.pair(key, value); + } +} diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingFilterTransformerSupplier.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingFilterTransformerSupplier.java new file mode 100644 index 0000000000..7f3503649e --- /dev/null +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingFilterTransformerSupplier.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.kafka.streams; + +import org.apache.kafka.streams.KeyValue; +import org.apache.kafka.streams.kstream.Predicate; +import org.apache.kafka.streams.kstream.Transformer; +import org.apache.kafka.streams.kstream.TransformerSupplier; + +public class TracingFilterTransformerSupplier + implements TransformerSupplier> { + + final KafkaStreamsTracing kafkaStreamsTracing; + final String spanName; + final Predicate delegatePredicate; + final boolean filterNot; + + public TracingFilterTransformerSupplier(KafkaStreamsTracing kafkaStreamsTracing, + String spanName, Predicate delegatePredicate, boolean filterNot) { + this.kafkaStreamsTracing = kafkaStreamsTracing; + this.spanName = spanName; + this.delegatePredicate = delegatePredicate; + this.filterNot = filterNot; + } + + @Override public Transformer> get() { + return new TracingFilterTransformer<>(kafkaStreamsTracing, spanName, delegatePredicate, + filterNot); + } +} diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingFilterValueTransformerWithKey.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingFilterValueTransformerWithKey.java new file mode 100644 index 0000000000..4fea19d278 --- /dev/null +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingFilterValueTransformerWithKey.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.kafka.streams; + +import org.apache.kafka.streams.kstream.Predicate; +import org.apache.kafka.streams.kstream.ValueTransformerWithKey; +import org.apache.kafka.streams.processor.ProcessorContext; + +class TracingFilterValueTransformerWithKey extends TracingFilter implements + ValueTransformerWithKey { + + TracingFilterValueTransformerWithKey(KafkaStreamsTracing tracing, String spanName, + Predicate delegatePredicate, boolean filterNot) { + super(tracing, spanName, delegatePredicate, filterNot); + } + + @Override + public void init(ProcessorContext context) { + super.init(context); + } + + @Override + public V transform(K key, V value) { + return super.transform(key, value); + } + + @Override public void close() { + } + + @Override V result(K key, V value) { + return value; + } +} diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingFilterValueTransformerWithKeySupplier.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingFilterValueTransformerWithKeySupplier.java new file mode 100644 index 0000000000..b4d574c64c --- /dev/null +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingFilterValueTransformerWithKeySupplier.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.kafka.streams; + +import org.apache.kafka.streams.kstream.Predicate; +import org.apache.kafka.streams.kstream.ValueTransformerWithKey; +import org.apache.kafka.streams.kstream.ValueTransformerWithKeySupplier; + +public class TracingFilterValueTransformerWithKeySupplier implements + ValueTransformerWithKeySupplier { + + final KafkaStreamsTracing kafkaStreamsTracing; + final String spanName; + final Predicate delegatePredicate; + final boolean filterNot; + + public TracingFilterValueTransformerWithKeySupplier(KafkaStreamsTracing kafkaStreamsTracing, + String spanName, Predicate delegatePredicate, boolean filterNot) { + this.kafkaStreamsTracing = kafkaStreamsTracing; + this.spanName = spanName; + this.delegatePredicate = delegatePredicate; + this.filterNot = filterNot; + } + + @Override public ValueTransformerWithKey get() { + return new TracingFilterValueTransformerWithKey<>(kafkaStreamsTracing, spanName, + delegatePredicate, + filterNot); + } +} diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingKafkaClientSupplier.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingKafkaClientSupplier.java index 2c4a85c943..645e81ce27 100644 --- a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingKafkaClientSupplier.java +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingKafkaClientSupplier.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2021 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -37,6 +37,10 @@ final class TracingKafkaClientSupplier implements KafkaClientSupplier { this.kafkaTracing = kafkaTracing; } + @Deprecated public AdminClient getAdminClient(Map config) { + return getAdmin(config); + } + @Override public AdminClient getAdmin(Map config) { return AdminClient.create(config); } diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingProcessor.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingProcessor.java new file mode 100644 index 0000000000..dd0d85a5bd --- /dev/null +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingProcessor.java @@ -0,0 +1,74 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.kafka.streams; + +import brave.Span; +import brave.Tracer; +import org.apache.kafka.streams.processor.Processor; +import org.apache.kafka.streams.processor.ProcessorContext; + +import static brave.internal.Throwables.propagateIfFatal; + +class TracingProcessor implements Processor { + final KafkaStreamsTracing kafkaStreamsTracing; + final Tracer tracer; + final String spanName; + final Processor delegateProcessor; + + ProcessorContext processorContext; + + TracingProcessor(KafkaStreamsTracing kafkaStreamsTracing, + String spanName, Processor delegateProcessor) { + this.kafkaStreamsTracing = kafkaStreamsTracing; + this.tracer = kafkaStreamsTracing.tracer; + this.spanName = spanName; + this.delegateProcessor = delegateProcessor; + } + + @Override + public void init(ProcessorContext processorContext) { + this.processorContext = processorContext; + delegateProcessor.init(processorContext); + } + + @Override + public void process(K k, V v) { + Span span = kafkaStreamsTracing.nextSpan(processorContext); + if (!span.isNoop()) { + span.name(spanName); + span.start(); + } + + Tracer.SpanInScope ws = tracer.withSpanInScope(span); + Throwable error = null; + try { + delegateProcessor.process(k, v); + } catch (Throwable e) { + error = e; + propagateIfFatal(e); + throw e; + } finally { + // Inject this span so that the next stage uses it as a parent + kafkaStreamsTracing.injector.inject(span.context(), processorContext.headers()); + if (error != null) span.error(error); + span.finish(); + ws.close(); + } + } + + @Override + public void close() { + delegateProcessor.close(); + } +} diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingProcessorSupplier.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingProcessorSupplier.java new file mode 100644 index 0000000000..e0e496a289 --- /dev/null +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingProcessorSupplier.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.kafka.streams; + +import org.apache.kafka.streams.processor.Processor; +import org.apache.kafka.streams.processor.ProcessorSupplier; + +class TracingProcessorSupplier implements ProcessorSupplier { + final KafkaStreamsTracing kafkaStreamsTracing; + final String spanName; + final ProcessorSupplier delegateProcessorSupplier; + + TracingProcessorSupplier(KafkaStreamsTracing kafkaStreamsTracing, + String spanName, + ProcessorSupplier processorSupplier) { + this.kafkaStreamsTracing = kafkaStreamsTracing; + this.spanName = spanName; + this.delegateProcessorSupplier = processorSupplier; + } + + /** This wraps process method to enable tracing. */ + @Override public Processor get() { + return new TracingProcessor<>(kafkaStreamsTracing, spanName, delegateProcessorSupplier.get()); + } +} diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingTransformer.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingTransformer.java new file mode 100644 index 0000000000..a2d02dcdce --- /dev/null +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingTransformer.java @@ -0,0 +1,76 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.kafka.streams; + +import brave.Span; +import brave.Tracer; +import org.apache.kafka.streams.kstream.Transformer; +import org.apache.kafka.streams.processor.ProcessorContext; + +import static brave.internal.Throwables.propagateIfFatal; + +class TracingTransformer implements Transformer { + final KafkaStreamsTracing kafkaStreamsTracing; + final Tracer tracer; + final String spanName; + final Transformer delegateTransformer; + + ProcessorContext processorContext; + + TracingTransformer(KafkaStreamsTracing kafkaStreamsTracing, String spanName, + Transformer delegateTransformer) { + this.kafkaStreamsTracing = kafkaStreamsTracing; + this.tracer = kafkaStreamsTracing.tracer; + this.spanName = spanName; + this.delegateTransformer = delegateTransformer; + } + + @Override + public void init(ProcessorContext processorContext) { + this.processorContext = processorContext; + delegateTransformer.init(processorContext); + } + + @Override + public R transform(K k, V v) { + Span span = kafkaStreamsTracing.nextSpan(processorContext); + if (!span.isNoop()) { + span.name(spanName); + span.start(); + } + + Tracer.SpanInScope ws = tracer.withSpanInScope(span); + Throwable error = null; + try { + return delegateTransformer.transform(k, v); + } catch (Throwable e) { + error = e; + propagateIfFatal(e); + throw e; + } finally { + // Inject this span so that the next stage uses it as a parent + kafkaStreamsTracing.injector.inject(span.context(), processorContext.headers()); + if (error != null) span.error(error); + span.finish(); + ws.close(); + } + } + + @Override + public void close() { + delegateTransformer.close(); + } +} + + diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingTransformerSupplier.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingTransformerSupplier.java new file mode 100644 index 0000000000..9923c530ec --- /dev/null +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingTransformerSupplier.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.kafka.streams; + +import org.apache.kafka.streams.kstream.Transformer; +import org.apache.kafka.streams.kstream.TransformerSupplier; + +class TracingTransformerSupplier implements TransformerSupplier { + final KafkaStreamsTracing kafkaStreamsTracing; + final String spanName; + final TransformerSupplier delegateTransformerSupplier; + + TracingTransformerSupplier(KafkaStreamsTracing kafkaStreamsTracing, + String spanName, + TransformerSupplier delegateTransformerSupplier) { + this.kafkaStreamsTracing = kafkaStreamsTracing; + this.spanName = spanName; + this.delegateTransformerSupplier = delegateTransformerSupplier; + } + + /** This wraps transform method to enable tracing. */ + @Override public Transformer get() { + return new TracingTransformer<>(kafkaStreamsTracing, spanName, delegateTransformerSupplier.get()); + } +} diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingV2FixedKeyProcessor.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingV2FixedKeyProcessor.java index 8bf8fcae4a..2e49f250db 100644 --- a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingV2FixedKeyProcessor.java +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingV2FixedKeyProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -55,7 +55,7 @@ public void process(FixedKeyRecord record) { span.start(); } - Tracer.SpanInScope scope = tracer.withSpanInScope(span); + Tracer.SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { delegateProcessor.process(record); @@ -68,7 +68,7 @@ public void process(FixedKeyRecord record) { kafkaStreamsTracing.injector.inject(span.context(), record.headers()); if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingV2Processor.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingV2Processor.java index 89bf1680f7..683da2853b 100644 --- a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingV2Processor.java +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingV2Processor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -55,7 +55,7 @@ public void process(Record record) { span.start(); } - Tracer.SpanInScope scope = tracer.withSpanInScope(span); + Tracer.SpanInScope ws = tracer.withSpanInScope(span); Throwable error = null; try { delegateProcessor.process(record); @@ -68,7 +68,7 @@ public void process(Record record) { kafkaStreamsTracing.injector.inject(span.context(), record.headers()); if (error != null) span.error(error); span.finish(); - scope.close(); + ws.close(); } } diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingValueTransformer.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingValueTransformer.java new file mode 100644 index 0000000000..865468d2c8 --- /dev/null +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingValueTransformer.java @@ -0,0 +1,76 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.kafka.streams; + +import brave.Span; +import brave.Tracer; +import org.apache.kafka.streams.kstream.ValueTransformer; +import org.apache.kafka.streams.processor.ProcessorContext; + +import static brave.internal.Throwables.propagateIfFatal; + +class TracingValueTransformer implements ValueTransformer { + final KafkaStreamsTracing kafkaStreamsTracing; + final Tracer tracer; + final String spanName; + final ValueTransformer delegateTransformer; + + ProcessorContext processorContext; + + TracingValueTransformer(KafkaStreamsTracing kafkaStreamsTracing, String spanName, + ValueTransformer delegateTransformer) { + this.kafkaStreamsTracing = kafkaStreamsTracing; + this.tracer = kafkaStreamsTracing.tracer; + this.spanName = spanName; + this.delegateTransformer = delegateTransformer; + } + + @Override + public void init(ProcessorContext processorContext) { + this.processorContext = processorContext; + delegateTransformer.init(processorContext); + } + + @Override + public VR transform(V v) { + Span span = kafkaStreamsTracing.nextSpan(processorContext); + if (!span.isNoop()) { + span.name(spanName); + span.start(); + } + + Tracer.SpanInScope ws = tracer.withSpanInScope(span); + Throwable error = null; + try { + return delegateTransformer.transform(v); + } catch (Throwable e) { + error = e; + propagateIfFatal(e); + throw e; + } finally { + // Inject this span so that the next stage uses it as a parent + kafkaStreamsTracing.injector.inject(span.context(), processorContext.headers()); + if (error != null) span.error(error); + span.finish(); + ws.close(); + } + } + + @Override + public void close() { + delegateTransformer.close(); + } +} + + diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingValueTransformerSupplier.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingValueTransformerSupplier.java new file mode 100644 index 0000000000..31873bdcab --- /dev/null +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingValueTransformerSupplier.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.kafka.streams; + +import org.apache.kafka.streams.kstream.ValueTransformer; +import org.apache.kafka.streams.kstream.ValueTransformerSupplier; + +class TracingValueTransformerSupplier implements ValueTransformerSupplier { + final KafkaStreamsTracing kafkaStreamsTracing; + final String spanName; + final ValueTransformerSupplier delegateTransformerSupplier; + + TracingValueTransformerSupplier(KafkaStreamsTracing kafkaStreamsTracing, + String spanName, + ValueTransformerSupplier delegateTransformerSupplier) { + this.kafkaStreamsTracing = kafkaStreamsTracing; + this.spanName = spanName; + this.delegateTransformerSupplier = delegateTransformerSupplier; + } + + /** This wraps transform method to enable tracing. */ + @Override public ValueTransformer get() { + return new TracingValueTransformer<>(kafkaStreamsTracing, spanName, delegateTransformerSupplier.get()); + } +} diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingValueTransformerWithKey.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingValueTransformerWithKey.java new file mode 100644 index 0000000000..e92269b2ed --- /dev/null +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingValueTransformerWithKey.java @@ -0,0 +1,76 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.kafka.streams; + +import brave.Span; +import brave.Tracer; +import org.apache.kafka.streams.kstream.ValueTransformerWithKey; +import org.apache.kafka.streams.processor.ProcessorContext; + +import static brave.internal.Throwables.propagateIfFatal; + +class TracingValueTransformerWithKey implements ValueTransformerWithKey { + final KafkaStreamsTracing kafkaStreamsTracing; + final Tracer tracer; + final String spanName; + final ValueTransformerWithKey delegateTransformer; + + ProcessorContext processorContext; + + TracingValueTransformerWithKey(KafkaStreamsTracing kafkaStreamsTracing, String spanName, + ValueTransformerWithKey delegateTransformer) { + this.kafkaStreamsTracing = kafkaStreamsTracing; + this.tracer = kafkaStreamsTracing.tracer; + this.spanName = spanName; + this.delegateTransformer = delegateTransformer; + } + + @Override + public void init(ProcessorContext processorContext) { + this.processorContext = processorContext; + delegateTransformer.init(processorContext); + } + + @Override + public VR transform(K k, V v) { + Span span = kafkaStreamsTracing.nextSpan(processorContext); + if (!span.isNoop()) { + span.name(spanName); + span.start(); + } + + Tracer.SpanInScope ws = tracer.withSpanInScope(span); + Throwable error = null; + try { + return delegateTransformer.transform(k, v); + } catch (Throwable e) { + error = e; + propagateIfFatal(e); + throw e; + } finally { + // Inject this span so that the next stage uses it as a parent + kafkaStreamsTracing.injector.inject(span.context(), processorContext.headers()); + if (error != null) span.error(error); + span.finish(); + ws.close(); + } + } + + @Override + public void close() { + delegateTransformer.close(); + } +} + + diff --git a/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingValueTransformerWithKeySupplier.java b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingValueTransformerWithKeySupplier.java new file mode 100644 index 0000000000..bdf2134fd7 --- /dev/null +++ b/instrumentation/kafka-streams/src/main/java/brave/kafka/streams/TracingValueTransformerWithKeySupplier.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.kafka.streams; + +import org.apache.kafka.streams.kstream.ValueTransformerWithKey; +import org.apache.kafka.streams.kstream.ValueTransformerWithKeySupplier; + +class TracingValueTransformerWithKeySupplier implements + ValueTransformerWithKeySupplier { + final KafkaStreamsTracing kafkaStreamsTracing; + final String spanName; + final ValueTransformerWithKeySupplier delegateTransformerSupplier; + + TracingValueTransformerWithKeySupplier(KafkaStreamsTracing kafkaStreamsTracing, + String spanName, + ValueTransformerWithKeySupplier delegateTransformerSupplier) { + this.kafkaStreamsTracing = kafkaStreamsTracing; + this.spanName = spanName; + this.delegateTransformerSupplier = delegateTransformerSupplier; + } + + /** This wraps transform method to enable tracing. */ + @Override public ValueTransformerWithKey get() { + return new TracingValueTransformerWithKey<>(kafkaStreamsTracing, spanName, delegateTransformerSupplier.get()); + } +} diff --git a/instrumentation/kafka-streams/src/test/java/brave/kafka/streams/ITKafkaStreamsTracing.java b/instrumentation/kafka-streams/src/test/java/brave/kafka/streams/ITKafkaStreamsTracing.java index 9bf6cdff6b..0e5fec947a 100644 --- a/instrumentation/kafka-streams/src/test/java/brave/kafka/streams/ITKafkaStreamsTracing.java +++ b/instrumentation/kafka-streams/src/test/java/brave/kafka/streams/ITKafkaStreamsTracing.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -16,8 +16,12 @@ import brave.handler.MutableSpan; import brave.kafka.clients.KafkaTracing; import brave.messaging.MessagingTracing; +import brave.propagation.TraceContext; +import java.io.FileNotFoundException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Properties; import org.apache.kafka.clients.CommonClientConfigs; import org.apache.kafka.clients.consumer.Consumer; @@ -28,10 +32,21 @@ import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.serialization.Serdes; import org.apache.kafka.streams.KafkaStreams; +import org.apache.kafka.streams.KeyValue; import org.apache.kafka.streams.StreamsBuilder; import org.apache.kafka.streams.StreamsConfig; import org.apache.kafka.streams.Topology; import org.apache.kafka.streams.kstream.Consumed; +import org.apache.kafka.streams.kstream.Produced; +import org.apache.kafka.streams.kstream.Transformer; +import org.apache.kafka.streams.kstream.TransformerSupplier; +import org.apache.kafka.streams.kstream.ValueTransformer; +import org.apache.kafka.streams.kstream.ValueTransformerSupplier; +import org.apache.kafka.streams.kstream.ValueTransformerWithKey; +import org.apache.kafka.streams.kstream.ValueTransformerWithKeySupplier; +import org.apache.kafka.streams.processor.AbstractProcessor; +import org.apache.kafka.streams.processor.ProcessorContext; +import org.apache.kafka.streams.processor.ProcessorSupplier; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -40,9 +55,11 @@ import static brave.Span.Kind.CONSUMER; import static brave.Span.Kind.PRODUCER; +import static brave.kafka.streams.KafkaStreamsTags.KAFKA_STREAMS_FILTERED_TAG; import static brave.kafka.streams.KafkaStreamsTracingTest.TEST_KEY; import static brave.kafka.streams.KafkaStreamsTracingTest.TEST_VALUE; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ITKafkaStreamsTracing extends ITKafkaStreams { @@ -185,6 +202,44 @@ class ITKafkaStreamsTracing extends ITKafkaStreams { consumer.close(); } + @Test void should_create_spans_from_stream_with_tracing_processor() { + ProcessorSupplier processorSupplier = + kafkaStreamsTracing.processor( + "forward-1", () -> + new AbstractProcessor() { + @Override + public void process(String key, String value) { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + + String inputTopic = testName + "-input"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .process(processorSupplier); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + + streams.close(); + streams.cleanUp(); + } + @Test void should_create_spans_from_stream_with_tracing_v2_processor() { org.apache.kafka.streams.processor.api.ProcessorSupplier processorSupplier = @@ -202,7 +257,690 @@ record -> { StreamsBuilder builder = new StreamsBuilder(); builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) - .process(processorSupplier); + .process(processorSupplier); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_v2_fixed_key_processor() { + org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier + processorSupplier = + kafkaStreamsTracing.processValues( + "forward-1", () -> + record -> { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + + String inputTopic = testName + "-input"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .processValues(processorSupplier); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_filter_predicate_true() { + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transform(kafkaStreamsTracing.filter("filter-1", (key, value) -> true)) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + assertThat(spanProcessor.tags()).containsEntry(KAFKA_STREAMS_FILTERED_TAG, "false"); + + // the filter transformer returns true so record is not dropped + + MutableSpan spanOutput = testSpanHandler.takeRemoteSpan(PRODUCER); + assertThat(spanOutput.tags()).containsEntry("kafka.topic", outputTopic); + assertChildOf(spanOutput, spanProcessor); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_filter_predicate_false() { + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transform(kafkaStreamsTracing.filter("filter-2", (key, value) -> false)) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + assertThat(spanProcessor.tags()).containsEntry(KAFKA_STREAMS_FILTERED_TAG, "true"); + + // the filter transformer returns false so record is dropped + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_and_propagate_extra_from_stream_with_multi_processor() { + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transformValues(kafkaStreamsTracing.peek("transform1", (o, o2) -> { + TraceContext context = currentTraceContext.get(); + assertThat(BAGGAGE_FIELD.getValue(context)).isEqualTo("user1"); + BAGGAGE_FIELD.updateValue(context, "user2"); + })) + .transformValues(kafkaStreamsTracing.peek("transform2", (s, s2) -> { + TraceContext context = currentTraceContext.get(); + assertThat(BAGGAGE_FIELD.getValue(context)).isEqualTo("user2"); + })) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + ProducerRecord record = new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE); + record.headers().add(BAGGAGE_FIELD_KEY, "user1".getBytes()); + send(record); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanTransform1 = testSpanHandler.takeLocalSpan(); + assertChildOf(spanTransform1, spanInput); + + MutableSpan spanTransform2 = testSpanHandler.takeLocalSpan(); + assertChildOf(spanTransform2, spanTransform1); + + MutableSpan spanOutput = testSpanHandler.takeRemoteSpan(PRODUCER); + assertThat(spanOutput.tags()).containsEntry("kafka.topic", outputTopic); + assertChildOf(spanOutput, spanTransform2); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_filter_not_predicate_true() { + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transform(kafkaStreamsTracing.filterNot("filterNot-1", (key, value) -> true)) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + assertThat(spanProcessor.tags()).containsEntry(KAFKA_STREAMS_FILTERED_TAG, "true"); + + // the filterNot transformer returns true so record is dropped + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_filter_not_predicate_false() { + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transform(kafkaStreamsTracing.filterNot("filterNot-2", (key, value) -> false)) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + assertThat(spanProcessor.tags()).containsEntry(KAFKA_STREAMS_FILTERED_TAG, "false"); + + // the filter transformer returns true so record is not dropped + + MutableSpan spanOutput = testSpanHandler.takeRemoteSpan(PRODUCER); + assertThat(spanOutput.tags()).containsEntry("kafka.topic", outputTopic); + assertChildOf(spanOutput, spanProcessor); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_mark_as_filtered_predicate_true() { + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transformValues(kafkaStreamsTracing.markAsFiltered("filter-1", (key, value) -> true)) + .filterNot((k, v) -> Objects.isNull(v)) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + assertThat(spanProcessor.tags()).containsEntry(KAFKA_STREAMS_FILTERED_TAG, "false"); + + // the filter transformer returns true so record is not dropped + + MutableSpan spanOutput = testSpanHandler.takeRemoteSpan(PRODUCER); + assertThat(spanOutput.tags()).containsEntry("kafka.topic", outputTopic); + assertChildOf(spanOutput, spanProcessor); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_mark_as_filtered_predicate_false() { + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transformValues(kafkaStreamsTracing.markAsFiltered("filter-2", (key, value) -> false)) + .filterNot((k, v) -> Objects.isNull(v)) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + assertThat(spanProcessor.tags()).containsEntry(KAFKA_STREAMS_FILTERED_TAG, "true"); + + // the filter transformer returns false so record is dropped + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_mark_as_not_filtered_predicate_true() { + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transformValues(kafkaStreamsTracing.markAsNotFiltered("filterNot-1", (key, value) -> true)) + .filterNot((k, v) -> Objects.isNull(v)) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + assertThat(spanProcessor.tags()).containsEntry(KAFKA_STREAMS_FILTERED_TAG, "true"); + + // the filterNot transformer returns true so record is dropped + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_mark_as_not_filtered_predicate_false() { + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transformValues( + kafkaStreamsTracing.markAsNotFiltered("filterNot-2", (key, value) -> false)) + .filterNot((k, v) -> Objects.isNull(v)) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + assertThat(spanProcessor.tags()).containsEntry(KAFKA_STREAMS_FILTERED_TAG, "false"); + + // the filter transformer returns true so record is not dropped + + MutableSpan spanOutput = testSpanHandler.takeRemoteSpan(PRODUCER); + assertThat(spanOutput.tags()).containsEntry("kafka.topic", outputTopic); + assertChildOf(spanOutput, spanProcessor); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_peek() { + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + long now = System.currentTimeMillis(); + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transformValues(kafkaStreamsTracing.peek("peek-1", (key, value) -> { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + tracing.tracer().currentSpan().annotate(now, "test"); + })) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + assertThat(spanProcessor.annotations()).contains(entry(now, "test")); + + MutableSpan spanOutput = testSpanHandler.takeRemoteSpan(PRODUCER); + assertThat(spanOutput.tags()).containsEntry("kafka.topic", outputTopic); + assertChildOf(spanOutput, spanProcessor); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_mark() { + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transformValues(kafkaStreamsTracing.mark("mark-1")) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + + MutableSpan spanOutput = testSpanHandler.takeRemoteSpan(PRODUCER); + assertThat(spanOutput.tags()).containsEntry("kafka.topic", outputTopic); + assertChildOf(spanOutput, spanProcessor); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_foreach() { + String inputTopic = testName + "-input"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .process(kafkaStreamsTracing.foreach("foreach-1", (key, value) -> { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + })); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_without_tracing_and_tracing_processor() { + ProcessorSupplier processorSupplier = + kafkaStreamsTracing.processor( + "forward-1", () -> + new AbstractProcessor() { + @Override + public void process(String key, String value) { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + + String inputTopic = testName + "-input"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .process(processorSupplier); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreamsWithoutTracing(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + assertThat(testSpanHandler.takeLocalSpan().tags()) + .containsOnlyKeys("kafka.streams.application.id", "kafka.streams.task.id"); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_transformer() { + TransformerSupplier> transformerSupplier = + kafkaStreamsTracing.transformer( + "transformer-1", () -> + new Transformer>() { + ProcessorContext context; + + @Override + public void init(ProcessorContext context) { + this.context = context; + } + + @Override + public KeyValue transform(String key, String value) { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return KeyValue.pair(key, value); + } + + @Override + public void close() { + } + }); + + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transform(transformerSupplier) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + + MutableSpan spanOutput = testSpanHandler.takeRemoteSpan(PRODUCER); + assertThat(spanOutput.tags()).containsEntry("kafka.topic", outputTopic); + assertChildOf(spanOutput, spanProcessor); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_throw_exception_upwards() { + TransformerSupplier> transformerSupplier = + kafkaStreamsTracing.transformer( + "exception-transformer", () -> + new Transformer>() { + ProcessorContext context; + + @Override + public void init(ProcessorContext context) { + this.context = context; + } + + @Override + public KeyValue transform(String key, String value) { + throw new IllegalArgumentException("illegal-argument"); + } + + @Override + public void close() { + } + }); + + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transform(transformerSupplier) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertThat(spanProcessor.error()).hasMessage("illegal-argument"); + assertChildOf(spanProcessor, spanInput); + + assertThat(!streams.state().isRunningOrRebalancing()); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_throw_sneaky_exception_upwards() { + TransformerSupplier> transformerSupplier = + kafkaStreamsTracing.transformer( + "sneaky-exception-transformer", () -> + new Transformer>() { + ProcessorContext context; + + @Override + public void init(ProcessorContext context) { + this.context = context; + } + + @Override + public KeyValue transform(String key, String value) { + doThrowUnsafely(new FileNotFoundException("file-not-found")); + return KeyValue.pair(key, value); + } + + @Override + public void close() { + } + }); + + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transform(transformerSupplier) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertThat(spanProcessor.error()).hasMessage("file-not-found"); + assertChildOf(spanProcessor, spanInput); + + assertThat(!streams.state().isRunningOrRebalancing()); + + streams.close(); + streams.cleanUp(); + } + + // Armeria Black Magic copied from + // https://github.com/line/armeria/blob/master/core/src/main/java/com/linecorp/armeria/common/util/Exceptions.java#L197 + @SuppressWarnings("unchecked") + private static void doThrowUnsafely(Throwable cause) throws E { + throw (E) cause; + } + + @Test void should_create_spans_from_stream_without_tracing_with_tracing_flattransformer() { + TransformerSupplier>> transformerSupplier = + kafkaStreamsTracing.transformer( + "double-transformer-1", () -> + new Transformer>>() { + ProcessorContext context; + + @Override + public void init(ProcessorContext context) { + this.context = context; + } + + @Override + public Iterable> transform(String key, String value) { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return Arrays.asList(KeyValue.pair(key, value), KeyValue.pair(key, value)); + } + + @Override + public void close() { + } + }); + + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .flatTransform(transformerSupplier) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); Topology topology = builder.build(); KafkaStreams streams = buildKafkaStreams(topology); @@ -216,29 +954,359 @@ record -> { MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); assertChildOf(spanProcessor, spanInput); + assertThat(spanProcessor.tags()) + .containsOnlyKeys("kafka.streams.application.id", "kafka.streams.task.id"); + + for (int i = 0; i < 2; i++) { + MutableSpan spanOutput = testSpanHandler.takeRemoteSpan(PRODUCER); + assertThat(spanOutput.tags()).containsEntry("kafka.topic", outputTopic); + assertChildOf(spanOutput, spanProcessor); + } streams.close(); streams.cleanUp(); } - @Test void should_create_spans_from_stream_with_tracing_v2_fixed_key_processor() { - org.apache.kafka.streams.processor.api.FixedKeyProcessorSupplier - processorSupplier = - kafkaStreamsTracing.processValues( - "forward-1", () -> - record -> { - try { - Thread.sleep(100L); - } catch (InterruptedException e) { - e.printStackTrace(); + @Test void should_create_spans_from_stream_without_tracing_with_tracing_transformer() { + TransformerSupplier> transformerSupplier = + kafkaStreamsTracing.transformer( + "transformer-1", () -> + new Transformer>() { + ProcessorContext context; + + @Override + public void init(ProcessorContext context) { + this.context = context; + } + + @Override + public KeyValue transform(String key, String value) { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return KeyValue.pair(key, value); + } + + @Override + public void close() { } }); String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; StreamsBuilder builder = new StreamsBuilder(); builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) - .processValues(processorSupplier); + .transform(transformerSupplier) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreamsWithoutTracing(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + assertThat(testSpanHandler.takeLocalSpan().tags()) + .containsOnlyKeys("kafka.streams.application.id", "kafka.streams.task.id"); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_valueTransformer() { + ValueTransformerSupplier transformerSupplier = + kafkaStreamsTracing.valueTransformer( + "transformer-1", () -> + new ValueTransformer() { + ProcessorContext context; + + @Override + public void init(ProcessorContext context) { + this.context = context; + } + + @Override + public String transform(String value) { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return value; + } + + @Override + public void close() { + } + }); + + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transformValues(transformerSupplier) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + + MutableSpan spanOutput = testSpanHandler.takeRemoteSpan(PRODUCER); + assertThat(spanOutput.tags()).containsEntry("kafka.topic", outputTopic); + assertChildOf(spanOutput, spanProcessor); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_map() { + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transform(kafkaStreamsTracing.map("map-1", (key, value) -> { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return KeyValue.pair(key, value); + })) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + + MutableSpan spanOutput = testSpanHandler.takeRemoteSpan(PRODUCER); + assertThat(spanOutput.tags()).containsEntry("kafka.topic", outputTopic); + assertChildOf(spanOutput, spanProcessor); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_flatMap() { + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .flatTransform(kafkaStreamsTracing.flatMap("flat-map-1", (key, value) -> { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return Arrays.asList(KeyValue.pair(key, value + "-1"), KeyValue.pair(key, value + "-2")); + })) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + assertThat(spanProcessor.tags()) + .containsOnlyKeys("kafka.streams.application.id", "kafka.streams.task.id"); + + for (int i = 0; i < 2; i++) { + MutableSpan spanOutput = testSpanHandler.takeRemoteSpan(PRODUCER); + assertThat(spanOutput.tags()).containsEntry("kafka.topic", outputTopic); + assertChildOf(spanOutput, spanProcessor); + } + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_mapValues() { + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transformValues(kafkaStreamsTracing.mapValues("mapValue-1", value -> { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return value; + })) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + + MutableSpan spanOutput = testSpanHandler.takeRemoteSpan(PRODUCER); + assertThat(spanOutput.tags()).containsEntry("kafka.topic", outputTopic); + assertChildOf(spanOutput, spanProcessor); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_mapValues_withKey() { + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transformValues(kafkaStreamsTracing.mapValues("mapValue-1", (key, value) -> { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return value; + })) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreams(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + MutableSpan spanInput = testSpanHandler.takeRemoteSpan(CONSUMER); + assertThat(spanInput.tags()).containsEntry("kafka.topic", inputTopic); + + MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); + assertChildOf(spanProcessor, spanInput); + + MutableSpan spanOutput = testSpanHandler.takeRemoteSpan(PRODUCER); + assertThat(spanOutput.tags()).containsEntry("kafka.topic", outputTopic); + assertChildOf(spanOutput, spanProcessor); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_without_tracing_with_tracing_valueTransformer() { + ValueTransformerSupplier transformerSupplier = + kafkaStreamsTracing.valueTransformer( + "transformer-1", () -> + new ValueTransformer() { + ProcessorContext context; + + @Override + public void init(ProcessorContext context) { + this.context = context; + } + + @Override + public String transform(String value) { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return value; + } + + @Override + public void close() { + } + }); + + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transformValues(transformerSupplier) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreamsWithoutTracing(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + assertThat(testSpanHandler.takeLocalSpan().tags()) + .containsOnlyKeys("kafka.streams.application.id", "kafka.streams.task.id"); + + streams.close(); + streams.cleanUp(); + } + + @Test void should_create_spans_from_stream_with_tracing_valueTransformerWithKey() { + ValueTransformerWithKeySupplier transformerSupplier = + kafkaStreamsTracing.valueTransformerWithKey( + "transformer-1", () -> + new ValueTransformerWithKey() { + ProcessorContext context; + + @Override + public void init(ProcessorContext context) { + this.context = context; + } + + @Override + public String transform(String key, String value) { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return value; + } + + @Override + public void close() { + } + }); + + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transformValues(transformerSupplier) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); Topology topology = builder.build(); KafkaStreams streams = buildKafkaStreams(topology); @@ -253,6 +1321,60 @@ record -> { MutableSpan spanProcessor = testSpanHandler.takeLocalSpan(); assertChildOf(spanProcessor, spanInput); + MutableSpan spanOutput = testSpanHandler.takeRemoteSpan(PRODUCER); + assertThat(spanOutput.tags()).containsEntry("kafka.topic", outputTopic); + assertChildOf(spanOutput, spanProcessor); + + streams.close(); + streams.cleanUp(); + } + + @Test + void should_create_spans_from_stream_without_tracing_with_tracing_valueTransformerWithKey() { + ValueTransformerWithKeySupplier transformerSupplier = + kafkaStreamsTracing.valueTransformerWithKey( + "transformer-1", () -> + new ValueTransformerWithKey() { + ProcessorContext context; + + @Override + public void init(ProcessorContext context) { + this.context = context; + } + + @Override + public String transform(String key, String value) { + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return value; + } + + @Override + public void close() { + } + }); + + String inputTopic = testName + "-input"; + String outputTopic = testName + "-output"; + + StreamsBuilder builder = new StreamsBuilder(); + builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String())) + .transformValues(transformerSupplier) + .to(outputTopic, Produced.with(Serdes.String(), Serdes.String())); + Topology topology = builder.build(); + + KafkaStreams streams = buildKafkaStreamsWithoutTracing(topology); + + send(new ProducerRecord<>(inputTopic, TEST_KEY, TEST_VALUE)); + + waitForStreamToRun(streams); + + assertThat(testSpanHandler.takeLocalSpan().tags()) + .containsOnlyKeys("kafka.streams.application.id", "kafka.streams.task.id"); + streams.close(); streams.cleanUp(); } @@ -290,6 +1412,11 @@ Properties streamsProperties() { return properties; } + KafkaStreams buildKafkaStreamsWithoutTracing(Topology topology) { + Properties properties = streamsProperties(); + return new KafkaStreams(topology, properties); + } + Producer createProducer() { return kafka.createStringProducer(); } diff --git a/instrumentation/kafka-streams/src/test/java/brave/kafka/streams/KafkaStreamsTest.java b/instrumentation/kafka-streams/src/test/java/brave/kafka/streams/KafkaStreamsTest.java index a71de05da5..bdb2715c94 100644 --- a/instrumentation/kafka-streams/src/test/java/brave/kafka/streams/KafkaStreamsTest.java +++ b/instrumentation/kafka-streams/src/test/java/brave/kafka/streams/KafkaStreamsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -23,7 +23,16 @@ import java.util.function.Function; import java.util.function.Supplier; import org.apache.kafka.common.header.Headers; +import org.apache.kafka.streams.KeyValue; +import org.apache.kafka.streams.kstream.Transformer; +import org.apache.kafka.streams.kstream.TransformerSupplier; +import org.apache.kafka.streams.kstream.ValueTransformer; +import org.apache.kafka.streams.kstream.ValueTransformerSupplier; +import org.apache.kafka.streams.kstream.ValueTransformerWithKey; +import org.apache.kafka.streams.kstream.ValueTransformerWithKeySupplier; +import org.apache.kafka.streams.processor.AbstractProcessor; import org.apache.kafka.streams.processor.ProcessorContext; +import org.apache.kafka.streams.processor.ProcessorSupplier; import org.apache.kafka.streams.processor.TaskId; import org.apache.kafka.streams.processor.api.Record; @@ -73,6 +82,16 @@ class KafkaStreamsTest { return processorContext; }; + ProcessorSupplier fakeProcessorSupplier = + kafkaStreamsTracing.processor( + "forward-1", () -> + new AbstractProcessor() { + @Override + public void process(String key, String value) { + context().forward(key, value); + } + }); + org.apache.kafka.streams.processor.api.ProcessorSupplier fakeV2ProcessorSupplier = kafkaStreamsTracing.process( "forward-1", () -> @@ -87,4 +106,67 @@ public void process(Record record) { context.forward(record); } }); + + TransformerSupplier> fakeTransformerSupplier = + kafkaStreamsTracing.transformer( + "transformer-1", () -> + new Transformer>() { + ProcessorContext context; + + @Override + public void init(ProcessorContext context) { + this.context = context; + } + + @Override + public KeyValue transform(String key, String value) { + return KeyValue.pair(key, value); + } + + @Override + public void close() { + } + }); + + ValueTransformerSupplier fakeValueTransformerSupplier = + kafkaStreamsTracing.valueTransformer( + "value-transformer-1", () -> + new ValueTransformer() { + ProcessorContext context; + + @Override + public void init(ProcessorContext context) { + this.context = context; + } + + @Override + public String transform(String value) { + return value; + } + + @Override + public void close() { + } + }); + + ValueTransformerWithKeySupplier fakeValueTransformerWithKeySupplier = + kafkaStreamsTracing.valueTransformerWithKey( + "value-transformer-1", () -> + new ValueTransformerWithKey() { + ProcessorContext context; + + @Override + public void init(ProcessorContext context) { + this.context = context; + } + + @Override + public String transform(String key, String value) { + return value; + } + + @Override + public void close() { + } + }); } diff --git a/instrumentation/kafka-streams/src/test/java/brave/kafka/streams/KafkaStreamsTracingTest.java b/instrumentation/kafka-streams/src/test/java/brave/kafka/streams/KafkaStreamsTracingTest.java index 76655849b3..1b257af55d 100644 --- a/instrumentation/kafka-streams/src/test/java/brave/kafka/streams/KafkaStreamsTracingTest.java +++ b/instrumentation/kafka-streams/src/test/java/brave/kafka/streams/KafkaStreamsTracingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -15,12 +15,19 @@ import brave.Span; import brave.propagation.CurrentTraceContext.Scope; -import java.util.Date; import org.apache.kafka.common.header.Headers; import org.apache.kafka.common.header.internals.RecordHeaders; +import org.apache.kafka.streams.KeyValue; +import org.apache.kafka.streams.kstream.Transformer; +import org.apache.kafka.streams.kstream.ValueTransformer; +import org.apache.kafka.streams.kstream.ValueTransformerWithKey; +import org.apache.kafka.streams.processor.AbstractProcessor; +import org.apache.kafka.streams.processor.Processor; import org.apache.kafka.streams.processor.ProcessorContext; +import org.apache.kafka.streams.processor.ProcessorSupplier; import org.apache.kafka.streams.processor.api.Record; import org.junit.jupiter.api.Test; +import java.util.Date; import static brave.test.ITRemote.BAGGAGE_FIELD; import static brave.test.ITRemote.BAGGAGE_FIELD_KEY; @@ -31,7 +38,7 @@ public class KafkaStreamsTracingTest extends KafkaStreamsTest { @Test void nextSpan_uses_current_context() { ProcessorContext fakeProcessorContext = processorContextSupplier.apply(new RecordHeaders()); Span child; - try (Scope scope = tracing.currentTraceContext().newScope(parent)) { + try (Scope ws = tracing.currentTraceContext().newScope(parent)) { child = kafkaStreamsTracing.nextSpan(fakeProcessorContext); } child.finish(); @@ -43,7 +50,7 @@ public class KafkaStreamsTracingTest extends KafkaStreamsTest { @Test void nextSpanWithHeaders_uses_current_context() { org.apache.kafka.streams.processor.api.ProcessorContext fakeProcessorContext = processorV2ContextSupplier.get(); Span child; - try (Scope scope = tracing.currentTraceContext().newScope(parent)) { + try (Scope ws = tracing.currentTraceContext().newScope(parent)) { child = kafkaStreamsTracing.nextSpan(fakeProcessorContext, new RecordHeaders()); } child.finish(); @@ -82,6 +89,17 @@ public class KafkaStreamsTracingTest extends KafkaStreamsTest { entry("kafka.streams.task.id", TEST_TASK_ID)); } + @Test void processorSupplier_should_tag_app_id_and_task_id() { + Processor processor = fakeProcessorSupplier.get(); + processor.init(processorContextSupplier.apply(new RecordHeaders())); + processor.process(TEST_KEY, TEST_VALUE); + + assertThat(spans.get(0).tags()) + .containsOnly( + entry("kafka.streams.application.id", TEST_APPLICATION_ID), + entry("kafka.streams.task.id", TEST_TASK_ID)); + } + @Test void newProcessorSupplier_should_tag_app_id_and_task_id() { org.apache.kafka.streams.processor.api.Processor processor = fakeV2ProcessorSupplier.get(); processor.init(processorV2ContextSupplier.get()); @@ -93,6 +111,22 @@ public class KafkaStreamsTracingTest extends KafkaStreamsTest { entry("kafka.streams.task.id", TEST_TASK_ID)); } + @Test void processorSupplier_should_add_baggage_field() { + ProcessorSupplier processorSupplier = + kafkaStreamsTracing.processor( + "forward-1", () -> + new AbstractProcessor() { + @Override + public void process(String key, String value) { + assertThat(BAGGAGE_FIELD.getValue(currentTraceContext.get())).isEqualTo("user1"); + } + }); + Headers headers = new RecordHeaders().add(BAGGAGE_FIELD_KEY, "user1".getBytes()); + Processor processor = processorSupplier.get(); + processor.init(processorContextSupplier.apply(headers)); + processor.process(TEST_KEY, TEST_VALUE); + } + @Test void newProcessorSupplier_should_add_baggage_field() { org.apache.kafka.streams.processor.api.ProcessorSupplier processorSupplier = kafkaStreamsTracing.process( @@ -104,4 +138,38 @@ public class KafkaStreamsTracingTest extends KafkaStreamsTest { processor.init(processorV2ContextSupplier.get()); processor.process(new Record<>(TEST_KEY, TEST_VALUE, new Date().getTime(), headers)); } + + @Test void transformSupplier_should_tag_app_id_and_task_id() { + Transformer> processor = fakeTransformerSupplier.get(); + processor.init(processorContextSupplier.apply(new RecordHeaders())); + processor.transform(TEST_KEY, TEST_VALUE); + + assertThat(spans.get(0).tags()) + .containsOnly( + entry("kafka.streams.application.id", TEST_APPLICATION_ID), + entry("kafka.streams.task.id", TEST_TASK_ID)); + } + + @Test void valueTransformSupplier_should_tag_app_id_and_task_id() { + ValueTransformer processor = fakeValueTransformerSupplier.get(); + processor.init(processorContextSupplier.apply(new RecordHeaders())); + processor.transform(TEST_VALUE); + + assertThat(spans.get(0).tags()) + .containsOnly( + entry("kafka.streams.application.id", TEST_APPLICATION_ID), + entry("kafka.streams.task.id", TEST_TASK_ID)); + } + + @Test void valueTransformWithKeySupplier_should_tag_app_id_and_task_id() { + ValueTransformerWithKey processor = + fakeValueTransformerWithKeySupplier.get(); + processor.init(processorContextSupplier.apply(new RecordHeaders())); + processor.transform(TEST_KEY, TEST_VALUE); + + assertThat(spans.get(0).tags()) + .containsOnly( + entry("kafka.streams.application.id", TEST_APPLICATION_ID), + entry("kafka.streams.task.id", TEST_TASK_ID)); + } } diff --git a/instrumentation/kafka-streams/src/test/java/brave/kafka/streams/TracingKafkaClientSupplierTests.java b/instrumentation/kafka-streams/src/test/java/brave/kafka/streams/TracingKafkaClientSupplierTests.java index c1990417f5..6fd4c6c906 100644 --- a/instrumentation/kafka-streams/src/test/java/brave/kafka/streams/TracingKafkaClientSupplierTests.java +++ b/instrumentation/kafka-streams/src/test/java/brave/kafka/streams/TracingKafkaClientSupplierTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -15,6 +15,7 @@ import java.util.Collections; import java.util.Map; + import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.streams.KafkaClientSupplier; @@ -27,8 +28,10 @@ class TracingKafkaClientSupplierTests { final Map props = Collections.singletonMap("bootstrap.servers","localhost:9092"); - @Test void shouldReturnNewAdmin() { + @Test + public void shouldReturnNewAdminClient() { TracingKafkaClientSupplier supplier = new TracingKafkaClientSupplier(null); + assertThat(supplier.getAdminClient(props)).isNotNull(); assertThat(supplier.getAdmin(props)).isNotNull(); } diff --git a/instrumentation/messaging/RATIONALE.md b/instrumentation/messaging/RATIONALE.md index 38d6387558..ea9bab1f47 100644 --- a/instrumentation/messaging/RATIONALE.md +++ b/instrumentation/messaging/RATIONALE.md @@ -9,7 +9,7 @@ span in scope. This is due to ordering precedence of tools like Tools that extract incoming IDs from message requests prioritize headers over the current span (`Tracer.currentSpan()`). This is to prevent an -accidentally leaked span (due to lack of `scope.close()`) to become the parent +accidentally leaked span (due to lack of `Scope.close()`) to become the parent of all spans, creating a huge trace. This logic is the same in RPC server instrumentation. When we are in control of invoking the message listener, we can guarantee no mistakes by clearing the headers first. diff --git a/instrumentation/messaging/src/main/java/brave/messaging/ConsumerRequest.java b/instrumentation/messaging/src/main/java/brave/messaging/ConsumerRequest.java index fa690d7b26..90d00d0b27 100644 --- a/instrumentation/messaging/src/main/java/brave/messaging/ConsumerRequest.java +++ b/instrumentation/messaging/src/main/java/brave/messaging/ConsumerRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,6 +14,7 @@ package brave.messaging; import brave.Span; +import brave.internal.Nullable; import static brave.Span.Kind.CONSUMER; diff --git a/instrumentation/messaging/src/main/java/brave/messaging/MessagingTracing.java b/instrumentation/messaging/src/main/java/brave/messaging/MessagingTracing.java index cf7b475458..814921809a 100644 --- a/instrumentation/messaging/src/main/java/brave/messaging/MessagingTracing.java +++ b/instrumentation/messaging/src/main/java/brave/messaging/MessagingTracing.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/instrumentation/messaging/src/test/java/brave/messaging/MessagingTracingTest.java b/instrumentation/messaging/src/test/java/brave/messaging/MessagingTracingTest.java index ae2bfd5148..ee27d12d40 100644 --- a/instrumentation/messaging/src/test/java/brave/messaging/MessagingTracingTest.java +++ b/instrumentation/messaging/src/test/java/brave/messaging/MessagingTracingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/instrumentation/okhttp3/src/main/java/brave/okhttp3/TracingInterceptor.java b/instrumentation/okhttp3/src/main/java/brave/okhttp3/TracingInterceptor.java index 0791b9d6bf..d7ed3fee39 100644 --- a/instrumentation/okhttp3/src/main/java/brave/okhttp3/TracingInterceptor.java +++ b/instrumentation/okhttp3/src/main/java/brave/okhttp3/TracingInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -70,7 +70,7 @@ public static Interceptor create(HttpTracing httpTracing) { Response response = null; Throwable error = null; - try (Scope scope = currentTraceContext.newScope(span.context())) { + try (Scope ws = currentTraceContext.newScope(span.context())) { return response = chain.proceed(request.build()); } catch (Throwable t) { error = t; diff --git a/instrumentation/okhttp3/src/test/java/brave/okhttp3/features/ITRetrofit.java b/instrumentation/okhttp3/src/test/java/brave/okhttp3/features/ITRetrofit.java index 102cb771dc..c8f72107a7 100644 --- a/instrumentation/okhttp3/src/test/java/brave/okhttp3/features/ITRetrofit.java +++ b/instrumentation/okhttp3/src/test/java/brave/okhttp3/features/ITRetrofit.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -82,5 +82,8 @@ interface Service { .containsKey("http.path") .withFailMessage("HTTP span wasn't reported!") .isNotNull(); + + // Context propagation, ex using RxJava2 or similar to follow-up with another call, + // is a different matter. See brave-context-rxjava2 } } diff --git a/instrumentation/p6spy/README.md b/instrumentation/p6spy/README.md new file mode 100644 index 0000000000..de9bf98b53 --- /dev/null +++ b/instrumentation/p6spy/README.md @@ -0,0 +1,72 @@ +# brave-instrumentation-p6spy + +## Deprecated + +P6Spy hasn't been released since July 2020. This module will be removed in +Brave v6. + +## Overview + +This includes a tracing event listener for [P6Spy](https://github.com/p6spy/p6spy) (a proxy for calls to your JDBC driver). +It reports to Zipkin how long each statement takes, along with relevant tags like the query. + + +P6Spy requires a `spy.properties` in your application classpath +(ex `src/main/resources`). `brave.p6spy.TracingP6Factory` must be in the +`modulelist` to enable tracing. + +``` +modulelist=brave.p6spy.TracingP6Factory +url=jdbc:p6spy:derby:memory:p6spy;create=true +``` + +## Options +In addition to the required settings above, you can specify additional options to affect what is +included in the traces. + +### Remove Service Name +By default the zipkin service name for your database is the name of the database. +Set the `remoteServiceName` property to override it: + +``` +remoteServiceName=myProductionDatabase +``` + +### Parameter values +When the `includeParameterValues` option is set to `true`, the tag `sql.query` will also include the +JDBC parameter values. + +**Note**: if you enable this please also consider enabling `excludebinary` to avoid logging large +blob values as hex (see http://p6spy.readthedocs.io/en/latest/configandusage.html#excludebinary). + +``` +includeParameterValues=true +excludebinary=true +``` + +### Affected rows count +When the `includeAffectedRowsCount` option is set to `true`, the tag `sql.affected_rows` of traces +for SQL insert and update statements performed using `executeUpdate` and `executeBatch` (but not +`execute`, as it does not report the update count) will include the number of rows that were +inserted/updated (comma separated in the case of `executebatch`), if the database and driver +supports that. No row count is included for calls to `executeQuery` (e.g. select statements). + +## Service name as URL query parameter +`spy.properties` applies globally to any instrumented jdbc connection. To override this, add the +`zipkinServiceName` property to your connection string. + +``` +jdbc:mysql://127.0.0.1:3306/mydatabase?zipkinServiceName=myServiceName +``` + +This will override the `remoteServiceName` set in `spy.properties`. + +The current tracing component is used at runtime. Until you have instantiated `brave.Tracing`, no traces will appear. + +## Filtering spans + +By default, all statements are recorded as client spans. +You may wish to exclude statements like `set session` from tracing. This library reuses p6spy's log filtering for this purpose. +Filtering options are picked up from `spy.properties`, so you can blacklist/whitelist what type of statements to record as spans. + +For configuration details please see p6spy's log filtering documentation. diff --git a/instrumentation/p6spy/bnd.bnd b/instrumentation/p6spy/bnd.bnd new file mode 100644 index 0000000000..4d71a7af17 --- /dev/null +++ b/instrumentation/p6spy/bnd.bnd @@ -0,0 +1,6 @@ +# We use brave.internal.Nullable, but it is not used at runtime. +Import-Package: \ + !brave.internal*,\ + * +Export-Package: \ + brave.p6spy diff --git a/instrumentation/p6spy/pom.xml b/instrumentation/p6spy/pom.xml new file mode 100644 index 0000000000..f9f03fca4c --- /dev/null +++ b/instrumentation/p6spy/pom.xml @@ -0,0 +1,110 @@ + + + + + io.zipkin.brave + brave-instrumentation-parent + 5.18.1-SNAPSHOT + + 4.0.0 + + brave-instrumentation-p6spy + Brave Instrumentation: P6Spy (JDBC) + + + + brave.p6spy + + ${project.basedir}/../.. + + 3.9.1 + 10.15.2.0 + + + + + + p6spy + p6spy + ${p6spy.version} + + + + + org.apache.derby + derby + ${derby.version} + test + + + org.apache.derby + derbyclient + ${derby.version} + test + + + + org.apache.derby + derbyshared + ${derby.version} + test + + + ${project.groupId} + brave-tests + ${project.version} + test + + + + + + release + + + 1.6 + 1.6 + 6 + + + + + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + enforce-java + + enforce + + + + + + [11,12) + + + + + + + + + + + diff --git a/instrumentation/p6spy/src/main/java/brave/p6spy/TracingJdbcEventListener.java b/instrumentation/p6spy/src/main/java/brave/p6spy/TracingJdbcEventListener.java new file mode 100644 index 0000000000..e562c4bcc4 --- /dev/null +++ b/instrumentation/p6spy/src/main/java/brave/p6spy/TracingJdbcEventListener.java @@ -0,0 +1,178 @@ +/* + * Copyright 2013-2022 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.p6spy; + +import brave.Span; +import brave.internal.Nullable; +import brave.propagation.ThreadLocalSpan; + +import com.p6spy.engine.common.PreparedStatementInformation; +import com.p6spy.engine.common.StatementInformation; +import com.p6spy.engine.event.SimpleJdbcEventListener; +import com.p6spy.engine.logging.P6LogLoadableOptions; +import java.net.URI; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static brave.Span.Kind.CLIENT; + +final class TracingJdbcEventListener extends SimpleJdbcEventListener { + + // Captures all the characters between = and either the next & or the end of the string. + private static final Pattern URL_SERVICE_NAME_FINDER = + Pattern.compile("zipkinServiceName=(.*?)(?:&|$)"); + + @Nullable final String remoteServiceName; + final boolean includeParameterValues; + final boolean includeAffectedRowsCount; + final P6LogLoadableOptions logOptions; + + TracingJdbcEventListener(@Nullable String remoteServiceName, boolean includeParameterValues, + P6LogLoadableOptions logOptions) { + this(remoteServiceName, includeParameterValues, false, logOptions); + } + + TracingJdbcEventListener(@Nullable String remoteServiceName, boolean includeParameterValues, + boolean includeAffectedRowsCount, P6LogLoadableOptions logOptions) { + this.remoteServiceName = remoteServiceName; + this.includeParameterValues = includeParameterValues; + this.includeAffectedRowsCount = includeAffectedRowsCount; + this.logOptions = logOptions; + } + + /** + * Uses {@link ThreadLocalSpan} as there's no attribute namespace shared between callbacks, but + * all callbacks happen on the same thread. + * + *

Uses {@link ThreadLocalSpan#CURRENT_TRACER} and this interceptor initializes before + * tracing. + */ + @Override public void onBeforeAnyExecute(StatementInformation info) { + String sql = includeParameterValues ? info.getSqlWithValues() : info.getSql(); + if (!isLoggable(sql)) return; + + // Gets the next span (and places it in scope) so code between here and postProcess can read it + Span span = ThreadLocalSpan.CURRENT_TRACER.next(); + if (span == null || span.isNoop()) return; + + int spaceIndex = sql.indexOf(' '); // Allow span names of single-word statements like COMMIT + span.kind(CLIENT).name(spaceIndex == -1 ? sql : sql.substring(0, spaceIndex)); + span.tag("sql.query", sql); + parseServerIpAndPort(info.getConnectionInformation().getConnection(), span); + span.start(); + } + + @Override public void onAfterExecuteUpdate( + PreparedStatementInformation statementInformation, long timeElapsedNanos, int rowCount, SQLException e + ) { + Span span = ThreadLocalSpan.CURRENT_TRACER.remove(); + if (span == null || span.isNoop()) return; + if (includeAffectedRowsCount) { + span.tag("sql.affected_rows", String.valueOf(rowCount)); + } + finishSpan(span, e); + } + + @Override public void onAfterExecuteUpdate( + StatementInformation statementInformation, long timeElapsedNanos, String sql, int rowCount, SQLException e + ) { + Span span = ThreadLocalSpan.CURRENT_TRACER.remove(); + if (span == null || span.isNoop()) return; + if (includeAffectedRowsCount) { + span.tag("sql.affected_rows", String.valueOf(rowCount)); + } + finishSpan(span, e); + } + + @Override + public void onAfterExecuteBatch(StatementInformation statementInformation, long timeElapsedNanos, int[] updateCounts, SQLException e) { + Span span = ThreadLocalSpan.CURRENT_TRACER.remove(); + if (span == null || span.isNoop()) return; + if (includeAffectedRowsCount && updateCounts.length > 0) { + StringBuilder sb = new StringBuilder(); + sb.append(updateCounts[0]); + for (int i = 1; i < updateCounts.length; i++) { + sb.append(','); + sb.append(updateCounts[i]); + } + span.tag("sql.affected_rows", sb.toString()); + } + finishSpan(span, e); + } + + @Override public void onAfterAnyExecute(StatementInformation info, long elapsed, SQLException e) { + Span span = ThreadLocalSpan.CURRENT_TRACER.remove(); + if (span == null || span.isNoop()) return; + finishSpan(span, e); + } + + private void finishSpan(Span span, SQLException e) { + if (e != null) { + span.error(e); + span.tag("error", Integer.toString(e.getErrorCode())); + } + span.finish(); + } + + boolean isLoggable(String sql) { + // don't start a span unless there is SQL as we cannot choose a relevant name without it + // empty batches and connection commits/rollbacks + if (sql == null || sql.isEmpty()) { + return false; + } + if (!logOptions.getFilter()) { + return true; + } + + final Pattern sqlExpressionPattern = logOptions.getSQLExpressionPattern(); + final Pattern includeExcludePattern = logOptions.getIncludeExcludePattern(); + + return (sqlExpressionPattern == null || sqlExpressionPattern.matcher(sql).matches()) + && (includeExcludePattern == null || includeExcludePattern.matcher(sql).matches()); + } + + /** + * This attempts to get the ip and port from the JDBC URL. Ex. localhost and 5555 from {@code + * jdbc:mysql://localhost:5555/mydatabase}. + */ + void parseServerIpAndPort(Connection connection, Span span) { + try { + String urlAsString = connection.getMetaData().getURL().substring(5); // strip "jdbc:" + URI url = + URI.create(urlAsString.replace(" ", "")); // Remove all white space according to RFC 2396 + String defaultRemoteServiceName = remoteServiceName; + Matcher matcher = URL_SERVICE_NAME_FINDER.matcher(url.toString()); + if (matcher.find() && matcher.groupCount() == 1) { + String parsedServiceName = matcher.group(1); + if (parsedServiceName != null + && !parsedServiceName.isEmpty()) { // Do not override global service name if parsed service name is invalid + defaultRemoteServiceName = parsedServiceName; + } + } + if (defaultRemoteServiceName == null || "".equals(defaultRemoteServiceName)) { + String databaseName = connection.getCatalog(); + if (databaseName != null && !databaseName.isEmpty()) { + span.remoteServiceName(databaseName); + } + } else { + span.remoteServiceName(defaultRemoteServiceName); + } + span.remoteIpAndPort(url.getHost(), url.getPort()); + } catch (Exception e) { + // remote address is optional + } + } +} diff --git a/instrumentation/p6spy/src/main/java/brave/p6spy/TracingP6Factory.java b/instrumentation/p6spy/src/main/java/brave/p6spy/TracingP6Factory.java new file mode 100644 index 0000000000..1cca035d29 --- /dev/null +++ b/instrumentation/p6spy/src/main/java/brave/p6spy/TracingP6Factory.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.p6spy; + +import com.p6spy.engine.event.JdbcEventListener; +import com.p6spy.engine.spy.P6Factory; +import com.p6spy.engine.spy.P6LoadableOptions; +import com.p6spy.engine.spy.option.P6OptionsRepository; + +/** + * Add this class name to the "moduleslist" in spy.properties + * + * @deprecated P6Spy hasn't been released since July 2020. Tracing support will be removed in Brave + * v6. + */ +@Deprecated +public final class TracingP6Factory implements P6Factory { + + TracingP6SpyOptions options; + + @Override public P6LoadableOptions getOptions(P6OptionsRepository repository) { + return options = new TracingP6SpyOptions(repository); + } + + @Override public JdbcEventListener getJdbcEventListener() { + return new TracingJdbcEventListener(options.remoteServiceName(), + options.includeParameterValues(), options.includeAffectedRowsCount(), + options.getLogOptions()); + } +} diff --git a/instrumentation/p6spy/src/main/java/brave/p6spy/TracingP6SpyOptions.java b/instrumentation/p6spy/src/main/java/brave/p6spy/TracingP6SpyOptions.java new file mode 100644 index 0000000000..2177d9a181 --- /dev/null +++ b/instrumentation/p6spy/src/main/java/brave/p6spy/TracingP6SpyOptions.java @@ -0,0 +1,73 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.p6spy; + +import com.p6spy.engine.logging.P6LogLoadableOptions; +import com.p6spy.engine.logging.P6LogOptions; +import com.p6spy.engine.spy.P6SpyOptions; +import com.p6spy.engine.spy.option.P6OptionsRepository; +import java.util.LinkedHashMap; +import java.util.Map; + +final class TracingP6SpyOptions extends P6SpyOptions { + + static final String REMOTE_SERVICE_NAME = "remoteServiceName"; + static final String INCLUDE_PARAMETER_VALUES = "includeParameterValues"; + static final String INCLUDE_AFFECTED_ROWS_COUNT = "includeAffectedRowsCount"; + + private final P6OptionsRepository optionsRepository; + private final P6LogLoadableOptions logLoadableOptions; + + TracingP6SpyOptions(P6OptionsRepository optionsRepository) { + super(optionsRepository); + logLoadableOptions = new P6LogOptions(optionsRepository); + this.optionsRepository = optionsRepository; + } + + @Override + public void load(Map options) { + super.load(options); + logLoadableOptions.load(options); + optionsRepository.set(String.class, REMOTE_SERVICE_NAME, options.get(REMOTE_SERVICE_NAME)); + optionsRepository.set(Boolean.class, INCLUDE_PARAMETER_VALUES, + options.get(INCLUDE_PARAMETER_VALUES)); + optionsRepository.set(Boolean.class, INCLUDE_AFFECTED_ROWS_COUNT, + options.get(INCLUDE_AFFECTED_ROWS_COUNT)); + } + + @Override + public Map getDefaults() { + Map allDefaults = new LinkedHashMap(super.getDefaults()); + allDefaults.putAll(logLoadableOptions.getDefaults()); + allDefaults.put(INCLUDE_PARAMETER_VALUES, Boolean.FALSE.toString()); + allDefaults.put(INCLUDE_AFFECTED_ROWS_COUNT, Boolean.FALSE.toString()); + return allDefaults; + } + + P6LogLoadableOptions getLogOptions() { + return logLoadableOptions; + } + + String remoteServiceName() { + return optionsRepository.get(String.class, REMOTE_SERVICE_NAME); + } + + Boolean includeParameterValues() { + return optionsRepository.get(Boolean.class, INCLUDE_PARAMETER_VALUES); + } + + Boolean includeAffectedRowsCount() { + return optionsRepository.get(Boolean.class, INCLUDE_AFFECTED_ROWS_COUNT); + } +} diff --git a/instrumentation/p6spy/src/test/java/brave/p6spy/DerbyUtils.java b/instrumentation/p6spy/src/test/java/brave/p6spy/DerbyUtils.java new file mode 100644 index 0000000000..0ae4cfac69 --- /dev/null +++ b/instrumentation/p6spy/src/test/java/brave/p6spy/DerbyUtils.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2019 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.p6spy; + +import java.io.OutputStream; + +public class DerbyUtils { + + //Get rid of the annoying derby.log file + public static void disableLog() { + System.setProperty("derby.stream.error.field", DerbyUtils.class.getName() + ".DEV_NULL"); + } + + public static final OutputStream DEV_NULL = new OutputStream() { + public void write(int b) { + } + }; +} diff --git a/instrumentation/p6spy/src/test/java/brave/p6spy/ITTracingP6Factory.java b/instrumentation/p6spy/src/test/java/brave/p6spy/ITTracingP6Factory.java new file mode 100644 index 0000000000..c980d4c8fa --- /dev/null +++ b/instrumentation/p6spy/src/test/java/brave/p6spy/ITTracingP6Factory.java @@ -0,0 +1,250 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.p6spy; + +import brave.ScopedSpan; +import brave.Span.Kind; +import brave.Tracing; +import brave.handler.MutableSpan; +import brave.propagation.StrictCurrentTraceContext; +import brave.test.TestSpanHandler; +import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Optional; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +class ITTracingP6Factory { + String testName; + + static final String QUERY = "SELECT 1 FROM SYSIBM.SYSDUMMY1"; + + //Get rid of annoying derby.log + static { + DerbyUtils.disableLog(); + } + + /** JDBC is synchronous and we aren't using thread pools: everything happens on the main thread */ + StrictCurrentTraceContext currentTraceContext = StrictCurrentTraceContext.create(); + TestSpanHandler spans = new TestSpanHandler(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(currentTraceContext).addSpanHandler(spans).build(); + Connection connection; + + @BeforeEach void setup(TestInfo testInfo) throws Exception { + Optional testMethod = testInfo.getTestMethod(); + if (testMethod.isPresent()) { + this.testName = testMethod.get().getName(); + } + String url = String.format("jdbc:p6spy:derby:memory:%s;create=true", testName); + connection = DriverManager.getConnection(url, "foo", "bar"); + Statement statement = connection.createStatement(); + statement.executeUpdate("create table t (i integer, c char )"); + statement.executeUpdate("insert into t (i, c) values (1, 'a')"); + statement.executeUpdate("insert into t (i, c) values (2, 'b')"); + statement.executeUpdate("insert into t (i, c) values (2, 'c')"); + statement.close(); + spans.clear(); + } + + @AfterEach void close() throws Exception { + if (connection != null) connection.close(); + tracing.close(); + currentTraceContext.close(); + } + + @Test void makesChildOfCurrentSpan() throws Exception { + ScopedSpan parent = tracing.tracer().startScopedSpan("test"); + try { + prepareExecuteSelect(QUERY); + } finally { + parent.finish(); + } + + assertThat(spans) + .hasSize(2); + } + + @Test void reportsClientKindToZipkin() throws Exception { + prepareExecuteSelect(QUERY); + + assertThat(spans) + .extracting(MutableSpan::kind) + .containsExactly(Kind.CLIENT); + } + + @Test void defaultSpanNameIsOperationName() throws Exception { + prepareExecuteSelect(QUERY); + + assertThat(spans) + .extracting(MutableSpan::name) + .containsExactly("SELECT"); + } + + @Test void addsQueryTag() throws Exception { + prepareExecuteSelect(QUERY); + + assertThat(spans) + .flatExtracting(s -> s.tags().entrySet()) + .containsExactly(entry("sql.query", QUERY)); + } + + @Test void addsAffectedRowsTagToPreparedUpdateStatements() throws Exception { + prepareExecuteUpdate("update t set c='x' where i=2"); + + assertThat(spans) + .flatExtracting(s -> s.tags().entrySet()) + .contains(entry("sql.affected_rows", "2")); + } + + @Test void addsAffectedRowsTagToPlainUpdateStatements() throws Exception { + executeUpdate("update t set c='x' where i=2"); + + assertThat(spans) + .flatExtracting(s -> s.tags().entrySet()) + .contains(entry("sql.affected_rows", "2")); + } + + @Test void addsAffectedRowsTagToPlainBatchUpdateStatements() throws Exception { + executeBatch("update t set c='x' where i=2", "update t set c='y' where i=1"); + + assertThat(spans) + .flatExtracting(s -> s.tags().entrySet()) + .contains(entry("sql.affected_rows", "2,1")); + } + + @Test void doesNotProduceAnySpansForEmptyPlainBatchUpdates() throws Exception { + // No SQL at all means no span is started in onBeforeAnyExecute due to there not being any loggable SQL + // (see isLoggable) + executeBatch(); + + assertThat(spans).isEmpty(); + } + + @Test void addsAffectedRowsTagToPreparedBatchUpdateStatementsWithOneBatch() throws Exception { + prepareExecuteBatchWithInts("update t set c='x' where i=?", 2); + + assertThat(spans) + .flatExtracting(s -> s.tags().entrySet()) + .contains(entry("sql.affected_rows", "2")); + } + + @Test void addsAffectedRowsTagToPreparedBatchUpdateStatementsWithOneBatchWithZeroUpdates() + throws Exception { + prepareExecuteBatchWithInts("update t set c='x' where i=?", 0); + + assertThat(spans) + .flatExtracting(s -> s.tags().entrySet()) + .contains(entry("sql.affected_rows", "0")); + } + + @Test void addsAffectedRowsTagToPreparedBatchUpdateStatementsWithMoreThanOneBatch() + throws Exception { + prepareExecuteBatchWithInts("update t set c='x' where i=?", 2, 1); + + assertThat(spans) + .flatExtracting(s -> s.tags().entrySet()) + .contains(entry("sql.affected_rows", "2,1")); + } + + @Test void addsAffectedRowsTagToPreparedBatchUpdateStatementsWithMoreThanOneBatchWhereOneBatcheHasZeroUpdates() + throws Exception { + prepareExecuteBatchWithInts("update t set c='x' where i=?", 2, 0, 1); + + assertThat(spans) + .flatExtracting(s -> s.tags().entrySet()) + .contains(entry("sql.affected_rows", "2,0,1")); + } + + @Test void addsAffectedRowsTagToPreparedBatchUpdateStatementsWithMoreThanOneBatchWhereBatchesHaveZeroUpdates() + throws Exception { + prepareExecuteBatchWithInts("update t set c='x' where i=?", 2, 0, 3); + + assertThat(spans) + .flatExtracting(s -> s.tags().entrySet()) + .contains(entry("sql.affected_rows", "2,0,0")); + } + + @Test void addsEmptyAffectedRowsTagToEmptyPreparedBatchUpdates() throws Exception { + // In contrast to the plain statement case, this does produce loggable SQL, so a span is started. Since there are + // no entries in the batch, no updates are made, so there are no update counts. Therefore, the span does not have + // any sql.affected_rows tag. + prepareExecuteBatchWithInts("update t set c='x' where i=?"); + + assertThat(spans).anySatisfy(span -> { + assertThat(span.tags()) + .contains(entry("sql.query", "update t set c='x' where i=?")) + .doesNotContainKey("sql.affected_rows"); + }); + } + + @Test void reportsServerAddress() throws Exception { + prepareExecuteSelect(QUERY); + + assertThat(spans) + .extracting(MutableSpan::remoteServiceName) + .containsExactly("myservice"); + } + + void prepareExecuteSelect(String query) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement(query)) { + try (ResultSet resultSet = ps.executeQuery()) { + while (resultSet.next()) { + resultSet.getString(1); + } + } + } + } + + void prepareExecuteUpdate(String sql) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement(sql)) { + ps.executeUpdate(); + } + } + + void executeUpdate(String sql) throws SQLException { + try (Statement s = connection.createStatement()) { + s.executeUpdate(sql); + } + } + + void executeBatch(String... sqls) throws SQLException { + try (Statement s = connection.createStatement()) { + for (String sql : sqls) { + s.addBatch(sql); + } + s.executeBatch(); + } + } + + void prepareExecuteBatchWithInts(String sql, int... ints) throws SQLException { + try (PreparedStatement s = connection.prepareStatement(sql)) { + for (int i : ints) { + s.setInt(1, i); + s.addBatch(); + } + s.executeBatch(); + } + } +} diff --git a/instrumentation/p6spy/src/test/java/brave/p6spy/TracingJdbcEventListenerTest.java b/instrumentation/p6spy/src/test/java/brave/p6spy/TracingJdbcEventListenerTest.java new file mode 100644 index 0000000000..49e3a77dc5 --- /dev/null +++ b/instrumentation/p6spy/src/test/java/brave/p6spy/TracingJdbcEventListenerTest.java @@ -0,0 +1,233 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.p6spy; + +import brave.ScopedSpan; +import brave.Span; +import brave.Tracing; +import brave.propagation.StrictCurrentTraceContext; +import brave.test.TestSpanHandler; +import com.p6spy.engine.common.ConnectionInformation; +import com.p6spy.engine.common.StatementInformation; +import com.p6spy.engine.logging.P6LogOptions; +import com.p6spy.engine.spy.option.P6OptionsRepository; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class TracingJdbcEventListenerTest { + @Mock Connection connection; + @Mock DatabaseMetaData metaData; + @Mock StatementInformation statementInformation; + @Mock ConnectionInformation connectionInformation; + + @Mock Span span; + String url = "jdbc:mysql://1.2.3.4:5555/mydatabase"; + String urlWithServiceName = url + "?zipkinServiceName=mysql_service&foo=bar"; + String urlWithEmptyServiceName = url + "?zipkinServiceName=&foo=bar"; + String urlWithWhiteSpace = + "jdbc:sqlserver://1.2.3.4;databaseName=mydatabase;applicationName=Microsoft JDBC Driver for SQL Server"; + P6OptionsRepository p6OptionsRepository; + P6LogOptions logOptions; + + StrictCurrentTraceContext currentTraceContext = StrictCurrentTraceContext.create(); + TestSpanHandler spans = new TestSpanHandler(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(currentTraceContext).addSpanHandler(spans).build(); + + @BeforeEach public void init() { + p6OptionsRepository = new P6OptionsRepository(); + logOptions = new P6LogOptions(p6OptionsRepository); + logOptions.load(logOptions.getDefaults()); + p6OptionsRepository.initCompleted(); + } + + @AfterEach public void close() { + tracing.close(); + currentTraceContext.close(); + } + + @Test void parseServerIpAndPort_IpAndPortFromUrl() throws SQLException { + when(connection.getMetaData()).thenReturn(metaData); + when(metaData.getURL()).thenReturn(url); + + new TracingJdbcEventListener("", false, false, logOptions).parseServerIpAndPort(connection, span); + + verify(span).remoteIpAndPort("1.2.3.4", 5555); + } + + @Test void parseServerIpAndPort_serviceNameFromDatabaseName() throws SQLException { + when(connection.getMetaData()).thenReturn(metaData); + when(metaData.getURL()).thenReturn(url); + when(connection.getCatalog()).thenReturn("mydatabase"); + + new TracingJdbcEventListener("", false, false, logOptions).parseServerIpAndPort(connection, span); + + verify(span).remoteServiceName("mydatabase"); + verify(span).remoteIpAndPort("1.2.3.4", 5555); + } + + @Test void parseServerIpAndPort_serviceNameFromUrl() throws SQLException { + when(connection.getMetaData()).thenReturn(metaData); + when(metaData.getURL()).thenReturn(urlWithServiceName); + + new TracingJdbcEventListener("", false, false, logOptions).parseServerIpAndPort(connection, span); + + verify(span).remoteServiceName("mysql_service"); + verify(span).remoteIpAndPort("1.2.3.4", 5555); + } + + @Test void parseServerIpAndPort_emptyServiceNameFromUrl() throws SQLException { + when(connection.getMetaData()).thenReturn(metaData); + when(metaData.getURL()).thenReturn(urlWithEmptyServiceName); + when(connection.getCatalog()).thenReturn("mydatabase"); + + new TracingJdbcEventListener("", false, false, logOptions).parseServerIpAndPort(connection, span); + + verify(span).remoteServiceName("mydatabase"); + verify(span).remoteIpAndPort("1.2.3.4", 5555); + } + + @Test void parseServerIpAndPort_overrideServiceName() throws SQLException { + when(connection.getMetaData()).thenReturn(metaData); + when(metaData.getURL()).thenReturn(url); + + new TracingJdbcEventListener("foo", false, false, logOptions).parseServerIpAndPort(connection, span); + + verify(span).remoteServiceName("foo"); + verify(span).remoteIpAndPort("1.2.3.4", 5555); + } + + static Object[][] exceptionsTraced() { + return new Object[][] { + {"?zipkinServiceName=myDatabase&foo=bar", "myDatabase"}, + {"?zipkinServiceName=my_database&foo=bar", "my_database"}, + {"?zipkinServiceName=my-database&foo=bar", "my-database"}, + {"?zipkinServiceName=my-database-1&foo=bar", "my-database-1"}, + {"?zipkinServiceName=my.database&foo=bar", "my.database"}, + {"?zipkinServiceName=my-database:5432&foo=bar", "my-database:5432"}, + {"?zipkinServiceName=my-database@localhost&foo=bar", "my-database@localhost"}, + {"?zipkinServiceName=my-database", "my-database"}, + {"?zipkinServiceName=my-database-1&zipkinServiceName=my-database-2", "my-database-1"}, + {"?zipkinServiceName=", null}, + {"?zipkinServiceName=&", null}, + {"", null} + }; + } + + @ExtendWith(MockitoExtension.class) + @Nested + class ParserTest { + @Mock Connection connection; + @Mock DatabaseMetaData metaData; + + @Mock Span span; + String queryString; + String remoteServiceName; + + @MethodSource("brave.p6spy.TracingJdbcEventListenerTest#exceptionsTraced") + @ParameterizedTest(name = "remoteServiceName for {0} should be '{1}'") + void parseServerIpAndPort_overridesRemoteServiceNameFromUrlParameter(String queryString, String remoteServiceName) + throws SQLException { + initParserTest(queryString, remoteServiceName); + when(connection.getMetaData()).thenReturn(metaData); + when(metaData.getURL()).thenReturn("jdbc:mysql://1.2.3.4:5555/mydatabase" + queryString); + + new TracingJdbcEventListener(null, false, false, + P6LogOptions.getActiveInstance()).parseServerIpAndPort(connection, span); + + if (remoteServiceName != null) { // shouldn't invoke if no service name was parsed + verify(span).remoteServiceName(remoteServiceName); + } + verify(span).remoteIpAndPort("1.2.3.4", 5555); + } + + public void initParserTest(String queryString, String remoteServiceName) { + this.queryString = queryString; + this.remoteServiceName = remoteServiceName; + } + } + + @Test void parseServerIpAndPort_doesntCrash() throws SQLException { + when(connection.getMetaData()).thenThrow(new SQLException()); + + new TracingJdbcEventListener("", false, false, logOptions).parseServerIpAndPort(connection, span); + + verifyNoMoreInteractions(span); + } + + @Test void parseServerIpAndPort_withWhiteSpace() throws SQLException { + when(connection.getMetaData()).thenReturn(metaData); + when(metaData.getURL()).thenReturn(urlWithWhiteSpace); + + new TracingJdbcEventListener("foo", false, false, logOptions).parseServerIpAndPort(connection, span); + + verify(span).remoteServiceName("foo"); + } + + @Test void shouldFilterSqlExclusion() throws SQLException { + logOptions.setFilter(true); + logOptions.setExclude("set session"); + when(statementInformation.getSql()).thenReturn("set session foo foo;"); + when(statementInformation.getConnectionInformation()).thenReturn(connectionInformation); + when(connectionInformation.getConnection()).thenReturn(connection); + when(connection.getMetaData()).thenReturn(metaData); + + TracingJdbcEventListener listener = new TracingJdbcEventListener("", false, false, logOptions); + listener.onBeforeAnyExecute(statementInformation); + listener.onAfterAnyExecute(statementInformation, 1, null); + + logOptions.setFilter(false); + listener.onBeforeAnyExecute(statementInformation); + listener.onAfterAnyExecute(statementInformation, 1, null); + + assertThat(spans).size().isEqualTo(1); + } + + @Test void nullSqlWontNPE() { + when(statementInformation.getSql()).thenReturn(null); + + TracingJdbcEventListener listener = new TracingJdbcEventListener("", false, false, logOptions); + listener.onBeforeAnyExecute(statementInformation); + listener.onAfterAnyExecute(statementInformation, 1, null); + + assertThat(spans).isEmpty(); + } + + @Test void handleAfterExecute_without_beforeExecute_getting_called() { + ScopedSpan parent = tracing.tracer().startScopedSpan("test"); + try { + TracingJdbcEventListener listener = new TracingJdbcEventListener("", false, false, logOptions); + listener.onAfterAnyExecute(statementInformation, 1, null); + listener.onAfterAnyExecute(statementInformation, 1, null); + } finally { + parent.finish(); + } + } +} diff --git a/instrumentation/p6spy/src/test/resources/log4j2.properties b/instrumentation/p6spy/src/test/resources/log4j2.properties new file mode 100755 index 0000000000..20b51873e7 --- /dev/null +++ b/instrumentation/p6spy/src/test/resources/log4j2.properties @@ -0,0 +1,8 @@ +appenders=console +appender.console.type=Console +appender.console.name=STDOUT +appender.console.layout.type=PatternLayout +appender.console.layout.pattern=%d{ABSOLUTE} %-5p [%t] %C{2} (%F:%L) - %m%n +rootLogger.level=warn +rootLogger.appenderRefs=stdout +rootLogger.appenderRef.stdout.ref=STDOUT diff --git a/instrumentation/p6spy/src/test/resources/spy.properties b/instrumentation/p6spy/src/test/resources/spy.properties new file mode 100644 index 0000000000..bc2007dfdc --- /dev/null +++ b/instrumentation/p6spy/src/test/resources/spy.properties @@ -0,0 +1,4 @@ +modulelist=brave.p6spy.TracingP6Factory +remoteServiceName=myservice +#includeParameterValues=true +includeAffectedRowsCount=true diff --git a/instrumentation/pom.xml b/instrumentation/pom.xml index 9aaa1b9fe6..2d1bbd1d21 100644 --- a/instrumentation/pom.xml +++ b/instrumentation/pom.xml @@ -36,6 +36,7 @@ http-tests http-tests-jakarta dubbo + dubbo-rpc grpc httpasyncclient httpclient @@ -51,9 +52,11 @@ mysql6 mysql8 okhttp3 + p6spy rpc servlet servlet-jakarta + sparkjava spring-rabbit spring-web spring-webmvc diff --git a/instrumentation/rpc/README.md b/instrumentation/rpc/README.md index b856112e42..499090bda2 100644 --- a/instrumentation/rpc/README.md +++ b/instrumentation/rpc/README.md @@ -117,7 +117,7 @@ RpcClientRequestWrapper requestWrapper = new RpcClientRequestWrapper(request); Span span = handler.handleSend(requestWrapper); // 1. ClientResponse response = null; Throwable error = null; -try (Scope scope = currentTraceContext.newScope(span.context())) { // 2. +try (Scope ws = currentTraceContext.newScope(span.context())) { // 2. return response = invoke(request); // 3. } catch (Throwable e) { error = e; // 4. @@ -187,7 +187,7 @@ RpcServerRequestWrapper requestWrapper = new RpcServerRequestWrapper(request); Span span = handler.handleReceive(requestWrapper); // 1. ServerResponse response = null; Throwable error = null; -try (Scope scope = currentTraceContext.newScope(span.context())) { // 2. +try (Scope ws = currentTraceContext.newScope(span.context())) { // 2. return response = process(request); // 3. } catch (Throwable e) { error = e; // 4. diff --git a/instrumentation/rpc/src/main/java/brave/rpc/RpcClientHandler.java b/instrumentation/rpc/src/main/java/brave/rpc/RpcClientHandler.java index 230abcf09a..75e0a6d07e 100644 --- a/instrumentation/rpc/src/main/java/brave/rpc/RpcClientHandler.java +++ b/instrumentation/rpc/src/main/java/brave/rpc/RpcClientHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -41,7 +41,7 @@ * Span span = handler.handleSend(requestWrapper); // 1. * ClientResponse response = null; * Throwable error = null; - * try (Scope scope = currentTraceContext.newScope(span.context())) { // 2. + * try (Scope ws = currentTraceContext.newScope(span.context())) { // 2. * return response = invoke(request); // 3. * } catch (Throwable e) { * error = e; // 4. diff --git a/instrumentation/rpc/src/main/java/brave/rpc/RpcServerHandler.java b/instrumentation/rpc/src/main/java/brave/rpc/RpcServerHandler.java index 6011d17c7c..420b3cc4de 100644 --- a/instrumentation/rpc/src/main/java/brave/rpc/RpcServerHandler.java +++ b/instrumentation/rpc/src/main/java/brave/rpc/RpcServerHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -40,7 +40,7 @@ * Span span = handler.handleReceive(requestWrapper); // 1. * ServerResponse response = null; * Throwable error = null; - * try (Scope scope = currentTraceContext.newScope(span.context())) { // 2. + * try (Scope ws = currentTraceContext.newScope(span.context())) { // 2. * return response = process(request); // 3. * } catch (Throwable e) { * error = e; // 4. diff --git a/instrumentation/rpc/src/main/java/brave/rpc/RpcTracing.java b/instrumentation/rpc/src/main/java/brave/rpc/RpcTracing.java index b6ced75aec..3d174daacf 100644 --- a/instrumentation/rpc/src/main/java/brave/rpc/RpcTracing.java +++ b/instrumentation/rpc/src/main/java/brave/rpc/RpcTracing.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/instrumentation/rpc/src/test/java/brave/rpc/RpcClientHandlerTest.java b/instrumentation/rpc/src/test/java/brave/rpc/RpcClientHandlerTest.java index 152f5126d8..359edce3bb 100644 --- a/instrumentation/rpc/src/test/java/brave/rpc/RpcClientHandlerTest.java +++ b/instrumentation/rpc/src/test/java/brave/rpc/RpcClientHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -117,7 +117,7 @@ Tracing.Builder tracingBuilder() { } @Test void handleSendWithParent_overrideContext() { - try (Scope scope = httpTracing.tracing.currentTraceContext().newScope(context)) { + try (Scope ws = httpTracing.tracing.currentTraceContext().newScope(context)) { brave.Span span = handler.handleSendWithParent(request, null); // If the overwrite was successful, we have a root span. @@ -126,7 +126,7 @@ Tracing.Builder tracingBuilder() { } @Test void handleSendWithParent_overrideNull() { - try (Scope scope = httpTracing.tracing.currentTraceContext().newScope(null)) { + try (Scope ws = httpTracing.tracing.currentTraceContext().newScope(null)) { brave.Span span = handler.handleSendWithParent(request, context); // If the overwrite was successful, we have a child span. diff --git a/instrumentation/rpc/src/test/java/brave/rpc/RpcTracingTest.java b/instrumentation/rpc/src/test/java/brave/rpc/RpcTracingTest.java index 969d000956..f770538584 100644 --- a/instrumentation/rpc/src/test/java/brave/rpc/RpcTracingTest.java +++ b/instrumentation/rpc/src/test/java/brave/rpc/RpcTracingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/instrumentation/servlet-jakarta/src/main/java/brave/jakarta/servlet/TracingFilter.java b/instrumentation/servlet-jakarta/src/main/java/brave/jakarta/servlet/TracingFilter.java index ba51007e29..0d15ddfe3b 100644 --- a/instrumentation/servlet-jakarta/src/main/java/brave/jakarta/servlet/TracingFilter.java +++ b/instrumentation/servlet-jakarta/src/main/java/brave/jakarta/servlet/TracingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2022 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -24,6 +24,9 @@ import brave.propagation.CurrentTraceContext; import brave.propagation.CurrentTraceContext.Scope; import brave.propagation.TraceContext; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; @@ -32,10 +35,6 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.concurrent.atomic.AtomicBoolean; - -import static brave.internal.Throwables.propagateIfFatal; public final class TracingFilter implements Filter { public static Filter create(Tracing tracing) { @@ -88,7 +87,6 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha // any downstream code can see Tracer.currentSpan() or use Tracer.currentSpanCustomizer() chain.doFilter(req, res); } catch (Throwable e) { - propagateIfFatal(e); error = e; throw e; } finally { diff --git a/instrumentation/servlet-jakarta/src/main/java/brave/jakarta/servlet/internal/ServletRuntime.java b/instrumentation/servlet-jakarta/src/main/java/brave/jakarta/servlet/internal/ServletRuntime.java index ffe456e2d6..9dd940b461 100644 --- a/instrumentation/servlet-jakarta/src/main/java/brave/jakarta/servlet/internal/ServletRuntime.java +++ b/instrumentation/servlet-jakarta/src/main/java/brave/jakarta/servlet/internal/ServletRuntime.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2022 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -17,6 +17,8 @@ import brave.http.HttpServerHandler; import brave.http.HttpServerRequest; import brave.http.HttpServerResponse; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; import jakarta.servlet.AsyncContext; import jakarta.servlet.AsyncEvent; import jakarta.servlet.AsyncListener; @@ -24,8 +26,6 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; /** * Access to servlet version-specific features diff --git a/instrumentation/servlet-jakarta/src/test/java/brave/jakarta/servlet/HttpServletRequestWrapperTest.java b/instrumentation/servlet-jakarta/src/test/java/brave/jakarta/servlet/HttpServletRequestWrapperTest.java index 2af7f56e36..d7dbae4ed3 100644 --- a/instrumentation/servlet-jakarta/src/test/java/brave/jakarta/servlet/HttpServletRequestWrapperTest.java +++ b/instrumentation/servlet-jakarta/src/test/java/brave/jakarta/servlet/HttpServletRequestWrapperTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,6 +14,7 @@ package brave.jakarta.servlet; import brave.Span; + import jakarta.servlet.RequestDispatcher; import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.Test; diff --git a/instrumentation/servlet-jakarta/src/test/java/brave/jakarta/servlet/HttpServletResponseWrapperTest.java b/instrumentation/servlet-jakarta/src/test/java/brave/jakarta/servlet/HttpServletResponseWrapperTest.java index e736af1803..45d73c8fbc 100644 --- a/instrumentation/servlet-jakarta/src/test/java/brave/jakarta/servlet/HttpServletResponseWrapperTest.java +++ b/instrumentation/servlet-jakarta/src/test/java/brave/jakarta/servlet/HttpServletResponseWrapperTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,6 +14,7 @@ package brave.jakarta.servlet; import brave.http.HttpServerResponse; + import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; diff --git a/instrumentation/servlet-jakarta/src/test/java/brave/jakarta/servlet/ITTracingFilter.java b/instrumentation/servlet-jakarta/src/test/java/brave/jakarta/servlet/ITTracingFilter.java index 8b6e4cf89b..62da3e5e68 100644 --- a/instrumentation/servlet-jakarta/src/test/java/brave/jakarta/servlet/ITTracingFilter.java +++ b/instrumentation/servlet-jakarta/src/test/java/brave/jakarta/servlet/ITTracingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,7 +13,7 @@ */ package brave.jakarta.servlet; -import brave.test.jakarta.http.ITServlet5Container; +import java.util.EnumSet; import jakarta.servlet.DispatcherType; import jakarta.servlet.Filter; import jakarta.servlet.FilterRegistration; @@ -21,9 +21,9 @@ import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.UnavailableException; -import java.util.EnumSet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.junit.jupiter.api.Disabled; +import brave.test.jakarta.http.ITServlet5Container; class ITTracingFilter extends ITServlet5Container { diff --git a/instrumentation/servlet/src/main/java/brave/servlet/HttpServletAdapter.java b/instrumentation/servlet/src/main/java/brave/servlet/HttpServletAdapter.java new file mode 100644 index 0000000000..4608fa0a81 --- /dev/null +++ b/instrumentation/servlet/src/main/java/brave/servlet/HttpServletAdapter.java @@ -0,0 +1,117 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.servlet; + +import brave.Span; +import brave.http.HttpServerAdapter; +import brave.http.HttpServerRequest; +import brave.http.HttpServerResponse; +import brave.internal.Nullable; +import brave.servlet.internal.ServletRuntime; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +/** + * This can also parse the remote IP of the client. + * + * @deprecated Since 5.7, use {@link HttpServerRequest} and {@link HttpServerResponse} instead. + */ +@Deprecated +public class HttpServletAdapter extends HttpServerAdapter { + + /** + * Looks for the {@link HttpServletRequest#setAttribute(String, Object) request attribute} + * "http.route". When present, returns a response wrapper that this adapter can use to parse it. + */ + // not static so that this can be overridden by implementations as needed. + public HttpServletResponse adaptResponse(HttpServletRequest req, HttpServletResponse resp) { + Object maybeRoute = req.getAttribute("http.route"); + return maybeRoute instanceof String + ? new DecoratedHttpServletResponse(resp, req.getMethod(), (String) maybeRoute) + : resp; + } + + final ServletRuntime servlet = ServletRuntime.get(); + + /** + * This sets the client IP:port to the {@linkplain HttpServletRequest#getRemoteAddr() remote + * address} if the {@link HttpServerAdapter#parseClientIpAndPort default parsing} fails. + */ + @Override public boolean parseClientIpAndPort(HttpServletRequest req, Span span) { + if (parseClientIpFromXForwardedFor(req, span)) return true; + return span.remoteIpAndPort(req.getRemoteAddr(), req.getRemotePort()); + } + + @Override public String method(HttpServletRequest request) { + return request.getMethod(); + } + + @Override public String path(HttpServletRequest request) { + return request.getRequestURI(); + } + + @Override public String url(HttpServletRequest request) { + StringBuffer url = request.getRequestURL(); + if (request.getQueryString() != null && !request.getQueryString().isEmpty()) { + url.append('?').append(request.getQueryString()); + } + return url.toString(); + } + + @Override public String requestHeader(HttpServletRequest request, String name) { + return request.getHeader(name); + } + + /** + * When applied to {@link #adaptResponse(HttpServletRequest, HttpServletResponse)}, returns the + * {@link HttpServletRequest#getMethod() request method}. + */ + @Override public String methodFromResponse(HttpServletResponse response) { + if (response instanceof DecoratedHttpServletResponse) { + return ((DecoratedHttpServletResponse) response).method; + } + return null; + } + + /** + * When applied to {@link #adaptResponse(HttpServletRequest, HttpServletResponse)}, returns the + * {@link HttpServletRequest#getAttribute(String) request attribute} "http.route". + */ + @Override public String route(HttpServletResponse response) { + if (response instanceof DecoratedHttpServletResponse) { + return ((DecoratedHttpServletResponse) response).httpRoute; + } + return null; + } + + @Override @Nullable public Integer statusCode(HttpServletResponse response) { + int result = statusCodeAsInt(response); + return result != 0 ? result : null; + } + + @Override public int statusCodeAsInt(HttpServletResponse response) { + return servlet.status(response); + } + + static class DecoratedHttpServletResponse extends HttpServletResponseWrapper { + final String method, httpRoute; + + DecoratedHttpServletResponse(HttpServletResponse response, String method, String httpRoute) { + super(response); + this.method = method; + this.httpRoute = httpRoute; + } + } +} diff --git a/instrumentation/servlet/src/main/java/brave/servlet/internal/ServletRuntime.java b/instrumentation/servlet/src/main/java/brave/servlet/internal/ServletRuntime.java index 266c456d3f..7fca26afad 100644 --- a/instrumentation/servlet/src/main/java/brave/servlet/internal/ServletRuntime.java +++ b/instrumentation/servlet/src/main/java/brave/servlet/internal/ServletRuntime.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -17,6 +17,7 @@ import brave.http.HttpServerHandler; import brave.http.HttpServerRequest; import brave.http.HttpServerResponse; +import brave.servlet.TracingFilter; import java.io.IOException; import java.lang.reflect.Method; import java.util.LinkedHashMap; diff --git a/instrumentation/servlet/src/test/java/brave/servlet/HttpServletAdapterTest.java b/instrumentation/servlet/src/test/java/brave/servlet/HttpServletAdapterTest.java new file mode 100644 index 0000000000..839e95b05e --- /dev/null +++ b/instrumentation/servlet/src/test/java/brave/servlet/HttpServletAdapterTest.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.servlet; + +import brave.Span; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@Deprecated public class HttpServletAdapterTest { + HttpServletAdapter adapter = new HttpServletAdapter(); + @Mock HttpServletRequest request; + @Mock HttpServletResponse response; + @Mock Span span; + + @Test void path_doesntCrashOnNullUrl() { + assertThat(adapter.path(request)) + .isNull(); + } + + @Test void path_getRequestURI() { + when(request.getRequestURI()).thenReturn("/bar"); + + assertThat(adapter.path(request)) + .isEqualTo("/bar"); + } + + @Test void url_derivedFromUrlAndQueryString() { + when(request.getRequestURL()).thenReturn(new StringBuffer("http://foo:8080/bar")); + when(request.getQueryString()).thenReturn("hello=world"); + + assertThat(adapter.url(request)) + .isEqualTo("http://foo:8080/bar?hello=world"); + } + + @Test void parseClientIpAndPort_prefersXForwardedFor() { + when(span.remoteIpAndPort("1.2.3.4", 0)).thenReturn(true); + when(adapter.requestHeader(request, "X-Forwarded-For")).thenReturn("1.2.3.4"); + + adapter.parseClientIpAndPort(request, span); + + verify(span).remoteIpAndPort("1.2.3.4", 0); + verifyNoMoreInteractions(span); + } + + @Test void parseClientIpAndPort_skipsRemotePortOnXForwardedFor() { + when(request.getHeader("X-Forwarded-For")).thenReturn("1.2.3.4"); + when(span.remoteIpAndPort("1.2.3.4", 0)).thenReturn(true); + + adapter.parseClientIpAndPort(request, span); + + verify(span).remoteIpAndPort("1.2.3.4", 0); + verifyNoMoreInteractions(span); + } + + @Test void parseClientIpAndPort_acceptsRemoteAddr() { + when(request.getRemoteAddr()).thenReturn("1.2.3.4"); + when(request.getRemotePort()).thenReturn(61687); + + adapter.parseClientIpAndPort(request, span); + + verify(span).remoteIpAndPort("1.2.3.4", 61687); + verifyNoMoreInteractions(span); + } + + @Test void statusCodeAsInt() { + when(response.getStatus()).thenReturn(200); + + assertThat(adapter.statusCodeAsInt(response)).isEqualTo(200); + assertThat(adapter.statusCode(response)).isEqualTo(200); + } + + @Test void statusCodeAsInt_zeroNoResponse() { + assertThat(adapter.statusCodeAsInt(response)).isZero(); + assertThat(adapter.statusCode(response)).isNull(); + } +} diff --git a/instrumentation/sparkjava/README.md b/instrumentation/sparkjava/README.md new file mode 100644 index 0000000000..5c31417236 --- /dev/null +++ b/instrumentation/sparkjava/README.md @@ -0,0 +1,34 @@ +# brave-instrumentation-sparkjava + +## Deprecated + +sparkjava hasn't been released since July 2022. This module will be removed in +Brave v6. + +## Overview + +This module contains tracing filters and exception handlers for [SparkJava](http://sparkjava.com/) +The filters extract trace state from incoming requests. Then, they +reports Zipkin how long each request takes, along with relevant tags +like the http url. The exception handler ensures any errors are also +sent to Zipkin. + +To enable tracing you need to add `before`, `exception` and `afterAfter` +hooks: +```java +sparkTracing = SparkTracing.create(tracing); +Spark.before(sparkTracing.before()); +Spark.exception(Exception.class, sparkTracing.exception(new ExceptionHandlerImpl())); +Spark.afterAfter(sparkTracing.afterAfter()); + +// any routes you add are now traced, such as the below +Spark.get("/foo", (req, res) -> "bar"); +``` + +## Non-embedded mode +SparkJava can run with embedded Jetty or as a [Servlet Filter](http://sparkjava.com/documentation#other-web-server). +Servlet filter deployment allows you to run SparkJava in a war file, or +anything that provides a servlet layer (such as Spring Boot). When using +the filter approach, use our [TracingFilter](../servlet), not +`SparkTracing` types. + diff --git a/instrumentation/sparkjava/bnd.bnd b/instrumentation/sparkjava/bnd.bnd new file mode 100644 index 0000000000..c385c3c148 --- /dev/null +++ b/instrumentation/sparkjava/bnd.bnd @@ -0,0 +1,4 @@ +Import-Package: \ + * +Export-Package: \ + brave.sparkjava diff --git a/instrumentation/sparkjava/pom.xml b/instrumentation/sparkjava/pom.xml new file mode 100644 index 0000000000..023d6f3248 --- /dev/null +++ b/instrumentation/sparkjava/pom.xml @@ -0,0 +1,55 @@ + + + + + io.zipkin.brave + brave-instrumentation-parent + 5.18.1-SNAPSHOT + + 4.0.0 + + brave-instrumentation-sparkjava + Brave Instrumentation: Spark Framework + + + + brave.sparkjava + + ${project.basedir}/../.. + + + + + ${project.groupId} + brave-instrumentation-servlet + ${project.version} + + + com.sparkjava + spark-core + ${sparkjava.version} + provided + + + + ${project.groupId} + brave-instrumentation-http-tests + ${project.version} + test + + + diff --git a/instrumentation/sparkjava/src/main/java/brave/sparkjava/SparkTracing.java b/instrumentation/sparkjava/src/main/java/brave/sparkjava/SparkTracing.java new file mode 100644 index 0000000000..584a875afd --- /dev/null +++ b/instrumentation/sparkjava/src/main/java/brave/sparkjava/SparkTracing.java @@ -0,0 +1,87 @@ +/* + * Copyright 2013-2024 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.sparkjava; + +import brave.Span; +import brave.Tracing; +import brave.http.HttpServerHandler; +import brave.http.HttpServerRequest; +import brave.http.HttpServerResponse; +import brave.http.HttpTracing; +import brave.propagation.CurrentTraceContext; +import brave.propagation.CurrentTraceContext.Scope; +import brave.servlet.HttpServletRequestWrapper; +import brave.servlet.HttpServletResponseWrapper; +import spark.ExceptionHandler; +import spark.Filter; + +/** + * @deprecated sparkjava hasn't been released since July 2022. Tracing support will be removed in + * Brave v6. + */ +@Deprecated +public final class SparkTracing { + public static SparkTracing create(Tracing tracing) { + return new SparkTracing(HttpTracing.create(tracing)); + } + + public static SparkTracing create(HttpTracing httpTracing) { + return new SparkTracing(httpTracing); + } + + final CurrentTraceContext currentTraceContext; + final HttpServerHandler handler; + + SparkTracing(HttpTracing httpTracing) { // intentionally hidden constructor + currentTraceContext = httpTracing.tracing().currentTraceContext(); + handler = HttpServerHandler.create(httpTracing); + } + + public Filter before() { + return (request, response) -> { + // Add servlet attribute "http.route" if this or similar is merged: + // https://github.com/perwendel/spark/pull/1126 + Span span = handler.handleReceive(HttpServletRequestWrapper.create(request.raw())); + request.attribute(Span.class.getName(), span); + request.attribute(Scope.class.getName(), currentTraceContext.newScope(span.context())); + }; + } + + public Filter afterAfter() { + return (req, res) -> { + Span span = req.attribute(Span.class.getName()); + if (span == null) return; + HttpServerResponse response = HttpServletResponseWrapper.create(req.raw(), res.raw(), null); + handler.handleSend(response, span); + ((Scope) req.attribute(Scope.class.getName())).close(); + }; + } + + public ExceptionHandler exception(ExceptionHandler delegate) { + return (error, req, res) -> { + try { + delegate.handle(error, req, res); + } finally { + Span span = req.attribute(Span.class.getName()); + if (span != null) { + HttpServerResponse response = + HttpServletResponseWrapper.create(req.raw(), res.raw(), error); + handler.handleSend(response, span); + req.raw().removeAttribute(Span.class.getName()); // prevent double-processing + ((Scope) req.attribute(Scope.class.getName())).close(); + } + } + }; + } +} diff --git a/instrumentation/sparkjava/src/test/java/brave/sparkjava/ITSparkTracing.java b/instrumentation/sparkjava/src/test/java/brave/sparkjava/ITSparkTracing.java new file mode 100644 index 0000000000..a31b246b3e --- /dev/null +++ b/instrumentation/sparkjava/src/test/java/brave/sparkjava/ITSparkTracing.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.sparkjava; + +import brave.test.http.ITHttpServer; +import brave.test.http.Log4J2Log; +import java.io.IOException; +import okhttp3.Response; +import org.eclipse.jetty.util.log.Log; +import org.junit.AssumptionViolatedException; +import org.junit.jupiter.api.AfterEach; +import spark.Spark; + +class ITSparkTracing extends ITHttpServer { + public ITSparkTracing() { + Log.setLog(new Log4J2Log()); + } + + @Override protected Response get(String path) throws IOException { + if (path.toLowerCase().indexOf("async") == -1) return super.get(path); + throw new AssumptionViolatedException( + "ignored until https://github.com/perwendel/spark/issues/208"); + } + + @Override protected void init() { + stop(); + + SparkTracing spark = SparkTracing.create(httpTracing); + Spark.before(spark.before()); + Spark.exception(Exception.class, spark.exception( + (exception, request, response) -> response.body("exception")) + ); + Spark.afterAfter(spark.afterAfter()); + + new TestApplication().init(); + Spark.awaitInitialization(); + } + + @Override + protected String url(String path) {//default port 4567 + return "http://localhost:4567" + path; + } + + @AfterEach + public void stop() { + Spark.stop(); + Spark.awaitStop(); + } +} diff --git a/instrumentation/sparkjava/src/test/java/brave/sparkjava/ITTracingFilter.java b/instrumentation/sparkjava/src/test/java/brave/sparkjava/ITTracingFilter.java new file mode 100644 index 0000000000..deb45d88f1 --- /dev/null +++ b/instrumentation/sparkjava/src/test/java/brave/sparkjava/ITTracingFilter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.sparkjava; + +import brave.servlet.TracingFilter; +import brave.test.http.ITServletContainer; +import brave.test.http.Jetty9ServerController; +import java.util.EnumSet; +import javax.servlet.DispatcherType; +import javax.servlet.FilterRegistration.Dynamic; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.junit.jupiter.api.Disabled; +import spark.servlet.SparkFilter; + +class ITTracingFilter extends ITServletContainer { + public ITTracingFilter() { + super(new Jetty9ServerController()); + } + + @Override public void init(ServletContextHandler handler) { + handler.getServletContext() + .addFilter("tracingFilter", TracingFilter.create(httpTracing)) + .addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*"); + + Dynamic sparkFilter = handler.getServletContext().addFilter("sparkFilter", new SparkFilter()); + sparkFilter.setInitParameter("applicationClass", TestApplication.class.getName()); + sparkFilter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*"); + } + + @Override @Disabled("TODO: make a spark.ExceptionMapper that adds the \"error\" request property") + public void setsErrorAndHttpStatusOnUncaughtException() { + } + + @Override @Disabled("TODO: make a spark.ExceptionMapper that adds the \"error\" request property") + public void spanHandlerSeesError() { + } + + @Disabled("We can't set the error code for an uncaught exception with jetty-servlet") + @Override public void httpStatusCodeSettable_onUncaughtException() { + } +} diff --git a/instrumentation/sparkjava/src/test/java/brave/sparkjava/TestApplication.java b/instrumentation/sparkjava/src/test/java/brave/sparkjava/TestApplication.java new file mode 100644 index 0000000000..519edbfd4b --- /dev/null +++ b/instrumentation/sparkjava/src/test/java/brave/sparkjava/TestApplication.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.sparkjava; + +import brave.Tracing; +import spark.Spark; +import spark.servlet.SparkApplication; + +import static brave.test.ITRemote.BAGGAGE_FIELD; +import static brave.test.http.ITHttpServer.NOT_READY_ISE; + +public class TestApplication implements SparkApplication { + @Override public void init() { + Spark.options("/", (req, res) -> ""); + Spark.get("/foo", (req, res) -> "bar"); + Spark.get("/baggage", (req, res) -> BAGGAGE_FIELD.getValue()); + Spark.get("/badrequest", (req, res) -> { + res.status(400); + return res; + }); + Spark.get("/child", (req, res) -> { + Tracing.currentTracer().nextSpan().name("child").start().finish(); + return "happy"; + }); + Spark.get("/exception", (req, res) -> { + res.status(503); + throw NOT_READY_ISE; + }); + + // TODO: we need matchUri: https://github.com/perwendel/spark/issues/959 + //Spark.get("/items/:itemId", (request, response) -> request.params(":itemId")); + //Spark.path("/nested", () -> + // Spark.get("/items/:itemId", (request, response) -> request.params(":itemId")) + //); + } +} diff --git a/instrumentation/spring-rabbit/src/main/java/brave/spring/rabbit/MessageConsumerRequest.java b/instrumentation/spring-rabbit/src/main/java/brave/spring/rabbit/MessageConsumerRequest.java index 3c1abf9bbf..951c265303 100644 --- a/instrumentation/spring-rabbit/src/main/java/brave/spring/rabbit/MessageConsumerRequest.java +++ b/instrumentation/spring-rabbit/src/main/java/brave/spring/rabbit/MessageConsumerRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,6 +14,7 @@ package brave.spring.rabbit; import brave.Span.Kind; +import brave.internal.Nullable; import brave.messaging.ConsumerRequest; import brave.propagation.Propagation.RemoteGetter; import brave.propagation.Propagation.RemoteSetter; diff --git a/instrumentation/spring-rabbit/src/main/java/brave/spring/rabbit/SpringRabbitTracing.java b/instrumentation/spring-rabbit/src/main/java/brave/spring/rabbit/SpringRabbitTracing.java index 05c28efae8..d87cdbad07 100644 --- a/instrumentation/spring-rabbit/src/main/java/brave/spring/rabbit/SpringRabbitTracing.java +++ b/instrumentation/spring-rabbit/src/main/java/brave/spring/rabbit/SpringRabbitTracing.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2022 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -19,6 +19,7 @@ import brave.internal.Nullable; import brave.messaging.MessagingRequest; import brave.messaging.MessagingTracing; +import brave.propagation.B3Propagation; import brave.propagation.Propagation; import brave.propagation.TraceContext.Extractor; import brave.propagation.TraceContext.Injector; @@ -86,6 +87,14 @@ public Builder remoteServiceName(String remoteServiceName) { return this; } + /** + * @deprecated as of v5.9, this is ignored because single format is default for messaging. Use + * {@link B3Propagation#newFactoryBuilder()} to change the default. + */ + @Deprecated public Builder writeB3SingleFormat(boolean writeB3SingleFormat) { + return this; + } + public SpringRabbitTracing build() { return new SpringRabbitTracing(this); } @@ -96,6 +105,7 @@ public SpringRabbitTracing build() { final Extractor producerExtractor; final Extractor consumerExtractor; final Injector producerInjector; + final Injector consumerInjector; final String[] traceIdHeaders; final SamplerFunction producerSampler, consumerSampler; final String remoteServiceName; @@ -112,6 +122,7 @@ public SpringRabbitTracing build() { this.producerExtractor = propagation.extractor(MessageProducerRequest.GETTER); this.consumerExtractor = propagation.extractor(MessageConsumerRequest.GETTER); this.producerInjector = propagation.injector(MessageProducerRequest.SETTER); + this.consumerInjector = propagation.injector(MessageConsumerRequest.SETTER); this.producerSampler = messagingTracing.producerSampler(); this.consumerSampler = messagingTracing.consumerSampler(); this.remoteServiceName = builder.remoteServiceName; @@ -262,7 +273,7 @@ Span nextMessagingSpan( } // We can't just skip clearing headers we use because we might inject B3 single, yet have stale B3 - // multi, or vice versa. + // multi, or visa versa. void clearTraceIdHeaders(Map headers) { for (String traceIDHeader : traceIdHeaders) headers.remove(traceIDHeader); } diff --git a/instrumentation/spring-rabbit/src/main/java/brave/spring/rabbit/TracingRabbitListenerAdvice.java b/instrumentation/spring-rabbit/src/main/java/brave/spring/rabbit/TracingRabbitListenerAdvice.java index b603606542..9fe0273b54 100644 --- a/instrumentation/spring-rabbit/src/main/java/brave/spring/rabbit/TracingRabbitListenerAdvice.java +++ b/instrumentation/spring-rabbit/src/main/java/brave/spring/rabbit/TracingRabbitListenerAdvice.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -19,10 +19,10 @@ import brave.internal.Nullable; import brave.messaging.MessagingRequest; import brave.propagation.TraceContext.Extractor; +import brave.propagation.TraceContext.Injector; import brave.propagation.TraceContextOrSamplingFlags; import brave.sampler.SamplerFunction; import com.rabbitmq.client.Channel; -import java.util.List; import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; @@ -31,8 +31,9 @@ import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import java.util.List; + import static brave.Span.Kind.CONSUMER; -import static brave.internal.Throwables.propagateIfFatal; import static brave.spring.rabbit.SpringRabbitTracing.RABBIT_EXCHANGE; import static brave.spring.rabbit.SpringRabbitTracing.RABBIT_QUEUE; import static brave.spring.rabbit.SpringRabbitTracing.RABBIT_ROUTING_KEY; @@ -54,6 +55,7 @@ final class TracingRabbitListenerAdvice implements MethodInterceptor { final Tracing tracing; final Tracer tracer; final Extractor extractor; + final Injector injector; final SamplerFunction sampler; @Nullable final String remoteServiceName; @@ -63,6 +65,7 @@ final class TracingRabbitListenerAdvice implements MethodInterceptor { this.tracer = tracing.tracer(); this.extractor = springRabbitTracing.consumerExtractor; this.sampler = springRabbitTracing.consumerSampler; + this.injector = springRabbitTracing.consumerInjector; this.remoteServiceName = springRabbitTracing.remoteServiceName; } @@ -99,18 +102,17 @@ final class TracingRabbitListenerAdvice implements MethodInterceptor { listenerSpan.name("on-message").start(consumerFinish); } - Tracer.SpanInScope scope = tracer.withSpanInScope(listenerSpan); + Tracer.SpanInScope ws = tracer.withSpanInScope(listenerSpan); Throwable error = null; try { return methodInvocation.proceed(); } catch (Throwable t) { - propagateIfFatal(t); error = t; throw t; } finally { if (error != null) listenerSpan.error(error); listenerSpan.finish(); - scope.close(); + ws.close(); } } diff --git a/instrumentation/spring-rabbit/src/test/java/brave/spring/rabbit/ITSpringRabbitTracing.java b/instrumentation/spring-rabbit/src/test/java/brave/spring/rabbit/ITSpringRabbitTracing.java index 867d6d8d0c..b7ebf9c169 100644 --- a/instrumentation/spring-rabbit/src/test/java/brave/spring/rabbit/ITSpringRabbitTracing.java +++ b/instrumentation/spring-rabbit/src/test/java/brave/spring/rabbit/ITSpringRabbitTracing.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -21,6 +21,7 @@ import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.amqp.core.Message; +import org.springframework.amqp.rabbit.core.RabbitTemplate; import static brave.Span.Kind.CONSUMER; import static brave.Span.Kind.PRODUCER; diff --git a/instrumentation/spring-rabbit/src/test/java/brave/spring/rabbit/SpringRabbitTracingTest.java b/instrumentation/spring-rabbit/src/test/java/brave/spring/rabbit/SpringRabbitTracingTest.java index 655301c1d8..1214a98f19 100644 --- a/instrumentation/spring-rabbit/src/test/java/brave/spring/rabbit/SpringRabbitTracingTest.java +++ b/instrumentation/spring-rabbit/src/test/java/brave/spring/rabbit/SpringRabbitTracingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -16,6 +16,7 @@ import brave.Tracing; import java.util.Collection; import java.util.List; + import org.aopalliance.aop.Advice; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; diff --git a/instrumentation/spring-rabbit/src/test/java/brave/spring/rabbit/TracingRabbitListenerAdviceTest.java b/instrumentation/spring-rabbit/src/test/java/brave/spring/rabbit/TracingRabbitListenerAdviceTest.java index 2f7b949af9..1862ab73f3 100644 --- a/instrumentation/spring-rabbit/src/test/java/brave/spring/rabbit/TracingRabbitListenerAdviceTest.java +++ b/instrumentation/spring-rabbit/src/test/java/brave/spring/rabbit/TracingRabbitListenerAdviceTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -20,8 +20,6 @@ import brave.propagation.B3Propagation; import brave.propagation.StrictCurrentTraceContext; import brave.test.TestSpanHandler; -import java.util.Arrays; -import java.util.List; import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -29,6 +27,9 @@ import org.springframework.amqp.core.MessageBuilder; import org.springframework.amqp.core.MessageProperties; +import java.util.Arrays; +import java.util.List; + import static brave.Span.Kind.CONSUMER; import static brave.test.ITRemote.BAGGAGE_FIELD; import static brave.test.ITRemote.BAGGAGE_FIELD_KEY; @@ -308,4 +309,19 @@ void onBatchMessageConsumed(List messages) throws Throwable { tracingRabbitListenerAdvice.invoke(methodInvocation); } + + void onBatchMessageConsumeFailed(List messages, Throwable throwable) throws Throwable { + when(methodInvocation.getArguments()).thenReturn(new Object[] { + null, // AMQPChannel - doesn't matter + messages + }); + when(methodInvocation.proceed()).thenThrow(throwable); + + try { + tracingRabbitListenerAdvice.invoke(methodInvocation); + fail("should have thrown exception"); + } catch (RuntimeException ex) { + } + } + } diff --git a/instrumentation/spring-web/src/main/java/brave/spring/web/TracingAsyncClientHttpRequestInterceptor.java b/instrumentation/spring-web/src/main/java/brave/spring/web/TracingAsyncClientHttpRequestInterceptor.java index 1a1991592d..c33562851d 100644 --- a/instrumentation/spring-web/src/main/java/brave/spring/web/TracingAsyncClientHttpRequestInterceptor.java +++ b/instrumentation/spring-web/src/main/java/brave/spring/web/TracingAsyncClientHttpRequestInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -64,7 +64,7 @@ public static AsyncClientHttpRequestInterceptor create(HttpTracing httpTracing) ? currentTraceContext.get() : null; - Scope scope = currentTraceContext.maybeScope(span.context()); + Scope ws = currentTraceContext.maybeScope(span.context()); Throwable error = null; try { ListenableFuture result = execution.executeAsync(req, body); @@ -86,7 +86,7 @@ public static AsyncClientHttpRequestInterceptor create(HttpTracing httpTracing) if (error != null) { handler.handleReceive(new ClientHttpResponseWrapper(request, null, error), span); } - scope.close(); + ws.close(); } } diff --git a/instrumentation/spring-web/src/main/java/brave/spring/web/TracingClientHttpRequestInterceptor.java b/instrumentation/spring-web/src/main/java/brave/spring/web/TracingClientHttpRequestInterceptor.java index c988b097ae..563110d6b1 100644 --- a/instrumentation/spring-web/src/main/java/brave/spring/web/TracingClientHttpRequestInterceptor.java +++ b/instrumentation/spring-web/src/main/java/brave/spring/web/TracingClientHttpRequestInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -53,7 +53,7 @@ public static ClientHttpRequestInterceptor create(HttpTracing httpTracing) { HttpRequestWrapper request = new HttpRequestWrapper(req); Span span = handler.handleSend(request); ClientHttpResponse response = null; - Scope scope = currentTraceContext.newScope(span.context()); + Scope ws = currentTraceContext.newScope(span.context()); Throwable error = null; try { return response = execution.execute(req, body); @@ -69,7 +69,7 @@ public static ClientHttpRequestInterceptor create(HttpTracing httpTracing) { throw e; } finally { handler.handleReceive(new ClientHttpResponseWrapper(request, response, error), span); - scope.close(); + ws.close(); } } diff --git a/instrumentation/spring-webmvc/src/test/java/brave/spring/webmvc/ITSpanCustomizingAsyncHandlerInterceptor.java b/instrumentation/spring-webmvc/src/test/java/brave/spring/webmvc/ITSpanCustomizingAsyncHandlerInterceptor.java index c91369ccee..83eb1937fe 100644 --- a/instrumentation/spring-webmvc/src/test/java/brave/spring/webmvc/ITSpanCustomizingAsyncHandlerInterceptor.java +++ b/instrumentation/spring-webmvc/src/test/java/brave/spring/webmvc/ITSpanCustomizingAsyncHandlerInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -13,7 +13,6 @@ */ package brave.spring.webmvc; -import brave.Span; import brave.test.http.ITServletContainer; import brave.test.http.Jetty9ServerController; import java.util.EnumSet; @@ -30,6 +29,7 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import brave.Span; import static org.assertj.core.api.Assertions.assertThat; diff --git a/instrumentation/vertx-web/src/main/java/brave/vertx/web/TracingRoutingContextHandler.java b/instrumentation/vertx-web/src/main/java/brave/vertx/web/TracingRoutingContextHandler.java index b89b2fc06b..c9c030cbc4 100644 --- a/instrumentation/vertx-web/src/main/java/brave/vertx/web/TracingRoutingContextHandler.java +++ b/instrumentation/vertx-web/src/main/java/brave/vertx/web/TracingRoutingContextHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -59,7 +59,7 @@ final class TracingRoutingContextHandler implements Handler { context.put(TracingHandler.class.getName(), handler); context.addHeadersEndHandler(handler); - try (Scope scope = currentTraceContext.maybeScope(span.context())) { + try (Scope ws = currentTraceContext.maybeScope(span.context())) { context.next(); } } diff --git a/pom.xml b/pom.xml index 707af8475d..2ef75a8737 100755 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,10 @@ 2.24.1 + + 2.27.0 + 3.0.0 + 1.3.2 @@ -92,6 +96,7 @@ 3.2.18.RELEASE + 9.4.53.v20231009 7.6.21.v20160908 3.15.6.Final @@ -121,6 +126,7 @@ 4.1.93.Final 4.1.5 + 2.9.4 5.10.1 diff --git a/spring-beans/pom.xml b/spring-beans/pom.xml index 71c6ec7b5a..0b1378a547 100644 --- a/spring-beans/pom.xml +++ b/spring-beans/pom.xml @@ -54,6 +54,11 @@ brave-instrumentation-rpc ${project.version} + + io.zipkin.reporter2 + zipkin-reporter-spring-beans + ${zipkin-reporter.version} + org.springframework spring-beans diff --git a/spring-beans/src/main/java/brave/spring/beans/EndpointFactoryBean.java b/spring-beans/src/main/java/brave/spring/beans/EndpointFactoryBean.java new file mode 100644 index 0000000000..41032b82d9 --- /dev/null +++ b/spring-beans/src/main/java/brave/spring/beans/EndpointFactoryBean.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.spring.beans; + +import org.springframework.beans.factory.FactoryBean; +import zipkin2.Endpoint; + +/** Spring XML config does not support chained builders. This converts accordingly */ +public class EndpointFactoryBean implements FactoryBean { + String serviceName; + String ip; + Integer port; + + @Override public Endpoint getObject() { + Endpoint.Builder builder = Endpoint.newBuilder(); + if (serviceName != null) builder.serviceName(serviceName); + if (ip != null && !builder.parseIp(ip)) { + throw new IllegalArgumentException("endpoint.ip: " + ip + " is not an IP literal"); + } + if (port != null) builder.port(port); + return builder.build(); + } + + @Override public Class getObjectType() { + return Endpoint.class; + } + + @Override public boolean isSingleton() { + return true; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public void setPort(Integer port) { + this.port = port; + } +} diff --git a/spring-beans/src/main/java/brave/spring/beans/ExtraFieldPropagationFactoryBean.java b/spring-beans/src/main/java/brave/spring/beans/ExtraFieldPropagationFactoryBean.java new file mode 100644 index 0000000000..12f635375b --- /dev/null +++ b/spring-beans/src/main/java/brave/spring/beans/ExtraFieldPropagationFactoryBean.java @@ -0,0 +1,67 @@ +/* + * Copyright 2013-2020 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.spring.beans; + +import brave.propagation.B3Propagation; +import brave.propagation.ExtraFieldCustomizer; +import brave.propagation.ExtraFieldPropagation; +import brave.propagation.Propagation; +import java.util.List; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.FactoryBean; + +/** @deprecated Since 5.11 use {@link BaggagePropagationFactoryBean} */ +@Deprecated public class ExtraFieldPropagationFactoryBean implements FactoryBean { + // Spring uses commons logging + static final Log logger = LogFactory.getLog(ExtraFieldPropagationFactoryBean.class); + + Propagation.Factory propagationFactory = B3Propagation.FACTORY; + List fields; + List customizers; + + @Override public Propagation.Factory getObject() { + logger.warn("The factory '" + getClass().getName() + "' will be removed in a future release.\n" + + "Use '" + BaggagePropagationFactoryBean.class.getName() + "' instead"); + ExtraFieldPropagation.FactoryBuilder builder = + ExtraFieldPropagation.newFactoryBuilder(propagationFactory); + if (fields != null) { + for (String field : fields) builder.addField(field); + } + if (customizers != null) { + for (ExtraFieldCustomizer customizer : customizers) customizer.customize(builder); + } + return builder.build(); + } + + @Override public Class getObjectType() { + return Propagation.Factory.class; + } + + @Override public boolean isSingleton() { + return true; + } + + public void setPropagationFactory(Propagation.Factory propagationFactory) { + this.propagationFactory = propagationFactory; + } + + public void setFields(List fields) { + this.fields = fields; + } + + public void setCustomizers(List customizers) { + this.customizers = customizers; + } +} diff --git a/spring-beans/src/main/java/brave/spring/beans/HttpTracingFactoryBean.java b/spring-beans/src/main/java/brave/spring/beans/HttpTracingFactoryBean.java index b4a96d87af..fdadf0d06c 100644 --- a/spring-beans/src/main/java/brave/spring/beans/HttpTracingFactoryBean.java +++ b/spring-beans/src/main/java/brave/spring/beans/HttpTracingFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,9 +14,11 @@ package brave.spring.beans; import brave.Tracing; +import brave.http.HttpClientParser; import brave.http.HttpRequest; import brave.http.HttpRequestParser; import brave.http.HttpResponseParser; +import brave.http.HttpServerParser; import brave.http.HttpTracing; import brave.http.HttpTracingCustomizer; import brave.propagation.Propagation; @@ -32,6 +34,8 @@ public class HttpTracingFactoryBean implements FactoryBean { static final Log logger = LogFactory.getLog(HttpTracingFactoryBean.class); Tracing tracing; + @Deprecated HttpClientParser clientParser; + @Deprecated HttpServerParser serverParser; HttpRequestParser clientRequestParser, serverRequestParser; HttpResponseParser clientResponseParser, serverResponseParser; SamplerFunction clientSampler, serverSampler; @@ -40,10 +44,12 @@ public class HttpTracingFactoryBean implements FactoryBean { @Override public HttpTracing getObject() { HttpTracing.Builder builder = HttpTracing.newBuilder(tracing); + if (clientParser != null) builder.clientParser(clientParser); if (clientRequestParser != null) builder.clientRequestParser(clientRequestParser); if (clientResponseParser != null) builder.clientResponseParser(clientResponseParser); if (serverRequestParser != null) builder.serverRequestParser(serverRequestParser); if (serverResponseParser != null) builder.serverResponseParser(serverResponseParser); + if (serverParser != null) builder.serverParser(serverParser); if (clientSampler != null) builder.clientSampler(clientSampler); if (serverSampler != null) builder.serverSampler(serverSampler); if (propagation != null) builder.propagation(propagation); @@ -65,6 +71,11 @@ public void setTracing(Tracing tracing) { this.tracing = tracing; } + @Deprecated public void setClientParser(HttpClientParser clientParser) { + logger.warn("The property 'setClientParser' will be removed in a future release.\n" + + "Use the property 'clientRequestParser' or 'clientResponseParser' instead"); + this.clientParser = clientParser; + } public void setClientRequestParser(HttpRequestParser clientRequestParser) { this.clientRequestParser = clientRequestParser; @@ -82,6 +93,12 @@ public void setServerResponseParser(HttpResponseParser serverResponseParser) { this.serverResponseParser = serverResponseParser; } + @Deprecated public void setServerParser(HttpServerParser serverParser) { + logger.warn("The property 'setServerParser' will be removed in a future release.\n" + + "Use the property 'serverRequestParser' or 'serverResponseParser' instead"); + this.serverParser = serverParser; + } + public void setClientSampler(SamplerFunction clientSampler) { this.clientSampler = clientSampler; } diff --git a/spring-beans/src/main/java/brave/spring/beans/MessagingTracingFactoryBean.java b/spring-beans/src/main/java/brave/spring/beans/MessagingTracingFactoryBean.java index 215dc9a3e2..8ad84a6740 100644 --- a/spring-beans/src/main/java/brave/spring/beans/MessagingTracingFactoryBean.java +++ b/spring-beans/src/main/java/brave/spring/beans/MessagingTracingFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/spring-beans/src/main/java/brave/spring/beans/RpcTracingFactoryBean.java b/spring-beans/src/main/java/brave/spring/beans/RpcTracingFactoryBean.java index 5ea1e8a40e..bdb9abd648 100644 --- a/spring-beans/src/main/java/brave/spring/beans/RpcTracingFactoryBean.java +++ b/spring-beans/src/main/java/brave/spring/beans/RpcTracingFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2020 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/spring-beans/src/main/java/brave/spring/beans/TracingFactoryBean.java b/spring-beans/src/main/java/brave/spring/beans/TracingFactoryBean.java index 5ce34ae8b1..fe89bafc2e 100644 --- a/spring-beans/src/main/java/brave/spring/beans/TracingFactoryBean.java +++ b/spring-beans/src/main/java/brave/spring/beans/TracingFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,6 +14,7 @@ package brave.spring.beans; import brave.Clock; +import brave.ErrorParser; import brave.Tracing; import brave.TracingCustomizer; import brave.handler.SpanHandler; @@ -22,14 +23,25 @@ import brave.sampler.Sampler; import java.util.ArrayList; import java.util.List; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.config.AbstractFactoryBean; +import zipkin2.Endpoint; +import zipkin2.Span; +import zipkin2.reporter.Reporter; /** Spring XML config does not support chained builders. This converts accordingly */ public class TracingFactoryBean extends AbstractFactoryBean { + // Spring uses commons logging + static final Log logger = LogFactory.getLog(TracingFactoryBean.class); + String localServiceName; + @Deprecated Object localEndpoint, endpoint; // don't pin zipkin class + @Deprecated Object spanReporter; // don't pin zipkin class List spanHandlers = new ArrayList(); Clock clock; Sampler sampler; + @Deprecated ErrorParser errorParser; CurrentTraceContext currentTraceContext; Propagation.Factory propagationFactory; Boolean traceId128Bit; @@ -39,9 +51,17 @@ public class TracingFactoryBean extends AbstractFactoryBean { @Override protected Tracing createInstance() { Tracing.Builder builder = Tracing.newBuilder(); if (localServiceName != null) builder.localServiceName(localServiceName); + if (localEndpoint == null) localEndpoint = endpoint; + if (localEndpoint != null) { + builder.endpoint((Endpoint) localEndpoint); + } + if (spanReporter != null) { + builder.spanReporter((Reporter) spanReporter); + } for (SpanHandler spanHandler : spanHandlers) { builder.addSpanHandler(spanHandler); } + if (errorParser != null) builder.errorParser(errorParser); if (clock != null) builder.clock(clock); if (sampler != null) builder.sampler(sampler); if (currentTraceContext != null) builder.currentTraceContext(currentTraceContext); @@ -70,6 +90,31 @@ public void setLocalServiceName(String localServiceName) { this.localServiceName = localServiceName; } + @Deprecated public void setLocalEndpoint(Object localEndpoint) { + logger.warn("The property 'localEndpoint' will be removed in a future release.\n" + + "Use the property 'localServiceName' instead"); + this.localEndpoint = localEndpoint; + } + + @Deprecated public void setEndpoint(Object endpoint) { + logger.warn("The property 'endpoint' will be removed in a future release.\n" + + "Use the property 'localServiceName' instead"); + this.endpoint = endpoint; + } + + @Deprecated public void setSpanReporter(Object spanReporter) { + logger.warn("The property 'spanReporter' will be removed in a future release.\n" + + "Add ZipkinSpanHandler the list property 'spanHandlers' instead"); + this.spanReporter = spanReporter; + } + + // NOTE: we don't need to use the FinishedSpanHandler type as it extends SpanHandler + @Deprecated public void setFinishedSpanHandlers(List finishedSpanHandlers) { + logger.warn("The list property 'finishedSpanHandlers' will be removed in a future release.\n" + + "Use the list property 'spanHandlers' instead"); + this.spanHandlers.addAll(finishedSpanHandlers); + } + public void setSpanHandlers(List spanHandlers) { this.spanHandlers.addAll(spanHandlers); } @@ -78,6 +123,12 @@ public void setClock(Clock clock) { this.clock = clock; } + @Deprecated public void setErrorParser(ErrorParser errorParser) { + logger.warn("The property 'errorParser' will be removed in a future release.\n" + + "Add ZipkinSpanHandler with the 'errorTag' you want into list property 'spanHandlers'"); + this.errorParser = errorParser; + } + public void setSampler(Sampler sampler) { this.sampler = sampler; } diff --git a/spring-beans/src/test/java/brave/spring/beans/BaggagePropagationFactoryBeanTest.java b/spring-beans/src/test/java/brave/spring/beans/BaggagePropagationFactoryBeanTest.java index 9f0d8de27a..fd06d88905 100755 --- a/spring-beans/src/test/java/brave/spring/beans/BaggagePropagationFactoryBeanTest.java +++ b/spring-beans/src/test/java/brave/spring/beans/BaggagePropagationFactoryBeanTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -16,6 +16,7 @@ import brave.baggage.BaggagePropagation; import brave.baggage.BaggagePropagationCustomizer; import brave.propagation.B3Propagation; +import brave.propagation.B3SinglePropagation; import brave.propagation.Propagation; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -26,8 +27,6 @@ import static org.mockito.Mockito.verify; class BaggagePropagationFactoryBeanTest { - public static Propagation.Factory PROPAGATION_FACTORY = mock(Propagation.Factory.class); - XmlBeans context; @AfterEach void close() { @@ -47,13 +46,13 @@ class BaggagePropagationFactoryBeanTest { context = new XmlBeans("" + "\n" + " \n" - + " \n" + + " \n" + " \n" + "\n" ); assertThat(context.getBean("propagationFactory", Propagation.Factory.class)) - .isEqualTo(PROPAGATION_FACTORY); + .isEqualTo(B3SinglePropagation.FACTORY); } @Test void configs() { @@ -117,7 +116,7 @@ class BaggagePropagationFactoryBeanTest { + "\n" + "\n" + " \n" - + " \n" + + " \n" + " \n" + " \n" + " \n" @@ -131,7 +130,7 @@ class BaggagePropagationFactoryBeanTest { assertThat(context.getBean("propagationFactory", Propagation.Factory.class)) .extracting("delegate") - .isEqualTo(B3Propagation.FACTORY); + .isEqualTo(B3SinglePropagation.FACTORY); } public static final BaggagePropagationCustomizer diff --git a/spring-beans/src/test/java/brave/spring/beans/EndpointFactoryBeanTest.java b/spring-beans/src/test/java/brave/spring/beans/EndpointFactoryBeanTest.java new file mode 100755 index 0000000000..a3b87e8082 --- /dev/null +++ b/spring-beans/src/test/java/brave/spring/beans/EndpointFactoryBeanTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.spring.beans; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.BeanCreationException; +import zipkin2.Endpoint; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +public class EndpointFactoryBeanTest { + XmlBeans context; + + @AfterEach void close() { + if (context != null) context.close(); + } + + @Test void serviceName() { + context = new XmlBeans("" + + "\n" + + " \n" + + "" + ); + + assertThat(context.getBean("endpoint", Endpoint.class)) + .isEqualTo(Endpoint.newBuilder().serviceName("brave-webmvc-example").build()); + } + + @Test void ip() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + "" + ); + + assertThat(context.getBean("endpoint", Endpoint.class)) + .isEqualTo(Endpoint.newBuilder() + .serviceName("brave-webmvc-example") + .ip("1.2.3.4") + .build()); + } + + @Test void ip_malformed() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + "" + ); + + try { + context.getBean("endpoint", Endpoint.class); + failBecauseExceptionWasNotThrown(BeanCreationException.class); + } catch (BeanCreationException e) { + assertThat(e) + .hasMessageContaining("endpoint.ip: localhost is not an IP literal"); + } + } + + @Test void port() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + " \n" + + "" + ); + + assertThat(context.getBean("endpoint", Endpoint.class)) + .isEqualTo(Endpoint.newBuilder() + .serviceName("brave-webmvc-example") + .ip("1.2.3.4") + .port(8080).build()); + } +} diff --git a/spring-beans/src/test/java/brave/spring/beans/ExtraFieldPropagationFactoryBeanTest.java b/spring-beans/src/test/java/brave/spring/beans/ExtraFieldPropagationFactoryBeanTest.java new file mode 100755 index 0000000000..34f26b75cf --- /dev/null +++ b/spring-beans/src/test/java/brave/spring/beans/ExtraFieldPropagationFactoryBeanTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2013-2023 The OpenZipkin Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package brave.spring.beans; + +import brave.baggage.BaggagePropagation; +import brave.propagation.B3Propagation; +import brave.propagation.B3SinglePropagation; +import brave.propagation.ExtraFieldCustomizer; +import brave.propagation.ExtraFieldPropagation; +import brave.propagation.Propagation; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class ExtraFieldPropagationFactoryBeanTest { + XmlBeans context; + + @AfterEach void close() { + if (context != null) context.close(); + } + + @Test void propagationFactory_default() { + context = new XmlBeans("" + + "" + ); + + assertThat(context.getBean("propagationFactory", Propagation.Factory.class)) + .extracting("delegate") + .isEqualTo(B3Propagation.FACTORY); + } + + @Test void propagationFactory() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + " \n" + + "" + ); + + assertThat(context.getBean("propagationFactory", Propagation.Factory.class)) + .extracting("delegate") + .isEqualTo(B3SinglePropagation.FACTORY); + } + + @Test void fields() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + " customer-id\n" + + " x-vcap-request-id\n" + + " \n" + + " " + + "" + ); + + Propagation propagation = + context.getBean("propagationFactory", Propagation.Factory.class).get(); + + assertThat(BaggagePropagation.allKeyNames(propagation)).endsWith( + "customer-id", + "x-vcap-request-id" + ); + } + + public static final ExtraFieldCustomizer CUSTOMIZER_ONE = mock(ExtraFieldCustomizer.class); + public static final ExtraFieldCustomizer CUSTOMIZER_TWO = mock(ExtraFieldCustomizer.class); + + @Test void customizers() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " " + + "" + ); + + context.getBean("propagationFactory", Propagation.Factory.class); + + verify(CUSTOMIZER_ONE).customize(any(ExtraFieldPropagation.FactoryBuilder.class)); + verify(CUSTOMIZER_TWO).customize(any(ExtraFieldPropagation.FactoryBuilder.class)); + } +} diff --git a/spring-beans/src/test/java/brave/spring/beans/HttpTracingFactoryBeanTest.java b/spring-beans/src/test/java/brave/spring/beans/HttpTracingFactoryBeanTest.java index aab695fe1b..cbce9311a9 100755 --- a/spring-beans/src/test/java/brave/spring/beans/HttpTracingFactoryBeanTest.java +++ b/spring-beans/src/test/java/brave/spring/beans/HttpTracingFactoryBeanTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,8 +14,11 @@ package brave.spring.beans; import brave.Tracing; +import brave.http.HttpClientParser; import brave.http.HttpRequestParser; import brave.http.HttpResponseParser; +import brave.http.HttpSampler; +import brave.http.HttpServerParser; import brave.http.HttpTracing; import brave.http.HttpTracingCustomizer; import brave.propagation.Propagation; @@ -31,6 +34,8 @@ public class HttpTracingFactoryBeanTest { public static Tracing TRACING = mock(Tracing.class); + public static HttpClientParser CLIENT_PARSER = mock(HttpClientParser.class); + public static HttpServerParser SERVER_PARSER = mock(HttpServerParser.class); public static HttpRequestParser REQUEST_PARSER = mock(HttpRequestParser.class); public static HttpResponseParser RESPONSE_PARSER = mock(HttpResponseParser.class); public static Propagation PROPAGATION = mock(Propagation.class); @@ -55,6 +60,22 @@ public class HttpTracingFactoryBeanTest { .isEqualTo(TRACING); } + @Test void clientParser() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "" + ); + + assertThat(context.getBean("httpTracing", HttpTracing.class).clientParser()) + .isEqualTo(CLIENT_PARSER); + } + @Test void clientRequestParser() { context = new XmlBeans("" + "\n" @@ -89,6 +110,22 @@ public class HttpTracingFactoryBeanTest { .isEqualTo(RESPONSE_PARSER); } + @Test void serverParser() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "" + ); + + assertThat(context.getBean("httpTracing", HttpTracing.class).serverParser()) + .isEqualTo(SERVER_PARSER); + } + @Test void serverRequestParser() { context = new XmlBeans("" + "\n" @@ -123,6 +160,24 @@ public class HttpTracingFactoryBeanTest { .isEqualTo(RESPONSE_PARSER); } + @Test void clientSampler() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "" + ); + + assertThat(context.getBean("httpTracing", HttpTracing.class)) + .extracting("clientSampler") + .usingRecursiveComparison() + .isEqualTo(HttpSampler.NEVER_SAMPLE); + } + @Test void clientRequestSampler() { context = new XmlBeans("" + "\n" @@ -139,6 +194,22 @@ public class HttpTracingFactoryBeanTest { .isEqualTo(SamplerFunctions.neverSample()); } + @Test void serverSampler() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "" + ); + + assertThat(context.getBean("httpTracing", HttpTracing.class).serverSampler()) + .isEqualTo(HttpSampler.NEVER_SAMPLE); + } + @Test void serverRequestSampler() { context = new XmlBeans("" + "\n" diff --git a/spring-beans/src/test/java/brave/spring/beans/MessagingTracingFactoryBeanTest.java b/spring-beans/src/test/java/brave/spring/beans/MessagingTracingFactoryBeanTest.java index 3901131d01..e8e07cdae7 100755 --- a/spring-beans/src/test/java/brave/spring/beans/MessagingTracingFactoryBeanTest.java +++ b/spring-beans/src/test/java/brave/spring/beans/MessagingTracingFactoryBeanTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/spring-beans/src/test/java/brave/spring/beans/RpcTracingFactoryBeanTest.java b/spring-beans/src/test/java/brave/spring/beans/RpcTracingFactoryBeanTest.java index 36a33b8f21..ce8005b0e5 100755 --- a/spring-beans/src/test/java/brave/spring/beans/RpcTracingFactoryBeanTest.java +++ b/spring-beans/src/test/java/brave/spring/beans/RpcTracingFactoryBeanTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at diff --git a/spring-beans/src/test/java/brave/spring/beans/TracingFactoryBeanTest.java b/spring-beans/src/test/java/brave/spring/beans/TracingFactoryBeanTest.java index 26145b2764..278df93bd8 100755 --- a/spring-beans/src/test/java/brave/spring/beans/TracingFactoryBeanTest.java +++ b/spring-beans/src/test/java/brave/spring/beans/TracingFactoryBeanTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 The OpenZipkin Authors + * Copyright 2013-2023 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at @@ -14,14 +14,21 @@ package brave.spring.beans; import brave.Clock; +import brave.ErrorParser; import brave.Tracing; import brave.TracingCustomizer; +import brave.handler.FinishedSpanHandler; import brave.handler.SpanHandler; +import brave.handler.MutableSpan; +import brave.propagation.B3SinglePropagation; import brave.propagation.ThreadLocalCurrentTraceContext; import brave.sampler.Sampler; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import zipkin2.reporter.Reporter; + import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -29,6 +36,8 @@ public class TracingFactoryBeanTest { public static final Clock CLOCK = mock(Clock.class); + public static final ErrorParser ERROR_PARSER = mock(ErrorParser.class); + XmlBeans context; @AfterEach void close() { @@ -64,6 +73,82 @@ public class TracingFactoryBeanTest { .isEqualTo("brave-webmvc-example"); } + @Test void localEndpoint() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + " \n" + + "" + , "" + + "\n" + + " \n" + + "" + ); + + MutableSpan defaultSpan = new MutableSpan(); + defaultSpan.localServiceName("brave-webmvc-example"); + defaultSpan.localIp("1.2.3.4"); + defaultSpan.localPort(8080); + + assertThat(context.getBean("tracing", Tracing.class)) + .extracting("tracer.pendingSpans.defaultSpan") + .isEqualTo(defaultSpan); + } + + @Test void endpoint() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + " \n" + + "" + , "" + + "\n" + + " \n" + + "" + ); + + MutableSpan defaultSpan = new MutableSpan(); + defaultSpan.localServiceName("brave-webmvc-example"); + defaultSpan.localIp("1.2.3.4"); + defaultSpan.localPort(8080); + + assertThat(context.getBean("tracing", Tracing.class)) + .extracting("tracer.pendingSpans.defaultSpan") + .isEqualTo(defaultSpan); + } + + @Test void spanReporter() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + " \n" + + "" + ); + + assertThat(context.getBean("tracing", Tracing.class)) + .extracting("tracer.spanHandler.delegate.spanReporter.delegate") + .isEqualTo(Reporter.CONSOLE); + } + + public static final FinishedSpanHandler FINISHED_SPAN_HANDLER = mock(FinishedSpanHandler.class); + + @Test void finishedSpanHandlers() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + " \n" + + "" + ); + + assertThat(context.getBean("tracing", Tracing.class)) + .extracting("tracer.spanHandler.delegate") + .isEqualTo(FINISHED_SPAN_HANDLER); + } + public static final SpanHandler SPAN_HANDLER = mock(SpanHandler.class); @Test void spanHandlers() { @@ -80,6 +165,24 @@ public class TracingFactoryBeanTest { .isEqualTo(SPAN_HANDLER); } + @Test void bothSpanHandlers() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "" + ); + + assertThat(context.getBean("tracing", Tracing.class)) + .extracting("tracer.spanHandler.delegate.handlers") + .asInstanceOf(InstanceOfAssertFactories.ARRAY) + .containsExactly(FINISHED_SPAN_HANDLER, SPAN_HANDLER); + } + @Test void clock() { context = new XmlBeans("" + "\n" @@ -90,10 +193,24 @@ public class TracingFactoryBeanTest { ); assertThat(context.getBean("tracing", Tracing.class)) - .extracting("tracer.pendingSpans.clock") + .extracting("tracer.clock") .isEqualTo(CLOCK); } + @Test void errorParser() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + " \n" + + "" + ); + + assertThat(context.getBean("tracing", Tracing.class)) + .extracting("errorParser") + .isEqualTo(ERROR_PARSER); + } + @Test void sampler() { context = new XmlBeans("" + "\n" @@ -122,6 +239,19 @@ public class TracingFactoryBeanTest { .isInstanceOf(ThreadLocalCurrentTraceContext.class); } + @Test void propagationFactory() { + context = new XmlBeans("" + + "\n" + + " \n" + + " \n" + + " \n" + + "" + ); + + assertThat(context.getBean("tracing", Tracing.class).propagationFactory()) + .isSameAs(B3SinglePropagation.FACTORY); + } + @Test void traceId128Bit() { context = new XmlBeans("" + "\n"