From 1f45dadf345b257dd7dfca0acd667a2837f9fa3d Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 15 Nov 2021 13:33:28 +0100 Subject: [PATCH] Added Brave, OTel, Zipkin & Wavefront (#1) * WIP on adding wavefront, brave, otel and zipkin * Moved most of the code * Added tests * Added brave & otel local sample setup for zipkin * Fixed setup * Bumped libs * Fixed javadoc issues * Updated dependencies * Added milestone & snapshot repo --- .github/ISSUE_TEMPLATE/bug_report.md | 14 +- .github/ISSUE_TEMPLATE/enhancement-request.md | 7 +- .../workflows/gradle-wrapper-validation.yml | 2 +- CONTRIBUTING.md | 24 +- README.md | 6 +- build.gradle | 588 +++++++++--------- config/checkstyle/checkstyle-suppressions.xml | 7 +- config/checkstyle/checkstyle.xml | 122 ++-- config/checkstyle/suppressions.xml | 11 +- dependencies.gradle | 64 +- gradle.properties | 1 - gradle/wrapper/gradle-wrapper.properties | 2 +- micrometer-tracing-bom/build.gradle | 30 +- .../build.gradle | 22 + .../brave/bridge/BraveBaggageInScope.java | 78 +++ .../brave/bridge/BraveBaggageManager.java | 71 +++ .../bridge/BraveContextWrappingFunction.java | 62 ++ .../bridge/BraveCurrentTraceContext.java | 115 ++++ .../brave/bridge/BraveFinishedSpan.java | 126 ++++ .../brave/bridge/BraveHttpClientHandler.java | 70 +++ .../brave/bridge/BraveHttpClientRequest.java | 117 ++++ .../brave/bridge/BraveHttpClientResponse.java | 119 ++++ .../brave/bridge/BraveHttpRequest.java | 83 +++ .../brave/bridge/BraveHttpRequestParser.java | 56 ++ .../brave/bridge/BraveHttpResponse.java | 93 +++ .../brave/bridge/BraveHttpResponseParser.java | 56 ++ .../brave/bridge/BraveHttpServerHandler.java | 49 ++ .../brave/bridge/BraveHttpServerRequest.java | 149 +++++ .../brave/bridge/BraveHttpServerResponse.java | 130 ++++ .../tracing/brave/bridge/BravePropagator.java | 61 ++ .../brave/bridge/BraveSamplerFunction.java | 57 ++ .../tracing/brave/bridge/BraveScopedSpan.java | 94 +++ .../tracing/brave/bridge/BraveSpan.java | 144 +++++ .../brave/bridge/BraveSpanBuilder.java | 136 ++++ .../brave/bridge/BraveSpanCustomizer.java | 59 ++ .../brave/bridge/BraveTraceContext.java | 92 +++ .../bridge/BraveTraceContextBuilder.java | 71 +++ .../tracing/brave/bridge/BraveTracer.java | 148 +++++ .../bridge/CompositePropagationFactory.java | 237 +++++++ .../CompositePropagationFactorySupplier.java | 73 +++ .../brave/bridge/CompositeSpanHandler.java | 71 +++ .../tracing/brave/bridge/W3CPropagation.java | 482 ++++++++++++++ .../PropagationFactorySupplier.java | 25 +- .../brave/propagation/PropagationType.java | 38 +- .../sampler/ProbabilityBasedSampler.java | 111 ++++ .../brave/sampler/RateLimitingSampler.java | 52 ++ .../io/micrometer/tracing/brave/DocTests.java | 171 +++++ .../bridge/BraveHttpClientHandlerTests.java | 36 ++ .../bridge/BraveTraceContextBuilderTests.java | 53 ++ .../bridge/W3CBaggagePropagatorTest.java | 170 +++++ .../brave/bridge/W3CPropagationTest.java | 325 ++++++++++ .../sampler/ProbabilityBasedSamplerTests.java | 87 +++ .../tracing/internal/SpanNameUtilTests.java | 54 ++ .../build.gradle | 26 + .../otel/bridge/ArrayListSpanProcessor.java | 105 ++++ .../bridge/BaggageTaggingSpanProcessor.java | 68 ++ .../otel/bridge/BigendianEncoding.java | 86 +++ .../otel/bridge/CompositeSpanExporter.java | 77 +++ .../DefaultHttpClientAttributesExtractor.java | 97 +++ .../DefaultHttpServerAttributesExtractor.java | 142 +++++ .../tracing/otel/bridge/EventListener.java | 31 + .../bridge/EventPublishingContextWrapper.java | 125 ++++ ...tpRequestNetClientAttributesExtractor.java | 54 ++ ...tpRequestNetServerAttributesExtractor.java | 54 ++ .../otel/bridge/OtelBaggageInScope.java | 140 +++++ .../otel/bridge/OtelBaggageManager.java | 280 +++++++++ .../otel/bridge/OtelCurrentTraceContext.java | 115 ++++ .../tracing/otel/bridge/OtelFinishedSpan.java | 196 ++++++ .../otel/bridge/OtelHttpClientHandler.java | 136 ++++ .../otel/bridge/OtelHttpServerHandler.java | 136 ++++ .../tracing/otel/bridge/OtelPropagator.java | 81 +++ .../tracing/otel/bridge/OtelScopedSpan.java | 82 +++ .../tracing/otel/bridge/OtelSpan.java | 152 +++++ .../tracing/otel/bridge/OtelSpanBuilder.java | 141 +++++ .../otel/bridge/OtelSpanCustomizer.java | 53 ++ .../tracing/otel/bridge/OtelSpanInScope.java | 50 ++ .../tracing/otel/bridge/OtelTraceContext.java | 141 +++++ .../otel/bridge/OtelTraceContextBuilder.java | 79 +++ .../tracing/otel/bridge/OtelTracer.java | 149 +++++ .../otel/bridge/PathAttributeExtractor.java | 52 ++ .../otel/bridge/SkipPatternSampler.java | 49 ++ .../bridge/Slf4JBaggageEventListener.java | 87 +++ .../otel/bridge/Slf4JEventListener.java | 65 ++ .../otel/bridge/SpanExporterCustomizer.java | 38 ++ .../otel/bridge/SpanFromSpanContext.java | 182 ++++++ .../propagation/BaggageTextMapPropagator.java | 90 +++ .../CompositeTextMapPropagator.java | 167 +++++ .../otel/propagation/PropagationType.java | 57 ++ .../BaggageTaggingSpanProcessorTest.java | 69 ++ .../CompositeTextMapPropagatorTest.java | 88 +++ .../build.gradle | 23 + .../wavefront/WavefrontBraveSpanHandler.java | 59 ++ .../wavefront/WavefrontOtelSpanHandler.java | 85 +++ ...vefrontSpringObservabilitySpanHandler.java | 483 ++++++++++++++ .../build.gradle | 44 ++ .../reporter/zipkin/ZipkinBraveSetup.java | 255 ++++++++ .../reporter/zipkin/ZipkinOtelSetup.java | 277 +++++++++ .../zipkin/ZipkinBraveSetupTests.java | 71 +++ .../reporter/zipkin/ZipkinOtelSetupTests.java | 72 +++ micrometer-tracing/build.gradle | 10 +- .../io/micrometer/tracing/BaggageInScope.java | 14 +- .../io/micrometer/tracing/BaggageManager.java | 14 +- .../tracing/CurrentTraceContext.java | 16 +- .../micrometer/tracing/SamplerFunction.java | 14 +- .../io/micrometer/tracing/ScopedSpan.java | 20 +- .../main/java/io/micrometer/tracing/Span.java | 99 +-- .../io/micrometer/tracing/SpanAndScope.java | 31 +- .../io/micrometer/tracing/SpanCustomizer.java | 11 +- .../java/io/micrometer/tracing/SpanName.java | 9 +- .../java/io/micrometer/tracing/SpanNamer.java | 3 +- .../java/io/micrometer/tracing/Taggable.java | 6 +- .../micrometer/tracing/ThreadLocalSpan.java | 45 +- .../io/micrometer/tracing/TraceContext.java | 28 +- .../java/io/micrometer/tracing/Tracer.java | 23 +- .../tracing/annotation/ContinueSpan.java | 2 +- .../tracing/annotation/NewSpan.java | 2 +- .../tracing/annotation/NewSpanParser.java | 38 ++ .../annotation/NoOpTagValueResolver.java | 2 +- .../SleuthMethodInvocationProcessor.java} | 38 +- .../tracing/annotation/SpanTag.java | 2 +- .../TagValueExpressionResolver.java | 2 +- .../tracing/annotation/TagValueResolver.java | 2 +- .../tracing/docs/AssertingSpan.java | 40 +- .../tracing/docs/AssertingSpanBuilder.java | 29 +- .../tracing/docs/AssertingSpanCustomizer.java | 15 +- .../tracing/docs/DocumentedSpan.java | 12 +- .../docs/DocumentedSpanAssertions.java | 6 +- .../micrometer/tracing/docs/EventValue.java | 4 +- .../tracing/docs/ImmutableAssertingSpan.java | 4 +- .../docs/ImmutableAssertingSpanBuilder.java | 4 +- .../ImmutableAssertingSpanCustomizer.java | 4 +- .../io/micrometer/tracing/docs/TagKey.java | 5 +- .../tracing/exporter/FinishedSpan.java | 38 +- .../tracing/exporter/SpanFilter.java | 9 +- .../exporter/SpanIgnoringSpanFilter.java | 21 +- .../tracing/exporter/SpanReporter.java | 5 +- .../DefaultTracingRecordingHandler.java | 87 +++ .../HttpClientTracingRecordingHandler.java | 69 ++ .../HttpServerTracingRecordingHandler.java | 109 ++++ .../handler/HttpTracingRecordingHandler.java | 133 ++++ .../handler/TracingRecordingHandler.java | 169 +++++ .../tracing/http/HttpClientHandler.java | 7 +- .../micrometer/tracing/http/HttpRequest.java | 95 --- .../tracing/http/HttpRequestParser.java | 4 +- .../micrometer/tracing/http/HttpResponse.java | 80 --- .../tracing/http/HttpResponseParser.java | 4 +- .../tracing/http/HttpServerHandler.java | 10 +- .../tracing/http/HttpServerRequest.java | 57 -- .../io/micrometer/tracing/http/Request.java | 55 -- .../io/micrometer/tracing/http/Response.java | 72 --- .../tracing/internal/DefaultSpanNamer.java | 68 ++ .../tracing/internal/EncodingUtils.java | 4 +- .../tracing/internal/SpanNameUtil.java | 2 +- .../tracing/propagation/Propagator.java | 40 +- .../io/micrometer/tracing/transport/Kind.java | 53 -- .../transport/http/HttpClientResponse.java | 49 -- .../tracing/transport/http/HttpRequest.java | 95 --- .../tracing/transport/http/HttpResponse.java | 80 --- .../transport/http/HttpServerRequest.java | 57 -- .../transport/http/HttpServerResponse.java | 49 -- .../tracing/transport/http/Request.java | 55 -- .../tracing/transport/http/Response.java | 72 --- .../NoOpTagValueResolverTests.java} | 32 +- .../docs/DocumentedSpanAssertionsTests.java | 306 +++++++++ .../exporter/SpanIgnoringSpanFilterTests.java | 74 +++ .../src/test/resources/logback.xml | 40 +- settings.gradle | 32 +- 167 files changed, 11078 insertions(+), 1838 deletions(-) create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/build.gradle create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveBaggageInScope.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveBaggageManager.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveContextWrappingFunction.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveCurrentTraceContext.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveFinishedSpan.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpClientHandler.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpClientRequest.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpClientResponse.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpRequest.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpRequestParser.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpResponse.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpResponseParser.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpServerHandler.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpServerRequest.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpServerResponse.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BravePropagator.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveSamplerFunction.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveScopedSpan.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveSpan.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveSpanBuilder.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveSpanCustomizer.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveTraceContext.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveTraceContextBuilder.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveTracer.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/CompositePropagationFactory.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/CompositePropagationFactorySupplier.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/CompositeSpanHandler.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/W3CPropagation.java rename micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpClientRequest.java => micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/propagation/PropagationFactorySupplier.java (55%) mode change 100644 => 100755 rename micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpClientRequest.java => micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/propagation/PropagationType.java (55%) mode change 100644 => 100755 create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/sampler/ProbabilityBasedSampler.java create mode 100755 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/sampler/RateLimitingSampler.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/DocTests.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/BraveHttpClientHandlerTests.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/BraveTraceContextBuilderTests.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/W3CBaggagePropagatorTest.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/W3CPropagationTest.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/sampler/ProbabilityBasedSamplerTests.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/internal/SpanNameUtilTests.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/build.gradle create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/ArrayListSpanProcessor.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/BaggageTaggingSpanProcessor.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/BigendianEncoding.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/CompositeSpanExporter.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/DefaultHttpClientAttributesExtractor.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/DefaultHttpServerAttributesExtractor.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/EventListener.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/EventPublishingContextWrapper.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/HttpRequestNetClientAttributesExtractor.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/HttpRequestNetServerAttributesExtractor.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelBaggageInScope.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelBaggageManager.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelCurrentTraceContext.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelFinishedSpan.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelHttpClientHandler.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelHttpServerHandler.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelPropagator.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelScopedSpan.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelSpan.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelSpanBuilder.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelSpanCustomizer.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelSpanInScope.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelTraceContext.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelTraceContextBuilder.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelTracer.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/PathAttributeExtractor.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/SkipPatternSampler.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/Slf4JBaggageEventListener.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/Slf4JEventListener.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/SpanExporterCustomizer.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/SpanFromSpanContext.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/propagation/BaggageTextMapPropagator.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/propagation/CompositeTextMapPropagator.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/propagation/PropagationType.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/test/java/io/micrometer/tracing/otel/bridge/BaggageTaggingSpanProcessorTest.java create mode 100644 micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/test/java/io/micrometer/tracing/otel/propagation/CompositeTextMapPropagatorTest.java create mode 100644 micrometer-tracing-reporters/micrometer-tracing-reporter-wavefront/build.gradle create mode 100755 micrometer-tracing-reporters/micrometer-tracing-reporter-wavefront/src/main/java/io/micrometer/tracing/reporter/wavefront/WavefrontBraveSpanHandler.java create mode 100755 micrometer-tracing-reporters/micrometer-tracing-reporter-wavefront/src/main/java/io/micrometer/tracing/reporter/wavefront/WavefrontOtelSpanHandler.java create mode 100755 micrometer-tracing-reporters/micrometer-tracing-reporter-wavefront/src/main/java/io/micrometer/tracing/reporter/wavefront/WavefrontSpringObservabilitySpanHandler.java create mode 100644 micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/build.gradle create mode 100644 micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/src/main/java/io/micrometer/tracing/reporter/zipkin/ZipkinBraveSetup.java create mode 100644 micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/src/main/java/io/micrometer/tracing/reporter/zipkin/ZipkinOtelSetup.java create mode 100644 micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/src/test/java/io/micrometer/tracing/reporter/zipkin/ZipkinBraveSetupTests.java create mode 100644 micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/src/test/java/io/micrometer/tracing/reporter/zipkin/ZipkinOtelSetupTests.java create mode 100644 micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/NewSpanParser.java rename micrometer-tracing/src/main/java/io/micrometer/tracing/{http/HttpServerResponse.java => annotation/SleuthMethodInvocationProcessor.java} (52%) create mode 100755 micrometer-tracing/src/main/java/io/micrometer/tracing/handler/DefaultTracingRecordingHandler.java create mode 100755 micrometer-tracing/src/main/java/io/micrometer/tracing/handler/HttpClientTracingRecordingHandler.java create mode 100755 micrometer-tracing/src/main/java/io/micrometer/tracing/handler/HttpServerTracingRecordingHandler.java create mode 100755 micrometer-tracing/src/main/java/io/micrometer/tracing/handler/HttpTracingRecordingHandler.java create mode 100755 micrometer-tracing/src/main/java/io/micrometer/tracing/handler/TracingRecordingHandler.java delete mode 100644 micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpRequest.java delete mode 100644 micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpResponse.java delete mode 100644 micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpServerRequest.java delete mode 100644 micrometer-tracing/src/main/java/io/micrometer/tracing/http/Request.java delete mode 100644 micrometer-tracing/src/main/java/io/micrometer/tracing/http/Response.java create mode 100755 micrometer-tracing/src/main/java/io/micrometer/tracing/internal/DefaultSpanNamer.java delete mode 100644 micrometer-tracing/src/main/java/io/micrometer/tracing/transport/Kind.java delete mode 100644 micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpClientResponse.java delete mode 100644 micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpRequest.java delete mode 100644 micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpResponse.java delete mode 100644 micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpServerRequest.java delete mode 100644 micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpServerResponse.java delete mode 100644 micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/Request.java delete mode 100644 micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/Response.java rename micrometer-tracing/src/{main/java/io/micrometer/tracing/http/HttpClientResponse.java => test/java/io/micrometer/tracing/annotation/NoOpTagValueResolverTests.java} (52%) create mode 100644 micrometer-tracing/src/test/java/io/micrometer/tracing/docs/DocumentedSpanAssertionsTests.java create mode 100644 micrometer-tracing/src/test/java/io/micrometer/tracing/exporter/SpanIgnoringSpanFilterTests.java diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 7dd00a4a..627a03d4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,9 +1,6 @@ --- -name: Bug report -about: Report a bug in Micrometer Tracing -title: '' -labels: bug -assignees: '' +name: Bug report about: Report a bug in Micrometer Tracing title: '' +labels: bug assignees: '' --- @@ -12,9 +9,10 @@ A clear and concise description of what the bug is. **Environment** - - Micrometer Tracing version [e.g. 1.7.1] - - OS: [e.g. macOS] - - Java version: [e.g. output of `java -version`] + +- Micrometer Tracing version [e.g. 1.7.1] +- OS: [e.g. macOS] +- Java version: [e.g. output of `java -version`] **To Reproduce** How to reproduce the bug: diff --git a/.github/ISSUE_TEMPLATE/enhancement-request.md b/.github/ISSUE_TEMPLATE/enhancement-request.md index 1eb75c9d..c69ac68a 100644 --- a/.github/ISSUE_TEMPLATE/enhancement-request.md +++ b/.github/ISSUE_TEMPLATE/enhancement-request.md @@ -1,9 +1,6 @@ --- -name: Enhancement request -about: Request an enhancement for Micrometer -title: '' -labels: enhancement -assignees: '' +name: Enhancement request about: Request an enhancement for Micrometer title: '' +labels: enhancement assignees: '' --- diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 405a2b30..f247eb57 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -1,5 +1,5 @@ name: "Validate Gradle Wrapper" -on: [push, pull_request] +on: [ push, pull_request ] jobs: validation: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 54844d40..b7acd8c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,8 @@ This Contributing Guide is intended for those that would like to contribute to Micrometer Tracing. -If you would like to use any of the published Micrometer Tracing modules as a library in your project, you can instead include the Micrometer Tracing artifacts from the Maven Central repository using your build tool of choice. +If you would like to use any of the published Micrometer Tracing modules as a library in your project, you can instead +include the Micrometer Tracing artifacts from the Maven Central repository using your build tool of choice. ## Code of Conduct @@ -10,25 +11,28 @@ See [our Contributor Code of Conduct](https://github.com/micrometer-metrics/.git ## Contributions -Contributions come in various forms and are not limited to code changes. -The Micrometer Tracing community benefits from contributions in all forms. +Contributions come in various forms and are not limited to code changes. The Micrometer Tracing community benefits from +contributions in all forms. + +For example, those with Micrometer Tracing knowledge and experience can contribute by: -For example, those with Micrometer Tracing knowledge and experience can contribute by: * TODO: [Contributing documentation]() * Answering [Stackoverflow questions](https://stackoverflow.com/tags/micrometer-tracing) * Answering questions on the [Micrometer slack](https://slack.micrometer.io) * Share Micrometer Tracing knowledge in other ways (e.g. presentations, blogs) -The remainder of this document will focus on guidance for contributing code changes. It will help contributors to build, modify, or test the Micrometer Tracing source code. +The remainder of this document will focus on guidance for contributing code changes. It will help contributors to build, +modify, or test the Micrometer Tracing source code. ## Contributor License Agreement -Contributions in the form of source changes require that you fill out and submit the [Contributor License Agreement](https://cla.pivotal.io/sign/pivotal) if you have not done so previously. +Contributions in the form of source changes require that you fill out and submit +the [Contributor License Agreement](https://cla.pivotal.io/sign/pivotal) if you have not done so previously. ## Getting the source -The Micrometer Tracing source code is hosted on GitHub at https://github.com/micrometer-metrics/tracing. -You can use a Git client to clone the source code to your local machine. +The Micrometer Tracing source code is hosted on GitHub at https://github.com/micrometer-metrics/tracing. You can use a +Git client to clone the source code to your local machine. ## Building @@ -50,8 +54,8 @@ The Gradle `check` task depends on the `test` task, and so tests will be run as ### Publishing local snapshots -Run `./gradlew pTML` to publish a Maven-style snapshot to your Maven local repo. -The build automatically calculates the "next" version for you when publishing snapshots. +Run `./gradlew pTML` to publish a Maven-style snapshot to your Maven local repo. The build automatically calculates +the "next" version for you when publishing snapshots. These local snapshots can be used in another project to test the changes. For example: diff --git a/README.md b/README.md index 0419d5c3..5d4fa5a4 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,9 @@ dependencies { ## Milestone releases -Milestone releases are published to https://repo.spring.io/milestone. -Include that as a maven repository in your build configuration to use milestone releases. -Note that milestone releases are for testing purposes and are not intended for production use. +Milestone releases are published to https://repo.spring.io/milestone. Include that as a maven repository in your build +configuration to use milestone releases. Note that milestone releases are for testing purposes and are not intended for +production use. ## Documentation diff --git a/build.gradle b/build.gradle index a5235421..b32822ea 100644 --- a/build.gradle +++ b/build.gradle @@ -1,30 +1,30 @@ buildscript { - dependencyLocking { - lockAllConfigurations() - } - - repositories { - mavenCentral() - gradlePluginPortal() - } - - dependencies { - classpath 'gradle.plugin.com.hierynomus.gradle.plugins:license-gradle-plugin:0.16.1' - classpath 'com.netflix.nebula:nebula-release-plugin:15.3.1' - classpath 'com.netflix.nebula:nebula-publishing-plugin:17.3.2' - classpath 'com.netflix.nebula:nebula-project-plugin:8.1.0' - classpath 'io.spring.nohttp:nohttp-gradle:0.0.10' - classpath 'org.gradle:test-retry-gradle-plugin:1.3.1' - classpath 'io.github.gradle-nexus:publish-plugin:1.1.0' - - constraints { - classpath('org.ow2.asm:asm:7.3.1') { - because 'Supports modern JDKs' - } - } - } - - configurations.classpath.resolutionStrategy.cacheDynamicVersionsFor 0, 'minutes' + dependencyLocking { + lockAllConfigurations() + } + + repositories { + mavenCentral() + gradlePluginPortal() + } + + dependencies { + classpath 'gradle.plugin.com.hierynomus.gradle.plugins:license-gradle-plugin:0.16.1' + classpath 'com.netflix.nebula:nebula-release-plugin:15.3.1' + classpath 'com.netflix.nebula:nebula-publishing-plugin:17.3.2' + classpath 'com.netflix.nebula:nebula-project-plugin:8.1.0' + classpath 'io.spring.nohttp:nohttp-gradle:0.0.10' + classpath 'org.gradle:test-retry-gradle-plugin:1.3.1' + classpath 'io.github.gradle-nexus:publish-plugin:1.1.0' + + constraints { + classpath('org.ow2.asm:asm:7.3.1') { + because 'Supports modern JDKs' + } + } + } + + configurations.classpath.resolutionStrategy.cacheDynamicVersionsFor 0, 'minutes' } // TODO: remove this hack, see: https://github.com/nebula-plugins/nebula-release-plugin/issues/213 @@ -36,281 +36,287 @@ apply plugin: 'io.github.gradle-nexus.publish-plugin' apply from: "dependencies.gradle" allprojects { - group = 'io.micrometer' - ext.'release.stage' = releaseStage ?: 'SNAPSHOT' + group = 'io.micrometer' + ext.'release.stage' = releaseStage ?: 'SNAPSHOT' - afterEvaluate { project -> println "I'm configuring $project.name with version $project.version" } + afterEvaluate { project -> println "I'm configuring $project.name with version $project.version" } } subprojects { - apply plugin: 'signing' - - if (project.name != 'micrometer-tracing-bom') { - apply plugin: 'java-library' - apply plugin: 'com.github.hierynomus.license' - apply plugin: 'checkstyle' - apply plugin: 'io.spring.nohttp' - apply plugin: 'org.gradle.test-retry' - - java { - // It is more idiomatic to define different features for different sets of optional - // dependencies, e.g., 'dropwizard' and 'reactor'. If this library published Gradle - // metadata, Gradle users would be able to use these feature names in their dependency - // declarations instead of understanding the actual required optional dependencies. - // But we don't publish Gradle metadata yet and this may be overkill so just have a - // single feature for now to correspond to any optional dependency. - registerFeature('optional') { - usingSourceSet(sourceSets.main) - } - } - - // All projects use optional annotations, but since we don't expose them downstream we would - // have to add the dependency in every project, which is tedious so just do it here. - dependencies { - // JSR-305 only used for non-required meta-annotations - optionalApi "com.google.code.findbugs:jsr305:latest.release" - checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:latest.release") - } - - tasks { - compileJava { - options.encoding = 'UTF-8' - options.compilerArgs << '-Xlint:unchecked' << '-Xlint:deprecation' - - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - - // ensure Java 8 baseline is enforced for main source - if (JavaVersion.current().isJava9Compatible()) { - options.release = 8 - } - } - compileTestJava { - options.encoding = 'UTF-8' - options.compilerArgs << '-Xlint:unchecked' << '-Xlint:deprecation' - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - - javadoc { - configure(options) { - tags( - 'apiNote:a:API Note:', - 'implSpec:a:Implementation Requirements:', - 'implNote:a:Implementation Note:' - ) - } - } - } - - normalization { - runtimeClasspath { - metaInf { - [ - 'Build-Date', - 'Built-By', - 'Built-OS', - 'Build-Host', - 'Build-Job', - 'Build-Number', - 'Build-Id', - 'Change', - 'Full-Change', - 'Branch', - 'Module-Origin' - ].each { - ignoreAttribute it - ignoreProperty it - } - } - } - } - - //noinspection GroovyAssignabilityCheck - test { - // set heap size for the test JVM(s) - maxHeapSize = "1500m" - - useJUnitPlatform { - excludeTags 'docker' - } - - retry { - maxFailures = 5 - maxRetries = 3 - } - } - - project.tasks.withType(Test) { Test testTask -> - testTask.testLogging.exceptionFormat = 'full' - } - - license { - header rootProject.file('gradle/licenseHeader.txt') - strictCheck true - mapping { - kt = 'JAVADOC_STYLE' - } - sourceSets = project.sourceSets - - ext.year = Calendar.getInstance().get(Calendar.YEAR) - skipExistingHeaders = true - exclude '**/*.json' // comments not supported - } - - // Publish resolved versions. - plugins.withId('maven-publish') { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - - publishing { - publications { - nebula(MavenPublication) { - versionMapping { - allVariants { - fromResolutionResult() - } - } - - // We publish resolved versions so don't need to publish our dependencyManagement - // too. This is different from many Maven projects, where published artifacts often - // don't include resolved versions and have a parent POM including dependencyManagement. - pom.withXml { - def dependencyManagement = asNode().get('dependencyManagement') - if (dependencyManagement != null) { - asNode().remove(dependencyManagement) - } - } - } - } - } - } - } - - plugins.withId('maven-publish') { - publishing { - publications { - nebula(MavenPublication) { - // Nebula converts dynamic versions to static ones so it's ok. - suppressAllPomMetadataWarnings() - } - } - repositories { - maven { - name = 'Snapshot' - url = 'https://repo.spring.io/snapshot' - credentials { - username findProperty('SNAPSHOT_REPO_USER') - password findProperty('SNAPSHOT_REPO_PASSWORD') - } - } - maven { - name = 'Milestone' - url = 'https://repo.spring.io/milestone' - credentials { - username findProperty('MILESTONE_REPO_USER') - password findProperty('MILESTONE_REPO_PASSWORD') - } - } - } - } - - signing { - required = System.env.CIRCLE_STAGE == 'deploy' - useInMemoryPgpKeys(findProperty('SIGNING_KEY'), findProperty('SIGNING_PASSWORD')) - sign publishing.publications.nebula - } - - // Nebula doesn't interface with Gradle's module format so just disable it for now. - tasks.withType(GenerateModuleMetadata) { - enabled = false - } - } - - dependencyLocking { - lockAllConfigurations() - } - - tasks.register('resolveAndLockAll') { - description = 'Resolves dependencies of all configurations and writes them into the lock file.' - outputs.upToDateWhen { false } - doFirst { - assert gradle.startParameter.writeDependencyLocks || gradle.startParameter.lockedDependenciesToUpdate : 'Execute resolveAndLockAll --write-locks or --update-locks ' - } - doLast { - project.configurations.findAll { it.canBeResolved }*.resolve() - } - } - - tasks.register('downloadDependencies') { - outputs.upToDateWhen { false } - doLast { - project.configurations.findAll { it.canBeResolved }*.files - } - } - - if(!['samples', 'benchmarks'].find{project.name.contains(it)}) { - apply plugin: 'nebula.maven-publish' - apply plugin: 'nebula.maven-manifest' - apply plugin: 'nebula.maven-developer' - apply plugin: 'nebula.javadoc-jar' - apply plugin: 'nebula.source-jar' - apply plugin: 'nebula.maven-apache-license' - apply plugin: 'nebula.publish-verification' - apply plugin: 'nebula.contacts' - apply plugin: 'nebula.info' - apply plugin: 'nebula.project' - - if (project.name != 'micrometer-tracing-bom') { - jar { - manifest.attributes.put('Automatic-Module-Name', project.name.replace('-', '.')) - metaInf { - from "$rootDir/LICENSE" - from "$rootDir/NOTICE" - } - } - } - - contacts { - 'tludwig@vmware.com' { - moniker 'Tommy Ludwig' - github 'shakuzen' - } - 'jivanov@vmware.com' { - moniker 'Jonatan Ivanov' - github 'jonatan-ivanov' - } - 'mgrzejszczak@vmware.com' { - moniker 'Marcin Grzejszczak' - github 'marcingrzejszczak' - } - } - } - - description = 'Facade over tracing concepts' - - repositories { - mavenCentral() - mavenLocal() - } - - def check = tasks.findByName('check') - if (check) project.rootProject.tasks.releaseCheck.dependsOn check + apply plugin: 'signing' + + if (project.name != 'micrometer-tracing-bom') { + apply plugin: 'java-library' + apply plugin: 'com.github.hierynomus.license' + apply plugin: 'checkstyle' + apply plugin: 'io.spring.nohttp' + apply plugin: 'org.gradle.test-retry' + + java { + // It is more idiomatic to define different features for different sets of optional + // dependencies, e.g., 'dropwizard' and 'reactor'. If this library published Gradle + // metadata, Gradle users would be able to use these feature names in their dependency + // declarations instead of understanding the actual required optional dependencies. + // But we don't publish Gradle metadata yet and this may be overkill so just have a + // single feature for now to correspond to any optional dependency. + registerFeature('optional') { + usingSourceSet(sourceSets.main) + } + } + + // All projects use optional annotations, but since we don't expose them downstream we would + // have to add the dependency in every project, which is tedious so just do it here. + dependencies { + // JSR-305 only used for non-required meta-annotations + optionalApi "com.google.code.findbugs:jsr305:latest.release" + checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:latest.release") + } + + tasks { + compileJava { + options.encoding = 'UTF-8' + options.compilerArgs << '-Xlint:unchecked' << '-Xlint:deprecation' + + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + + // ensure Java 8 baseline is enforced for main source + if (JavaVersion.current().isJava9Compatible()) { + options.release = 8 + } + } + compileTestJava { + options.encoding = 'UTF-8' + options.compilerArgs << '-Xlint:unchecked' << '-Xlint:deprecation' + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + javadoc { + configure(options) { + tags( + 'apiNote:a:API Note:', + 'implSpec:a:Implementation Requirements:', + 'implNote:a:Implementation Note:' + ) + } + } + } + + normalization { + runtimeClasspath { + metaInf { + [ + 'Build-Date', + 'Built-By', + 'Built-OS', + 'Build-Host', + 'Build-Job', + 'Build-Number', + 'Build-Id', + 'Change', + 'Full-Change', + 'Branch', + 'Module-Origin' + ].each { + ignoreAttribute it + ignoreProperty it + } + } + } + } + + //noinspection GroovyAssignabilityCheck + test { + // set heap size for the test JVM(s) + maxHeapSize = "1500m" + + useJUnitPlatform { + excludeTags 'docker' + } + + retry { + maxFailures = 5 + maxRetries = 3 + } + } + + project.tasks.withType(Test) { Test testTask -> + testTask.testLogging.exceptionFormat = 'full' + } + + license { + header rootProject.file('gradle/licenseHeader.txt') + strictCheck true + mapping { + kt = 'JAVADOC_STYLE' + } + sourceSets = project.sourceSets + + ext.year = Calendar.getInstance().get(Calendar.YEAR) + skipExistingHeaders = true + exclude '**/*.json' // comments not supported + } + + // Publish resolved versions. + plugins.withId('maven-publish') { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + + publishing { + publications { + nebula(MavenPublication) { + versionMapping { + allVariants { + fromResolutionResult() + } + } + + // We publish resolved versions so don't need to publish our dependencyManagement + // too. This is different from many Maven projects, where published artifacts often + // don't include resolved versions and have a parent POM including dependencyManagement. + pom.withXml { + def dependencyManagement = asNode().get('dependencyManagement') + if (dependencyManagement != null) { + asNode().remove(dependencyManagement) + } + } + } + } + } + } + } + + plugins.withId('maven-publish') { + publishing { + publications { + nebula(MavenPublication) { + // Nebula converts dynamic versions to static ones so it's ok. + suppressAllPomMetadataWarnings() + } + } + repositories { + maven { + name = 'Snapshot' + url = 'https://repo.spring.io/snapshot' + credentials { + username findProperty('SNAPSHOT_REPO_USER') + password findProperty('SNAPSHOT_REPO_PASSWORD') + } + } + maven { + name = 'Milestone' + url = 'https://repo.spring.io/milestone' + credentials { + username findProperty('MILESTONE_REPO_USER') + password findProperty('MILESTONE_REPO_PASSWORD') + } + } + } + } + + signing { + required = System.env.CIRCLE_STAGE == 'deploy' + useInMemoryPgpKeys(findProperty('SIGNING_KEY'), findProperty('SIGNING_PASSWORD')) + sign publishing.publications.nebula + } + + // Nebula doesn't interface with Gradle's module format so just disable it for now. + tasks.withType(GenerateModuleMetadata) { + enabled = false + } + } + + dependencyLocking { + lockAllConfigurations() + } + + tasks.register('resolveAndLockAll') { + description = 'Resolves dependencies of all configurations and writes them into the lock file.' + outputs.upToDateWhen { false } + doFirst { + assert gradle.startParameter.writeDependencyLocks || gradle.startParameter.lockedDependenciesToUpdate: 'Execute resolveAndLockAll --write-locks or --update-locks ' + } + doLast { + project.configurations.findAll { it.canBeResolved }*.resolve() + } + } + + tasks.register('downloadDependencies') { + outputs.upToDateWhen { false } + doLast { + project.configurations.findAll { it.canBeResolved }*.files + } + } + + if (!['samples', 'benchmarks'].find { project.name.contains(it) }) { + apply plugin: 'nebula.maven-publish' + apply plugin: 'nebula.maven-manifest' + apply plugin: 'nebula.maven-developer' + apply plugin: 'nebula.javadoc-jar' + apply plugin: 'nebula.source-jar' + apply plugin: 'nebula.maven-apache-license' + apply plugin: 'nebula.publish-verification' + apply plugin: 'nebula.contacts' + apply plugin: 'nebula.info' + apply plugin: 'nebula.project' + + if (project.name != 'micrometer-tracing-bom') { + jar { + manifest.attributes.put('Automatic-Module-Name', project.name.replace('-', '.')) + metaInf { + from "$rootDir/LICENSE" + from "$rootDir/NOTICE" + } + } + } + + contacts { + 'tludwig@vmware.com' { + moniker 'Tommy Ludwig' + github 'shakuzen' + } + 'jivanov@vmware.com' { + moniker 'Jonatan Ivanov' + github 'jonatan-ivanov' + } + 'mgrzejszczak@vmware.com' { + moniker 'Marcin Grzejszczak' + github 'marcingrzejszczak' + } + } + } + + description = 'Facade over tracing concepts' + + repositories { + mavenCentral() + if (((String) project.version).matches(".*-(M|RC)[0-9][0-9]?")) { + maven { url "https://repo.spring.io/milestone" } + } + if (((String) project.version).endsWith("SNAPSHOT")) { + maven { url "https://repo.spring.io/snapshot" } + } + mavenLocal() + } + + def check = tasks.findByName('check') + if (check) project.rootProject.tasks.releaseCheck.dependsOn check } nexusPublishing { - repositories { - mavenCentral { - nexusUrl.set(uri('https://s01.oss.sonatype.org/service/local/')) - snapshotRepositoryUrl.set(uri('https://repo.spring.io/snapshot/')) // not used but necessary for the plugin - username = findProperty('MAVEN_CENTRAL_USER') - password = findProperty('MAVEN_CENTRAL_PASSWORD') - } - } + repositories { + mavenCentral { + nexusUrl.set(uri('https://s01.oss.sonatype.org/service/local/')) + snapshotRepositoryUrl.set(uri('https://repo.spring.io/snapshot/')) // not used but necessary for the plugin + username = findProperty('MAVEN_CENTRAL_USER') + password = findProperty('MAVEN_CENTRAL_PASSWORD') + } + } } wrapper { - gradleVersion = '7.2' + gradleVersion = '7.3' } defaultTasks 'build' diff --git a/config/checkstyle/checkstyle-suppressions.xml b/config/checkstyle/checkstyle-suppressions.xml index 23e614d1..9070bcb1 100755 --- a/config/checkstyle/checkstyle-suppressions.xml +++ b/config/checkstyle/checkstyle-suppressions.xml @@ -3,10 +3,5 @@ "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN" "https://checkstyle.org/dtds/suppressions_1_2.dtd"> - - - - - - + diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 2815e2f0..ef6bc457 100755 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -1,61 +1,69 @@ + "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" + "https://checkstyle.org/dtds/configuration_1_3.dtd"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml index dbbba21e..3a16a594 100755 --- a/config/checkstyle/suppressions.xml +++ b/config/checkstyle/suppressions.xml @@ -1,8 +1,9 @@ - + - - - - + + + + diff --git a/dependencies.gradle b/dependencies.gradle index fc03f558..9c2023e1 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,29 +1,45 @@ def VERSIONS = [ - 'ch.qos.logback:logback-classic:1.2.+', - 'org.apache.logging.log4j:log4j-core:2.+', - 'org.assertj:assertj-core:latest.release', - 'org.awaitility:awaitility:latest.release', - 'org.junit.jupiter:junit-jupiter:5.8.+', - 'org.junit.platform:junit-platform-launcher:1.8.+', - 'org.junit.vintage:junit-vintage-engine:5.8.+', - 'org.mockito:mockito-core:latest.release', - 'org.mockito:mockito-inline:latest.release', - 'org.slf4j:slf4j-api:1.7.+' + 'javax.servlet:javax.servlet-api:4.0.1', + 'aopalliance:aopalliance:1.0', + // logging + 'ch.qos.logback:logback-classic:1.2.+', + 'org.apache.logging.log4j:log4j-core:2.+', + 'org.slf4j:slf4j-api:1.7.+', + // otel + 'io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:1.7.0-alpha', + // zipkin + 'io.zipkin.aws:brave-propagation-aws:0.23.2', + // wavefront + 'com.wavefront:wavefront-sdk-java:2.6.4', + 'com.wavefront:wavefront-internal-reporter-java:1.7.5', + // test + 'org.assertj:assertj-core:latest.release', + 'org.awaitility:awaitility:latest.release', + 'org.junit.jupiter:junit-jupiter:5.8.+', + 'org.junit.platform:junit-platform-launcher:1.8.+', + 'org.junit.vintage:junit-vintage-engine:5.8.+', + 'org.mockito:mockito-core:latest.release', + 'org.mockito:mockito-inline:latest.release', + 'com.squareup.okhttp3:mockwebserver:4.8.0' ] subprojects { - plugins.withId('java-library') { - dependencies { - constraints { - // Direct dependencies - VERSIONS.each {version-> - // java-library plugin has three root configurations, so we apply constraints too all of - // them so they all can use the managed versions. - api version - compileOnly version - runtimeOnly version - } - } - } - } + plugins.withId('java-library') { + dependencies { + constraints { + // Direct dependencies + VERSIONS.each { version -> + // java-library plugin has three root configurations, so we apply constraints too all of + // them so they all can use the managed versions. + api version + compileOnly version + runtimeOnly version + } + } + implementation platform('io.micrometer:micrometer-bom:2.0.0-SNAPSHOT') + implementation platform('io.zipkin.brave:brave-bom:5.13.2') + implementation platform('io.opentelemetry:opentelemetry-bom:1.7.0') + implementation platform('io.opentelemetry:opentelemetry-bom-alpha:1.7.0-alpha') + } + } } diff --git a/gradle.properties b/gradle.properties index 0cc84d7a..ef7bfebf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,6 @@ org.gradle.caching=true org.gradle.parallel=true org.gradle.vfs.watch=true - nebula.dependencyLockPluginEnabled=false # TODO remove before merging release.version=1.0.0-SNAPSHOT diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ffed3a25..e750102e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/micrometer-tracing-bom/build.gradle b/micrometer-tracing-bom/build.gradle index 36ab2538..16f5d1a4 100644 --- a/micrometer-tracing-bom/build.gradle +++ b/micrometer-tracing-bom/build.gradle @@ -1,25 +1,25 @@ plugins { - id 'java-platform' + id 'java-platform' } description 'Micrometer Tracing BOM (Bill of Materials) for managing Micrometer Tracing artifact versions' dependencies { - constraints { - rootProject.subprojects.findAll { - !it.name.contains('micrometer-tracing-bom') - }.each { - api(group: it.group, - name: it.name, - version: it.version.toString()) - } - } + constraints { + rootProject.subprojects.findAll { + !it.name.contains('micrometer-tracing-bom') + }.each { + api(group: it.group, + name: it.name, + version: it.version.toString()) + } + } } publishing { - publications { - nebula(MavenPublication) { - from components.javaPlatform - } - } + publications { + nebula(MavenPublication) { + from components.javaPlatform + } + } } diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/build.gradle b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/build.gradle new file mode 100644 index 00000000..4f8ad4e7 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'idea' +} + +dependencies { + api project(':micrometer-tracing') + api 'org.slf4j:slf4j-api' + api 'javax.servlet:javax.servlet-api' + + implementation("io.zipkin.brave:brave") { + exclude group: "io.zipkin.reporter2" + exclude group: "io.zipkin.zipkin2" + } + implementation("io.zipkin.brave:brave-context-slf4j") + implementation("io.zipkin.brave:brave-instrumentation-http") + implementation("io.zipkin.aws:brave-propagation-aws") + + testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation 'org.assertj:assertj-core' + testImplementation 'io.zipkin.brave:brave-instrumentation-http-tests' +} + diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveBaggageInScope.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveBaggageInScope.java new file mode 100755 index 00000000..1921c290 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveBaggageInScope.java @@ -0,0 +1,78 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import brave.baggage.BaggageField; +import io.micrometer.tracing.BaggageInScope; +import io.micrometer.tracing.TraceContext; + +/** + * Brave implementation of a {@link BaggageInScope}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +class BraveBaggageInScope implements BaggageInScope { + + private final BaggageField delegate; + + BraveBaggageInScope(BaggageField delegate) { + this.delegate = delegate; + } + + @Override + public String name() { + return this.delegate.name(); + } + + @Override + public String get() { + return this.delegate.getValue(); + } + + @Override + public String get(TraceContext traceContext) { + return this.delegate.getValue(BraveTraceContext.toBrave(traceContext)); + } + + @Override + public BraveBaggageInScope set(String value) { + this.delegate.updateValue(value); + return this; + } + + BaggageField unwrap() { + return this.delegate; + } + + @Override + public BraveBaggageInScope set(TraceContext traceContext, String value) { + this.delegate.updateValue(BraveTraceContext.toBrave(traceContext), value); + return this; + } + + @Override + public BaggageInScope makeCurrent() { + return this; + } + + @Override + public void close() { + + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveBaggageManager.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveBaggageManager.java new file mode 100755 index 00000000..c5d72b10 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveBaggageManager.java @@ -0,0 +1,71 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.io.Closeable; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import brave.baggage.BaggageField; +import io.micrometer.tracing.BaggageInScope; +import io.micrometer.tracing.BaggageManager; +import io.micrometer.tracing.TraceContext; + +/** + * Brave implementation of a {@link BaggageManager}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class BraveBaggageManager implements Closeable, BaggageManager { + + private static final Map CACHE = new ConcurrentHashMap<>(); + + public Map getAllBaggage() { + return BaggageField.getAllValues(); + } + + @Override + public BaggageInScope getBaggage(String name) { + return createBaggage(name); + } + + @Override + public BaggageInScope getBaggage(TraceContext traceContext, String name) { + BaggageField baggageField = BaggageField.getByName(BraveTraceContext.toBrave(traceContext), name); + if (baggageField == null) { + return null; + } + return new BraveBaggageInScope(baggageField); + } + + @Override + public BaggageInScope createBaggage(String name) { + return CACHE.computeIfAbsent(name, s -> new BraveBaggageInScope(BaggageField.create(s))); + } + + @Override + public BaggageInScope createBaggage(String name, String value) { + return createBaggage(name).set(value); + } + + @Override + public void close() { + CACHE.clear(); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveContextWrappingFunction.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveContextWrappingFunction.java new file mode 100644 index 00000000..f7c51e70 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveContextWrappingFunction.java @@ -0,0 +1,62 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +//import java.util.function.Function; +// +//import reactor.util.context.Context; +// +//import io.micrometer.tracing.Span; +//import io.micrometer.tracing.TraceContext; + +/** + * A function that wraps {@code Context} with Brave versions. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class BraveContextWrappingFunction { + + // implements Function { + + /*@Override + public Context apply(Context context) { + Span span = context.getOrDefault(Span.class, null); + TraceContext traceContext = context.getOrDefault(TraceContext.class, null); + if (span == null && traceContext == null) { + return context; + } + if (context.hasKey(brave.propagation.TraceContext.class)) { + return context; + } + return mutateContextWithBrave(context, span, traceContext); + } + + private Context mutateContextWithBrave(Context context, Span span, TraceContext traceContext) { + brave.Span braveSpan = BraveSpan.toBrave(span); + brave.propagation.TraceContext braveTraceContext = BraveTraceContext.toBrave(traceContext); + Context mutatedContext = context; + if (braveSpan != null) { + mutatedContext = context.put(brave.Span.class, braveSpan); + } + if (braveTraceContext != null) { + mutatedContext = mutatedContext.put(brave.propagation.TraceContext.class, braveTraceContext); + } + return mutatedContext; + }*/ + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveCurrentTraceContext.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveCurrentTraceContext.java new file mode 100755 index 00000000..755ff705 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveCurrentTraceContext.java @@ -0,0 +1,115 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; + +import io.micrometer.tracing.CurrentTraceContext; +import io.micrometer.tracing.TraceContext; + +/** + * Brave implementation of a {@link CurrentTraceContext}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class BraveCurrentTraceContext implements CurrentTraceContext { + + final brave.propagation.CurrentTraceContext delegate; + + /** + * @param delegate Brave delegate + */ + public BraveCurrentTraceContext(brave.propagation.CurrentTraceContext delegate) { + this.delegate = delegate; + } + + /** + * Converts from Spring Observability to Brave. + * @param context Spring Observability delegate + * @return converted version + */ + public static brave.propagation.CurrentTraceContext toBrave(CurrentTraceContext context) { + return ((BraveCurrentTraceContext) context).delegate; + } + + /** + * Converts from Brave to Spring Observability. + * @param context Brave delegate + * @return converted version + */ + public static CurrentTraceContext fromBrave(brave.propagation.CurrentTraceContext context) { + return new BraveCurrentTraceContext(context); + } + + @Override + public TraceContext context() { + brave.propagation.TraceContext context = this.delegate.get(); + if (context == null) { + return null; + } + return new io.micrometer.tracing.brave.bridge.BraveTraceContext(context); + } + + @Override + public Scope newScope(TraceContext context) { + return new BraveScope(this.delegate.newScope(io.micrometer.tracing.brave.bridge.BraveTraceContext.toBrave(context))); + } + + @Override + public Scope maybeScope(TraceContext context) { + return new BraveScope(this.delegate.maybeScope(io.micrometer.tracing.brave.bridge.BraveTraceContext.toBrave(context))); + } + + @Override + public Callable wrap(Callable task) { + return this.delegate.wrap(task); + } + + @Override + public Runnable wrap(Runnable task) { + return this.delegate.wrap(task); + } + + @Override + public Executor wrap(Executor delegate) { + return this.delegate.executor(delegate); + } + + @Override + public ExecutorService wrap(ExecutorService delegate) { + return this.delegate.executorService(delegate); + } + +} + +class BraveScope implements CurrentTraceContext.Scope { + + private final brave.propagation.CurrentTraceContext.Scope delegate; + + BraveScope(brave.propagation.CurrentTraceContext.Scope delegate) { + this.delegate = delegate; + } + + @Override + public void close() { + this.delegate.close(); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveFinishedSpan.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveFinishedSpan.java new file mode 100755 index 00000000..6c28e691 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveFinishedSpan.java @@ -0,0 +1,126 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.Collection; +import java.util.Map; + +import brave.handler.MutableSpan; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.exporter.FinishedSpan; + +/** + * Brave implementation of a {@link FinishedSpan}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class BraveFinishedSpan implements FinishedSpan { + + private final MutableSpan mutableSpan; + + public BraveFinishedSpan(MutableSpan mutableSpan) { + this.mutableSpan = mutableSpan; + } + + public static FinishedSpan fromBrave(MutableSpan mutableSpan) { + return new BraveFinishedSpan(mutableSpan); + } + + public static MutableSpan toBrave(FinishedSpan mutableSpan) { + return ((BraveFinishedSpan) mutableSpan).mutableSpan; + } + + @Override + public String getName() { + return this.mutableSpan.name(); + } + + @Override + public long getStartTimestamp() { + return this.mutableSpan.startTimestamp(); + } + + @Override + public long getEndTimestamp() { + return this.mutableSpan.finishTimestamp(); + } + + @Override + public Map getTags() { + return this.mutableSpan.tags(); + } + + @Override + public Collection> getEvents() { + return this.mutableSpan.annotations(); + } + + @Override + public String getSpanId() { + return this.mutableSpan.id(); + } + + @Override + public String getParentId() { + return this.mutableSpan.parentId(); + } + + @Override + public String getRemoteIp() { + return this.mutableSpan.remoteIp(); + } + + @Override + public String getLocalIp() { + return this.mutableSpan.localIp(); + } + + @Override + public int getRemotePort() { + return this.mutableSpan.remotePort(); + } + + @Override + public String getTraceId() { + return this.mutableSpan.traceId(); + } + + @Override + public Throwable getError() { + return this.mutableSpan.error(); + } + + @Override + public Span.Kind getKind() { + if (this.mutableSpan.kind() == null) { + return null; + } + return Span.Kind.valueOf(this.mutableSpan.kind().name()); + } + + @Override + public String getRemoteServiceName() { + return this.mutableSpan.remoteServiceName(); + } + + @Override + public String toString() { + return "BraveFinishedSpan{" + "mutableSpan=" + mutableSpan + '}'; + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpClientHandler.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpClientHandler.java new file mode 100755 index 00000000..801a3ff4 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpClientHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import io.micrometer.core.instrument.transport.http.HttpClientRequest; +import io.micrometer.core.instrument.transport.http.HttpClientResponse; +import io.micrometer.core.util.internal.logging.InternalLogger; +import io.micrometer.core.util.internal.logging.InternalLoggerFactory; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.http.HttpClientHandler; + +/** + * Brave implementation of a {@link HttpClientHandler}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class BraveHttpClientHandler implements HttpClientHandler { + + private static final InternalLogger log = InternalLoggerFactory.getInstance(BraveHttpClientHandler.class); + + final brave.http.HttpClientHandler delegate; + + public BraveHttpClientHandler( + brave.http.HttpClientHandler delegate) { + this.delegate = delegate; + } + + @Override + public Span handleSend(HttpClientRequest request) { + return BraveSpan.fromBrave(this.delegate.handleSend(BraveHttpClientRequest.toBrave(request))); + } + + @Override + public Span handleSend(HttpClientRequest request, TraceContext parent) { + brave.Span span = this.delegate.handleSendWithParent(BraveHttpClientRequest.toBrave(request), + BraveTraceContext.toBrave(parent)); + if (!span.isNoop()) { + span.remoteIpAndPort(request.remoteIp(), request.remotePort()); + } + return BraveSpan.fromBrave(span); + } + + @Override + public void handleReceive(HttpClientResponse response, Span span) { + if (response == null) { + if (log.isDebugEnabled()) { + log.debug("Response is null, will not handle receiving of span [" + span + "]"); + } + return; + } + this.delegate.handleReceive(BraveHttpClientResponse.toBrave(response), BraveSpan.toBrave(span)); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpClientRequest.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpClientRequest.java new file mode 100755 index 00000000..a5da4e6f --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpClientRequest.java @@ -0,0 +1,117 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.Collection; +import java.util.Collections; + +import io.micrometer.core.instrument.transport.http.HttpClientRequest; + +/** + * Brave implementation of a {@link HttpClientRequest}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +class BraveHttpClientRequest implements HttpClientRequest { + + final brave.http.HttpClientRequest delegate; + + BraveHttpClientRequest(brave.http.HttpClientRequest delegate) { + this.delegate = delegate; + } + + static brave.http.HttpClientRequest toBrave(HttpClientRequest httpClientRequest) { + if (httpClientRequest instanceof BraveHttpClientRequest) { + return ((BraveHttpClientRequest) httpClientRequest).delegate; + } + return new brave.http.HttpClientRequest() { + + @Override + public Object unwrap() { + return httpClientRequest.unwrap(); + } + + @Override + public String method() { + return httpClientRequest.method(); + } + + @Override + public String path() { + return httpClientRequest.path(); + } + + @Override + public String url() { + return httpClientRequest.url(); + } + + @Override + public String header(String name) { + return httpClientRequest.header(name); + } + + @Override + public void header(String name, String value) { + httpClientRequest.header(name, value); + } + }; + } + + @Override + public String method() { + return this.delegate.method(); + } + + @Override + public String route() { + return this.delegate.route(); + } + + @Override + public Object unwrap() { + return this.delegate.unwrap(); + } + + @Override + public Collection headerNames() { + // this is unused by Brave + return Collections.emptyList(); + } + + @Override + public void header(String name, String value) { + this.delegate.header(name, value); + } + + @Override + public String path() { + return this.delegate.path(); + } + + @Override + public String url() { + return this.delegate.url(); + } + + @Override + public String header(String name) { + return this.delegate.header(name); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpClientResponse.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpClientResponse.java new file mode 100755 index 00000000..7bfdc2f9 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpClientResponse.java @@ -0,0 +1,119 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.Collection; +import java.util.Collections; + +import io.micrometer.core.instrument.transport.http.HttpClientRequest; +import io.micrometer.core.instrument.transport.http.HttpClientResponse; + +/** + * Brave implementation of a {@link HttpClientResponse}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +class BraveHttpClientResponse implements HttpClientResponse { + + final brave.http.HttpClientResponse delegate; + + BraveHttpClientResponse(brave.http.HttpClientResponse delegate) { + this.delegate = delegate; + } + + static brave.http.HttpClientResponse toBrave(HttpClientResponse httpClientResponse) { + if (httpClientResponse == null) { + return null; + } + else if (httpClientResponse instanceof BraveHttpClientResponse) { + return ((BraveHttpClientResponse) httpClientResponse).delegate; + } + return new brave.http.HttpClientResponse() { + @Override + public int statusCode() { + return httpClientResponse.statusCode(); + } + + @Override + public Object unwrap() { + return httpClientResponse.unwrap(); + } + + @Override + public brave.http.HttpClientRequest request() { + return BraveHttpClientRequest.toBrave(httpClientResponse.request()); + } + + @Override + public Throwable error() { + return httpClientResponse.error(); + } + + @Override + public String method() { + return httpClientResponse.method(); + } + + @Override + public String route() { + return httpClientResponse.route(); + } + }; + } + + @Override + public String method() { + return this.delegate.method(); + } + + @Override + public String route() { + return this.delegate.route(); + } + + @Override + public int statusCode() { + return this.delegate.statusCode(); + } + + @Override + public Object unwrap() { + return this.delegate.unwrap(); + } + + @Override + public Collection headerNames() { + // this is unused by Brave + return Collections.emptyList(); + } + + @Override + public HttpClientRequest request() { + brave.http.HttpClientRequest request = this.delegate.request(); + if (request == null) { + return null; + } + return new BraveHttpClientRequest(request); + } + + @Override + public Throwable error() { + return this.delegate.error(); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpRequest.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpRequest.java new file mode 100755 index 00000000..b6e5243d --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpRequest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.Collection; +import java.util.Collections; + +import io.micrometer.core.instrument.transport.Kind; +import io.micrometer.core.instrument.transport.http.HttpRequest; + +/** + * Brave implementation of a {@link HttpRequest}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +class BraveHttpRequest implements HttpRequest { + + final brave.http.HttpRequest delegate; + + BraveHttpRequest(brave.http.HttpRequest delegate) { + this.delegate = delegate; + } + + static brave.http.HttpRequest toBrave(HttpRequest httpRequest) { + return ((BraveHttpRequest) httpRequest).delegate; + } + + static HttpRequest fromBrave(brave.http.HttpRequest httpRequest) { + return new BraveHttpRequest(httpRequest); + } + + @Override + public String method() { + return this.delegate.method(); + } + + @Override + public String path() { + return this.delegate.path(); + } + + @Override + public String url() { + return this.delegate.url(); + } + + @Override + public String header(String name) { + return this.delegate.header(name); + } + + @Override + public Collection headerNames() { + // this is unused by Brave + return Collections.emptyList(); + } + + @Override + public Kind kind() { + return Kind.valueOf(this.delegate.spanKind().name()); + } + + @Override + public Object unwrap() { + return this.delegate.unwrap(); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpRequestParser.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpRequestParser.java new file mode 100755 index 00000000..06ba0a0c --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpRequestParser.java @@ -0,0 +1,56 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import io.micrometer.core.instrument.transport.http.HttpRequest; +import io.micrometer.tracing.SpanCustomizer; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.http.HttpRequestParser; + +/** + * Brave implementation of a {@link HttpRequestParser}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class BraveHttpRequestParser implements HttpRequestParser { + + final brave.http.HttpRequestParser delegate; + + public BraveHttpRequestParser(brave.http.HttpRequestParser delegate) { + this.delegate = delegate; + } + + /** + * @param parser Sleuth's API parser + * @return Brave version of the parser + */ + public static brave.http.HttpRequestParser toBrave(HttpRequestParser parser) { + if (parser instanceof BraveHttpRequestParser) { + return ((BraveHttpRequestParser) parser).delegate; + } + return (request, context, span) -> parser.parse(BraveHttpRequest.fromBrave(request), + BraveTraceContext.fromBrave(context), BraveSpanCustomizer.fromBrave(span)); + } + + @Override + public void parse(HttpRequest request, TraceContext context, SpanCustomizer span) { + this.delegate.parse(BraveHttpRequest.toBrave(request), BraveTraceContext.toBrave(context), + BraveSpanCustomizer.toBrave(span)); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpResponse.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpResponse.java new file mode 100755 index 00000000..0631ae3c --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpResponse.java @@ -0,0 +1,93 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.Collection; +import java.util.Collections; + +import io.micrometer.core.instrument.transport.Kind; +import io.micrometer.core.instrument.transport.http.HttpRequest; +import io.micrometer.core.instrument.transport.http.HttpResponse; + +/** + * Brave implementation of a {@link HttpResponse}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +class BraveHttpResponse implements HttpResponse { + + final brave.http.HttpResponse delegate; + + BraveHttpResponse(brave.http.HttpResponse delegate) { + this.delegate = delegate; + } + + static brave.http.HttpResponse toBrave(HttpResponse httpResponse) { + return ((BraveHttpResponse) httpResponse).delegate; + } + + static HttpResponse fromBrave(brave.http.HttpResponse httpResponse) { + return new BraveHttpResponse(httpResponse); + } + + @Override + public String method() { + return this.delegate.method(); + } + + @Override + public String route() { + return this.delegate.route(); + } + + @Override + public int statusCode() { + return this.delegate.statusCode(); + } + + @Override + public Object unwrap() { + return this.delegate.unwrap(); + } + + @Override + public Collection headerNames() { + // this is unused by Brave + return Collections.emptyList(); + } + + @Override + public Kind kind() { + return Kind.valueOf(this.delegate.spanKind().name()); + } + + @Override + public HttpRequest request() { + brave.http.HttpRequest request = this.delegate.request(); + if (request == null) { + return null; + } + return new BraveHttpRequest(request); + } + + @Override + public Throwable error() { + return this.delegate.error(); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpResponseParser.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpResponseParser.java new file mode 100755 index 00000000..f525b3c5 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpResponseParser.java @@ -0,0 +1,56 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import io.micrometer.core.instrument.transport.http.HttpResponse; +import io.micrometer.tracing.SpanCustomizer; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.http.HttpResponseParser; + +/** + * Brave implementation of a {@link HttpResponseParser}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class BraveHttpResponseParser implements HttpResponseParser { + + final brave.http.HttpResponseParser delegate; + + public BraveHttpResponseParser(brave.http.HttpResponseParser delegate) { + this.delegate = delegate; + } + + /** + * @param parser Sleuth's API parser + * @return Brave's parser + */ + public static brave.http.HttpResponseParser toBrave(HttpResponseParser parser) { + if (parser instanceof BraveHttpResponseParser) { + return ((BraveHttpResponseParser) parser).delegate; + } + return (response, context, span) -> parser.parse(BraveHttpResponse.fromBrave(response), + BraveTraceContext.fromBrave(context), BraveSpanCustomizer.fromBrave(span)); + } + + @Override + public void parse(HttpResponse response, TraceContext context, SpanCustomizer span) { + this.delegate.parse(BraveHttpResponse.toBrave(response), BraveTraceContext.toBrave(context), + BraveSpanCustomizer.toBrave(span)); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpServerHandler.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpServerHandler.java new file mode 100755 index 00000000..9d59f568 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpServerHandler.java @@ -0,0 +1,49 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import io.micrometer.core.instrument.transport.http.HttpServerRequest; +import io.micrometer.core.instrument.transport.http.HttpServerResponse; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.http.HttpServerHandler; + +/** + * Brave implementation of a {@link HttpServerHandler}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class BraveHttpServerHandler implements HttpServerHandler { + + final brave.http.HttpServerHandler delegate; + + public BraveHttpServerHandler( + brave.http.HttpServerHandler delegate) { + this.delegate = delegate; + } + + @Override + public Span handleReceive(HttpServerRequest request) { + return BraveSpan.fromBrave(this.delegate.handleReceive(BraveHttpServerRequest.toBrave(request))); + } + + @Override + public void handleSend(HttpServerResponse response, Span span) { + this.delegate.handleSend(BraveHttpServerResponse.toBrave(response), BraveSpan.toBrave(span)); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpServerRequest.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpServerRequest.java new file mode 100755 index 00000000..ba443456 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpServerRequest.java @@ -0,0 +1,149 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.Collection; +import java.util.Collections; + +import javax.servlet.ServletRequest; + +import io.micrometer.core.instrument.transport.Kind; +import io.micrometer.core.instrument.transport.http.HttpServerRequest; + + +/** + * Brave implementation of a {@link HttpServerRequest}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +class BraveHttpServerRequest implements HttpServerRequest { + + final brave.http.HttpServerRequest delegate; + + BraveHttpServerRequest(brave.http.HttpServerRequest delegate) { + this.delegate = delegate; + } + + static brave.http.HttpServerRequest toBrave(HttpServerRequest request) { + if (request == null) { + return null; + } + if (request instanceof BraveHttpServerRequest) { + return ((BraveHttpServerRequest) request).delegate; + } + return new brave.http.HttpServerRequest() { + + @Override + public Object unwrap() { + return request.unwrap(); + } + + @Override + public String method() { + return request.method(); + } + + @Override + public String path() { + return request.path(); + } + + @Override + public String url() { + return request.url(); + } + + @Override + public String header(String name) { + return request.header(name); + } + + @Override + public boolean parseClientIpAndPort(brave.Span span) { + boolean clientIpAndPortParsed = super.parseClientIpAndPort(span); + if (clientIpAndPortParsed) { + return true; + } + return resolveFromInetAddress(span); + } + + private boolean resolveFromInetAddress(brave.Span span) { + Object delegate = request.unwrap(); +// if (delegate instanceof ServerHttpRequest) { +// InetSocketAddress addr = ((ServerHttpRequest) delegate).getRemoteAddress(); +// if (addr == null) { +// return false; +// } +// return span.remoteIpAndPort(addr.getAddress().getHostAddress(), addr.getPort()); +// } + if (delegate instanceof ServletRequest) { + ServletRequest servletRequest = (ServletRequest) delegate; + String addr = servletRequest.getRemoteAddr(); + if (addr == null) { + return false; + } + return span.remoteIpAndPort(addr, servletRequest.getRemotePort()); + } + return false; + } + + }; + } + + @Override + public String method() { + return this.delegate.method(); + } + + @Override + public String route() { + return this.delegate.route(); + } + + @Override + public Object unwrap() { + return this.delegate.unwrap(); + } + + @Override + public Collection headerNames() { + // this is unused by Brave + return Collections.emptyList(); + } + + @Override + public Kind kind() { + return Kind.valueOf(this.delegate.spanKind().name()); + } + + @Override + public String path() { + return this.delegate.path(); + } + + @Override + public String url() { + return this.delegate.url(); + } + + @Override + public String header(String name) { + return this.delegate.header(name); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpServerResponse.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpServerResponse.java new file mode 100755 index 00000000..c0af8709 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveHttpServerResponse.java @@ -0,0 +1,130 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.Collection; +import java.util.Collections; + +import io.micrometer.core.instrument.transport.Kind; +import io.micrometer.core.instrument.transport.http.HttpServerRequest; +import io.micrometer.core.instrument.transport.http.HttpServerResponse; + +/** + * Brave implementation of a {@link HttpServerResponse}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +class BraveHttpServerResponse implements HttpServerResponse { + + final brave.http.HttpServerResponse delegate; + + BraveHttpServerResponse(brave.http.HttpServerResponse delegate) { + this.delegate = delegate; + } + + static brave.http.HttpServerResponse toBrave(HttpServerResponse response) { + if (response == null) { + return null; + } + else if (response instanceof BraveHttpServerResponse) { + return ((BraveHttpServerResponse) response).delegate; + } + return new brave.http.HttpServerResponse() { + @Override + public brave.http.HttpServerRequest request() { + return BraveHttpServerRequest.toBrave(response.request()); + } + + @Override + public Throwable error() { + return response.error(); + } + + @Override + public String method() { + return response.method(); + } + + @Override + public String route() { + return response.route(); + } + + @Override + public String toString() { + return response.toString(); + } + + @Override + public int statusCode() { + return response.statusCode(); + } + + @Override + public Object unwrap() { + return response.unwrap(); + } + }; + } + + @Override + public String method() { + return this.delegate.method(); + } + + @Override + public String route() { + return this.delegate.route(); + } + + @Override + public int statusCode() { + return this.delegate.statusCode(); + } + + @Override + public Object unwrap() { + return this.delegate.unwrap(); + } + + @Override + public Collection headerNames() { + // this is unused by Brave + return Collections.emptyList(); + } + + @Override + public Kind kind() { + return Kind.valueOf(this.delegate.spanKind().name()); + } + + @Override + public HttpServerRequest request() { + brave.http.HttpServerRequest request = this.delegate.request(); + if (request == null) { + return null; + } + return new BraveHttpServerRequest(request); + } + + @Override + public Throwable error() { + return this.delegate.error(); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BravePropagator.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BravePropagator.java new file mode 100755 index 00000000..c0f81c2d --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BravePropagator.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.List; + +import brave.Tracing; +import brave.propagation.SamplingFlags; +import brave.propagation.TraceContextOrSamplingFlags; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.propagation.Propagator; + +/** + * Brave implementation of a {@link Propagator}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class BravePropagator implements Propagator { + + private final Tracing tracing; + + public BravePropagator(Tracing tracing) { + this.tracing = tracing; + } + + @Override + public List fields() { + return this.tracing.propagation().keys(); + } + + @Override + public void inject(TraceContext traceContext, C carrier, Setter setter) { + this.tracing.propagation().injector(setter::set).inject(BraveTraceContext.toBrave(traceContext), carrier); + } + + @Override + public Span.Builder extract(C carrier, Getter getter) { + TraceContextOrSamplingFlags extract = this.tracing.propagation().extractor(getter::get).extract(carrier); + if (extract.samplingFlags() == SamplingFlags.EMPTY) { + return new BraveSpanBuilder(this.tracing.tracer()); + } + return BraveSpanBuilder.toBuilder(this.tracing.tracer(), extract); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveSamplerFunction.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveSamplerFunction.java new file mode 100755 index 00000000..e6391c35 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveSamplerFunction.java @@ -0,0 +1,57 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import brave.sampler.SamplerFunctions; +import io.micrometer.core.instrument.transport.http.HttpRequest; +import io.micrometer.tracing.SamplerFunction; + +/** + * Brave implementation of a {@link SamplerFunction}. + * + * @param type of the input, for example a request or method + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +@SuppressWarnings("unchecked") +public final class BraveSamplerFunction implements SamplerFunction { + + final brave.sampler.SamplerFunction samplerFunction; + + public BraveSamplerFunction(brave.sampler.SamplerFunction samplerFunction) { + this.samplerFunction = samplerFunction; + } + + static brave.sampler.SamplerFunction toBrave(SamplerFunction samplerFunction, Class sleuthInput, + Class braveInput) { + if (sleuthInput.equals(HttpRequest.class) && braveInput.equals(brave.http.HttpRequest.class)) { + return arg -> samplerFunction.trySample((T) BraveHttpRequest.fromBrave((brave.http.HttpRequest) arg)); + } + return SamplerFunctions.deferDecision(); + } + + public static brave.sampler.SamplerFunction toHttpBrave( + SamplerFunction samplerFunction) { + return arg -> samplerFunction.trySample(BraveHttpRequest.fromBrave((brave.http.HttpRequest) arg)); + } + + @Override + public Boolean trySample(T arg) { + return this.samplerFunction.trySample(arg); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveScopedSpan.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveScopedSpan.java new file mode 100755 index 00000000..990de0e3 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveScopedSpan.java @@ -0,0 +1,94 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.Objects; + +import io.micrometer.tracing.ScopedSpan; +import io.micrometer.tracing.TraceContext; + +/** + * Brave implementation of a {@link ScopedSpan}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +class BraveScopedSpan implements ScopedSpan { + + final brave.ScopedSpan span; + + BraveScopedSpan(brave.ScopedSpan span) { + this.span = span; + } + + @Override + public boolean isNoop() { + return this.span.isNoop(); + } + + @Override + public TraceContext context() { + return new BraveTraceContext(this.span.context()); + } + + @Override + public ScopedSpan name(String name) { + this.span.name(name); + return this; + } + + @Override + public ScopedSpan tag(String key, String value) { + this.span.tag(key, value); + return this; + } + + @Override + public ScopedSpan event(String value) { + this.span.annotate(value); + return this; + } + + @Override + public ScopedSpan error(Throwable throwable) { + this.span.error(throwable); + return this; + } + + @Override + public void end() { + this.span.finish(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BraveScopedSpan that = (BraveScopedSpan) o; + return Objects.equals(this.span, that.span); + } + + @Override + public int hashCode() { + return Objects.hash(this.span); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveSpan.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveSpan.java new file mode 100755 index 00000000..bb4411ec --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveSpan.java @@ -0,0 +1,144 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.Objects; + +import io.micrometer.tracing.Span; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.docs.AssertingSpan; + +/** + * Brave implementation of a {@link Span}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class BraveSpan implements Span { + + final brave.Span delegate; + + public BraveSpan(brave.Span delegate) { + this.delegate = delegate; + } + + public static brave.Span toBrave(Span span) { + BraveSpan unwrap = (BraveSpan) AssertingSpan.unwrap(span); + if (unwrap == null) { + return null; + } + return unwrap.delegate; + } + + public static Span fromBrave(brave.Span span) { + return new BraveSpan(span); + } + + @Override + public boolean isNoop() { + return this.delegate.isNoop(); + } + + @Override + public TraceContext context() { + if (this.delegate == null) { + return null; + } + return new BraveTraceContext(this.delegate.context()); + } + + @Override + public Span start() { + this.delegate.start(); + return this; + } + + @Override + public Span name(String name) { + this.delegate.name(name); + return this; + } + + @Override + public Span event(String value) { + this.delegate.annotate(value); + return this; + } + + @Override + public Span tag(String key, String value) { + this.delegate.tag(key, value); + return this; + } + + @Override + public Span error(Throwable throwable) { + String message = throwable.getMessage() == null ? throwable.getClass().getSimpleName() : throwable.getMessage(); + this.delegate.tag("error", message); + this.delegate.error(throwable); + return this; + } + + @Override + public void end() { + this.delegate.finish(); + } + + @Override + public void abandon() { + this.delegate.abandon(); + } + + @Override + public Span remoteServiceName(String remoteServiceName) { + this.delegate.remoteServiceName(remoteServiceName); + return this; + } + + @Override + public Span remoteIpAndPort(String ip, int port) { + this.delegate.remoteIpAndPort(ip, port); + return this; + } + + @Override + public String toString() { + return this.delegate != null ? this.delegate.toString() : "null"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + Object unwrapped = o; + if (o instanceof AssertingSpan) { + unwrapped = ((AssertingSpan) o).getDelegate(); + } + if (unwrapped == null || getClass() != unwrapped.getClass()) { + return false; + } + BraveSpan braveSpan = (BraveSpan) unwrapped; + return Objects.equals(this.delegate, braveSpan.delegate); + } + + @Override + public int hashCode() { + return Objects.hash(this.delegate); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveSpanBuilder.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveSpanBuilder.java new file mode 100755 index 00000000..a3e425e9 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveSpanBuilder.java @@ -0,0 +1,136 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import brave.Tracer; +import brave.propagation.TraceContextOrSamplingFlags; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.TraceContext; + +/** + * Brave implementation of a {@link Span.Builder}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +class BraveSpanBuilder implements Span.Builder { + + private final Tracer tracer; + + brave.Span delegate; + + TraceContextOrSamplingFlags parentContext; + + private long startTimestamp; + + BraveSpanBuilder(Tracer tracer) { + this.tracer = tracer; + } + + BraveSpanBuilder(Tracer tracer, TraceContextOrSamplingFlags parentContext) { + this.tracer = tracer; + this.parentContext = parentContext; + } + + static Span.Builder toBuilder(Tracer tracer, TraceContextOrSamplingFlags context) { + return new BraveSpanBuilder(tracer, context); + } + + brave.Span span() { + if (this.delegate != null) { + return this.delegate; + } + else if (this.parentContext != null) { + this.delegate = this.tracer.nextSpan(this.parentContext); + } + else { + this.delegate = this.tracer.nextSpan(); + } + return this.delegate; + } + + @Override + public Span.Builder setParent(TraceContext context) { + this.parentContext = TraceContextOrSamplingFlags.create(BraveTraceContext.toBrave(context)); + return this; + } + + @Override + public Span.Builder setNoParent() { + return this; + } + + @Override + public Span.Builder name(String name) { + span().name(name); + return this; + } + + @Override + public Span.Builder event(String value) { + span().annotate(value); + return this; + } + + @Override + public Span.Builder tag(String key, String value) { + span().tag(key, value); + return this; + } + + @Override + public Span.Builder error(Throwable throwable) { + span().error(throwable); + return this; + } + + @Override + public Span.Builder kind(Span.Kind kind) { + span().kind(kind != null ? brave.Span.Kind.valueOf(kind.toString()) : null); + return this; + } + + @Override + public Span.Builder remoteServiceName(String remoteServiceName) { + span().remoteServiceName(remoteServiceName); + return this; + } + + @Override + public Span.Builder remoteIpAndPort(String ip, int port) { + span().remoteIpAndPort(ip, port); + return this; + } + + @Override + public Span start() { + if (this.startTimestamp > 0) { + span().start(this.startTimestamp); + } + else { + span().start(); + } + return BraveSpan.fromBrave(this.delegate); + } + + @Override + public String toString() { + return "{" + " delegate='" + this.delegate + "'" + ", parentContext='" + this.parentContext + "'" + + ", startTimestamp='" + this.startTimestamp + "'" + "}"; + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveSpanCustomizer.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveSpanCustomizer.java new file mode 100755 index 00000000..0dbfca01 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveSpanCustomizer.java @@ -0,0 +1,59 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import io.micrometer.tracing.SpanCustomizer; +import io.micrometer.tracing.docs.AssertingSpanCustomizer; + +/** + * Brave implementation of a {@link SpanCustomizer}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class BraveSpanCustomizer implements SpanCustomizer { + + private final brave.SpanCustomizer spanCustomizer; + + public BraveSpanCustomizer(brave.SpanCustomizer spanCustomizer) { + this.spanCustomizer = spanCustomizer; + } + + static brave.SpanCustomizer toBrave(SpanCustomizer spanCustomizer) { + return ((BraveSpanCustomizer) AssertingSpanCustomizer.unwrap(spanCustomizer)).spanCustomizer; + } + + static SpanCustomizer fromBrave(brave.SpanCustomizer spanCustomizer) { + return new BraveSpanCustomizer(spanCustomizer); + } + + @Override + public SpanCustomizer name(String name) { + return new BraveSpanCustomizer(this.spanCustomizer.name(name)); + } + + @Override + public SpanCustomizer tag(String key, String value) { + return new BraveSpanCustomizer(this.spanCustomizer.tag(key, value)); + } + + @Override + public SpanCustomizer event(String value) { + return new BraveSpanCustomizer(this.spanCustomizer.annotate(value)); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveTraceContext.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveTraceContext.java new file mode 100755 index 00000000..6df1a1b2 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveTraceContext.java @@ -0,0 +1,92 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.Objects; + +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.lang.Nullable; + +/** + * Brave implementation of a {@link TraceContext}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class BraveTraceContext implements TraceContext { + + final brave.propagation.TraceContext traceContext; + + public BraveTraceContext(brave.propagation.TraceContext traceContext) { + this.traceContext = traceContext; + } + + public static brave.propagation.TraceContext toBrave(TraceContext traceContext) { + if (traceContext == null) { + return null; + } + return ((BraveTraceContext) traceContext).traceContext; + } + + public static TraceContext fromBrave(brave.propagation.TraceContext traceContext) { + return new BraveTraceContext(traceContext); + } + + @Override + public String traceId() { + return this.traceContext.traceIdString(); + } + + @Override + @Nullable + public String parentId() { + return this.traceContext.parentIdString(); + } + + @Override + public String spanId() { + return this.traceContext.spanIdString(); + } + + @Override + public Boolean sampled() { + return this.traceContext.sampled(); + } + + @Override + public String toString() { + return this.traceContext != null ? this.traceContext.toString() : "null"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BraveTraceContext that = (BraveTraceContext) o; + return Objects.equals(this.traceContext, that.traceContext); + } + + @Override + public int hashCode() { + return Objects.hash(this.traceContext); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveTraceContextBuilder.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveTraceContextBuilder.java new file mode 100755 index 00000000..c343bd04 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveTraceContextBuilder.java @@ -0,0 +1,71 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.internal.EncodingUtils; + +/** + * Brave implementation of a {@link TraceContext.Builder}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +class BraveTraceContextBuilder implements TraceContext.Builder { + + brave.propagation.TraceContext.Builder delegate = brave.propagation.TraceContext.newBuilder(); + + @Override + public TraceContext.Builder traceId(String traceId) { + long[] fromString = EncodingUtils.fromString(traceId); + if (fromString.length == 2) { + this.delegate.traceIdHigh(fromString[0]); + this.delegate.traceId(fromString[1]); + } + else { + this.delegate.traceId(fromString[0]); + } + return this; + } + + @Override + public TraceContext.Builder parentId(String traceId) { + long[] fromString = EncodingUtils.fromString(traceId); + this.delegate.parentId(fromString[fromString.length == 2 ? 1 : 0]); + return this; + } + + @Override + public TraceContext.Builder spanId(String spanId) { + long[] fromString = EncodingUtils.fromString(spanId); + this.delegate.spanId(fromString[fromString.length == 2 ? 1 : 0]); + return this; + } + + @Override + public TraceContext.Builder sampled(Boolean sampled) { + this.delegate.sampled(sampled); + return this; + } + + @Override + public TraceContext build() { + brave.propagation.TraceContext context = this.delegate.build(); + return BraveTraceContext.fromBrave(context); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveTracer.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveTracer.java new file mode 100755 index 00000000..00a024ed --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveTracer.java @@ -0,0 +1,148 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.Map; + +import brave.propagation.TraceContextOrSamplingFlags; +import io.micrometer.tracing.BaggageInScope; +import io.micrometer.tracing.CurrentTraceContext; +import io.micrometer.tracing.ScopedSpan; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.SpanCustomizer; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.docs.AssertingSpan; + +/** + * Brave implementation of a {@link Tracer}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class BraveTracer implements Tracer { + + private final brave.Tracer tracer; + + private final BraveBaggageManager braveBaggageManager; + + private final CurrentTraceContext currentTraceContext; + + public BraveTracer(brave.Tracer tracer, CurrentTraceContext context, BraveBaggageManager braveBaggageManager) { + this.tracer = tracer; + this.braveBaggageManager = braveBaggageManager; + this.currentTraceContext = context; + } + + @Override + public Span nextSpan(Span parent) { + if (parent == null) { + return nextSpan(); + } + brave.propagation.TraceContext context = (((BraveTraceContext) parent.context()).traceContext); + if (context == null) { + return null; + } + return new BraveSpan(this.tracer.nextSpan(TraceContextOrSamplingFlags.create(context))); + } + + @Override + public SpanInScope withSpan(Span span) { + return new BraveSpanInScope( + tracer.withSpanInScope(span == null ? null : ((BraveSpan) AssertingSpan.unwrap(span)).delegate)); + } + + @Override + public SpanCustomizer currentSpanCustomizer() { + return new BraveSpanCustomizer(this.tracer.currentSpanCustomizer()); + } + + @Override + public Span currentSpan() { + brave.Span currentSpan = this.tracer.currentSpan(); + if (currentSpan == null) { + return null; + } + return new BraveSpan(currentSpan); + } + + @Override + public Span nextSpan() { + return new BraveSpan(this.tracer.nextSpan()); + } + + @Override + public ScopedSpan startScopedSpan(String name) { + return new BraveScopedSpan(this.tracer.startScopedSpan(name)); + } + + @Override + public Span.Builder spanBuilder() { + return new BraveSpanBuilder(this.tracer); + } + + @Override + public TraceContext.Builder traceContextBuilder() { + return new BraveTraceContextBuilder(); + } + + @Override + public Map getAllBaggage() { + return this.braveBaggageManager.getAllBaggage(); + } + + @Override + public BaggageInScope getBaggage(String name) { + return this.braveBaggageManager.getBaggage(name); + } + + @Override + public BaggageInScope getBaggage(TraceContext traceContext, String name) { + return this.braveBaggageManager.getBaggage(traceContext, name); + } + + @Override + public BaggageInScope createBaggage(String name) { + return this.braveBaggageManager.createBaggage(name); + } + + @Override + public BaggageInScope createBaggage(String name, String value) { + return this.braveBaggageManager.createBaggage(name).set(value); + } + + @Override + public CurrentTraceContext currentTraceContext() { + return this.currentTraceContext; + } + +} + +class BraveSpanInScope implements Tracer.SpanInScope { + + final brave.Tracer.SpanInScope delegate; + + BraveSpanInScope(brave.Tracer.SpanInScope delegate) { + this.delegate = delegate; + } + + @Override + public void close() { + this.delegate.close(); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/CompositePropagationFactory.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/CompositePropagationFactory.java new file mode 100644 index 00000000..e7ebde3b --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/CompositePropagationFactory.java @@ -0,0 +1,237 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.AbstractMap; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import brave.internal.propagation.StringPropagationAdapter; +import brave.propagation.B3Propagation; +import brave.propagation.Propagation; +import brave.propagation.TraceContext; +import brave.propagation.TraceContextOrSamplingFlags; +import brave.propagation.aws.AWSPropagation; +import io.micrometer.tracing.brave.propagation.PropagationType; + +@SuppressWarnings({"unchecked", "deprecation"}) +class CompositePropagationFactory extends Propagation.Factory implements Propagation { + + private final Map>> mapping = new HashMap<>(); + + private final List types; + + CompositePropagationFactory(Supplier factorySupplier, BraveBaggageManager braveBaggageManager, + List localFields, List types) { + this.types = types; + this.mapping.put(PropagationType.AWS, + new AbstractMap.SimpleEntry<>(AWSPropagation.FACTORY, AWSPropagation.FACTORY.get())); + // Note: Versions <2.2.3 use injectFormat(MULTI) for non-remote (ex + // spring-messaging) + // See #1643 + Factory b3Factory = b3Factory(); + this.mapping.put(PropagationType.B3, new AbstractMap.SimpleEntry<>(b3Factory, b3Factory.get())); + W3CPropagation w3CPropagation = new W3CPropagation(braveBaggageManager, localFields); + this.mapping.put(io.micrometer.tracing.brave.propagation.PropagationType.W3C, new AbstractMap.SimpleEntry<>(w3CPropagation, w3CPropagation.get())); + LazyPropagationFactory lazyPropagationFactory = new LazyPropagationFactory(factorySupplier); + this.mapping.put(io.micrometer.tracing.brave.propagation.PropagationType.CUSTOM, + new AbstractMap.SimpleEntry<>(lazyPropagationFactory, lazyPropagationFactory.get())); + } + + private Factory b3Factory() { + return B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE_NO_PARENT).build(); + } + + @Override + public List keys() { + return this.types.stream().map(this.mapping::get).flatMap(p -> p.getValue().keys().stream()) + .collect(Collectors.toList()); + } + + @Override + public TraceContext.Injector injector(Setter setter) { + return (traceContext, request) -> { + this.types.stream().map(this.mapping::get) + .forEach(p -> p.getValue().injector(setter).inject(traceContext, request)); + }; + } + + @Override + public TraceContext.Extractor extractor(Getter getter) { + return request -> { + for (PropagationType type : this.types) { + Map.Entry> entry = this.mapping.get(type); + if (entry == null) { + continue; + } + Propagation propagator = entry.getValue(); + if (propagator == null || propagator == NoOpPropagation.INSTANCE) { + continue; + } + TraceContextOrSamplingFlags extract = propagator.extractor(getter).extract(request); + if (extract != TraceContextOrSamplingFlags.EMPTY) { + return extract; + } + } + return TraceContextOrSamplingFlags.EMPTY; + }; + } + + @Override + public Propagation create(KeyFactory keyFactory) { + return StringPropagationAdapter.create(this, keyFactory); + } + + @Override + public boolean supportsJoin() { + return this.types.stream().map(this.mapping::get).allMatch(e -> e.getKey().supportsJoin()); + } + + @Override + public boolean requires128BitTraceId() { + return this.types.stream().map(this.mapping::get).allMatch(e -> e.getKey().requires128BitTraceId()); + } + + @Override + public TraceContext decorate(TraceContext context) { + for (PropagationType type : this.types) { + Map.Entry> entry = this.mapping.get(type); + if (entry == null) { + continue; + } + TraceContext decorate = entry.getKey().decorate(context); + if (decorate != context) { + return decorate; + } + } + return super.decorate(context); + } + + @SuppressWarnings("unchecked") + private static final class LazyPropagationFactory extends Propagation.Factory { + + private final Supplier delegate; + + private volatile Propagation.Factory propagationFactory; + + private LazyPropagationFactory(Supplier delegate) { + this.delegate = delegate; + } + + private Propagation.Factory propagationFactory() { + if (this.propagationFactory == null) { + Factory supplier = this.delegate.get(); + this.propagationFactory = supplier != null ? supplier : NoOpPropagation.INSTANCE; + } + return this.propagationFactory; + } + + @Override + public Propagation create(KeyFactory keyFactory) { + return propagationFactory().create(keyFactory); + } + + @Override + public boolean supportsJoin() { + return propagationFactory().supportsJoin(); + } + + @Override + public boolean requires128BitTraceId() { + return propagationFactory().requires128BitTraceId(); + } + + @Override + public Propagation get() { + return new LazyPropagation(this); + } + + @Override + public TraceContext decorate(TraceContext context) { + return propagationFactory().decorate(context); + } + + } + + @SuppressWarnings("unchecked") + private static final class LazyPropagation implements Propagation { + + private final LazyPropagationFactory delegate; + + private volatile Propagation propagation; + + private LazyPropagation(LazyPropagationFactory delegate) { + this.delegate = delegate; + } + + private Propagation propagation() { + if (this.propagation == null) { + this.propagation = this.delegate.propagationFactory().get(); + } + return this.propagation; + } + + @Override + public List keys() { + return propagation().keys(); + } + + @Override + public TraceContext.Injector injector(Setter setter) { + return propagation().injector(setter); + } + + @Override + public TraceContext.Extractor extractor(Getter getter) { + return propagation().extractor(getter); + } + + } + + private static class NoOpPropagation extends Propagation.Factory implements Propagation { + + static final NoOpPropagation INSTANCE = new NoOpPropagation(); + + @Override + public List keys() { + return Collections.emptyList(); + } + + @Override + public TraceContext.Injector injector(Setter setter) { + return (traceContext, request) -> { + + }; + } + + @Override + public TraceContext.Extractor extractor(Getter getter) { + return request -> TraceContextOrSamplingFlags.EMPTY; + } + + @Override + public Propagation create(KeyFactory keyFactory) { + return StringPropagationAdapter.create(this, keyFactory); + } + + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/CompositePropagationFactorySupplier.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/CompositePropagationFactorySupplier.java new file mode 100755 index 00000000..f3a744ad --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/CompositePropagationFactorySupplier.java @@ -0,0 +1,73 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.List; +import java.util.function.Supplier; + +import brave.propagation.Propagation; +import io.micrometer.tracing.brave.propagation.PropagationFactorySupplier; +import io.micrometer.tracing.brave.propagation.PropagationType; + +/** + * Merges various propagation factories into a composite. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class CompositePropagationFactorySupplier implements PropagationFactorySupplier { + + private final Supplier baggageManagerSupplier; + + private final Supplier factorySupplier; + + private final List localFields; + + private final List types; + + /** + * @param baggageManagerSupplier bean factory + * @param factorySupplier factory supplier + * @param localFields local fields to be set in context + * @param types supported propagation types + */ + public CompositePropagationFactorySupplier(Supplier baggageManagerSupplier, + Supplier factorySupplier, List localFields, + List types) { + this.baggageManagerSupplier = baggageManagerSupplier; + this.factorySupplier = factorySupplier; + this.localFields = localFields; + this.types = types; + } + + @Override + public Propagation.Factory get() { + return new CompositePropagationFactory(this.factorySupplier, + braveBaggageManager(), + this.localFields, this.types); + } + + private BraveBaggageManager braveBaggageManager() { + BraveBaggageManager baggageManager = baggageManagerSupplier.get(); + if (baggageManager != null) { + return baggageManager; + } + return new BraveBaggageManager(); + } + +} + diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/CompositeSpanHandler.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/CompositeSpanHandler.java new file mode 100755 index 00000000..a84dd4ae --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/CompositeSpanHandler.java @@ -0,0 +1,71 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.Collections; +import java.util.List; + +import brave.handler.MutableSpan; +import brave.handler.SpanHandler; +import brave.propagation.TraceContext; +import io.micrometer.tracing.exporter.SpanFilter; +import io.micrometer.tracing.exporter.SpanReporter; + +/** + * Merges {@link SpanFilter}s and {@link SpanReporter}s into a {@link SpanHandler}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class CompositeSpanHandler extends SpanHandler { + + private final List filters; + + private final List reporters; + + public CompositeSpanHandler(List filters, List reporters) { + this.filters = filters == null ? Collections.emptyList() : filters; + this.reporters = reporters == null ? Collections.emptyList() : reporters; + } + + @Override + public boolean end(TraceContext context, MutableSpan span, Cause cause) { + if (cause != Cause.FINISHED) { + return true; + } + boolean shouldProcess = shouldProcess(span); + if (!shouldProcess) { + return false; + } + shouldProcess = super.end(context, span, cause); + if (!shouldProcess) { + return false; + } + this.reporters.forEach(r -> r.report(BraveFinishedSpan.fromBrave(span))); + return true; + } + + private boolean shouldProcess(MutableSpan span) { + for (SpanFilter exporter : this.filters) { + if (!exporter.isExportable(BraveFinishedSpan.fromBrave(span))) { + return false; + } + } + return true; + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/W3CPropagation.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/W3CPropagation.java new file mode 100755 index 00000000..7e29b95d --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/W3CPropagation.java @@ -0,0 +1,482 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import brave.baggage.BaggageField; +import brave.baggage.BaggagePropagation; +import brave.baggage.BaggagePropagationConfig; +import brave.internal.baggage.BaggageFields; +import brave.internal.propagation.StringPropagationAdapter; +import brave.propagation.Propagation; +import brave.propagation.TraceContext; +import brave.propagation.TraceContextOrSamplingFlags; +import io.micrometer.core.util.internal.logging.InternalLogger; +import io.micrometer.core.util.internal.logging.InternalLoggerFactory; +import io.micrometer.tracing.BaggageInScope; +import io.micrometer.tracing.internal.EncodingUtils; +import io.micrometer.tracing.util.StringUtils; + +import static java.util.Collections.singletonList; + +/** + * Adopted from OpenTelemetry API. + * + * Implementation of the TraceContext propagation protocol. See w3c/distributed-tracing. + * + * @author OpenTelemetry Authors + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +@SuppressWarnings({"unchecked", "deprecation"}) +class W3CPropagation extends Propagation.Factory implements Propagation { + + static final String TRACE_PARENT = "traceparent"; + + static final String TRACE_STATE = "tracestate"; + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(W3CPropagation.class.getName()); + + private static final List FIELDS = Collections.unmodifiableList(Arrays.asList(TRACE_PARENT, TRACE_STATE)); + + private static final String VERSION = "00"; + + private static final int VERSION_SIZE = 2; + + private static final char TRACEPARENT_DELIMITER = '-'; + + private static final int TRACEPARENT_DELIMITER_SIZE = 1; + + private static final int LONG_BYTES = Long.SIZE / Byte.SIZE; + + private static final int BYTE_BASE16 = 2; + + private static final int LONG_BASE16 = BYTE_BASE16 * LONG_BYTES; + + private static final int TRACE_ID_HEX_SIZE = 2 * LONG_BASE16; + + private static final int SPAN_ID_SIZE = 8; + + private static final int SPAN_ID_HEX_SIZE = 2 * SPAN_ID_SIZE; + + private static final int FLAGS_SIZE = 1; + + private static final int TRACE_OPTION_HEX_SIZE = 2 * FLAGS_SIZE; + + private static final int TRACE_ID_OFFSET = VERSION_SIZE + TRACEPARENT_DELIMITER_SIZE; + + private static final int SPAN_ID_OFFSET = TRACE_ID_OFFSET + TRACE_ID_HEX_SIZE + TRACEPARENT_DELIMITER_SIZE; + + private static final int TRACE_OPTION_OFFSET = SPAN_ID_OFFSET + SPAN_ID_HEX_SIZE + TRACEPARENT_DELIMITER_SIZE; + + private static final int TRACEPARENT_HEADER_SIZE = TRACE_OPTION_OFFSET + TRACE_OPTION_HEX_SIZE; + + private static final String INVALID_TRACE_ID = "00000000000000000000000000000000"; + + private static final String INVALID_SPAN_ID = "0000000000000000"; + + // private static final char TRACESTATE_ENTRY_DELIMITER = ','; + + private static final Set VALID_VERSIONS; + + private static final String VERSION_00 = "00"; + + private final W3CBaggagePropagator baggagePropagator; + + private final BraveBaggageManager braveBaggageManager; + + W3CPropagation(BraveBaggageManager braveBaggageManager, List localFields) { + this.baggagePropagator = new W3CBaggagePropagator(braveBaggageManager, localFields); + this.braveBaggageManager = braveBaggageManager; + } + + private static boolean isTraceIdValid(CharSequence traceId) { + return (traceId.length() == TRACE_ID_HEX_SIZE) && !INVALID_TRACE_ID.contentEquals(traceId) + && EncodingUtils.isValidBase16String(traceId); + } + + private static boolean isSpanIdValid(String spanId) { + return (spanId.length() == SPAN_ID_HEX_SIZE) && !INVALID_SPAN_ID.equals(spanId) + && EncodingUtils.isValidBase16String(spanId); + } + + private static TraceContext extractContextFromTraceParent(String traceparent) { + // TODO(bdrutu): Do we need to verify that version is hex and that + // for the version the length is the expected one? + boolean isValid = (traceparent.length() == TRACEPARENT_HEADER_SIZE + || (traceparent.length() > TRACEPARENT_HEADER_SIZE + && traceparent.charAt(TRACEPARENT_HEADER_SIZE) == TRACEPARENT_DELIMITER)) + && traceparent.charAt(TRACE_ID_OFFSET - 1) == TRACEPARENT_DELIMITER + && traceparent.charAt(SPAN_ID_OFFSET - 1) == TRACEPARENT_DELIMITER + && traceparent.charAt(TRACE_OPTION_OFFSET - 1) == TRACEPARENT_DELIMITER; + if (!isValid) { + logger.info("Unparseable traceparent header. Returning INVALID span context."); + return null; + } + + try { + String version = traceparent.substring(0, 2); + if (!VALID_VERSIONS.contains(version)) { + return null; + } + if (version.equals(VERSION_00) && traceparent.length() > TRACEPARENT_HEADER_SIZE) { + return null; + } + + String traceId = traceparent.substring(TRACE_ID_OFFSET, TRACE_ID_OFFSET + TRACE_ID_HEX_SIZE); + String spanId = traceparent.substring(SPAN_ID_OFFSET, SPAN_ID_OFFSET + SPAN_ID_HEX_SIZE); + if (isTraceIdValid(traceId) && isSpanIdValid(spanId)) { + String traceIdHigh = traceId.substring(0, traceId.length() / 2); + String traceIdLow = traceId.substring(traceId.length() / 2); + byte isSampled = TraceFlags.byteFromHex(traceparent, TRACE_OPTION_OFFSET); + return TraceContext.newBuilder().shared(true) + .traceIdHigh(EncodingUtils.longFromBase16String(traceIdHigh)) + .traceId(EncodingUtils.longFromBase16String(traceIdLow)) + .spanId(EncodingUtils.longFromBase16String(spanId)).sampled(isSampled == TraceFlags.IS_SAMPLED) + .build(); + } + return null; + } + catch (IllegalArgumentException e) { + logger.info("Unparseable traceparent header. Returning INVALID span context."); + return null; + } + } + + @Override + public Propagation create(KeyFactory keyFactory) { + return StringPropagationAdapter.create(this, keyFactory); + } + + @Override + public List keys() { + return FIELDS; + } + + @Override + public TraceContext.Injector injector(Setter setter) { + return (context, carrier) -> { + Objects.requireNonNull(context, "context"); + Objects.requireNonNull(setter, "setter"); + char[] chars = TemporaryBuffers.chars(TRACEPARENT_HEADER_SIZE); + chars[0] = VERSION.charAt(0); + chars[1] = VERSION.charAt(1); + chars[2] = TRACEPARENT_DELIMITER; + String traceId = padLeftWithZeros(context.traceIdString(), TRACE_ID_HEX_SIZE); + for (int i = 0; i < traceId.length(); i++) { + chars[TRACE_ID_OFFSET + i] = traceId.charAt(i); + } + chars[SPAN_ID_OFFSET - 1] = TRACEPARENT_DELIMITER; + String spanId = context.spanIdString(); + for (int i = 0; i < spanId.length(); i++) { + chars[SPAN_ID_OFFSET + i] = spanId.charAt(i); + } + chars[TRACE_OPTION_OFFSET - 1] = TRACEPARENT_DELIMITER; + copyTraceFlagsHexTo(chars, TRACE_OPTION_OFFSET, context); + setter.put(carrier, TRACE_PARENT, new String(chars, 0, TRACEPARENT_HEADER_SIZE)); + addTraceState(setter, context, carrier); + this.baggagePropagator.injector(setter).inject(context, carrier); + }; + } + + private void addTraceState(Setter setter, TraceContext context, R carrier) { + if (carrier != null) { + BaggageInScope baggage = this.braveBaggageManager.getBaggage(BraveTraceContext.fromBrave(context), + TRACE_STATE); + if (baggage == null) { + return; + } + String traceState = baggage.get(BraveTraceContext.fromBrave(context)); + if (StringUtils.isNotBlank(traceState)) { + setter.put(carrier, TRACE_STATE, traceState); + } + } + } + + private String padLeftWithZeros(String string, int length) { + if (string.length() >= length) { + return string; + } + else { + StringBuilder sb = new StringBuilder(length); + for (int i = string.length(); i < length; i++) { + sb.append('0'); + } + + return sb.append(string).toString(); + } + } + + void copyTraceFlagsHexTo(char[] dest, int destOffset, TraceContext context) { + dest[destOffset] = '0'; + dest[destOffset + 1] = Boolean.TRUE.equals(context.sampled()) ? '1' : '0'; + } + + @Override + public TraceContext.Extractor extractor(Getter getter) { + Objects.requireNonNull(getter, "getter"); + return carrier -> { + String traceParent = getter.get(carrier, TRACE_PARENT); + if (traceParent == null) { + return withBaggage(TraceContextOrSamplingFlags.EMPTY, carrier, getter); + } + TraceContext contextFromParentHeader = extractContextFromTraceParent(traceParent); + if (contextFromParentHeader == null) { + return withBaggage(TraceContextOrSamplingFlags.EMPTY, carrier, getter); + } + String traceStateHeader = getter.get(carrier, TRACE_STATE); + return withBaggage(context(contextFromParentHeader, traceStateHeader), carrier, getter); + }; + } + + private TraceContextOrSamplingFlags withBaggage(TraceContextOrSamplingFlags context, R carrier, + Getter getter) { + if (context.context() == null) { + return context; + } + return this.baggagePropagator.contextWithBaggage(carrier, context, getter); + } + + TraceContextOrSamplingFlags context(TraceContext contextFromParentHeader, String traceStateHeader) { + if (!StringUtils.isNotBlank(traceStateHeader)) { + return TraceContextOrSamplingFlags.create(contextFromParentHeader); + } + try { + return TraceContextOrSamplingFlags + .newBuilder(TraceContext.newBuilder().traceId(contextFromParentHeader.traceId()) + .traceIdHigh(contextFromParentHeader.traceIdHigh()).spanId(contextFromParentHeader.spanId()) + .sampled(contextFromParentHeader.sampled()).shared(true).build()) + .build(); + } + catch (IllegalArgumentException e) { + logger.info("Unparseable tracestate header. Returning span context without state."); + return TraceContextOrSamplingFlags.create(contextFromParentHeader); + } + } + + static { + // A valid version is 1 byte representing an 8-bit unsigned integer, version ff is + // invalid. + VALID_VERSIONS = new HashSet<>(); + for (int i = 0; i < 255; i++) { + String version = Long.toHexString(i); + if (version.length() < 2) { + version = '0' + version; + } + VALID_VERSIONS.add(version); + } + } + +} + +/** + * Taken from OpenTelemetry API. + */ +@SuppressWarnings("deprecation") +class W3CBaggagePropagator { + + private static final InternalLogger log = InternalLoggerFactory.getInstance(W3CBaggagePropagator.class); + + private static final String TRACE_STATE = "tracestate"; + + private static final BaggageField TRACE_STATE_BAGGAGE = BaggageField.create(TRACE_STATE); + + private static final String FIELD = "baggage"; + + private static final List FIELDS = singletonList(FIELD); + + private final BraveBaggageManager braveBaggageManager; + + private final List localFields; + + W3CBaggagePropagator(BraveBaggageManager braveBaggageManager, List localFields) { + this.braveBaggageManager = braveBaggageManager; + this.localFields = localFields; + } + + private BaggagePropagation.FactoryBuilder factory() { + return BaggagePropagation.newFactoryBuilder(new Propagation.Factory() { + @Override + public Propagation create(Propagation.KeyFactory keyFactory) { + return null; + } + }); + } + + public List keys() { + return FIELDS; + } + + public TraceContext.Injector injector(Propagation.Setter setter) { + return (context, carrier) -> { + BaggageFields extra = context.findExtra(BaggageFields.class); + if (extra == null || extra.getAllFields().isEmpty()) { + return; + } + StringBuilder headerContent = new StringBuilder(); + // We ignore local keys - they won't get propagated + String[] strings = this.localFields.toArray(new String[0]); + Map filtered = extra.toMapFilteringFieldNames(strings); + for (Map.Entry entry : filtered.entrySet()) { + if (TRACE_STATE.equalsIgnoreCase(entry.getKey())) { + continue; + } + headerContent.append(entry.getKey()).append("=").append(entry.getValue()); + // TODO: [OTEL] No metadata support + // String metadataValue = entry.getEntryMetadata().getValue(); + // if (metadataValue != null && !metadataValue.isEmpty()) { + // headerContent.append(";").append(metadataValue); + // } + headerContent.append(","); + } + if (headerContent.length() > 0) { + headerContent.setLength(headerContent.length() - 1); + setter.put(carrier, FIELD, headerContent.toString()); + } + }; + } + + TraceContextOrSamplingFlags contextWithBaggage(R carrier, TraceContextOrSamplingFlags flags, + Propagation.Getter getter) { + BaggagePropagation.FactoryBuilder factoryBuilder = factory(); + String traceState = getter.get(carrier, TRACE_STATE); + boolean hasTraceState = StringUtils.isNotBlank(traceState); + if (hasTraceState) { + factoryBuilder = factoryBuilder + .add(BaggagePropagationConfig.SingleBaggageField.remote(TRACE_STATE_BAGGAGE)); + } + String baggageHeader = getter.get(carrier, FIELD); + List> pairs = baggageHeader == null || baggageHeader.isEmpty() + ? Collections.emptyList() : addBaggageToContext(baggageHeader); + Set names = pairs.stream().map(e -> e.getKey().name()).collect(Collectors.toSet()); + for (String name : names) { + factoryBuilder = factoryBuilder.add(BaggagePropagationConfig.SingleBaggageField + .remote(((BraveBaggageInScope) this.braveBaggageManager.createBaggage(name)).unwrap())); + } + TraceContext decoratedContext = factoryBuilder.build().decorate(flags.context()); + if (hasTraceState) { + BaggageInScope baggageInScope = this.braveBaggageManager.createBaggage(TRACE_STATE); + baggageInScope.set(new BraveTraceContext(decoratedContext), traceState); + } + pairs.forEach(e -> { + BaggageField baggage = ((BraveBaggageInScope) e.getKey()).unwrap(); + baggage.updateValue(decoratedContext, e.getValue()); + }); + return TraceContextOrSamplingFlags.create(decoratedContext); + } + + List> addBaggageToContext(String baggageHeader) { + List> pairs = new ArrayList<>(); + String[] entries = baggageHeader.split(","); + for (String entry : entries) { + int beginningOfMetadata = entry.indexOf(";"); + if (beginningOfMetadata > 0) { + entry = entry.substring(0, beginningOfMetadata); + } + String[] keyAndValue = entry.split("="); + for (int i = 0; i < keyAndValue.length; i += 2) { + try { + String key = keyAndValue[i].trim(); + String value = keyAndValue[i + 1].trim(); + BaggageInScope baggage = this.braveBaggageManager.createBaggage(key); + pairs.add(new AbstractMap.SimpleEntry<>(baggage, value)); + } + catch (Exception e) { + if (log.isDebugEnabled()) { + log.debug("Exception occurred while trying to parse baggage with key value [" + + Arrays.toString(keyAndValue) + "]. Will ignore that entry.", e); + } + } + } + } + return pairs; + } + +} + +/** + * Taken from OpenTelemetry API. + * + * {@link ThreadLocal} buffers for use when creating new derived objects such as + * {@link String}s. These buffers are reused within a single thread - it is _not safe_ to + * use the buffer to generate multiple derived objects at the same time because the same + * memory will be used. In general, you should get a temporary buffer, fill it with data, + * and finish by converting into the derived object within the same method to avoid + * multiple usages of the same buffer. + */ +final class TemporaryBuffers { + + private static final ThreadLocal CHAR_ARRAY = new ThreadLocal<>(); + + private TemporaryBuffers() { + } + + /** + * A {@link ThreadLocal} {@code char[]} of size {@code len}. Take care when using a + * large value of {@code len} as this buffer will remain for the lifetime of the + * thread. The returned buffer will not be zeroed and may be larger than the requested + * size, you must make sure to fill the entire content to the desired value and set + * the length explicitly when converting to a {@link String}. + */ + public static char[] chars(int len) { + char[] buffer = CHAR_ARRAY.get(); + if (buffer == null) { + buffer = new char[len]; + CHAR_ARRAY.set(buffer); + } + else if (buffer.length < len) { + buffer = new char[len]; + CHAR_ARRAY.set(buffer); + } + return buffer; + } + + // Visible for testing + static void clearChars() { + CHAR_ARRAY.set(null); + } + +} + +/** + * Taken from OpenTelemetry API. + */ +final class TraceFlags { + + // Bit to represent whether trace is sampled or not. + static final byte IS_SAMPLED = 0x1; + + private TraceFlags() { + } + + /** Extract the byte representation of the flags from a hex-representation. */ + static byte byteFromHex(CharSequence src, int srcOffset) { + return EncodingUtils.byteFromBase16String(src, srcOffset); + } + +} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpClientRequest.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/propagation/PropagationFactorySupplier.java old mode 100644 new mode 100755 similarity index 55% rename from micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpClientRequest.java rename to micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/propagation/PropagationFactorySupplier.java index d91a3e56..6c032b8d --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpClientRequest.java +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/propagation/PropagationFactorySupplier.java @@ -14,32 +14,21 @@ * limitations under the License. */ -package io.micrometer.tracing.transport.http; +package io.micrometer.tracing.brave.propagation; -import io.micrometer.tracing.transport.Kind; +import brave.propagation.Propagation; /** - * This API is taken from OpenZipkin Brave. + * Provides logic for supplying of a {@link Propagation.Factory}. * - * Abstract request type used for parsing and sampling. Represents an HTTP Client request. - * - * @author OpenZipkin Brave Authors * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 1.0.0 */ -public interface HttpClientRequest extends HttpRequest { +public interface PropagationFactorySupplier { /** - * Adds a new header. - * - * @param name header name - * @param value header value + * @return an instance of a {@link Propagation.Factory} */ - void header(String name, String value); - - @Override - default Kind kind() { - return Kind.CLIENT; - } + Propagation.Factory get(); } diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpClientRequest.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/propagation/PropagationType.java old mode 100644 new mode 100755 similarity index 55% rename from micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpClientRequest.java rename to micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/propagation/PropagationType.java index 0e328f29..b7cbcc5f --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpClientRequest.java +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/propagation/PropagationType.java @@ -14,32 +14,34 @@ * limitations under the License. */ -package io.micrometer.tracing.http; - -import io.micrometer.tracing.transport.Kind; +package io.micrometer.tracing.brave.propagation; /** - * This API is taken from OpenZipkin Brave. - * - * Abstract request type used for parsing and sampling. Represents an HTTP Client request. + * Supported propagation types. * - * @author OpenZipkin Brave Authors * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 1.0.0 */ -public interface HttpClientRequest extends HttpRequest { +public enum PropagationType { + + /** + * AWS propagation type. + */ + AWS, /** - * Adds a new header. - * - * @param name header name - * @param value header value + * B3 propagation type. */ - void header(String name, String value); + B3, - @Override - default Kind kind() { - return Kind.CLIENT; - } + /** + * W3C propagation type. + */ + W3C, + + /** + * Custom propagation type. + */ + CUSTOM } diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/sampler/ProbabilityBasedSampler.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/sampler/ProbabilityBasedSampler.java new file mode 100755 index 00000000..6e8159a9 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/sampler/ProbabilityBasedSampler.java @@ -0,0 +1,111 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.sampler; + +import java.util.BitSet; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +import brave.sampler.Sampler; + +/** + * This sampler is appropriate for low-traffic instrumentation (ex servers that each + * receive less than 100K requests), or those who do not provision random trace ids. It + * not appropriate for collectors as the sampling decision isn't idempotent (consistent + * based on trace id). + * + * Implementation + * + *

+ * Taken from CountingTraceIdSampler class from Zipkin project. + *

+ * + * This counts to see how many out of 100 traces should be retained. This means that it is + * accurate in units of 100 traces. + * + * @author Marcin Grzejszczak + * @author Adrian Cole + * @since 1.0.0 + */ +public class ProbabilityBasedSampler extends Sampler { + + private final AtomicInteger counter = new AtomicInteger(0); + + private final BitSet sampleDecisions; + + private final Supplier probability; + + /** + * @param probability supplier of probability + */ + public ProbabilityBasedSampler(Supplier probability) { + if (probability == null) { + throw new IllegalArgumentException("probability property is required for ProbabilityBasedSampler"); + } + this.probability = probability; + int outOf100 = (int) (probability.get() * 100.0f); + this.sampleDecisions = randomBitSet(100, outOf100, new Random()); + } + + /** + * Reservoir sampling algorithm borrowed from Stack Overflow. + * + * https://stackoverflow.com/questions/12817946/generate-a-random-bitset-with-n-1s + * @param size size of the bit set + * @param cardinality cardinality of the bit set + * @param rnd random generator + * @return a random bitset + */ + static BitSet randomBitSet(int size, int cardinality, Random rnd) { + BitSet result = new BitSet(size); + int[] chosen = new int[cardinality]; + int i; + for (i = 0; i < cardinality; ++i) { + chosen[i] = i; + result.set(i); + } + for (; i < size; ++i) { + int j = rnd.nextInt(i + 1); + if (j < cardinality) { + result.clear(chosen[j]); + result.set(i); + chosen[j] = i; + } + } + return result; + } + + @Override + public boolean isSampled(long traceId) { + if (this.probability.get() == 0) { + return false; + } + else if (this.probability.get() == 1.0f) { + return true; + } + synchronized (this) { + final int i = this.counter.getAndIncrement(); + boolean result = this.sampleDecisions.get(i); + if (i == 99) { + this.counter.set(0); + } + return result; + } + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/sampler/RateLimitingSampler.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/sampler/RateLimitingSampler.java new file mode 100755 index 00000000..2fb6539e --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/sampler/RateLimitingSampler.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.sampler; + +import java.util.function.Supplier; + +import brave.sampler.Sampler; + +/** + * The rate-limited sampler allows you to choose an amount of traces to accept on a + * per-second interval. The minimum number is 0 and the max is 2,147,483,647 (max int). + * + * You can read more about it in {@link brave.sampler.RateLimitingSampler} + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class RateLimitingSampler extends Sampler { + + private final Sampler sampler; + + /** + * @param rate supplier of rate + */ + public RateLimitingSampler(Supplier rate) { + this.sampler = brave.sampler.RateLimitingSampler.create(rateLimit(rate)); + } + + private Integer rateLimit(Supplier rate) { + return rate.get() != null ? rate.get() : 0; + } + + @Override + public boolean isSampled(long traceId) { + return this.sampler.isSampled(traceId); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/DocTests.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/DocTests.java new file mode 100644 index 00000000..e7512769 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/DocTests.java @@ -0,0 +1,171 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave; + +import java.util.Optional; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import brave.Tracing; +import brave.handler.MutableSpan; +import brave.propagation.StrictCurrentTraceContext; +import brave.sampler.Sampler; +import brave.test.TestSpanHandler; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.brave.bridge.BraveBaggageManager; +import io.micrometer.tracing.brave.bridge.BraveCurrentTraceContext; +import io.micrometer.tracing.brave.bridge.BraveTracer; +import org.assertj.core.api.BDDAssertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * Test class to be embedded in the docs. They use Tracing's API + * with Brave as tracer implementation. + * + * @author Marcin Grzejszczak + */ +class DocTests { + + TestSpanHandler spans = new TestSpanHandler(); + + StrictCurrentTraceContext braveCurrentTraceContext = StrictCurrentTraceContext.create(); + + BraveCurrentTraceContext bridgeContext = new BraveCurrentTraceContext(this.braveCurrentTraceContext); + + Tracing tracing = Tracing.newBuilder().currentTraceContext(this.braveCurrentTraceContext) + .sampler(Sampler.ALWAYS_SAMPLE).addSpanHandler(this.spans).build(); + + brave.Tracer braveTracer = this.tracing.tracer(); + + Tracer tracer = new BraveTracer(this.braveTracer, this.bridgeContext, new BraveBaggageManager()); + + @BeforeEach + void setup() { + this.spans.clear(); + } + + @AfterEach + void close() { + this.tracing.close(); + this.braveCurrentTraceContext.close(); + } + + @Test + void should_create_a_span_with_tracer() { + String taxValue = "10"; + + // tag::manual_span_creation[] + // Start a span. If there was a span present in this thread it will become + // the `newSpan`'s parent. + Span newSpan = this.tracer.nextSpan().name("calculateTax"); + try (Tracer.SpanInScope ws = this.tracer.withSpan(newSpan.start())) { + // ... + // You can tag a span + newSpan.tag("taxValue", taxValue); + // ... + // You can log an event on a span + newSpan.event("taxCalculated"); + } + finally { + // Once done remember to end the span. This will allow collecting + // the span to send it to a distributed tracing system e.g. Zipkin + newSpan.end(); + } + // end::manual_span_creation[] + + then(this.spans).hasSize(1); + then(this.spans.get(0).name()).isEqualTo("calculateTax"); + then(this.spans.get(0).tags()).containsEntry("taxValue", "10"); + then(this.spans.get(0).annotations()).hasSize(1); + } + + @Test + void should_continue_a_span_with_tracer() throws Exception { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + String taxValue = "10"; + // tag::manual_span_continuation[] + Span spanFromThreadX = this.tracer.nextSpan().name("calculateTax"); + try (Tracer.SpanInScope ws = this.tracer.withSpan(spanFromThreadX.start())) { + executorService.submit(() -> { + // Pass the span from thread X + Span continuedSpan = spanFromThreadX; + // ... + // You can tag a span + continuedSpan.tag("taxValue", taxValue); + // ... + // You can log an event on a span + continuedSpan.event("taxCalculated"); + }).get(); + } + finally { + spanFromThreadX.end(); + } + // end::manual_span_continuation[] + + BDDAssertions.then(spans).hasSize(1); + BDDAssertions.then(spans.get(0).name()).isEqualTo("calculateTax"); + BDDAssertions.then(spans.get(0).tags()).containsEntry("taxValue", "10"); + BDDAssertions.then(spans.get(0).annotations()).hasSize(1); + executorService.shutdown(); + } + + @Test + void should_start_a_span_with_explicit_parent() throws Exception { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + String commissionValue = "10"; + Span initialSpan = this.tracer.nextSpan().name("calculateTax").start(); + + executorService.submit(() -> { + // tag::manual_span_joining[] + // let's assume that we're in a thread Y and we've received + // the `initialSpan` from thread X. `initialSpan` will be the parent + // of the `newSpan` + Span newSpan = null; + try (Tracer.SpanInScope ws = this.tracer.withSpan(initialSpan)) { + newSpan = this.tracer.nextSpan().name("calculateCommission"); + // ... + // You can tag a span + newSpan.tag("commissionValue", commissionValue); + // ... + // You can log an event on a span + newSpan.event("commissionCalculated"); + } + finally { + // Once done remember to end the span. This will allow collecting + // the span to send it to e.g. Zipkin. The tags and events set on the + // newSpan will not be present on the parent + if (newSpan != null) { + newSpan.end(); + } + } + // end::manual_span_joining[] + }).get(); + + Optional calculateTax = spans.spans().stream() + .filter(span -> span.name().equals("calculateCommission")).findFirst(); + BDDAssertions.then(calculateTax).isPresent(); + BDDAssertions.then(calculateTax.get().tags()).containsEntry("commissionValue", "10"); + BDDAssertions.then(calculateTax.get().annotations()).hasSize(1); + executorService.shutdown(); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/BraveHttpClientHandlerTests.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/BraveHttpClientHandlerTests.java new file mode 100644 index 00000000..ba4ea19b --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/BraveHttpClientHandlerTests.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import brave.Tracing; +import brave.http.HttpClientHandler; +import brave.http.HttpTracing; +import org.junit.jupiter.api.Test; + +class BraveHttpClientHandlerTests { + + @Test + void should_not_throw_exception_when_response_null() { + Tracing tracing = Tracing.newBuilder().build(); + brave.http.HttpClientHandler delegate = HttpClientHandler + .create(HttpTracing.newBuilder(tracing).build()); + BraveHttpClientHandler handler = new BraveHttpClientHandler(delegate); + + handler.handleReceive(null, new BraveSpan(tracing.currentTracer().nextSpan())); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/BraveTraceContextBuilderTests.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/BraveTraceContextBuilderTests.java new file mode 100644 index 00000000..acdc1d13 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/BraveTraceContextBuilderTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import io.micrometer.tracing.TraceContext; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.BDDAssertions.then; + +class BraveTraceContextBuilderTests { + + @Test + void should_set_trace_context_for_64_bit() { + BraveTraceContextBuilder builder = new BraveTraceContextBuilder(); + + TraceContext traceContext = builder.parentId("7c6239a5ad0a4287").spanId("caff89f7f0f229dd") + .traceId("596e1787feb11040").sampled(true).build(); + + then(traceContext.parentId()).isEqualTo("7c6239a5ad0a4287"); + then(traceContext.spanId()).isEqualTo("caff89f7f0f229dd"); + then(traceContext.traceId()).isEqualTo("596e1787feb11040"); + then(traceContext.sampled()).isTrue(); + } + + @Test + void should_set_trace_context_for_128_bit() { + BraveTraceContextBuilder builder = new BraveTraceContextBuilder(); + + TraceContext traceContext = builder.parentId("00000000000000007c6239a5ad0a4287") + .spanId("0000000000000000caff89f7f0f229dd").traceId("596e1787feb11040caff89f7f0f229dd").sampled(true) + .build(); + + then(traceContext.parentId()).isEqualTo("7c6239a5ad0a4287"); + then(traceContext.spanId()).isEqualTo("caff89f7f0f229dd"); + then(traceContext.traceId()).isEqualTo("596e1787feb11040caff89f7f0f229dd"); + then(traceContext.sampled()).isTrue(); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/W3CBaggagePropagatorTest.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/W3CBaggagePropagatorTest.java new file mode 100644 index 00000000..241b386a --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/W3CBaggagePropagatorTest.java @@ -0,0 +1,170 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import brave.baggage.BaggageField; +import brave.internal.baggage.BaggageFields; +import brave.propagation.Propagation; +import brave.propagation.TraceContext; +import brave.propagation.TraceContextOrSamplingFlags; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static java.util.Collections.singletonMap; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test taken from OpenTelemetry. + */ +class W3CBaggagePropagatorTest { + + W3CBaggagePropagator propagator = new W3CBaggagePropagator(new BraveBaggageManager(), Collections.emptyList()); + + @Test + void fields() { + assertThat(propagator.keys()).containsExactly("baggage"); + } + + @Test + void extract_noBaggageHeader() { + TraceContextOrSamplingFlags context = context(); + Map carrier = new HashMap<>(); + + TraceContextOrSamplingFlags contextWithBaggage = propagator.contextWithBaggage(carrier, context, Map::get); + + assertThat(contextWithBaggage).isEqualTo(context); + } + + @Test + void extract_emptyBaggageHeader() { + TraceContextOrSamplingFlags context = context(); + Map carrier = new HashMap<>(); + carrier.put("baggage", ""); + + TraceContextOrSamplingFlags contextWithBaggage = propagator.contextWithBaggage(carrier, context, Map::get); + + assertThat(contextWithBaggage).isEqualTo(context); + } + + @Test + void extract_singleEntry() { + TraceContextOrSamplingFlags context = context(); + Map carrier = new HashMap<>(); + carrier.put("baggage", "key=value"); + + TraceContextOrSamplingFlags contextWithBaggage = propagator.contextWithBaggage(carrier, context, Map::get); + + Map baggageEntries = BaggageField.getAllValues(contextWithBaggage); + assertThat(baggageEntries).hasSize(1).containsEntry("key", "value"); + } + + private TraceContextOrSamplingFlags context() { + return TraceContextOrSamplingFlags + .create(TraceContext.newBuilder().traceId(1L).spanId(2L).sampled(true).build()); + } + + @Test + void extract_multiEntry() { + TraceContextOrSamplingFlags context = context(); + Map carrier = new HashMap<>(); + carrier.put("baggage", "key1=value1,key2=value2"); + + TraceContextOrSamplingFlags contextWithBaggage = propagator.contextWithBaggage(carrier, context, Map::get); + + Map baggageEntries = BaggageField.getAllValues(contextWithBaggage); + assertThat(baggageEntries).hasSize(2).containsEntry("key1", "value1").containsEntry("key2", "value2"); + } + + @Test + void extract_duplicateKeys() { + TraceContextOrSamplingFlags context = context(); + Map carrier = new HashMap<>(); + carrier.put("baggage", "key=value1,key=value2"); + + TraceContextOrSamplingFlags contextWithBaggage = propagator.contextWithBaggage(carrier, context, Map::get); + + Map baggageEntries = BaggageField.getAllValues(contextWithBaggage); + assertThat(baggageEntries).hasSize(1).containsEntry("key", "value2"); + } + + @Test + void extract_fullComplexities() { + TraceContextOrSamplingFlags context = context(); + Map carrier = new HashMap<>(); + carrier.put("baggage", + "key1= value1; metadata-key = value; othermetadata, " + "key2 =value2 , key3 =\tvalue3 ; "); + + TraceContextOrSamplingFlags contextWithBaggage = propagator.contextWithBaggage(carrier, context, Map::get); + + Map baggageEntries = BaggageField.getAllValues(contextWithBaggage); + assertThat(baggageEntries).hasSize(3).containsEntry("key1", "value1").containsEntry("key2", "value2") + .containsEntry("key3", "value3"); + } + + /** + * It would be cool if we could replace this with a fuzzer to generate tons of crud + * data, to make sure we don't blow up with it. + */ + @Test + @Disabled("We don't support additional data") + void extract_invalidHeader() { + TraceContextOrSamplingFlags context = context(); + Map carrier = new HashMap<>(); + carrier.put("baggage", "key1= v;alsdf;-asdflkjasdf===asdlfkjadsf ,,a sdf9asdf-alue1; metadata-key = " + + "value; othermetadata, key2 =value2 , key3 =\tvalue3 ; "); + + TraceContextOrSamplingFlags contextWithBaggage = propagator.contextWithBaggage(carrier, context, Map::get); + + Map baggageEntries = BaggageField.getAllValues(contextWithBaggage); + assertThat(baggageEntries).isEmpty(); + } + + @Test + void inject_noBaggage() { + TraceContextOrSamplingFlags context = context(); + Map carrier = new HashMap<>(); + + propagator.injector((Propagation.Setter, String>) Map::put).inject(context.context(), + carrier); + + assertThat(carrier).isEmpty(); + } + + @Test + void inject() { + TraceContextOrSamplingFlags.Builder builder = context().toBuilder(); + BaggageField nometa = BaggageField.create("nometa"); + BaggageField meta = BaggageField.create("meta"); + builder.addExtra(BaggageFields.newFactory(Arrays.asList(nometa, meta), 10).create()); + TraceContextOrSamplingFlags context = builder.build(); + nometa.updateValue(context, "nometa-value"); + meta.updateValue(context, "meta-value;somemetadata; someother=foo"); + Map carrier = new HashMap<>(); + + propagator.injector((Propagation.Setter, String>) Map::put).inject(context.context(), + carrier); + + assertThat(carrier).containsExactlyInAnyOrderEntriesOf( + singletonMap("baggage", "nometa=nometa-value,meta=meta-value;somemetadata; someother=foo")); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/W3CPropagationTest.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/W3CPropagationTest.java new file mode 100644 index 00000000..b1965ce9 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/W3CPropagationTest.java @@ -0,0 +1,325 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.bridge; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import brave.baggage.BaggageField; +import brave.internal.baggage.BaggageFields; +import brave.propagation.Propagation; +import brave.propagation.TraceContext; +import brave.propagation.TraceContextOrSamplingFlags; +import io.micrometer.tracing.internal.EncodingUtils; +import org.junit.jupiter.api.Test; + +import static io.micrometer.tracing.brave.bridge.W3CPropagation.TRACE_PARENT; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +/** + * Test taken from OpenTelemetry. + */ +class W3CPropagationTest { + + private static final String TRACE_STATE = "tracestate"; + + private static final String TRACE_ID_BASE16 = "ff000000000000000000000000000041"; + + private static final String SPAN_ID_BASE16 = "ff00000000000041"; + + private static final boolean SAMPLED_TRACE_OPTIONS = true; + + private static final String TRACEPARENT_HEADER_SAMPLED = "00-" + TRACE_ID_BASE16 + "-" + SPAN_ID_BASE16 + "-01"; + + private static final String TRACEPARENT_HEADER_NOT_SAMPLED = "00-" + TRACE_ID_BASE16 + "-" + SPAN_ID_BASE16 + "-00"; + + private static final Propagation.Getter, String> getter = Map::get; + + private static final String TRACESTATE_NOT_DEFAULT_ENCODING_WITH_SPACES = "bar=baz , foo=bar"; + + private static final String TRACESTATE_HEADER = "sappp=CwAAmEnGj0gThK52TCXZ270X8nBhc3Nwb3J0LWFwcABQT1NU"; + + private final W3CPropagation w3CPropagation = new W3CPropagation(new BraveBaggageManager(), new ArrayList<>()); + + @Test + void inject_NullCarrierUsage() { + final Map carrier = new LinkedHashMap<>(); + TraceContext traceContext = sampledTraceContext().build(); + w3CPropagation.injector((ignored, key, value) -> carrier.put(key, value)).inject(traceContext, null); + assertThat(carrier).containsExactly(entry(TRACE_PARENT, TRACEPARENT_HEADER_SAMPLED)); + } + + private TraceContext.Builder sampledTraceContext() { + return sampledTraceContext("ff00000000000000", "0000000000000041", "ff00000000000041"); + } + + private TraceContext.Builder sampledTraceContext(String traceIdHigh, String traceId, String spanId) { + return TraceContext.newBuilder().sampled(SAMPLED_TRACE_OPTIONS) + .traceIdHigh(EncodingUtils.longFromBase16String(traceIdHigh)) + .traceId(EncodingUtils.longFromBase16String(traceId)) + .spanId(EncodingUtils.longFromBase16String(spanId)); + } + + @Test + void inject_SampledContext() { + Map carrier = new LinkedHashMap<>(); + TraceContext traceContext = sampledTraceContext().build(); + w3CPropagation.injector((ignored, key, value) -> carrier.put(key, value)).inject(traceContext, carrier); + assertThat(carrier).containsExactly(entry(TRACE_PARENT, TRACEPARENT_HEADER_SAMPLED)); + } + + @Test + void inject_tracestate() { + Map carrier = new LinkedHashMap<>(); + BaggageField traceStateField = BaggageField.create(TRACE_STATE); + BaggageField mybaggageField = BaggageField.create("mybaggage"); + TraceContext traceContext = sampledTraceContext() + .addExtra(BaggageFields.newFactory(Arrays.asList(traceStateField, mybaggageField), 2).create()).build(); + traceStateField.updateValue(traceContext, TRACESTATE_HEADER); + mybaggageField.updateValue(traceContext, "mybaggagevalue"); + w3CPropagation.injector((ignored, key, value) -> carrier.put(key, value)).inject(traceContext, carrier); + assertThat(carrier).containsEntry("baggage", "mybaggage=mybaggagevalue").containsEntry("tracestate", + "sappp=CwAAmEnGj0gThK52TCXZ270X8nBhc3Nwb3J0LWFwcABQT1NU"); + } + + @Test + void inject_NotSampledContext() { + Map carrier = new LinkedHashMap<>(); + TraceContext traceContext = notSampledTraceContext().build(); + w3CPropagation.injector((ignored, key, value) -> carrier.put(key, value)).inject(traceContext, carrier); + assertThat(carrier).containsExactly(entry(TRACE_PARENT, TRACEPARENT_HEADER_NOT_SAMPLED)); + } + + /** + * see: gh-1809 + */ + @Test + void inject_traceIdShouldBePaddedWithZeros() { + Map carrier = new LinkedHashMap<>(); + TraceContext traceContext = sampledTraceContext("0000000000000000", "123456789abcdef0", "123456789abcdef1") + .build(); + w3CPropagation.injector((ignored, key, value) -> carrier.put(key, value)).inject(traceContext, carrier); + assertThat(carrier) + .containsExactly(entry(TRACE_PARENT, "00-0000000000000000123456789abcdef0-123456789abcdef1-01")); + } + + @Test + void extract_Nothing() { + // Context remains untouched. + assertThat(w3CPropagation.extractor(getter).extract(Collections.emptyMap())) + .isSameAs(TraceContextOrSamplingFlags.EMPTY); + } + + @Test + void extract_SampledContext() { + Map carrier = new LinkedHashMap<>(); + carrier.put(TRACE_PARENT, TRACEPARENT_HEADER_SAMPLED); + assertThat(w3CPropagation.extractor(getter).extract(carrier).context()).isEqualTo(sharedTraceContext().build()); + } + + @Test + void extract_NullCarrier() { + Map carrier = new LinkedHashMap<>(); + carrier.put(TRACE_PARENT, TRACEPARENT_HEADER_SAMPLED); + assertThat(w3CPropagation.extractor((request, key) -> carrier.get(key)).extract(null).context()) + .isEqualTo(sharedTraceContext().build()); + } + + @Test + void extract_NotSampledContext() { + Map carrier = new LinkedHashMap<>(); + carrier.put(TRACE_PARENT, TRACEPARENT_HEADER_NOT_SAMPLED); + assertThat(w3CPropagation.extractor(getter).extract(carrier).context()) + .isEqualTo(notSampledTraceContext().shared(true).build()); + } + + @Test + void extract_NotSampledContext_NextVersion() { + Map carrier = new LinkedHashMap<>(); + carrier.put(TRACE_PARENT, "01-" + TRACE_ID_BASE16 + "-" + SPAN_ID_BASE16 + "-00-02"); + assertThat(w3CPropagation.extractor(getter).extract(carrier).context()).isEqualTo(sharedTraceContext().build()); + } + + @Test + void extract_NotSampledContext_EmptyTraceState() { + Map carrier = new LinkedHashMap<>(); + carrier.put(TRACE_PARENT, TRACEPARENT_HEADER_NOT_SAMPLED); + carrier.put(TRACE_STATE, ""); + assertThat(w3CPropagation.extractor(getter).extract(carrier).context()) + .isEqualTo(notSampledTraceContext().shared(true).build()); + } + + private TraceContext.Builder notSampledTraceContext() { + return sampledTraceContext().sampled(false); + } + + @Test + void extract_NotSampledContext_TraceStateWithSpaces() { + Map carrier = new LinkedHashMap<>(); + carrier.put(TRACE_PARENT, TRACEPARENT_HEADER_NOT_SAMPLED); + carrier.put(TRACE_STATE, TRACESTATE_NOT_DEFAULT_ENCODING_WITH_SPACES); + assertThat(w3CPropagation.extractor(getter).extract(carrier).context()) + .isEqualTo(sharedTraceContext().sampled(false).build()); + } + + @Test + void extract_tracestate_shouldNotBePartOfBaggage() { + Map carrier = new LinkedHashMap<>(); + carrier.put(TRACE_PARENT, TRACEPARENT_HEADER_NOT_SAMPLED); + carrier.put(TRACE_STATE, TRACESTATE_HEADER); + carrier.put("baggage", "mybaggage=mybaggagevalue"); + + TraceContext context = w3CPropagation.extractor(getter).extract(carrier).context(); + + assertThat(BaggageField.getByName(context, TRACE_STATE)).isNotNull(); + assertThat(BaggageField.getByName(context, "mybaggage").getValue(context)).isEqualTo("mybaggagevalue"); + } + + @Test + void extract_EmptyHeader() { + Map invalidHeaders = new LinkedHashMap<>(); + invalidHeaders.put(TRACE_PARENT, ""); + assertThat(w3CPropagation.extractor(getter).extract(invalidHeaders)) + .isSameAs(TraceContextOrSamplingFlags.EMPTY); + } + + @Test + void extract_InvalidTraceId() { + Map invalidHeaders = new LinkedHashMap<>(); + invalidHeaders.put(TRACE_PARENT, "00-" + "abcdefghijklmnopabcdefghijklmnop" + "-" + SPAN_ID_BASE16 + "-01"); + assertThat(w3CPropagation.extractor(getter).extract(invalidHeaders)) + .isSameAs(TraceContextOrSamplingFlags.EMPTY); + } + + @Test + void extract_InvalidTraceId_Size() { + Map invalidHeaders = new LinkedHashMap<>(); + invalidHeaders.put(TRACE_PARENT, "00-" + TRACE_ID_BASE16 + "00-" + SPAN_ID_BASE16 + "-01"); + assertThat(w3CPropagation.extractor(getter).extract(invalidHeaders)) + .isSameAs(TraceContextOrSamplingFlags.EMPTY); + } + + @Test + void extract_InvalidSpanId() { + Map invalidHeaders = new HashMap<>(); + invalidHeaders.put(TRACE_PARENT, "00-" + TRACE_ID_BASE16 + "-" + "abcdefghijklmnop" + "-01"); + assertThat(w3CPropagation.extractor(getter).extract(invalidHeaders)) + .isSameAs(TraceContextOrSamplingFlags.EMPTY); + } + + @Test + void extract_InvalidSpanId_Size() { + Map invalidHeaders = new HashMap<>(); + invalidHeaders.put(TRACE_PARENT, "00-" + TRACE_ID_BASE16 + "-" + SPAN_ID_BASE16 + "00-01"); + assertThat(w3CPropagation.extractor(getter).extract(invalidHeaders)) + .isSameAs(TraceContextOrSamplingFlags.EMPTY); + } + + @Test + void extract_InvalidTraceFlags() { + Map invalidHeaders = new HashMap<>(); + invalidHeaders.put(TRACE_PARENT, "00-" + TRACE_ID_BASE16 + "-" + SPAN_ID_BASE16 + "-gh"); + assertThat(w3CPropagation.extractor(getter).extract(invalidHeaders)) + .isSameAs(TraceContextOrSamplingFlags.EMPTY); + } + + @Test + void extract_InvalidTraceFlags_Size() { + Map invalidHeaders = new HashMap<>(); + invalidHeaders.put(TRACE_PARENT, "00-" + TRACE_ID_BASE16 + "-" + SPAN_ID_BASE16 + "-0100"); + assertThat(w3CPropagation.extractor(getter).extract(invalidHeaders)) + .isSameAs(TraceContextOrSamplingFlags.EMPTY); + } + + @Test + void extract_InvalidTracestate_EntriesDelimiter() { + Map invalidHeaders = new HashMap<>(); + invalidHeaders.put(TRACE_PARENT, "00-" + TRACE_ID_BASE16 + "-" + SPAN_ID_BASE16 + "-01"); + invalidHeaders.put(TRACE_STATE, "foo=bar;test=test"); + assertThat(w3CPropagation.extractor(getter).extract(invalidHeaders).context()) + .isEqualTo(sharedTraceContext().build()); + } + + private TraceContext.Builder sharedTraceContext() { + return sampledTraceContext().shared(true); + } + + @Test + void extract_InvalidTracestate_KeyValueDelimiter() { + Map invalidHeaders = new HashMap<>(); + invalidHeaders.put(TRACE_PARENT, "00-" + TRACE_ID_BASE16 + "-" + SPAN_ID_BASE16 + "-01"); + invalidHeaders.put(TRACE_STATE, "foo=bar,test-test"); + assertThat(w3CPropagation.extractor(getter).extract(invalidHeaders).context()) + .isEqualTo(sharedTraceContext().build()); + } + + @Test + void extract_InvalidTracestate_OneString() { + Map invalidHeaders = new HashMap<>(); + invalidHeaders.put(TRACE_PARENT, "00-" + TRACE_ID_BASE16 + "-" + SPAN_ID_BASE16 + "-01"); + invalidHeaders.put(TRACE_STATE, "test-test"); + assertThat(w3CPropagation.extractor(getter).extract(invalidHeaders).context()) + .isEqualTo(sampledTraceContext().shared(true).build()); + } + + @Test + void extract_InvalidVersion_ff() { + Map invalidHeaders = new HashMap<>(); + invalidHeaders.put(TRACE_PARENT, "ff-" + TRACE_ID_BASE16 + "-" + SPAN_ID_BASE16 + "-01"); + assertThat(w3CPropagation.extractor(getter).extract(invalidHeaders)) + .isSameAs(TraceContextOrSamplingFlags.EMPTY); + } + + @Test + void extract_InvalidTraceparent_extraTrailing() { + Map invalidHeaders = new HashMap<>(); + invalidHeaders.put(TRACE_PARENT, "00-" + TRACE_ID_BASE16 + "-" + SPAN_ID_BASE16 + "-00-01"); + assertThat(w3CPropagation.extractor(getter).extract(invalidHeaders)) + .isSameAs(TraceContextOrSamplingFlags.EMPTY); + } + + @Test + void extract_ValidTraceparent_nextVersion_extraTrailing() { + Map invalidHeaders = new HashMap<>(); + invalidHeaders.put(TRACE_PARENT, "01-" + TRACE_ID_BASE16 + "-" + SPAN_ID_BASE16 + "-00-01"); + assertThat(w3CPropagation.extractor(getter).extract(invalidHeaders).context()) + .isEqualTo(sharedTraceContext().build()); + } + + @Test + void fieldsList() { + assertThat(w3CPropagation.keys()).containsExactly(TRACE_PARENT, TRACE_STATE); + } + + @Test + void headerNames() { + assertThat(TRACE_PARENT).isEqualTo("traceparent"); + assertThat(TRACE_STATE).isEqualTo("tracestate"); + } + + @Test + void extract_emptyCarrier() { + Map emptyHeaders = new HashMap<>(); + assertThat(w3CPropagation.extractor(getter).extract(emptyHeaders)).isSameAs(TraceContextOrSamplingFlags.EMPTY); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/sampler/ProbabilityBasedSamplerTests.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/sampler/ProbabilityBasedSamplerTests.java new file mode 100644 index 00000000..3e7f6f42 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/sampler/ProbabilityBasedSamplerTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.brave.sampler; + +import java.util.Random; +import java.util.function.Supplier; + +import brave.sampler.Sampler; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +class ProbabilityBasedSamplerTests { + + private static Random RANDOM = new Random(); + + @Test + void should_pass_all_samples_when_config_has_1_probability() throws Exception { + for (int i = 0; i < 10; i++) { + then(new ProbabilityBasedSampler(() -> 1f).isSampled(RANDOM.nextLong())).isTrue(); + } + + } + + @Test + void should_reject_all_samples_when_config_has_0_probability() throws Exception { + for (int i = 0; i < 10; i++) { + then(new ProbabilityBasedSampler(() -> 0f).isSampled(RANDOM.nextLong())).isFalse(); + } + } + + @Test + void should_pass_given_percent_of_samples() throws Exception { + int numberOfIterations = 1000; + float probability = 1f; + + int numberOfSampledElements = countNumberOfSampledElements(numberOfIterations, () -> probability); + + then(numberOfSampledElements).isEqualTo((int) (numberOfIterations * probability)); + } + + @Test + void should_pass_given_percent_of_samples_with_fractional_element() throws Exception { + int numberOfIterations = 1000; + float probability = 0.35f; + + int numberOfSampledElements = countNumberOfSampledElements(numberOfIterations, () -> probability); + + int threshold = (int) (numberOfIterations * probability); + then(numberOfSampledElements).isEqualTo(threshold); + } + + @Test + void should_fail_given_no_probability() { + assertThatThrownBy(() -> new ProbabilityBasedSampler(null)).isInstanceOf(IllegalArgumentException.class) + .hasMessage("probability property is required for ProbabilityBasedSampler"); + } + + private int countNumberOfSampledElements(int numberOfIterations, Supplier probability) { + Sampler sampler = new ProbabilityBasedSampler(probability); + int passedCounter = 0; + for (int i = 0; i < numberOfIterations; i++) { + boolean passed = sampler.isSampled(RANDOM.nextLong()); + passedCounter = passedCounter + (passed ? 1 : 0); + } + return passedCounter; + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/internal/SpanNameUtilTests.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/internal/SpanNameUtilTests.java new file mode 100644 index 00000000..3fe6efd7 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/internal/SpanNameUtilTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.internal; + +import org.assertj.core.api.BDDAssertions; +import org.junit.jupiter.api.Test; + +class SpanNameUtilTests { + + @Test + void should_convert_a_name_in_hyphen_based_notation() throws Exception { + BDDAssertions.then(SpanNameUtil.toLowerHyphen("aMethodNameInCamelCaseNotation")) + .isEqualTo("a-method-name-in-camel-case-notation"); + } + + @Test + void should_convert_a_class_name_in_hyphen_based_notation() throws Exception { + BDDAssertions.then(SpanNameUtil.toLowerHyphen("MySuperClassName")).isEqualTo("my-super-class-name"); + } + + @Test + void should_not_shorten_a_name_that_is_below_max_threshold() throws Exception { + BDDAssertions.then(SpanNameUtil.shorten("someName")).isEqualTo("someName"); + } + + @Test + void should_not_shorten_a_name_that_is_null() throws Exception { + BDDAssertions.then(SpanNameUtil.shorten(null)).isNull(); + } + + @Test + void should_shorten_a_name_that_is_above_max_threshold() throws Exception { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 60; i++) { + sb.append("a"); + } + BDDAssertions.then(SpanNameUtil.shorten(sb.toString()).length()).isEqualTo(SpanNameUtil.MAX_NAME_LENGTH); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/build.gradle b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/build.gradle new file mode 100644 index 00000000..4f09db7c --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'idea' +} + +dependencies { + api project(':micrometer-tracing') + api 'org.slf4j:slf4j-api' + api 'io.micrometer:micrometer-core' + + implementation("io.opentelemetry:opentelemetry-api") + implementation("io.opentelemetry:opentelemetry-api-metrics") + implementation("io.opentelemetry:opentelemetry-extension-aws") + implementation("io.opentelemetry:opentelemetry-semconv") + implementation("io.opentelemetry:opentelemetry-sdk-common") + implementation("io.opentelemetry:opentelemetry-sdk-trace") + implementation("io.opentelemetry:opentelemetry-sdk") + implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api") + optionalApi("io.opentelemetry:opentelemetry-exporter-logging") + implementation("io.opentelemetry:opentelemetry-extension-trace-propagators") + optionalApi("io.opentelemetry:opentelemetry-opentracing-shim") + + testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation 'org.assertj:assertj-core' + testImplementation 'org.mockito:mockito-core' +} + diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/ArrayListSpanProcessor.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/ArrayListSpanProcessor.java new file mode 100644 index 00000000..2eb379d2 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/ArrayListSpanProcessor.java @@ -0,0 +1,105 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.Collection; +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; + +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.ReadWriteSpan; +import io.opentelemetry.sdk.trace.ReadableSpan; +import io.opentelemetry.sdk.trace.SpanProcessor; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; + +import static java.util.concurrent.TimeUnit.SECONDS; + +/** + * Stores spans in a queue. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class ArrayListSpanProcessor implements SpanProcessor, SpanExporter { + + Queue spans = new LinkedBlockingQueue<>(50); + + @Override + public void onStart(Context parent, ReadWriteSpan span) { + + } + + @Override + public boolean isStartRequired() { + return false; + } + + @Override + public void onEnd(ReadableSpan span) { + this.spans.add(span.toSpanData()); + } + + @Override + public boolean isEndRequired() { + return true; + } + + @Override + public CompletableResultCode export(Collection spans) { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode forceFlush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public void close() { + shutdown().join(10, SECONDS); + } + + public SpanData takeLocalSpan() { + return this.spans.poll(); + } + + public Queue spans() { + return this.spans; + } + + public void clear() { + this.spans.clear(); + } + + @Override + public String toString() { + return "ArrayListSpanProcessor{" + "spans=" + spans + '}'; + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/BaggageTaggingSpanProcessor.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/BaggageTaggingSpanProcessor.java new file mode 100644 index 00000000..d5e1b06f --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/BaggageTaggingSpanProcessor.java @@ -0,0 +1,68 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.List; +import java.util.Map; + +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.ReadWriteSpan; +import io.opentelemetry.sdk.trace.ReadableSpan; +import io.opentelemetry.sdk.trace.SpanProcessor; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static java.util.stream.Collectors.toMap; + +public class BaggageTaggingSpanProcessor implements SpanProcessor { + + private final Map> tagsToApply; + + public BaggageTaggingSpanProcessor(List tagsToApply) { + this.tagsToApply = tagsToApply.stream().map(tag -> stringKey(tag)) + .collect(toMap(AttributeKey::getKey, key -> key)); + } + + @Override + public void onStart(Context context, ReadWriteSpan readWriteSpan) { + Baggage baggage = Baggage.fromContext(context); + + baggage.forEach((key, baggageEntry) -> { + AttributeKey attributeKey = tagsToApply.get(key); + if (attributeKey != null) { + readWriteSpan.setAttribute(attributeKey, baggageEntry.getValue()); + } + }); + } + + @Override + public boolean isStartRequired() { + return true; + } + + @Override + public void onEnd(ReadableSpan readableSpan) { + // no-op + } + + @Override + public boolean isEndRequired() { + return false; + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/BigendianEncoding.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/BigendianEncoding.java new file mode 100644 index 00000000..1fde3eff --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/BigendianEncoding.java @@ -0,0 +1,86 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.Arrays; + +import io.opentelemetry.api.internal.Utils; + +/** + * Copied from io.opentelemetry.api.trace.BigendianEncoding. + */ +final class BigendianEncoding { + + static final int LONG_BYTES = Long.SIZE / Byte.SIZE; + + static final int BYTE_BASE16 = 2; + + static final int LONG_BASE16 = BYTE_BASE16 * LONG_BYTES; + + private static final String ALPHABET = "0123456789abcdef"; + + private static final int ASCII_CHARACTERS = 128; + + private static final byte[] DECODING = buildDecodingArray(); + + private BigendianEncoding() { + } + + private static byte[] buildDecodingArray() { + byte[] decoding = new byte[ASCII_CHARACTERS]; + Arrays.fill(decoding, (byte) -1); + for (int i = 0; i < ALPHABET.length(); i++) { + char c = ALPHABET.charAt(i); + decoding[c] = (byte) i; + } + return decoding; + } + + /** + * Returns the {@code long} value whose base16 representation is stored in the first + * 16 chars of {@code chars} starting from the {@code offset}. + * @param chars the base16 representation of the {@code long}. + */ + static long longFromBase16String(CharSequence chars) { + return longFromBase16String(chars, 0); + } + + /** + * Returns the {@code long} value whose base16 representation is stored in the first + * 16 chars of {@code chars} starting from the {@code offset}. + * @param chars the base16 representation of the {@code long}. + */ + static long longFromBase16String(CharSequence chars, int offset) { + Utils.checkArgument(chars.length() >= offset + LONG_BASE16, "chars too small"); + return (decodeByte(chars.charAt(offset), chars.charAt(offset + 1)) & 0xFFL) << 56 + | (decodeByte(chars.charAt(offset + 2), chars.charAt(offset + 3)) & 0xFFL) << 48 + | (decodeByte(chars.charAt(offset + 4), chars.charAt(offset + 5)) & 0xFFL) << 40 + | (decodeByte(chars.charAt(offset + 6), chars.charAt(offset + 7)) & 0xFFL) << 32 + | (decodeByte(chars.charAt(offset + 8), chars.charAt(offset + 9)) & 0xFFL) << 24 + | (decodeByte(chars.charAt(offset + 10), chars.charAt(offset + 11)) & 0xFFL) << 16 + | (decodeByte(chars.charAt(offset + 12), chars.charAt(offset + 13)) & 0xFFL) << 8 + | (decodeByte(chars.charAt(offset + 14), chars.charAt(offset + 15)) & 0xFFL); + } + + private static byte decodeByte(char hi, char lo) { + Utils.checkArgument(lo < ASCII_CHARACTERS && DECODING[lo] != -1, "invalid character " + lo); + Utils.checkArgument(hi < ASCII_CHARACTERS && DECODING[hi] != -1, "invalid character " + hi); + int decoded = DECODING[hi] << 4 | DECODING[lo]; + return (byte) decoded; + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/CompositeSpanExporter.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/CompositeSpanExporter.java new file mode 100644 index 00000000..5eacb142 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/CompositeSpanExporter.java @@ -0,0 +1,77 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import io.micrometer.tracing.exporter.SpanFilter; +import io.micrometer.tracing.exporter.SpanReporter; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; + + +/** + * Composes multiple {@link SpanFilter} into a single {@link SpanExporter}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class CompositeSpanExporter implements io.opentelemetry.sdk.trace.export.SpanExporter { + + private final io.opentelemetry.sdk.trace.export.SpanExporter delegate; + + private final List filters; + + private final List reporters; + + public CompositeSpanExporter(SpanExporter delegate, List filters, List reporters) { + this.delegate = delegate; + this.filters = filters; + this.reporters = reporters; + } + + @Override + public CompletableResultCode export(Collection spans) { + return this.delegate.export(spans.stream().filter(this::shouldProcess).map(spanData -> { + this.reporters.forEach(reporter -> reporter.report(OtelFinishedSpan.fromOtel(spanData))); + return spanData; + }).collect(Collectors.toList())); + } + + private boolean shouldProcess(SpanData span) { + for (SpanFilter filter : this.filters) { + if (!filter.isExportable(OtelFinishedSpan.fromOtel(span))) { + return false; + } + } + return true; + } + + @Override + public CompletableResultCode flush() { + return this.delegate.flush(); + } + + @Override + public CompletableResultCode shutdown() { + return this.delegate.shutdown(); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/DefaultHttpClientAttributesExtractor.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/DefaultHttpClientAttributesExtractor.java new file mode 100644 index 00000000..86829205 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/DefaultHttpClientAttributesExtractor.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.Collections; +import java.util.List; + +import io.micrometer.core.instrument.transport.http.HttpClientRequest; +import io.micrometer.core.instrument.transport.http.HttpClientResponse; +import io.micrometer.tracing.lang.Nullable; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; + +/** + * Extracts OpenTelemetry http semantic attributes value for client http spans. + * + * @author Nikita Salnikov-Tarnovski + */ +public class DefaultHttpClientAttributesExtractor + extends HttpClientAttributesExtractor { + + @Nullable + @Override + protected String url(HttpClientRequest httpClientRequest) { + return httpClientRequest.url(); + } + + @Nullable + @Override + protected String flavor(HttpClientRequest httpClientRequest, @Nullable HttpClientResponse httpClientResponse) { + return null; + } + + @Override + protected String method(HttpClientRequest httpClientRequest) { + return httpClientRequest.method(); + } + + @Override + protected List requestHeader(HttpClientRequest httpClientRequest, String name) { + String value = httpClientRequest.header(name); + return value == null ? Collections.emptyList() : Collections.singletonList(value); + } + + @Nullable + @Override + protected Long requestContentLength(HttpClientRequest httpClientRequest, + @Nullable HttpClientResponse httpClientResponse) { + return null; + } + + @Nullable + @Override + protected Long requestContentLengthUncompressed(HttpClientRequest httpClientRequest, + @Nullable HttpClientResponse httpClientResponse) { + return null; + } + + @Override + protected Integer statusCode(HttpClientRequest httpClientRequest, HttpClientResponse httpClientResponse) { + return httpClientResponse.statusCode(); + } + + @Nullable + @Override + protected Long responseContentLength(HttpClientRequest httpClientRequest, HttpClientResponse httpClientResponse) { + return null; + } + + @Nullable + @Override + protected Long responseContentLengthUncompressed(HttpClientRequest httpClientRequest, + HttpClientResponse httpClientResponse) { + return null; + } + + @Override + protected List responseHeader(HttpClientRequest httpClientRequest, HttpClientResponse httpClientResponse, + String name) { + String value = httpClientResponse.header(name); + return value == null ? Collections.emptyList() : Collections.singletonList(value); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/DefaultHttpServerAttributesExtractor.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/DefaultHttpServerAttributesExtractor.java new file mode 100644 index 00000000..3438cf62 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/DefaultHttpServerAttributesExtractor.java @@ -0,0 +1,142 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.net.URI; +import java.util.Collections; +import java.util.List; + +import io.micrometer.core.instrument.transport.http.HttpServerRequest; +import io.micrometer.core.instrument.transport.http.HttpServerResponse; +import io.micrometer.tracing.lang.Nullable; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor; + +/** + * Extracts OpenTelemetry http semantic attributes value for server http spans. + * + * @author Nikita Salnikov-Tarnovski + */ +public class DefaultHttpServerAttributesExtractor + extends HttpServerAttributesExtractor { + + @Nullable + @Override + protected String flavor(HttpServerRequest httpServerRequest) { + return null; + } + + @Nullable + @Override + protected String target(HttpServerRequest httpServerRequest) { + URI uri = toUri(httpServerRequest); + if (uri == null) { + return null; + } + return uri.getPath() + queryPart(uri); + } + + private URI toUri(HttpServerRequest request) { + String url = request.url(); + return url == null ? null : URI.create(url); + } + + private String queryPart(URI uri) { + String query = uri.getQuery(); + return query != null ? "?" + query : ""; + } + + @Nullable + @Override + protected String route(HttpServerRequest httpServerRequest) { + return httpServerRequest.route(); + } + + @Nullable + @Override + protected String scheme(HttpServerRequest httpServerRequest) { + String url = httpServerRequest.url(); + if (url == null) { + return null; + } + if (url.startsWith("https:")) { + return "https"; + } + if (url.startsWith("http:")) { + return "http"; + } + return null; + } + + @Nullable + @Override + protected String serverName(HttpServerRequest httpServerRequest, @Nullable HttpServerResponse httpServerResponse) { + return null; + } + + @Nullable + @Override + protected String method(HttpServerRequest httpServerRequest) { + return httpServerRequest.method(); + } + + @Override + protected List requestHeader(HttpServerRequest httpServerRequest, String name) { + String value = httpServerRequest.header(name); + return value == null ? Collections.emptyList() : Collections.singletonList(value); + } + + @Nullable + @Override + protected Long requestContentLength(HttpServerRequest httpServerRequest, + @Nullable HttpServerResponse httpServerResponse) { + return null; + } + + @Nullable + @Override + protected Long requestContentLengthUncompressed(HttpServerRequest httpServerRequest, + @Nullable HttpServerResponse httpServerResponse) { + return null; + } + + @Nullable + @Override + protected Integer statusCode(HttpServerRequest httpServerRequest, HttpServerResponse httpServerResponse) { + return httpServerResponse.statusCode(); + } + + @Nullable + @Override + protected Long responseContentLength(HttpServerRequest httpServerRequest, HttpServerResponse httpServerResponse) { + return null; + } + + @Nullable + @Override + protected Long responseContentLengthUncompressed(HttpServerRequest httpServerRequest, + HttpServerResponse httpServerResponse) { + return null; + } + + @Override + protected List responseHeader(HttpServerRequest httpServerRequest, HttpServerResponse httpServerResponse, + String name) { + String value = httpServerResponse.header(name); + return value == null ? Collections.emptyList() : Collections.singletonList(value); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/EventListener.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/EventListener.java new file mode 100644 index 00000000..49226185 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/EventListener.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +/** + * Listener to events. + * + * @since 1.0.0 + */ +public interface EventListener { + /** + * Processes an event. + * + * @param event event to process + */ + void onEvent(Object event); +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/EventPublishingContextWrapper.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/EventPublishingContextWrapper.java new file mode 100644 index 00000000..82a7fc7a --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/EventPublishingContextWrapper.java @@ -0,0 +1,125 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.function.Function; + +import io.micrometer.tracing.lang.Nullable; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextStorage; + +public final class EventPublishingContextWrapper implements Function { + + private final OtelTracer.EventPublisher publisher; + + public EventPublishingContextWrapper(OtelTracer.EventPublisher publisher) { + this.publisher = publisher; + } + + @Override + public ContextStorage apply(ContextStorage contextStorage) { + return new ContextStorage() { + @Override + public io.opentelemetry.context.Scope attach(Context context) { + Context currentContext = Context.current(); + io.opentelemetry.context.Scope scope = contextStorage.attach(context); + if (scope == io.opentelemetry.context.Scope.noop()) { + return scope; + } + publisher.publishEvent(new ScopeAttachedEvent(context)); + return () -> { + scope.close(); + publisher.publishEvent(new ScopeClosedEvent()); + publisher.publishEvent(new ScopeRestoredEvent(currentContext)); + }; + } + + @Override + public Context current() { + return contextStorage.current(); + } + }; + } + + public static class ScopeAttachedEvent { + + /** + * Context corresponding to the attached scope. Might be {@code null}. + */ + final Context context; + + /** + * Create a new event. + * @param context corresponding otel context + */ + public ScopeAttachedEvent(@Nullable Context context) { + this.context = context; + } + + Span getSpan() { + return Span.fromContextOrNull(context); + } + + Baggage getBaggage() { + return Baggage.fromContextOrNull(context); + } + + @Override + public String toString() { + return "ScopeAttached{context: [span: " + getSpan() + "] [baggage: " + getBaggage() + "]}"; + } + + } + + public static class ScopeClosedEvent { + + } + + public static class ScopeRestoredEvent { + + /** + * {@link Context} corresponding to the scope being restored. Might be + * {@code null}. + */ + final Context context; + + /** + * Create a new event. + * @param context corresponding otel context + */ + public ScopeRestoredEvent(@Nullable Context context) { + this.context = context; + } + + Span getSpan() { + return Span.fromContextOrNull(context); + } + + Baggage getBaggage() { + return Baggage.fromContextOrNull(context); + } + + @Override + public String toString() { + return "ScopeRestored{context: [span: " + getSpan() + "] [baggage: " + getBaggage() + "]}"; + } + + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/HttpRequestNetClientAttributesExtractor.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/HttpRequestNetClientAttributesExtractor.java new file mode 100644 index 00000000..81bdf736 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/HttpRequestNetClientAttributesExtractor.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import io.micrometer.core.instrument.transport.http.HttpRequest; +import io.micrometer.core.instrument.transport.http.HttpResponse; +import io.micrometer.tracing.lang.Nullable; +import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesExtractor; + +/** + * Extracts OpenTelemetry network semantic attributes value for client http spans. + * + * @author Nikita Salnikov-Tarnovski + */ +class HttpRequestNetClientAttributesExtractor extends NetClientAttributesExtractor { + + @Nullable + @Override + public String transport(HttpRequest httpRequest, @Nullable HttpResponse httpResponse) { + return null; + } + + @Nullable + @Override + public String peerName(HttpRequest httpRequest, @Nullable HttpResponse httpResponse) { + return null; + } + + @Override + public Integer peerPort(HttpRequest httpRequest, @Nullable HttpResponse httpResponse) { + return httpRequest == null ? null : httpRequest.remotePort(); + } + + @Nullable + @Override + public String peerIp(HttpRequest httpRequest, @Nullable HttpResponse httpResponse) { + return httpRequest == null ? null : httpRequest.remoteIp(); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/HttpRequestNetServerAttributesExtractor.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/HttpRequestNetServerAttributesExtractor.java new file mode 100644 index 00000000..6fbbcd82 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/HttpRequestNetServerAttributesExtractor.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import io.micrometer.core.instrument.transport.http.HttpRequest; +import io.micrometer.core.instrument.transport.http.HttpResponse; +import io.micrometer.tracing.lang.Nullable; +import io.opentelemetry.instrumentation.api.instrumenter.net.NetServerAttributesExtractor; + +/** + * Extracts OpenTelemetry network semantic attributes value for server http spans. + * + * @author Nikita Salnikov-Tarnovski + */ +class HttpRequestNetServerAttributesExtractor extends NetServerAttributesExtractor { + + @Nullable + @Override + public String transport(HttpRequest httpRequest) { + return null; + } + + @Nullable + @Override + public String peerName(HttpRequest httpRequest) { + return null; + } + + @Override + public Integer peerPort(HttpRequest httpRequest) { + return httpRequest.remotePort(); + } + + @Nullable + @Override + public String peerIp(HttpRequest httpRequest) { + return httpRequest.remoteIp(); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelBaggageInScope.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelBaggageInScope.java new file mode 100644 index 00000000..5b7e92c3 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelBaggageInScope.java @@ -0,0 +1,140 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import io.micrometer.tracing.BaggageInScope; +import io.micrometer.tracing.CurrentTraceContext; +import io.micrometer.tracing.TraceContext; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; + + +/** + * OpenTelemetry implementation of a {@link BaggageInScope}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +class OtelBaggageInScope implements BaggageInScope { + + private final OtelBaggageManager otelBaggageManager; + + private final CurrentTraceContext currentTraceContext; + + private final List tagFields; + + private final AtomicReference entry = new AtomicReference<>(); + + private final AtomicReference scope = new AtomicReference<>(); + + OtelBaggageInScope(OtelBaggageManager otelBaggageManager, CurrentTraceContext currentTraceContext, + List tagFields, Entry entry) { + this.otelBaggageManager = otelBaggageManager; + this.currentTraceContext = currentTraceContext; + this.tagFields = tagFields; + this.entry.set(entry); + } + + @Override + public String name() { + return entry().getKey(); + } + + @Override + public String get() { + return this.otelBaggageManager.currentBaggage().getEntryValue(entry().getKey()); + } + + @Override + public String get(TraceContext traceContext) { + Entry entry = this.otelBaggageManager.getEntry((OtelTraceContext) traceContext, entry().getKey()); + if (entry == null) { + return null; + } + return entry.getValue(); + } + + @Override + public BaggageInScope set(String value) { + return doSet(this.currentTraceContext.context(), value); + } + + private BaggageInScope doSet(TraceContext context, String value) { + Context current = Context.current(); + Span currentSpan = Span.current(); + io.opentelemetry.api.baggage.Baggage baggage; + if (context != null) { + OtelTraceContext ctx = (OtelTraceContext) context; + Context storedCtx = ctx.context(); + Baggage fromContext = Baggage.fromContext(storedCtx); + + BaggageBuilder newBaggageBuilder = fromContext.toBuilder(); + Baggage.current().forEach((key, baggageEntry) -> newBaggageBuilder.put(key, baggageEntry.getValue(), + baggageEntry.getMetadata())); + + baggage = newBaggageBuilder.put(entry().getKey(), value, entry().getMetadata()).build(); + current = current.with(baggage); + ctx.updateContext(current); + } + else { + baggage = Baggage.builder().put(entry().getKey(), value, entry().getMetadata()).build(); + } + Context withBaggage = current.with(baggage); + this.scope.set(withBaggage.makeCurrent()); + if (this.tagFields.stream().map(String::toLowerCase).anyMatch(s -> s.equals(entry().getKey()))) { + currentSpan.setAttribute(entry().getKey(), value); + } + Entry previous = entry(); + this.entry.set(new Entry(previous.getKey(), value, previous.getMetadata())); + return this; + } + + private Entry entry() { + return this.entry.get(); + } + + @Override + public BaggageInScope set(TraceContext traceContext, String value) { + return doSet(traceContext, value); + } + + @Override + public BaggageInScope makeCurrent() { + close(); + Entry entry = entry(); + Scope scope = Baggage.builder().put(entry.getKey(), entry.getValue(), entry.getMetadata()).build() + .makeCurrent(); + this.scope.set(scope); + return this; + } + + @Override + public void close() { + Scope scope = this.scope.get(); + if (scope != null) { + this.scope.set(null); + scope.close(); + } + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelBaggageManager.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelBaggageManager.java new file mode 100644 index 00000000..67ca158e --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelBaggageManager.java @@ -0,0 +1,280 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; + +import io.micrometer.tracing.BaggageInScope; +import io.micrometer.tracing.BaggageManager; +import io.micrometer.tracing.CurrentTraceContext; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.Tracer; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageBuilder; +import io.opentelemetry.api.baggage.BaggageEntry; +import io.opentelemetry.api.baggage.BaggageEntryMetadata; +import io.opentelemetry.context.Context; + +import static java.util.Collections.unmodifiableCollection; +import static java.util.Collections.unmodifiableMap; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; + +/** + * OpenTelemetry implementation of a {@link BaggageManager}. Doesn't implement an + * interface cause {@link Tracer} already implements it. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class OtelBaggageManager implements BaggageManager { + + private final CurrentTraceContext currentTraceContext; + + private final List remoteFields; + + private final List tagFields; + + public OtelBaggageManager(CurrentTraceContext currentTraceContext, List remoteFields, + List tagFields) { + this.currentTraceContext = currentTraceContext; + this.remoteFields = remoteFields; + this.tagFields = tagFields; + } + + @Override + public Map getAllBaggage() { + Map baggage = new HashMap<>(); + currentBaggage().getEntries().forEach(entry -> baggage.put(entry.getKey(), entry.getValue())); + return baggage; + } + + CompositeBaggage currentBaggage() { + OtelTraceContext traceContext = (OtelTraceContext) currentTraceContext.context(); + Context context = Context.current(); + Deque stack = new ArrayDeque<>(); + if (traceContext != null) { + stack.addFirst(traceContext.context()); + } + stack.addFirst(context); + return new CompositeBaggage(stack); + } + + @Override + public BaggageInScope getBaggage(String name) { + Entry entry = getBaggage(name, currentBaggage()); + return createNewEntryIfMissing(name, entry); + } + + BaggageInScope createNewEntryIfMissing(String name, Entry entry) { + if (entry == null) { + return createBaggage(name); + } + return otelBaggage(entry); + } + + private Entry getBaggage(String name, io.opentelemetry.api.baggage.Baggage baggage) { + return entryForName(name, baggage); + } + + @Override + public BaggageInScope getBaggage(TraceContext traceContext, String name) { + OtelTraceContext context = (OtelTraceContext) traceContext; + // TODO: Refactor + Deque stack = new ArrayDeque<>(); + stack.addFirst(Context.current()); + stack.addFirst(context.context()); + Context ctx = removeFirst(stack); + Entry entry = null; + while (ctx != null && entry == null) { + entry = getBaggage(name, Baggage.fromContext(ctx)); + ctx = removeFirst(stack); + } + return createNewEntryIfMissing(name, entry); + } + + Entry getEntry(OtelTraceContext traceContext, String name) { + OtelTraceContext context = traceContext; + Context ctx = context.context(); + return getBaggage(name, Baggage.fromContext(ctx)); + } + + Context removeFirst(Deque stack) { + return stack.isEmpty() ? null : stack.removeFirst(); + } + + private Entry entryForName(String name, io.opentelemetry.api.baggage.Baggage baggage) { + return Entry.fromBaggage(baggage).stream().filter(e -> e.getKey().toLowerCase().equals(name.toLowerCase())) + .findFirst().orElse(null); + } + + private BaggageInScope otelBaggage(Entry entry) { + return new OtelBaggageInScope(this, this.currentTraceContext, this.tagFields, entry); + } + + @Override + public BaggageInScope createBaggage(String name) { + return createBaggage(name, ""); + } + + @Override + public BaggageInScope createBaggage(String name, String value) { + BaggageInScope baggageInScope = baggageWithValue(name, ""); + return baggageInScope.set(value); + } + + private BaggageInScope baggageWithValue(String name, String value) { + List remoteFieldsFields = this.remoteFields; + boolean remoteField = remoteFieldsFields.stream().map(String::toLowerCase) + .anyMatch(s -> s.equals(name.toLowerCase())); + BaggageEntryMetadata entryMetadata = BaggageEntryMetadata.create(propagationString(remoteField)); + Entry entry = new Entry(name, value, entryMetadata); + return new OtelBaggageInScope(this, this.currentTraceContext, this.tagFields, entry); + } + + private String propagationString(boolean remoteField) { + // TODO: [OTEL] Magic strings + String propagation = ""; + if (remoteField) { + propagation = "propagation=unlimited"; + } + return propagation; + } + +} + +class CompositeBaggage implements io.opentelemetry.api.baggage.Baggage { + + // TODO: Try to use a Map of BaggageEntry only: delete the Entry class + // (might need a bigger refactor) + private final Collection entries; + + private final Map baggageEntries; + + CompositeBaggage(Deque stack) { + this.entries = unmodifiableCollection(createEntries(stack)); + this.baggageEntries = unmodifiableMap(this.entries.stream().collect(toMap(Entry::getKey, identity()))); + } + + private Collection createEntries(Deque stack) { + // parent baggage foo=bar + // child baggage foo=baz - we want the last one to override the previous one + Map map = new HashMap<>(); + Iterator iterator = stack.descendingIterator(); + while (iterator.hasNext()) { + Context next = iterator.next(); + Baggage baggage = Baggage.fromContext(next); + baggage.forEach((key, value) -> map.put(key, new Entry(key, value.getValue(), value.getMetadata()))); + } + + return map.values(); + } + + Collection getEntries() { + return this.entries; + } + + @Override + public int size() { + return this.entries.size(); + } + + @Override + public void forEach(BiConsumer consumer) { + this.entries.forEach(entry -> consumer.accept(entry.getKey(), entry)); + } + + @Override + public Map asMap() { + return this.baggageEntries; + } + + @Override + public String getEntryValue(String entryKey) { + return this.entries.stream().filter(entry -> entryKey.equals(entry.getKey())).map(Entry::getValue).findFirst() + .orElse(null); + } + + @Override + public BaggageBuilder toBuilder() { + return Baggage.builder(); + } + +} + +class Entry implements BaggageEntry { + + final String key; + + final String value; + + final BaggageEntryMetadata entryMetadata; + + Entry(String key, String value, BaggageEntryMetadata entryMetadata) { + this.key = key; + this.value = value; + this.entryMetadata = entryMetadata; + } + + static List fromBaggage(Baggage baggage) { + List list = new ArrayList<>(baggage.size()); + baggage.forEach((key, value) -> list.add(new Entry(key, value.getValue(), value.getMetadata()))); + return list; + } + + public String getKey() { + return this.key; + } + + @Override + public String getValue() { + return this.value; + } + + @Override + public BaggageEntryMetadata getMetadata() { + return this.entryMetadata; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Entry entry = (Entry) o; + return Objects.equals(this.key, entry.key) && Objects.equals(this.value, entry.value) + && Objects.equals(this.entryMetadata, entry.entryMetadata); + } + + @Override + public int hashCode() { + return Objects.hash(this.key, this.value, this.entryMetadata); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelCurrentTraceContext.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelCurrentTraceContext.java new file mode 100644 index 00000000..cca8d340 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelCurrentTraceContext.java @@ -0,0 +1,115 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; + +import io.micrometer.tracing.CurrentTraceContext; +import io.micrometer.tracing.TraceContext; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.context.Context; + +/** + * OpenTelemetry implementation of a {@link CurrentTraceContext}. + * + * @author Marcin Grzejszczak + * @author John Watson + * @since 1.0.0 + */ +public class OtelCurrentTraceContext implements CurrentTraceContext { + + @Override + public TraceContext context() { + Span currentSpan = Span.current(); + if (Span.getInvalid().equals(currentSpan)) { + return null; + } + if (currentSpan instanceof SpanFromSpanContext) { + return new OtelTraceContext((SpanFromSpanContext) currentSpan); + } + return new OtelTraceContext(currentSpan); + } + + @Override + public Scope newScope(TraceContext context) { + OtelTraceContext otelTraceContext = (OtelTraceContext) context; + if (otelTraceContext == null) { + return io.opentelemetry.context.Scope::noop; + } + Context current = Context.current(); + Context old = otelTraceContext.context(); + + Span currentSpan = Span.fromContext(current); + Span oldSpan = Span.fromContext(otelTraceContext.context()); + SpanContext spanContext = otelTraceContext.delegate; + boolean sameSpan = currentSpan.getSpanContext().equals(oldSpan.getSpanContext()) + && currentSpan.getSpanContext().equals(spanContext); + SpanFromSpanContext fromContext = new SpanFromSpanContext(((OtelTraceContext) context).span, spanContext, + otelTraceContext); + + Baggage currentBaggage = Baggage.fromContext(current); + Baggage oldBaggage = Baggage.fromContext(old); + boolean sameBaggage = sameBaggage(currentBaggage, oldBaggage); + + if (sameSpan && sameBaggage) { + return io.opentelemetry.context.Scope::noop; + } + + BaggageBuilder baggageBuilder = currentBaggage.toBuilder(); + oldBaggage.forEach( + (key, baggageEntry) -> baggageBuilder.put(key, baggageEntry.getValue(), baggageEntry.getMetadata())); + Baggage updatedBaggage = baggageBuilder.build(); + + io.opentelemetry.context.Scope attach = old.with(fromContext).with(updatedBaggage).makeCurrent(); + return attach::close; + } + + private boolean sameBaggage(Baggage currentBaggage, Baggage oldBaggage) { + return currentBaggage.equals(oldBaggage); + } + + @Override + public Scope maybeScope(TraceContext context) { + return newScope(context); + } + + @Override + public Callable wrap(Callable task) { + return Context.current().wrap(task); + } + + @Override + public Runnable wrap(Runnable task) { + return Context.current().wrap(task); + } + + @Override + public Executor wrap(Executor delegate) { + return Context.current().wrap(delegate); + } + + @Override + public ExecutorService wrap(ExecutorService delegate) { + return Context.current().wrap(delegate); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelFinishedSpan.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelFinishedSpan.java new file mode 100644 index 00000000..93ebdbd5 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelFinishedSpan.java @@ -0,0 +1,196 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.AbstractMap; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import io.micrometer.tracing.Span; +import io.micrometer.tracing.exporter.FinishedSpan; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.sdk.trace.data.EventData; +import io.opentelemetry.sdk.trace.data.SpanData; + +/** + * OpenTelemetry implementation of a {@link FinishedSpan}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class OtelFinishedSpan implements FinishedSpan { + + private final SpanData spanData; + + private final Map tags = new HashMap<>(); + + private volatile String linkLocalIp; + + OtelFinishedSpan(SpanData spanData) { + this.spanData = spanData; + } + + public static FinishedSpan fromOtel(SpanData span) { + return new OtelFinishedSpan(span); + } + + public static SpanData toOtel(FinishedSpan span) { + return ((OtelFinishedSpan) span).spanData; + } + + @Override + public String getName() { + return this.spanData.getName(); + } + + @Override + public long getStartTimestamp() { + return this.spanData.getStartEpochNanos(); + } + + @Override + public long getEndTimestamp() { + return this.spanData.getEndEpochNanos(); + } + + @Override + public Map getTags() { + if (this.tags.isEmpty()) { + this.spanData.getAttributes().forEach((key, value) -> tags.put(key.getKey(), String.valueOf(value))); + } + return this.tags; + } + + @Override + public Collection> getEvents() { + return this.spanData.getEvents().stream() + .map(e -> new AbstractMap.SimpleEntry<>(e.getEpochNanos(), e.getName())).collect(Collectors.toList()); + } + + @Override + public String getSpanId() { + return this.spanData.getSpanId(); + } + + @Override + public String getParentId() { + return this.spanData.getParentSpanId(); + } + + @Override + public String getRemoteIp() { + return getTags().get("net.peer.ip"); + } + + @Override + public String getLocalIp() { + // taken from Brave + // uses synchronized variant of double-checked locking as getting the endpoint can + // be expensive + if (this.linkLocalIp != null) { + return this.linkLocalIp; + } + synchronized (this) { + if (this.linkLocalIp == null) { + this.linkLocalIp = produceLinkLocalIp(); + } + } + return this.linkLocalIp; + } + + private String produceLinkLocalIp() { + try { + Enumeration nics = NetworkInterface.getNetworkInterfaces(); + while (nics.hasMoreElements()) { + NetworkInterface nic = nics.nextElement(); + Enumeration addresses = nic.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress address = addresses.nextElement(); + if (address.isSiteLocalAddress()) { + return address.getHostAddress(); + } + } + } + } + catch (Exception e) { + } + return null; + } + + @Override + public int getRemotePort() { + return Integer.parseInt(getTags().get("net.peer.port")); + } + + @Override + public String getTraceId() { + return this.spanData.getTraceId(); + } + + @Override + public Throwable getError() { + Attributes attributes = this.spanData.getEvents().stream().filter(e -> e.getName().equals("exception")) + .findFirst().map(EventData::getAttributes).orElse(null); + if (attributes != null) { + return new AssertingThrowable(attributes); + } + return null; + } + + @Override + public Span.Kind getKind() { + if (this.spanData.getKind() == SpanKind.INTERNAL) { + return null; + } + return Span.Kind.valueOf(this.spanData.getKind().name()); + } + + @Override + public String getRemoteServiceName() { + return this.spanData.getAttributes().get(AttributeKey.stringKey("peer.service")); + } + + @Override + public String toString() { + return "SpanDataToReportedSpan{" + "spanData=" + spanData + ", tags=" + tags + '}'; + } + + /** + * {@link Throwable} with attributes. + */ + public static class AssertingThrowable extends Throwable { + + /** + * Attributes set on the span. + */ + public final Attributes attributes; + + AssertingThrowable(Attributes attributes) { + super(attributes.get(AttributeKey.stringKey("exception.message"))); + this.attributes = attributes; + } + + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelHttpClientHandler.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelHttpClientHandler.java new file mode 100644 index 00000000..600a06ed --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelHttpClientHandler.java @@ -0,0 +1,136 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import io.micrometer.core.instrument.transport.http.HttpClientRequest; +import io.micrometer.core.instrument.transport.http.HttpClientResponse; +import io.micrometer.core.instrument.transport.http.HttpRequest; +import io.micrometer.core.util.internal.logging.InternalLogger; +import io.micrometer.core.util.internal.logging.InternalLoggerFactory; +import io.micrometer.tracing.SamplerFunction; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.http.HttpClientHandler; +import io.micrometer.tracing.http.HttpRequestParser; +import io.micrometer.tracing.http.HttpResponseParser; +import io.micrometer.tracing.lang.Nullable; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextKey; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; + + +/** + * OpenTelemetry implementation of a {@link HttpClientHandler}. + * + * @author Marcin Grzejszczak + * @author Nikita Salnikov-Tarnovski + * @since 1.0.0 + */ +public class OtelHttpClientHandler implements HttpClientHandler { + + private static final InternalLogger log = InternalLoggerFactory.getInstance(OtelHttpClientHandler.class); + + private static final ContextKey REQUEST_CONTEXT_KEY = ContextKey + .named(OtelHttpClientHandler.class.getName() + ".request"); + + private final HttpRequestParser httpClientRequestParser; + + private final HttpResponseParser httpClientResponseParser; + + private final SamplerFunction samplerFunction; + + private final Instrumenter instrumenter; + + public OtelHttpClientHandler(OpenTelemetry openTelemetry, @Nullable HttpRequestParser httpClientRequestParser, + @Nullable HttpResponseParser httpClientResponseParser, SamplerFunction samplerFunction, + HttpClientAttributesExtractor httpAttributesExtractor) { + this.httpClientRequestParser = httpClientRequestParser; + this.httpClientResponseParser = httpClientResponseParser; + this.samplerFunction = samplerFunction; + this.instrumenter = Instrumenter + .newBuilder(openTelemetry, "io.micrometer.tracing", + HttpSpanNameExtractor.create(httpAttributesExtractor)) + .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesExtractor)) + .addAttributesExtractor(new HttpRequestNetClientAttributesExtractor()) + .addAttributesExtractor(httpAttributesExtractor).addAttributesExtractor(new PathAttributeExtractor()) + .newClientInstrumenter(HttpClientRequest::header); + } + + @Override + public Span handleSend(HttpClientRequest request) { + Context parentContext = Context.current(); + return startSpan(request, parentContext); + } + + @Override + public Span handleSend(HttpClientRequest request, TraceContext parent) { + Context parentContext = OtelTraceContext.toOtelContext(parent); + return startSpan(request, parentContext); + } + + private Span startSpan(HttpClientRequest request, Context parentContext) { + if (Boolean.FALSE.equals(this.samplerFunction.trySample(request))) { + if (log.isDebugEnabled()) { + log.debug("Returning an invalid span since url [" + request.path() + "] is on a list of urls to skip"); + } + return OtelSpan.fromOtel(io.opentelemetry.api.trace.Span.getInvalid()); + } + if (instrumenter.shouldStart(parentContext, request)) { + Context context = instrumenter.start(parentContext, request); + return span(context, request); + } + else { + return OtelSpan.fromOtel(io.opentelemetry.api.trace.Span.getInvalid()); + } + } + + private Span span(Context context, HttpClientRequest request) { + io.opentelemetry.api.trace.Span span = io.opentelemetry.api.trace.Span.fromContext(context); + Span result = OtelSpan.fromOtel(span, context.with(REQUEST_CONTEXT_KEY, request)); + if (this.httpClientRequestParser != null) { + this.httpClientRequestParser.parse(request, result.context(), result); + } + return result; + } + + @Override + public void handleReceive(HttpClientResponse response, Span span) { + OtelSpan otelSpanWrapper = (OtelSpan) span; + if (!otelSpanWrapper.delegate.getSpanContext().isValid()) { + if (log.isDebugEnabled()) { + log.debug("Not doing anything because the span is invalid"); + } + return; + } + + if (this.httpClientResponseParser != null) { + this.httpClientResponseParser.parse(response, span.context(), span); + } + OtelTraceContext traceContext = otelSpanWrapper.context(); + Context otelContext = traceContext.context(); + // TODO this must be otelContext, but OpenTelemetry context handling is not + // entirely correct here atm + Context contextToEnd = Context.current().with(otelSpanWrapper.delegate); + // response.getRequest() too often returns null + instrumenter.end(contextToEnd, otelContext.get(REQUEST_CONTEXT_KEY), response, response.error()); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelHttpServerHandler.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelHttpServerHandler.java new file mode 100644 index 00000000..e9c5fef1 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelHttpServerHandler.java @@ -0,0 +1,136 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.regex.Pattern; + +import io.micrometer.core.instrument.transport.http.HttpServerRequest; +import io.micrometer.core.instrument.transport.http.HttpServerResponse; +import io.micrometer.core.util.internal.logging.InternalLogger; +import io.micrometer.core.util.internal.logging.InternalLoggerFactory; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.http.HttpRequestParser; +import io.micrometer.tracing.http.HttpResponseParser; +import io.micrometer.tracing.http.HttpServerHandler; +import io.micrometer.tracing.util.StringUtils; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextKey; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; + + +/** + * OpenTelemetry implementation of a {@link HttpServerHandler}. + * + * @author Marcin Grzejszczak + * @author Nikita Salnikov-Tarnovski + * @since 1.0.0 + */ +public class OtelHttpServerHandler implements HttpServerHandler { + + private static final InternalLogger log = InternalLoggerFactory.getInstance(OtelHttpClientHandler.class); + + private static final ContextKey REQUEST_CONTEXT_KEY = ContextKey + .named(OtelHttpServerHandler.class.getName() + ".request"); + + private final HttpRequestParser httpServerRequestParser; + + private final HttpResponseParser httpServerResponseParser; + + private final Pattern pattern; + + private final Instrumenter instrumenter; + + public OtelHttpServerHandler(OpenTelemetry openTelemetry, HttpRequestParser httpServerRequestParser, + HttpResponseParser httpServerResponseParser, Pattern skipPattern, + HttpServerAttributesExtractor httpAttributesExtractor) { + this.httpServerRequestParser = httpServerRequestParser; + this.httpServerResponseParser = httpServerResponseParser; + this.pattern = skipPattern; + this.instrumenter = Instrumenter + .newBuilder(openTelemetry, "io.micrometer.tracing", + HttpSpanNameExtractor.create(httpAttributesExtractor)) + .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesExtractor)) + .addAttributesExtractor(new HttpRequestNetServerAttributesExtractor()) + .addAttributesExtractor(httpAttributesExtractor).addAttributesExtractor(new PathAttributeExtractor()) + .newServerInstrumenter(getGetter()); + } + + @Override + public Span handleReceive(HttpServerRequest request) { + String url = request.path(); + boolean shouldSkip = !StringUtils.isEmpty(url) && this.pattern.matcher(url).matches(); + if (shouldSkip) { + return OtelSpan.fromOtel(io.opentelemetry.api.trace.Span.getInvalid()); + } + Context parentContext = Context.current(); + if (instrumenter.shouldStart(parentContext, request)) { + Context context = instrumenter.start(parentContext, request); + return span(context, request); + } + else { + return OtelSpan.fromOtel(io.opentelemetry.api.trace.Span.getInvalid()); + } + } + + private Span span(Context context, HttpServerRequest request) { + io.opentelemetry.api.trace.Span span = io.opentelemetry.api.trace.Span.fromContext(context); + Span result = OtelSpan.fromOtel(span, context.with(REQUEST_CONTEXT_KEY, request)); + if (this.httpServerRequestParser != null) { + this.httpServerRequestParser.parse(request, result.context(), result); + } + return result; + } + + @Override + public void handleSend(HttpServerResponse response, Span span) { + OtelSpan otelSpanWrapper = (OtelSpan) span; + if (!otelSpanWrapper.delegate.getSpanContext().isValid()) { + if (log.isDebugEnabled()) { + log.debug("Not doing anything because the span is invalid"); + } + return; + } + + if (this.httpServerResponseParser != null) { + this.httpServerResponseParser.parse(response, span.context(), span); + } + OtelTraceContext traceContext = otelSpanWrapper.context(); + Context otelContext = traceContext.context(); + // response.getRequest() too often returns null + instrumenter.end(otelContext, otelContext.get(REQUEST_CONTEXT_KEY), response, response.error()); + } + + private TextMapGetter getGetter() { + return new TextMapGetter() { + @Override + public Iterable keys(HttpServerRequest carrier) { + return carrier.headerNames(); + } + + @Override + public String get(HttpServerRequest carrier, String key) { + return carrier.header(key); + } + }; + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelPropagator.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelPropagator.java new file mode 100644 index 00000000..79cbfb25 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelPropagator.java @@ -0,0 +1,81 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.ArrayList; +import java.util.List; + +import io.micrometer.tracing.Span; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.propagation.Propagator; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapPropagator; + + +/** + * OpenTelemetry implementation of a {@link Propagator}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class OtelPropagator implements Propagator { + + private final TextMapPropagator propagator; + + private final Tracer tracer; + + public OtelPropagator(ContextPropagators propagation, Tracer tracer) { + this.propagator = propagation.getTextMapPropagator(); + this.tracer = tracer; + } + + @Override + public List fields() { + // TODO: We should make Propagator::fields returning Collection + return new ArrayList<>(this.propagator.fields()); + } + + @Override + public void inject(TraceContext traceContext, C carrier, Setter setter) { + Context context = OtelTraceContext.toOtelContext(traceContext); + this.propagator.inject(context, carrier, setter::set); + } + + @Override + public Span.Builder extract(C carrier, Getter getter) { + Context extracted = this.propagator.extract(Context.current(), carrier, new TextMapGetter() { + @Override + public Iterable keys(C carrier) { + return fields(); + } + + @Override + public String get(C carrier, String key) { + return getter.get(carrier, key); + } + }); + io.opentelemetry.api.trace.Span span = io.opentelemetry.api.trace.Span.fromContextOrNull(extracted); + if (span == null || span.equals(io.opentelemetry.api.trace.Span.getInvalid())) { + return OtelSpanBuilder.fromOtel(tracer.spanBuilder("")); + } + return OtelSpanBuilder.fromOtel(this.tracer.spanBuilder("").setParent(extracted)); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelScopedSpan.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelScopedSpan.java new file mode 100644 index 00000000..78793f59 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelScopedSpan.java @@ -0,0 +1,82 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import io.micrometer.tracing.ScopedSpan; +import io.micrometer.tracing.TraceContext; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Scope; + + +/** + * OpenTelemetry implementation of a {@link ScopedSpan}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +class OtelScopedSpan implements ScopedSpan { + + final Span span; + + final Scope scope; + + OtelScopedSpan(Span span, Scope scope) { + this.span = span; + this.scope = scope; + } + + @Override + public boolean isNoop() { + return !this.span.isRecording(); + } + + @Override + public TraceContext context() { + return new OtelTraceContext(this.span); + } + + @Override + public ScopedSpan name(String name) { + this.span.updateName(name); + return this; + } + + @Override + public ScopedSpan tag(String key, String value) { + this.span.setAttribute(key, value); + return this; + } + + @Override + public ScopedSpan event(String value) { + this.span.addEvent(value); + return this; + } + + @Override + public ScopedSpan error(Throwable throwable) { + this.span.recordException(throwable); + return this; + } + + @Override + public void end() { + this.scope.close(); + this.span.end(); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelSpan.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelSpan.java new file mode 100644 index 00000000..d0973855 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelSpan.java @@ -0,0 +1,152 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +import io.micrometer.tracing.Span; +import io.micrometer.tracing.docs.AssertingSpan; +import io.opentelemetry.context.Context; + + +/** + * OpenTelemetry implementation of a {@link Span}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +class OtelSpan implements Span { + + final io.opentelemetry.api.trace.Span delegate; + + private final AtomicReference context; + + OtelSpan(io.opentelemetry.api.trace.Span delegate) { + this.delegate = delegate; + if (delegate instanceof SpanFromSpanContext) { + SpanFromSpanContext fromSpanContext = (SpanFromSpanContext) delegate; + this.context = fromSpanContext.otelTraceContext.context; + } + else { + this.context = new AtomicReference<>(Context.current()); + } + } + + OtelSpan(io.opentelemetry.api.trace.Span delegate, Context context) { + this.delegate = delegate; + this.context = new AtomicReference<>(context); + } + + static io.opentelemetry.api.trace.Span toOtel(Span span) { + return ((OtelSpan) AssertingSpan.unwrap(span)).delegate; + } + + static Span fromOtel(io.opentelemetry.api.trace.Span span) { + return new OtelSpan(span); + } + + static Span fromOtel(io.opentelemetry.api.trace.Span span, Context context) { + return new OtelSpan(span, context); + } + + @Override + public boolean isNoop() { + return !this.delegate.isRecording(); + } + + @Override + public OtelTraceContext context() { + if (this.delegate == null) { + return null; + } + return new OtelTraceContext(this.context, this.delegate.getSpanContext(), this.delegate); + } + + @Override + public Span start() { + // they are already started via the builder + return this; + } + + @Override + public Span name(String name) { + this.delegate.updateName(name); + return new OtelSpan(this.delegate); + } + + @Override + public Span event(String value) { + this.delegate.addEvent(value); + return new OtelSpan(this.delegate); + } + + @Override + public Span tag(String key, String value) { + this.delegate.setAttribute(key, value); + return new OtelSpan(this.delegate); + } + + @Override + public Span error(Throwable throwable) { + this.delegate.recordException(throwable); + return new OtelSpan(this.delegate); + } + + @Override + public void end() { + this.delegate.end(); + } + + @Override + public void abandon() { + // TODO: [OTEL] doesn't seem to have this notion yet + } + + @Override + public Span remoteServiceName(String remoteServiceName) { + this.delegate.setAttribute(OtelSpanBuilder.REMOTE_SERVICE_NAME_KEY, remoteServiceName); + return this; + } + + @Override + public String toString() { + return this.delegate != null ? this.delegate.toString() : "null"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + Object unwrapped = o; + if (o instanceof AssertingSpan) { + unwrapped = ((AssertingSpan) o).getDelegate(); + } + if (unwrapped == null || getClass() != unwrapped.getClass()) { + return false; + } + OtelSpan otelSpan = (OtelSpan) unwrapped; + return Objects.equals(this.delegate, otelSpan.delegate); + } + + @Override + public int hashCode() { + return Objects.hash(this.delegate); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelSpanBuilder.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelSpanBuilder.java new file mode 100644 index 00000000..f1c8ef3d --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelSpanBuilder.java @@ -0,0 +1,141 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.LinkedList; +import java.util.List; + +import io.micrometer.tracing.Span; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.util.StringUtils; +import io.opentelemetry.api.trace.SpanKind; + + +/** + * OpenTelemetry implementation of a {@link Span.Builder}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +class OtelSpanBuilder implements Span.Builder { + + static final String REMOTE_SERVICE_NAME_KEY = "peer.service"; + + private final io.opentelemetry.api.trace.SpanBuilder delegate; + + private final List annotations = new LinkedList<>(); + + private String name; + + private Throwable error; + + OtelSpanBuilder(io.opentelemetry.api.trace.SpanBuilder delegate) { + this.delegate = delegate; + } + + static Span.Builder fromOtel(io.opentelemetry.api.trace.SpanBuilder builder) { + return new OtelSpanBuilder(builder); + } + + @Override + public Span.Builder setParent(TraceContext context) { + this.delegate.setParent(OtelTraceContext.toOtelContext(context)); + return this; + } + + @Override + public Span.Builder setNoParent() { + this.delegate.setNoParent(); + return this; + } + + @Override + public Span.Builder name(String name) { + this.name = name; + return this; + } + + @Override + public Span.Builder event(String value) { + this.annotations.add(value); + return this; + } + + @Override + public Span.Builder tag(String key, String value) { + this.delegate.setAttribute(key, value); + return this; + } + + @Override + public Span.Builder error(Throwable throwable) { + this.error = throwable; + return this; + } + + @Override + public Span.Builder kind(Span.Kind spanKind) { + if (spanKind == null) { + this.delegate.setSpanKind(SpanKind.INTERNAL); + return this; + } + SpanKind kind = SpanKind.INTERNAL; + switch (spanKind) { + case CLIENT: + kind = SpanKind.CLIENT; + break; + case SERVER: + kind = SpanKind.SERVER; + break; + case PRODUCER: + kind = SpanKind.PRODUCER; + break; + case CONSUMER: + kind = SpanKind.CONSUMER; + break; + } + this.delegate.setSpanKind(kind); + return this; + } + + @Override + public Span.Builder remoteServiceName(String remoteServiceName) { + this.delegate.setAttribute(REMOTE_SERVICE_NAME_KEY, remoteServiceName); + return this; + } + + @Override + public Span.Builder remoteIpAndPort(String ip, int port) { + this.delegate.setAttribute("net.peer.ip", ip); + this.delegate.setAttribute("net.peer.port", port); + return this; + } + + @Override + public Span start() { + io.opentelemetry.api.trace.Span span = this.delegate.startSpan(); + if (StringUtils.isNotEmpty(this.name)) { + span.updateName(this.name); + } + if (this.error != null) { + span.recordException(error); + } + this.annotations.forEach(span::addEvent); + return OtelSpan.fromOtel(span); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelSpanCustomizer.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelSpanCustomizer.java new file mode 100644 index 00000000..b72a37b8 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelSpanCustomizer.java @@ -0,0 +1,53 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import io.micrometer.tracing.SpanCustomizer; +import io.opentelemetry.api.trace.Span; + + +/** + * OpenTelemetry implementation of a {@link SpanCustomizer}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class OtelSpanCustomizer implements SpanCustomizer { + + @Override + public SpanCustomizer name(String name) { + currentSpan().updateName(name); + return this; + } + + private Span currentSpan() { + return Span.current(); + } + + @Override + public SpanCustomizer tag(String key, String value) { + currentSpan().setAttribute(key, value); + return this; + } + + @Override + public SpanCustomizer event(String value) { + currentSpan().addEvent(value); + return this; + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelSpanInScope.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelSpanInScope.java new file mode 100644 index 00000000..273d8883 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelSpanInScope.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import io.micrometer.core.util.internal.logging.InternalLogger; +import io.micrometer.core.util.internal.logging.InternalLoggerFactory; +import io.micrometer.tracing.Tracer; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.context.Scope; + +class OtelSpanInScope implements Tracer.SpanInScope { + + private static final InternalLogger log = InternalLoggerFactory.getInstance(OtelSpanInScope.class); + + final Scope delegate; + + final OtelSpan sleuthSpan; + + final io.opentelemetry.api.trace.Span otelSpan; + + final SpanContext spanContext; + + OtelSpanInScope(OtelSpan sleuthSpan, io.opentelemetry.api.trace.Span otelSpan) { + this.sleuthSpan = sleuthSpan; + this.otelSpan = otelSpan; + this.delegate = otelSpan.makeCurrent(); + this.spanContext = otelSpan.getSpanContext(); + } + + @Override + public void close() { + log.trace("Will close scope for trace context [{}]", this.spanContext); + this.delegate.close(); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelTraceContext.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelTraceContext.java new file mode 100644 index 00000000..dfffcc42 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelTraceContext.java @@ -0,0 +1,141 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.lang.Nullable; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.ReadableSpan; + + +/** + * OpenTelemetry implementation of a {@link TraceContext}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class OtelTraceContext implements TraceContext { + + final AtomicReference context; + + final SpanContext delegate; + + final Span span; + + OtelTraceContext(Context context, SpanContext delegate, @Nullable Span span) { + this(new AtomicReference<>(context), delegate, span); + } + + OtelTraceContext(AtomicReference context, SpanContext delegate, @Nullable Span span) { + this.context = context; + this.delegate = delegate; + this.span = span; + } + + OtelTraceContext(SpanContext delegate, @Nullable Span span) { + this.context = new AtomicReference<>(Context.current()); + this.delegate = delegate; + this.span = span; + } + + OtelTraceContext(Span span) { + this(Context.current(), span.getSpanContext(), span); + } + + OtelTraceContext(SpanFromSpanContext span) { + this(span.otelTraceContext.context.get(), span.getSpanContext(), span); + } + + public static TraceContext fromOtel(SpanContext traceContext) { + return new OtelTraceContext(traceContext, null); + } + + public static Context toOtelContext(TraceContext context) { + if (context instanceof OtelTraceContext) { + Span span = ((OtelTraceContext) context).span; + if (span != null) { + return span.storeInContext(Context.current()); + } + } + return Context.current(); + } + + @Override + public String traceId() { + return this.delegate.getTraceId(); + } + + @Override + @Nullable + public String parentId() { + if (this.span instanceof ReadableSpan) { + ReadableSpan readableSpan = (ReadableSpan) this.span; + return readableSpan.toSpanData().getParentSpanId(); + } + return null; + } + + @Override + public String spanId() { + return this.delegate.getSpanId(); + } + + @Override + public Boolean sampled() { + return this.delegate.isSampled(); + } + + @Override + public String toString() { + return this.delegate != null ? this.delegate.toString() : "null"; + } + + Span span() { + return this.span; + } + + Context context() { + return this.context.get(); + } + + void updateContext(Context context) { + this.context.set(context); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + OtelTraceContext context = (OtelTraceContext) o; + return Objects.equals(this.delegate, context.delegate); + } + + @Override + public int hashCode() { + return Objects.hash(this.delegate); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelTraceContextBuilder.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelTraceContextBuilder.java new file mode 100644 index 00000000..b2d6ca59 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelTraceContextBuilder.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.util.StringUtils; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; + +/** + * OpenTelemetry implementation of a {@link TraceContext.Builder}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class OtelTraceContextBuilder implements TraceContext.Builder { + + private String traceId; + + private String parentId; + + private String spanId; + + private Boolean sampled; + + @Override + public TraceContext.Builder traceId(String traceId) { + this.traceId = traceId; + return this; + } + + @Override + public TraceContext.Builder parentId(String parentId) { + this.parentId = parentId; + return this; + } + + @Override + public TraceContext.Builder spanId(String spanId) { + this.spanId = spanId; + return this; + } + + @Override + public TraceContext.Builder sampled(Boolean sampled) { + this.sampled = sampled; + return this; + } + + @Override + public TraceContext build() { + if (StringUtils.isNotEmpty(this.parentId)) { + return new OtelTraceContext( + SpanContext.createFromRemoteParent(this.traceId, this.spanId, + this.sampled ? TraceFlags.getSampled() : TraceFlags.getDefault(), TraceState.getDefault()), + null); + } + return new OtelTraceContext( + SpanContext.create(this.traceId, this.spanId, + this.sampled ? TraceFlags.getSampled() : TraceFlags.getDefault(), TraceState.getDefault()), + null); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelTracer.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelTracer.java new file mode 100644 index 00000000..6767ba63 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelTracer.java @@ -0,0 +1,149 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.Map; + +import io.micrometer.tracing.BaggageInScope; +import io.micrometer.tracing.BaggageManager; +import io.micrometer.tracing.CurrentTraceContext; +import io.micrometer.tracing.ScopedSpan; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.SpanCustomizer; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.docs.AssertingSpan; + +/** + * OpenTelemetry implementation of a {@link Tracer}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class OtelTracer implements Tracer { + + private final io.opentelemetry.api.trace.Tracer tracer; + + private final BaggageManager otelBaggageManager; + + private final OtelCurrentTraceContext otelCurrentTraceContext; + + private final EventPublisher publisher; + + public OtelTracer(io.opentelemetry.api.trace.Tracer tracer, OtelCurrentTraceContext otelCurrentTraceContext, EventPublisher publisher, + BaggageManager otelBaggageManager) { + this.tracer = tracer; + this.publisher = publisher; + this.otelBaggageManager = otelBaggageManager; + this.otelCurrentTraceContext = otelCurrentTraceContext; + } + + @Override + public Span nextSpan(Span parent) { + if (parent == null) { + return nextSpan(); + } + return OtelSpan.fromOtel( + this.tracer.spanBuilder("").setParent(OtelTraceContext.toOtelContext(parent.context())).startSpan()); + } + + @Override + public Tracer.SpanInScope withSpan(Span span) { + io.opentelemetry.api.trace.Span delegate = delegate(span); + return new OtelSpanInScope(AssertingSpan.unwrap(span), delegate); + } + + private io.opentelemetry.api.trace.Span delegate(Span span) { + if (span == null) { + // remove any existing span/baggage data from the current state of anything + // that might be holding on to it. + this.publisher.publishEvent(new EventPublishingContextWrapper.ScopeClosedEvent()); + return io.opentelemetry.api.trace.Span.getInvalid(); + } + return ((OtelSpan) AssertingSpan.unwrap(span)).delegate; + } + + @Override + public SpanCustomizer currentSpanCustomizer() { + return new OtelSpanCustomizer(); + } + + @Override + public Span currentSpan() { + io.opentelemetry.api.trace.Span currentSpan = io.opentelemetry.api.trace.Span.current(); + if (currentSpan == null || currentSpan.equals(io.opentelemetry.api.trace.Span.getInvalid())) { + return null; + } + return new OtelSpan(currentSpan); + } + + @Override + public Span nextSpan() { + return new OtelSpan(this.tracer.spanBuilder("").startSpan()); + } + + @Override + public ScopedSpan startScopedSpan(String name) { + io.opentelemetry.api.trace.Span span = this.tracer.spanBuilder(name).startSpan(); + return new OtelScopedSpan(span, span.makeCurrent()); + } + + @Override + public Span.Builder spanBuilder() { + return new OtelSpanBuilder(this.tracer.spanBuilder("")); + } + + @Override + public TraceContext.Builder traceContextBuilder() { + return new OtelTraceContextBuilder(); + } + + @Override + public CurrentTraceContext currentTraceContext() { + return this.otelCurrentTraceContext; + } + + @Override + public Map getAllBaggage() { + return this.otelBaggageManager.getAllBaggage(); + } + + @Override + public BaggageInScope getBaggage(String name) { + return this.otelBaggageManager.getBaggage(name); + } + + @Override + public BaggageInScope getBaggage(TraceContext traceContext, String name) { + return this.otelBaggageManager.getBaggage(traceContext, name); + } + + @Override + public BaggageInScope createBaggage(String name) { + return this.otelBaggageManager.createBaggage(name); + } + + @Override + public BaggageInScope createBaggage(String name, String value) { + return this.otelBaggageManager.createBaggage(name, value); + } + + public interface EventPublisher { + + void publishEvent(Object event); + } +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/PathAttributeExtractor.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/PathAttributeExtractor.java new file mode 100644 index 00000000..8b732951 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/PathAttributeExtractor.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import io.micrometer.core.instrument.transport.http.HttpRequest; +import io.micrometer.core.instrument.transport.http.HttpResponse; +import io.micrometer.tracing.lang.Nullable; +import io.micrometer.tracing.util.StringUtils; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; + + +class PathAttributeExtractor implements AttributesExtractor { + + private static final AttributeKey HTTP_PATH = AttributeKey.stringKey("http.path"); + + @Override + public void onStart(AttributesBuilder attributes, HttpRequest httpRequest) { + String path = httpRequest.path(); + if (StringUtils.isNotEmpty(path)) { + // TODO some tests expect this even on client spans, but this goes against + // Otel semantic conventions + // should fix tests + set(attributes, SemanticAttributes.HTTP_ROUTE, path); + // some tests from Sleuth expect http.route attribute and some http.path + set(attributes, HTTP_PATH, path); + } + } + + @Override + public void onEnd(AttributesBuilder attributes, HttpRequest httpRequest, @Nullable HttpResponse httpResponse, + @Nullable Throwable error) { + + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/SkipPatternSampler.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/SkipPatternSampler.java new file mode 100644 index 00000000..ea52a178 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/SkipPatternSampler.java @@ -0,0 +1,49 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.regex.Pattern; + +import io.micrometer.core.instrument.transport.http.HttpRequest; +import io.micrometer.tracing.SamplerFunction; + + +/** + * Decides if sampling should take place for the given request. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class SkipPatternSampler implements SamplerFunction { + + private final Pattern pattern; + + public SkipPatternSampler(Pattern pattern) { + this.pattern = pattern; + } + + @Override + public final Boolean trySample(HttpRequest request) { + String url = request.path(); + boolean shouldSkip = this.pattern.matcher(url).matches(); + if (shouldSkip) { + return false; + } + return null; + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/Slf4JBaggageEventListener.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/Slf4JBaggageEventListener.java new file mode 100644 index 00000000..d2b4db68 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/Slf4JBaggageEventListener.java @@ -0,0 +1,87 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.List; +import java.util.stream.Collectors; + +import io.micrometer.core.util.internal.logging.InternalLogger; +import io.micrometer.core.util.internal.logging.InternalLoggerFactory; +import io.opentelemetry.api.baggage.Baggage; +import org.slf4j.MDC; + +public class Slf4JBaggageEventListener implements EventListener { + + private static final InternalLogger log = InternalLoggerFactory.getInstance(Slf4JBaggageEventListener.class); + + private final List lowerCaseCorrelationFields; + + private final List correlationFields; + + public Slf4JBaggageEventListener(List correlationFields) { + this.lowerCaseCorrelationFields = correlationFields.stream().map(String::toLowerCase) + .collect(Collectors.toList()); + this.correlationFields = correlationFields; + } + + private void onScopeAttached(EventPublishingContextWrapper.ScopeAttachedEvent event) { + if (log.isTraceEnabled()) { + log.trace("Got scope attached event [" + event + "]"); + } + if (event.getBaggage() != null) { + putEntriesIntoMdc(event.getBaggage()); + } + } + + private void onScopeRestored(EventPublishingContextWrapper.ScopeRestoredEvent event) { + if (log.isTraceEnabled()) { + log.trace("Got scope restored event [" + event + "]"); + } + if (event.getBaggage() != null) { + putEntriesIntoMdc(event.getBaggage()); + } + } + + private void putEntriesIntoMdc(Baggage baggage) { + baggage.forEach((key, baggageEntry) -> { + if (lowerCaseCorrelationFields.contains(key.toLowerCase())) { + MDC.put(key, baggageEntry.getValue()); + } + }); + } + + private void onScopeClosed(EventPublishingContextWrapper.ScopeClosedEvent event) { + if (log.isTraceEnabled()) { + log.trace("Got scope closed event [" + event + "]"); + } + correlationFields.forEach(MDC::remove); + } + + @Override + public void onEvent(Object event) { + if (event instanceof EventPublishingContextWrapper.ScopeAttachedEvent) { + onScopeAttached((EventPublishingContextWrapper.ScopeAttachedEvent) event); + } + else if (event instanceof EventPublishingContextWrapper.ScopeClosedEvent) { + onScopeClosed((EventPublishingContextWrapper.ScopeClosedEvent) event); + } + else if (event instanceof EventPublishingContextWrapper.ScopeRestoredEvent) { + onScopeRestored((EventPublishingContextWrapper.ScopeRestoredEvent) event); + } + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/Slf4JEventListener.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/Slf4JEventListener.java new file mode 100644 index 00000000..426e9426 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/Slf4JEventListener.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import io.micrometer.core.util.internal.logging.InternalLogger; +import io.micrometer.core.util.internal.logging.InternalLoggerFactory; +import io.opentelemetry.api.trace.Span; +import org.slf4j.MDC; + +public class Slf4JEventListener implements EventListener { + + private static final InternalLogger log = InternalLoggerFactory.getInstance(Slf4JEventListener.class); + + private void onScopeAttached(EventPublishingContextWrapper.ScopeAttachedEvent event) { + log.trace("Got scope changed event [{}]", event); + Span span = event.getSpan(); + if (span != null) { + MDC.put("traceId", span.getSpanContext().getTraceId()); + MDC.put("spanId", span.getSpanContext().getSpanId()); + } + } + + private void onScopeRestored(EventPublishingContextWrapper.ScopeRestoredEvent event) { + log.trace("Got scope restored event [{}]", event); + Span span = event.getSpan(); + if (span != null) { + MDC.put("traceId", span.getSpanContext().getTraceId()); + MDC.put("spanId", span.getSpanContext().getSpanId()); + } + } + + private void onScopeClosed(EventPublishingContextWrapper.ScopeClosedEvent event) { + log.trace("Got scope closed event [{}]", event); + MDC.remove("traceId"); + MDC.remove("spanId"); + } + + @Override + public void onEvent(Object event) { + if (event instanceof EventPublishingContextWrapper.ScopeAttachedEvent) { + onScopeAttached((EventPublishingContextWrapper.ScopeAttachedEvent) event); + } + else if (event instanceof EventPublishingContextWrapper.ScopeClosedEvent) { + onScopeClosed((EventPublishingContextWrapper.ScopeClosedEvent) event); + } + else if (event instanceof EventPublishingContextWrapper.ScopeRestoredEvent) { + onScopeRestored((EventPublishingContextWrapper.ScopeRestoredEvent) event); + } + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/SpanExporterCustomizer.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/SpanExporterCustomizer.java new file mode 100644 index 00000000..c3bb2905 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/SpanExporterCustomizer.java @@ -0,0 +1,38 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import io.opentelemetry.sdk.trace.export.SpanExporter; + +/** + * Allows customization of a {@link SpanExporter}. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public interface SpanExporterCustomizer { + + /** + * Customizes a span exporter. + * @param spanExporter to customize + * @return customized span exporter + */ + default SpanExporter customize(SpanExporter spanExporter) { + return spanExporter; + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/SpanFromSpanContext.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/SpanFromSpanContext.java new file mode 100644 index 00000000..62fc56cf --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/SpanFromSpanContext.java @@ -0,0 +1,182 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.time.Instant; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import io.micrometer.tracing.lang.Nullable; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.context.Context; + +class SpanFromSpanContext implements io.opentelemetry.api.trace.Span { + + final io.opentelemetry.api.trace.Span span; + + final SpanContext newSpanContext; + + final OtelTraceContext otelTraceContext; + + SpanFromSpanContext(io.opentelemetry.api.trace.Span span, SpanContext newSpanContext, + OtelTraceContext otelTraceContext) { + this.span = span != null ? span : io.opentelemetry.api.trace.Span.wrap(newSpanContext); + this.newSpanContext = newSpanContext; + this.otelTraceContext = otelTraceContext; + } + + @Override + public io.opentelemetry.api.trace.Span setAttribute(String key, @Nullable String value) { + return span.setAttribute(key, value); + } + + @Override + public io.opentelemetry.api.trace.Span setAttribute(String key, long value) { + return span.setAttribute(key, value); + } + + @Override + public io.opentelemetry.api.trace.Span setAttribute(String key, double value) { + return span.setAttribute(key, value); + } + + @Override + public io.opentelemetry.api.trace.Span setAttribute(String key, boolean value) { + return span.setAttribute(key, value); + } + + @Override + public io.opentelemetry.api.trace.Span addEvent(String name) { + return span.addEvent(name); + } + + @Override + public io.opentelemetry.api.trace.Span addEvent(String name, long timestamp, TimeUnit unit) { + return span.addEvent(name, timestamp, unit); + } + + @Override + public io.opentelemetry.api.trace.Span addEvent(String name, Instant timestamp) { + return span.addEvent(name, timestamp); + } + + @Override + public io.opentelemetry.api.trace.Span addEvent(String name, Attributes attributes) { + return span.addEvent(name, attributes); + } + + @Override + public io.opentelemetry.api.trace.Span addEvent(String name, Attributes attributes, long timestamp, TimeUnit unit) { + return span.addEvent(name, attributes, timestamp, unit); + } + + @Override + public io.opentelemetry.api.trace.Span addEvent(String name, Attributes attributes, Instant timestamp) { + return span.addEvent(name, attributes, timestamp); + } + + @Override + public io.opentelemetry.api.trace.Span setStatus(StatusCode canonicalCode) { + return span.setStatus(canonicalCode); + } + + @Override + public io.opentelemetry.api.trace.Span setStatus(StatusCode canonicalCode, String description) { + return span.setStatus(canonicalCode, description); + } + + @Override + public io.opentelemetry.api.trace.Span setAttribute(AttributeKey key, int value) { + return span.setAttribute(key, value); + } + + @Override + public io.opentelemetry.api.trace.Span setAttribute(AttributeKey key, T value) { + return span.setAttribute(key, value); + } + + @Override + public io.opentelemetry.api.trace.Span recordException(Throwable exception) { + return span.recordException(exception); + } + + @Override + public io.opentelemetry.api.trace.Span recordException(Throwable exception, Attributes additionalAttributes) { + return span.recordException(exception, additionalAttributes); + } + + @Override + public io.opentelemetry.api.trace.Span updateName(String name) { + return span.updateName(name); + } + + @Override + public void end() { + span.end(); + } + + @Override + public void end(long timestamp, TimeUnit unit) { + span.end(timestamp, unit); + } + + @Override + public void end(Instant timestamp) { + span.end(timestamp); + } + + @Override + public SpanContext getSpanContext() { + return newSpanContext; + } + + @Override + public boolean isRecording() { + return span.isRecording(); + } + + @Override + public Context storeInContext(Context context) { + return span.storeInContext(context); + } + + @Override + public String toString() { + return "SpanFromSpanContext{" + "span=" + span + ", newSpanContext=" + newSpanContext + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SpanFromSpanContext that = (SpanFromSpanContext) o; + return Objects.equals(span, that.span) && Objects.equals(this.newSpanContext, that.newSpanContext); + } + + @Override + public int hashCode() { + return Objects.hash(this.span, this.newSpanContext); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/propagation/BaggageTextMapPropagator.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/propagation/BaggageTextMapPropagator.java new file mode 100644 index 00000000..ee6e6a1a --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/propagation/BaggageTextMapPropagator.java @@ -0,0 +1,90 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.propagation; + +import java.util.AbstractMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import io.micrometer.core.util.internal.logging.InternalLogger; +import io.micrometer.core.util.internal.logging.InternalLoggerFactory; +import io.micrometer.tracing.BaggageManager; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageBuilder; +import io.opentelemetry.api.baggage.BaggageEntryMetadata; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.context.propagation.TextMapSetter; + +/** + * {@link TextMapPropagator} that adds Sleuth compatible baggage entries (name of the + * field means an HTTP header entry). + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class BaggageTextMapPropagator implements TextMapPropagator { + + private static final InternalLogger log = InternalLoggerFactory.getInstance(BaggageTextMapPropagator.class); + + private final List remoteFields; + + private final BaggageManager baggageManager; + + public BaggageTextMapPropagator(List remoteFields, BaggageManager baggageManager) { + this.remoteFields = remoteFields; + this.baggageManager = baggageManager; + } + + @Override + public List fields() { + return this.remoteFields; + } + + @Override + public void inject(Context context, C c, TextMapSetter setter) { + List> baggageEntries = applicableBaggageEntries(c); + baggageEntries.forEach(e -> setter.set(c, e.getKey(), e.getValue())); + } + + private List> applicableBaggageEntries(C c) { + Map allBaggage = this.baggageManager.getAllBaggage(); + List lowerCaseKeys = this.remoteFields.stream().map(String::toLowerCase).collect(Collectors.toList()); + return allBaggage.entrySet().stream().filter(e -> lowerCaseKeys.contains(e.getKey().toLowerCase())) + .collect(Collectors.toList()); + } + + @Override + public Context extract(Context context, C c, TextMapGetter getter) { + Map baggageEntries = this.remoteFields.stream() + .map(s -> new AbstractMap.SimpleEntry<>(s, getter.get(c, s))).filter(e -> e.getValue() != null) + .collect(Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue())); + BaggageBuilder builder = Baggage.current().toBuilder(); + // TODO: [OTEL] magic string + baggageEntries + .forEach((key, value) -> builder.put(key, value, BaggageEntryMetadata.create("propagation=unlimited"))); + Baggage baggage = builder.build(); + Context withBaggage = context.with(baggage); + if (log.isDebugEnabled()) { + log.debug("Will propagate new baggage context for entries " + baggageEntries); + } + return withBaggage; + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/propagation/CompositeTextMapPropagator.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/propagation/CompositeTextMapPropagator.java new file mode 100644 index 00000000..946996b2 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/propagation/CompositeTextMapPropagator.java @@ -0,0 +1,167 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.propagation; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import io.micrometer.core.util.internal.logging.InternalLogger; +import io.micrometer.core.util.internal.logging.InternalLoggerFactory; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.context.propagation.TextMapSetter; +import io.opentelemetry.extension.aws.AwsXrayPropagator; +import io.opentelemetry.extension.trace.propagation.B3Propagator; +import io.opentelemetry.extension.trace.propagation.JaegerPropagator; +import io.opentelemetry.extension.trace.propagation.OtTracePropagator; + +/** + * {@link TextMapPropagator} implementation that can read / write from multiple + * {@link TextMapPropagator} implementations. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class CompositeTextMapPropagator implements TextMapPropagator { + + private static final InternalLogger log = InternalLoggerFactory.getInstance(CompositeTextMapPropagator.class); + + private final Map mapping = new HashMap<>(); + + private final List types; + + public CompositeTextMapPropagator(PropagationSupplier beanFactory, List types) { + this.types = types; + if (isOnClasspath(awsClass())) { + this.mapping.put(PropagationType.AWS, beanFactory.getProvider(AwsXrayPropagator.class) + .getIfAvailable(AwsXrayPropagator::getInstance)); + } + if (isOnClasspath(b3Class())) { + this.mapping.put(PropagationType.B3, beanFactory.getProvider(B3Propagator.class) + .getIfAvailable(B3Propagator::injectingSingleHeader)); + } + if (isOnClasspath(jaegerClass())) { + this.mapping.put(PropagationType.JAEGER, + beanFactory.getProvider(JaegerPropagator.class).getIfAvailable(JaegerPropagator::getInstance)); + } + if (isOnClasspath(otClass())) { + this.mapping.put(PropagationType.OT_TRACER, beanFactory.getProvider(OtTracePropagator.class) + .getIfAvailable(OtTracePropagator::getInstance)); + } + this.mapping.put(PropagationType.W3C, TextMapPropagator.composite(W3CTraceContextPropagator.getInstance(), + W3CBaggagePropagator.getInstance())); + this.mapping.put(PropagationType.CUSTOM, NoopTextMapPropagator.INSTANCE); + if (log.isDebugEnabled()) { + log.debug("Registered the following context propagation types " + this.mapping.keySet()); + } + } + + String otClass() { + return "io.opentelemetry.extension.trace.propagation.OtTracePropagator"; + } + + String jaegerClass() { + return "io.opentelemetry.extension.trace.propagation.JaegerPropagator"; + } + + String b3Class() { + return "io.opentelemetry.extension.trace.propagation.B3Propagator"; + } + + String awsClass() { + return "io.opentelemetry.extension.aws.AwsXrayPropagator"; + } + + private boolean isOnClasspath(String clazz) { + try { + Class.forName(clazz); + return true; + } + catch (ClassNotFoundException e) { + return false; + } + } + + @Override + public List fields() { + return this.types.stream().map(key -> this.mapping.getOrDefault(key, NoopTextMapPropagator.INSTANCE)) + .flatMap(p -> p.fields().stream()).collect(Collectors.toList()); + } + + @Override + public void inject(Context context, C carrier, TextMapSetter setter) { + this.types.stream().map(key -> this.mapping.getOrDefault(key, NoopTextMapPropagator.INSTANCE)) + .forEach(p -> p.inject(context, carrier, setter)); + } + + @Override + public Context extract(Context context, C carrier, TextMapGetter getter) { + for (PropagationType type : this.types) { + TextMapPropagator propagator = this.mapping.get(type); + if (propagator == null || propagator == NoopTextMapPropagator.INSTANCE) { + continue; + } + Context extractedContext = propagator.extract(context, carrier, getter); + Span span = Span.fromContextOrNull(extractedContext); + Baggage baggage = Baggage.fromContextOrNull(extractedContext); + if (span != null || baggage != null) { + return extractedContext; + } + } + return context; + } + + public interface PropagationSupplier { + + ObjectProvider getProvider(Class clazz); + + interface ObjectProvider { + + T getIfAvailable(Supplier supplier); + } + } + + private static final class NoopTextMapPropagator implements TextMapPropagator { + + private static final NoopTextMapPropagator INSTANCE = new NoopTextMapPropagator(); + + @Override + public List fields() { + return Collections.emptyList(); + } + + @Override + public void inject(Context context, C carrier, TextMapSetter setter) { + } + + @Override + public Context extract(Context context, C carrier, TextMapGetter getter) { + return context; + } + + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/propagation/PropagationType.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/propagation/PropagationType.java new file mode 100644 index 00000000..81aa4d06 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/propagation/PropagationType.java @@ -0,0 +1,57 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.propagation; + +/* + * Supported propagation types. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public enum PropagationType { + + /** + * AWS propagation type. + */ + AWS, + + /** + * B3 propagation type. + */ + B3, + + /** + * Jaeger propagation type. + */ + JAEGER, + + /** + * Lightstep propagation type. + */ + OT_TRACER, + + /** + * W3C propagation type. + */ + W3C, + + /** + * Custom propagation type. + */ + CUSTOM + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/test/java/io/micrometer/tracing/otel/bridge/BaggageTaggingSpanProcessorTest.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/test/java/io/micrometer/tracing/otel/bridge/BaggageTaggingSpanProcessorTest.java new file mode 100644 index 00000000..9ab025f3 --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/test/java/io/micrometer/tracing/otel/bridge/BaggageTaggingSpanProcessorTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.bridge; + +import java.util.Arrays; +import java.util.Collections; + +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.ReadWriteSpan; +import org.junit.jupiter.api.Test; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +class BaggageTaggingSpanProcessorTest { + + @Test + void interfaceMethods() { + BaggageTaggingSpanProcessor spanProcessor = new BaggageTaggingSpanProcessor(Collections.emptyList()); + assertThat(spanProcessor.isEndRequired()).isFalse(); + assertThat(spanProcessor.isStartRequired()).isTrue(); + } + + @Test + void onStart_emptyBaggage() { + BaggageTaggingSpanProcessor spanProcessor = new BaggageTaggingSpanProcessor(Arrays.asList("tagOne", "tagTwo")); + + Baggage baggage = Baggage.builder().build(); + ReadWriteSpan span = mock(ReadWriteSpan.class); + + spanProcessor.onStart(Context.root().with(baggage), span); + verifyNoInteractions(span); + } + + @Test + void onStart_withBaggage() { + BaggageTaggingSpanProcessor spanProcessor = new BaggageTaggingSpanProcessor(Arrays.asList("tagOne", "tagTwo")); + + Baggage baggage = Baggage.builder().put("tagOne", "valueOne").put("tagTwo", "valueTwo") + .put("otherTag", "otherValue").build(); + ReadWriteSpan span = mock(ReadWriteSpan.class); + + spanProcessor.onStart(Context.root().with(baggage), span); + verify(span).setAttribute(stringKey("tagOne"), "valueOne"); + verify(span).setAttribute(stringKey("tagTwo"), "valueTwo"); + + verifyNoMoreInteractions(span); + } + +} diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/test/java/io/micrometer/tracing/otel/propagation/CompositeTextMapPropagatorTest.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/test/java/io/micrometer/tracing/otel/propagation/CompositeTextMapPropagatorTest.java new file mode 100644 index 00000000..5be3bf2a --- /dev/null +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/test/java/io/micrometer/tracing/otel/propagation/CompositeTextMapPropagatorTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.otel.propagation; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.extension.aws.AwsXrayPropagator; +import io.opentelemetry.extension.trace.propagation.B3Propagator; +import io.opentelemetry.extension.trace.propagation.JaegerPropagator; +import io.opentelemetry.extension.trace.propagation.OtTracePropagator; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class CompositeTextMapPropagatorTest { + + @Test + void extract_onlyBaggage() { + CompositeTextMapPropagator compositeTextMapPropagator = new CompositeTextMapPropagator(new CompositeTextMapPropagator.PropagationSupplier() { + @Override + public ObjectProvider getProvider(Class clazz) { + return Supplier::get; + } + }, + Collections.singletonList(PropagationType.W3C)); + + Map carrier = new HashMap<>(); + carrier.put("baggage", "key=value"); + Context result = compositeTextMapPropagator.extract(Context.root(), carrier, new MapGetter()); + + assertThat(Baggage.fromContextOrNull(result)).isNotNull(); + assertThat(Baggage.fromContext(result)).isEqualTo(Baggage.builder().put("key", "value").build()); + } + + @Test + void should_map_propagaotr_string_class_names_to_actual_classes() { + CompositeTextMapPropagator propagator = new CompositeTextMapPropagator(new CompositeTextMapPropagator.PropagationSupplier() { + @Override + public ObjectProvider getProvider(Class clazz) { + return Supplier::get; + } + }, + Collections.emptyList()); + + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(propagator.awsClass()).isEqualTo(AwsXrayPropagator.class.getName()); + softly.assertThat(propagator.b3Class()).isEqualTo(B3Propagator.class.getName()); + softly.assertThat(propagator.jaegerClass()).isEqualTo(JaegerPropagator.class.getName()); + softly.assertThat(propagator.otClass()).isEqualTo(OtTracePropagator.class.getName()); + softly.assertAll(); + } + + private static class MapGetter implements TextMapGetter> { + + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + + @Override + public String get(Map carrier, String key) { + return carrier.get(key); + } + + } + +} diff --git a/micrometer-tracing-reporters/micrometer-tracing-reporter-wavefront/build.gradle b/micrometer-tracing-reporters/micrometer-tracing-reporter-wavefront/build.gradle new file mode 100644 index 00000000..7cd849af --- /dev/null +++ b/micrometer-tracing-reporters/micrometer-tracing-reporter-wavefront/build.gradle @@ -0,0 +1,23 @@ +plugins { + id 'idea' +} + +dependencies { + api project(':micrometer-tracing') + api 'io.micrometer:micrometer-core' + + optionalImplementation("io.zipkin.brave:brave") { + exclude group: "io.zipkin.reporter2" + exclude group: "io.zipkin.zipkin2" + } + optionalImplementation project(':micrometer-tracing-bridge-brave') + optionalImplementation project(':micrometer-tracing-bridge-otel') + optionalImplementation("io.opentelemetry:opentelemetry-sdk-trace") + + implementation("com.wavefront:wavefront-sdk-java") + implementation("com.wavefront:wavefront-internal-reporter-java") + + testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation 'org.assertj:assertj-core' +} + diff --git a/micrometer-tracing-reporters/micrometer-tracing-reporter-wavefront/src/main/java/io/micrometer/tracing/reporter/wavefront/WavefrontBraveSpanHandler.java b/micrometer-tracing-reporters/micrometer-tracing-reporter-wavefront/src/main/java/io/micrometer/tracing/reporter/wavefront/WavefrontBraveSpanHandler.java new file mode 100755 index 00000000..ebb95d1a --- /dev/null +++ b/micrometer-tracing-reporters/micrometer-tracing-reporter-wavefront/src/main/java/io/micrometer/tracing/reporter/wavefront/WavefrontBraveSpanHandler.java @@ -0,0 +1,59 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.reporter.wavefront; + +import java.io.Closeable; + +import brave.handler.MutableSpan; +import brave.handler.SpanHandler; +import brave.propagation.TraceContext; +import io.micrometer.tracing.brave.bridge.BraveFinishedSpan; +import io.micrometer.tracing.brave.bridge.BraveTraceContext; + +/** + * A {@link SpanHandler} that sends spans to Wavefront. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class WavefrontBraveSpanHandler extends SpanHandler implements Runnable, Closeable { + + private final WavefrontSpringObservabilitySpanHandler spanHandler; + + /** + * @param spanHandler wavefront span handler + */ + public WavefrontBraveSpanHandler(WavefrontSpringObservabilitySpanHandler spanHandler) { + this.spanHandler = spanHandler; + } + + @Override + public boolean end(TraceContext context, MutableSpan span, Cause cause) { + return spanHandler.end(BraveTraceContext.fromBrave(context), BraveFinishedSpan.fromBrave(span)); + } + + @Override + public void close() { + this.spanHandler.close(); + } + + @Override + public void run() { + this.spanHandler.run(); + } + +} diff --git a/micrometer-tracing-reporters/micrometer-tracing-reporter-wavefront/src/main/java/io/micrometer/tracing/reporter/wavefront/WavefrontOtelSpanHandler.java b/micrometer-tracing-reporters/micrometer-tracing-reporter-wavefront/src/main/java/io/micrometer/tracing/reporter/wavefront/WavefrontOtelSpanHandler.java new file mode 100755 index 00000000..504e4a8a --- /dev/null +++ b/micrometer-tracing-reporters/micrometer-tracing-reporter-wavefront/src/main/java/io/micrometer/tracing/reporter/wavefront/WavefrontOtelSpanHandler.java @@ -0,0 +1,85 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.reporter.wavefront; + +import java.util.Collection; + +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.otel.bridge.OtelFinishedSpan; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; + +/** + * A {@link SpanExporter} that sends spans to Wavefront. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class WavefrontOtelSpanHandler implements SpanExporter { + + private final WavefrontSpringObservabilitySpanHandler spanHandler; + + /** + * @param spanHandler wavefront span handler + */ + public WavefrontOtelSpanHandler(WavefrontSpringObservabilitySpanHandler spanHandler) { + this.spanHandler = spanHandler; + } + + @Override + public CompletableResultCode export(Collection spans) { + spans.forEach(spanData -> spanHandler.end(traceContext(spanData), OtelFinishedSpan.fromOtel(spanData))); + return CompletableResultCode.ofSuccess(); + } + + private TraceContext traceContext(SpanData spanData) { + return new TraceContext() { + @Override + public String traceId() { + return spanData.getTraceId(); + } + + @Override + public String parentId() { + return spanData.getParentSpanId(); + } + + @Override + public String spanId() { + return spanData.getSpanId(); + } + + @Override + public Boolean sampled() { + return spanData.getSpanContext().isSampled(); + } + }; + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + spanHandler.close(); + return CompletableResultCode.ofSuccess(); + } + +} diff --git a/micrometer-tracing-reporters/micrometer-tracing-reporter-wavefront/src/main/java/io/micrometer/tracing/reporter/wavefront/WavefrontSpringObservabilitySpanHandler.java b/micrometer-tracing-reporters/micrometer-tracing-reporter-wavefront/src/main/java/io/micrometer/tracing/reporter/wavefront/WavefrontSpringObservabilitySpanHandler.java new file mode 100755 index 00000000..c8a5bebd --- /dev/null +++ b/micrometer-tracing-reporters/micrometer-tracing-reporter-wavefront/src/main/java/io/micrometer/tracing/reporter/wavefront/WavefrontSpringObservabilitySpanHandler.java @@ -0,0 +1,483 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.reporter.wavefront; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import com.wavefront.internal.reporter.WavefrontInternalReporter; +import com.wavefront.java_sdk.com.google.common.collect.Iterators; +import com.wavefront.java_sdk.com.google.common.collect.Sets; +import com.wavefront.sdk.common.NamedThreadFactory; +import com.wavefront.sdk.common.Pair; +import com.wavefront.sdk.common.WavefrontSender; +import com.wavefront.sdk.common.application.ApplicationTags; +import com.wavefront.sdk.entities.tracing.SpanLog; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.util.internal.logging.InternalLogger; +import io.micrometer.core.util.internal.logging.InternalLoggerFactory; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.exporter.FinishedSpan; +import io.micrometer.tracing.util.StringUtils; + +import static com.wavefront.internal.SpanDerivedMetricsUtils.TRACING_DERIVED_PREFIX; +import static com.wavefront.internal.SpanDerivedMetricsUtils.reportHeartbeats; +import static com.wavefront.internal.SpanDerivedMetricsUtils.reportWavefrontGeneratedData; +import static com.wavefront.sdk.common.Constants.APPLICATION_TAG_KEY; +import static com.wavefront.sdk.common.Constants.CLUSTER_TAG_KEY; +import static com.wavefront.sdk.common.Constants.COMPONENT_TAG_KEY; +import static com.wavefront.sdk.common.Constants.DEBUG_TAG_KEY; +import static com.wavefront.sdk.common.Constants.ERROR_TAG_KEY; +import static com.wavefront.sdk.common.Constants.NULL_TAG_VAL; +import static com.wavefront.sdk.common.Constants.SERVICE_TAG_KEY; +import static com.wavefront.sdk.common.Constants.SHARD_TAG_KEY; +import static com.wavefront.sdk.common.Constants.SOURCE_KEY; +import static com.wavefront.sdk.common.Constants.SPAN_LOG_KEY; + +/** + * This converts a span recorded by Brave and invokes {@link WavefrontSender#sendSpan}. + * + *

+ * This uses a combination of conversion approaches from Wavefront projects: + *

    + *
  • https://github.com/wavefrontHQ/wavefront-opentracing-sdk-java
  • + *
  • https://github.com/wavefrontHQ/wavefront-proxy
  • + *
+ * + *

+ * On conflict, we make a comment and prefer wavefront-opentracing-sdk-java. The rationale + * is wavefront-opentracing-sdk-java uses the same {@link WavefrontSender#sendSpan} + * library, so it is easier to reason with. This policy can be revisited by future + * maintainers. + * + *

+ * Note:UUID conversions follow the same conventions used in practice in + * Wavefront. Ex. + * https://github.com/wavefrontHQ/wavefront-opentracing-sdk-java/blob/6babf2ff95daa37452e1e8c35ae54b58b6abb50f/src/main/java/com/wavefront/opentracing/propagation/JaegerWavefrontPropagator.java#L191-L204 + * While in practice this is not a problem, it is worth mentioning that this convention + * will only only result in RFC 4122 timestamp (version 1) format by accident. In other + * words, don't call {@link UUID#timestamp()} on UUIDs converted here, or in other + * Wavefront code, as it might throw an exception. + * + * @since 3.1.0 + */ +public class WavefrontSpringObservabilitySpanHandler implements Runnable, Closeable { + + private static final InternalLogger LOG = InternalLoggerFactory.getInstance(WavefrontSpringObservabilitySpanHandler.class); + + // https://github.com/wavefrontHQ/wavefront-proxy/blob/3dd1fa11711a04de2d9d418e2269f0f9fb464f36/proxy/src/main/java/com/wavefront/agent/listeners/tracing/ZipkinPortUnificationHandler.java#L114-L114 + private static final String DEFAULT_SPAN_NAME = "defaultOperation"; + + private static final String DEFAULT_SOURCE = "wavefront-spring-boot"; + + private static final String WAVEFRONT_GENERATED_COMPONENT = "wavefront-generated"; + + private static final int LONG_BYTES = Long.SIZE / Byte.SIZE; + + private static final int BYTE_BASE16 = 2; + + private static final int LONG_BASE16 = BYTE_BASE16 * LONG_BYTES; + + private static final int TRACE_ID_HEX_SIZE = 2 * LONG_BASE16; + + private static final String ALPHABET = "0123456789abcdef"; + + private static final int ASCII_CHARACTERS = 128; + + private static final byte[] DECODING = buildDecodingArray(); + + private final LinkedBlockingQueue> spanBuffer; + + private final WavefrontSender wavefrontSender; + + private final WavefrontInternalReporter wfInternalReporter; + + private final Set traceDerivedCustomTagKeys; + + private final Counter spansDropped; + + private final Counter spansReceived; + + private final Counter reportErrors; + + private final Thread sendingThread; + + private final Set, String>> discoveredHeartbeatMetrics; + + private final ScheduledExecutorService heartbeatMetricsScheduledExecutorService; + + private final String source; + + private final List> defaultTags; + + private final Set defaultTagKeys; + + private final ApplicationTags applicationTags; + + private volatile boolean stop = false; + + /** + * @param maxQueueSize maximal span queue size + * @param wavefrontSender wavefront server + * @param meterRegistry meter registry + * @param source source of metrics and spans + * @param applicationTags additional application tags + * @param redMetricsCustomTagKeys RED metrics custom tag keys + */ + public WavefrontSpringObservabilitySpanHandler(int maxQueueSize, WavefrontSender wavefrontSender, MeterRegistry meterRegistry, + String source, ApplicationTags applicationTags, Set redMetricsCustomTagKeys) { + this.wavefrontSender = wavefrontSender; + this.applicationTags = applicationTags; + this.discoveredHeartbeatMetrics = Sets.newConcurrentHashSet(); + + this.heartbeatMetricsScheduledExecutorService = Executors.newScheduledThreadPool(1, + new NamedThreadFactory("observability-heart-beater").setDaemon(true)); + + // Emit Heartbeats Metrics every 1 min. + heartbeatMetricsScheduledExecutorService.scheduleAtFixedRate(() -> { + try { + reportHeartbeats(wavefrontSender, discoveredHeartbeatMetrics, WAVEFRONT_GENERATED_COMPONENT); + } + catch (IOException e) { + LOG.warn("Cannot report heartbeat metric to wavefront"); + } + }, 1, 60, TimeUnit.SECONDS); + + this.traceDerivedCustomTagKeys = new HashSet<>(redMetricsCustomTagKeys); + + // Start the reporter + wfInternalReporter = new WavefrontInternalReporter.Builder().prefixedWith(TRACING_DERIVED_PREFIX) + .withSource(DEFAULT_SOURCE).reportMinuteDistribution().build(wavefrontSender); + wfInternalReporter.start(1, TimeUnit.MINUTES); + + this.source = source; + this.defaultTags = createDefaultTags(applicationTags); + this.defaultTagKeys = defaultTags.stream().map(p -> p._1).collect(Collectors.toSet()); + this.defaultTagKeys.add(SOURCE_KEY); + + this.spanBuffer = new LinkedBlockingQueue<>(maxQueueSize); + + // init internal metrics + meterRegistry.gauge("reporter.queue.size", spanBuffer, sb -> (double) sb.size()); + meterRegistry.gauge("reporter.queue.remaining_capacity", spanBuffer, sb -> (double) sb.remainingCapacity()); + this.spansReceived = meterRegistry.counter("reporter.spans.received"); + this.spansDropped = meterRegistry.counter("reporter.spans.dropped"); + this.reportErrors = meterRegistry.counter("reporter.errors"); + + this.sendingThread = new Thread(this, "wavefrontSpanReporter"); + this.sendingThread.setDaemon(true); + this.sendingThread.start(); + } + + private static byte[] buildDecodingArray() { + byte[] decoding = new byte[ASCII_CHARACTERS]; + Arrays.fill(decoding, (byte) -1); + for (int i = 0; i < ALPHABET.length(); i++) { + char c = ALPHABET.charAt(i); + decoding[c] = (byte) i; + } + return decoding; + } + + /** + * Returns the {@code long} value whose base16 representation is stored in the first + * 16 chars of {@code chars} starting from the {@code offset}. + * @param chars the base16 representation of the {@code long}. + */ + private static long longFromBase16String(CharSequence chars) { + int offset = 0; + return (decodeByte(chars.charAt(offset), chars.charAt(offset + 1)) & 0xFFL) << 56 + | (decodeByte(chars.charAt(offset + 2), chars.charAt(offset + 3)) & 0xFFL) << 48 + | (decodeByte(chars.charAt(offset + 4), chars.charAt(offset + 5)) & 0xFFL) << 40 + | (decodeByte(chars.charAt(offset + 6), chars.charAt(offset + 7)) & 0xFFL) << 32 + | (decodeByte(chars.charAt(offset + 8), chars.charAt(offset + 9)) & 0xFFL) << 24 + | (decodeByte(chars.charAt(offset + 10), chars.charAt(offset + 11)) & 0xFFL) << 16 + | (decodeByte(chars.charAt(offset + 12), chars.charAt(offset + 13)) & 0xFFL) << 8 + | (decodeByte(chars.charAt(offset + 14), chars.charAt(offset + 15)) & 0xFFL); + } + + private static byte decodeByte(char hi, char lo) { + int decoded = DECODING[hi] << 4 | DECODING[lo]; + return (byte) decoded; + } + + // https://github.com/wavefrontHQ/wavefront-proxy/blob/3dd1fa11711a04de2d9d418e2269f0f9fb464f36/proxy/src/main/java/com/wavefront/agent/listeners/tracing/ZipkinPortUnificationHandler.java#L397-L402 + static List convertAnnotationsToSpanLogs(FinishedSpan span) { + int annotationCount = span.getEvents().size(); + if (annotationCount == 0) { + return Collections.emptyList(); + } + List spanLogs = new ArrayList<>(annotationCount); + for (int i = 0; i < annotationCount; i++) { + Map.Entry entry = Iterators.get(span.getEvents().iterator(), i); + long epochMicros = entry.getKey(); + String value = entry.getValue(); + spanLogs.add(new SpanLog(epochMicros, Collections.singletonMap("annotation", value))); + } + return spanLogs; + } + + // https://github.com/wavefrontHQ/wavefront-opentracing-sdk-java/blob/f1f08d8daf7b692b9b61dcd5bc24ca6befa8e710/src/main/java/com/wavefront/opentracing/WavefrontTracer.java#L275-L280 + static List> createDefaultTags(ApplicationTags applicationTags) { + List> result = new ArrayList<>(); + result.add(Pair.of(APPLICATION_TAG_KEY, applicationTags.getApplication())); + result.add(Pair.of(SERVICE_TAG_KEY, applicationTags.getService())); + result.add(Pair.of(CLUSTER_TAG_KEY, + applicationTags.getCluster() == null ? NULL_TAG_VAL : applicationTags.getCluster())); + result.add( + Pair.of(SHARD_TAG_KEY, applicationTags.getShard() == null ? NULL_TAG_VAL : applicationTags.getShard())); + if (applicationTags.getCustomTags() != null) { + applicationTags.getCustomTags().forEach((k, v) -> result.add(Pair.of(k, v))); + } + return result; + } + + /** + * Exact same behavior as WavefrontSpanReporter. + * https://github.com/wavefrontHQ/wavefront-opentracing-sdk-java/blob/f1f08d8daf7b692b9b61dcd5bc24ca6befa8e710/src/main/java/com/wavefront/opentracing/reporting/WavefrontSpanReporter.java#L163-L179 + * @param context trace context + * @param span reported span + * @return should other handler be ran + */ + public boolean end(TraceContext context, FinishedSpan span) { + spansReceived.increment(); + if (!spanBuffer.offer(Pair.of(context, span))) { + spansDropped.increment(); + if (LOG.isWarnEnabled()) { + LOG.warn("Buffer full, dropping span: " + span); + LOG.warn("Total spans dropped: " + spansDropped.count()); + } + } + return true; // regardless of error, other handlers should run + } + + List> getDefaultTags() { + return Collections.unmodifiableList(this.defaultTags); + } + + private String padLeftWithZeros(String string, int length) { + if (string.length() >= length) { + return string; + } + else { + StringBuilder sb = new StringBuilder(length); + for (int i = string.length(); i < length; i++) { + sb.append('0'); + } + + return sb.append(string).toString(); + } + } + + private void send(TraceContext context, FinishedSpan span) { + String traceIdString = padLeftWithZeros(context.traceId(), TRACE_ID_HEX_SIZE); + String traceIdHigh = traceIdString.substring(0, traceIdString.length() / 2); + String traceIdLow = traceIdString.substring(traceIdString.length() / 2); + UUID traceId = new UUID(longFromBase16String(traceIdHigh), longFromBase16String(traceIdLow)); + UUID spanId = new UUID(0L, longFromBase16String(context.spanId())); + + // NOTE: wavefront-opentracing-sdk-java and wavefront-proxy differ, but we prefer + // the former. + // https://github.com/wavefrontHQ/wavefront-opentracing-sdk-java/blob/f1f08d8daf7b692b9b61dcd5bc24ca6befa8e710/src/main/java/com/wavefront/opentracing/reporting/WavefrontSpanReporter.java#L187-L190 + // https://github.com/wavefrontHQ/wavefront-proxy/blob/3dd1fa11711a04de2d9d418e2269f0f9fb464f36/proxy/src/main/java/com/wavefront/agent/listeners/tracing/ZipkinPortUnificationHandler.java#L248-L252 + List parents = null; + String parentId = context.parentId(); + if (!StringUtils.isEmpty(parentId) && longFromBase16String(parentId) != 0L) { + parents = Collections.singletonList(new UUID(0L, longFromBase16String(parentId))); + } + List followsFrom = null; + + // https://github.com/wavefrontHQ/wavefront-proxy/blob/3dd1fa11711a04de2d9d418e2269f0f9fb464f36/proxy/src/main/java/com/wavefront/agent/listeners/tracing/ZipkinPortUnificationHandler.java#L344-L345 + String name = span.getName(); + if (name == null) { + name = DEFAULT_SPAN_NAME; + } + + // Start and duration become 0L if unset. Any positive duration rounds up to 1 + // millis. + long startMillis = span.getStartTimestamp() / 1000L; + long finishMillis = span.getEndTimestamp() / 1000L; + long durationMicros = span.getEndTimestamp() - span.getStartTimestamp(); + long durationMillis = startMillis != 0 && finishMillis != 0L ? Math.max(finishMillis - startMillis, 1L) : 0L; + + List spanLogs = convertAnnotationsToSpanLogs(span); + TagList tags = new TagList(defaultTagKeys, defaultTags, span); + + try { + wavefrontSender.sendSpan(name, startMillis, durationMillis, source, traceId, spanId, parents, followsFrom, + tags, spanLogs); + } + catch (IOException | RuntimeException t) { + if (LOG.isDebugEnabled()) { + LOG.debug("error sending span " + context, t); + } + this.reportErrors.increment(); + } + + // report stats irrespective of span sampling. + if (wfInternalReporter != null) { + // report converted metrics/histograms from the span + try { + discoveredHeartbeatMetrics.add(reportWavefrontGeneratedData(wfInternalReporter, name, + applicationTags.getApplication(), applicationTags.getService(), + applicationTags.getCluster() == null ? NULL_TAG_VAL : applicationTags.getCluster(), + applicationTags.getShard() == null ? NULL_TAG_VAL : applicationTags.getShard(), source, + tags.componentTagValue, tags.isError, durationMicros, traceDerivedCustomTagKeys, tags)); + } + catch (RuntimeException t) { + if (LOG.isDebugEnabled()) { + LOG.debug("error sending span RED metrics " + context, t); + } + this.reportErrors.increment(); + } + } + } + + @Override + public void run() { + while (!stop) { + try { + Pair contextAndSpan = spanBuffer.take(); + send(contextAndSpan._1, contextAndSpan._2); + } + catch (InterruptedException ex) { + if (LOG.isInfoEnabled()) { + LOG.info("reporting thread interrupted"); + } + } + catch (Throwable ex) { + LOG.warn("Error processing buffer", ex); + } + } + } + + @Override + public void close() { + stop = true; + try { + // wait for 5 secs max + sendingThread.join(5000); + heartbeatMetricsScheduledExecutorService.shutdownNow(); + } + catch (InterruptedException ex) { + // no-op + } + } + + /** + * Extracted for test isolation and as parsing otherwise implies multiple-returns or + * scanning later. + * + *

+ * Ex. {@code SpanDerivedMetricsUtils#reportWavefrontGeneratedData} needs tags + * separately from the component tag and error status. + */ + static final class TagList extends ArrayList> { + + String componentTagValue = NULL_TAG_VAL; + + boolean isError; // See explanation here: + + // https://github.com/openzipkin/brave/pull/1221 + + TagList(Set defaultTagKeys, List> defaultTags, FinishedSpan span) { + super(defaultTags.size() + span.getTags().size()); + // TODO: OTel doesn't have a notion of debug + boolean debug = false; + boolean hasAnnotations = span.getEvents().size() > 0; + isError = span.getError() != null; + + int tagCount = span.getTags().size(); + addAll(defaultTags); + for (int i = 0; i < tagCount; i++) { + String tagKey = Iterators.get(span.getTags().keySet().iterator(), i); + String tagValue = Iterators.get(span.getTags().values().iterator(), i); + String key = tagKey; + String value = tagValue; + String lcKey = key.toLowerCase(Locale.ROOT); + if (lcKey.equals(ERROR_TAG_KEY)) { + isError = true; + continue; // We later replace whatever the potentially empty value was + // with "true" + } + if (value.isEmpty()) { + continue; + } + if (defaultTagKeys.contains(lcKey)) { + continue; + } + if (lcKey.equals(DEBUG_TAG_KEY)) { + debug = true; // This tag is set out-of-band + continue; + } + if (lcKey.equals(COMPONENT_TAG_KEY)) { + componentTagValue = value; + } + add(Pair.of(key, value)); + } + + // Check for span.error() for uncaught exception in request mapping and add it + // to Wavefront span tag + if (isError) { + add(Pair.of("error", "true")); + } + + // https://github.com/wavefrontHQ/wavefront-proxy/blob/3dd1fa11711a04de2d9d418e2269f0f9fb464f36/proxy/src/main/java/com/wavefront/agent/listeners/tracing/ZipkinPortUnificationHandler.java#L300-L303 + if (debug) { + add(Pair.of(DEBUG_TAG_KEY, "true")); + } + + // https://github.com/wavefrontHQ/wavefront-proxy/blob/3dd1fa11711a04de2d9d418e2269f0f9fb464f36/proxy/src/main/java/com/wavefront/agent/listeners/tracing/ZipkinPortUnificationHandler.java#L254-L266 + if (span.getKind() != null) { + String kind = span.getKind().toString().toLowerCase(); + add(Pair.of("span.kind", kind)); + if (hasAnnotations) { + add(Pair.of("_spanSecondaryId", kind)); + } + } + + // https://github.com/wavefrontHQ/wavefront-proxy/blob/3dd1fa11711a04de2d9d418e2269f0f9fb464f36/proxy/src/main/java/com/wavefront/agent/listeners/tracing/ZipkinPortUnificationHandler.java#L329-L332 + if (hasAnnotations) { + add(Pair.of(SPAN_LOG_KEY, "true")); + } + + // https://github.com/wavefrontHQ/wavefront-proxy/blob/3dd1fa11711a04de2d9d418e2269f0f9fb464f36/proxy/src/main/java/com/wavefront/agent/listeners/tracing/ZipkinPortUnificationHandler.java#L324-L327 + if (span.getLocalIp() != null) { + String localIp = span.getLocalIp(); + String version = localIp.contains(":") ? "ipv6" : "ipv4"; + add(Pair.of(version, localIp)); + } + } + + } + +} diff --git a/micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/build.gradle b/micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/build.gradle new file mode 100644 index 00000000..4aec1acd --- /dev/null +++ b/micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/build.gradle @@ -0,0 +1,44 @@ +plugins { + id 'idea' +} + +dependencies { + api project(':micrometer-tracing') + + // Brave + optionalApi project(':micrometer-tracing-bridge-brave') + optionalApi("io.zipkin.brave:brave") + optionalApi("io.zipkin.brave:brave-context-slf4j") + optionalApi("io.zipkin.brave:brave-instrumentation-http") + optionalApi("io.zipkin.aws:brave-propagation-aws") + optionalApi("io.zipkin.reporter2:zipkin-reporter-brave") + + // OTel + optionalApi project(':micrometer-tracing-bridge-otel') + optionalApi("io.opentelemetry:opentelemetry-exporter-zipkin") + optionalApi("io.opentelemetry:opentelemetry-sdk-trace") + optionalApi("io.opentelemetry:opentelemetry-extension-trace-propagators") + optionalApi("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api") + + // Zipkin + implementation("io.zipkin.zipkin2:zipkin") + implementation("io.zipkin.reporter2:zipkin-reporter") + optionalImplementation("io.zipkin.reporter2:zipkin-sender-urlconnection") + optionalImplementation("io.zipkin.reporter2:zipkin-sender-kafka") { + exclude group: "org.apache.kafka", module: "kafka-clients" + } + optionalImplementation("io.zipkin.reporter2:zipkin-sender-activemq-client") { + exclude group: "org.apache.activemq", module: "activemq-client" + } + optionalImplementation("io.zipkin.reporter2:zipkin-sender-amqp-client") { + exclude group: "com.rabbitmq", module: "amqp-client" + } + + // JUnit 5 + testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation 'com.squareup.okhttp3:mockwebserver' + testImplementation 'org.assertj:assertj-core' + testImplementation 'org.awaitility:awaitility' + testImplementation 'ch.qos.logback:logback-classic' +} + diff --git a/micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/src/main/java/io/micrometer/tracing/reporter/zipkin/ZipkinBraveSetup.java b/micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/src/main/java/io/micrometer/tracing/reporter/zipkin/ZipkinBraveSetup.java new file mode 100644 index 00000000..e5e24e0b --- /dev/null +++ b/micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/src/main/java/io/micrometer/tracing/reporter/zipkin/ZipkinBraveSetup.java @@ -0,0 +1,255 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.reporter.zipkin; + +import java.util.Arrays; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import brave.Tracing; +import brave.http.HttpTracing; +import brave.sampler.Sampler; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.TimerRecordingHandler; +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.brave.bridge.BraveBaggageManager; +import io.micrometer.tracing.brave.bridge.BraveCurrentTraceContext; +import io.micrometer.tracing.brave.bridge.BraveHttpClientHandler; +import io.micrometer.tracing.brave.bridge.BraveHttpServerHandler; +import io.micrometer.tracing.brave.bridge.BraveTracer; +import io.micrometer.tracing.handler.DefaultTracingRecordingHandler; +import io.micrometer.tracing.handler.HttpClientTracingRecordingHandler; +import io.micrometer.tracing.handler.HttpServerTracingRecordingHandler; +import io.micrometer.tracing.http.HttpClientHandler; +import io.micrometer.tracing.http.HttpServerHandler; +import io.micrometer.tracing.reporter.zipkin.ZipkinBraveSetup.Builder.BraveBuildingBlocks; +import zipkin2.Span; +import zipkin2.reporter.AsyncReporter; +import zipkin2.reporter.Reporter; +import zipkin2.reporter.brave.ZipkinSpanHandler; +import zipkin2.reporter.urlconnection.URLConnectionSender; + +/** + * Work in progress. Requires HTTP instrumentation dependency to be on the classpath. + * + * Provides Zipkin setup with Brave. + */ +public final class ZipkinBraveSetup implements AutoCloseable { + + private final Consumer closingFunction; + + private final BraveBuildingBlocks braveBuildingBlocks; + + ZipkinBraveSetup(Consumer closingFunction, BraveBuildingBlocks braveBuildingBlocks) { + this.closingFunction = closingFunction; + this.braveBuildingBlocks = braveBuildingBlocks; + } + + @Override + public void close() { + this.closingFunction.accept(this.braveBuildingBlocks); + } + + /** + * @return all the Brave building blocks required to communicate with Zipkin + */ + public BraveBuildingBlocks getBuildingBlocks() { + return this.braveBuildingBlocks; + } + + /** + * @return builder for the {@link ZipkinBraveSetup} + */ + public static ZipkinBraveSetup.Builder builder() { + return new ZipkinBraveSetup.Builder(); + } + + /** + * Builder for Brave with Zipkin. + */ + public static class Builder { + + private Supplier> reporter; + + private Function tracing; + + private Function tracer; + + private Function httpTracing; + + private Function httpServerHandler; + + private Function httpClientHandler; + + private Function handlers; + + private Consumer closingFunction; + + /** + * All Brave building blocks required to communicate with Zipkin. + */ + public static class BraveBuildingBlocks { + public final AsyncReporter reporter; + public final Tracing tracing; + public final Tracer tracer; + public final HttpTracing httpTracing; + public final HttpServerHandler httpServerHandler; + public final HttpClientHandler httpClientHandler; + + public BraveBuildingBlocks(AsyncReporter reporter, Tracing tracing, Tracer tracer, HttpTracing httpTracing, HttpServerHandler httpServerHandler, HttpClientHandler httpClientHandler) { + this.reporter = reporter; + this.tracing = tracing; + this.tracer = tracer; + this.httpTracing = httpTracing; + this.httpServerHandler = httpServerHandler; + this.httpClientHandler = httpClientHandler; + } + } + + public Builder reporter(Supplier> reporter) { + this.reporter = reporter; + return this; + } + + public Builder tracing(Function tracing) { + this.tracing = tracing; + return this; + } + + public Builder tracer(Function tracer) { + this.tracer = tracer; + return this; + } + + public Builder httpTracing(Function httpTracing) { + this.httpTracing = httpTracing; + return this; + } + + public Builder httpServerHandler(Function httpServerHandler) { + this.httpServerHandler = httpServerHandler; + return this; + } + + public Builder httpClientHandler(Function httpClientHandler) { + this.httpClientHandler = httpClientHandler; + return this; + } + + public Builder handlers(Function tracingHandlers) { + this.handlers = tracingHandlers; + return this; + } + + public Builder closingFunction(Consumer closingFunction) { + this.closingFunction = closingFunction; + return this; + } + + /** + * @param meterRegistry meter registry to which the {@link TimerRecordingHandler} should be attached + * @return setup with all Brave building blocks + */ + public ZipkinBraveSetup register(MeterRegistry meterRegistry) { + AsyncReporter reporter = this.reporter != null ? this.reporter.get() : reporter(); + Tracing tracing = this.tracing != null ? this.tracing.apply(reporter) : tracing(reporter); + Tracer tracer = this.tracer != null ? this.tracer.apply(tracing) : tracer(tracing); + HttpTracing httpTracing = this.httpTracing != null ? this.httpTracing.apply(tracing) : httpTracing(tracing); + HttpServerHandler httpServerHandler = this.httpServerHandler != null ? this.httpServerHandler.apply(httpTracing) : httpServerHandler(httpTracing); + HttpClientHandler httpClientHandler = this.httpClientHandler != null ? this.httpClientHandler.apply(httpTracing) : httpClientHandler(httpTracing); + BraveBuildingBlocks braveBuildingBlocks = new BraveBuildingBlocks(reporter, tracing, tracer, httpTracing, httpServerHandler, httpClientHandler); + TimerRecordingHandler tracingHandlers = this.handlers != null ? this.handlers.apply(braveBuildingBlocks) : tracingHandlers(braveBuildingBlocks); + meterRegistry.config().timerRecordingListener(tracingHandlers); + Consumer closingFunction = this.closingFunction != null ? this.closingFunction : closingFunction(braveBuildingBlocks); + return new ZipkinBraveSetup(closingFunction, braveBuildingBlocks); + } + + private static AsyncReporter reporter() { + return AsyncReporter + .builder(URLConnectionSender.newBuilder() + .connectTimeout(1000) + .readTimeout(1000) + .endpoint("http://localhost:9411/api/v2/spans").build()) + .build(); + } + + private static Tracer tracer(Tracing tracing) { + return new BraveTracer(tracing.tracer(), new BraveCurrentTraceContext(tracing.currentTraceContext()), new BraveBaggageManager()); + } + + private static Tracing tracing(AsyncReporter reporter) { + return Tracing.newBuilder() + .addSpanHandler(ZipkinSpanHandler.newBuilder(reporter).build()) + .sampler(Sampler.ALWAYS_SAMPLE) + .build(); + } + + private static HttpTracing httpTracing(Tracing tracing) { + return HttpTracing.newBuilder(tracing).build(); + } + + private static HttpServerHandler httpServerHandler(HttpTracing httpTracing) { + return new BraveHttpServerHandler(brave.http.HttpServerHandler.create(httpTracing)); + } + + private static HttpClientHandler httpClientHandler(HttpTracing httpTracing) { + return new BraveHttpClientHandler(brave.http.HttpClientHandler.create(httpTracing)); + } + + private static Consumer closingFunction(BraveBuildingBlocks braveBuildingBlocks) { + return deps -> { + deps.httpTracing.close(); + deps.tracing.close(); + AsyncReporter reporter = deps.reporter; + reporter.flush(); + reporter.close(); + }; + } + + @SuppressWarnings("rawtypes") + private static TimerRecordingHandler tracingHandlers(BraveBuildingBlocks braveBuildingBlocks) { + Tracer tracer = braveBuildingBlocks.tracer; + HttpServerHandler httpServerHandler = braveBuildingBlocks.httpServerHandler; + HttpClientHandler httpClientHandler = braveBuildingBlocks.httpClientHandler; + return new TimerRecordingHandler.FirstMatchingCompositeTimerRecordingHandler(Arrays.asList(new HttpServerTracingRecordingHandler(tracer, httpServerHandler), new HttpClientTracingRecordingHandler(tracer, httpClientHandler), new DefaultTracingRecordingHandler(tracer))); + } + + } + + /** + * Runs the given lambda with Zipkin setup. + * @param meterRegistry meter registry to register the handlers against + * @param consumer lambda to be executed with the building blocks + */ + public static void run(MeterRegistry meterRegistry, Consumer consumer) { + run(ZipkinBraveSetup.builder().register(meterRegistry), consumer); + } + + /** + * @param localZipkinBrave Brave setup with Zipkin + * @param consumer runnable to run + */ + public static void run(ZipkinBraveSetup localZipkinBrave, Consumer consumer) { + try { + consumer.accept(localZipkinBrave.getBuildingBlocks()); + } + finally { + localZipkinBrave.close(); + } + } +} diff --git a/micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/src/main/java/io/micrometer/tracing/reporter/zipkin/ZipkinOtelSetup.java b/micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/src/main/java/io/micrometer/tracing/reporter/zipkin/ZipkinOtelSetup.java new file mode 100644 index 00000000..31954411 --- /dev/null +++ b/micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/src/main/java/io/micrometer/tracing/reporter/zipkin/ZipkinOtelSetup.java @@ -0,0 +1,277 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.reporter.zipkin; + +import java.util.Arrays; +import java.util.Collections; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.regex.Pattern; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.TimerRecordingHandler; +import io.micrometer.tracing.SamplerFunction; +import io.micrometer.tracing.handler.DefaultTracingRecordingHandler; +import io.micrometer.tracing.handler.HttpClientTracingRecordingHandler; +import io.micrometer.tracing.handler.HttpServerTracingRecordingHandler; +import io.micrometer.tracing.http.HttpClientHandler; +import io.micrometer.tracing.http.HttpServerHandler; +import io.micrometer.tracing.otel.bridge.DefaultHttpClientAttributesExtractor; +import io.micrometer.tracing.otel.bridge.DefaultHttpServerAttributesExtractor; +import io.micrometer.tracing.otel.bridge.OtelBaggageManager; +import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; +import io.micrometer.tracing.otel.bridge.OtelHttpClientHandler; +import io.micrometer.tracing.otel.bridge.OtelHttpServerHandler; +import io.micrometer.tracing.otel.bridge.OtelTracer; +import io.micrometer.tracing.reporter.zipkin.ZipkinOtelSetup.Builder.OtelBuildingBlocks; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; +import io.opentelemetry.extension.trace.propagation.B3Propagator; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import zipkin2.reporter.urlconnection.URLConnectionSender; + +/** + * Work in progress. Requires HTTP instrumentation dependendency to be on the classpath. + * + * Provides Zipkin setup with OTel. + */ +public final class ZipkinOtelSetup implements AutoCloseable { + + private final Consumer closingFunction; + + private final OtelBuildingBlocks otelBuildingBlocks; + + ZipkinOtelSetup(Consumer closingFunction, OtelBuildingBlocks otelBuildingBlocks) { + this.closingFunction = closingFunction; + this.otelBuildingBlocks = otelBuildingBlocks; + } + + @Override + public void close() { + this.closingFunction.accept(this.otelBuildingBlocks); + } + + /** + * @return all the OTel building blocks required to communicate with Zipkin + */ + public OtelBuildingBlocks getBuildingBlocks() { + return this.otelBuildingBlocks; + } + + /** + * @return builder for the {@link ZipkinOtelSetup} + */ + public static ZipkinOtelSetup.Builder builder() { + return new ZipkinOtelSetup.Builder(); + } + + /** + * Builder for OTel with Zipkin. + */ + public static class Builder { + + private Supplier zipkinSpanExporter; + + private Function sdkTracerProvider; + + private Function openTelemetrySdk; + + private Function tracer; + + private Function otelTracer; + + private Function httpServerHandler; + + private Function httpClientHandler; + + private Function handlers; + + private Consumer closingFunction; + + /** + * All OTel building blocks required to communicate with Zipkin. + */ + public static class OtelBuildingBlocks { + + public final ZipkinSpanExporter zipkinSpanExporter; + + public final SdkTracerProvider sdkTracerProvider; + + public final OpenTelemetrySdk openTelemetrySdk; + + public final io.opentelemetry.api.trace.Tracer tracer; + + public final OtelTracer otelTracer; + + public final HttpServerHandler httpServerHandler; + + public final HttpClientHandler httpClientHandler; + + public OtelBuildingBlocks(ZipkinSpanExporter zipkinSpanExporter, SdkTracerProvider sdkTracerProvider, OpenTelemetrySdk openTelemetrySdk, io.opentelemetry.api.trace.Tracer tracer, OtelTracer otelTracer, HttpServerHandler httpServerHandler, HttpClientHandler httpClientHandler) { + this.zipkinSpanExporter = zipkinSpanExporter; + this.sdkTracerProvider = sdkTracerProvider; + this.openTelemetrySdk = openTelemetrySdk; + this.tracer = tracer; + this.otelTracer = otelTracer; + this.httpServerHandler = httpServerHandler; + this.httpClientHandler = httpClientHandler; + } + } + + public Builder zipkinSpanExporter(Supplier zipkinSpanExporter) { + this.zipkinSpanExporter = zipkinSpanExporter; + return this; + } + + public Builder sdkTracerProvider(Function sdkTracerProvider) { + this.sdkTracerProvider = sdkTracerProvider; + return this; + } + + public Builder openTelemetrySdk(Function openTelemetrySdk) { + this.openTelemetrySdk = openTelemetrySdk; + return this; + } + + public Builder tracer(Function tracer) { + this.tracer = tracer; + return this; + } + + public Builder otelTracer(Function otelTracer) { + this.otelTracer = otelTracer; + return this; + } + + public Builder httpServerHandler(Function httpServerHandler) { + this.httpServerHandler = httpServerHandler; + return this; + } + + public Builder httpClientHandler(Function httpClientHandler) { + this.httpClientHandler = httpClientHandler; + return this; + } + + public Builder handlers(Function tracingHandlers) { + this.handlers = tracingHandlers; + return this; + } + + public Builder closingFunction(Consumer closingFunction) { + this.closingFunction = closingFunction; + return this; + } + + /** + * @param meterRegistry meter registry to which the {@link TimerRecordingHandler} should be attached + * @return setup with all OTel building blocks + */ + public ZipkinOtelSetup register(MeterRegistry meterRegistry) { + ZipkinSpanExporter zipkinSpanExporter = this.zipkinSpanExporter != null ? this.zipkinSpanExporter.get() : zipkinSpanExporter(); + SdkTracerProvider sdkTracerProvider = this.sdkTracerProvider != null ? this.sdkTracerProvider.apply(zipkinSpanExporter) : sdkTracerProvider(zipkinSpanExporter); + OpenTelemetrySdk openTelemetrySdk = this.openTelemetrySdk != null ? this.openTelemetrySdk.apply(sdkTracerProvider) : openTelemetrySdk(sdkTracerProvider); + io.opentelemetry.api.trace.Tracer tracer = this.tracer != null ? this.tracer.apply(openTelemetrySdk) : tracer(openTelemetrySdk); + OtelTracer otelTracer = this.otelTracer != null ? this.otelTracer.apply(tracer) : otelTracer(tracer); + HttpServerHandler httpServerHandler = this.httpServerHandler != null ? this.httpServerHandler.apply(openTelemetrySdk) : httpServerHandler(openTelemetrySdk); + HttpClientHandler httpClientHandler = this.httpClientHandler != null ? this.httpClientHandler.apply(openTelemetrySdk) : httpClientHandler(openTelemetrySdk); + OtelBuildingBlocks otelBuildingBlocks = new OtelBuildingBlocks(zipkinSpanExporter, sdkTracerProvider, openTelemetrySdk, tracer, otelTracer, httpServerHandler, httpClientHandler); + TimerRecordingHandler tracingHandlers = this.handlers != null ? this.handlers.apply(otelBuildingBlocks) : tracingHandlers(otelBuildingBlocks); + meterRegistry.config().timerRecordingListener(tracingHandlers); + Consumer closingFunction = this.closingFunction != null ? this.closingFunction : closingFunction(otelBuildingBlocks); + return new ZipkinOtelSetup(closingFunction, otelBuildingBlocks); + } + + private static ZipkinSpanExporter zipkinSpanExporter() { + return ZipkinSpanExporter.builder() + .setSender(URLConnectionSender.newBuilder() + .connectTimeout(1000) + .readTimeout(1000) + .endpoint("http://localhost:9411/api/v2/spans").build()) + .build(); + } + + private static SdkTracerProvider sdkTracerProvider(ZipkinSpanExporter zipkinSpanExporter) { + return SdkTracerProvider.builder().setSampler(io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOn()) + .addSpanProcessor(SimpleSpanProcessor.create(zipkinSpanExporter)).build(); + } + + private static OpenTelemetrySdk openTelemetrySdk(SdkTracerProvider sdkTracerProvider) { + return OpenTelemetrySdk.builder().setTracerProvider(sdkTracerProvider).setPropagators(ContextPropagators.create(B3Propagator.injectingSingleHeader())).build(); + } + + private static io.opentelemetry.api.trace.Tracer tracer(OpenTelemetrySdk openTelemetrySdk) { + return openTelemetrySdk.getTracerProvider().get("io.micrometer.micrometer-tracing"); + } + + private static OtelTracer otelTracer(io.opentelemetry.api.trace.Tracer tracer) { + OtelCurrentTraceContext otelCurrentTraceContext = new OtelCurrentTraceContext(); + return new OtelTracer(tracer, otelCurrentTraceContext, event -> { + }, new OtelBaggageManager(otelCurrentTraceContext, Collections.emptyList(), Collections.emptyList())); + } + + private static HttpServerHandler httpServerHandler(OpenTelemetrySdk openTelemetrySdk) { + return new OtelHttpServerHandler(openTelemetrySdk, null, null, Pattern.compile(""), new DefaultHttpServerAttributesExtractor()); + } + + private static HttpClientHandler httpClientHandler(OpenTelemetrySdk openTelemetrySdk) { + return new OtelHttpClientHandler(openTelemetrySdk, null, null, SamplerFunction.alwaysSample(), new DefaultHttpClientAttributesExtractor()); + } + + private static Consumer closingFunction(OtelBuildingBlocks otelBuildingBlocks) { + return deps -> { + ZipkinSpanExporter reporter = deps.zipkinSpanExporter; + reporter.flush(); + reporter.close(); + }; + } + + @SuppressWarnings("rawtypes") + private static TimerRecordingHandler tracingHandlers(OtelBuildingBlocks otelBuildingBlocks) { + OtelTracer tracer = otelBuildingBlocks.otelTracer; + HttpServerHandler httpServerHandler = otelBuildingBlocks.httpServerHandler; + HttpClientHandler httpClientHandler = otelBuildingBlocks.httpClientHandler; + return new TimerRecordingHandler.FirstMatchingCompositeTimerRecordingHandler(Arrays.asList(new HttpServerTracingRecordingHandler(tracer, httpServerHandler), new HttpClientTracingRecordingHandler(tracer, httpClientHandler), new DefaultTracingRecordingHandler(tracer))); + } + + } + + /** + * Runs the given lambda with Zipkin setup. + * @param meterRegistry meter registry to register the handlers against + * @param consumer lambda to be executed with the building blocks + */ + public static void run(MeterRegistry meterRegistry, Consumer consumer) { + run(ZipkinOtelSetup.builder().register(meterRegistry), consumer); + } + + /** + * @param localZipkinBrave Brave setup with Zipkin + * @param consumer runnable to run + */ + public static void run(ZipkinOtelSetup localZipkinBrave, Consumer consumer) { + try { + consumer.accept(localZipkinBrave.getBuildingBlocks()); + } + finally { + localZipkinBrave.close(); + } + } +} diff --git a/micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/src/test/java/io/micrometer/tracing/reporter/zipkin/ZipkinBraveSetupTests.java b/micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/src/test/java/io/micrometer/tracing/reporter/zipkin/ZipkinBraveSetupTests.java new file mode 100644 index 00000000..59ef2eed --- /dev/null +++ b/micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/src/test/java/io/micrometer/tracing/reporter/zipkin/ZipkinBraveSetupTests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.reporter.zipkin; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.core.util.internal.logging.InternalLogger; +import io.micrometer.core.util.internal.logging.InternalLoggerFactory; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import zipkin2.reporter.AsyncReporter; +import zipkin2.reporter.urlconnection.URLConnectionSender; + +import static org.assertj.core.api.BDDAssertions.then; + +class ZipkinBraveSetupTests { + + private static InternalLogger log = InternalLoggerFactory.getInstance(ZipkinBraveSetupTests.class); + + SimpleMeterRegistry simpleMeterRegistry = new SimpleMeterRegistry(); + + MockWebServer server = new MockWebServer(); + + @BeforeEach + void setup() throws IOException { + server.start(); + } + + @Test + void should_register_a_span_in_zipkin() throws InterruptedException { + ZipkinBraveSetup setup = ZipkinBraveSetup.builder().reporter(() -> AsyncReporter + .builder(URLConnectionSender.newBuilder() + .connectTimeout(1000) + .readTimeout(1000) + .endpoint(this.server.url("/") + "api/v2/spans").build()) + .build()).register(this.simpleMeterRegistry); + + ZipkinBraveSetup.run(setup, __ -> { + Timer.Sample sample = Timer.start(simpleMeterRegistry); + log.info("New sample created"); + sample.stop(Timer.builder("the-name")); + }); + + Awaitility.await().atMost(1, TimeUnit.SECONDS) + .untilAsserted(() -> then(this.server.getRequestCount()).isGreaterThan(0)); + + RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS); + then(request).isNotNull(); + then(request.getPath()).isEqualTo("/api/v2/spans"); + } +} diff --git a/micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/src/test/java/io/micrometer/tracing/reporter/zipkin/ZipkinOtelSetupTests.java b/micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/src/test/java/io/micrometer/tracing/reporter/zipkin/ZipkinOtelSetupTests.java new file mode 100644 index 00000000..f39379a4 --- /dev/null +++ b/micrometer-tracing-reporters/micrometer-tracing-reporter-zipkin/src/test/java/io/micrometer/tracing/reporter/zipkin/ZipkinOtelSetupTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.reporter.zipkin; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.core.util.internal.logging.InternalLogger; +import io.micrometer.core.util.internal.logging.InternalLoggerFactory; +import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import zipkin2.reporter.urlconnection.URLConnectionSender; + +import static org.assertj.core.api.BDDAssertions.then; + +class ZipkinOtelSetupTests { + + private static InternalLogger log = InternalLoggerFactory.getInstance(ZipkinOtelSetupTests.class); + + SimpleMeterRegistry simpleMeterRegistry = new SimpleMeterRegistry(); + + MockWebServer server = new MockWebServer(); + + @BeforeEach + void setup() throws IOException { + server.start(); + } + + @Test + void should_register_a_span_in_zipkin() throws InterruptedException { + ZipkinOtelSetup setup = ZipkinOtelSetup.builder().zipkinSpanExporter(() -> + ZipkinSpanExporter.builder() + .setSender(URLConnectionSender.newBuilder() + .connectTimeout(1000) + .readTimeout(1000) + .endpoint(this.server.url("/") + "api/v2/spans").build()) + .build()).register(this.simpleMeterRegistry); + + ZipkinOtelSetup.run(setup, __ -> { + Timer.Sample sample = Timer.start(simpleMeterRegistry); + log.info("New sample created"); + sample.stop(Timer.builder("the-name")); + }); + + Awaitility.await().atMost(1, TimeUnit.SECONDS) + .untilAsserted(() -> then(this.server.getRequestCount()).isGreaterThan(0)); + + RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS); + then(request).isNotNull(); + then(request.getPath()).isEqualTo("/api/v2/spans"); + } +} diff --git a/micrometer-tracing/build.gradle b/micrometer-tracing/build.gradle index 3064ca1f..4d36b033 100644 --- a/micrometer-tracing/build.gradle +++ b/micrometer-tracing/build.gradle @@ -1,9 +1,13 @@ plugins { - id 'idea' + id 'idea' } dependencies { - // JUnit 5 - testImplementation 'org.junit.jupiter:junit-jupiter' + api 'io.micrometer:micrometer-core' + api 'aopalliance:aopalliance' + + testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation 'org.assertj:assertj-core' + testImplementation 'org.mockito:mockito-core' } diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/BaggageInScope.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/BaggageInScope.java index 06afa6bf..e12f1609 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/BaggageInScope.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/BaggageInScope.java @@ -30,37 +30,31 @@ * Represents a single baggage entry. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 */ public interface BaggageInScope extends Closeable { /** - * Returns the baggage name. - * * @return name of the baggage entry */ String name(); /** - * Returns the baggage value. - * - * @return value of the baggage entry or {@code null} if not set + * @return value of the baggage entry or {@code null} if not set. */ @Nullable String get(); /** * Retrieves baggage from the given {@link TraceContext}. - * * @param traceContext context containing baggage - * @return value of the baggage entry or {@code null} if not set + * @return value of the baggage entry or {@code null} if not set. */ @Nullable String get(TraceContext traceContext); /** * Sets the baggage value. - * * @param value to set * @return new scope */ @@ -68,7 +62,6 @@ public interface BaggageInScope extends Closeable { /** * Sets the baggage value for the given {@link TraceContext}. - * * @param traceContext context containing baggage * @param value to set * @return new scope @@ -77,7 +70,6 @@ public interface BaggageInScope extends Closeable { /** * Sets the current baggage in scope. - * * @return this in scope */ BaggageInScope makeCurrent(); diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/BaggageManager.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/BaggageManager.java index c7f81759..d1eac10f 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/BaggageManager.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/BaggageManager.java @@ -18,44 +18,43 @@ import java.util.Map; +import io.micrometer.tracing.lang.Nullable; + /** * Manages {@link BaggageInScope} entries. Upon retrieval / creation of a baggage entry * puts it in scope. Scope must be closed. * * @author OpenTelemetry Authors * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 */ public interface BaggageManager { /** - * Returns the mapping of all baggage entries from the given scope. - * - * @return mapping of all baggage entries + * @return mapping of all baggage entries from the given scope */ Map getAllBaggage(); /** * Retrieves {@link BaggageInScope} for the given name. - * * @param name baggage name * @return baggage or {@code null} if not present */ + @Nullable BaggageInScope getBaggage(String name); /** * Retrieves {@link BaggageInScope} for the given name. - * * @param traceContext trace context with baggage attached to it * @param name baggage name * @return baggage or {@code null} if not present */ + @Nullable BaggageInScope getBaggage(TraceContext traceContext, String name); /** * Creates a new {@link BaggageInScope} entry for the given name or returns an * existing one if it's already present. - * * @param name baggage name * @return new or already created baggage */ @@ -64,7 +63,6 @@ public interface BaggageManager { /** * Creates a new {@link BaggageInScope} entry for the given name or returns an * existing one if it's already present. - * * @param name baggage name * @param value baggage value * @return new or already created baggage diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/CurrentTraceContext.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/CurrentTraceContext.java index 5d90632b..83e3047b 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/CurrentTraceContext.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/CurrentTraceContext.java @@ -32,14 +32,12 @@ * * @author OpenZipkin Brave Authors * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 */ public interface CurrentTraceContext { /** - * Returns the current {@link TraceContext}. - * - * @return current {@link TraceContext} or {@code null} if not set + * @return current {@link TraceContext} or {@code null} if not set. */ @Nullable TraceContext context(); @@ -48,24 +46,21 @@ public interface CurrentTraceContext { * Sets the current span in scope until the returned object is closed. It is a * programming error to drop or never close the result. Using try-with-resources is * preferred for this reason. - * * @param context span to place into scope or {@code null} to clear the scope * @return the scope with the span set */ - Scope newScope(@Nullable TraceContext context); + CurrentTraceContext.Scope newScope(@Nullable TraceContext context); /** * Like {@link #newScope(TraceContext)}, except returns a noop scope if the given * context is already in scope. - * * @param context span to place into scope or {@code null} to clear the scope * @return the scope with the span set */ - Scope maybeScope(@Nullable TraceContext context); + CurrentTraceContext.Scope maybeScope(@Nullable TraceContext context); /** * Wraps a task in a trace representation. - * * @param task task to wrap * @param task return type * @return wrapped task @@ -74,7 +69,6 @@ public interface CurrentTraceContext { /** * Wraps a task in a trace representation. - * * @param task task to wrap * @return wrapped task */ @@ -82,7 +76,6 @@ public interface CurrentTraceContext { /** * Wraps an executor in a trace representation. - * * @param delegate executor to wrap * @return wrapped executor */ @@ -90,7 +83,6 @@ public interface CurrentTraceContext { /** * Wraps an executor service in a trace representation. - * * @param delegate executor service to wrap * @return wrapped executor service */ diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/SamplerFunction.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/SamplerFunction.java index c28dbbac..469ac714 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/SamplerFunction.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/SamplerFunction.java @@ -27,54 +27,52 @@ * @param type of the input, for example a request or method * @author OpenZipkin Brave Authors * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 */ -@SuppressWarnings("unchecked") public interface SamplerFunction { /** * Always deferring {@link SamplerFunction}. - * * @param type of the input, for example a request or method * @return decision deferring sampler function */ + @SuppressWarnings("unchecked") static SamplerFunction deferDecision() { return (SamplerFunction) Constants.DEFER_DECISION; } /** * Never sampling {@link SamplerFunction}. - * * @param type of the input, for example a request or method * @return never sampling sampler function */ + @SuppressWarnings("unchecked") static SamplerFunction neverSample() { return (SamplerFunction) Constants.NEVER_SAMPLE; } /** * Always sampling {@link SamplerFunction}. - * * @param type of the input, for example a request or method * @return always sampling sampler function */ + @SuppressWarnings("unchecked") static SamplerFunction alwaysSample() { return (SamplerFunction) Constants.ALWAYS_SAMPLE; } /** * Returns an overriding sampling decision for a new trace. - * * @param arg parameter to evaluate for a sampling decision. {@code null} input * results in a {@code null} result * @return {@code true} to sample a new trace or {@code false} to deny. {@code null} - * defers the decision + * defers the decision. */ @Nullable Boolean trySample(@Nullable T arg); /** - * Constant {@link SamplerFunction}. + * Constant {@link SamplerFunction}s. */ enum Constants implements SamplerFunction { diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/ScopedSpan.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/ScopedSpan.java index 491cab86..764c6236 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/ScopedSpan.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/ScopedSpan.java @@ -21,56 +21,48 @@ * * @author OpenZipkin Brave Authors * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 */ public interface ScopedSpan { /** - * Decides whether span is noop. - * * @return {@code true} when no recording is done and nothing is reported to an * external system. However, this span should still be injected into outgoing - * requests. Use this flag to avoid performing expensive computation + * requests. Use this flag to avoid performing expensive computation. */ boolean isNoop(); /** - * Returns the {@link TraceContext}. - * * @return {@link TraceContext} corresponding to this span. */ TraceContext context(); /** * Sets a name on this span. - * * @param name name to set on the span - * @return this + * @return this span */ ScopedSpan name(String name); /** * Sets a tag on this span. - * * @param key tag key * @param value tag value - * @return this + * @return this span */ ScopedSpan tag(String key, String value); /** * Sets an event on this span. - * * @param value event name to set on the span - * @return this + * @return this span */ ScopedSpan event(String value); /** * Records an exception for this span. - * * @param throwable to record - * @return this + * @return this span */ ScopedSpan error(Throwable throwable); diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/Span.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/Span.java index cb2ab900..405a4525 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/Span.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/Span.java @@ -28,83 +28,54 @@ * * @author OpenZipkin Brave Authors * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 */ -public interface Span extends SpanCustomizer { +public interface Span extends io.micrometer.tracing.SpanCustomizer { /** - * Decides whether span is noop. - * * @return {@code true} when no recording is done and nothing is reported to an * external system. However, this span should still be injected into outgoing - * requests. Use this flag to avoid performing expensive computation + * requests. Use this flag to avoid performing expensive computation. */ boolean isNoop(); /** - * Returns the {@link TraceContext}. - * - * @return {@link TraceContext} corresponding to this span + * @return {@link TraceContext} corresponding to this span. */ - TraceContext context(); + io.micrometer.tracing.TraceContext context(); /** * Starts this span. - * - * @return this + * @return this span */ Span start(); - /** - * Starts this span except with a given timestamp in microseconds. - * - * @param micros timestamp in microseconds - * @return this - */ - Span start(long micros); - /** * Sets a name on this span. - * * @param name name to set on the span - * @return this + * @return this span */ - @Override Span name(String name); /** * Sets an event on this span. - * * @param value event name to set on the span - * @return this + * @return this span */ - @Override Span event(String value); - /** - * Sets an event on this span. - * - * @param micros event timestamp in microseconds - * @param value event name to set on the span - * @return this - */ - Span event(long micros, String value); - /** * Sets a tag on this span. - * * @param key tag key * @param value tag value - * @return this + * @return this span */ - @Override Span tag(String key, String value); /** * Records an exception for this span. - * * @param throwable to record - * @return this + * @return this span */ Span error(Throwable throwable); @@ -113,14 +84,6 @@ public interface Span extends SpanCustomizer { */ void end(); - /** - * Ends the span with a given timestamp in microseconds. The span gets stopped and - * recorded if not noop. - * - * @param micros timestamp in microseconds - */ - void end(long micros); - /** * Ends the span. The span gets stopped but does not get recorded. */ @@ -128,20 +91,24 @@ public interface Span extends SpanCustomizer { /** * Sets the remote service name for the span. - * * @param remoteServiceName remote service name - * @return this + * @return this span + * @since 3.0.3 */ - Span remoteServiceName(String remoteServiceName); + default Span remoteServiceName(String remoteServiceName) { + return this; + } /** * Sets the remote url on the span. - * * @param ip remote ip * @param port remote port - * @return this + * @return this span + * @since 3.1.0 */ - Span remoteIpAndPort(String ip, int port); + default Span remoteIpAndPort(String ip, int port) { + return this; + } /** * Type of span. Can be used to specify additional relationships between spans in @@ -192,22 +159,19 @@ interface Builder { /** * Sets the parent of the built span. - * * @param context parent's context * @return this */ - Builder setParent(TraceContext context); + Builder setParent(io.micrometer.tracing.TraceContext context); /** * Sets no parent of the built span. - * * @return this */ Builder setNoParent(); /** * Sets the name of the span. - * * @param name span name * @return this */ @@ -215,7 +179,6 @@ interface Builder { /** * Sets an event on the span. - * * @param value event value * @return this */ @@ -223,7 +186,6 @@ interface Builder { /** * Sets a tag on the span. - * * @param key tag key * @param value tag value * @return this @@ -232,7 +194,6 @@ interface Builder { /** * Sets an error on the span. - * * @param throwable error to set * @return this */ @@ -240,15 +201,13 @@ interface Builder { /** * Sets the kind on the span. - * * @param spanKind kind of the span * @return this */ - Builder kind(Kind spanKind); + Builder kind(Span.Kind spanKind); /** * Sets the remote service name for the span. - * * @param remoteServiceName remote service name * @return this */ @@ -256,28 +215,20 @@ interface Builder { /** * Sets the remote URL for the span. - * * @param ip remote service ip * @param port remote service port * @return this */ - Builder remoteIpAndPort(String ip, int port); + default Builder remoteIpAndPort(String ip, int port) { + return this; + } /** * Builds and starts the span. - * * @return started span */ Span start(); - /** - * Builds and starts the span. - * - * @param micros span start time in micros - * @return started span - */ - Span start(long micros); - } } diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/SpanAndScope.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/SpanAndScope.java index a361a73c..e5818e5f 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/SpanAndScope.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/SpanAndScope.java @@ -18,43 +18,33 @@ import java.io.Closeable; +import io.micrometer.core.util.internal.logging.InternalLogger; +import io.micrometer.core.util.internal.logging.InternalLoggerFactory; + /** * Container object for {@link Span} and its corresponding {@link Tracer.SpanInScope}. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @author Arthur Gavlyukovskiy + * @since 3.1.0 */ public class SpanAndScope implements Closeable { + private static final InternalLogger log = InternalLoggerFactory.getInstance(SpanAndScope.class); + private final Span span; private final Tracer.SpanInScope scope; - /** - * Creates a new instance of {@link SpanAndScope}. - * - * @param span span - * @param scope scope of the span - */ public SpanAndScope(Span span, Tracer.SpanInScope scope) { this.span = span; this.scope = scope; } - /** - * Returns the span. - * - * @return stored span - */ public Span getSpan() { return this.span; } - /** - * Returns the span in scope. - * - * @return stored scope of the span - */ public Tracer.SpanInScope getScope() { return this.scope; } @@ -66,7 +56,12 @@ public String toString() { @Override public void close() { - this.scope.close(); + if (log.isTraceEnabled()) { + log.trace("Closing span [" + this.span + "]"); + } + if (this.scope != null) { + this.scope.close(); + } this.span.end(); } diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/SpanCustomizer.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/SpanCustomizer.java index 481e68f1..0010eebb 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/SpanCustomizer.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/SpanCustomizer.java @@ -21,32 +21,29 @@ * * @author OpenZipkin Brave Authors * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 */ public interface SpanCustomizer { /** * Sets a name on a span. - * * @param name name to set on a span - * @return this + * @return this, for chaining */ SpanCustomizer name(String name); /** * Sets a tag on a span. - * * @param key tag key * @param value tag value - * @return this + * @return this, for chaining */ SpanCustomizer tag(String key, String value); /** * Sets an event on a span. - * * @param value event name - * @return this + * @return this, for chaining */ SpanCustomizer event(String value); diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/SpanName.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/SpanName.java index 345430f8..cd29ad7d 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/SpanName.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/SpanName.java @@ -53,12 +53,8 @@ * }; * } * - * Starting with version {@code 1.3.0} you can also put the annotation on an - * {@code @Async} annotated method and the value of that annotation will be used as the - * span name. - * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 1.0.0 */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @@ -67,8 +63,7 @@ /** * Name of the span to be resolved at runtime. - * - * @return value of the span name + * @return - value of the span name. */ String value(); diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/SpanNamer.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/SpanNamer.java index 2a78136b..60480d5e 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/SpanNamer.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/SpanNamer.java @@ -22,13 +22,12 @@ * be resolved at runtime this interface will provide the name of the span. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 1.0.0 */ public interface SpanNamer { /** * Retrieves the span name for the given object. - * * @param object - object for which span name should be picked * @param defaultValue - the default valued to be returned if span name can't be * calculated diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/Taggable.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/Taggable.java index 2dad7ea0..4ef50d12 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/Taggable.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/Taggable.java @@ -17,19 +17,19 @@ package io.micrometer.tracing; /** + * * Describes the behaviour of an object that can be tagged. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.3 */ public interface Taggable { /** * Sets a tag. - * * @param key tag key * @param value tag value - * @return this + * @return this, for chaining */ Taggable tag(String key, String value); diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/ThreadLocalSpan.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/ThreadLocalSpan.java index d89e2206..69ca4222 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/ThreadLocalSpan.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/ThreadLocalSpan.java @@ -20,45 +20,32 @@ import java.util.NoSuchElementException; import java.util.concurrent.LinkedBlockingDeque; +import io.micrometer.core.util.internal.logging.InternalLogger; +import io.micrometer.core.util.internal.logging.InternalLoggerFactory; /** * Represents a {@link Span} stored in thread local. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.1.0 */ public class ThreadLocalSpan { + private static final InternalLogger log = InternalLoggerFactory.getInstance(ThreadLocalSpan.class); + private final ThreadLocal threadLocalSpan = new ThreadLocal<>(); private final Deque spans = new LinkedBlockingDeque<>(); private final Tracer tracer; - /** - * Creates a new instance of {@link ThreadLocalSpan}. - * - * @param tracer tracer - */ public ThreadLocalSpan(Tracer tracer) { this.tracer = tracer; } - /** - * Creates a new span and sets it in scope. - * - * @return new thread local span - */ - public Span nextSpan() { - Span span = this.tracer.nextSpan(); - set(span); - return span; - } - /** * Sets given span and scope. - * - * @param span span to be put in scope + * @param span - span to be put in scope */ public void set(Span span) { Tracer.SpanInScope spanInScope = this.tracer.withSpan(span); @@ -71,8 +58,6 @@ public void set(Span span) { } /** - * Returns the current span and scope. - * * @return currently stored span and scope */ public SpanAndScope get() { @@ -90,22 +75,16 @@ public void remove() { } try { SpanAndScope span = this.spans.removeFirst(); + if (log.isDebugEnabled()) { + log.debug("Took span [" + span + "] from thread local"); + } this.threadLocalSpan.set(span); } catch (NoSuchElementException ex) { + if (log.isTraceEnabled()) { + log.trace("Failed to remove a span from the queue", ex); + } } } - /** - * Ends the current span and puts the previous one as the current span if present. - */ - public void end() { - SpanAndScope spanAndScope = get(); - if (spanAndScope == null) { - return; - } - spanAndScope.close(); - remove(); - } - } diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/TraceContext.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/TraceContext.java index 01568dc5..e0bb4fff 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/TraceContext.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/TraceContext.java @@ -22,35 +22,30 @@ * Contains trace and span data. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 */ public interface TraceContext { /** - * Returns the trace id. - * + * Trace id. * @return trace id of a span */ String traceId(); /** - * Returns the parent span id. - * + * Parent span id. * @return parent span id or {@code null} if one is not set */ @Nullable String parentId(); /** - * Returns the span id. - * + * Span id. * @return span id */ String spanId(); /** - * Decides whether the span is sampled. - * * @return {@code true} when sampled, {@code false} when not sampled and {@code null} * when sampling decision should be deferred */ @@ -59,45 +54,40 @@ public interface TraceContext { /** * Builder for {@link TraceContext}. * - * @since 6.0.0 + * @since 3.1.0 */ interface Builder { /** * Sets trace id on the trace context. - * * @param traceId trace id * @return this */ - Builder traceId(String traceId); + TraceContext.Builder traceId(String traceId); /** * Sets parent id on the trace context. - * * @param parentId parent trace id * @return this */ - Builder parentId(String parentId); + TraceContext.Builder parentId(String parentId); /** * Sets span id on the trace context. - * * @param spanId span id * @return this */ - Builder spanId(String spanId); + TraceContext.Builder spanId(String spanId); /** * Sets sampled on the trace context. - * * @param sampled if span is sampled * @return this */ - Builder sampled(Boolean sampled); + TraceContext.Builder sampled(Boolean sampled); /** * Builds the trace context. - * * @return trace context */ TraceContext build(); diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/Tracer.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/Tracer.java index 00e5a33f..00d3d8ca 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/Tracer.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/Tracer.java @@ -61,7 +61,7 @@ * * @author OpenZipkin Brave Authors * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 * @see Span * @see ScopedSpan * @see Propagator @@ -71,7 +71,6 @@ public interface Tracer extends BaggageManager { /** * This creates a new span based on the current span in scope. If there's no such span * a new trace will be created. - * * @return a child span or a new trace if no span was present */ Span nextSpan(); @@ -79,9 +78,8 @@ public interface Tracer extends BaggageManager { /** * This creates a new span whose parent is {@link Span}. If parent is {@code null} * then will create act as {@link #nextSpan()}. - * * @param parent parent span - * @return a child span for the given parent, {@code null} if context was empty + * @return a child span for the given parent, {@code null} if context was empty. */ Span nextSpan(@Nullable Span parent); @@ -99,11 +97,10 @@ public interface Tracer extends BaggageManager { * close on the result have no effect on the input. For example, calling close on the * result does not finish the span. Not only is it safe to call close, you must call * close to end the scope, or risk leaking resources associated with the scope. - * * @param span span to place into scope or null to clear the scope * @return scope with span in it */ - SpanInScope withSpan(@Nullable Span span); + Tracer.SpanInScope withSpan(@Nullable Span span); /** * Returns a new child span if there's a {@link #currentSpan()} or a new trace if @@ -122,7 +119,6 @@ public interface Tracer extends BaggageManager { * span.end(); * } * } - * * @param name of the span in scope * @return span in scope */ @@ -134,28 +130,28 @@ public interface Tracer extends BaggageManager { * that has not yet been started, yet it's heavily configurable (some options are not * possible to be set when a span has already been started). We can achieve that by * using a builder. - * * @return a span builder */ Span.Builder spanBuilder(); /** * Builder for {@link TraceContext}. - * * @return a trace context builder */ TraceContext.Builder traceContextBuilder(); /** - * Returns the current trace context. - * + * Returns the {@link CurrentTraceContext}. Can be {@code null} so that we don't break + * backward compatibility. * @return current trace context */ - CurrentTraceContext currentTraceContext(); + @Nullable + default CurrentTraceContext currentTraceContext() { + return null; + } /** * Allows to customize the current span in scope. - * * @return current span customizer */ @Nullable @@ -163,7 +159,6 @@ public interface Tracer extends BaggageManager { /** * Retrieves the current span in scope or {@code null} if one is not available. - * * @return current span in scope */ @Nullable diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/ContinueSpan.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/ContinueSpan.java index eac418cb..187ecc41 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/ContinueSpan.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/ContinueSpan.java @@ -26,7 +26,7 @@ * Continues the existing span. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 1.0.0 */ @Retention(RetentionPolicy.RUNTIME) @Inherited diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/NewSpan.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/NewSpan.java index c27c47fd..77216901 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/NewSpan.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/NewSpan.java @@ -33,7 +33,7 @@ * * * @author Christian Schwerdtfeger - * @since 6.0.0 + * @since 1.0.0 */ @Retention(RetentionPolicy.RUNTIME) @Inherited diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/NewSpanParser.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/NewSpanParser.java new file mode 100644 index 00000000..4ad12bf5 --- /dev/null +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/NewSpanParser.java @@ -0,0 +1,38 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.annotation; + +import io.micrometer.tracing.Span; +import org.aopalliance.intercept.MethodInvocation; + +/** + * Parses data for a span created via a {@link NewSpan} annotation. + * + * @author Adrian Cole + * @since 2.0.0 + */ +public interface NewSpanParser { + + /** + * Override to control the name and tags on an annotation-based span. + * @param methodInvocation method invocation annotated with new span + * @param newSpan meta data of the new span + * @param span span to customize + */ + void parse(MethodInvocation methodInvocation, NewSpan newSpan, Span span); + +} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/NoOpTagValueResolver.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/NoOpTagValueResolver.java index 2704a5ee..a4f56001 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/NoOpTagValueResolver.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/NoOpTagValueResolver.java @@ -20,7 +20,7 @@ * A no-op implementation of a {@link TagValueResolver}. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 1.0.0 */ public class NoOpTagValueResolver implements TagValueResolver { diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpServerResponse.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/SleuthMethodInvocationProcessor.java similarity index 52% rename from micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpServerResponse.java rename to micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/SleuthMethodInvocationProcessor.java index 67da2f3e..93ff29c9 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpServerResponse.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/SleuthMethodInvocationProcessor.java @@ -14,36 +14,26 @@ * limitations under the License. */ -package io.micrometer.tracing.http; +package io.micrometer.tracing.annotation; -import io.micrometer.tracing.lang.Nullable; -import io.micrometer.tracing.transport.Kind; +import org.aopalliance.intercept.MethodInvocation; /** - * This API is taken from OpenZipkin Brave. + * Contract for processing Sleuth annotations. * - * Abstract response type used for parsing and sampling. Represents an HTTP Server - * response. - * - * @author OpenZipkin Brave Authors * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 2.1.0 */ -public interface HttpServerResponse extends HttpResponse { - - @Nullable - default HttpServerRequest request() { - return null; - } - - @Override - default Throwable error() { - return null; - } +public interface SleuthMethodInvocationProcessor { - @Override - default Kind kind() { - return Kind.SERVER; - } + /** + * Executes a given Sleuth annotated method. + * @param invocation method invocation + * @param newSpan annotation + * @param continueSpan annotation + * @return executed method result + * @throws Throwable exception upon running a method + */ + Object process(MethodInvocation invocation, NewSpan newSpan, ContinueSpan continueSpan) throws Throwable; } diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/SpanTag.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/SpanTag.java index 7c79ddf8..75d07818 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/SpanTag.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/SpanTag.java @@ -31,7 +31,7 @@ * {@code toString()} value of the parameter * * @author Christian Schwerdtfeger - * @since 6.0.0 + * @since 1.0.0 */ @Retention(RetentionPolicy.RUNTIME) @Inherited diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/TagValueExpressionResolver.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/TagValueExpressionResolver.java index 0320b8d4..f88f54cc 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/TagValueExpressionResolver.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/TagValueExpressionResolver.java @@ -20,7 +20,7 @@ * Resolves the tag value for the given parameter and the provided expression. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 1.0.0 */ public interface TagValueExpressionResolver { diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/TagValueResolver.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/TagValueResolver.java index b62ddbd7..ef6f37ba 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/TagValueResolver.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/annotation/TagValueResolver.java @@ -20,7 +20,7 @@ * Resolves the tag value for the given parameter. * * @author Christian Schwerdtfeger - * @since 6.0.0 + * @since 1.0.0 */ public interface TagValueResolver { diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/AssertingSpan.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/AssertingSpan.java index f53c2af6..6369986f 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/AssertingSpan.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/AssertingSpan.java @@ -28,13 +28,11 @@ * information. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.1.0 */ public interface AssertingSpan extends Span { /** - * Wraps a span in an asserting wrapper. - * * @param documentedSpan span configuration * @param span span to wrap in assertions * @return asserting span @@ -51,7 +49,6 @@ else if (span instanceof AssertingSpan) { /** * Returns the underlying delegate. Used when casting is necessary. - * * @param span span to check for wrapping * @param type extending a span * @return unwrapped object @@ -68,22 +65,16 @@ else if (span instanceof AssertingSpan) { } /** - * Returns a {@link DocumentedSpan}. - * - * @return a {@link DocumentedSpan}. + * @return a {@link DocumentedSpan} with span configuration */ DocumentedSpan getDocumentedSpan(); /** - * Returns the delegate. - * - * @return wrapped {@link Span}. + * @return wrapped {@link Span} */ Span getDelegate(); /** - * Determines whether the span was started. - * * @return {@code true} when this span was started */ default boolean isStarted() { @@ -99,10 +90,9 @@ default AssertingSpan tag(String key, String value) { /** * Tags a span via {@link TagKey}. - * * @param key tag key * @param value tag value - * @return this + * @return this for chaining */ default AssertingSpan tag(TagKey key, String value) { DocumentedSpanAssertions.assertThatKeyIsValid(key, getDocumentedSpan()); @@ -117,18 +107,10 @@ default AssertingSpan event(String value) { return this; } - @Override - default AssertingSpan event(long micros, String value) { - DocumentedSpanAssertions.assertThatEventIsValid(value, getDocumentedSpan()); - getDelegate().event(micros, value); - return this; - } - /** * Annotates with an event via {@link EventValue}. - * * @param value event value - * @return this + * @return this for chaining */ default AssertingSpan event(EventValue value) { DocumentedSpanAssertions.assertThatEventIsValid(value, getDocumentedSpan()); @@ -159,12 +141,6 @@ default AssertingSpan start() { return this; } - @Override - default Span start(long micros) { - getDelegate().start(micros); - return this; - } - @Override default AssertingSpan error(Throwable throwable) { getDelegate().error(throwable); @@ -177,12 +153,6 @@ default void end() { getDelegate().end(); } - @Override - default void end(long micros) { - DocumentedSpanAssertions.assertThatSpanStartedBeforeEnd(this); - getDelegate().end(micros); - } - @Override default void abandon() { getDelegate().abandon(); diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/AssertingSpanBuilder.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/AssertingSpanBuilder.java index 98fe7fa3..2cb7365a 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/AssertingSpanBuilder.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/AssertingSpanBuilder.java @@ -23,13 +23,11 @@ * A {@link Span.Builder} that can perform assertions on itself. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.1.0 */ public interface AssertingSpanBuilder extends Span.Builder { /** - * Creates an asserting wrapper. - * * @param documentedSpan span configuration * @param builder builder to wrap in assertions * @return asserting span builder @@ -45,15 +43,11 @@ else if (builder instanceof AssertingSpanBuilder) { } /** - * Returns a {@link DocumentedSpan} with span configuration. - * - * @return a {@link DocumentedSpan} + * @return a {@link DocumentedSpan} with span configuration */ DocumentedSpan getDocumentedSpan(); /** - * The delegate. - * * @return wrapped {@link Span.Builder} */ Span.Builder getDelegate(); @@ -69,7 +63,7 @@ default AssertingSpanBuilder tag(String key, String value) { * Sets a tag on a span. * @param key tag key * @param value tag - * @return this + * @return this, for chaining */ default AssertingSpanBuilder tag(TagKey key, String value) { DocumentedSpanAssertions.assertThatKeyIsValid(key, getDocumentedSpan()); @@ -87,7 +81,7 @@ default AssertingSpanBuilder event(String value) { /** * Sets an event on a span. * @param value event - * @return this + * @return this, for chaining */ default AssertingSpanBuilder event(EventValue value) { DocumentedSpanAssertions.assertThatEventIsValid(value, getDocumentedSpan()); @@ -141,15 +135,6 @@ default AssertingSpanBuilder kind(Span.Kind spanKind) { @Override default AssertingSpan start() { Span span = getDelegate().start(); - return assertingDocumentedSpan(span); - } - - /** - * Converts a span to an asserting span for the given documented span. - * @param span span to convert - * @return asserting span - */ - default AssertingSpan assertingDocumentedSpan(Span span) { DocumentedSpan documentedSpan = getDocumentedSpan(); return new AssertingSpan() { @Override @@ -174,10 +159,4 @@ public String toString() { }; } - @Override - default Span start(long micros) { - Span span = getDelegate().start(micros); - return assertingDocumentedSpan(span); - } - } diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/AssertingSpanCustomizer.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/AssertingSpanCustomizer.java index a1079bb1..84d4586e 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/AssertingSpanCustomizer.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/AssertingSpanCustomizer.java @@ -22,13 +22,11 @@ * A {@link SpanCustomizer} that can perform assertions on itself. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.1.0 */ public interface AssertingSpanCustomizer extends SpanCustomizer { /** - * Creates an asserting wrapper. - * * @param documentedSpan span configuration * @param span span to wrap in assertions * @return asserting span customizer @@ -42,7 +40,6 @@ static AssertingSpanCustomizer of(DocumentedSpan documentedSpan, SpanCustomizer /** * Returns the underlying delegate. Used when casting is necessary. - * * @param span span to check for wrapping * @param type extending a span * @return unwrapped object @@ -59,15 +56,11 @@ else if (span instanceof AssertingSpanCustomizer) { } /** - * Returns a documented span. - * * @return a {@link DocumentedSpan} with span configuration */ DocumentedSpan getDocumentedSpan(); /** - * Returns the delegate. - * * @return wrapped {@link SpanCustomizer} */ SpanCustomizer getDelegate(); @@ -81,10 +74,9 @@ default AssertingSpanCustomizer tag(String key, String value) { /** * Sets a tag on a span. - * * @param key tag key * @param value tag - * @return this + * @return this, for chaining */ default AssertingSpanCustomizer tag(TagKey key, String value) { DocumentedSpanAssertions.assertThatKeyIsValid(key, getDocumentedSpan()); @@ -101,9 +93,8 @@ default AssertingSpanCustomizer event(String value) { /** * Sets an event on a span. - * * @param value event - * @return this + * @return this, for chaining */ default AssertingSpanCustomizer event(EventValue value) { DocumentedSpanAssertions.assertThatEventIsValid(value, getDocumentedSpan()); diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/DocumentedSpan.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/DocumentedSpan.java index 1abdc55d..9ede2403 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/DocumentedSpan.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/DocumentedSpan.java @@ -35,20 +35,16 @@ * method to retrieve the array of allowed keys / events * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.1.0 */ public interface DocumentedSpan { /** - * Returns the span name. - * * @return span name */ String getName(); /** - * Returns the allowed tag keys. - * * @return allowed tag keys */ default TagKey[] getTagKeys() { @@ -56,8 +52,6 @@ default TagKey[] getTagKeys() { } /** - * Returns the allowed events. - * * @return allowed events */ default EventValue[] getEvents() { @@ -68,7 +62,6 @@ default EventValue[] getEvents() { * Returns required prefix to be there for events and tags. Example {@code foo.} would * require the tags and events to have a {code foo} prefix like this for tags: * {@code foo.bar=true} and {@code foo.started} for events. - * * @return required prefix */ default String prefix() { @@ -77,7 +70,6 @@ default String prefix() { /** * Asserts on tags, names and allowed events. - * * @param span to wrap * @return wrapped span */ @@ -93,7 +85,6 @@ else if (span instanceof AssertingSpan) { /** * Asserts on tags, names and allowed events. - * * @param span to wrap * @return wrapped span */ @@ -109,7 +100,6 @@ else if (span instanceof AssertingSpanCustomizer) { /** * Asserts on tags, names and allowed events. - * * @param span builder to wrap * @return wrapped span */ diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/DocumentedSpanAssertions.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/DocumentedSpanAssertions.java index 55a21326..0116a880 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/DocumentedSpanAssertions.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/DocumentedSpanAssertions.java @@ -28,8 +28,6 @@ * In order to turn on the assertions you need to either turn on the * {@code spring.cloud.sleuth.assertions.enabled} system property or * {@code SPRING_CLOUD_SLEUTH_ASSERTIONS_ENABLED} environment variable. - * - * @author Marcin Grzejszczak */ final class DocumentedSpanAssertions { @@ -63,7 +61,7 @@ static void assertThatKeyIsValid(String key, DocumentedSpan documentedSpan) { } private static String prefixWarningIfPresent(DocumentedSpan documentedSpan) { - return StringUtils.isNotBlank(documentedSpan.prefix()) + return StringUtils.isNotEmpty(documentedSpan.prefix()) ? ". Also it has start with [" + documentedSpan.prefix() + "] prefix" : ""; } @@ -138,7 +136,7 @@ private static boolean patternOrValueMatches(String pickedValue, String allowedV } private static boolean hasRequiredPrefix(String value, String prefix) { - if (StringUtils.isNotBlank(prefix)) { + if (StringUtils.isNotEmpty(prefix)) { return value.startsWith(prefix); } return true; diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/EventValue.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/EventValue.java index 4466fc4d..b021d161 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/EventValue.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/EventValue.java @@ -20,13 +20,11 @@ * Event value representing a notable event in time. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.1.0 */ public interface EventValue { /** - * Returns the event value. - * * @return event value */ String getValue(); diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/ImmutableAssertingSpan.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/ImmutableAssertingSpan.java index 45cfa519..12b5b4c3 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/ImmutableAssertingSpan.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/ImmutableAssertingSpan.java @@ -46,7 +46,7 @@ public boolean equals(Object o) { return false; } ImmutableAssertingSpan that = (ImmutableAssertingSpan) o; - return Objects.equals(this.documentedSpan, that.documentedSpan) && Objects.equals(this.delegate, that.delegate); + return Objects.equals(documentedSpan, that.documentedSpan) && Objects.equals(delegate, that.delegate); } @Override @@ -56,7 +56,7 @@ public String toString() { @Override public int hashCode() { - return Objects.hash(this.documentedSpan, this.delegate); + return Objects.hash(documentedSpan, delegate); } @Override diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/ImmutableAssertingSpanBuilder.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/ImmutableAssertingSpanBuilder.java index a18747bc..d23f03ed 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/ImmutableAssertingSpanBuilder.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/ImmutableAssertingSpanBuilder.java @@ -44,7 +44,7 @@ public boolean equals(Object o) { return false; } ImmutableAssertingSpanBuilder that = (ImmutableAssertingSpanBuilder) o; - return Objects.equals(this.documentedSpan, that.documentedSpan) && Objects.equals(this.delegate, that.delegate); + return Objects.equals(documentedSpan, that.documentedSpan) && Objects.equals(delegate, that.delegate); } @Override @@ -54,7 +54,7 @@ public String toString() { @Override public int hashCode() { - return Objects.hash(this.documentedSpan, this.delegate); + return Objects.hash(documentedSpan, delegate); } @Override diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/ImmutableAssertingSpanCustomizer.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/ImmutableAssertingSpanCustomizer.java index 8b928a97..dc468ab9 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/ImmutableAssertingSpanCustomizer.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/ImmutableAssertingSpanCustomizer.java @@ -44,12 +44,12 @@ public boolean equals(Object o) { return false; } ImmutableAssertingSpanCustomizer that = (ImmutableAssertingSpanCustomizer) o; - return Objects.equals(this.documentedSpan, that.documentedSpan) && Objects.equals(this.delegate, that.delegate); + return Objects.equals(documentedSpan, that.documentedSpan) && Objects.equals(delegate, that.delegate); } @Override public int hashCode() { - return Objects.hash(this.documentedSpan, this.delegate); + return Objects.hash(documentedSpan, delegate); } @Override diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/TagKey.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/TagKey.java index a03ef408..e33318cc 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/TagKey.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/docs/TagKey.java @@ -22,13 +22,12 @@ * Represents a tag key. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.1.0 */ public interface TagKey { /** * Merges arrays of tags. - * * @param tags array of tags * @return a merged array of tags */ @@ -37,8 +36,6 @@ static TagKey[] merge(TagKey[]... tags) { } /** - * Returns the tag key. - * * @return tag key */ String getKey(); diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/FinishedSpan.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/FinishedSpan.java index 01d2f02c..7c7623cb 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/FinishedSpan.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/FinishedSpan.java @@ -30,71 +30,53 @@ * * @author OpenZipkin Brave Authors * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 */ public interface FinishedSpan { /** - * Returns the span name. - * - * @return name + * @return span's name */ String getName(); /** - * Returns the span's start timestamp. - * - * @return start timestamp + * @return span's start timestamp */ long getStartTimestamp(); /** - * Returns the span's end timestamp. - * - * @return end timestamp + * @return span's end timestamp */ long getEndTimestamp(); /** - * Returns the span tags. - * - * @return tags + * @return span's tags */ Map getTags(); /** - * Returns the span events. - * * @return span's events as timestamp to value mapping */ Collection> getEvents(); /** - * Returns the span id. - * * @return span's span id */ String getSpanId(); /** - * Returns the span's parent id. - * * @return span's parent id or {@code null} if not set */ @Nullable String getParentId(); /** - * Returns the span's remote ip. - * * @return span's remote ip */ @Nullable String getRemoteIp(); /** - * Returns the span's local ip. - * * @return span's local ip */ @Nullable @@ -103,37 +85,27 @@ default String getLocalIp() { } /** - * Returns the span's remote port. - * * @return span's remote port */ int getRemotePort(); /** - * Returns the span's trace id. - * * @return span's trace id */ String getTraceId(); /** - * Returns the error. - * * @return corresponding error or {@code null} if one was not thrown */ @Nullable Throwable getError(); /** - * Returns the span's kind. - * * @return span's kind */ Span.Kind getKind(); /** - * Returns the remote service name. - * * @return remote service name or {@code null} if not set */ @Nullable diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/SpanFilter.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/SpanFilter.java index a6102653..a693157c 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/SpanFilter.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/SpanFilter.java @@ -21,15 +21,14 @@ * not. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 */ public interface SpanFilter { /** - * Called to export sampled {@code Span}. - * - * @param span the collection of sampled Spans to be exported - * @return {@code true} when spans should be exported + * Called to export sampled {@code Span}s. + * @param span the collection of sampled Spans to be exported. + * @return whether should export spans */ boolean isExportable(FinishedSpan span); diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/SpanIgnoringSpanFilter.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/SpanIgnoringSpanFilter.java index cd0271de..7198dd46 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/SpanIgnoringSpanFilter.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/SpanIgnoringSpanFilter.java @@ -23,27 +23,26 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import io.micrometer.core.util.internal.logging.InternalLogger; +import io.micrometer.core.util.internal.logging.InternalLoggerFactory; import io.micrometer.tracing.util.StringUtils; + /** * {@link SpanFilter} that ignores spans via names. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 */ public class SpanIgnoringSpanFilter implements SpanFilter { static final Map cache = new ConcurrentHashMap<>(); + private static final InternalLogger log = InternalLoggerFactory.getInstance(SpanIgnoringSpanFilter.class); + private final List spanNamePatternsToSkip; private final List additionalSpanNamePatternsToIgnore; - /** - * Creates a new instance of {@link SpanIgnoringSpanFilter}. - * - * @param spanNamePatternsToSkip default span name patterns to skip - * @param additionalSpanNamePatternsToIgnore additional span name patterns to ignore - */ public SpanIgnoringSpanFilter(List spanNamePatternsToSkip, List additionalSpanNamePatternsToIgnore) { this.spanNamePatternsToSkip = spanNamePatternsToSkip; @@ -65,7 +64,13 @@ private List spanNames() { public boolean isExportable(FinishedSpan span) { List spanNamesToIgnore = spanNamesToIgnore(); String name = span.getName(); - return !StringUtils.isNotBlank(name) || spanNamesToIgnore.stream().noneMatch(p -> p.matcher(name).matches()); + if (StringUtils.isNotEmpty(name) && spanNamesToIgnore.stream().anyMatch(p -> p.matcher(name).matches())) { + if (log.isDebugEnabled()) { + log.debug("Will ignore a span with name [" + name + "]"); + } + return false; + } + return true; } } diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/SpanReporter.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/SpanReporter.java index f3b844df..a80f45af 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/SpanReporter.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/SpanReporter.java @@ -20,14 +20,13 @@ * An interface that allows to process spans after they got finished. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.1.0 */ public interface SpanReporter { /** * Reports the finished span. - * - * @param span a span that was ended and is ready to be reported + * @param span a span that was ended and is ready to be reported. */ void report(FinishedSpan span); diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/DefaultTracingRecordingHandler.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/DefaultTracingRecordingHandler.java new file mode 100755 index 00000000..5250bc7f --- /dev/null +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/DefaultTracingRecordingHandler.java @@ -0,0 +1,87 @@ +/* + * Copyright 2021-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.handler; + +import java.time.Duration; + +import io.micrometer.core.instrument.Timer; +import io.micrometer.tracing.CurrentTraceContext; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.internal.SpanNameUtil; + +/** + * TracingRecordingListener that uses the Tracing API to record events. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class DefaultTracingRecordingHandler implements TracingRecordingHandler { + + private final Tracer tracer; + + /** + * Creates a new instance of {@link DefaultTracingRecordingHandler}. + * + * @param tracer the tracer to use to record events + */ + public DefaultTracingRecordingHandler(Tracer tracer) { + this.tracer = tracer; + } + + @Override + public void onRestore(Timer.Sample sample, Timer.HandlerContext context) { + // TODO: check for nulls + CurrentTraceContext.Scope scope = this.tracer.currentTraceContext() + .maybeScope(getTracingContext(context).getSpan().context()); + getTracingContext(context).setScope(scope); + } + + @Override + public void onStart(Timer.Sample sample, Timer.HandlerContext context) { + Span parentSpan = getTracingContext(context).getSpan(); + Span childSpan = parentSpan != null ? getTracer().nextSpan(parentSpan) + : getTracer().nextSpan(); + // childSpan.name(sample.getHighCardinalityName()) + childSpan.start(); + setSpanAndScope(context, childSpan); + } + + @Override + public void onStop(Timer.Sample sample, Timer.HandlerContext context, Timer timer, + Duration duration) { + Span span = getTracingContext(context).getSpan(); + span.name(SpanNameUtil.toLowerHyphen(timer.getId().getName())); + tagSpan(context, span); + cleanup(context); + span.end(); + } + + @Override + public void onError(Timer.Sample sample, Timer.HandlerContext context, + Throwable throwable) { + Span span = getTracingContext(context).getSpan(); + span.error(throwable); + } + + @Override + public Tracer getTracer() { + return this.tracer; + } + +} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/HttpClientTracingRecordingHandler.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/HttpClientTracingRecordingHandler.java new file mode 100755 index 00000000..f027357f --- /dev/null +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/HttpClientTracingRecordingHandler.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.handler; + +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.tracing.context.IntervalHttpClientEvent; +import io.micrometer.core.instrument.transport.http.HttpClientRequest; +import io.micrometer.core.instrument.transport.http.HttpClientResponse; +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.http.HttpClientHandler; + +/** + * TracingRecordingListener that uses the Tracing API to record events for HTTP client + * side. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class HttpClientTracingRecordingHandler extends + HttpTracingRecordingHandler + implements TracingRecordingHandler { + + /** + * Creates a new instance of {@link HttpClientTracingRecordingHandler}. + * + * @param tracer tracer + * @param handler http client handler + */ + public HttpClientTracingRecordingHandler(Tracer tracer, HttpClientHandler handler) { + super(tracer, handler::handleSend, handler::handleReceive); + } + + @Override + public boolean supportsContext(Timer.HandlerContext context) { + return context != null && IntervalHttpClientEvent.class.isAssignableFrom(context.getClass()); + } + + @Override + HttpClientRequest getRequest(IntervalHttpClientEvent event) { + IntervalHttpClientEvent clientEvent = event; + return clientEvent.getRequest(); + } + + @Override + String getSpanName(IntervalHttpClientEvent event) { + return getRequest(event).method(); + } + + @Override + HttpClientResponse getResponse(IntervalHttpClientEvent event) { + IntervalHttpClientEvent clientEvent = event; + return clientEvent.getResponse(); + } + +} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/HttpServerTracingRecordingHandler.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/HttpServerTracingRecordingHandler.java new file mode 100755 index 00000000..c2bd7ab9 --- /dev/null +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/HttpServerTracingRecordingHandler.java @@ -0,0 +1,109 @@ +/* + * Copyright 2021-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.handler; + +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.tracing.context.IntervalHttpServerEvent; +import io.micrometer.core.instrument.transport.http.HttpResponse; +import io.micrometer.core.instrument.transport.http.HttpServerRequest; +import io.micrometer.core.instrument.transport.http.HttpServerResponse; +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.http.HttpServerHandler; + +/** + * TracingRecordingListener that uses the Tracing API to record events for HTTP server + * side. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class HttpServerTracingRecordingHandler extends + HttpTracingRecordingHandler + implements TracingRecordingHandler { + + /** + * Creates a new instance of {@link HttpServerTracingRecordingHandler}. + * + * @param tracer tracer + * @param handler http server handler + */ + public HttpServerTracingRecordingHandler(Tracer tracer, HttpServerHandler handler) { + super(tracer, handler::handleReceive, handler::handleSend); + } + + @Override + HttpServerRequest getRequest(IntervalHttpServerEvent event) { + IntervalHttpServerEvent serverEvent = event; + return serverEvent.getRequest(); + } + + @Override + String getSpanName(IntervalHttpServerEvent event) { + IntervalHttpServerEvent serverEvent = event; + if (serverEvent.getResponse() != null) { + return spanNameFromRoute(serverEvent.getResponse()); + } + return serverEvent.getRequest().method(); + } + + @Override + public boolean supportsContext(Timer.HandlerContext context) { + return context != null && IntervalHttpServerEvent.class.isAssignableFrom(context.getClass()); + } + + // taken from Brave + private String spanNameFromRoute(HttpResponse response) { + int statusCode = response.statusCode(); + String method = response.method(); + if (method == null) { + return null; // don't undo a valid name elsewhere + } + String route = response.route(); + if (route == null) { + return null; // don't undo a valid name elsewhere + } + if (!"".equals(route)) { + return method + " " + route; + } + return catchAllName(method, statusCode); + } + + // taken from Brave + private String catchAllName(String method, int statusCode) { + switch (statusCode) { + // from https://tools.ietf.org/html/rfc7231#section-6.4 + case 301: + case 302: + case 303: + case 305: + case 306: + case 307: + return method + " redirected"; + case 404: + return method + " not_found"; + default: + return null; + } + } + + @Override + HttpServerResponse getResponse(IntervalHttpServerEvent event) { + IntervalHttpServerEvent serverEvent = event; + return serverEvent.getResponse(); + } + +} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/HttpTracingRecordingHandler.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/HttpTracingRecordingHandler.java new file mode 100755 index 00000000..43d0e46a --- /dev/null +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/HttpTracingRecordingHandler.java @@ -0,0 +1,133 @@ +/* + * Copyright 2021-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.handler; + + +import java.time.Duration; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.tracing.context.IntervalHttpEvent; +import io.micrometer.core.instrument.transport.http.HttpRequest; +import io.micrometer.core.instrument.transport.http.HttpResponse; +import io.micrometer.tracing.CurrentTraceContext; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.lang.Nullable; + +@SuppressWarnings({"rawtypes", "unchecked"}) +abstract class HttpTracingRecordingHandler + implements TracingRecordingHandler { + + private final Tracer tracer; + + private final CurrentTraceContext currentTraceContext; + + private final Function startFunction; + + private final BiConsumer stopConsumer; + + // private final TracingTagFilter tracingTagFilter = new TracingTagFilter(); + + HttpTracingRecordingHandler(Tracer tracer, Function startFunction, + BiConsumer stopConsumer) { + this.tracer = tracer; + this.currentTraceContext = tracer.currentTraceContext(); + this.startFunction = startFunction; + this.stopConsumer = stopConsumer; + } + + @Override + public void onError(Timer.Sample sample, CTX ctx, Throwable throwable) { + + } + + @Override + public void onStart(Timer.Sample sample, CTX event) { + Span parentSpan = getTracingContext(event).getSpan(); + CurrentTraceContext.Scope scope = null; + if (parentSpan != null) { + scope = this.currentTraceContext.maybeScope(parentSpan.context()); + } + REQ request = getRequest(event); + Span span = this.startFunction.apply(request); + scope = this.currentTraceContext.newScope(span.context()); + getTracingContext(event).setSpanAndScope(span, scope); + } + + @Override + public void onRestore(Timer.Sample sample, CTX ctx) { + CurrentTraceContext.Scope scope = this.currentTraceContext + .maybeScope(getTracingContext(ctx).getSpan().context()); + getTracingContext(ctx).setScope(scope); + } + + @Override + public boolean supportsContext(Timer.HandlerContext context) { + return context != null + && context.getClass().isAssignableFrom(IntervalHttpEvent.class); + } + + @Override + public Tracer getTracer() { + return this.tracer; + } + + abstract REQ getRequest(CTX event); + + @Override + public void onStop(Timer.Sample sample, CTX ctx, Timer timer, + Duration duration) { + Span span = getTracingContext(ctx).getSpan(); + // this.tracingTagFilter.tagSpan(span, sample.getTags()); + span.name(getSpanName(ctx)); + tagSpan(ctx, span); + RES response = getResponse(ctx); + error(response, span); + this.stopConsumer.accept(response, span); + cleanup(ctx); + } + + // @Override + // public void record(InstantRecording instantRecording) { + // // TODO: Throw an exception? + // } + + abstract String getSpanName(CTX event); + + abstract RES getResponse(CTX event); + + private void error(@Nullable HttpResponse response, Span span) { + if (response == null) { + return; + } + int httpStatus = response.statusCode(); + Throwable error = response.error(); + if (error != null) { + return; + } + if (httpStatus == 0) { + return; + } + if (httpStatus < 100 || httpStatus > 399) { + // TODO: Move to a common place + span.tag("error", String.valueOf(httpStatus)); + } + } + +} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/TracingRecordingHandler.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/TracingRecordingHandler.java new file mode 100755 index 00000000..735800df --- /dev/null +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/TracingRecordingHandler.java @@ -0,0 +1,169 @@ +/* + * Copyright 2021-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.handler; + +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.TimerRecordingHandler; +import io.micrometer.tracing.CurrentTraceContext; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.lang.Nullable; + +/** + * Marker interface for tracing listeners. + * + * @author Marcin Grzejszczak + * @param type of event + * @since 1.0.0 + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public interface TracingRecordingHandler + extends TimerRecordingHandler { + + /** + * Sets span and a scope for that span in context. + * + * @param context recording with context to mutate + * @param span span to put in context + */ + default void setSpanAndScope(T context, Span span) { + if (span == null) { + return; + } + CurrentTraceContext.Scope scope = getTracer().currentTraceContext() + .maybeScope(span.context()); + getTracingContext(context).setSpanAndScope(span, scope); + } + + // @Override + // default void onCreate(Timer.Sample sample) { + // Span span = getTracer().currentSpan(); + // if (span != null) { + // setSpanAndScope(sample, span); + // } + // } + + default void tagSpan(T context, Span span) { + context.getAllTags().forEach(tag -> span.tag(tag.getKey(), tag.getValue())); + } + + /** + * Cleans the scope present in the context. + * + * @param context recording with context containing scope + */ + default void cleanup(T context) { + TracingContext tracingContext = getTracingContext(context); + tracingContext.getScope().close(); + } + + @Override + default void onRestore(Timer.Sample sample, T context) { + Span span = getTracingContext(context).getSpan(); + setSpanAndScope(context, span); + } + + @Nullable + default TracingContext getTracingContext(T context) { + // maybe consider returning a null ? + return context.computeIfAbsent(TracingContext.class, + (clazz) -> new TracingContext()); + } + + @Nullable + default void setTracingContext(T context, TracingContext tracingContext) { + context.put(TracingContext.class, tracingContext); + } + + @Override + default boolean supportsContext(Timer.HandlerContext context) { + return true; + } + + /** + * Returns the {@link Tracer}. + * + * @return tracer + */ + Tracer getTracer(); + + class HandledContextAttribute { + + } + + /** + * Basic tracing context. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + */ + class TracingContext { + + private Span span; + + private CurrentTraceContext.Scope scope; + + /** + * Returns the span. + * + * @return span + */ + Span getSpan() { + return this.span; + } + + /** + * Sets the span. + * + * @param span span to set + */ + void setSpan(Span span) { + this.span = span; + } + + /** + * Returns the scope of the span. + * + * @return scope of the span + */ + CurrentTraceContext.Scope getScope() { + return this.scope; + } + + /** + * Sets the current trace context scope. + * + * @param scope scope to set + */ + void setScope(CurrentTraceContext.Scope scope) { + this.scope = scope; + } + + /** + * Convenience method to set both span and scope. + * + * @param span span to set + * @param scope scope to set + */ + void setSpanAndScope(Span span, CurrentTraceContext.Scope scope) { + setSpan(span); + setScope(scope); + } + + } + +} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpClientHandler.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpClientHandler.java index 78239aa0..47792ec9 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpClientHandler.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpClientHandler.java @@ -16,6 +16,8 @@ package io.micrometer.tracing.http; +import io.micrometer.core.instrument.transport.http.HttpClientRequest; +import io.micrometer.core.instrument.transport.http.HttpClientResponse; import io.micrometer.tracing.Span; import io.micrometer.tracing.TraceContext; import io.micrometer.tracing.lang.Nullable; @@ -29,7 +31,7 @@ * * @author OpenZipkin Brave Authors * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 */ public interface HttpClientHandler { @@ -38,7 +40,6 @@ public interface HttpClientHandler { * context onto the request before returning. * * Call this before sending the request on the wire. - * * @param request to inject the tracing context with * @return client side span */ @@ -47,7 +48,6 @@ public interface HttpClientHandler { /** * Same as {@link #handleSend(HttpClientRequest)} but with an explicit parent * {@link TraceContext}. - * * @param request to inject the tracing context with * @param parent {@link TraceContext} that is to be the client side span's parent * @return client side span @@ -57,7 +57,6 @@ public interface HttpClientHandler { /** * Finishes the client span after assigning it tags according to the response or * error. - * * @param response the HTTP response * @param span span to be ended */ diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpRequest.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpRequest.java deleted file mode 100644 index cab29a3b..00000000 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpRequest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://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 io.micrometer.tracing.http; - -import io.micrometer.tracing.lang.Nullable; - -/** - * This API is taken from OpenZipkin Brave. - * - * Abstract response type used for parsing and sampling. Represents an HTTP request. - * - * @author OpenZipkin Brave Authors - * @author Marcin Grzejszczak - * @since 6.0.0 - */ -public interface HttpRequest extends Request { - - /** - * Returns an HTTP method. - * - * @return an HTTP method. - */ - String method(); - - /** - * Returns an HTTP path. - * - * @return an HTTP path or {@code null} if not set. - */ - @Nullable - String path(); - - /** - * Returns an expression such as "/items/:itemId" representing an application - * endpoint, conventionally associated with the tag key "http.route". If no route - * matched, "" (empty string) is returned. {@code null} indicates this instrumentation - * doesn't understand http routes. - * - * @return an HTTP route or {@code null} if not set - */ - @Nullable - default String route() { - return null; - } - - /** - * Returns an HTTP URL. - * - * @return an HTTP URL or {@code null} if not set. - */ - @Nullable - String url(); - - /** - * Returns a header. - * - * @param name header name - * @return an HTTP header or {@code null} if not set. - */ - @Nullable - String header(String name); - - /** - * Returns a remote IP. - * - * @return remote IP for the given connection. - */ - default String remoteIp() { - return null; - } - - /** - * Returns a remote port. - * - * @return remote port for the given connection. - */ - default int remotePort() { - return 0; - } - -} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpRequestParser.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpRequestParser.java index 2f8d0edb..1747d94b 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpRequestParser.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpRequestParser.java @@ -16,6 +16,7 @@ package io.micrometer.tracing.http; +import io.micrometer.core.instrument.transport.http.HttpRequest; import io.micrometer.tracing.SpanCustomizer; import io.micrometer.tracing.TraceContext; @@ -26,14 +27,13 @@ * * @author OpenZipkin Brave Authors * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 */ public interface HttpRequestParser { /** * Implement to choose what data from the http request are parsed into the span * representing it. - * * @param request current request * @param context corresponding trace context * @param span customizer for the current span diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpResponse.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpResponse.java deleted file mode 100644 index f0eac161..00000000 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpResponse.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://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 io.micrometer.tracing.http; - -import io.micrometer.tracing.lang.Nullable; - -/** - * This API is taken from OpenZipkin Brave. - * - * Abstract response type used for parsing and sampling. Represents an HTTP response. - * - * @author OpenZipkin Brave Authors - * @author Marcin Grzejszczak - * @since 6.0.0 - */ -public interface HttpResponse extends Response { - - @Nullable - @Override - default HttpRequest request() { - return null; - } - - /** - * Returns an HTTP method. - * - * @return an HTTP method - */ - @Nullable - default String method() { - HttpRequest request = request(); - return request != null ? request.method() : null; - } - - /** - * Returns an expression such as "/items/:itemId" representing an application - * endpoint, conventionally associated with the tag key "http.route". If no route - * matched, "" (empty string) is returned. {@code null} indicates this instrumentation - * doesn't understand http routes. - * - * @return an HTTP route or {@code null} if not set - */ - @Nullable - default String route() { - HttpRequest request = request(); - return request != null ? request.route() : null; - } - - /** - * Returns the HTTP status code. - * - * @return an HTTP status code or zero if unreadable - */ - int statusCode(); - - /** - * Returns the header value. - * - * @param header header name - * @return an HTTP header or {@code null} if not set. - */ - default String header(String header) { - return null; - } - -} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpResponseParser.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpResponseParser.java index 44653104..586b984b 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpResponseParser.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpResponseParser.java @@ -16,6 +16,7 @@ package io.micrometer.tracing.http; +import io.micrometer.core.instrument.transport.http.HttpResponse; import io.micrometer.tracing.SpanCustomizer; import io.micrometer.tracing.TraceContext; @@ -26,14 +27,13 @@ * * @author OpenZipkin Brave Authors * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 */ public interface HttpResponseParser { /** * Implement to choose what data from the http response are parsed into the span * representing it. - * * @param response current response * @param context corresponding trace context * @param span customizer for the current span diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpServerHandler.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpServerHandler.java index 9cbba81c..13f6f5ed 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpServerHandler.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpServerHandler.java @@ -16,6 +16,8 @@ package io.micrometer.tracing.http; +import io.micrometer.core.instrument.transport.http.HttpServerRequest; +import io.micrometer.core.instrument.transport.http.HttpServerResponse; import io.micrometer.tracing.Span; /** @@ -27,15 +29,14 @@ * * @author OpenZipkin Brave Authors * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 */ public interface HttpServerHandler { /** * Conditionally joins a span, or starts a new trace, depending on if a trace context * was extracted from the request. Tags are added before the span is started. - * - * @param request an HTTP request + * @param request HTTP request * @return server side span (either joined or a new trace) */ Span handleReceive(HttpServerRequest request); @@ -43,8 +44,7 @@ public interface HttpServerHandler { /** * Finishes the server span after assigning it tags according to the response or * error. - * - * @param response an HTTP response + * @param response HTTP response * @param span server side span to end */ void handleSend(HttpServerResponse response, Span span); diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpServerRequest.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpServerRequest.java deleted file mode 100644 index fae51a32..00000000 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpServerRequest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://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 io.micrometer.tracing.http; - -import io.micrometer.tracing.transport.Kind; - -/** - * This API is taken from OpenZipkin Brave. - * - * Abstract request type used for parsing and sampling. Represents an HTTP Server request. - * - * @author OpenZipkin Brave Authors - * @author Marcin Grzejszczak - * @since 6.0.0 - */ -public interface HttpServerRequest extends HttpRequest { - - /** - * Returns an HTTP attribute. - * - * @param key attribute key - * @return attribute with the given key or {@code null} if not set - */ - default Object getAttribute(String key) { - return null; - } - - /** - * Sets an HTTP attribute. - * - * @param key attribute key - * @param value attribute value - */ - default void setAttribute(String key, Object value) { - - } - - @Override - default Kind kind() { - return Kind.SERVER; - } - -} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/Request.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/http/Request.java deleted file mode 100644 index df15b44f..00000000 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/Request.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://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 io.micrometer.tracing.http; - -import java.util.Collection; - -import io.micrometer.tracing.transport.Kind; - -/** - * This API is taken from OpenZipkin Brave. - * - * Abstract request type used for parsing and sampling. - * - * @author OpenZipkin Brave Authors - * @author Marcin Grzejszczak - * @since 6.0.0 - */ -public interface Request { - - /** - * Returns the header names. - * - * @return list of header names - */ - Collection headerNames(); - - /** - * Returns the transport kind. - * - * @return the remote kind describing the direction and type of the request - */ - Kind kind(); - - /** - * Returns the underlying request object. - * - * @return the underlying request object or {@code null} if there is none - */ - Object unwrap(); - -} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/Response.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/http/Response.java deleted file mode 100644 index 35c2a256..00000000 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/Response.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://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 io.micrometer.tracing.http; - -import java.util.Collection; - -import io.micrometer.tracing.lang.Nullable; -import io.micrometer.tracing.transport.Kind; - -/** - * This API is taken from OpenZipkin Brave. - * - * Abstract response type used for parsing and sampling. - * - * @author OpenZipkin Brave Authors - * @author Marcin Grzejszczak - * @since 6.0.0 - */ -public interface Response { - - /** - * Returns the header names. - * - * @return list of header names - */ - Collection headerNames(); - - /** - * Returns the HTTP request. - * - * @return corresponding request - */ - @Nullable - Request request(); - - /** - * Returns the exception. - * - * @return exception that occurred or {@code null} if there was none. - */ - @Nullable - Throwable error(); - - /** - * Returns the underlying response object. - * - * @return the underlying response object or {@code null} if there is none. - */ - Object unwrap(); - - /** - * Returns the transport kind. - * - * @return the remote kind describing the direction and type of the request - */ - Kind kind(); - -} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/internal/DefaultSpanNamer.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/internal/DefaultSpanNamer.java new file mode 100755 index 00000000..c8fc5c91 --- /dev/null +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/internal/DefaultSpanNamer.java @@ -0,0 +1,68 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 org.springframework.boot.autoconfigure.observability.tracing.internal; + +import java.lang.reflect.Method; + +import io.micrometer.tracing.SpanName; +import io.micrometer.tracing.SpanNamer; + +/** + * Default implementation of SpanNamer that tries to get the span name as follows: + * + * * from the @SpanName annotation on the class if one is present. + * + * * from the @SpanName annotation on the method if passed object is of a {@link Method}. + * type + * + * * from the toString() of the delegate if it's not the default + * {@link Object#toString()}. + * + * * the default provided value. + * + * @author Marcin Grzejszczak + * @since 1.0.0 + * @see SpanName + */ +public class DefaultSpanNamer implements SpanNamer { + + private static boolean isDefaultToString(Object delegate, String spanName) { + if (delegate instanceof Method) { + return delegate.toString().equals(spanName); + } + return (delegate.getClass().getName() + "@" + Integer.toHexString(delegate.hashCode())).equals(spanName); + } + + @Override + public String name(Object object, String defaultValue) { + SpanName annotation = annotation(object); + String spanName = annotation != null ? annotation.value() : object.toString(); + // If there is no overridden toString method we'll put a constant value + if (isDefaultToString(object, spanName)) { + return defaultValue; + } + return spanName; + } + + private SpanName annotation(Object o) { + if (o instanceof Method) { + return ((Method) o).getAnnotation(SpanName.class); + } + return o.getClass().getAnnotation(SpanName.class); + } + +} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/internal/EncodingUtils.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/internal/EncodingUtils.java index f78da179..59ecd5bd 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/internal/EncodingUtils.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/internal/EncodingUtils.java @@ -22,7 +22,7 @@ * Adopted from OpenTelemetry API. * * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 1.0.0 */ public final class EncodingUtils { @@ -114,7 +114,7 @@ static long longFromBase16String(CharSequence chars, int offset) { private static void isTrue(boolean expression, String text) { if (!expression) { - throw new IllegalStateException(text); + throw new IllegalArgumentException(text); } } diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/internal/SpanNameUtil.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/internal/SpanNameUtil.java index e51b831b..a2341ded 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/internal/SpanNameUtil.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/internal/SpanNameUtil.java @@ -22,7 +22,7 @@ * Utility class that provides the name in hyphen based notation. * * @author Adrian Cole - * @since 6.0.0 + * @since 1.0.0 */ public final class SpanNameUtil { diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/propagation/Propagator.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/propagation/Propagator.java index 50379369..e90aa619 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/propagation/Propagator.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/propagation/Propagator.java @@ -33,14 +33,12 @@ * @author OpenZipkin Brave Authors * @author OpenTelemetry Authors * @author Marcin Grzejszczak - * @since 6.0.0 + * @since 3.0.0 */ public interface Propagator { /** - * Collection of headers that contain tracing information. - * - * @return tracing fields + * @return collection of headers that contain tracing information */ List fields(); @@ -48,11 +46,10 @@ public interface Propagator { * Injects the value downstream, for example as HTTP headers. The carrier may be null * to facilitate calling this method with a lambda for the {@link Setter}, in which * case that null will be passed to the {@link Setter} implementation. - * - * @param context the {@code Context} containing the value to be injected + * @param context the {@code Context} containing the value to be injected. * @param carrier holds propagation fields. For example, an outgoing message or http - * request - * @param setter invoked for each propagation key to add or remove + * request. + * @param setter invoked for each propagation key to add or remove. * @param carrier of propagation fields, such as an http request */ void inject(TraceContext context, @Nullable C carrier, Setter setter); @@ -64,12 +61,11 @@ public interface Propagator { * If the value could not be parsed, the underlying implementation will decide to set * an object representing either an empty value, an invalid value, or a valid value. * Implementation must not set {@code null}. - * * @param carrier holds propagation fields. For example, an outgoing message or http - * request - * @param getter invoked for each propagation key to get - * @param carrier of propagation fields, such as an http request - * @return the {@code Context} containing the extracted value + * request. + * @param getter invoked for each propagation key to get. + * @param carrier of propagation fields, such as an http request. + * @return the {@code Context} containing the extracted value. */ Span.Builder extract(C carrier, Getter getter); @@ -81,8 +77,8 @@ public interface Propagator { * {@code Setter} is stateless and allows to be saved as a constant to avoid runtime * allocations. * - * @since 6.0.0 * @param carrier of propagation fields, such as an http request + * @since 0.1.0 */ interface Setter { @@ -92,13 +88,12 @@ interface Setter { *

* For example, a setter for an {@link java.net.HttpURLConnection} would be the * method reference - * * {@link java.net.HttpURLConnection#addRequestProperty(String, String)} * @param carrier holds propagation fields. For example, an outgoing message or * http request. To facilitate implementations as java lambdas, this parameter may - * be null - * @param key the key of the field - * @param value the value of the field + * be null. + * @param key the key of the field. + * @param value the value of the field. */ void set(@Nullable C carrier, String key, String value); @@ -112,18 +107,17 @@ interface Setter { * {@code Getter} is stateless and allows to be saved as a constant to avoid runtime * allocations. * - * @param carrier of propagation fields, such as an http request + * @param carrier of propagation fields, such as an http request. */ interface Getter { /** * Returns the first value of the given propagation {@code key} or returns * {@code null}. - * - * @param carrier carrier of propagation fields, such as an http request - * @param key the key of the field + * @param carrier carrier of propagation fields, such as an http request. + * @param key the key of the field. * @return the first value of the given propagation {@code key} or returns - * {@code null} + * {@code null}. */ @Nullable String get(C carrier, String key); diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/Kind.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/Kind.java deleted file mode 100644 index 60d76ad5..00000000 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/Kind.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://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 io.micrometer.tracing.transport; - -/** - * Represents side of communication. - * - * @author Marcin Grzejszczak - * @since 6.0.0 - */ -public enum Kind { - - /** - * Indicates that the span covers server-side handling of an RPC or other remote - * request. - */ - SERVER, - - /** - * Indicates that the span covers the client-side wrapper around an RPC or other - * remote request. - */ - CLIENT, - - /** - * Indicates that the span describes producer sending a message to a broker. Unlike - * client and server, there is no direct critical path latency relationship between - * producer and consumer spans. - */ - PRODUCER, - - /** - * Indicates that the span describes consumer receiving a message from a broker. - * Unlike client and server, there is no direct critical path latency relationship - * between producer and consumer spans. - */ - CONSUMER - -} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpClientResponse.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpClientResponse.java deleted file mode 100644 index bdee4b11..00000000 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpClientResponse.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://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 io.micrometer.tracing.transport.http; - -import io.micrometer.tracing.lang.Nullable; -import io.micrometer.tracing.transport.Kind; - -/** - * This API is taken from OpenZipkin Brave. - * - * Abstract response type used for parsing and sampling. Represents an HTTP Client - * response. - * - * @author OpenZipkin Brave Authors - * @author Marcin Grzejszczak - * @since 6.0.0 - */ -public interface HttpClientResponse extends HttpResponse { - - @Nullable - default HttpClientRequest request() { - return null; - } - - @Override - default Throwable error() { - return null; - } - - @Override - default Kind kind() { - return Kind.CLIENT; - } - -} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpRequest.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpRequest.java deleted file mode 100644 index 96c28810..00000000 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpRequest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://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 io.micrometer.tracing.transport.http; - -import io.micrometer.tracing.lang.Nullable; - -/** - * This API is taken from OpenZipkin Brave. - * - * Abstract response type used for parsing and sampling. Represents an HTTP request. - * - * @author OpenZipkin Brave Authors - * @author Marcin Grzejszczak - * @since 6.0.0 - */ -public interface HttpRequest extends Request { - - /** - * Returns an HTTP method. - * - * @return an HTTP method. - */ - String method(); - - /** - * Returns an HTTP path. - * - * @return an HTTP path or {@code null} if not set. - */ - @Nullable - String path(); - - /** - * Returns an expression such as "/items/:itemId" representing an application - * endpoint, conventionally associated with the tag key "http.route". If no route - * matched, "" (empty string) is returned. {@code null} indicates this instrumentation - * doesn't understand http routes. - * - * @return an HTTP route or {@code null} if not set - */ - @Nullable - default String route() { - return null; - } - - /** - * Returns an HTTP URL. - * - * @return an HTTP URL or {@code null} if not set. - */ - @Nullable - String url(); - - /** - * Returns a header. - * - * @param name header name - * @return an HTTP header or {@code null} if not set. - */ - @Nullable - String header(String name); - - /** - * Returns a remote IP. - * - * @return remote IP for the given connection. - */ - default String remoteIp() { - return null; - } - - /** - * Returns a remote port. - * - * @return remote port for the given connection. - */ - default int remotePort() { - return 0; - } - -} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpResponse.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpResponse.java deleted file mode 100644 index 17f2b5ee..00000000 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpResponse.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://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 io.micrometer.tracing.transport.http; - -import io.micrometer.tracing.lang.Nullable; - -/** - * This API is taken from OpenZipkin Brave. - * - * Abstract response type used for parsing and sampling. Represents an HTTP response. - * - * @author OpenZipkin Brave Authors - * @author Marcin Grzejszczak - * @since 6.0.0 - */ -public interface HttpResponse extends Response { - - @Nullable - @Override - default HttpRequest request() { - return null; - } - - /** - * Returns an HTTP method. - * - * @return an HTTP method - */ - @Nullable - default String method() { - HttpRequest request = request(); - return request != null ? request.method() : null; - } - - /** - * Returns an expression such as "/items/:itemId" representing an application - * endpoint, conventionally associated with the tag key "http.route". If no route - * matched, "" (empty string) is returned. {@code null} indicates this instrumentation - * doesn't understand http routes. - * - * @return an HTTP route or {@code null} if not set - */ - @Nullable - default String route() { - HttpRequest request = request(); - return request != null ? request.route() : null; - } - - /** - * Returns the HTTP status code. - * - * @return an HTTP status code or zero if unreadable - */ - int statusCode(); - - /** - * Returns the header value. - * - * @param header header name - * @return an HTTP header or {@code null} if not set. - */ - default String header(String header) { - return null; - } - -} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpServerRequest.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpServerRequest.java deleted file mode 100644 index bcba8351..00000000 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpServerRequest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://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 io.micrometer.tracing.transport.http; - -import io.micrometer.tracing.transport.Kind; - -/** - * This API is taken from OpenZipkin Brave. - * - * Abstract request type used for parsing and sampling. Represents an HTTP Server request. - * - * @author OpenZipkin Brave Authors - * @author Marcin Grzejszczak - * @since 6.0.0 - */ -public interface HttpServerRequest extends HttpRequest { - - /** - * Returns an HTTP attribute. - * - * @param key attribute key - * @return attribute with the given key or {@code null} if not set - */ - default Object getAttribute(String key) { - return null; - } - - /** - * Sets an HTTP attribute. - * - * @param key attribute key - * @param value attribute value - */ - default void setAttribute(String key, Object value) { - - } - - @Override - default Kind kind() { - return Kind.SERVER; - } - -} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpServerResponse.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpServerResponse.java deleted file mode 100644 index 92b16b6e..00000000 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/HttpServerResponse.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://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 io.micrometer.tracing.transport.http; - -import io.micrometer.tracing.lang.Nullable; -import io.micrometer.tracing.transport.Kind; - -/** - * This API is taken from OpenZipkin Brave. - * - * Abstract response type used for parsing and sampling. Represents an HTTP Server - * response. - * - * @author OpenZipkin Brave Authors - * @author Marcin Grzejszczak - * @since 6.0.0 - */ -public interface HttpServerResponse extends HttpResponse { - - @Nullable - default HttpServerRequest request() { - return null; - } - - @Override - default Throwable error() { - return null; - } - - @Override - default Kind kind() { - return Kind.SERVER; - } - -} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/Request.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/Request.java deleted file mode 100644 index f5b5bbe3..00000000 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/Request.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://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 io.micrometer.tracing.transport.http; - -import java.util.Collection; - -import io.micrometer.tracing.transport.Kind; - -/** - * This API is taken from OpenZipkin Brave. - * - * Abstract request type used for parsing and sampling. - * - * @author OpenZipkin Brave Authors - * @author Marcin Grzejszczak - * @since 6.0.0 - */ -public interface Request { - - /** - * Returns the header names. - * - * @return list of header names - */ - Collection headerNames(); - - /** - * Returns the transport kind. - * - * @return the remote kind describing the direction and type of the request - */ - Kind kind(); - - /** - * Returns the underlying request object. - * - * @return the underlying request object or {@code null} if there is none - */ - Object unwrap(); - -} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/Response.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/Response.java deleted file mode 100644 index c5336788..00000000 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/transport/http/Response.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://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 io.micrometer.tracing.transport.http; - -import java.util.Collection; - -import io.micrometer.tracing.lang.Nullable; -import io.micrometer.tracing.transport.Kind; - -/** - * This API is taken from OpenZipkin Brave. - * - * Abstract response type used for parsing and sampling. - * - * @author OpenZipkin Brave Authors - * @author Marcin Grzejszczak - * @since 6.0.0 - */ -public interface Response { - - /** - * Returns the header names. - * - * @return list of header names - */ - Collection headerNames(); - - /** - * Returns the HTTP request. - * - * @return corresponding request - */ - @Nullable - Request request(); - - /** - * Returns the exception. - * - * @return exception that occurred or {@code null} if there was none. - */ - @Nullable - Throwable error(); - - /** - * Returns the underlying response object. - * - * @return the underlying response object or {@code null} if there is none. - */ - Object unwrap(); - - /** - * Returns the transport kind. - * - * @return the remote kind describing the direction and type of the request - */ - Kind kind(); - -} diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpClientResponse.java b/micrometer-tracing/src/test/java/io/micrometer/tracing/annotation/NoOpTagValueResolverTests.java similarity index 52% rename from micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpClientResponse.java rename to micrometer-tracing/src/test/java/io/micrometer/tracing/annotation/NoOpTagValueResolverTests.java index 772b8169..d61583f4 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/http/HttpClientResponse.java +++ b/micrometer-tracing/src/test/java/io/micrometer/tracing/annotation/NoOpTagValueResolverTests.java @@ -14,36 +14,20 @@ * limitations under the License. */ -package io.micrometer.tracing.http; +package io.micrometer.tracing.annotation; -import io.micrometer.tracing.lang.Nullable; -import io.micrometer.tracing.transport.Kind; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.BDDAssertions.then; /** - * This API is taken from OpenZipkin Brave. - * - * Abstract response type used for parsing and sampling. Represents an HTTP Client - * response. - * - * @author OpenZipkin Brave Authors * @author Marcin Grzejszczak - * @since 6.0.0 */ -public interface HttpClientResponse extends HttpResponse { - - @Nullable - default HttpClientRequest request() { - return null; - } - - @Override - default Throwable error() { - return null; - } +class NoOpTagValueResolverTests { - @Override - default Kind kind() { - return Kind.CLIENT; + @Test + void should_return_null() throws Exception { + then(new NoOpTagValueResolver().resolve("")).isNull(); } } diff --git a/micrometer-tracing/src/test/java/io/micrometer/tracing/docs/DocumentedSpanAssertionsTests.java b/micrometer-tracing/src/test/java/io/micrometer/tracing/docs/DocumentedSpanAssertionsTests.java new file mode 100644 index 00000000..82765516 --- /dev/null +++ b/micrometer-tracing/src/test/java/io/micrometer/tracing/docs/DocumentedSpanAssertionsTests.java @@ -0,0 +1,306 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.docs; + +import io.micrometer.tracing.Span; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.BDDMockito; + +import static io.micrometer.tracing.docs.DocumentedSpanAssertions.assertThatEventIsValid; +import static io.micrometer.tracing.docs.DocumentedSpanAssertions.assertThatKeyIsValid; +import static io.micrometer.tracing.docs.DocumentedSpanAssertions.assertThatNameIsValid; +import static io.micrometer.tracing.docs.DocumentedSpanAssertions.assertThatSpanStartedBeforeEnd; +import static io.micrometer.tracing.docs.DocumentedSpanAssertionsTests.MyEventsWithNotMatchingPrefix.A_BAR_EVENT; +import static io.micrometer.tracing.docs.DocumentedSpanAssertionsTests.MySpan.SPAN_WITH_DYNAMIC_ENTRIES; +import static io.micrometer.tracing.docs.DocumentedSpanAssertionsTests.MySpan.SPAN_WITH_EMPTY_TAGS_AND_EVENTS; +import static io.micrometer.tracing.docs.DocumentedSpanAssertionsTests.MySpan.SPAN_WITH_NOT_MATCHING_PREFIX; +import static io.micrometer.tracing.docs.DocumentedSpanAssertionsTests.MySpan.SPAN_WITH_PREFIX; +import static io.micrometer.tracing.docs.DocumentedSpanAssertionsTests.MyTags.A_FOO_TAG; +import static org.assertj.core.api.BDDAssertions.thenThrownBy; + +class DocumentedSpanAssertionsTests { + + @BeforeEach + void setup() { + DocumentedSpanAssertions.SLEUTH_SPAN_ASSERTIONS_ON = true; + } + + @Test + void should_do_nothing_when_system_property_not_turned_on() { + DocumentedSpanAssertions.SLEUTH_SPAN_ASSERTIONS_ON = false; + + assertThatKeyIsValid("unknown_key", SPAN_WITH_NOT_MATCHING_PREFIX); + assertThatKeyIsValid(A_FOO_TAG, SPAN_WITH_PREFIX); + assertThatEventIsValid("unknown_event", SPAN_WITH_PREFIX); + assertThatEventIsValid(A_BAR_EVENT, SPAN_WITH_PREFIX); + assertThatNameIsValid("unknown_name", SPAN_WITH_NOT_MATCHING_PREFIX); + assertThatSpanStartedBeforeEnd( + new ImmutableAssertingSpan(SPAN_WITH_NOT_MATCHING_PREFIX, BDDMockito.mock(Span.class))); + } + + @Test + void should_do_nothing_when_tags_or_events_are_empty() { + assertThatKeyIsValid("unknown_key", SPAN_WITH_EMPTY_TAGS_AND_EVENTS); + assertThatKeyIsValid(A_FOO_TAG, SPAN_WITH_EMPTY_TAGS_AND_EVENTS); + assertThatEventIsValid("unknown_event", SPAN_WITH_EMPTY_TAGS_AND_EVENTS); + assertThatEventIsValid(A_BAR_EVENT, SPAN_WITH_EMPTY_TAGS_AND_EVENTS); + } + + @Test + void should_not_fail_when_keys_and_values_are_properly_prefixed() { + assertThatKeyIsValid("some.key", SPAN_WITH_DYNAMIC_ENTRIES); + assertThatKeyIsValid(String.format(MyDynamicTags.A_DYNAMIC_TAG.getKey(), "some"), SPAN_WITH_DYNAMIC_ENTRIES); + assertThatEventIsValid("some.value", SPAN_WITH_DYNAMIC_ENTRIES); + assertThatEventIsValid(String.format(MyDynamicEvents.A_DYNAMIC_EVENT.getValue(), "some"), + SPAN_WITH_DYNAMIC_ENTRIES); + } + + @Test + void should_not_fail_when_span_was_started_and_then_ended() { + assertThatSpanStartedBeforeEnd( + new ImmutableAssertingSpan(SPAN_WITH_NOT_MATCHING_PREFIX, BDDMockito.mock(Span.class)).start()); + } + + @Test + void should_fail_when_assertion_is_on_and_a_key_is_unknown() { + thenThrownBy(() -> assertThatKeyIsValid("unknown_key", SPAN_WITH_NOT_MATCHING_PREFIX)) + .hasMessageContaining("The key [unknown_key] is invalid"); + thenThrownBy(() -> assertThatKeyIsValid(A_FOO_TAG, SPAN_WITH_NOT_MATCHING_PREFIX)) + .hasMessageContaining("The key [foo.key] is invalid"); + } + + @Test + void should_fail_when_assertion_is_on_and_an_event_is_unknown() { + thenThrownBy(() -> assertThatEventIsValid("unknown_event", SPAN_WITH_PREFIX)) + .hasMessageContaining("The event [unknown_event] is invalid"); + thenThrownBy(() -> assertThatEventIsValid(A_BAR_EVENT, SPAN_WITH_PREFIX)) + .hasMessageContaining("The event [bar.value] is invalid"); + } + + @Test + void should_fail_when_assertion_is_on_and_a_key_is_known_but_wrongly_prefixed() { + thenThrownBy(() -> assertThatKeyIsValid("bar.key", SPAN_WITH_NOT_MATCHING_PREFIX)) + .hasMessageContaining("Also it has start with [foo.] prefix"); + thenThrownBy(() -> assertThatKeyIsValid(MyTagsWithNotMatchingPrefix.A_BAR_TAG, SPAN_WITH_NOT_MATCHING_PREFIX)) + .hasMessageContaining("Also it has start with [foo.] prefix"); + } + + @Test + void should_fail_when_assertion_is_on_and_an_event_is_known_but_wrongly_prefixed() { + thenThrownBy(() -> assertThatEventIsValid("bar.value", SPAN_WITH_NOT_MATCHING_PREFIX)) + .hasMessageContaining("Also it has start with [foo.] prefix"); + thenThrownBy(() -> assertThatEventIsValid(A_BAR_EVENT, SPAN_WITH_NOT_MATCHING_PREFIX)) + .hasMessageContaining("Also it has start with [foo.] prefix"); + } + + @Test + void should_fail_when_assertion_is_on_and_a_key_is_known_but_dynamic_key_is_not_matched() { + thenThrownBy(() -> assertThatKeyIsValid("notmatching", SPAN_WITH_DYNAMIC_ENTRIES)) + .hasMessageContaining("The key [notmatching] is invalid. You can use only one matching [%s.key]"); + thenThrownBy(() -> assertThatKeyIsValid(MySimpleTag.A_SIMPLE_TAG, SPAN_WITH_DYNAMIC_ENTRIES)) + .hasMessageContaining("The key [simple] is invalid. You can use only one matching [%s.key]"); + } + + @Test + void should_fail_when_assertion_is_on_and_an_event_is_known_but_dynamic_value_is_not_matched() { + thenThrownBy(() -> assertThatEventIsValid("notmatching", SPAN_WITH_DYNAMIC_ENTRIES)) + .hasMessageContaining("The event [notmatching] is invalid. You can use only one matching [%s.value]"); + thenThrownBy(() -> assertThatEventIsValid(MySimpleEvent.A_SIMPLE_EVENT, SPAN_WITH_DYNAMIC_ENTRIES)) + .hasMessageContaining("The event [simple] is invalid. You can use only one matching [%s.value]"); + } + + @Test + void should_fail_when_assertion_is_on_and_name_is_invalid() { + thenThrownBy(() -> assertThatNameIsValid("unknown_name", SPAN_WITH_NOT_MATCHING_PREFIX)) + .hasMessageContaining("The name [unknown_name] is invalid"); + } + + @Test + void should_fail_when_assertion_is_on_and_name_is_not_matching() { + thenThrownBy(() -> assertThatNameIsValid("unknown_name", SPAN_WITH_DYNAMIC_ENTRIES)).hasMessageContaining( + "The name [unknown_name] is invalid. You can use only one matching [%s somename]"); + } + + @Test + void should_fail_when_span_was_ended_but_not_started() { + thenThrownBy(() -> assertThatSpanStartedBeforeEnd( + new ImmutableAssertingSpan(SPAN_WITH_NOT_MATCHING_PREFIX, BDDMockito.mock(Span.class)))) + .hasMessageContaining("The span was not started"); + } + + enum MySpan implements DocumentedSpan { + + SPAN_WITH_PREFIX { + @Override + public String getName() { + return "foo"; + } + + @Override + public TagKey[] getTagKeys() { + return MyTags.values(); + } + + @Override + public EventValue[] getEvents() { + return MyEvents.values(); + } + + @Override + public String prefix() { + return "foo."; + } + }, + + SPAN_WITH_NOT_MATCHING_PREFIX { + @Override + public String getName() { + return "bar"; + } + + @Override + public TagKey[] getTagKeys() { + return MyTagsWithNotMatchingPrefix.values(); + } + + @Override + public EventValue[] getEvents() { + return MyEventsWithNotMatchingPrefix.values(); + } + + @Override + public String prefix() { + return "foo."; + } + }, + + SPAN_WITH_EMPTY_TAGS_AND_EVENTS { + @Override + public String getName() { + return "baz"; + } + }, + + SPAN_WITH_DYNAMIC_ENTRIES { + @Override + public String getName() { + return "%s somename"; + } + + @Override + public TagKey[] getTagKeys() { + return MyDynamicTags.values(); + } + + @Override + public EventValue[] getEvents() { + return MyDynamicEvents.values(); + } + } + + } + + enum MyTags implements TagKey { + + A_FOO_TAG { + @Override + public String getKey() { + return "foo.key"; + } + } + + } + + enum MyEvents implements EventValue { + + A_FOO_EVENT { + @Override + public String getValue() { + return "foo.value"; + } + } + + } + + enum MyTagsWithNotMatchingPrefix implements TagKey { + + A_BAR_TAG { + @Override + public String getKey() { + return "bar.key"; + } + } + + } + + enum MyEventsWithNotMatchingPrefix implements EventValue { + + A_BAR_EVENT { + @Override + public String getValue() { + return "bar.value"; + } + } + + } + + enum MySimpleTag implements TagKey { + + A_SIMPLE_TAG { + @Override + public String getKey() { + return "simple"; + } + } + + } + + enum MySimpleEvent implements EventValue { + + A_SIMPLE_EVENT { + @Override + public String getValue() { + return "simple"; + } + } + + } + + enum MyDynamicTags implements TagKey { + + A_DYNAMIC_TAG { + @Override + public String getKey() { + return "%s.key"; + } + } + + } + + enum MyDynamicEvents implements EventValue { + + A_DYNAMIC_EVENT { + @Override + public String getValue() { + return "%s.value"; + } + } + + } + +} diff --git a/micrometer-tracing/src/test/java/io/micrometer/tracing/exporter/SpanIgnoringSpanFilterTests.java b/micrometer-tracing/src/test/java/io/micrometer/tracing/exporter/SpanIgnoringSpanFilterTests.java new file mode 100644 index 00000000..27adbf0c --- /dev/null +++ b/micrometer-tracing/src/test/java/io/micrometer/tracing/exporter/SpanIgnoringSpanFilterTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://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 io.micrometer.tracing.exporter; + +import java.util.Collections; + +import org.junit.jupiter.api.Test; +import org.mockito.BDDMockito; + +import static org.assertj.core.api.BDDAssertions.then; + +class SpanIgnoringSpanFilterTests { + + private FinishedSpan namedSpan() { + FinishedSpan span = BDDMockito.mock(FinishedSpan.class); + BDDMockito.given(span.getName()).willReturn("someName"); + return span; + } + + @Test + void should_not_handle_span_when_present_in_main_list_of_spans_to_skip() { + SpanIgnoringSpanFilter handler = new SpanIgnoringSpanFilter(Collections.singletonList("someName"), + Collections.emptyList()); + + then(handler.isExportable(namedSpan())).isFalse(); + } + + @Test + void should_not_handle_span_when_present_in_additional_list_of_spans_to_skip() { + SpanIgnoringSpanFilter handler = new SpanIgnoringSpanFilter(Collections.emptyList(), + Collections.singletonList("someName")); + + then(handler.isExportable(namedSpan())).isFalse(); + } + + @Test + void should_use_cached_entry_for_same_patterns() { + export(handler("someOtherName")); + export(handler("someOtherName")); + export(handler("someOtherName")); + + then(SpanIgnoringSpanFilter.cache).containsKey("someOtherName"); + + export(handler("a")); + export(handler("b")); + export(handler("c")); + + then(SpanIgnoringSpanFilter.cache).containsKey("someOtherName").containsKey("a").containsKey("b") + .containsKey("c"); + } + + private void export(SpanIgnoringSpanFilter handler) { + handler.isExportable(namedSpan()); + } + + private SpanIgnoringSpanFilter handler(String name) { + return new SpanIgnoringSpanFilter(Collections.emptyList(), Collections.singletonList(name)); + } + +} diff --git a/micrometer-tracing/src/test/resources/logback.xml b/micrometer-tracing/src/test/resources/logback.xml index 4f0c9679..7831a9ac 100644 --- a/micrometer-tracing/src/test/resources/logback.xml +++ b/micrometer-tracing/src/test/resources/logback.xml @@ -17,30 +17,30 @@ --> - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + - - - + + + - + - - - + + + - - - - + + + + - + - + diff --git a/settings.gradle b/settings.gradle index 2a1fcb9d..d513d954 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,25 +1,35 @@ pluginManagement { - repositories { - gradlePluginPortal() - maven { url 'https://repo.spring.io/release' } - maven { url "https://repo.spring.io/milestone" } - } + repositories { + gradlePluginPortal() + maven { url 'https://repo.spring.io/release' } + maven { url "https://repo.spring.io/milestone" } + } } plugins { - id 'com.gradle.enterprise' version '3.7' - id 'io.spring.ge.conventions' version '0.0.8' + id 'com.gradle.enterprise' version '3.7' + id 'io.spring.ge.conventions' version '0.0.8' } rootProject.name = 'tracing' buildCache { - remote(HttpBuildCache) { - url = 'https://ge.micrometer.io/cache/' - } + remote(HttpBuildCache) { + url = 'https://ge.micrometer.io/cache/' + } } gradleEnterprise { - server = 'https://ge.micrometer.io' + server = 'https://ge.micrometer.io' } include 'micrometer-tracing', 'micrometer-tracing-bom' + +['brave', 'otel'].each { bridge -> + include "micrometer-tracing-bridge-$bridge" + project(":micrometer-tracing-bridge-$bridge").projectDir = new File(rootProject.projectDir, "micrometer-tracing-bridges/micrometer-tracing-bridge-$bridge") +} + +['wavefront', 'zipkin'].each { reporter -> + include "micrometer-tracing-reporter-$reporter" + project(":micrometer-tracing-reporter-$reporter").projectDir = new File(rootProject.projectDir, "micrometer-tracing-reporters/micrometer-tracing-reporter-$reporter") +}