├── src └── etc │ └── header.txt ├── http ├── src │ ├── main │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── services │ │ │ │ └── com.twitter.finagle.tracing.Tracer │ │ └── java │ │ │ ├── zipkin │ │ │ └── http │ │ │ │ ├── hostHeader$.java │ │ │ │ ├── tlsEnabled$.java │ │ │ │ ├── compressionEnabled$.java │ │ │ │ ├── path$.java │ │ │ │ ├── host$.java │ │ │ │ └── tlsValidationEnabled$.java │ │ │ └── zipkin2 │ │ │ └── finagle │ │ │ └── http │ │ │ ├── HttpSender.java │ │ │ └── HttpZipkinTracer.java │ └── test │ │ ├── resources │ │ └── log4j2.properties │ │ └── java │ │ ├── zipkin │ │ └── http │ │ │ ├── hostTest.java │ │ │ ├── pathTest.java │ │ │ ├── tlsEnabledTest.java │ │ │ ├── hostHeaderTest.java │ │ │ ├── compressionEnabledTest.java │ │ │ └── tlsValidationEnabledTest.java │ │ └── zipkin2 │ │ └── finagle │ │ └── http │ │ └── ITHttpZipkinTracer.java └── pom.xml ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── kafka ├── src │ ├── main │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── services │ │ │ │ └── com.twitter.finagle.tracing.Tracer │ │ └── java │ │ │ ├── zipkin │ │ │ └── kafka │ │ │ │ ├── topic$.java │ │ │ │ └── bootstrapServers$.java │ │ │ └── zipkin2 │ │ │ └── finagle │ │ │ └── kafka │ │ │ └── KafkaZipkinTracer.java │ └── test │ │ ├── resources │ │ └── log4j2.properties │ │ └── java │ │ ├── zipkin2 │ │ └── finagle │ │ │ └── kafka │ │ │ ├── KafkaZipkinTracerTest.java │ │ │ └── ITKafkaZipkinTracer.java │ │ └── zipkin │ │ └── kafka │ │ ├── topicTest.java │ │ └── bootstrapServersTest.java └── pom.xml ├── scribe ├── src │ ├── main │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── services │ │ │ │ └── com.twitter.finagle.tracing.Tracer │ │ └── java │ │ │ ├── zipkin │ │ │ └── scribe │ │ │ │ └── host$.java │ │ │ └── zipkin2 │ │ │ └── finagle │ │ │ └── scribe │ │ │ ├── ScribeSender.java │ │ │ └── ScribeZipkinTracer.java │ └── test │ │ ├── resources │ │ └── log4j2.properties │ │ └── java │ │ ├── zipkin2 │ │ └── finagle │ │ │ └── scribe │ │ │ ├── ScribeSenderTest.java │ │ │ └── ITScribeZipkinTracer.java │ │ └── zipkin │ │ └── scribe │ │ └── hostTest.java └── pom.xml ├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── bug.md │ └── feature.md ├── CONTRIBUTING.md └── workflows │ ├── test.yml │ ├── create_release.yml │ └── deploy.yml ├── core ├── src │ ├── test │ │ ├── resources │ │ │ └── log4j2.properties │ │ └── java │ │ │ ├── zipkin2 │ │ │ └── finagle │ │ │ │ ├── FinagleTestObjects.java │ │ │ │ ├── WithTimeAt.java │ │ │ │ ├── FakeSender.java │ │ │ │ ├── ITZipkinTracer.java │ │ │ │ ├── SpanRecorderTest.java │ │ │ │ └── ZipkinTracerTest.java │ │ │ └── zipkin │ │ │ ├── initialSampleRateTest.java │ │ │ └── localServiceNameTest.java │ └── main │ │ └── java │ │ ├── zipkin │ │ ├── initialSampleRate$.java │ │ └── localServiceName$.java │ │ └── zipkin2 │ │ └── finagle │ │ ├── Endpoints.java │ │ ├── FinagleSender.java │ │ ├── ReporterMetricsAdapter.java │ │ ├── MutableSpan.java │ │ ├── SpanRecorder.java │ │ └── ZipkinTracer.java └── pom.xml ├── .settings.xml ├── RELEASE.md ├── mvnw.cmd ├── README.md ├── mvnw └── LICENSE /src/etc/header.txt: -------------------------------------------------------------------------------- 1 | Copyright The OpenZipkin Authors 2 | SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /http/src/main/resources/META-INF/services/com.twitter.finagle.tracing.Tracer: -------------------------------------------------------------------------------- 1 | zipkin2.finagle.http.HttpZipkinTracer 2 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openzipkin/zipkin-finagle/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /kafka/src/main/resources/META-INF/services/com.twitter.finagle.tracing.Tracer: -------------------------------------------------------------------------------- 1 | zipkin2.finagle.kafka.KafkaZipkinTracer 2 | -------------------------------------------------------------------------------- /scribe/src/main/resources/META-INF/services/com.twitter.finagle.tracing.Tracer: -------------------------------------------------------------------------------- 1 | zipkin2.finagle.scribe.ScribeZipkinTracer 2 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 4 | hs_err_pid* 5 | 6 | # Maven 7 | target/ 8 | 9 | # IntelliJ 10 | .idea/ 11 | *.iml 12 | 13 | # macOS 14 | .DS_Store 15 | 16 | # Eclipse 17 | .classpath 18 | .project 19 | .settings/ 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Question 4 | url: https://gitter.im/openzipkin/zipkin 5 | about: Please ask questions about how to do something or to understand why something isn't working on our Gitter chat - we'll be happy to respond there in detail. This issue tracker is not for questions. 6 | -------------------------------------------------------------------------------- /core/src/test/resources/log4j2.properties: -------------------------------------------------------------------------------- 1 | appenders = console 2 | appender.console.type = Console 3 | appender.console.name = STDOUT 4 | appender.console.layout.type = PatternLayout 5 | appender.console.layout.pattern = %d{ABSOLUTE} %-5p [%t] %C{2} (%F:%L) - %m%n 6 | rootLogger.level = warn 7 | rootLogger.appenderRefs = stdout 8 | rootLogger.appenderRef.stdout.ref = STDOUT 9 | -------------------------------------------------------------------------------- /http/src/test/resources/log4j2.properties: -------------------------------------------------------------------------------- 1 | appenders = console 2 | appender.console.type = Console 3 | appender.console.name = STDOUT 4 | appender.console.layout.type = PatternLayout 5 | appender.console.layout.pattern = %d{ABSOLUTE} %-5p [%t] %C{2} (%F:%L) - %m%n 6 | rootLogger.level = warn 7 | rootLogger.appenderRefs = stdout 8 | rootLogger.appenderRef.stdout.ref = STDOUT 9 | -------------------------------------------------------------------------------- /scribe/src/test/resources/log4j2.properties: -------------------------------------------------------------------------------- 1 | appenders = console 2 | appender.console.type = Console 3 | appender.console.name = STDOUT 4 | appender.console.layout.type = PatternLayout 5 | appender.console.layout.pattern = %d{ABSOLUTE} %-5p [%t] %C{2} (%F:%L) - %m%n 6 | rootLogger.level = warn 7 | rootLogger.appenderRefs = stdout 8 | rootLogger.appenderRef.stdout.ref = STDOUT 9 | -------------------------------------------------------------------------------- /kafka/src/test/resources/log4j2.properties: -------------------------------------------------------------------------------- 1 | appenders=console 2 | appender.console.type=Console 3 | appender.console.name=STDOUT 4 | appender.console.layout.type=PatternLayout 5 | appender.console.layout.pattern=%d{ABSOLUTE} %-5p [%t] %C{2} (%F:%L) - %m%n 6 | rootLogger.level=warn 7 | rootLogger.appenderRefs=stdout 8 | rootLogger.appenderRef.stdout.ref=STDOUT 9 | # uncomment to include kafka consumer configuration in test logs 10 | #logger.kafka-clients.name=org.apache.kafka.clients 11 | #logger.kafka-clients.level=info 12 | logger.kafkaunit.name=com.github.charithe.kafka 13 | logger.kafkaunit.level=warn 14 | -------------------------------------------------------------------------------- /kafka/src/main/java/zipkin/kafka/topic$.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.kafka; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.Flaggable; 9 | import com.twitter.app.JavaGlobalFlag; 10 | 11 | public final class topic$ extends JavaGlobalFlag { 12 | private topic$() { 13 | super("zipkin", "Kafka topic zipkin traces will be sent to", Flaggable.ofString()); 14 | } 15 | 16 | public static final Flag Flag = new topic$(); 17 | 18 | public static Flag globalFlagInstance() { 19 | return Flag; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /kafka/src/test/java/zipkin2/finagle/kafka/KafkaZipkinTracerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle.kafka; 6 | 7 | import org.junit.Test; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | public class KafkaZipkinTracerTest { 12 | 13 | @Test 14 | public void bootstrapServersParsesMultipleHosts() { 15 | zipkin.kafka.bootstrapServers$.Flag.parse("host1:9092,host2:9092"); 16 | 17 | assertThat(KafkaZipkinTracer.Config.builder().build().bootstrapServers()) 18 | .isEqualTo("host1:9092,host2:9092"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /http/src/main/java/zipkin/http/hostHeader$.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.http; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.Flaggable; 9 | import com.twitter.app.JavaGlobalFlag; 10 | 11 | public final class hostHeader$ extends JavaGlobalFlag { 12 | private hostHeader$() { 13 | super("zipkin", "The Host header used when sending spans to Zipkin", Flaggable.ofString()); 14 | } 15 | 16 | public static final Flag Flag = new hostHeader$(); 17 | 18 | public static Flag globalFlagInstance() { 19 | return Flag; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /http/src/main/java/zipkin/http/tlsEnabled$.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.http; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.Flaggable; 9 | import com.twitter.app.JavaGlobalFlag; 10 | 11 | public final class tlsEnabled$ extends JavaGlobalFlag { 12 | private tlsEnabled$() { 13 | super(false, "Whether or not the Zipkin host uses TLS", 14 | Flaggable.ofJavaBoolean()); 15 | } 16 | 17 | public static final Flag Flag = new tlsEnabled$(); 18 | 19 | public static Flag globalFlagInstance() { 20 | return Flag; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/java/zipkin/initialSampleRate$.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.Flaggable; 9 | import com.twitter.app.JavaGlobalFlag; 10 | 11 | public final class initialSampleRate$ extends JavaGlobalFlag { 12 | private initialSampleRate$() { 13 | super(0.001f, "Percentage of traces to sample (report to zipkin) in the range [0.0 - 1.0]", 14 | Flaggable.ofJavaFloat()); 15 | } 16 | 17 | public static final Flag Flag = new initialSampleRate$(); 18 | 19 | public static Flag globalFlagInstance() { 20 | return Flag; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /http/src/main/java/zipkin/http/compressionEnabled$.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.http; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.Flaggable; 9 | import com.twitter.app.JavaGlobalFlag; 10 | 11 | public final class compressionEnabled$ extends JavaGlobalFlag { 12 | private compressionEnabled$() { 13 | super(true, "True implies that spans will be gzipped before transport", 14 | Flaggable.ofJavaBoolean()); 15 | } 16 | 17 | public static final Flag Flag = new compressionEnabled$(); 18 | 19 | public static Flag globalFlagInstance() { 20 | return Flag; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/java/zipkin/localServiceName$.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.Flaggable; 9 | import com.twitter.app.JavaGlobalFlag; 10 | 11 | public final class localServiceName$ extends JavaGlobalFlag { 12 | private localServiceName$() { 13 | super("unknown", "The localEndpoint.serviceName to use for all spans sent to Zipkin", 14 | Flaggable.ofString()); 15 | } 16 | 17 | public static final com.twitter.app.Flag Flag = new localServiceName$(); 18 | 19 | public static Flag globalFlagInstance() { 20 | return Flag; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /http/src/main/java/zipkin/http/path$.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.http; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.Flaggable; 9 | import com.twitter.app.JavaGlobalFlag; 10 | 11 | public final class path$ extends JavaGlobalFlag { 12 | public static String DEFAULT_PATH = "/api/v2/spans"; 13 | 14 | private path$() { 15 | super(DEFAULT_PATH, 16 | "The path to the spans endpoint", 17 | Flaggable.ofString()); 18 | } 19 | 20 | public static final Flag Flag = new path$(); 21 | 22 | public static Flag globalFlagInstance() { 23 | return Flag; 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /http/src/main/java/zipkin/http/host$.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.http; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.Flaggable; 9 | import com.twitter.app.JavaGlobalFlag; 10 | 11 | public final class host$ extends JavaGlobalFlag { 12 | private host$() { 13 | super("localhost:9411", 14 | "The network location of the Zipkin http service. See http://twitter.github.io/finagle/guide/Names.html", 15 | Flaggable.ofString()); 16 | } 17 | 18 | public static final Flag Flag = new host$(); 19 | 20 | public static Flag globalFlagInstance() { 21 | return Flag; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /scribe/src/main/java/zipkin/scribe/host$.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.scribe; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.Flaggable; 9 | import com.twitter.app.JavaGlobalFlag; 10 | 11 | public final class host$ extends JavaGlobalFlag { 12 | private host$() { 13 | super("localhost:1463", 14 | "The network location of the Scribe service. See http://twitter.github.io/finagle/guide/Names.html", 15 | Flaggable.ofString()); 16 | } 17 | 18 | public static final Flag Flag = new host$(); 19 | 20 | public static Flag globalFlagInstance() { 21 | return Flag; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /http/src/main/java/zipkin/http/tlsValidationEnabled$.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.http; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.Flaggable; 9 | import com.twitter.app.JavaGlobalFlag; 10 | 11 | public final class tlsValidationEnabled$ extends JavaGlobalFlag { 12 | private tlsValidationEnabled$() { 13 | super(true, "Whether or not to enable TLS validation for the Zipkin host when TLS is enabled", 14 | Flaggable.ofJavaBoolean()); 15 | } 16 | 17 | public static final Flag Flag = new tlsValidationEnabled$(); 18 | 19 | public static Flag globalFlagInstance() { 20 | return Flag; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 3 | about: If you’ve found a bug, spend the time to write a failing test. Bugs with tests get fixed and stay fixed. If you have a solution in mind, skip raising an issue and open a pull request instead. 4 | labels: bug 5 | --- 6 | ## Describe the Bug 7 | A clear and concise description of what the bug is. If you have a solution in mind, skip raising an issue and open a pull request instead. 8 | 9 | Regardless, the best is to spend some time to write a failing test. Bugs with tests get fixed and stay fixed. 10 | 11 | ## Steps to Reproduce 12 | Steps to reproduce the behavior: 13 | 14 | ## Expected Behaviour 15 | Suggest what you think correct behaviour should be. Note, it may be solved differently depending on the problem. 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Please first, look at existing issues to see if the feature has been requested before. 4 | labels: enhancement 5 | --- 6 | Please first, look at [existing issues](https://github.com/openzipkin/zipkin-finagle/issues) to see if the feature has been requested before. If you don't find anything tell us what problem you’re trying to solve. Often a solution already exists! Don’t send pull requests to implement new features without first getting our support. Sometimes we leave features out on purpose to keep the project small. 7 | 8 | ## Feature 9 | Description of the feature 10 | 11 | ## Rationale 12 | Why would this feature help others besides me? 13 | 14 | ## Example Scenario 15 | What kind of use cases would benefit from this feature? 16 | 17 | ## Prior Art 18 | * Links to prior art 19 | * More links 20 | -------------------------------------------------------------------------------- /.settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | 14 | gpg.passphrase 15 | ${env.GPG_PASSPHRASE} 16 | 17 | 18 | ossrh 19 | ${env.SONATYPE_USER} 20 | ${env.SONATYPE_PASSWORD} 21 | 22 | 23 | github.com 24 | zipkinci 25 | ${env.GH_TOKEN} 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Zipkin 2 | 3 | If you would like to contribute code, fork this GitHub repository and 4 | send a pull request (on a branch other than `master` or `gh-pages`). 5 | 6 | When submitting code, please apply [Square Code Style](https://github.com/square/java-code-styles). 7 | * If the settings import correctly, CodeStyle/Java will be named Square and use 2 space tab and indent, with 4 space continuation indent. 8 | 9 | ## License 10 | 11 | By contributing your code, you agree to license your contribution under 12 | the terms of the [APLv2](../LICENSE). 13 | 14 | All files are released with the Apache 2.0 license. 15 | 16 | If you are adding a new file it should have a header like below. This 17 | can be automatically added by running `./mvnw com.mycila:license-maven-plugin:format`. 18 | 19 | ``` 20 | /* 21 | * Copyright The OpenZipkin Authors 22 | * SPDX-License-Identifier: Apache-2.0 23 | */ 24 | ``` 25 | -------------------------------------------------------------------------------- /kafka/src/main/java/zipkin/kafka/bootstrapServers$.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.kafka; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.Flaggable; 9 | import com.twitter.app.JavaGlobalFlag; 10 | import java.net.InetSocketAddress; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | public final class bootstrapServers$ extends JavaGlobalFlag> { 15 | private bootstrapServers$() { 16 | super(Collections.singletonList(new InetSocketAddress("localhost", 9092)), 17 | "Initial set of kafka servers to connect to, rest of cluster will be discovered (comma separated)", 18 | Flaggable.ofJavaList(Flaggable.ofInetSocketAddress())); 19 | } 20 | 21 | public static final Flag> Flag = new bootstrapServers$(); 22 | 23 | public static Flag globalFlagInstance() { 24 | return Flag; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 4.0.0 10 | 11 | 12 | io.zipkin.finagle2 13 | zipkin-finagle-parent_2.12 14 | 23.0.3-SNAPSHOT 15 | 16 | 17 | zipkin-finagle_2.12 18 | 23.0.3-SNAPSHOT 19 | Zipkin Finagle: Core 20 | 21 | 22 | ${project.basedir}/.. 23 | 24 | 25 | 26 | 27 | 28 | maven-jar-plugin 29 | 30 | 31 | 32 | test-jar 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # yamllint --format github .github/workflows/test.yml 2 | --- 3 | name: test 4 | 5 | # We don't test documentation-only commits. 6 | on: 7 | # We run tests on non-tagged pushes to master that aren't a commit made by the release plugin 8 | push: 9 | tags: '' 10 | branches: master 11 | paths-ignore: '**/*.md' 12 | # We also run tests on pull requests targeted at the master branch. 13 | pull_request: 14 | branches: master 15 | paths-ignore: '**/*.md' 16 | 17 | jobs: 18 | test: 19 | # Hard-coding an LTS means maintenance, but only once each 2 years! 20 | runs-on: ubuntu-22.04 21 | if: "!contains(github.event.head_commit.message, 'maven-release-plugin')" 22 | steps: 23 | - name: Checkout Repository 24 | uses: actions/checkout@v4 25 | - name: Setup java 26 | uses: actions/setup-java@v4 27 | with: 28 | distribution: 'zulu' # zulu as it supports a wide version range 29 | java-version: 17 30 | - name: Cache local Maven repository 31 | uses: actions/cache@v3 32 | with: 33 | path: ~/.m2/repository 34 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 35 | restore-keys: ${{ runner.os }}-maven- 36 | - name: Test 37 | run: build-bin/configure_test && build-bin/test 38 | -------------------------------------------------------------------------------- /scribe/src/test/java/zipkin2/finagle/scribe/ScribeSenderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle.scribe; 6 | 7 | import org.apache.thrift.TException; 8 | import org.junit.Test; 9 | import zipkin2.Endpoint; 10 | import zipkin2.Span; 11 | import zipkin2.codec.SpanBytesEncoder; 12 | import zipkin2.finagle.FinagleTestObjects; 13 | import zipkin2.finagle.scribe.ScribeZipkinTracer.Config; 14 | 15 | import static java.util.Arrays.asList; 16 | 17 | public class ScribeSenderTest { 18 | 19 | Config config = Config.builder().initialSampleRate(1.0f).host("127.0.0.1:9410").build(); 20 | 21 | @Test 22 | public void makeRequest() throws TException { 23 | Endpoint web = Endpoint.newBuilder().serviceName("web").ip("127.0.0.1").build(); 24 | Span span1 = 25 | Span.newBuilder() 26 | .traceId(FinagleTestObjects.root.traceId().toString()) 27 | .id(FinagleTestObjects.root.spanId().toLong()) 28 | .name("get") 29 | .timestamp(FinagleTestObjects.TODAY * 1000) 30 | .duration(1000L) 31 | .kind(Span.Kind.SERVER) 32 | .localEndpoint(web) 33 | .build(); 34 | 35 | Span span2 = 36 | span1 37 | .toBuilder() 38 | .kind(Span.Kind.CLIENT) 39 | .parentId(FinagleTestObjects.child.parentId().toLong()) 40 | .id(FinagleTestObjects.child.spanId().toLong()) 41 | .build(); 42 | 43 | new ScribeSender(config) 44 | .makeRequest( 45 | asList(SpanBytesEncoder.THRIFT.encode(span1), SpanBytesEncoder.THRIFT.encode(span2))); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /core/src/test/java/zipkin2/finagle/FinagleTestObjects.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle; 6 | 7 | import com.twitter.finagle.tracing.Flags$; 8 | import com.twitter.finagle.tracing.SpanId; 9 | import com.twitter.finagle.tracing.TraceId; 10 | import java.util.Calendar; 11 | import java.util.TimeZone; 12 | import scala.Option; 13 | import scala.collection.immutable.Seq; 14 | import scala.collection.immutable.Seq$; 15 | import scala.collection.mutable.Builder; 16 | 17 | import static scala.Option.empty; 18 | 19 | public final class FinagleTestObjects { 20 | public static final long TODAY = midnightUTC(System.currentTimeMillis()); 21 | public static final SpanId traceId = SpanId.fromString("d2f9288a2904503d").get(); 22 | public static final TraceId root = new TraceId(Option.apply(traceId), empty(), traceId, empty(), 23 | Flags$.MODULE$.apply()); 24 | public static final TraceId child = new TraceId(Option.apply(traceId), Option.apply(traceId), 25 | SpanId.fromString("0f28590523a46541").get(), empty(), Flags$.MODULE$.apply()); 26 | 27 | public static Seq seq(String... entries) { 28 | // Raw Seq param to avoid generics conflict between scala 2.12 and 2.13 29 | Builder builder = Seq$.MODULE$.newBuilder(); 30 | for (String entry: entries) builder.$plus$eq(entry); 31 | return (Seq) builder.result(); 32 | } 33 | 34 | static long midnightUTC(long epochMillis) { 35 | Calendar day = Calendar.getInstance(TimeZone.getTimeZone("UTC")); 36 | day.setTimeInMillis(epochMillis); 37 | day.set(14, 0); 38 | day.set(13, 0); 39 | day.set(12, 0); 40 | day.set(11, 0); 41 | return day.getTimeInMillis(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /core/src/main/java/zipkin2/finagle/Endpoints.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle; 6 | 7 | import java.net.InetAddress; 8 | import java.net.InetSocketAddress; 9 | import java.net.SocketAddress; 10 | import zipkin2.Endpoint; 11 | 12 | final class Endpoints { 13 | static final Endpoint LOOPBACK = Endpoint.newBuilder().ip("127.0.0.1").build(); 14 | static final Endpoint UNKNOWN = Endpoint.newBuilder().build(); 15 | static final Endpoint LOCAL; 16 | 17 | static { 18 | Endpoint.Builder builder = Endpoint.newBuilder(); 19 | builder.parseIp(InetAddress.getLoopbackAddress()); 20 | LOCAL = builder.build(); 21 | } 22 | 23 | private Endpoints() { 24 | } 25 | 26 | /** 27 | * @return If possible, convert from a SocketAddress object to an Endpoint. If not, return Unknown 28 | * Endpoint. 29 | */ 30 | static Endpoint fromSocketAddress(SocketAddress socketAddress) { 31 | if (socketAddress instanceof InetSocketAddress) { 32 | Endpoint.Builder builder = Endpoint.newBuilder(); 33 | InetSocketAddress inet = (InetSocketAddress) socketAddress; 34 | builder.parseIp(inet.getAddress()); 35 | builder.port(inet.getPort()); 36 | return builder.build(); 37 | } 38 | return UNKNOWN; 39 | } 40 | 41 | /** 42 | * @return If this endpoint's ip is 0.0.0.0 or 127.0.0.1 we get the local host and return that. 43 | */ 44 | static Endpoint boundEndpoint(Endpoint endpoint) { 45 | if (endpoint.ipv4() == null || endpoint.ipv4().equals(LOOPBACK.ipv4())) { 46 | return LOCAL 47 | .toBuilder() 48 | .serviceName(endpoint.serviceName()) 49 | .port(endpoint.portAsInt()) 50 | .build(); 51 | } else { 52 | return endpoint; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /core/src/test/java/zipkin2/finagle/WithTimeAt.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle; 6 | 7 | import com.twitter.util.Duration; 8 | import com.twitter.util.Time; 9 | import com.twitter.util.Time$; 10 | import com.twitter.util.TimeControl; 11 | import java.util.concurrent.atomic.AtomicReference; 12 | import org.junit.rules.TestRule; 13 | import org.junit.runner.Description; 14 | import org.junit.runners.model.Statement; 15 | import scala.runtime.AbstractFunction1; 16 | 17 | public final class WithTimeAt implements TestRule, TimeControl { 18 | private final static ThreadLocal holder = new ThreadLocal<>(); 19 | private final long epochMillis; 20 | 21 | public WithTimeAt(long epochMillis) { 22 | this.epochMillis = epochMillis; 23 | } 24 | 25 | @Override public void set(Time time) { 26 | holder.get().set(time); 27 | } 28 | 29 | @Override public void advance(Duration delta) { 30 | holder.get().advance(delta); 31 | } 32 | 33 | @Override 34 | public Statement apply(Statement base, Description description) { 35 | return new Statement() { 36 | AtomicReference throwable = new AtomicReference<>(); 37 | 38 | @Override public void evaluate() throws Throwable { 39 | Time$.MODULE$.withTimeAt(Time.fromMilliseconds(epochMillis), 40 | new AbstractFunction1() { 41 | @Override public Object apply(TimeControl tc) { 42 | holder.set(tc); 43 | try { 44 | base.evaluate(); 45 | } catch (Throwable t) { 46 | throwable.set(t); 47 | } 48 | return null; 49 | } 50 | }); 51 | Throwable t = throwable.get(); 52 | if (t != null) throw t; 53 | } 54 | }; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /http/src/test/java/zipkin/http/hostTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.http; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.GlobalFlag$; 9 | import java.util.Collection; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | import org.junit.After; 12 | import org.junit.Test; 13 | import scala.Function0; 14 | import scala.Option; 15 | import scala.runtime.AbstractFunction0; 16 | import scala.runtime.BoxedUnit; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static scala.collection.JavaConverters.asJavaCollection; 20 | 21 | public class hostTest { 22 | 23 | @After public void resetGlobalFlags() { 24 | for (Flag globalFlag: globalFlags()) globalFlag.reset(); 25 | } 26 | 27 | @Test public void defaultValue() { 28 | Option> flagOption = GlobalFlag$.MODULE$.get("zipkin.http.host"); 29 | assertThat(flagOption.get().apply()).isEqualTo("localhost:9411"); 30 | } 31 | 32 | @Test public void letOverridesDefault() { 33 | final String override = "foo:9411"; 34 | 35 | final AtomicBoolean ran = new AtomicBoolean(); 36 | Function0 fn0 = new AbstractFunction0() { 37 | @Override public BoxedUnit apply() { 38 | ran.set(true); // used to verify this block is executed. 39 | assertThat(host$.Flag.isDefined()).isTrue(); 40 | assertThat(host$.Flag.apply()).isEqualTo(override); 41 | return BoxedUnit.UNIT; 42 | } 43 | }; 44 | host$.Flag.let(override, fn0); 45 | 46 | assertThat(ran.get()).isTrue(); 47 | } 48 | 49 | @Test 50 | public void registersGlobal() { 51 | assertThat(globalFlags()) 52 | .extracting(f -> f.name()) 53 | .containsOnlyOnce("zipkin.http.host"); 54 | } 55 | 56 | Collection> globalFlags() { 57 | return asJavaCollection(GlobalFlag$.MODULE$.getAll(getClass().getClassLoader())); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /http/src/test/java/zipkin/http/pathTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.http; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.GlobalFlag$; 9 | import java.util.Collection; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | import org.junit.After; 12 | import org.junit.Test; 13 | import scala.Function0; 14 | import scala.Option; 15 | import scala.runtime.AbstractFunction0; 16 | import scala.runtime.BoxedUnit; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static scala.collection.JavaConverters.asJavaCollection; 20 | 21 | public class pathTest { 22 | 23 | @After public void resetGlobalFlags() { 24 | for (Flag globalFlag: globalFlags()) globalFlag.reset(); 25 | } 26 | 27 | @Test public void defaultValue() { 28 | Option> flagOption = GlobalFlag$.MODULE$.get("zipkin.http.path"); 29 | assertThat(flagOption.get().apply()).isEqualTo("/api/v2/spans"); 30 | } 31 | 32 | @Test public void letOverridesDefault() { 33 | final String override = "/custom/api/v2/spans"; 34 | 35 | final AtomicBoolean ran = new AtomicBoolean(); 36 | Function0 fn0 = new AbstractFunction0() { 37 | @Override public BoxedUnit apply() { 38 | ran.set(true); // used to verify this block is executed. 39 | assertThat(path$.Flag.isDefined()).isTrue(); 40 | assertThat(path$.Flag.apply()).isEqualTo(override); 41 | return BoxedUnit.UNIT; 42 | } 43 | }; 44 | path$.Flag.let(override, fn0); 45 | 46 | assertThat(ran.get()).isTrue(); 47 | } 48 | 49 | @Test 50 | public void registersGlobal() { 51 | assertThat(globalFlags()) 52 | .extracting(f -> f.name()) 53 | .containsOnlyOnce("zipkin.http.path"); 54 | } 55 | 56 | Collection> globalFlags() { 57 | return asJavaCollection(GlobalFlag$.MODULE$.getAll(getClass().getClassLoader())); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /http/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 4.0.0 10 | 11 | 12 | io.zipkin.finagle2 13 | zipkin-finagle-parent_2.12 14 | 23.0.3-SNAPSHOT 15 | 16 | 17 | zipkin-finagle-http_2.12 18 | 23.0.3-SNAPSHOT 19 | Zipkin Finagle: Http 20 | 21 | 22 | ${project.basedir}/.. 23 | 24 | 25 | 26 | 27 | ${project.groupId} 28 | zipkin-finagle_2.12 29 | ${project.version} 30 | 31 | 32 | 33 | com.twitter 34 | finagle-http_2.12 35 | ${finagle.version} 36 | 37 | 38 | 39 | ${project.groupId} 40 | zipkin-finagle_2.12 41 | ${project.version} 42 | test-jar 43 | test 44 | 45 | 46 | 47 | io.zipkin.zipkin2 48 | zipkin-junit5 49 | ${zipkin.version} 50 | test 51 | 52 | 53 | 54 | com.squareup.okhttp3 55 | okhttp-tls 56 | ${okhttp.version} 57 | test 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /kafka/src/test/java/zipkin/kafka/topicTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.kafka; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.GlobalFlag$; 9 | import java.util.Collection; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | import org.junit.After; 12 | import org.junit.Test; 13 | import scala.Function0; 14 | import scala.Option; 15 | import scala.runtime.AbstractFunction0; 16 | import scala.runtime.BoxedUnit; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static scala.collection.JavaConverters.asJavaCollection; 20 | 21 | public class topicTest { 22 | 23 | @After public void resetGlobalFlags() { 24 | for (Flag globalFlag: globalFlags()) globalFlag.reset(); 25 | } 26 | 27 | @Test public void defaultValue() { 28 | Option> flagOption = GlobalFlag$.MODULE$.get("zipkin.kafka.topic"); 29 | assertThat(flagOption.get().apply()).isEqualTo("zipkin"); 30 | } 31 | 32 | @Test public void letOverridesDefault() { 33 | final String override = "zipkin-dev"; 34 | 35 | final AtomicBoolean ran = new AtomicBoolean(); 36 | Function0 fn0 = new AbstractFunction0() { 37 | @Override public BoxedUnit apply() { 38 | ran.set(true); // used to verify this block is executed. 39 | assertThat(topic$.Flag.isDefined()).isTrue(); 40 | assertThat(topic$.Flag.apply()).isEqualTo(override); 41 | return BoxedUnit.UNIT; 42 | } 43 | }; 44 | topic$.Flag.let(override, fn0); 45 | 46 | assertThat(ran.get()).isTrue(); 47 | } 48 | 49 | @Test 50 | public void registersGlobal() { 51 | assertThat(globalFlags()) 52 | .extracting(f -> f.name()) 53 | .containsOnlyOnce("zipkin.kafka.topic"); 54 | } 55 | 56 | Collection> globalFlags() { 57 | return asJavaCollection(GlobalFlag$.MODULE$.getAllOrEmptyArray(getClass().getClassLoader())); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /scribe/src/test/java/zipkin/scribe/hostTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.scribe; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.GlobalFlag$; 9 | import java.util.Collection; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | import org.junit.After; 12 | import org.junit.Test; 13 | import scala.Function0; 14 | import scala.Option; 15 | import scala.runtime.AbstractFunction0; 16 | import scala.runtime.BoxedUnit; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static scala.collection.JavaConverters.asJavaCollection; 20 | 21 | public class hostTest { 22 | 23 | @After public void resetGlobalFlags() { 24 | for (Flag globalFlag: globalFlags()) globalFlag.reset(); 25 | } 26 | 27 | @Test public void defaultValue() { 28 | Option> flagOption = GlobalFlag$.MODULE$.get("zipkin.scribe.host"); 29 | assertThat(flagOption.get().apply()).isEqualTo("localhost:1463"); 30 | } 31 | 32 | @Test public void letOverridesDefault() { 33 | final String override = "localhost:9410"; 34 | 35 | final AtomicBoolean ran = new AtomicBoolean(); 36 | Function0 fn0 = new AbstractFunction0() { 37 | @Override public BoxedUnit apply() { 38 | ran.set(true); // used to verify this block is executed. 39 | assertThat(host$.Flag.isDefined()).isTrue(); 40 | assertThat(host$.Flag.apply()).isEqualTo(override); 41 | return BoxedUnit.UNIT; 42 | } 43 | }; 44 | host$.Flag.let(override, fn0); 45 | 46 | assertThat(ran.get()).isTrue(); 47 | } 48 | 49 | @Test 50 | public void registersGlobal() { 51 | assertThat(globalFlags()) 52 | .extracting(f -> f.name()) 53 | .containsOnlyOnce("zipkin.scribe.host"); 54 | } 55 | 56 | Collection> globalFlags() { 57 | return asJavaCollection(GlobalFlag$.MODULE$.getAll(getClass().getClassLoader())); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /http/src/test/java/zipkin/http/tlsEnabledTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.http; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.GlobalFlag$; 9 | import java.util.Collection; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | import org.junit.After; 12 | import org.junit.Test; 13 | import scala.Function0; 14 | import scala.Option; 15 | import scala.runtime.AbstractFunction0; 16 | import scala.runtime.BoxedUnit; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static scala.collection.JavaConverters.asJavaCollection; 20 | 21 | public class tlsEnabledTest { 22 | 23 | @After public void resetGlobalFlags() { 24 | for (Flag globalFlag: globalFlags()) globalFlag.reset(); 25 | } 26 | 27 | @Test public void defaultValue() { 28 | Option> flagOption = GlobalFlag$.MODULE$.get("zipkin.http.tlsEnabled"); 29 | assertThat(flagOption.get().apply()).isEqualTo(false); 30 | } 31 | 32 | @Test public void letOverridesDefault() { 33 | final boolean override = true; 34 | 35 | final AtomicBoolean ran = new AtomicBoolean(); 36 | Function0 fn0 = new AbstractFunction0() { 37 | @Override public BoxedUnit apply() { 38 | ran.set(true); // used to verify this block is executed. 39 | assertThat(tlsEnabled$.Flag.isDefined()).isTrue(); 40 | assertThat(tlsEnabled$.Flag.apply()).isEqualTo(override); 41 | return BoxedUnit.UNIT; 42 | } 43 | }; 44 | tlsEnabled$.Flag.let(override, fn0); 45 | 46 | assertThat(ran.get()).isTrue(); 47 | } 48 | 49 | @Test 50 | public void registersGlobal() { 51 | assertThat(globalFlags()) 52 | .extracting(f -> f.name()) 53 | .containsOnlyOnce("zipkin.http.tlsEnabled"); 54 | } 55 | 56 | Collection> globalFlags() { 57 | return asJavaCollection(GlobalFlag$.MODULE$.getAll(getClass().getClassLoader())); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /http/src/test/java/zipkin/http/hostHeaderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.http; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.GlobalFlag$; 9 | import java.util.Collection; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | import org.junit.After; 12 | import org.junit.Test; 13 | import scala.Function0; 14 | import scala.Option; 15 | import scala.runtime.AbstractFunction0; 16 | import scala.runtime.BoxedUnit; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static scala.collection.JavaConverters.asJavaCollection; 20 | 21 | public class hostHeaderTest { 22 | 23 | @After public void resetGlobalFlags() { 24 | for (Flag globalFlag: globalFlags()) globalFlag.reset(); 25 | } 26 | 27 | @Test public void defaultValue() { 28 | Option> flagOption = GlobalFlag$.MODULE$.get("zipkin.http.hostHeader"); 29 | assertThat(flagOption.get().apply()).isEqualTo("zipkin"); 30 | } 31 | 32 | @Test public void letOverridesDefault() { 33 | final String override = "amazon"; 34 | 35 | final AtomicBoolean ran = new AtomicBoolean(); 36 | Function0 fn0 = new AbstractFunction0() { 37 | @Override public BoxedUnit apply() { 38 | ran.set(true); // used to verify this block is executed. 39 | assertThat(hostHeader$.Flag.isDefined()).isTrue(); 40 | assertThat(hostHeader$.Flag.apply()).isEqualTo(override); 41 | return BoxedUnit.UNIT; 42 | } 43 | }; 44 | hostHeader$.Flag.let(override, fn0); 45 | 46 | assertThat(ran.get()).isTrue(); 47 | } 48 | 49 | @Test 50 | public void registersGlobal() { 51 | assertThat(globalFlags()) 52 | .extracting(f -> f.name()) 53 | .containsOnlyOnce("zipkin.http.hostHeader"); 54 | } 55 | 56 | Collection> globalFlags() { 57 | return asJavaCollection(GlobalFlag$.MODULE$.getAll(getClass().getClassLoader())); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /core/src/test/java/zipkin/initialSampleRateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.GlobalFlag$; 9 | import java.util.Collection; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | import org.junit.After; 12 | import org.junit.Test; 13 | import scala.Function0; 14 | import scala.Option; 15 | import scala.runtime.AbstractFunction0; 16 | import scala.runtime.BoxedUnit; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static scala.collection.JavaConverters.asJavaCollection; 20 | 21 | public class initialSampleRateTest { 22 | 23 | @After public void resetGlobalFlags() { 24 | for (Flag globalFlag: globalFlags()) globalFlag.reset(); 25 | } 26 | 27 | @Test public void defaultValue() { 28 | Option> flagOption = GlobalFlag$.MODULE$.get("zipkin.initialSampleRate"); 29 | assertThat(flagOption.get().apply()).isEqualTo(0.001f); 30 | } 31 | 32 | @Test public void letOverridesDefault() { 33 | final float override = 1.0f; 34 | 35 | final AtomicBoolean ran = new AtomicBoolean(); 36 | Function0 fn0 = new AbstractFunction0() { 37 | @Override public BoxedUnit apply() { 38 | ran.set(true); // used to verify this block is executed. 39 | assertThat(initialSampleRate$.Flag.isDefined()).isTrue(); 40 | assertThat(initialSampleRate$.Flag.apply()).isEqualTo(override); 41 | return BoxedUnit.UNIT; 42 | } 43 | }; 44 | initialSampleRate$.Flag.let(override, fn0); 45 | 46 | assertThat(ran.get()).isTrue(); 47 | } 48 | 49 | @Test 50 | public void registersGlobal() { 51 | assertThat(globalFlags()) 52 | .extracting(f -> f.name()) 53 | .containsOnlyOnce("zipkin.initialSampleRate"); 54 | } 55 | 56 | Collection> globalFlags() { 57 | return asJavaCollection(GlobalFlag$.MODULE$.getAll(getClass().getClassLoader())); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /core/src/test/java/zipkin/localServiceNameTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.GlobalFlag$; 9 | import java.util.Collection; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | import org.junit.After; 12 | import org.junit.Test; 13 | import scala.Function0; 14 | import scala.Option; 15 | import scala.runtime.AbstractFunction0; 16 | import scala.runtime.BoxedUnit; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static scala.collection.JavaConverters.asJavaCollection; 20 | 21 | public class localServiceNameTest { 22 | @After public void resetGlobalFlags() { 23 | for (Flag globalFlag: globalFlags()) globalFlag.reset(); 24 | } 25 | 26 | @Test public void defaultValue() { 27 | Option> flagOption = GlobalFlag$.MODULE$.get("zipkin.localServiceName"); 28 | assertThat(flagOption.get().apply()).isEqualTo("unknown"); 29 | } 30 | 31 | @Test public void letOverridesDefault() { 32 | final String override = "favstar"; 33 | 34 | final AtomicBoolean ran = new AtomicBoolean(); 35 | Function0 fn0 = new AbstractFunction0() { 36 | @Override public BoxedUnit apply() { 37 | ran.set(true); // used to verify this block is executed. 38 | assertThat(localServiceName$.Flag.isDefined()).isTrue(); 39 | assertThat(localServiceName$.Flag.apply()).isEqualTo(override); 40 | return BoxedUnit.UNIT; 41 | } 42 | }; 43 | localServiceName$.Flag.let(override, fn0); 44 | 45 | assertThat(ran.get()).isTrue(); 46 | } 47 | 48 | @Test 49 | public void registersGlobal() { 50 | assertThat(globalFlags()) 51 | .extracting(f -> f.name()) 52 | .containsOnlyOnce("zipkin.localServiceName"); 53 | } 54 | 55 | Collection> globalFlags() { 56 | return asJavaCollection(GlobalFlag$.MODULE$.getAll(getClass().getClassLoader())); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /http/src/test/java/zipkin/http/compressionEnabledTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.http; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.GlobalFlag$; 9 | import java.util.Collection; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | import org.junit.After; 12 | import org.junit.Test; 13 | import scala.Function0; 14 | import scala.Option; 15 | import scala.runtime.AbstractFunction0; 16 | import scala.runtime.BoxedUnit; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static scala.collection.JavaConverters.asJavaCollection; 20 | 21 | public class compressionEnabledTest { 22 | 23 | @After public void resetGlobalFlags() { 24 | for (Flag globalFlag: globalFlags()) globalFlag.reset(); 25 | } 26 | 27 | @Test public void defaultValue() { 28 | Option> flagOption = GlobalFlag$.MODULE$.get("zipkin.http.compressionEnabled"); 29 | assertThat(flagOption.get().apply()).isEqualTo(true); 30 | } 31 | 32 | @Test public void letOverridesDefault() { 33 | final boolean override = false; 34 | 35 | final AtomicBoolean ran = new AtomicBoolean(); 36 | Function0 fn0 = new AbstractFunction0() { 37 | @Override public BoxedUnit apply() { 38 | ran.set(true); // used to verify this block is executed. 39 | assertThat(compressionEnabled$.Flag.isDefined()).isTrue(); 40 | assertThat(compressionEnabled$.Flag.apply()).isEqualTo(override); 41 | return BoxedUnit.UNIT; 42 | } 43 | }; 44 | compressionEnabled$.Flag.let(override, fn0); 45 | 46 | assertThat(ran.get()).isTrue(); 47 | } 48 | 49 | @Test 50 | public void registersGlobal() { 51 | assertThat(globalFlags()) 52 | .extracting(f -> f.name()) 53 | .containsOnlyOnce("zipkin.http.compressionEnabled"); 54 | } 55 | 56 | Collection> globalFlags() { 57 | return asJavaCollection(GlobalFlag$.MODULE$.getAll(getClass().getClassLoader())); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /http/src/test/java/zipkin/http/tlsValidationEnabledTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.http; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.GlobalFlag$; 9 | import java.util.Collection; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | import org.junit.After; 12 | import org.junit.Test; 13 | import scala.Function0; 14 | import scala.Option; 15 | import scala.runtime.AbstractFunction0; 16 | import scala.runtime.BoxedUnit; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static scala.collection.JavaConverters.asJavaCollection; 20 | 21 | public class tlsValidationEnabledTest { 22 | 23 | @After public void resetGlobalFlags() { 24 | for (Flag globalFlag: globalFlags()) globalFlag.reset(); 25 | } 26 | 27 | @Test public void defaultValue() { 28 | Option> flagOption = GlobalFlag$.MODULE$.get("zipkin.http.tlsValidationEnabled"); 29 | assertThat(flagOption.get().apply()).isEqualTo(true); 30 | } 31 | 32 | @Test public void letOverridesDefault() { 33 | final boolean override = false; 34 | 35 | final AtomicBoolean ran = new AtomicBoolean(); 36 | Function0 fn0 = new AbstractFunction0() { 37 | @Override public BoxedUnit apply() { 38 | ran.set(true); // used to verify this block is executed. 39 | assertThat(tlsValidationEnabled$.Flag.isDefined()).isTrue(); 40 | assertThat(tlsValidationEnabled$.Flag.apply()).isEqualTo(override); 41 | return BoxedUnit.UNIT; 42 | } 43 | }; 44 | tlsValidationEnabled$.Flag.let(override, fn0); 45 | 46 | assertThat(ran.get()).isTrue(); 47 | } 48 | 49 | @Test 50 | public void registersGlobal() { 51 | assertThat(globalFlags()) 52 | .extracting(f -> f.name()) 53 | .containsOnlyOnce("zipkin.http.tlsValidationEnabled"); 54 | } 55 | 56 | Collection> globalFlags() { 57 | return asJavaCollection(GlobalFlag$.MODULE$.getAll(getClass().getClassLoader())); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.github/workflows/create_release.yml: -------------------------------------------------------------------------------- 1 | # yamllint --format github .github/workflows/create_release.yml 2 | --- 3 | name: create_release 4 | 5 | # We create a release version on a trigger tag, regardless of if the commit is documentation-only. 6 | # 7 | # See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet 8 | on: 9 | push: 10 | tags: 'release-[0-9]+.[0-9]+.[0-9]+**' # Ex. release-1.2.3 11 | 12 | jobs: 13 | create_release: 14 | # Hard-coding an LTS means maintenance, but only once each 2 years! 15 | runs-on: ubuntu-22.04 16 | steps: 17 | - name: Checkout Repository 18 | uses: actions/checkout@v2 19 | with: 20 | # Prevent use of implicit GitHub Actions read-only token GITHUB_TOKEN. We don't deploy on 21 | # the tag MAJOR.MINOR.PATCH event, but we still need to deploy the maven-release-plugin master commit. 22 | token: ${{ secrets.GH_TOKEN }} 23 | fetch-depth: 1 # only need the base commit as license check isn't run 24 | - name: Setup java 25 | uses: actions/setup-java@v4 26 | with: 27 | distribution: 'zulu' # zulu as it supports a wide version range 28 | java-version: 17 29 | - name: Cache local Maven repository 30 | uses: actions/cache@v2 31 | with: 32 | path: ~/.m2/repository 33 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 34 | restore-keys: ${{ runner.os }}-maven- 35 | - name: Create Release 36 | env: 37 | # GH_USER= 38 | GH_USER: ${{ secrets.GH_USER }} 39 | # GH_TOKEN= 40 | # - makes release commits and tags 41 | # - needs repo:status, public_repo 42 | # - referenced in .settings.xml 43 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 44 | run: | # GITHUB_REF will be refs/tags/release-MAJOR.MINOR.PATCH 45 | build-bin/git/login_git && 46 | build-bin/maven/maven_release $(echo ${GITHUB_REF} | cut -d/ -f 3) 47 | -------------------------------------------------------------------------------- /kafka/src/test/java/zipkin/kafka/bootstrapServersTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin.kafka; 6 | 7 | import com.twitter.app.Flag; 8 | import com.twitter.app.GlobalFlag$; 9 | import java.net.InetSocketAddress; 10 | import java.util.Collection; 11 | import java.util.List; 12 | import java.util.concurrent.atomic.AtomicBoolean; 13 | import org.junit.After; 14 | import org.junit.Test; 15 | import scala.Function0; 16 | import scala.Option; 17 | import scala.runtime.AbstractFunction0; 18 | import scala.runtime.BoxedUnit; 19 | 20 | import static java.util.Collections.singletonList; 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | import static scala.collection.JavaConverters.asJavaCollection; 23 | 24 | public class bootstrapServersTest { 25 | 26 | @After public void resetGlobalFlags() { 27 | for (Flag globalFlag: globalFlags()) globalFlag.reset(); 28 | } 29 | 30 | @Test public void defaultValue() { 31 | Option> flagOption = GlobalFlag$.MODULE$.get("zipkin.kafka.bootstrapServers"); 32 | assertThat(flagOption.get().apply()) 33 | .isEqualTo(singletonList(new InetSocketAddress("localhost", 9092))); 34 | } 35 | 36 | @Test public void letOverridesDefault() { 37 | final List override = singletonList(new InetSocketAddress("zipkin", 9092)); 38 | 39 | final AtomicBoolean ran = new AtomicBoolean(); 40 | Function0 fn0 = new AbstractFunction0() { 41 | @Override public BoxedUnit apply() { 42 | ran.set(true); // used to verify this block is executed. 43 | assertThat(bootstrapServers$.Flag.isDefined()).isTrue(); 44 | assertThat(bootstrapServers$.Flag.apply()).isEqualTo(override); 45 | return BoxedUnit.UNIT; 46 | } 47 | }; 48 | bootstrapServers$.Flag.let(override, fn0); 49 | 50 | assertThat(ran.get()).isTrue(); 51 | } 52 | 53 | @Test 54 | public void registersGlobal() { 55 | assertThat(globalFlags()) 56 | .extracting(f -> f.name()) 57 | .containsOnlyOnce("zipkin.kafka.bootstrapServers"); 58 | } 59 | 60 | Collection> globalFlags() { 61 | return asJavaCollection(GlobalFlag$.MODULE$.getAll(getClass().getClassLoader())); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # yamllint --format github .github/workflows/deploy.yml 2 | --- 3 | name: deploy 4 | 5 | # We deploy on master and release versions, regardless of if the commit is 6 | # documentation-only or not. 7 | # 8 | # See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet 9 | on: 10 | push: 11 | # Don't deploy tags as they conflict with [maven-release-plugin] prepare release MAJOR.MINOR.PATCH 12 | tags: '' 13 | branches: master 14 | 15 | jobs: 16 | deploy: 17 | # Hard-coding an LTS means maintenance, but only once each 2 years! 18 | runs-on: ubuntu-22.04 19 | steps: 20 | - name: Checkout Repository 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 1 # only needed to get the sha label 24 | - name: Setup java 25 | uses: actions/setup-java@v4 26 | with: 27 | distribution: 'zulu' # zulu as it supports a wide version range 28 | java-version: 17 29 | - name: Cache local Maven repository 30 | uses: actions/cache@v4 31 | with: 32 | path: ~/.m2/repository 33 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 34 | restore-keys: ${{ runner.os }}-maven- 35 | - name: Deploy 36 | env: 37 | GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} 38 | # GPG_PASSPHRASE= 39 | # - referenced in .settings.xml 40 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} 41 | # SONATYPE_USER= 42 | # - deploys snapshots and releases to Sonatype 43 | # - needs access to io.zipkin via https://issues.sonatype.org/browse/OSSRH-16669 44 | # - generate via https://oss.sonatype.org/#profile;User%20Token 45 | # - referenced in .settings.xml 46 | SONATYPE_USER: ${{ secrets.SONATYPE_USER }} 47 | # SONATYPE_PASSWORD= 48 | # - referenced in .settings.xml 49 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 50 | run: | # GITHUB_REF will be refs/heads/master or refs/tags/MAJOR.MINOR.PATCH 51 | build-bin/configure_deploy && 52 | build-bin/deploy $(echo ${GITHUB_REF} | cut -d/ -f 3) 53 | -------------------------------------------------------------------------------- /core/src/test/java/zipkin2/finagle/FakeSender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle; 6 | 7 | import java.util.List; 8 | import java.util.function.Consumer; 9 | import java.util.stream.Collectors; 10 | import zipkin2.Span; 11 | import zipkin2.codec.BytesDecoder; 12 | import zipkin2.codec.BytesEncoder; 13 | import zipkin2.codec.SpanBytesDecoder; 14 | import zipkin2.codec.SpanBytesEncoder; 15 | import zipkin2.reporter.BytesMessageEncoder; 16 | import zipkin2.reporter.BytesMessageSender; 17 | import zipkin2.reporter.Encoding; 18 | 19 | public final class FakeSender implements BytesMessageSender { 20 | static FakeSender create() { 21 | return new FakeSender( 22 | Encoding.THRIFT, 23 | Integer.MAX_VALUE, 24 | BytesMessageEncoder.forEncoding(Encoding.THRIFT), 25 | SpanBytesEncoder.THRIFT, 26 | SpanBytesDecoder.THRIFT, 27 | spans -> { 28 | } 29 | ); 30 | } 31 | 32 | final Encoding encoding; 33 | final int messageMaxBytes; 34 | final BytesMessageEncoder messageEncoder; 35 | final BytesEncoder encoder; 36 | final BytesDecoder decoder; 37 | final Consumer> onSpans; 38 | 39 | FakeSender( 40 | Encoding encoding, 41 | int messageMaxBytes, 42 | BytesMessageEncoder messageEncoder, 43 | BytesEncoder encoder, 44 | BytesDecoder decoder, 45 | Consumer> onSpans 46 | ) { 47 | this.encoding = encoding; 48 | this.messageMaxBytes = messageMaxBytes; 49 | this.messageEncoder = messageEncoder; 50 | this.encoder = encoder; 51 | this.decoder = decoder; 52 | this.onSpans = onSpans; 53 | } 54 | 55 | FakeSender onSpans(Consumer> onSpans) { 56 | return new FakeSender( 57 | encoding, 58 | messageMaxBytes, 59 | messageEncoder, 60 | encoder, 61 | decoder, 62 | onSpans 63 | ); 64 | } 65 | 66 | @Override public Encoding encoding() { 67 | return encoding; 68 | } 69 | 70 | @Override public int messageMaxBytes() { 71 | return messageMaxBytes; 72 | } 73 | 74 | @Override public int messageSizeInBytes(List encodedSpans) { 75 | return encoding.listSizeInBytes(encodedSpans); 76 | } 77 | 78 | @Override public int messageSizeInBytes(int encodedSizeInBytes) { 79 | return encoding.listSizeInBytes(encodedSizeInBytes); 80 | } 81 | 82 | /** close is typically called from a different thread */ 83 | volatile boolean closeCalled; 84 | 85 | @Override public void send(List encodedSpans) { 86 | if (closeCalled) throw new IllegalStateException("closed"); 87 | List decoded = encodedSpans.stream() 88 | .map(decoder::decodeOne) 89 | .collect(Collectors.toList()); 90 | onSpans.accept(decoded); 91 | } 92 | 93 | @Override public void close() { 94 | closeCalled = true; 95 | } 96 | 97 | @Override public String toString() { 98 | return "FakeSender"; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /core/src/main/java/zipkin2/finagle/FinagleSender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle; 6 | 7 | import com.twitter.finagle.Service; 8 | import com.twitter.util.Await; 9 | import com.twitter.util.Future; 10 | import com.twitter.util.Try; 11 | import java.io.IOException; 12 | import java.util.List; 13 | import scala.runtime.AbstractFunction1; 14 | import scala.runtime.BoxedUnit; 15 | import zipkin2.reporter.Callback; 16 | import zipkin2.reporter.Call; 17 | import zipkin2.reporter.BytesMessageSender; 18 | import zipkin2.reporter.Sender; 19 | 20 | /** Receives the Finagle generated traces and sends them off. */ 21 | public abstract class FinagleSender extends Sender { 22 | final Service client; 23 | 24 | /** close is typically called from a different thread */ 25 | volatile boolean closeCalled; 26 | 27 | protected FinagleSender(C config) { 28 | if (config == null) throw new NullPointerException("config == null"); 29 | this.client = newClient(config); 30 | if (client == null) throw new NullPointerException("client == null"); 31 | } 32 | 33 | protected abstract Service newClient(C config); 34 | 35 | @Override public Call sendSpans(List spans) { 36 | if (closeCalled) throw new IllegalStateException("closed"); 37 | 38 | return new SendSpans(spans); 39 | } 40 | 41 | protected abstract Req makeRequest(List spans) throws Exception; 42 | 43 | @Override public void close() { 44 | closeFuture(); 45 | } 46 | 47 | public Future closeFuture() { 48 | if (closeCalled) return Future.Done(); 49 | closeCalled = true; 50 | return client.close(); 51 | } 52 | 53 | static final class WrappedException extends RuntimeException { 54 | WrappedException(Exception e) { 55 | super(e); 56 | } 57 | } 58 | 59 | class SendSpans extends Call.Base { 60 | final List spans; 61 | 62 | SendSpans(List spans) { 63 | this.spans = spans; 64 | } 65 | 66 | @Override protected Void doExecute() throws IOException { 67 | try { 68 | Await.result(client.apply(makeRequest(spans))); 69 | } catch (RuntimeException |IOException e) { 70 | throw e; 71 | } catch (Exception e) { 72 | throw new WrappedException(e); 73 | } 74 | return null; 75 | } 76 | 77 | @Override protected void doEnqueue(Callback callback) { 78 | try { 79 | client.apply(makeRequest(spans)).respond(new AbstractFunction1, BoxedUnit>() { 80 | @Override public BoxedUnit apply(Try result) { 81 | if (result.isReturn()) { 82 | callback.onSuccess(null); 83 | } else { 84 | callback.onError(result.throwable()); 85 | } 86 | return BoxedUnit.UNIT; 87 | } 88 | }); 89 | } catch (Exception e) { 90 | callback.onError(e); 91 | } 92 | } 93 | 94 | @Override public Call clone() { 95 | return new SendSpans(spans); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /core/src/main/java/zipkin2/finagle/ReporterMetricsAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle; 6 | 7 | import com.twitter.finagle.stats.Counter; 8 | import com.twitter.finagle.stats.StatsReceiver; 9 | import com.twitter.finagle.stats.StatsReceivers; 10 | import com.twitter.util.Throwables; 11 | import java.util.concurrent.Callable; 12 | import java.util.concurrent.atomic.AtomicLong; 13 | import scala.collection.Seq; 14 | import zipkin2.reporter.ReporterMetrics; 15 | 16 | public final class ReporterMetricsAdapter implements ReporterMetrics { 17 | public static ReporterMetrics create(StatsReceiver stats) { 18 | return new ReporterMetricsAdapter(stats); 19 | } 20 | 21 | final Counter spans; 22 | final Counter spansDropped; 23 | final Counter spanBytes; 24 | final Counter messages; 25 | final Counter messageBytes; 26 | final StatsReceiver messagesDropped; 27 | final AtomicLong spanQueueSize; 28 | final AtomicLong spanQueueBytes; 29 | 30 | ReporterMetricsAdapter(StatsReceiver stats) { 31 | if (stats == null) throw new NullPointerException("stats == null"); 32 | this.spans = stats.counter0("spans"); 33 | this.spanBytes = stats.counter0("span_bytes"); 34 | this.spansDropped = stats.counter0("spans_dropped"); 35 | this.messages = stats.counter0("messages"); 36 | this.messageBytes = stats.counter0("message_bytes"); 37 | this.messagesDropped = stats.scope("messages_dropped"); 38 | this.spanQueueSize = gaugeFor(stats, "span_queue_size"); 39 | this.spanQueueBytes = gaugeFor(stats, "span_queue_bytes"); 40 | } 41 | 42 | @Override public void incrementMessages() { 43 | messages.incr(); 44 | } 45 | 46 | @Override public void incrementMessagesDropped(Throwable cause) { 47 | if (cause instanceof FinagleSender.WrappedException) cause = cause.getCause(); 48 | Seq causes = Throwables.mkString(cause); 49 | 50 | // Manually implement inits() as Traversable was replaced with Iterable in 2.13 51 | messagesDropped.counter().incr(); 52 | int causeCount = causes.size(); 53 | for (int i = 1; i <= causeCount; i++) { 54 | messagesDropped.counter(causes.slice(0, i).toSeq()).incr(); 55 | } 56 | } 57 | 58 | @Override public void incrementSpans(int i) { 59 | spans.incr(i); 60 | } 61 | 62 | @Override public void incrementSpanBytes(int i) { 63 | spanBytes.incr(i); 64 | } 65 | 66 | @Override public void incrementMessageBytes(int i) { 67 | messageBytes.incr(i); 68 | } 69 | 70 | @Override public void incrementSpansDropped(int i) { 71 | spansDropped.incr(i); 72 | } 73 | 74 | @Override public void updateQueuedSpans(int i) { 75 | spanQueueSize.set(i); 76 | } 77 | 78 | @Override public void updateQueuedBytes(int i) { 79 | spanQueueBytes.set(i); 80 | } 81 | 82 | static AtomicLong gaugeFor(StatsReceiver stats, String scope) { 83 | final AtomicLong result = new AtomicLong(); 84 | StatsReceivers.addGauge(stats, new Callable() { 85 | @Override public Float call() throws Exception { 86 | return result.floatValue(); 87 | } 88 | }, scope); 89 | return result; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /kafka/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 4.0.0 10 | 11 | 12 | io.zipkin.finagle2 13 | zipkin-finagle-parent_2.12 14 | 23.0.3-SNAPSHOT 15 | 16 | 17 | zipkin-finagle-kafka_2.12 18 | 23.0.3-SNAPSHOT 19 | Zipkin Finagle: Kafka 20 | 21 | 22 | ${project.basedir}/.. 23 | 24 | 3.6.0 25 | 26 | 27 | 28 | 29 | ${project.groupId} 30 | zipkin-finagle_2.12 31 | ${project.version} 32 | 33 | 34 | 35 | io.zipkin.reporter2 36 | zipkin-sender-kafka 37 | 38 | 39 | 40 | ${project.groupId} 41 | zipkin-finagle_2.12 42 | ${project.version} 43 | test-jar 44 | test 45 | 46 | 47 | 48 | org.apache.kafka 49 | kafka-clients 50 | ${kafka.version} 51 | 52 | 53 | 54 | org.apache.curator 55 | curator-test 56 | 5.6.0 57 | test 58 | 59 | 60 | 61 | org.apache.kafka 62 | kafka_2.12 63 | ${kafka.version} 64 | test 65 | 66 | 67 | org.slf4j 68 | * 69 | 70 | 71 | 72 | 73 | 74 | com.github.charithe 75 | kafka-junit 76 | 78 | 4.2.10 79 | test 80 | 81 | 82 | org.slf4j 83 | * 84 | 85 | 86 | org.apache.kafka 87 | * 88 | 89 | 90 | org.junit.jupiter 91 | * 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /http/src/main/java/zipkin2/finagle/http/HttpSender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle.http; 6 | 7 | import com.twitter.finagle.Http; 8 | import com.twitter.finagle.Http$; 9 | import com.twitter.finagle.Service; 10 | import com.twitter.finagle.ServiceFactory; 11 | import com.twitter.finagle.Stack; 12 | import com.twitter.finagle.http.Method; 13 | import com.twitter.finagle.http.Request; 14 | import com.twitter.finagle.http.Response; 15 | import com.twitter.finagle.tracing.NullTracer; 16 | import java.io.ByteArrayOutputStream; 17 | import java.io.IOException; 18 | import java.util.List; 19 | import java.util.zip.GZIPOutputStream; 20 | 21 | import zipkin2.finagle.FinagleSender; 22 | import zipkin2.reporter.BytesMessageEncoder; 23 | import zipkin2.reporter.Encoding; 24 | 25 | /** Receives the Finagle generated traces and sends them off to Zipkin via Http. */ 26 | final class HttpSender extends FinagleSender { 27 | static final Method POST = Method.apply("POST"); 28 | final HttpZipkinTracer.Config config; 29 | 30 | HttpSender(HttpZipkinTracer.Config config) { 31 | super(config); 32 | this.config = config; 33 | } 34 | 35 | @Override public Encoding encoding() { 36 | return Encoding.JSON; 37 | } 38 | 39 | @Override public int messageMaxBytes() { 40 | return 5 * 1024 * 1024; // TODO: enforce 41 | } 42 | 43 | @Override public int messageSizeInBytes(int encodedSizeInBytes) { 44 | return encoding().listSizeInBytes(encodedSizeInBytes); 45 | } 46 | 47 | @Override public int messageSizeInBytes(List encodedSpans) { 48 | return encoding().listSizeInBytes(encodedSpans); 49 | } 50 | 51 | @Override protected Service newClient(HttpZipkinTracer.Config config) { 52 | // use special knowledge to yank out the trace filter since we are literally sending to zipkin 53 | // https://groups.google.com/forum/#!topic/finaglers/LqVVVOr2EMM 54 | Stack> stack = 55 | Http$.MODULE$.client().stack().remove(new Stack.Role("TraceInitializerFilter")); 56 | Http.Client client = new Http.Client(stack, Http.Client$.MODULE$.apply$default$2()) 57 | .withTracer(new NullTracer()); 58 | if (config.tlsEnabled()) { 59 | if (config.tlsValidationEnabled()) { 60 | client = client.withTls(config.hostHeader()); 61 | } else { 62 | client = client.withTlsWithoutValidation(); 63 | } 64 | } 65 | return client.newService(config.host(), "zipkin-http"); 66 | } 67 | 68 | @Override protected Request makeRequest(List spans) throws IOException { 69 | byte[] json = BytesMessageEncoder.JSON.encode(spans); 70 | Request request = Request.apply(POST, config.path()); 71 | request.headerMap().add("Host", config.hostHeader()); 72 | request.headerMap().add("Content-Type", "application/json"); 73 | // Eventhough finagle compression flag exists, it only works for servers! 74 | if (config.compressionEnabled()) { 75 | request.headerMap().add("Content-Encoding", "gzip"); 76 | ByteArrayOutputStream gzipped = new ByteArrayOutputStream(); 77 | try (GZIPOutputStream compressor = new GZIPOutputStream(gzipped)) { 78 | compressor.write(json); 79 | } 80 | json = gzipped.toByteArray(); 81 | } 82 | request.headerMap().add("Content-Length", String.valueOf(json.length)); 83 | request.write(json); 84 | return request; 85 | } 86 | 87 | @Override public final String toString() { 88 | return "HttpSender{" + config.host() + "}"; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /scribe/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 4.0.0 10 | 11 | 12 | io.zipkin.finagle2 13 | zipkin-finagle-parent_2.12 14 | 23.0.3-SNAPSHOT 15 | 16 | 17 | zipkin-finagle-scribe_2.12 18 | 23.0.3-SNAPSHOT 19 | Zipkin Finagle: Scribe 20 | 21 | 22 | ${project.basedir}/.. 23 | 24 | 25 | 26 | 27 | ${project.groupId} 28 | zipkin-finagle_2.12 29 | ${project.version} 30 | 31 | 32 | 33 | com.twitter 34 | finagle-thrift_2.12 35 | ${finagle.version} 36 | 37 | 38 | 39 | io.zipkin.reporter2 40 | zipkin-sender-libthrift 41 | 42 | 43 | 44 | ${project.groupId} 45 | zipkin-finagle_2.12 46 | ${project.version} 47 | test-jar 48 | test 49 | 50 | 51 | 52 | io.zipkin.zipkin2 53 | zipkin-collector-scribe 54 | ${zipkin.version} 55 | test 56 | 57 | 58 | 59 | 60 | 61 | 62 | maven-shade-plugin 63 | ${maven-shade-plugin.version} 64 | 65 | 66 | package 67 | 68 | shade 69 | 70 | 71 | true 72 | false 73 | 74 | 75 | zipkin2.reporter.libthrift 76 | zipkin.finagle2.scribe.internal 77 | 78 | 79 | 80 | 81 | io.zipkin.reporter2:zipkin-sender-libthrift 82 | 83 | 84 | 85 | 86 | 87 | io.zipkin.reporter2:zipkin-sender-libthrift 88 | 89 | zipkin2/reporter/libthrift/InternalScribeCodec.class 90 | 91 | 92 | * 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /scribe/src/test/java/zipkin2/finagle/scribe/ITScribeZipkinTracer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle.scribe; 6 | 7 | import com.twitter.finagle.tracing.Annotation.ClientRecv$; 8 | import com.twitter.finagle.tracing.Annotation.ClientSend$; 9 | import com.twitter.finagle.tracing.Annotation.Rpc; 10 | import com.twitter.finagle.tracing.Annotation.ServiceName; 11 | import com.twitter.finagle.tracing.Record; 12 | import com.twitter.util.Duration; 13 | import com.twitter.util.Time; 14 | import java.util.List; 15 | import org.junit.jupiter.api.AfterEach; 16 | import org.junit.jupiter.api.BeforeEach; 17 | import org.junit.jupiter.api.Test; 18 | import scala.Option; 19 | import zipkin2.Span; 20 | import zipkin2.codec.SpanBytesEncoder; 21 | import zipkin2.collector.scribe.ScribeCollector; 22 | import zipkin2.finagle.ITZipkinTracer; 23 | import zipkin2.finagle.ZipkinTracer; 24 | import zipkin2.finagle.scribe.ScribeZipkinTracer.Config; 25 | import zipkin2.storage.InMemoryStorage; 26 | 27 | import static java.util.concurrent.TimeUnit.SECONDS; 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | import static org.assertj.core.api.Assertions.entry; 30 | import static org.awaitility.Awaitility.await; 31 | import static scala.collection.JavaConverters.mapAsJavaMap; 32 | import static zipkin2.finagle.FinagleTestObjects.TODAY; 33 | import static zipkin2.finagle.FinagleTestObjects.root; 34 | import static zipkin2.finagle.FinagleTestObjects.seq; 35 | 36 | public class ITScribeZipkinTracer extends ITZipkinTracer { 37 | final Option none = Option.empty(); // avoid having to force generics 38 | 39 | InMemoryStorage storage = InMemoryStorage.newBuilder().build(); 40 | ScribeCollector scribe; 41 | 42 | @BeforeEach public void start() { 43 | scribe = ScribeCollector.newBuilder().storage(storage).build(); 44 | scribe.start(); 45 | } 46 | 47 | @AfterEach public void close() { 48 | scribe.close(); 49 | } 50 | 51 | Config config = Config.builder().initialSampleRate(1.0f).host("127.0.0.1:9410").build(); 52 | 53 | @Override protected ZipkinTracer newTracer(String localServiceName) { 54 | config = config.toBuilder().localServiceName(localServiceName).build(); 55 | return new ScribeZipkinTracer(config, stats); 56 | } 57 | 58 | @Override protected List> getTraces() { 59 | return storage.getTraces(); 60 | } 61 | 62 | /** Scribe can only use thrift */ 63 | @Override protected SpanBytesEncoder encoder() { 64 | return SpanBytesEncoder.THRIFT; 65 | } 66 | 67 | @Test public void whenScribeIsDown() throws Exception { 68 | scribe.close(); 69 | 70 | tracer.record(new Record(root, Time.fromMilliseconds(TODAY), new ServiceName("web"), none)); 71 | tracer.record(new Record(root, Time.fromMilliseconds(TODAY), new Rpc("get"), none)); 72 | tracer.record(new Record(root, Time.fromMilliseconds(TODAY), ClientSend$.MODULE$, none)); 73 | tracer.record(new Record(root, Time.fromMilliseconds(TODAY + 1), ClientRecv$.MODULE$, none)); 74 | 75 | // wait for the Scribe request attempt to go through 76 | await().atMost(5, SECONDS).untilAsserted(() -> assertThat(mapAsJavaMap(stats.counters())) 77 | .contains( 78 | entry(seq("spans"), 1L), 79 | entry(seq("span_bytes"), 165L), 80 | entry(seq("spans_dropped"), 1L), 81 | entry(seq("messages"), 1L), 82 | entry(seq("message_bytes"), 170L), 83 | entry(seq("messages_dropped"), 1L), 84 | entry(seq("messages_dropped", "com.twitter.finagle.Failure"), 1L), 85 | entry( 86 | seq( 87 | "messages_dropped", 88 | "com.twitter.finagle.Failure", 89 | "com.twitter.finagle.ConnectionFailedException"), 90 | 1L), 91 | entry( 92 | seq( 93 | "messages_dropped", 94 | "com.twitter.finagle.Failure", 95 | "com.twitter.finagle.ConnectionFailedException", 96 | "io.netty.channel.AbstractChannel$AnnotatedConnectException"), 97 | 1L))); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /scribe/src/main/java/zipkin2/finagle/scribe/ScribeSender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle.scribe; 6 | 7 | import com.twitter.finagle.Name; 8 | import com.twitter.finagle.Service; 9 | import com.twitter.finagle.Status; 10 | import com.twitter.finagle.Thrift; 11 | import com.twitter.finagle.thrift.ThriftClientRequest; 12 | import com.twitter.finagle.tracing.NullTracer; 13 | import com.twitter.util.Duration; 14 | import com.twitter.util.Function; 15 | import com.twitter.util.Future; 16 | import com.twitter.util.Time; 17 | import java.util.List; 18 | import org.apache.thrift.TException; 19 | import org.apache.thrift.protocol.TBinaryProtocol; 20 | import org.apache.thrift.transport.TMemoryBuffer; 21 | import org.apache.thrift.transport.TMemoryInputTransport; 22 | import scala.runtime.BoxedUnit; 23 | import zipkin2.reporter.Encoding; 24 | import zipkin2.finagle.FinagleSender; 25 | import zipkin2.finagle.scribe.ScribeZipkinTracer.Config; 26 | import zipkin2.reporter.libthrift.InternalScribeCodec; 27 | 28 | /** Receives the Finagle generated traces and sends them off to Zipkin via Scribe. */ 29 | final class ScribeSender extends FinagleSender { 30 | static final byte[] category = new byte[] {'z', 'i', 'p', 'k', 'i', 'n'}; 31 | 32 | final Name host; 33 | 34 | ScribeSender(Config config) { 35 | super(config); 36 | this.host = config.host(); 37 | } 38 | 39 | @Override public int messageSizeInBytes(int encodedSizeInBytes) { 40 | return InternalScribeCodec.messageSizeInBytes(category, encodedSizeInBytes); 41 | } 42 | 43 | @Override public int messageSizeInBytes(List spans) { 44 | return InternalScribeCodec.messageSizeInBytes(category, spans); 45 | } 46 | 47 | @Override public Encoding encoding() { 48 | return Encoding.THRIFT; 49 | } 50 | 51 | @Override public int messageMaxBytes() { 52 | return 1 * 1024 * 1024; // previous default 53 | } 54 | 55 | @Override 56 | protected Service newClient(Config config) { 57 | return new ScribeClient(config); 58 | } 59 | 60 | /** This doesn't use thrift sequence ids because scrooge doesn't */ 61 | @Override protected ThriftClientRequest makeRequest(List spans) throws TException { 62 | int encodedSize = InternalScribeCodec.messageSizeInBytes(category, spans); 63 | TMemoryBuffer mem = new TMemoryBuffer(encodedSize); 64 | TBinaryProtocol prot = new TBinaryProtocol(mem); 65 | InternalScribeCodec.writeLogRequest(category, spans, 0, prot); 66 | return new ThriftClientRequest(mem.getArray(), false); 67 | } 68 | 69 | @Override 70 | public final String toString() { 71 | return "ScribeSender(" + host + ")"; 72 | } 73 | 74 | static final class ScribeClient extends Service { 75 | 76 | final Service delegate; 77 | 78 | ScribeClient(Config config) { 79 | delegate = Thrift.client() 80 | .withTracer(new NullTracer()) 81 | .newService(config.host(), "zipkin-scribe"); 82 | } 83 | 84 | @Override public Future apply(ThriftClientRequest request) { 85 | return delegate.apply(request).flatMap(READ_FUTURE); 86 | } 87 | 88 | @Override public Future close(Duration after) { 89 | return delegate.close(after); 90 | } 91 | 92 | @Override public Future close(Time deadline) { 93 | return delegate.close(deadline); 94 | } 95 | 96 | @Override public Status status() { 97 | return delegate.status(); 98 | } 99 | 100 | @Override public String toString() { 101 | return delegate.toString(); 102 | } 103 | } 104 | 105 | static final Function> READ_FUTURE = new Function>() { 106 | @Override public Future apply(byte[] responseBytes) { 107 | TBinaryProtocol iprot = new TBinaryProtocol(new TMemoryInputTransport(responseBytes)); 108 | try { 109 | if (InternalScribeCodec.readLogResponse(0, iprot)) { 110 | return Future.Void(); 111 | } else { 112 | return Future.exception(new IllegalStateException("try later")); 113 | } 114 | } catch (Exception e) { 115 | return Future.exception(e); 116 | } 117 | } 118 | }; 119 | } 120 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # OpenZipkin Release Process 2 | 3 | This repo uses semantic versions. Please keep this in mind when choosing version numbers. 4 | 5 | 1. **Alert others you are releasing** 6 | 7 | There should be no commits made to master while the release is in progress (about 10 minutes). Before you start 8 | a release, alert others on [gitter](https://gitter.im/openzipkin/zipkin) so that they don't accidentally merge 9 | anything. If they do, and the build fails because of that, you'll have to recreate the release tag described below. 10 | 11 | 1. **Push a git tag** 12 | 13 | The trigger format is `release-MAJOR.MINOR.PATCH`, ex `git tag release-1.18.1 && git push origin release-1.18.1`. 14 | 15 | 1. **Wait for CI** 16 | 17 | The `release-MAJOR.MINOR.PATCH` tag triggers [`build-bin/maven/maven_release`](build-bin/maven/maven_release), 18 | which creates commits, `MAJOR.MINOR.PATCH` tag, and increments the version (maven-release-plugin). 19 | 20 | The `MAJOR.MINOR.PATCH` tag triggers [`build-bin/deploy`](build-bin/deploy), which does the following: 21 | * Publishes jars to https://oss.sonatype.org/content/repositories/releases [`build-bin/maven/maven_deploy`](build-bin/maven/maven_deploy) 22 | * Later, the same jars synchronize to Maven Central 23 | 24 | Notes: 25 | * https://search.maven.org/ index will take longer than direct links like https://repo1.maven.org/maven2/io/zipkin 26 | 27 | ## Credentials 28 | 29 | The release process uses various credentials. If you notice something failing due to unauthorized, 30 | look at the notes in [.github/workflows/deploy.yml] and check the [org secrets](https://github.com/organizations/openzipkin/settings/secrets/actions). 31 | 32 | ### Troubleshooting invalid credentials 33 | 34 | If you receive a '401 unauthorized' failure from OSSRH, it is likely 35 | `SONATYPE_USER` or `SONATYPE_PASSWORD` entries are invalid, or possibly the 36 | user associated with them does not have rights to upload. 37 | 38 | The least destructive test is to try to publish a snapshot manually. By passing 39 | the values CI would use, you can kick off a snapshot from your laptop. This 40 | is a good way to validate that your unencrypted credentials are authorized. 41 | 42 | Here's an example of a snapshot deploy with specified credentials. 43 | ```bash 44 | $ export GPG_TTY=$(tty) && GPG_PASSPHRASE=whackamole SONATYPE_USER=adrianmole SONATYPE_PASSWORD=ed6f20bde9123bbb2312b221 build-bin/build-bin/maven/maven_deploy 45 | ``` 46 | 47 | ## First release of the year 48 | 49 | The license plugin verifies license headers of files include a copyright notice indicating the years a file was affected. 50 | This information is taken from git history. There's a once-a-year problem with files that include version numbers (pom.xml). 51 | When a release tag is made, it increments version numbers, then commits them to git. On the first release of the year, 52 | further commands will fail due to the version increments invalidating the copyright statement. The way to sort this out is 53 | the following: 54 | 55 | Before you do the first release of the year, move the SNAPSHOT version back and forth from whatever the current is. 56 | In-between, re-apply the licenses. 57 | ```bash 58 | $ ./mvnw versions:set -DnewVersion=1.3.3-SNAPSHOT -DgenerateBackupPoms=false 59 | $ ./mvnw com.mycila:license-maven-plugin:format 60 | $ ./mvnw versions:set -DnewVersion=1.3.2-SNAPSHOT -DgenerateBackupPoms=false 61 | $ git commit -am"Adjusts copyright headers for this year" 62 | ``` 63 | 64 | ## Manually releasing 65 | 66 | If for some reason, you lost access to CI or otherwise cannot get automation to work, bear in mind 67 | this is a normal maven project, and can be released accordingly. 68 | 69 | *Note:* If [Sonatype is down](https://status.sonatype.com/), the below will not work. 70 | 71 | ```bash 72 | # First, set variable according to your personal credentials. These would normally be assigned as 73 | # org secrets: https://github.com/organizations/openzipkin/settings/secrets/actions 74 | export GPG_TTY=$(tty) 75 | export GPG_PASSPHRASE=your_gpg_passphrase 76 | export SONATYPE_USER=your_sonatype_account 77 | export SONATYPE_PASSWORD=your_sonatype_password 78 | release_version=xx-version-to-release-xx 79 | 80 | # now from latest master, create the release. This creates and pushes the MAJOR.MINOR.PATCH tag 81 | ./build-bin/maven/maven_release release-${release_version} 82 | 83 | # once this works, deploy the release 84 | git checkout ${release_version} 85 | ./build-bin/deploy 86 | 87 | # Finally, clean up 88 | ./mvnw release:clean 89 | git checkout master 90 | git reset HEAD --hard 91 | ``` 92 | -------------------------------------------------------------------------------- /scribe/src/main/java/zipkin2/finagle/scribe/ScribeZipkinTracer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle.scribe; 6 | 7 | import com.google.auto.service.AutoService; 8 | import com.google.auto.value.AutoValue; 9 | import com.twitter.finagle.Name; 10 | import com.twitter.finagle.Resolver$; 11 | import com.twitter.finagle.stats.DefaultStatsReceiver$; 12 | import com.twitter.finagle.stats.NullStatsReceiver; 13 | import com.twitter.finagle.stats.StatsReceiver; 14 | import com.twitter.finagle.tracing.Annotation; 15 | import com.twitter.finagle.tracing.Tracer; 16 | import com.twitter.util.Future; 17 | import com.twitter.util.Time; 18 | import scala.runtime.AbstractFunction1; 19 | import scala.runtime.BoxedUnit; 20 | import zipkin.localServiceName$; 21 | import zipkin2.finagle.ZipkinTracer; 22 | 23 | @AutoService(Tracer.class) 24 | public final class ScribeZipkinTracer extends ZipkinTracer { 25 | private final ScribeSender scribe; 26 | 27 | /** 28 | * Default constructor for the service loader 29 | */ 30 | public ScribeZipkinTracer() { 31 | this(Config.builder().build(), DefaultStatsReceiver$.MODULE$.get().scope("zipkin.scribe")); 32 | } 33 | 34 | ScribeZipkinTracer(Config config, StatsReceiver stats) { 35 | this(new ScribeSender(config), config, stats); 36 | } 37 | 38 | private ScribeZipkinTracer(ScribeSender scribe, Config config, StatsReceiver stats) { 39 | super(scribe, config, stats); 40 | this.scribe = scribe; 41 | } 42 | 43 | /** 44 | * Create a new instance with default configuration. 45 | * 46 | * @param host The network location of the Zipkin scribe service 47 | * @param stats Gets notified when spans are accepted or dropped. If you are not interested in 48 | * these events you can use {@linkplain NullStatsReceiver} 49 | */ 50 | public static ScribeZipkinTracer create(String host, StatsReceiver stats) { 51 | return new ScribeZipkinTracer(Config.builder().host(host).build(), stats); 52 | } 53 | 54 | /** 55 | * @param config includes flush interval and scribe properties 56 | * @param stats Gets notified when spans are accepted or dropped. If you are not interested in 57 | * these events you can use {@linkplain NullStatsReceiver} 58 | */ 59 | public static ScribeZipkinTracer create(Config config, StatsReceiver stats) { 60 | return new ScribeZipkinTracer(config, stats); 61 | } 62 | 63 | @Override public Future close(final Time deadline) { 64 | return scribe.closeFuture().flatMap(new AbstractFunction1>() { 65 | @Override public Future apply(BoxedUnit v1) { 66 | return ScribeZipkinTracer.super.close(deadline); 67 | } 68 | }); 69 | } 70 | 71 | @AutoValue 72 | public static abstract class Config implements ZipkinTracer.Config { 73 | /** Creates a builder with the correct defaults derived from global flags */ 74 | public static Builder builder() { 75 | return new AutoValue_ScribeZipkinTracer_Config.Builder() 76 | .host(zipkin.scribe.host$.Flag.apply()) 77 | .localServiceName(localServiceName$.Flag.apply()) 78 | .initialSampleRate(zipkin.initialSampleRate$.Flag.apply()); 79 | } 80 | 81 | abstract public Builder toBuilder(); 82 | 83 | abstract Name host(); 84 | 85 | @AutoValue.Builder 86 | public abstract static class Builder { 87 | /** 88 | * Lower-case label of the remote node in the service graph, such as "favstar". Avoid names 89 | * with variables or unique identifiers embedded. 90 | * 91 | *

When unset, the service name is derived from {@link Annotation.ServiceName} which is 92 | * often incorrectly set to the remote service name. 93 | * 94 | *

This is a primary label for trace lookup and aggregation, so it should be intuitive and 95 | * consistent. Many use a name from service discovery. 96 | */ 97 | public abstract Builder localServiceName(String localServiceName); 98 | 99 | /** The network location of the Scribe service. Defaults to "localhost:1463" */ 100 | public abstract Builder host(Name host); 101 | 102 | /** Shortcut for a {@link #host(Name)} encoded as a String */ 103 | public final Builder host(String host) { 104 | return host(Resolver$.MODULE$.eval(host)); 105 | } 106 | 107 | /** @see ZipkinTracer.Config#initialSampleRate() */ 108 | public abstract Builder initialSampleRate(float initialSampleRate); 109 | 110 | public abstract Config build(); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /core/src/main/java/zipkin2/finagle/MutableSpan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle; 6 | 7 | import com.twitter.finagle.service.TimeoutFilter; 8 | import com.twitter.finagle.tracing.TraceId; 9 | import com.twitter.util.Time; 10 | import java.net.InetSocketAddress; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import zipkin2.Endpoint; 14 | import zipkin2.v1.V1Annotation; 15 | import zipkin2.v1.V1BinaryAnnotation; 16 | import zipkin2.v1.V1Span; 17 | 18 | import static com.twitter.finagle.thrift.thrift.Constants.CLIENT_RECV; 19 | import static com.twitter.finagle.thrift.thrift.Constants.SERVER_SEND; 20 | 21 | final class MutableSpan { 22 | private final V1Span.Builder span; 23 | private final Time started; 24 | private final List annotations = new ArrayList<>(); 25 | private final List binaryAnnotations = new ArrayList<>(); 26 | private boolean isComplete; 27 | private String service = "unknown"; 28 | private Endpoint endpoint = Endpoints.UNKNOWN; 29 | 30 | MutableSpan(TraceId traceId, Time started) { 31 | this.span = V1Span.newBuilder(); 32 | span.id(traceId.spanId().toLong()); 33 | if (traceId._parentId().isDefined()) { 34 | span.parentId(traceId.parentId().toLong()); 35 | } 36 | span.traceId(traceId.traceId().toLong()); 37 | if (traceId.traceIdHigh().isDefined()) { 38 | span.traceIdHigh(traceId.traceIdHigh().get().toLong()); 39 | } 40 | if (traceId.flags().isDebug()) { 41 | span.debug(true); 42 | } 43 | span.name("unknown"); 44 | this.started = started; 45 | } 46 | 47 | Time started() { // not synchronized as the field is immutable and final 48 | return started; 49 | } 50 | 51 | synchronized MutableSpan setName(String n) { 52 | span.name(n); 53 | return this; 54 | } 55 | 56 | synchronized MutableSpan setServiceName(String n) { 57 | service = n; 58 | return this; 59 | } 60 | 61 | synchronized MutableSpan addAnnotation(Time timestamp, String value) { 62 | if (annotations.isEmpty()) { 63 | span.timestamp(timestamp.inMicroseconds()); 64 | } 65 | if (!isComplete && 66 | (value.equals(CLIENT_RECV) || 67 | value.equals(SERVER_SEND) || 68 | value.equals(TimeoutFilter.TimeoutAnnotation()))) { 69 | if (!annotations.isEmpty()) { 70 | span.duration(timestamp.inMicroseconds() - annotations.get(0).timestamp()); 71 | } 72 | isComplete = true; 73 | } 74 | 75 | annotations.add(V1Annotation.create(timestamp.inMicroseconds(), value, endpoint)); 76 | return this; 77 | } 78 | 79 | synchronized MutableSpan addBinaryAnnotation(String key, String value) { 80 | binaryAnnotations.add(V1BinaryAnnotation.createString(key, value, endpoint)); 81 | return this; 82 | } 83 | 84 | /** 85 | * Sets the endpoint in the span for any future annotations. Also sets the endpoint in any 86 | * previous annotations that lack one. 87 | */ 88 | synchronized MutableSpan setEndpoint(Endpoint endpoint) { 89 | for (int i = 0; i < annotations.size(); i++) { 90 | V1Annotation a = annotations.get(i); 91 | if (a.endpoint().equals(Endpoints.UNKNOWN)) { 92 | annotations.set(i, V1Annotation.create(a.timestamp(), a.value(), endpoint)); 93 | } 94 | } 95 | this.endpoint = endpoint; 96 | return this; 97 | } 98 | 99 | synchronized MutableSpan setAddress(String key, InetSocketAddress ia) { 100 | binaryAnnotations.add(V1BinaryAnnotation.createAddress(key, Endpoints.fromSocketAddress(ia))); 101 | return this; 102 | } 103 | 104 | synchronized String getService() { 105 | return service; 106 | } 107 | 108 | synchronized boolean isComplete() { 109 | return isComplete; 110 | } 111 | 112 | synchronized V1Span toSpan() { 113 | // fill in the host/service data for all the annotations 114 | for (V1Annotation a : annotations) { 115 | Endpoint ep = Endpoints.boundEndpoint(a.endpoint()); 116 | span.addAnnotation(a.timestamp(), a.value(), ep.toBuilder().serviceName(service).build()); 117 | } 118 | for (V1BinaryAnnotation ann : binaryAnnotations) { 119 | Endpoint ep = Endpoints.boundEndpoint(ann.endpoint()); 120 | if (ann.stringValue() == null) { // service annotation 121 | span.addBinaryAnnotation(ann.key(), ep); 122 | } else { 123 | span.addBinaryAnnotation( 124 | ann.key(), ann.stringValue(), ep.toBuilder().serviceName(service).build()); 125 | } 126 | } 127 | return span.build(); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /kafka/src/test/java/zipkin2/finagle/kafka/ITKafkaZipkinTracer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle.kafka; 6 | 7 | import com.github.charithe.kafka.EphemeralKafkaBroker; 8 | import com.github.charithe.kafka.KafkaHelper; 9 | import com.github.charithe.kafka.KafkaJunitExtension; 10 | import com.github.charithe.kafka.KafkaJunitExtensionConfig; 11 | import com.github.charithe.kafka.StartupMode; 12 | import com.twitter.finagle.tracing.Annotation.ClientRecv$; 13 | import com.twitter.finagle.tracing.Annotation.ClientSend$; 14 | import com.twitter.finagle.tracing.Annotation.Rpc; 15 | import com.twitter.finagle.tracing.Annotation.ServiceName; 16 | import com.twitter.finagle.tracing.Record; 17 | import com.twitter.util.Duration; 18 | import com.twitter.util.Time; 19 | import java.util.LinkedHashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | import org.apache.kafka.clients.consumer.ConsumerRecord; 23 | import org.apache.kafka.clients.consumer.KafkaConsumer; 24 | import org.apache.kafka.clients.producer.ProducerConfig; 25 | import org.junit.jupiter.api.BeforeEach; 26 | import org.junit.jupiter.api.extension.ExtendWith; 27 | import org.junit.jupiter.api.Test; 28 | import scala.Option; 29 | import zipkin2.Span; 30 | import zipkin2.codec.SpanBytesDecoder; 31 | import zipkin2.finagle.FinagleTestObjects; 32 | import zipkin2.finagle.ITZipkinTracer; 33 | import zipkin2.finagle.ZipkinTracer; 34 | import zipkin2.reporter.kafka.KafkaSender; 35 | 36 | import static java.util.concurrent.TimeUnit.SECONDS; 37 | import static java.util.stream.Collectors.toList; 38 | import static org.assertj.core.api.Assertions.assertThat; 39 | import static org.assertj.core.api.Assertions.entry; 40 | import static org.awaitility.Awaitility.await; 41 | import static scala.collection.JavaConverters.mapAsJavaMap; 42 | 43 | @ExtendWith(KafkaJunitExtension.class) 44 | @KafkaJunitExtensionConfig(startupMode = StartupMode.WAIT_FOR_STARTUP) 45 | public class ITKafkaZipkinTracer extends ITZipkinTracer { 46 | final Option none = Option.empty(); // avoid having to force generics 47 | EphemeralKafkaBroker broker; 48 | KafkaHelper kafkaHelper; 49 | KafkaZipkinTracer.Config config; 50 | 51 | @BeforeEach public void setUp(KafkaHelper kafkaHelper, EphemeralKafkaBroker broker) { 52 | this.broker = broker; 53 | this.kafkaHelper = kafkaHelper; 54 | } 55 | 56 | @BeforeEach public void createTracer() { 57 | config = KafkaZipkinTracer.Config.builder() 58 | .bootstrapServers(broker.getBrokerList().get()) 59 | .initialSampleRate(1.0f).build(); 60 | super.createTracer(); 61 | } 62 | 63 | @Override protected ZipkinTracer newTracer(String localServiceName) { 64 | config = config.toBuilder().localServiceName(localServiceName).build(); 65 | return new KafkaZipkinTracer(config, stats); 66 | } 67 | 68 | @Override protected List> getTraces() throws Exception { 69 | KafkaConsumer consumer = kafkaHelper.createByteConsumer(); 70 | return kafkaHelper.consume(config.topic(), consumer, 1).get() 71 | .stream() 72 | .map(ConsumerRecord::value) 73 | .map(SpanBytesDecoder.JSON_V2::decodeList) 74 | .collect(toList()); 75 | } 76 | 77 | @Test public void whenKafkaIsDown() throws Exception { 78 | broker.stop(); 79 | 80 | // Make a new tracer that fails faster than 60 seconds 81 | tracer.close(); 82 | stats.clear(); 83 | Map overrides = new LinkedHashMap<>(); 84 | overrides.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, "100"); 85 | tracer = new KafkaZipkinTracer(KafkaSender.newBuilder() 86 | .bootstrapServers(config.bootstrapServers()) 87 | .topic(config.topic()) 88 | .overrides(overrides) 89 | .build(), config, stats); 90 | 91 | tracer.record( 92 | new Record(FinagleTestObjects.root, Time.fromMilliseconds(FinagleTestObjects.TODAY), 93 | new ServiceName("web"), none)); 94 | tracer.record( 95 | new Record(FinagleTestObjects.root, Time.fromMilliseconds(FinagleTestObjects.TODAY), 96 | new Rpc("get"), none)); 97 | tracer.record( 98 | new Record(FinagleTestObjects.root, Time.fromMilliseconds(FinagleTestObjects.TODAY), 99 | ClientSend$.MODULE$, none)); 100 | tracer.record(new Record( 101 | FinagleTestObjects.root, Time.fromMilliseconds(FinagleTestObjects.TODAY + 1), 102 | ClientRecv$.MODULE$, none)); 103 | 104 | // wait for the HTTP request attempt to go through 105 | await().atMost(5, SECONDS).untilAsserted(() -> assertThat(mapAsJavaMap(stats.counters())) 106 | .containsOnly( 107 | entry(FinagleTestObjects.seq("spans"), 1L), 108 | entry(FinagleTestObjects.seq("span_bytes"), 185L), 109 | entry(FinagleTestObjects.seq("spans_dropped"), 1L), 110 | entry(FinagleTestObjects.seq("messages"), 1L), 111 | entry(FinagleTestObjects.seq("message_bytes"), 187L), 112 | entry(FinagleTestObjects.seq("messages_dropped"), 1L), 113 | entry(FinagleTestObjects.seq("messages_dropped", 114 | "org.apache.kafka.common.errors.TimeoutException"), 1L) 115 | )); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /kafka/src/main/java/zipkin2/finagle/kafka/KafkaZipkinTracer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle.kafka; 6 | 7 | import com.google.auto.service.AutoService; 8 | import com.google.auto.value.AutoValue; 9 | import com.twitter.finagle.stats.DefaultStatsReceiver$; 10 | import com.twitter.finagle.stats.NullStatsReceiver; 11 | import com.twitter.finagle.stats.StatsReceiver; 12 | import com.twitter.finagle.tracing.Annotation; 13 | import com.twitter.util.AbstractClosable; 14 | import com.twitter.util.Closables; 15 | import com.twitter.util.Future; 16 | import com.twitter.util.Time; 17 | import java.net.InetSocketAddress; 18 | import java.util.Iterator; 19 | import scala.collection.mutable.StringBuilder; 20 | import scala.runtime.BoxedUnit; 21 | import zipkin.localServiceName$; 22 | import zipkin2.reporter.Encoding; 23 | import zipkin2.finagle.ZipkinTracer; 24 | import zipkin2.reporter.kafka.KafkaSender; 25 | 26 | @AutoService(com.twitter.finagle.tracing.Tracer.class) 27 | public final class KafkaZipkinTracer extends ZipkinTracer { 28 | 29 | private final KafkaSender kafka; 30 | 31 | /** 32 | * Default constructor for the service loader 33 | */ 34 | public KafkaZipkinTracer() { 35 | this(Config.builder().build(), DefaultStatsReceiver$.MODULE$.get().scope("zipkin.kafka")); 36 | } 37 | 38 | KafkaZipkinTracer(Config config, StatsReceiver stats) { 39 | this(KafkaSender.newBuilder() 40 | .encoding(Encoding.JSON) 41 | .bootstrapServers(config.bootstrapServers()) 42 | .topic(config.topic()) 43 | .build(), config, stats); 44 | } 45 | 46 | KafkaZipkinTracer(KafkaSender kafka, Config config, StatsReceiver stats) { 47 | super(kafka, config, stats); 48 | this.kafka = kafka; 49 | } 50 | 51 | /** 52 | * Create a new instance with default configuration. 53 | * 54 | * @param bootstrapServers A list of host/port pairs to use for establishing the initial 55 | * connection to the Kafka cluster. Like: host1:port1,host2:port2,... Does not to be all the 56 | * servers part of Kafka cluster. 57 | * @param stats Gets notified when spans are accepted or dropped. If you are not interested in 58 | * these events you can use {@linkplain NullStatsReceiver} 59 | */ 60 | public static KafkaZipkinTracer create(String bootstrapServers, StatsReceiver stats) { 61 | return new KafkaZipkinTracer(Config.builder().bootstrapServers(bootstrapServers).build(), 62 | stats); 63 | } 64 | 65 | /** 66 | * @param config includes flush interval and kafka properties 67 | * @param stats Gets notified when spans are accepted or dropped. If you are not interested in 68 | * these events you can use {@linkplain NullStatsReceiver} 69 | */ 70 | public static KafkaZipkinTracer create(Config config, StatsReceiver stats) { 71 | return new KafkaZipkinTracer(config, stats); 72 | } 73 | 74 | @Override public Future close(Time deadline) { 75 | return Closables.sequence( 76 | new AbstractClosable() { 77 | @Override public Future close(Time deadline) { 78 | kafka.close(); // TODO: blocking 79 | return Future.Done(); 80 | } 81 | }, 82 | new AbstractClosable() { 83 | @Override public Future close(Time deadline) { 84 | return KafkaZipkinTracer.super.close(deadline); 85 | } 86 | }).close(deadline); 87 | } 88 | 89 | @AutoValue 90 | public static abstract class Config implements ZipkinTracer.Config { 91 | /** Creates a builder with the correct defaults derived from global flags */ 92 | public static Builder builder() { 93 | return new AutoValue_KafkaZipkinTracer_Config.Builder() 94 | .bootstrapServers(bootstrapServersFromFlag()) 95 | .topic(zipkin.kafka.topic$.Flag.apply()) 96 | .localServiceName(localServiceName$.Flag.apply()) 97 | .initialSampleRate(zipkin.initialSampleRate$.Flag.apply()); 98 | } 99 | 100 | static String bootstrapServersFromFlag() { 101 | StringBuilder result = new StringBuilder(); 102 | for (Iterator i = zipkin.kafka.bootstrapServers$.Flag.apply().iterator(); 103 | i.hasNext(); ) { 104 | InetSocketAddress next = i.next(); 105 | result.append(next.getHostName()).append(':').append(next.getPort()); 106 | if (i.hasNext()) result.append(','); 107 | } 108 | return result.toString(); 109 | } 110 | 111 | abstract public Builder toBuilder(); 112 | 113 | abstract String bootstrapServers(); 114 | 115 | abstract String topic(); 116 | 117 | @AutoValue.Builder 118 | public interface Builder { 119 | /** 120 | * Lower-case label of the remote node in the service graph, such as "favstar". Avoid names 121 | * with variables or unique identifiers embedded. 122 | * 123 | *

When unset, the service name is derived from {@link Annotation.ServiceName} which is 124 | * often incorrectly set to the remote service name. 125 | * 126 | *

This is a primary label for trace lookup and aggregation, so it should be intuitive and 127 | * consistent. Many use a name from service discovery. 128 | */ 129 | Builder localServiceName(String localServiceName); 130 | 131 | /** 132 | * Initial set of kafka servers to connect to, rest of cluster will be discovered (comma 133 | * separated). Default localhost:9092 134 | */ 135 | Builder bootstrapServers(String bootstrapServers); 136 | 137 | /** Sets kafka-topic for zipkin to report to. Default topic zipkin. */ 138 | Builder topic(String topic); 139 | 140 | /** @see ZipkinTracer.Config#initialSampleRate() */ 141 | Builder initialSampleRate(float initialSampleRate); 142 | 143 | Config build(); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /http/src/main/java/zipkin2/finagle/http/HttpZipkinTracer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle.http; 6 | 7 | import com.google.auto.service.AutoService; 8 | import com.google.auto.value.AutoValue; 9 | import com.twitter.finagle.Name; 10 | import com.twitter.finagle.Resolver$; 11 | import com.twitter.finagle.stats.DefaultStatsReceiver$; 12 | import com.twitter.finagle.stats.NullStatsReceiver; 13 | import com.twitter.finagle.stats.StatsReceiver; 14 | import com.twitter.finagle.tracing.Annotation; 15 | import com.twitter.finagle.tracing.Tracer; 16 | import com.twitter.util.Future; 17 | import com.twitter.util.Time; 18 | import scala.runtime.AbstractFunction1; 19 | import scala.runtime.BoxedUnit; 20 | import zipkin.localServiceName$; 21 | import zipkin2.finagle.ZipkinTracer; 22 | 23 | @AutoService(Tracer.class) 24 | public final class HttpZipkinTracer extends ZipkinTracer { 25 | private final HttpSender http; 26 | 27 | /** 28 | * Default constructor for the service loader 29 | */ 30 | public HttpZipkinTracer() { 31 | this(Config.builder().build(), DefaultStatsReceiver$.MODULE$.get().scope("zipkin.http")); 32 | } 33 | 34 | HttpZipkinTracer(Config config, StatsReceiver stats) { 35 | this(new HttpSender(config), config, stats); 36 | } 37 | 38 | private HttpZipkinTracer(HttpSender http, Config config, StatsReceiver stats) { 39 | super(http, config, stats); 40 | this.http = http; 41 | } 42 | 43 | /** 44 | * Create a new instance with default configuration. 45 | * 46 | * @param host The network location of the Zipkin http service 47 | * @param stats Gets notified when spans are accepted or dropped. If you are not interested in 48 | * these events you can use {@linkplain NullStatsReceiver} 49 | */ 50 | public static HttpZipkinTracer create(String host, StatsReceiver stats) { 51 | return new HttpZipkinTracer(Config.builder().host(host).build(), stats); 52 | } 53 | 54 | /** 55 | * @param config includes flush interval and http properties 56 | * @param stats Gets notified when spans are accepted or dropped. If you are not interested in 57 | * these events you can use {@linkplain NullStatsReceiver} 58 | */ 59 | public static HttpZipkinTracer create(Config config, StatsReceiver stats) { 60 | return new HttpZipkinTracer(config, stats); 61 | } 62 | 63 | @Override public Future close(final Time deadline) { 64 | return http.closeFuture().flatMap(new AbstractFunction1>() { 65 | @Override public Future apply(BoxedUnit v1) { 66 | return HttpZipkinTracer.super.close(deadline); 67 | } 68 | }); 69 | } 70 | 71 | @AutoValue 72 | public static abstract class Config implements ZipkinTracer.Config { 73 | /** Creates a builder with the correct defaults derived from global flags */ 74 | public static Builder builder() { 75 | return new AutoValue_HttpZipkinTracer_Config.Builder() 76 | .hostHeader(zipkin.http.hostHeader$.Flag.apply()) 77 | .host(zipkin.http.host$.Flag.apply()) 78 | .path(zipkin.http.path$.Flag.apply()) 79 | .compressionEnabled(zipkin.http.compressionEnabled$.Flag.apply()) 80 | .tlsEnabled(zipkin.http.tlsEnabled$.Flag.apply()) 81 | .tlsValidationEnabled(zipkin.http.tlsValidationEnabled$.Flag.apply()) 82 | .localServiceName(localServiceName$.Flag.apply()) 83 | .initialSampleRate(zipkin.initialSampleRate$.Flag.apply()); 84 | } 85 | 86 | abstract public Builder toBuilder(); 87 | 88 | abstract Name host(); 89 | 90 | abstract String hostHeader(); 91 | 92 | abstract boolean tlsEnabled(); 93 | 94 | abstract boolean tlsValidationEnabled(); 95 | 96 | abstract boolean compressionEnabled(); 97 | 98 | abstract String path(); 99 | 100 | @AutoValue.Builder 101 | public abstract static class Builder { 102 | /** 103 | * Lower-case label of the remote node in the service graph, such as "favstar". Avoid names 104 | * with variables or unique identifiers embedded. 105 | * 106 | *

When unset, the service name is derived from {@link Annotation.ServiceName} which is 107 | * often incorrectly set to the remote service name. 108 | * 109 | *

This is a primary label for trace lookup and aggregation, so it should be intuitive and 110 | * consistent. Many use a name from service discovery. 111 | */ 112 | public abstract Builder localServiceName(String localServiceName); 113 | 114 | /** The network location of the Zipkin http service. Defaults to "localhost:9411" */ 115 | public abstract Builder host(Name host); 116 | 117 | /** The path to the Zipkin endpoint relative to the host. Defaults to "/api/v2/spans" */ 118 | public abstract Builder path(String path); 119 | 120 | /** Shortcut for a {@link #host(Name)} encoded as a String */ 121 | public final Builder host(String host) { 122 | return host(Resolver$.MODULE$.eval(host)); 123 | } 124 | 125 | /** The Host header used when sending spans to Zipkin. Defaults to "zipkin" */ 126 | public abstract Builder hostHeader(String host); 127 | 128 | /** True implies that spans will be gzipped before transport. Defaults to true. */ 129 | public abstract Builder compressionEnabled(boolean compressSpans); 130 | 131 | /** @see ZipkinTracer.Config#initialSampleRate() */ 132 | public abstract Builder initialSampleRate(float initialSampleRate); 133 | 134 | /** Whether or not the Zipkin host uses TLS. Defaults to false */ 135 | public abstract Builder tlsEnabled(boolean tlsEnabled); 136 | 137 | /** Whether or not to enable TLS validation for the TLS-enabled Zipkin host */ 138 | public abstract Builder tlsValidationEnabled(boolean tlsValidationEnabled); 139 | 140 | public abstract Config build(); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /core/src/test/java/zipkin2/finagle/ITZipkinTracer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle; 6 | 7 | import com.twitter.finagle.stats.InMemoryStatsReceiver; 8 | import com.twitter.finagle.tracing.Annotation.ClientRecv$; 9 | import com.twitter.finagle.tracing.Annotation.ClientSend$; 10 | import com.twitter.finagle.tracing.Annotation.Rpc; 11 | import com.twitter.finagle.tracing.Annotation.ServerRecv$; 12 | import com.twitter.finagle.tracing.Annotation.ServerSend$; 13 | import com.twitter.finagle.tracing.Annotation.ServiceName; 14 | import com.twitter.finagle.tracing.Record; 15 | import java.util.List; 16 | import org.assertj.core.api.InstanceOfAssertFactories; 17 | import org.junit.jupiter.api.AfterEach; 18 | import org.junit.jupiter.api.BeforeEach; 19 | import org.junit.jupiter.api.Test; 20 | import zipkin2.DependencyLink; 21 | import zipkin2.Endpoint; 22 | import zipkin2.Span; 23 | import zipkin2.codec.SpanBytesEncoder; 24 | import zipkin2.internal.DependencyLinker; 25 | 26 | import static com.twitter.util.Time.fromMilliseconds; 27 | import static java.util.Arrays.asList; 28 | import static java.util.concurrent.TimeUnit.SECONDS; 29 | import static org.assertj.core.api.Assertions.assertThat; 30 | import static org.assertj.core.api.Assertions.entry; 31 | import static org.awaitility.Awaitility.await; 32 | import static scala.Option.empty; 33 | import static scala.collection.JavaConverters.mapAsJavaMap; 34 | import static zipkin2.finagle.FinagleTestObjects.TODAY; 35 | import static zipkin2.finagle.FinagleTestObjects.child; 36 | import static zipkin2.finagle.FinagleTestObjects.root; 37 | 38 | public abstract class ITZipkinTracer { 39 | protected InMemoryStatsReceiver stats = new InMemoryStatsReceiver(); 40 | protected ZipkinTracer tracer; 41 | 42 | @AfterEach public void closeTracer() { 43 | tracer.close(); 44 | stats.clear(); 45 | } 46 | 47 | @BeforeEach public void createTracer() { 48 | tracer = newTracer("unknown"); 49 | } 50 | 51 | protected abstract ZipkinTracer newTracer(String localServiceName); 52 | 53 | protected abstract List> getTraces() throws Exception; 54 | 55 | /** v2 json is default, though proto3 and thrift are possible */ 56 | protected SpanBytesEncoder encoder() { 57 | return SpanBytesEncoder.JSON_V2; 58 | } 59 | 60 | int messageSizeInBytes(List encodedSpans) { 61 | return encoder().encoding().listSizeInBytes(encodedSpans); 62 | } 63 | 64 | @Test public void multipleSpansGoIntoSameMessage() { 65 | tracer.record(new Record(root, fromMilliseconds(TODAY), new ServiceName("web"), empty())); 66 | tracer.record(new Record(root, fromMilliseconds(TODAY), new Rpc("get"), empty())); 67 | tracer.record(new Record(root, fromMilliseconds(TODAY), ServerRecv$.MODULE$, empty())); 68 | tracer.record(new Record(root, fromMilliseconds(TODAY + 1), ServerSend$.MODULE$, empty())); 69 | 70 | tracer.record(new Record(child, fromMilliseconds(TODAY), new ServiceName("web"), empty())); 71 | tracer.record(new Record(child, fromMilliseconds(TODAY), new Rpc("get"), empty())); 72 | tracer.record(new Record(child, fromMilliseconds(TODAY), ClientSend$.MODULE$, empty())); 73 | tracer.record(new Record(child, fromMilliseconds(TODAY + 1), ClientRecv$.MODULE$, empty())); 74 | 75 | Endpoint web = Endpoint.newBuilder().serviceName("web").ip("127.0.0.1").build(); 76 | Span server = Span.newBuilder() 77 | .traceId(root.traceId().toString()) 78 | .id(root.spanId().toLong()) 79 | .name("get") 80 | .timestamp(TODAY * 1000) 81 | .duration(1000L) 82 | .kind(Span.Kind.SERVER) 83 | .localEndpoint(web) 84 | .build(); 85 | 86 | Span client = server.toBuilder() 87 | .kind(Span.Kind.CLIENT) 88 | .parentId(child.parentId().toLong()) 89 | .id(child.spanId().toLong()).build(); 90 | 91 | long expectedSpanBytes = encoder().sizeInBytes(server) + encoder().sizeInBytes(client); 92 | long expectedMessageSize = 93 | messageSizeInBytes(asList(encoder().encode(server), encoder().encode(client))); 94 | 95 | // the AsyncReporter thread has a default interval of 1s, but reporting delay can take longer 96 | await().atMost(5, SECONDS).untilAsserted(() -> { 97 | assertThat(getTraces()).containsExactly(asList(server, client)); 98 | 99 | assertThat(mapAsJavaMap(stats.counters())).containsExactly( 100 | entry(FinagleTestObjects.seq("span_bytes"), expectedSpanBytes), 101 | entry(FinagleTestObjects.seq("spans"), 2L), 102 | entry(FinagleTestObjects.seq("spans_dropped"), 0L), 103 | entry(FinagleTestObjects.seq("message_bytes"), expectedMessageSize), 104 | entry(FinagleTestObjects.seq("messages"), 1L) 105 | ); 106 | }); 107 | } 108 | 109 | @Test public void configOverridesLocalServiceName_client() { 110 | tracer.close(); 111 | tracer = newTracer("web"); 112 | 113 | tracer.record(new Record(root, fromMilliseconds(TODAY), new ServiceName("web"), empty())); 114 | tracer.record(new Record(root, fromMilliseconds(TODAY), new Rpc("get"), empty())); 115 | tracer.record(new Record(root, fromMilliseconds(TODAY), ServerRecv$.MODULE$, empty())); 116 | tracer.record(new Record(root, fromMilliseconds(TODAY + 1), ServerSend$.MODULE$, empty())); 117 | 118 | // Here we simulate someone setting the client ServiceName to the remote host 119 | tracer.record(new Record(child, fromMilliseconds(TODAY), new ServiceName("app"), empty())); 120 | tracer.record(new Record(child, fromMilliseconds(TODAY), new Rpc("get"), empty())); 121 | tracer.record(new Record(child, fromMilliseconds(TODAY), ClientSend$.MODULE$, empty())); 122 | tracer.record(new Record(child, fromMilliseconds(TODAY + 1), ClientRecv$.MODULE$, empty())); 123 | 124 | // the AsyncReporter thread has a default interval of 1s, but reporting delay can take longer 125 | await().atMost(5, SECONDS).untilAsserted(() -> assertThat(getTraces()) 126 | .isNotEmpty().first() 127 | .extracting(t -> new DependencyLinker().putTrace(t).link()) 128 | .asInstanceOf(InstanceOfAssertFactories.list(DependencyLink.class)) 129 | .containsExactly( 130 | DependencyLink.newBuilder().parent("web").child("app").callCount(1).build() 131 | ) 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zipkin-finagle 2 | 3 | [![Gitter chat](http://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/openzipkin/zipkin) 4 | [![Build Status](https://github.com/openzipkin/zipkin-finagle/workflows/test/badge.svg)](https://github.com/openzipkin/zipkin-finagle/actions?query=workflow%3Atest) 5 | [![Maven Central](https://img.shields.io/maven-central/v/io.zipkin.finagle2/zipkin-finagle_2.12.svg)](https://search.maven.org/search?q=g:io.zipkin.finagle2%20AND%20a:zipkin-finagle_2.12) 6 | 7 | Integration between Finagle tracing to Zipkin transports including http, kafka and scribe. 8 | 9 | ## Quick start 10 | Finagle will use a tracer that it detects in the classpath. For example, depending on `io.zipkin.finagle2:zipkin-finagle-http_2.12` will send to Zipkin over Http. 11 | 12 | You can look at the [example](https://github.com/openzipkin/zipkin-finagle-example) for this in use. 13 | 14 | ## Choosing a tracer explicitly 15 | You can also explicitly choose a tracer, which is more reliable, at the tradeoff of more configuration. 16 | 17 | Here an example initializing an http client in scala and java 18 | ```scala 19 | client = Http.client 20 | .withTracer(new HttpZipkinTracer()) 21 | .withLabel("frontend") // becomes the zipkin service name 22 | .newService("remotehost:8080"); 23 | ``` 24 | 25 | ```java 26 | client = Http$.MODULE$.client() 27 | .withTracer(new HttpZipkinTracer()) 28 | .withLabel("frontend") // becomes the zipkin service name 29 | .newService("remotehost:8080"); 30 | ``` 31 | 32 | ### Note on service names 33 | 34 | Sometimes labels used in Finagle do not match the intent of the Zipkin service name. If your traces 35 | or dependency graph looks incorrect, set the flag `zipkin.localServiceName`. This will ignore any of 36 | the labels set by servers. In clients, the `ServiceName` recorded by Finagle will be moved to 37 | `span.remoteEndpoint.serviceName` in Zipkin under the assumption that it was intended to name the 38 | remote host. 39 | 40 | ## Configuration 41 | 42 | ### Global Flags 43 | zipkin-finagle configuration is via [global flags](https://github.com/twitter/util/blob/master/util-app/src/main/scala/com/twitter/app/Flag.scala). 44 | 45 | Global flags can either be set by system property, or commandline argument (ex if using TwitterServer). 46 | 47 | Ex the following are equivalent ways to trace every request: 48 | ```bash 49 | $ java -Dzipkin.localServiceName=favstar -Dzipkin.initialSampleRate=1.0 ... 50 | $ java -cp my-twitter-server.jar -zipkin.localServiceName=favstar -zipkin.initialSampleRate=1.0 51 | ``` 52 | 53 | Here are the flags that apply to all transports: 54 | 55 | Flag | Default | Description 56 | --- | --- | --- 57 | zipkin.localServiceName | unknown | Overrides any ServiceName annotation set by Finagle. Controls Span.localEndpoint.serviceName in Zipkin. 58 | zipkin.initialSampleRate | 0.001 (0.1%) | Percentage of traces to sample (report to zipkin) in the range [0.0 - 1.0] 59 | 60 | ### Http Configuration 61 | Adding `io.zipkin.finagle2:zipkin-finagle-http_2.12` to your classpath will configure Finagle 62 | to report trace data to a Zipkin server via HTTP. 63 | 64 | Here are the flags that apply to Http: 65 | 66 | Flag | Default | Description 67 | --- | --- | --- 68 | zipkin.http.host | localhost:9411 | The network location of the Zipkin http service. See http://twitter.github.io/finagle/guide/Names.html 69 | zipkin.http.hostHeader | zipkin | The Host header used when sending spans to Zipkin 70 | zipkin.http.path | /api/v2/spans | The path to the spans endpoint 71 | zipkin.http.compressionEnabled | true | True implies that spans will be gzipped before transport 72 | zipkin.http.tlsEnabled | false | Whether or not the Zipkin host uses TLS 73 | zipkin.http.tlsValidationEnabled | true | Whether or not to enable TLS validation for the Zipkin host when TLS is enabled 74 | 75 | Ex. Here's how to configure the Zipkin server with a system property: 76 | ```bash 77 | $ java -Dzipkin.http.host=192.168.99.100:9411 ... 78 | ``` 79 | 80 | ### Kafka Configuration 81 | Adding `io.zipkin.finagle2:zipkin-finagle-kafka_2.12` to your classpath will configure Finagle 82 | to report trace data to a Kafka topic. The minimum Kafka server version is 0.10 83 | 84 | Here are the flags that apply to Kafka: 85 | 86 | Flag | Default | Description 87 | --- | --- | --- 88 | zipkin.kafka.bootstrapServers | localhost:9092 | Initial set of kafka servers to connect to, rest of cluster will be discovered (comma separated) 89 | zipkin.kafka.topic | zipkin | Kafka topic zipkin traces will be sent to 90 | 91 | Ex. Here's how to configure the Kafka server with a system property: 92 | ```bash 93 | $ java -Dzipkin.kafka.bootstrapServers=192.168.99.100 ... 94 | ``` 95 | 96 | ### Scribe Configuration 97 | Adding `io.zipkin.finagle2:zipkin-finagle-scribe_2.12` to your classpath will configure Finagle 98 | to report trace data to the zipkin category of Scribe. 99 | 100 | Here are the flags that apply to Scribe: 101 | 102 | Flag | Default | Description 103 | --- | --- | --- 104 | zipkin.scribe.host | localhost:1463 | The network location of the Scribe service. See http://twitter.github.io/finagle/guide/Names.html 105 | 106 | Ex. Here's how to configure the Scribe host with a system property. In this case pointing directly to a zipkin server: 107 | ```bash 108 | $ java -Dzipkin.scribe.host=zipkinhost:9410 ... 109 | ``` 110 | 111 | ### Programmatic 112 | 113 | You can also configure zipkin tracers programmatically. Here's an example: 114 | 115 | ```java 116 | HttpZipkinTracer.Config config = HttpZipkinTracer.Config.builder() 117 | // The frontend makes a sampling decision (via Trace.letTracerAndId) and propagates it downstream. 118 | // This property says sample 100% of traces. 119 | .initialSampleRate(1.0f) 120 | // All servers need to point to the same zipkin transport 121 | .host("127.0.0.1:9411").build(); 122 | 123 | Tracer tracer = HttpZipkinTracer.create(config, 124 | // print stats about zipkin to the console 125 | new JavaLoggerStatsReceiver(Logger.getAnonymousLogger())); 126 | ``` 127 | 128 | ### Custom 129 | 130 | You may need to use alternative reporters like [zipkin-aws](https://github.com/openzipkin/zipkin-aws/), 131 | control configuration not available in flags, or change metrics reporting configuration. To do that, 132 | use `ZipkinReporter.newBuilder()` and supply the arguments you need. 133 | 134 | ```java 135 | // Setup a sender without using Finagle flags like so: 136 | Properties overrides = new Properties(); 137 | overrides.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 5000); 138 | sender = KafkaSender.newBuilder() 139 | .bootstrapServers("host1:9092,host2:9092") 140 | .overrides(overrides) 141 | .encoding(Encoding.PROTO3) 142 | .build(); 143 | 144 | zipkinStats = stats.scope("zipkin"); 145 | spanReporter = AsyncReporter.builder(sender) 146 | .metrics(new ReporterMetricsAdapter(zipkinStats)) // routes reporter metrics to finagle stats 147 | .build() 148 | 149 | // Now, use it here, but don't forget to close the sender! 150 | tracer = ZipkinTracer.newBuilder(spanReporter).stats(zipkinStats).build(); 151 | ``` 152 | 153 | ## Metrics 154 | 155 | The following metrics are reported under a transport-specific category 156 | to the configured `StatsReceiver`. For example, if using kafka, they are 157 | reported relative to "zipkin.kafka". 158 | 159 | Metric | Description 160 | --- | --- 161 | spans | the count of spans recorded by the tracer 162 | span_bytes | the count of encoded span bytes recorded by the tracer 163 | spans_dropped | the count of spans dropped for any reason. For example, failure queueing or sending. 164 | messages | the count of messages sent to zipkin. Ex POST requests or Kafka messages. 165 | message_bytes | the count of encoded message bytes sent. This includes encoding overhead and excludes compression. 166 | messages_dropped/exception_class_name+ | count of messages dropped broken down by cause. 167 | span_queue_size | last count of spans in the pending queue 168 | span_queue_bytes | last count of encoded span bytes in the pending queue 169 | 170 | ## Artifacts 171 | All artifacts publish to the group ID "io.zipkin.finagle2". We use a common release version for all 172 | components. 173 | 174 | ### Library Releases 175 | Releases are at [Sonatype](https://oss.sonatype.org/content/repositories/releases) and [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22io.zipkin.finagle2%22) 176 | 177 | ### Library Snapshots 178 | Snapshots are uploaded to [Sonatype](https://oss.sonatype.org/content/repositories/snapshots) after 179 | commits to master. 180 | -------------------------------------------------------------------------------- /core/src/test/java/zipkin2/finagle/SpanRecorderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle; 6 | 7 | import com.twitter.finagle.service.TimeoutFilter; 8 | import com.twitter.finagle.stats.InMemoryStatsReceiver; 9 | import com.twitter.finagle.tracing.Annotation; 10 | import com.twitter.finagle.tracing.Record; 11 | import com.twitter.finagle.tracing.TraceId; 12 | import com.twitter.util.Duration; 13 | import com.twitter.util.MockTimer; 14 | import com.twitter.util.Time; 15 | import java.net.InetSocketAddress; 16 | import java.util.Date; 17 | import java.util.concurrent.BlockingQueue; 18 | import java.util.concurrent.LinkedBlockingDeque; 19 | import org.junit.Before; 20 | import org.junit.Rule; 21 | import org.junit.Test; 22 | import zipkin2.Span; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | import static org.assertj.core.api.Assertions.entry; 26 | import static scala.Option.empty; 27 | import static scala.collection.JavaConverters.mapAsJavaMap; 28 | import static zipkin2.finagle.FinagleTestObjects.TODAY; 29 | import static zipkin2.finagle.FinagleTestObjects.child; 30 | import static zipkin2.finagle.FinagleTestObjects.root; 31 | import static zipkin2.finagle.FinagleTestObjects.seq; 32 | 33 | public class SpanRecorderTest { 34 | @Rule 35 | public WithTimeAt time = new WithTimeAt(TODAY); 36 | MockTimer timer = new MockTimer(); 37 | 38 | InMemoryStatsReceiver stats = new InMemoryStatsReceiver(); 39 | BlockingQueue spansSent = new LinkedBlockingDeque<>(); 40 | SpanRecorder recorder; 41 | 42 | @Before 43 | public void setRecorder() { 44 | // Recorder schedules a flusher thread on instantiation. Do this in a Before block so 45 | // that we can control time. 46 | recorder = new SpanRecorder(span -> spansSent.add(span), stats, timer, "unknown"); 47 | } 48 | 49 | /** This is replaying actual events that happened with Finagle's tracer */ 50 | @Test public void exampleRootAndChild() throws InterruptedException { 51 | 52 | // Initiating a server-span based on an incoming request 53 | advanceAndRecord(0, root, new Annotation.Rpc("GET")); 54 | advanceAndRecord(4, root, new Annotation.BinaryAnnotation("http.uri", "/")); 55 | advanceAndRecord(15, root, new Annotation.ServiceName("frontend")); 56 | advanceAndRecord(0, root, new Annotation.BinaryAnnotation("srv/finagle.version", "6.36.0")); 57 | advanceAndRecord(0, root, Annotation.ServerRecv$.MODULE$); 58 | advanceAndRecord(1, root, new Annotation.LocalAddr(socketAddr("127.0.0.1", 8081))); 59 | advanceAndRecord(1, root, new Annotation.ServerAddr(socketAddr("127.0.0.1", 8081))); 60 | advanceAndRecord(1, root, new Annotation.ClientAddr(socketAddr("127.0.0.1", 58624))); 61 | 62 | // Creating a new child-span based on an outgoing request 63 | advanceAndRecord(3, child, new Annotation.Rpc("GET")); 64 | advanceAndRecord(0, child, new Annotation.BinaryAnnotation("http.uri", "/api")); 65 | advanceAndRecord(0, child, new Annotation.ServiceName("frontend")); 66 | advanceAndRecord(0, child, new Annotation.BinaryAnnotation("clnt/finagle.version", "6.36.0")); 67 | advanceAndRecord(0, child, Annotation.ClientSend$.MODULE$); 68 | advanceAndRecord(46, child, Annotation.WireSend$.MODULE$); 69 | advanceAndRecord(7, child, new Annotation.ServerAddr(socketAddr("127.0.0.1", 9000))); 70 | advanceAndRecord(1, child, new Annotation.ClientAddr(socketAddr("127.0.0.1", 58627))); 71 | advanceAndRecord(178, child, Annotation.WireRecv$.MODULE$); 72 | advanceAndRecord(2, child, Annotation.ClientRecv$.MODULE$); 73 | 74 | // Finishing the server span 75 | advanceAndRecord(40, root, Annotation.ServerSend$.MODULE$); 76 | 77 | Span clientSide = spansSent.take(); 78 | Span serverSide = spansSent.take(); 79 | 80 | assertThat(clientSide.kind()).isEqualTo(Span.Kind.CLIENT); 81 | assertThat(clientSide.annotations()).extracting(zipkin2.Annotation::value).containsExactly( 82 | "ws", "wr" 83 | ); 84 | 85 | assertThat(serverSide.kind()).isEqualTo(Span.Kind.SERVER); 86 | assertThat(serverSide.annotations()).isEmpty(); 87 | } 88 | 89 | @Test public void incrementsCounterWhenUnexpected_binaryAnnotation() throws Exception { 90 | recorder.record( 91 | new Record(root, Time.fromMilliseconds(TODAY), 92 | new Annotation.BinaryAnnotation("web", new Date()), empty()) 93 | ); 94 | 95 | assertThat(mapAsJavaMap(stats.counters())).containsExactly( 96 | entry(seq("record", "unhandled", "java.util.Date"), 1L) 97 | ); 98 | } 99 | 100 | /** Better to drop instead of crash on expected new Annotation types */ 101 | class FancyAnnotation extends Annotation { 102 | 103 | } 104 | 105 | @Test public void incrementsCounterWhenUnexpected_annotation() throws Exception { 106 | recorder.record( 107 | new Record(root, Time.fromMilliseconds(TODAY), new FancyAnnotation(), empty()) 108 | ); 109 | 110 | assertThat(mapAsJavaMap(stats.counters())).containsExactly( 111 | entry(seq("record", "unhandled", FancyAnnotation.class.getName()), 1L) 112 | ); 113 | } 114 | 115 | @Test public void reportsSpanOn_ClientRecv() throws Exception { 116 | advanceAndRecord(0, root, Annotation.ClientSend$.MODULE$); 117 | advanceAndRecord(1, root, Annotation.ClientRecv$.MODULE$); 118 | 119 | Span span = spansSent.take(); 120 | assertThat(span.kind()).isEqualTo(Span.Kind.CLIENT); 121 | assertThat(span.annotations()).isEmpty(); 122 | assertThat(span.timestamp()).isEqualTo(TODAY * 1000); 123 | assertThat(span.duration()).isEqualTo(1000); 124 | } 125 | 126 | // @Test public void reportsSpanOn_Timeout() throws Exception { 127 | // advanceAndRecord(0, root, Annotation.ClientSend$.MODULE$); 128 | // advanceAndRecord(1, root, new Annotation.Message(TimeoutFilter.TimeoutAnnotation())); 129 | 130 | // Span span = spansSent.take(); 131 | // assertThat(span.kind()).isEqualTo(Span.Kind.CLIENT); 132 | // assertThat(span.annotations()).extracting(zipkin2.Annotation::value).containsExactly( 133 | // "finagle.timeout" 134 | // ); 135 | // assertThat(span.timestamp()).isEqualTo(TODAY * 1000); 136 | // assertThat(span.duration()).isEqualTo(1000); 137 | // } 138 | 139 | @Test public void reportsSpanOn_ServerSend() throws Exception { 140 | advanceAndRecord(0, root, Annotation.ServerRecv$.MODULE$); 141 | advanceAndRecord(1, root, Annotation.ServerSend$.MODULE$); 142 | 143 | Span span = spansSent.take(); 144 | assertThat(span.kind()).isEqualTo(Span.Kind.SERVER); 145 | assertThat(span.annotations()).isEmpty(); 146 | assertThat(span.timestamp()).isEqualTo(TODAY * 1000); 147 | assertThat(span.duration()).isEqualTo(1000); 148 | } 149 | 150 | /** ServiceName can be set late, but it should be consistent across annotations. */ 151 | @Test public void serviceNameAppliesRetroactively() throws Exception { 152 | advanceAndRecord(0, root, new Annotation.Rpc("GET")); 153 | advanceAndRecord(0, root, Annotation.ServerRecv$.MODULE$); 154 | advanceAndRecord(0, root, new Annotation.ServiceName("frontend")); 155 | advanceAndRecord(15, root, Annotation.ServerSend$.MODULE$); 156 | 157 | Span span = spansSent.take(); 158 | assertThat(span.localServiceName()).isEqualTo("frontend"); 159 | } 160 | 161 | @Test public void flushesIncompleteSpans() throws Exception { 162 | advanceAndRecord(0, root, new Annotation.Rpc("GET")); 163 | advanceAndRecord(15, root, new Annotation.ServiceName("frontend")); 164 | advanceAndRecord(0, root, Annotation.ServerRecv$.MODULE$); 165 | // Note: there's no ServerSend() which would complete the span. 166 | 167 | time.advance(recorder.ttl.plus(Duration.fromMilliseconds(1))); // advance timer 168 | timer.tick(); // invokes a flush 169 | 170 | Span span = spansSent.take(); 171 | assertThat(span.id()).isEqualTo(root.spanId().toString()); 172 | assertThat(span.name()).isEqualTo("get"); 173 | assertThat(span.kind()).isEqualTo(Span.Kind.SERVER); 174 | assertThat(span.annotations()).extracting(zipkin2.Annotation::value).containsExactly( 175 | "finagle.flush" 176 | ); 177 | assertThat(span.duration()).isNull(); 178 | } 179 | 180 | private void advanceAndRecord(int millis, TraceId traceId, Annotation annotation) { 181 | time.advance(Duration.fromMilliseconds(millis)); 182 | recorder.record(new Record(traceId, Time.now(), annotation, empty())); 183 | } 184 | 185 | private InetSocketAddress socketAddr(String host, int port) { 186 | return new InetSocketAddress(host, port); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /core/src/main/java/zipkin2/finagle/SpanRecorder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle; 6 | 7 | import com.twitter.finagle.stats.StatsReceiver; 8 | import com.twitter.finagle.tracing.Annotation; 9 | import com.twitter.finagle.tracing.Record; 10 | import com.twitter.finagle.tracing.TraceId; 11 | import com.twitter.util.AbstractClosable; 12 | import com.twitter.util.Duration; 13 | import com.twitter.util.Future; 14 | import com.twitter.util.Time; 15 | import com.twitter.util.Time$; 16 | import com.twitter.util.Timer; 17 | import com.twitter.util.TimerTask; 18 | import java.net.InetSocketAddress; 19 | import java.util.Iterator; 20 | import java.util.concurrent.ConcurrentHashMap; 21 | import java.util.concurrent.TimeUnit; 22 | import scala.runtime.BoxedUnit; 23 | import zipkin2.Endpoint; 24 | import zipkin2.Span; 25 | import zipkin2.reporter.Reporter; 26 | import zipkin2.v1.V1SpanConverter; 27 | 28 | import static zipkin2.Span.Kind.CLIENT; 29 | 30 | final class SpanRecorder extends AbstractClosable { 31 | private static final String ERROR_FORMAT = "%s: %s"; // annotation: errorMessage 32 | final Duration ttl = Duration.apply(120, TimeUnit.SECONDS); 33 | private final ConcurrentHashMap spanMap = new ConcurrentHashMap<>(64); 34 | private final Reporter reporter; 35 | /** 36 | * Incrementing a counter instead of throwing allows finagle to add new event types ahead of 37 | * upgrading the zipkin tracer 38 | */ 39 | private final StatsReceiver unhandledReceiver; 40 | private final TimerTask flusher; 41 | private final String localServiceName; 42 | 43 | SpanRecorder(Reporter reporter, StatsReceiver stats, Timer timer, String localServiceName) { 44 | this.reporter = reporter; 45 | this.unhandledReceiver = stats.scope("record").scope("unhandled"); 46 | this.flusher = timer.schedule(ttl.$div(2L), () -> { 47 | flush(ttl.ago()); 48 | return null; 49 | }); 50 | this.localServiceName = "unknown".equals(localServiceName) ? null : localServiceName; 51 | } 52 | 53 | /** 54 | * Mutates the Span with whatever new info we have. If we see an "end" annotation we remove the 55 | * span and send it off. 56 | */ 57 | void record(Record record) { 58 | MutableSpan span = spanMap.get(record.traceId()); 59 | if (span == null) { 60 | MutableSpan newSpan = new MutableSpan(record.traceId(), Time$.MODULE$.now()); 61 | MutableSpan prev = spanMap.putIfAbsent(record.traceId(), newSpan); 62 | span = prev != null ? prev : newSpan; 63 | } 64 | 65 | append(record, span); 66 | 67 | if (span.isComplete()) { 68 | spanMap.remove(record.traceId(), span); 69 | report(span); 70 | } 71 | } 72 | 73 | /** 74 | * ported from {@link com.twitter.finagle.zipkin.core.RawZipkinTracer#record(Record)} 75 | */ 76 | void append(Record record, MutableSpan span) { 77 | Annotation annotation = record.annotation(); 78 | if (Annotation.WireSend$.MODULE$.equals(annotation)) { 79 | span.addAnnotation(record.timestamp(), "ws"); 80 | } else if (Annotation.WireRecv$.MODULE$.equals(annotation)) { 81 | span.addAnnotation(record.timestamp(), "wr"); 82 | } else if (annotation instanceof Annotation.WireRecvError) { 83 | String error = ((Annotation.WireRecvError) annotation).error(); 84 | span.addAnnotation(record.timestamp(), 85 | String.format(ERROR_FORMAT, "Wire Receive Error", error)); 86 | } else if (Annotation.ClientSend$.MODULE$.equals(annotation)) { 87 | span.addAnnotation(record.timestamp(), "cs"); 88 | } else if (Annotation.ClientRecv$.MODULE$.equals(annotation)) { 89 | span.addAnnotation(record.timestamp(), "cr"); 90 | } else if (annotation instanceof Annotation.ClientRecvError) { 91 | String error = ((Annotation.ClientRecvError) annotation).error(); 92 | span.addAnnotation(record.timestamp(), 93 | String.format(ERROR_FORMAT, "Client Receive Error", error)); 94 | } else if (Annotation.ServerSend$.MODULE$.equals(annotation)) { 95 | span.addAnnotation(record.timestamp(), "ss"); 96 | } else if (Annotation.ServerRecv$.MODULE$.equals(annotation)) { 97 | span.addAnnotation(record.timestamp(), "sr"); 98 | } else if (annotation instanceof Annotation.ServerSendError) { 99 | String error = ((Annotation.ServerSendError) annotation).error(); 100 | span.addAnnotation(record.timestamp(), 101 | String.format(ERROR_FORMAT, "Server Send Error", error)); 102 | } else if (Annotation.ClientSendFragment$.MODULE$.equals(annotation)) { 103 | span.addAnnotation(record.timestamp(), "csf"); 104 | } else if (Annotation.ClientRecvFragment$.MODULE$.equals(annotation)) { 105 | span.addAnnotation(record.timestamp(), "crf"); 106 | } else if (Annotation.ServerSendFragment$.MODULE$.equals(annotation)) { 107 | span.addAnnotation(record.timestamp(), "ssf"); 108 | } else if (Annotation.ServerRecvFragment$.MODULE$.equals(annotation)) { 109 | span.addAnnotation(record.timestamp(), "srf"); 110 | } else if (annotation instanceof Annotation.Message) { 111 | String value = ((Annotation.Message) annotation).content(); 112 | span.addAnnotation(record.timestamp(), value); 113 | } else if (annotation instanceof Annotation.Rpc) { 114 | String name = ((Annotation.Rpc) annotation).name(); 115 | span.setName(name); 116 | } else if (annotation instanceof Annotation.ServiceName) { 117 | String service = ((Annotation.ServiceName) annotation).service(); 118 | span.setServiceName(service); 119 | } else if (annotation instanceof Annotation.BinaryAnnotation) { 120 | String key = ((Annotation.BinaryAnnotation) annotation).key(); 121 | Object value = ((Annotation.BinaryAnnotation) annotation).value(); 122 | if (value instanceof Boolean) { 123 | span.addBinaryAnnotation(key, value.toString()); 124 | } else if (value instanceof Short) { 125 | span.addBinaryAnnotation(key, value.toString()); 126 | } else if (value instanceof Integer) { 127 | span.addBinaryAnnotation(key, value.toString()); 128 | } else if (value instanceof Long) { 129 | span.addBinaryAnnotation(key, value.toString()); 130 | } else if (value instanceof Double) { 131 | span.addBinaryAnnotation(key, value.toString()); 132 | } else if (value instanceof String) { 133 | span.addBinaryAnnotation(key, value.toString()); 134 | } else { 135 | unhandledReceiver.counter0(value.getClass().getName()).incr(); 136 | } 137 | } else if (annotation instanceof Annotation.LocalAddr) { 138 | InetSocketAddress ia = ((Annotation.LocalAddr) annotation).ia(); 139 | span.setEndpoint(Endpoints.boundEndpoint(Endpoints.fromSocketAddress(ia))); 140 | } else if (annotation instanceof Annotation.ClientAddr) { 141 | // use a binary annotation over a regular annotation to avoid a misleading timestamp 142 | InetSocketAddress ia = ((Annotation.ClientAddr) annotation).ia(); 143 | span.setAddress("ca", ia); 144 | } else if (annotation instanceof Annotation.ServerAddr) { 145 | InetSocketAddress ia = ((Annotation.ServerAddr) annotation).ia(); 146 | span.setAddress("sa", ia); 147 | } else { 148 | unhandledReceiver.counter0(annotation.getClass().getName()).incr(); 149 | } 150 | } 151 | 152 | /** This sends off spans after the deadline is hit, no matter if it ended naturally or not. */ 153 | void flush(Time deadline) { 154 | for (Iterator i = spanMap.values().iterator(); i.hasNext(); ) { 155 | MutableSpan span = i.next(); 156 | if (span.started().$less$eq(deadline)) { 157 | i.remove(); 158 | span.addAnnotation(deadline, "finagle.flush"); 159 | report(span); 160 | } 161 | } 162 | } 163 | 164 | @Override public Future close(Time deadline) { 165 | return flusher.close(deadline); 166 | } 167 | 168 | void report(MutableSpan span) { 169 | // Override the local service name 170 | String oldService = null; 171 | if (localServiceName != null) { 172 | oldService = span.getService(); 173 | if ("unknown".equals(oldService)) oldService = null; 174 | span.setServiceName(localServiceName); 175 | } 176 | for (zipkin2.Span v2span : V1SpanConverter.create().convert(span.toSpan())) { 177 | v2span = moveServiceNameToRemoteServiceName(v2span, oldService); 178 | reporter.report(moveServiceNameToRemoteServiceName(v2span, oldService)); 179 | } 180 | } 181 | 182 | static Span moveServiceNameToRemoteServiceName(Span v2span, String oldService) { 183 | if (CLIENT.equals(v2span.kind()) && oldService != null) { 184 | Endpoint remoteEndpoint = v2span.remoteEndpoint(); 185 | if (remoteEndpoint == null) { 186 | remoteEndpoint = Endpoint.newBuilder().serviceName(oldService).build(); 187 | } else { 188 | remoteEndpoint = remoteEndpoint.toBuilder().serviceName(oldService).build(); 189 | } 190 | v2span = v2span.toBuilder().remoteEndpoint(remoteEndpoint).build(); 191 | } 192 | return v2span; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /core/src/test/java/zipkin2/finagle/ZipkinTracerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle; 6 | 7 | import com.twitter.finagle.stats.InMemoryStatsReceiver; 8 | import com.twitter.finagle.tracing.Annotation; 9 | import com.twitter.finagle.tracing.Annotation.ClientRecv$; 10 | import com.twitter.finagle.tracing.Annotation.ClientSend$; 11 | import com.twitter.finagle.tracing.Annotation.Rpc; 12 | import com.twitter.finagle.tracing.Annotation.ServerRecv$; 13 | import com.twitter.finagle.tracing.Annotation.ServerSend$; 14 | import com.twitter.finagle.tracing.Annotation.ServiceName; 15 | import com.twitter.finagle.tracing.Flags$; 16 | import com.twitter.finagle.tracing.Record; 17 | import com.twitter.finagle.tracing.SpanId; 18 | import com.twitter.finagle.tracing.TraceId; 19 | import com.twitter.finagle.tracing.TraceId$; 20 | import com.twitter.finagle.tracing.traceId128Bit$; 21 | import java.net.InetSocketAddress; 22 | import java.util.List; 23 | import java.util.concurrent.BlockingQueue; 24 | import java.util.concurrent.LinkedBlockingDeque; 25 | import java.util.concurrent.TimeUnit; 26 | import java.util.function.Consumer; 27 | import org.junit.After; 28 | import org.junit.Rule; 29 | import org.junit.Test; 30 | import org.junit.rules.ExpectedException; 31 | import zipkin2.Endpoint; 32 | import zipkin2.Span; 33 | import zipkin2.reporter.AsyncReporter; 34 | 35 | import static com.twitter.util.Time.fromMilliseconds; 36 | import static org.assertj.core.api.Assertions.assertThat; 37 | import static org.assertj.core.api.Assertions.entry; 38 | import static scala.Option.empty; 39 | import static scala.collection.JavaConverters.mapAsJavaMap; 40 | import static zipkin2.finagle.FinagleTestObjects.TODAY; 41 | import static zipkin2.finagle.FinagleTestObjects.root; 42 | import static zipkin2.finagle.FinagleTestObjects.seq; 43 | 44 | public class ZipkinTracerTest { 45 | @Rule public ExpectedException thrown = ExpectedException.none(); 46 | 47 | InMemoryStatsReceiver stats = new InMemoryStatsReceiver(); 48 | BlockingQueue> spansSent = new LinkedBlockingDeque<>(); 49 | 50 | // Initialize with the special keyword unknown which delegates to Annotation.ServiceName otherwise 51 | ZipkinTracer tracer = newTracer(reporterBuilder(spansSent::add), "unknown"); 52 | 53 | AsyncReporter.Builder reporterBuilder(Consumer> onSpans) { 54 | return AsyncReporter.builder(FakeSender.create().onSpans(onSpans)) 55 | .messageTimeout(0, TimeUnit.MILLISECONDS) 56 | .messageMaxBytes(176 + 5) // size of a simple span w/ 128-bit trace ID + list overhead 57 | .metrics(new ReporterMetricsAdapter(stats)); 58 | } 59 | 60 | ZipkinTracer newTracer(AsyncReporter.Builder spanReporter, String localServiceName) { 61 | if (tracer != null) tracer.close(); 62 | return ZipkinTracer.newBuilder(spanReporter.build()) 63 | .initialSampleRate(1.0f) 64 | .localServiceName(localServiceName) 65 | .stats(stats) 66 | .build(); 67 | } 68 | 69 | @After public void closeTracer() { 70 | tracer.close(); 71 | } 72 | 73 | @Test public void defaultIsLocalServiceNameFromAnnotation() throws Exception { 74 | recordClientSpan(root); 75 | 76 | assertThat(spansSent.take().stream()) 77 | .flatExtracting(Span::localServiceName) 78 | .containsExactly("web"); 79 | } 80 | 81 | @Test public void configOverridesLocalServiceName_client() throws Exception { 82 | tracer = tracerWithLocalServiceName("favstar"); 83 | 84 | recordClientSpan(root); 85 | 86 | // Moves recorded service name to the remote service name 87 | assertThat(spansSent.take().stream()) 88 | .flatExtracting(Span::localServiceName, Span::remoteServiceName) 89 | .containsExactly("favstar", "web"); 90 | } 91 | 92 | @Test public void configOverridesLocalServiceName_client_combines() throws Exception { 93 | tracer = tracerWithLocalServiceName("favstar"); 94 | 95 | tracer.record(new Record(root, fromMilliseconds(TODAY), new Annotation.LocalAddr( 96 | new InetSocketAddress("1.2.3.3", 443)), empty())); 97 | tracer.record(new Record(root, fromMilliseconds(TODAY), new Annotation.ServerAddr( 98 | new InetSocketAddress("1.2.3.4", 80)), empty())); 99 | recordClientSpan(root); 100 | 101 | Span span = spansSent.take().get(0); 102 | // Combines the recorded service name with the remote IP and socket info 103 | assertThat(span.localEndpoint()) 104 | .isEqualTo(Endpoint.newBuilder().serviceName("favstar").ip("1.2.3.3").port(443).build()); 105 | assertThat(span.remoteEndpoint()) 106 | .isEqualTo(Endpoint.newBuilder().serviceName("web").ip("1.2.3.4").port(80).build()); 107 | } 108 | 109 | @Test public void configOverridesLocalServiceName_server() throws Exception { 110 | tracer = tracerWithLocalServiceName("favstar"); 111 | 112 | recordServerSpan(root); 113 | 114 | // Doesn't set remote service name 115 | assertThat(spansSent.take().stream()) 116 | .flatExtracting(Span::localServiceName, Span::remoteServiceName) 117 | .containsExactly("favstar", null); 118 | } 119 | 120 | @Test public void unfinishedSpansArentImplicitlyReported() throws Exception { 121 | tracer.record(new Record(root, fromMilliseconds(TODAY), new ServiceName("web"), empty())); 122 | tracer.record(new Record(root, fromMilliseconds(TODAY), new Rpc("get"), empty())); 123 | tracer.record(new Record(root, fromMilliseconds(TODAY), ClientSend$.MODULE$, empty())); 124 | 125 | flush(); 126 | 127 | assertThat(spansSent.take()).isEmpty(); 128 | } 129 | 130 | @Test public void finishedSpansAreImplicitlyReported() throws Exception { 131 | recordClientSpan(root); 132 | 133 | assertThat(spansSent.take().stream()) 134 | .flatExtracting(Span::kind) 135 | .containsExactly(Span.Kind.CLIENT); 136 | } 137 | 138 | /** See {@link traceId128Bit$} */ 139 | @Test public void traceId128Bit() throws Exception { 140 | TraceId root = TraceId$.MODULE$.apply( 141 | SpanId.fromString("0f28590523a46541"), 142 | empty(), 143 | SpanId.fromString("0f28590523a46541").get(), 144 | empty(), 145 | Flags$.MODULE$.apply(), 146 | SpanId.fromString("d2f9288a2904503d"), 147 | false 148 | ); 149 | 150 | recordClientSpan(root); 151 | 152 | assertThat(spansSent.take().stream()) 153 | .extracting(Span::traceId) 154 | .containsExactly("d2f9288a2904503d0f28590523a46541"); 155 | } 156 | 157 | @Test public void reportIncrementsAcceptedMetrics() { 158 | recordClientSpan(root); 159 | 160 | assertThat(mapAsJavaMap(stats.counters())).containsExactly( 161 | entry(seq("span_bytes"), 165L), 162 | entry(seq("spans"), 1L), 163 | entry(seq("spans_dropped"), 0L), 164 | entry(seq("message_bytes"), 170L), 165 | entry(seq("messages"), 1L) 166 | ); 167 | } 168 | 169 | @Test public void incrementsDropMetricsOnSendError() { 170 | tracer = newTracer(reporterBuilder(span -> { 171 | throw new IllegalStateException(new NullPointerException()); 172 | }), "unknown"); 173 | 174 | tracer.record(new Record(root, fromMilliseconds(TODAY), new ServiceName("web"), empty())); 175 | tracer.record(new Record(root, fromMilliseconds(TODAY), new Rpc("get"), empty())); 176 | tracer.record(new Record(root, fromMilliseconds(TODAY), ClientSend$.MODULE$, empty())); 177 | tracer.record(new Record(root, fromMilliseconds(TODAY + 1), ClientRecv$.MODULE$, empty())); 178 | 179 | flush(); 180 | 181 | assertThat(mapAsJavaMap(stats.counters())).containsOnly( 182 | entry(seq("spans"), 1L), 183 | entry(seq("span_bytes"), 165L), 184 | entry(seq("spans_dropped"), 1L), 185 | entry(seq("messages"), 1L), 186 | entry(seq("message_bytes"), 170L), 187 | entry(seq("messages_dropped"), 1L), 188 | entry(seq("messages_dropped", "java.lang.IllegalStateException"), 1L), 189 | entry(seq("messages_dropped", "java.lang.IllegalStateException", 190 | "java.lang.NullPointerException"), 1L) 191 | ); 192 | } 193 | 194 | void flush() { 195 | ((AsyncReporter) tracer.reporter).flush(); 196 | } 197 | 198 | ZipkinTracer tracerWithLocalServiceName(String localServiceName) { 199 | return newTracer(reporterBuilder(spansSent::add) 200 | .messageMaxBytes(500), // RPC spans are bigger than local ones 201 | localServiceName); 202 | } 203 | 204 | void recordClientSpan(TraceId traceId) { 205 | tracer.record(new Record(traceId, fromMilliseconds(TODAY), new ServiceName("web"), empty())); 206 | tracer.record(new Record(traceId, fromMilliseconds(TODAY), new Rpc("get"), empty())); 207 | tracer.record(new Record(traceId, fromMilliseconds(TODAY), ClientSend$.MODULE$, empty())); 208 | 209 | // client receive reports the span 210 | tracer.record(new Record(traceId, fromMilliseconds(TODAY + 1), ClientRecv$.MODULE$, empty())); 211 | 212 | flush(); 213 | } 214 | 215 | void recordServerSpan(TraceId traceId) { 216 | tracer.record(new Record(traceId, fromMilliseconds(TODAY), new ServiceName("web"), empty())); 217 | tracer.record(new Record(traceId, fromMilliseconds(TODAY), new Rpc("get"), empty())); 218 | tracer.record(new Record(traceId, fromMilliseconds(TODAY), ServerRecv$.MODULE$, empty())); 219 | 220 | // server send reports the span 221 | tracer.record(new Record(traceId, fromMilliseconds(TODAY + 1), ServerSend$.MODULE$, empty())); 222 | 223 | flush(); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /http/src/test/java/zipkin2/finagle/http/ITHttpZipkinTracer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle.http; 6 | 7 | import com.twitter.finagle.tracing.Annotation.ClientRecv$; 8 | import com.twitter.finagle.tracing.Annotation.ClientSend$; 9 | import com.twitter.finagle.tracing.Annotation.Rpc; 10 | import com.twitter.finagle.tracing.Annotation.ServiceName; 11 | import com.twitter.finagle.tracing.Record; 12 | import com.twitter.util.Duration; 13 | import com.twitter.util.Time; 14 | import java.net.InetAddress; 15 | import java.net.URI; 16 | import java.net.UnknownHostException; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Objects; 20 | import java.util.Optional; 21 | import okhttp3.mockwebserver.MockWebServer; 22 | import okhttp3.mockwebserver.RecordedRequest; 23 | import okhttp3.tls.HandshakeCertificates; 24 | import okhttp3.tls.HeldCertificate; 25 | import org.junit.jupiter.api.Test; 26 | import org.junit.jupiter.api.extension.Extensions; 27 | import org.junit.jupiter.api.extension.RegisterExtension; 28 | 29 | import scala.Option; 30 | import zipkin.http.path$; 31 | import zipkin2.Span; 32 | import zipkin2.finagle.FinagleTestObjects; 33 | import zipkin2.finagle.ITZipkinTracer; 34 | import zipkin2.finagle.ZipkinTracer; 35 | import zipkin2.finagle.http.HttpZipkinTracer.Config; 36 | import zipkin2.junit5.ZipkinExtension; 37 | 38 | import static java.util.Arrays.asList; 39 | import static java.util.concurrent.TimeUnit.SECONDS; 40 | import static org.assertj.core.api.Assertions.assertThat; 41 | import static org.assertj.core.api.Assertions.entry; 42 | import static org.awaitility.Awaitility.await; 43 | import static org.junit.Assert.assertNotNull; 44 | import static scala.collection.JavaConverters.mapAsJavaMap; 45 | import static zipkin2.finagle.FinagleTestObjects.TODAY; 46 | import static zipkin2.finagle.FinagleTestObjects.root; 47 | 48 | public class ITHttpZipkinTracer extends ITZipkinTracer { 49 | @RegisterExtension public ZipkinExtension http = new ZipkinExtension(); 50 | String host = "localhost:" + URI.create(http.httpUrl()).getPort(); 51 | Config config = Config.builder().initialSampleRate(1.0f).host(host).build(); 52 | 53 | final Option none = Option.empty(); // avoid having to force generics 54 | 55 | @Override protected ZipkinTracer newTracer(String localServiceName) { 56 | config = config.toBuilder().localServiceName(localServiceName).build(); 57 | return new HttpZipkinTracer(config, stats); 58 | } 59 | 60 | @Override protected List> getTraces() { 61 | return http.getTraces(); 62 | } 63 | 64 | @Test public void whenHttpIsDown() throws Exception { 65 | http.shutdown(); // shutdown the normal zipkin rule 66 | 67 | tracer.record(new Record(root, Time.fromMilliseconds(TODAY), new ServiceName("web"), none)); 68 | tracer.record(new Record(root, Time.fromMilliseconds(TODAY), new Rpc("get"), none)); 69 | tracer.record(new Record(root, Time.fromMilliseconds(TODAY), ClientSend$.MODULE$, none)); 70 | tracer.record(new Record(root, Time.fromMilliseconds(TODAY + 1), ClientRecv$.MODULE$, none)); 71 | 72 | // wait for the HTTP request attempt to go through 73 | await().atMost(5, SECONDS).untilAsserted(() -> assertThat(mapAsJavaMap(stats.counters())) 74 | .contains( 75 | entry(FinagleTestObjects.seq("spans"), 1L), 76 | entry(FinagleTestObjects.seq("span_bytes"), 185L), 77 | entry(FinagleTestObjects.seq("spans_dropped"), 1L), 78 | entry(FinagleTestObjects.seq("messages"), 1L), 79 | entry(FinagleTestObjects.seq("message_bytes"), 187L), 80 | entry(FinagleTestObjects.seq("messages_dropped"), 1L), 81 | entry(FinagleTestObjects.seq("messages_dropped", "com.twitter.finagle.Failure"), 1L), 82 | entry(FinagleTestObjects.seq("messages_dropped", "com.twitter.finagle.Failure", 83 | "com.twitter.finagle.ConnectionFailedException"), 1L), 84 | entry(FinagleTestObjects.seq("messages_dropped", "com.twitter.finagle.Failure", 85 | "com.twitter.finagle.ConnectionFailedException", 86 | "io.netty.channel.AbstractChannel$AnnotatedConnectException"), 1L) 87 | )); 88 | } 89 | 90 | @Test public void path() throws Exception { 91 | http.shutdown(); // shutdown the normal zipkin rule 92 | 93 | // create instructions to create a complete RPC span 94 | List records = asList( 95 | new Record(root, Time.fromMilliseconds(TODAY), new ServiceName("web"), none), 96 | new Record(root, Time.fromMilliseconds(TODAY), new Rpc("get"), none), 97 | new Record(root, Time.fromMilliseconds(TODAY), ClientSend$.MODULE$, none), 98 | new Record(root, Time.fromMilliseconds(TODAY + 1), ClientRecv$.MODULE$, none) 99 | ); 100 | 101 | MockWebServer server = new MockWebServer(); 102 | config = config.toBuilder().host("localhost:" + server.getPort()).build(); 103 | try { 104 | List requests = new ArrayList<>(); 105 | String customPath = "/custom/api/v2/spans"; 106 | for (Optional overridePath : asList(Optional.empty(), 107 | Optional.of(customPath))) { 108 | // recreate the tracer with the path configuration 109 | closeTracer(); 110 | overridePath.ifPresent(s -> config = config.toBuilder().path(s).build()); 111 | createTracer(); 112 | 113 | // write a complete span so that it gets reported 114 | records.forEach(tracer::record); 115 | 116 | // block until the request arrived 117 | requests.add(server.takeRequest()); 118 | } 119 | // we expect the first request to be sent to the default path, and the second to the overridden path 120 | assertThat(Objects.equals(requests.get(0).getPath(), path$.DEFAULT_PATH)); 121 | assertThat(Objects.equals(requests.get(1).getPath(), customPath)); 122 | } finally { 123 | server.shutdown(); 124 | } 125 | } 126 | 127 | @Test public void compression() throws Exception { 128 | http.shutdown(); // shutdown the normal zipkin rule 129 | 130 | // create instructions to create a complete RPC span 131 | List records = asList( 132 | new Record(root, Time.fromMilliseconds(TODAY), new ServiceName("web"), none), 133 | new Record(root, Time.fromMilliseconds(TODAY), new Rpc("get"), none), 134 | new Record(root, Time.fromMilliseconds(TODAY), ClientSend$.MODULE$, none), 135 | new Record(root, Time.fromMilliseconds(TODAY + 1), ClientRecv$.MODULE$, none) 136 | ); 137 | 138 | MockWebServer server = new MockWebServer(); 139 | config = config.toBuilder().host("localhost:" + server.getPort()).build(); 140 | try { 141 | List requests = new ArrayList<>(); 142 | for (boolean compressionEnabled : asList(true, false)) { 143 | // recreate the tracer with the compression configuration 144 | closeTracer(); 145 | config = config.toBuilder().compressionEnabled(compressionEnabled).build(); 146 | createTracer(); 147 | 148 | // write a complete span so that it gets reported 149 | records.forEach(tracer::record); 150 | 151 | // block until the request arrived 152 | requests.add(server.takeRequest()); 153 | } 154 | 155 | // we expect the first compressed request to be smaller than the uncompressed one. 156 | assertThat(requests.get(0).getBodySize()) 157 | .isLessThan(requests.get(1).getBodySize()); 158 | } finally { 159 | server.shutdown(); 160 | } 161 | } 162 | 163 | @Test public void tls() throws Exception { 164 | http.shutdown(); // shutdown the normal zipkin rule 165 | 166 | // create instructions to create a complete RPC span 167 | List records = asList( 168 | new Record(root, Time.fromMilliseconds(TODAY), new ServiceName("web"), none), 169 | new Record(root, Time.fromMilliseconds(TODAY), new Rpc("get"), none), 170 | new Record(root, Time.fromMilliseconds(TODAY), ClientSend$.MODULE$, none), 171 | new Record(root, Time.fromMilliseconds(TODAY + 1), ClientRecv$.MODULE$, none) 172 | ); 173 | 174 | MockWebServer server = createMockWebServerWithTLS(); 175 | 176 | config = config.toBuilder() 177 | .host("localhost:" + server.getPort()) 178 | .tlsEnabled(true) 179 | .tlsValidationEnabled(false) 180 | .build(); 181 | try { 182 | List requests = new ArrayList<>(); 183 | 184 | // recreate the tracer with the tls configuration 185 | closeTracer(); 186 | createTracer(); 187 | 188 | // write a complete span so that it gets reported 189 | records.forEach(tracer::record); 190 | 191 | // block until the request arrived 192 | requests.add(server.takeRequest()); 193 | 194 | // we expect the request to have a TLS version specified 195 | assertNotNull(requests.get(0).getTlsVersion()); 196 | } finally { 197 | server.shutdown(); 198 | } 199 | } 200 | 201 | MockWebServer createMockWebServerWithTLS() throws UnknownHostException { 202 | MockWebServer server = new MockWebServer(); 203 | String localhost = InetAddress.getByName("localhost").getCanonicalHostName(); 204 | HeldCertificate localhostCertificate = new HeldCertificate.Builder() 205 | .addSubjectAlternativeName(localhost) 206 | .build(); 207 | HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder() 208 | .heldCertificate(localhostCertificate) 209 | .build(); 210 | server.useHttps(serverCertificates.sslSocketFactory(), false); 211 | return server; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /core/src/main/java/zipkin2/finagle/ZipkinTracer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright The OpenZipkin Authors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package zipkin2.finagle; 6 | 7 | import com.twitter.finagle.stats.DefaultStatsReceiver$; 8 | import com.twitter.finagle.stats.StatsReceiver; 9 | import com.twitter.finagle.tracing.Annotation; 10 | import com.twitter.finagle.tracing.Record; 11 | import com.twitter.finagle.tracing.TraceId; 12 | import com.twitter.finagle.tracing.Tracer; 13 | import com.twitter.finagle.util.DefaultTimer; 14 | import com.twitter.finagle.zipkin.core.SamplingTracer; 15 | import com.twitter.util.Closable; 16 | import com.twitter.util.Duration; 17 | import com.twitter.util.Future; 18 | import com.twitter.util.Time; 19 | import java.io.Closeable; 20 | import java.io.IOException; 21 | import scala.Option; 22 | import scala.Some; 23 | import scala.runtime.AbstractFunction1; 24 | import scala.runtime.BoxedUnit; 25 | import zipkin2.Span; 26 | import zipkin2.internal.Nullable; 27 | import zipkin2.reporter.AsyncReporter; 28 | import zipkin2.reporter.Reporter; 29 | import zipkin2.reporter.Sender; 30 | 31 | /** 32 | * Receives the Finagle generated traces and sends them off to Zipkin. 33 | * 34 | *

Implement this by extending and registering in the service loader. For example: 35 | *

 36 |  * @AutoService(com.twitter.finagle.tracing.Tracer.class)
 37 |  * public final class HttpZipkinTracer extends ZipkinTracer {
 38 |  *
 39 |  *  --snip--
 40 |  *
 41 |  *   // Default constructor for the service loader
 42 |  *   public HttpZipkinTracer() {
 43 |  *     this(Config.builder().build(),
 44 |  *          DefaultStatsReceiver$.MODULE$.get().scope("zipkin.http")
 45 |  *     );
 46 |  *   }
 47 |  *
 48 |  *   HttpZipkinTracer(Config config, StatsReceiver stats) {
 49 |  *     super(new HttpReporter(config.host()), stats, config.initialSampleRate());
 50 |  *   }
 51 |  * }
52 | * 53 | *

If you don't need to use service loader, an alternate way to manually configure the tracer is 54 | * via {@link #newBuilder(Reporter)} 55 | */ 56 | // It would be cleaner to obviate SamplingTracer and the dependency on finagle-zipkin-core, but 57 | // SamplingTracer includes unrelated event logic https://github.com/twitter/finagle/issues/540 58 | public class ZipkinTracer extends SamplingTracer implements Closable { 59 | 60 | /** 61 | * Normally, Finagle configures the tracer implicitly and through flags. This implies constraints 62 | * needed for service loading. Alternatively, you can use this type to explicitly configure any 63 | * sender available in Zipkin. 64 | * 65 | *

Ex. 66 | *

{@code
 67 |    * // Setup a sender without using Finagle flags like so:
 68 |    * Properties overrides = new Properties();
 69 |    * overrides.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 5000);
 70 |    * sender = KafkaSender.newBuilder()
 71 |    *   .bootstrapServers("host1:9092,host2:9092")
 72 |    *   .overrides(overrides)
 73 |    *   .encoding(Encoding.PROTO3)
 74 |    *   .build();
 75 |    *
 76 |    * zipkinStats = stats.scope("zipkin");
 77 |    * spanReporter = AsyncReporter.builder(sender)
 78 |    *   .metrics(new ReporterMetricsAdapter(zipkinStats)) // routes reporter metrics to finagle stats
 79 |    *   .build()
 80 |    *
 81 |    * // Now, use it here, but don't forget to close the sender!
 82 |    * tracer = ZipkinTracer.newBuilder(spanReporter).stats(zipkinStats).build();
 83 |    * }
84 | * 85 | *

On closing resources

86 | * The resulting tracer will attempt to close an underlying reporter if it implements {@link 87 | * Closeable}. It is best to use normal tools like pre-destroy hooks to close resources in your 88 | * application. If you somehow cannot control your resources, yet can invoke this, consider 89 | * wrapping the input as a closeable to coordinate an ordered shutdown. 90 | * 91 | *

Ex. 92 | *

{@code
 93 |    * class ReporterThatClosesSender implements Reporter, Closeable {
 94 |    *   final Sender sender;
 95 |    *   final AsyncReporter reporter;
 96 |    *
 97 |    *   @Override public void close() throws IOException {
 98 |    *     reporter.close();
 99 |    *     sender.close();
100 |    *   }
101 |    *
102 |    *   @Override public void report(Span span) {
103 |    *     reporter.report(span);
104 |    *   }
105 |    * }
106 |    * }
107 | */ 108 | public static Builder newBuilder(Reporter spanReporter) { 109 | return new Builder(spanReporter); 110 | } 111 | 112 | final Reporter reporter; 113 | final RawZipkinTracer underlying; 114 | 115 | protected ZipkinTracer(Sender sender, Config config, StatsReceiver stats) { 116 | this(AsyncReporter.builder(sender) 117 | .metrics(new ReporterMetricsAdapter(stats)) 118 | .build(), config, stats); 119 | } 120 | 121 | ZipkinTracer(Reporter reporter, Config config, StatsReceiver stats) { 122 | this(reporter, new RawZipkinTracer(reporter, stats, config.localServiceName(), config.initialSampleRate()), config); 123 | } 124 | 125 | private ZipkinTracer(Reporter reporter, RawZipkinTracer underlying, Config config) { 126 | super(underlying, config.initialSampleRate()); 127 | this.reporter = reporter; 128 | this.underlying = underlying; 129 | } 130 | 131 | @Override public Future close() { 132 | return close(Time.Bottom()); 133 | } 134 | 135 | /** 136 | * There are two queues here. The recorder has in-flight data about operations not yet complete. 137 | * The reporter (usually) has a queue of spans for operations completed, not yet sent to Zipkin. 138 | * 139 | *

The close process tries to avoid dropping data on the floor by first flushing any in-flight 140 | * operations in the recorder (ideally none), then any spans waiting for the next send interval to 141 | * Zipkin. 142 | */ 143 | @Override public Future close(Time deadline) { 144 | Future result = underlying.recorder.close(deadline); 145 | if (!(reporter instanceof Closeable)) return result; 146 | return result.onSuccess(new AbstractFunction1() { 147 | @Override public BoxedUnit apply(BoxedUnit v1) { 148 | try { 149 | ((Closeable) reporter).close(); 150 | } catch (IOException | RuntimeException ignored) { 151 | } 152 | return BoxedUnit.UNIT; 153 | } 154 | }); 155 | } 156 | 157 | @Override public Future close(Duration after) { 158 | return close(after.fromNow()); 159 | } 160 | 161 | protected interface Config { 162 | @Nullable String localServiceName(); 163 | 164 | /** How much data to collect. Default sample rate 0.001 (0.1%). Max is 1, min 0. */ 165 | float initialSampleRate(); 166 | } 167 | 168 | static final class RawZipkinTracer implements Tracer { 169 | private final SpanRecorder recorder; 170 | private final float sampleRate; 171 | 172 | /** 173 | * @param stats We generate stats to keep track of traces sent, failures and so on 174 | * @param sampleRate 175 | */ 176 | RawZipkinTracer(Reporter reporter, StatsReceiver stats, String localServiceName, float sampleRate) { 177 | this.recorder = 178 | new SpanRecorder(reporter, stats, DefaultTimer.getInstance(), localServiceName); 179 | this.sampleRate = sampleRate; 180 | } 181 | 182 | @Override public Option sampleTrace(TraceId traceId) { 183 | return Some.apply(true); 184 | } 185 | 186 | @Override public boolean isNull() { 187 | return false; 188 | } 189 | 190 | @Override public boolean isActivelyTracing(TraceId traceId) { 191 | return true; 192 | } 193 | 194 | /** 195 | * Mutates the Span with whatever new info we have. If we see an "end" annotation we remove the 196 | * span and send it off. 197 | */ 198 | @Override public void record(Record record) { 199 | recorder.record(record); 200 | } 201 | 202 | @Override 203 | public float getSampleRate() { 204 | return sampleRate; 205 | } 206 | } 207 | 208 | public static final class Builder { 209 | final Reporter spanReporter; 210 | StaticConfig config = new StaticConfig(); 211 | StatsReceiver stats = DefaultStatsReceiver$.MODULE$.get().scope("zipkin"); 212 | 213 | Builder(Reporter spanReporter) { 214 | if (spanReporter == null) throw new NullPointerException("spanReporter == null"); 215 | this.spanReporter = spanReporter; 216 | } 217 | 218 | /** 219 | * Lower-case label of the remote node in the service graph, such as "favstar". Avoid names with 220 | * variables or unique identifiers embedded. 221 | * 222 | *

When unset, the service name is derived from {@link Annotation.ServiceName} which is 223 | * often incorrectly set to the remote service name. 224 | * 225 | *

This is a primary label for trace lookup and aggregation, so it should be intuitive and 226 | * consistent. Many use a name from service discovery. 227 | */ 228 | public Builder localServiceName(String localServiceName) { 229 | if (localServiceName == null) throw new NullPointerException("localServiceName == null"); 230 | this.config.localServiceName = localServiceName; 231 | return this; 232 | } 233 | 234 | /** 235 | * Percentage of traces to sample (report to zipkin) in the range [0.0 - 1.0]. 236 | * 237 | *

Default is the value of the flag {@code zipkin.initialSampleRate} which if not overridden 238 | * is 0.001f (0.1%). 239 | */ 240 | public Builder initialSampleRate(float initialSampleRate) { 241 | if (initialSampleRate < 0.0f || initialSampleRate > 1.0f) { 242 | throw new IllegalArgumentException("initialSampleRate must be in the range [0.0 - 1.0]"); 243 | } 244 | this.config.initialSampleRate = initialSampleRate; 245 | return this; 246 | } 247 | 248 | /** 249 | * It is possible that later versions of finagle add new types of {@link Annotation}. If this 250 | * occurs, the values won't be mapped until an update occurs here. We increment a counter using 251 | * below if that occurs. 252 | * 253 | * @param stats defaults to scope "zipkin" 254 | */ 255 | public Builder stats(StatsReceiver stats) { 256 | if (stats == null) throw new NullPointerException("stats == null"); 257 | this.stats = stats; 258 | return this; 259 | } 260 | 261 | public ZipkinTracer build() { 262 | return new ZipkinTracer(spanReporter, config, stats); 263 | } 264 | } 265 | 266 | static final class StaticConfig implements Config { 267 | float initialSampleRate = zipkin.initialSampleRate$.Flag.apply(); 268 | String localServiceName = zipkin.localServiceName$.Flag.apply(); 269 | 270 | @Override public String localServiceName() { 271 | return localServiceName; 272 | } 273 | 274 | @Override public float initialSampleRate() { 275 | return initialSampleRate; 276 | } 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | fi 118 | 119 | if [ -z "$JAVA_HOME" ]; then 120 | javaExecutable="`which javac`" 121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 122 | # readlink(1) is not available as standard on Solaris 10. 123 | readLink=`which readlink` 124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 125 | if $darwin ; then 126 | javaHome="`dirname \"$javaExecutable\"`" 127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 128 | else 129 | javaExecutable="`readlink -f \"$javaExecutable\"`" 130 | fi 131 | javaHome="`dirname \"$javaExecutable\"`" 132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 133 | JAVA_HOME="$javaHome" 134 | export JAVA_HOME 135 | fi 136 | fi 137 | fi 138 | 139 | if [ -z "$JAVACMD" ] ; then 140 | if [ -n "$JAVA_HOME" ] ; then 141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 142 | # IBM's JDK on AIX uses strange locations for the executables 143 | JAVACMD="$JAVA_HOME/jre/sh/java" 144 | else 145 | JAVACMD="$JAVA_HOME/bin/java" 146 | fi 147 | else 148 | JAVACMD="`which java`" 149 | fi 150 | fi 151 | 152 | if [ ! -x "$JAVACMD" ] ; then 153 | echo "Error: JAVA_HOME is not defined correctly." >&2 154 | echo " We cannot execute $JAVACMD" >&2 155 | exit 1 156 | fi 157 | 158 | if [ -z "$JAVA_HOME" ] ; then 159 | echo "Warning: JAVA_HOME environment variable is not set." 160 | fi 161 | 162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 163 | 164 | # traverses directory structure from process work directory to filesystem root 165 | # first directory with .mvn subdirectory is considered project base directory 166 | find_maven_basedir() { 167 | 168 | if [ -z "$1" ] 169 | then 170 | echo "Path not specified to find_maven_basedir" 171 | return 1 172 | fi 173 | 174 | basedir="$1" 175 | wdir="$1" 176 | while [ "$wdir" != '/' ] ; do 177 | if [ -d "$wdir"/.mvn ] ; then 178 | basedir=$wdir 179 | break 180 | fi 181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 182 | if [ -d "${wdir}" ]; then 183 | wdir=`cd "$wdir/.."; pwd` 184 | fi 185 | # end of workaround 186 | done 187 | echo "${basedir}" 188 | } 189 | 190 | # concatenates all lines of a file 191 | concat_lines() { 192 | if [ -f "$1" ]; then 193 | echo "$(tr -s '\n' ' ' < "$1")" 194 | fi 195 | } 196 | 197 | BASE_DIR=`find_maven_basedir "$(pwd)"` 198 | if [ -z "$BASE_DIR" ]; then 199 | exit 1; 200 | fi 201 | 202 | ########################################################################################## 203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 204 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 205 | ########################################################################################## 206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 207 | if [ "$MVNW_VERBOSE" = true ]; then 208 | echo "Found .mvn/wrapper/maven-wrapper.jar" 209 | fi 210 | else 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 213 | fi 214 | if [ -n "$MVNW_REPOURL" ]; then 215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 216 | else 217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 218 | fi 219 | while IFS="=" read key value; do 220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 221 | esac 222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 223 | if [ "$MVNW_VERBOSE" = true ]; then 224 | echo "Downloading from: $jarUrl" 225 | fi 226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 227 | if $cygwin; then 228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 229 | fi 230 | 231 | if command -v wget > /dev/null; then 232 | if [ "$MVNW_VERBOSE" = true ]; then 233 | echo "Found wget ... using wget" 234 | fi 235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 236 | wget "$jarUrl" -O "$wrapperJarPath" 237 | else 238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" 239 | fi 240 | elif command -v curl > /dev/null; then 241 | if [ "$MVNW_VERBOSE" = true ]; then 242 | echo "Found curl ... using curl" 243 | fi 244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 245 | curl -o "$wrapperJarPath" "$jarUrl" -f 246 | else 247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 248 | fi 249 | 250 | else 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo "Falling back to using Java to download" 253 | fi 254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 255 | # For Cygwin, switch paths to Windows format before running javac 256 | if $cygwin; then 257 | javaClass=`cygpath --path --windows "$javaClass"` 258 | fi 259 | if [ -e "$javaClass" ]; then 260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 261 | if [ "$MVNW_VERBOSE" = true ]; then 262 | echo " - Compiling MavenWrapperDownloader.java ..." 263 | fi 264 | # Compiling the Java class 265 | ("$JAVA_HOME/bin/javac" "$javaClass") 266 | fi 267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 268 | # Running the downloader 269 | if [ "$MVNW_VERBOSE" = true ]; then 270 | echo " - Running MavenWrapperDownloader.java ..." 271 | fi 272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 273 | fi 274 | fi 275 | fi 276 | fi 277 | ########################################################################################## 278 | # End of extension 279 | ########################################################################################## 280 | 281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 282 | if [ "$MVNW_VERBOSE" = true ]; then 283 | echo $MAVEN_PROJECTBASEDIR 284 | fi 285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 286 | 287 | # For Cygwin, switch paths to Windows format before running java 288 | if $cygwin; then 289 | [ -n "$M2_HOME" ] && 290 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 291 | [ -n "$JAVA_HOME" ] && 292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 293 | [ -n "$CLASSPATH" ] && 294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 295 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 297 | fi 298 | 299 | # Provide a "standardized" way to retrieve the CLI args that will 300 | # work with both Windows and non-Windows executions. 301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 302 | export MAVEN_CMD_LINE_ARGS 303 | 304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 305 | 306 | exec "$JAVACMD" \ 307 | $MAVEN_OPTS \ 308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 311 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------