├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .settings.xml ├── .travis.yml ├── LICENSE ├── README.md ├── RELEASE.md ├── header.txt ├── mvnw ├── mvnw.cmd ├── pom.xml ├── src ├── main │ └── java │ │ └── io │ │ └── opentracing │ │ └── contrib │ │ └── grpc │ │ ├── ActiveSpanContextSource.java │ │ ├── ActiveSpanSource.java │ │ ├── ClientCloseDecorator.java │ │ ├── ClientSpanDecorator.java │ │ ├── GrpcFields.java │ │ ├── GrpcTags.java │ │ ├── OpenTracingContextKey.java │ │ ├── OperationNameConstructor.java │ │ ├── ServerCloseDecorator.java │ │ ├── ServerSpanDecorator.java │ │ ├── TracingClientInterceptor.java │ │ └── TracingServerInterceptor.java └── test │ ├── java │ └── io │ │ └── opentracing │ │ └── contrib │ │ └── grpc │ │ ├── ActiveSpanContextSourceTest.java │ │ ├── ActiveSpanSourceTest.java │ │ ├── GrpcTagsTest.java │ │ ├── OpenTracingContextKeyTest.java │ │ ├── SecondClientInterceptor.java │ │ ├── SecondServerInterceptor.java │ │ ├── TracedClient.java │ │ ├── TracedService.java │ │ ├── TracingClientInterceptorTest.java │ │ ├── TracingInterceptorsTest.java │ │ └── TracingServerInterceptorTest.java │ └── proto │ └── helloworld │ └── helloworld.proto └── travis └── publish.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | target/ 4 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentracing-contrib/java-grpc/cee8e27cc9ac0dd93e6f44979389fd2ecf7e3232/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip -------------------------------------------------------------------------------- /.settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | 22 | 23 | sonatype 24 | ${env.SONATYPE_USER} 25 | ${env.SONATYPE_PASSWORD} 26 | 27 | 28 | bintray 29 | ${env.BINTRAY_USER} 30 | ${env.BINTRAY_KEY} 31 | 32 | 33 | jfrog-snapshots 34 | ${env.BINTRAY_USER} 35 | ${env.BINTRAY_KEY} 36 | 37 | 38 | github.com 39 | ${env.GH_USER} 40 | ${env.GH_TOKEN} 41 | 42 | 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | language: java 5 | jdk: 6 | - oraclejdk8 7 | 8 | cache: 9 | directories: 10 | - $HOME/.m2/repository 11 | 12 | before_install: 13 | # allocate commits to CI, not the owner of the deploy key 14 | - git config user.name "opentracingci" 15 | - git config user.email "opentracingci+opentracing@googlegroups.com" 16 | 17 | # setup https authentication credentials, used by ./mvnw release:prepare 18 | - git config credential.helper "store --file=.git/credentials" 19 | - echo "https://$GH_TOKEN:@github.com" > .git/credentials 20 | 21 | install: 22 | # Override default travis to use the maven wrapper 23 | - ./mvnw install -DskipTests=true -Dmaven.javadoc.skip=true -B -V 24 | 25 | script: 26 | - ./travis/publish.sh 27 | 28 | branches: 29 | except: 30 | - /^[0-9]/ 31 | 32 | after_success: 33 | - mvn jacoco:report coveralls:report 34 | 35 | env: 36 | global: 37 | # Ex. travis encrypt BINTRAY_USER=your_github_account 38 | - secure: "HauoUZ79Jqp7BC6Irv1vEGCkzrI5+8zl8TG+i7FwlWAQG1es3YhXKSUeeWC8CkPWUZ5Yo1vqHULE+yUvYFGuVMBwPTkngbcb7Aq3mlM/Eutv1ziBH00arcKmcyc9Lm7TIaFbuCHT3JsugS9XlcO7C9olqGLOLwgmRo92H3kwRJLSdBK5xEUU0RtM26qT/FwRGMmz2reEX/uXRAMHbtBjVXoG3aNgSv60WbC2gnocrurWAAvWES2UVmFlrquF0+nI1hExIfeLjH8VtfV/6HLs7L2oXJmdHQpTJKeGlcg56u6zyRFu389ZkA/h6o7E5oNLkynRd/E+XacW1DoIUq2l5gTwWsdFsrhgOq2frpo1RbmvrBdlLsWCcFhv8F18aPlxpJNyt/pyflsCGVgQKKd9IsBVjQ4LkcgH5ZT21DNP8LFBac0zsuCD/rEBs0BStOhEGmUrLNCz+asJGRSBo6lfurVP9mx9Xkgx0HpTOba+94/H0wBpcaP0P9pS7h5Uwpv8tvrDiyud1MRP8/mwkBHzBPTUuAaYziz1AnNPT2t1+wAhgTkOgqIES8Kgf7I8Slg2A+7ZE2bAh4wvrCuh+yYaAtkulgGRmNUi1EkmzVw/GvXHVQHxhAThiN3L58OUr9IOeC//1XQsViPkUOiE2YFFwUXjPugJJnPwm8oZ1aKG62c=" 39 | # Ex. travis encrypt BINTRAY_KEY=xxx-https://bintray.com/profile/edit-xxx --add 40 | - secure: "MHwRodvL0NXCnROMBLcHCbw7Mk8HoXQlfEjTHubPlJu4qlGnu3k9CB25nM3mhqvuNj6ID9vetTftIeJKM6/YJIA6UDXxSEdVrnGiYCpsPXOXa5fyg87QsS7y45crNcKDf1G3uvUPVbckiBpQWm/mQxRtq+0PJqrBGeW87glDGehxLpydNmGRcJpI+6YVgz2IQvwZsT5GWADyu+bj3TbxLc9CzcrKev4FcZcMcbYTo1jiBth6x86SpXXIp1PnJafiNExO5V0U1E6lqihfe/uAnSqoF6swE9/yUi9KG3FTHZUfqa+O+cIJLqj2h7kSmtqmmHThrhuLS+7D5dM0CWmBkcoHb0zA0fPj0azdjbdlkOjnEiPjPT5K6cQ2DHrz0AGcdwkX90365vjRF7Rl7R/M2NzncZM/xisHVRJR71jowvc+SiQLi4b4Q+dIPrBxnUUU6BXzuKkbbTlHZJ5F0rTxlca1BymboSieuxpk88d3qqgvb772mAHv2/mXV8TOL7kX8yq+skDySQ9wwISFoK+22D5mH/32PoulwXydTLU5Zvj4xi3q+85Yl4dFHfqml+VVGXe02GaAqVHWAqmsb0q1TLu243dlrI2BzvLjCtKSjfPl3GVwXVxGV4JzpkDxA8JWGSCNbzBxd/QQjogQGuMlY3HUY9EvtAyVnpxcmxBOOAU=" 41 | # Ex. travis encrypt GH_TOKEN=XXX-https://github.com/settings/tokens-XXX --add 42 | - secure: "XiCJIT+r80fnhQq5sj2oRfnqET69Bh7DirWxyn9TTrE7OxKllamg83OuVGxlwlvtx++k+MG5eBuOPjLKTz9hx01uYuBksV8+3k2s8BLSGEhnektQseUsZjnwYZY52OjTHWbojdvExRhhiiiMaMzURafCbMMmjhdssqoSxMjfelZozoz+cRFqAu2YW5kiCYc7v8NJzdMzuJrmFNtpHzPZy/0rmjvr30walpTq+laDpdsXoXiB/qJ8ZsEMqycyYRfAgEY+YdI+YNE/zgH04jGLsP5LJd8S9RP67CoxZ3iYREqwNcJr3djUv8M3zHW+3qXW9KoNM7jn4jElHk+//49zF3lBo7XTRMjBO9DDpziMs+3AE4aKnO6jjb4OgMXAK8CDilm6unwXXmBYnayMotkDSAc8tbe0xVgOLFdjFn1KaFMbMpx11cen7N4fPdbw+D1vxMQMJ++iFcb8XW8Vsi4agrfmG4SnkE5oGCTAPEQThAi3ShDgCuFu0Ro72f9mgsVpsq5thGSFkAWwYgHECRJUzXGoW3R12KCXF+1+f+cAYtTnFEPGqaX/3eYdLb08lcjitJVi9o8YQPMM9tipVxHUU20KgRtXDOTtrpA0Ye/UI2rNdjissUyD5460hcV4gZDrzaeXHwp/UIcAUGFkcg0BwgOMjqyogbJw8HKgIsdZefo=" 43 | # Ex. travis encrypt SONATYPE_USER=your_sonatype_account 44 | - secure: "r59PmY+QkDbVTSNHyZqTpCR2rqznBMzeZZYDodsLiGWdCFpR61hTUL/vFbm0jRXTXplaqDMN6OLDdm2aGlYZS1PA9ckiDtXAuocF8edi8DyTdVitDAC9XVr/dseodYnyuuf3/JVgHIX4UNe6nus2Ih4Aoppd+hg/TLoAidsFE8j7WgM5IcrYZcKAvWCsDbp81vXktlH0zeZHsqPf7j7egpY2WHFpYTz6eD8HJMv03XerMga3ReXumbRuDLXRjeSOn56DhJ1lwFzrxi5II4fbvQ8wwIsK/qls5KAJCgvQSwn9zUZE9gIxMu5ILuVq9/KU88gAxQYCa7hVcxGtkbOMYiAWXufa3IXqDfcR1hL/yZFsqbYzugkh79TfK6Z+y+5Itt387foxGFGe9JV06OQN4EwnXdQljNsf9Fehr61SIzCyPqtXQqM99CbRIkJjsxZavUIDC8emJjfxYIQ5y8XNmMeYc1R5jGkXE+RZGdEfhYCLYXPZWgJp/Q+qSisc/xWLuwbyMyTj6vD/k+gpil7ml/Ez0O8yEJJpMS3JuVl4KdYtdDq2Uz8Jl2rOBbnZm8v8KJ2roHKR0d0jSyOFzVJh+GiJ62/nmGud8hde4WpoNwYryG0oFWYucNVz3u9mGCffz31FIcRKxZrXdN7mdeuKKyZIzkUrVGr033l5S9oj0yI=" 45 | # Ex. travis encrypt SONATYPE_PASSWORD=your_sonatype_password 46 | - secure: "doj9hlexXVm64UYu+Y2/la2bxBq81qXy22CK74kV/wtlageSfsADZpE4E0WY1AO0wQYH4UCgL8LHts6lx0F2YHugnBfXBDzQNZt3aoBkCf4YTaviYyrmoOgVJRFMMv4aTiyrrhlR1EpntR0MHNh/GkLv830P+2wRglEJR8ElnOwWU8OcvAXjjW3habpwalJyv8uNKtJ9JDi8wpXJYKyRH/dU3q//2zDOyEO2dGq1CRfOHTiWj+R6qt3ym95XxHK2ZGTrziTl86qG6zYGWftvjTF2Yhu4GhsiJ9rjaGoerXgcY/WGZAOeAUgjbNNGo8sRBMqCk0k9+miI8LReFJYC4Rdmx3kVF6hQccUwkkMYAKBqP53U6JGX0VUVc82QDPwFLfFUnvCzy9jKYx4DEdQiZsQvN4l6FKuFn7Psu9g/tcijWuIGQ4NBAThlvM5YTNapGuYIx9Nw28e9oKegVtwZCWCE8w6hKiFM8alePKySvuFuFVHXuvqPGzzJf84jEFbU1DRh1rN277imshPOYa8xiC7Am6Uq5h2k+Yep8p0sNwAMp4hwZsLteEvGS5h4TCOdOSz1yAdH4G0U4OrbNv2OaTYbz4xzm81HIhxzNMNQCm4qOeGbPseywOBRwYWWj9P/ePp6T8g1DJia8B31Y8ABvJlio/b0erQ8VR8DWOvAunY=" 47 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![Released Version][maven-img]][maven] [![Apache-2.0 license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 2 | 3 | # OpenTracing gRPC Instrumentation 4 | OpenTracing instrumentation for gRPC. 5 | 6 | ## Installation 7 | 8 | pom.xml 9 | ```xml 10 | 11 | io.opentracing.contrib 12 | opentracing-grpc 13 | VERSION 14 | 15 | ``` 16 | 17 | ## Usage 18 | 19 | ### Server 20 | 21 | - Instantiate tracer 22 | - Optionally register tracer with GlobalTracer: `GlobalTracer.register(tracer)` 23 | - Create a `TracingServerInterceptor` 24 | - Intercept a service 25 | 26 | ```java 27 | import io.opentracing.Tracer; 28 | 29 | public class YourServer { 30 | 31 | private int port; 32 | private Server server; 33 | private final Tracer tracer; 34 | 35 | private void start() throws IOException { 36 | TracingServerInterceptor tracingInterceptor = TracingServerInterceptor 37 | .newBuilder() 38 | .withTracer(this.tracer) 39 | .build(); 40 | 41 | // If GlobalTracer is used: 42 | TracingServerInterceptor 43 | 44 | server = ServerBuilder.forPort(port) 45 | .addService(tracingInterceptor.intercept(someServiceDef)) 46 | .build() 47 | .start(); 48 | } 49 | } 50 | ``` 51 | 52 | ### Client 53 | 54 | - Instantiate a tracer 55 | - Optionally register tracer with GlobalTracer: `GlobalTracer.register(tracer)` 56 | - Create a `TracingClientInterceptor` 57 | - Intercept the client channel 58 | 59 | ```java 60 | import io.opentracing.Tracer;import io.opentracing.contrib.grpc.TracingClientInterceptor; 61 | 62 | public class YourClient { 63 | 64 | private final ManagedChannel channel; 65 | private final GreeterGrpc.GreeterBlockingStub blockingStub; 66 | private final Tracer tracer; 67 | 68 | public YourClient(String host, int port) { 69 | 70 | channel = ManagedChannelBuilder.forAddress(host, port) 71 | .usePlaintext(true) 72 | .build(); 73 | 74 | TracingClientInterceptor tracingInterceptor = TracingClientInterceptor 75 | .newBuilder() 76 | .withTracer(this.tracer) 77 | .build(); 78 | 79 | // If GlobalTracer is used: 80 | TracingClientInterceptor 81 | 82 | blockingStub = GreeterGrpc.newBlockingStub(tracingInterceptor.intercept(channel)); 83 | } 84 | } 85 | ``` 86 | 87 | 88 | ## Server Tracing 89 | 90 | A `TracingServerInterceptor` uses default settings, which you can override by creating it using a `TracingServerInterceptor.Builder`. 91 | 92 | - `withOperationName(OperationNameConstructor constructor)`: Define how the operation name is constructed for all spans created for the intercepted service. Default sets the operation name as the name of the RPC method. More details in the `Operation Name` section. 93 | - `withStreaming()`: Logs to the server span whenever a message is received. *Note:* This package supports streaming but has not been rigorously tested. If you come across any issues, please let us know. 94 | - `withVerbosity()`: Logs to the server span additional events, such as message received, half close (client finished sending messages), and call complete. Default only logs if a call is cancelled. 95 | - `withTracedAttributes(ServerRequestAttribute... attrs)`: Sets tags on the server span in case you want to track information about the RPC call. See ServerRequestAttribute.java for a list of traceable request attributes. 96 | 97 | ### Example 98 | 99 | ```java 100 | TracingServerInterceptor tracingInterceptor = TracingServerInterceptor 101 | .newBuilder() 102 | .withTracer(tracer) 103 | .withStreaming() 104 | .withVerbosity() 105 | .withOperationName(new OperationNameConstructor() { 106 | @Override 107 | public String constructOperationName(MethodDescriptor method) { 108 | // construct some operation name from the method descriptor 109 | } 110 | }) 111 | .withTracedAttributes(ServerRequestAttribute.HEADERS, 112 | ServerRequestAttribute.METHOD_TYPE) 113 | .build(); 114 | ``` 115 | 116 | ## Client Tracing 117 | 118 | A `TracingClientInterceptor` also has default settings, which you can override by creating it using a `TracingClientInterceptor.Builder`. 119 | 120 | - `withOperationName(String operationName)`: Define how the operation name is constructed for all spans created for this intercepted client. Default is the name of the RPC method. More details in the `Operation Name` section. 121 | - `withActiveSpanSource(ActiveSpanSource activeSpanSource)`: Define how to extract the current active span, if any. More details in the `Active Span Sources` section. 122 | - `withActiveSpanContextSource(ActiveSpanContextSource activeSpanContextSource)`: Define how to extract the current active span context, if any. More details in the `Active Span Context Sources` section. 123 | - `withStreaming()`: Logs to the client span whenever a message is sent or a response is received. *Note:* This package supports streaming but has not been rigorously tested. If you come across any issues, please let us know. 124 | - `withVerbosity()`: Logs to the client span additional events, such as call started, message sent, half close (client finished sending messages), response received, and call complete. Default only logs if a call is cancelled. 125 | - `withTracedAttributes(ClientRequestAttribute... attrs)`: Sets tags on the client span in case you want to track information about the RPC call. See ClientRequestAttribute.java for a list of traceable request attributes. 126 | 127 | ### Example 128 | ```java 129 | import io.opentracing.Span; 130 | 131 | TracingClientInterceptor tracingInterceptor = TracingClientInterceptor 132 | .newBuilder() 133 | .withTracer(tracer) 134 | .withStreaming() 135 | .withVerbosity() 136 | .withOperationName(new OperationNameConstructor() { 137 | @Override 138 | public String constructOperationName(MethodDescriptor method) { 139 | // construct some operation name from the method descriptor 140 | } 141 | }) 142 | .withActiveSpanSource(new ActiveSpanSource() { 143 | @Override 144 | public Span getActiveSpan() { 145 | // implement how to get the current active span, for example: 146 | return OpenTracingContextKey.activeSpan(); 147 | } 148 | }) 149 | .withTracingAttributes(ClientRequestAttribute.ALL_CALL_OPTIONS, 150 | ClientRequestAttribute.HEADERS) 151 | .build(); 152 | ``` 153 | 154 | ## Current Span Context 155 | 156 | In your server request handler, you can access the current active span for that request by calling 157 | 158 | ```java 159 | Span span = OpenTracingContextKey.activeSpan(); 160 | ``` 161 | 162 | This is useful if you want to manually set tags on the span, log important events, or create a new child span for internal units of work. You can also use this key to wrap these internal units of work with a new context that has a user-defined active span. 163 | 164 | For example: 165 | ```java 166 | Tracer tracer = ...; 167 | 168 | // some unit of internal work that you want to trace 169 | Runnable internalWork = someInternalWork 170 | 171 | // a wrapper that traces the work of the runnable 172 | class TracedRunnable implements Runnable { 173 | Runnable work; 174 | Tracer tracer; 175 | 176 | TracedRunnable(Runnable work, Tracer tracer) { 177 | this.work = work; 178 | this.tracer = tracer; 179 | } 180 | 181 | public void run() { 182 | 183 | // create a child span for the current active span 184 | Span span = tracer 185 | .buildSpan("internal-work") 186 | .asChildOf(OpenTracingContextKey.activeSpan()) 187 | .start(); 188 | 189 | // create a new context with the child span as the active span 190 | Context contextWithNewSpan = Context.current() 191 | .withValue(OpenTracingContextKey.get(), span); 192 | 193 | // wrap the original work and run it 194 | Runnable tracedWork = contextWithNewSpan.wrap(this.work); 195 | tracedWork.run(); 196 | 197 | // make sure to finish any manually created spans! 198 | span.finish(); 199 | } 200 | } 201 | 202 | Runnable tracedInternalWork = new TracedRunnable(internalWork, tracer); 203 | tracedInternalWork.run(); 204 | ``` 205 | 206 | ## Operation Names 207 | 208 | The default operation name for any span is the RPC method name (`io.grpc.MethodDescriptor.getFullMethodName()`). However, you may want to add your own prefixes, alter the name, or define a new name. For examples of good operation names, check out the OpenTracing `semantics`. 209 | 210 | To alter the operation name, you need to add an implementation of the interface `OperationNameConstructor` to the `TracingClientInterceptor.Builder` or `TracingServerInterceptor.Builder`. For example, if you want to add a prefix to the default operation name of your ClientInterceptor, your code would look like this: 211 | 212 | ```java 213 | TracingClientInterceptor interceptor = TracingClientInterceptor.Builder ... 214 | .withOperationName(new OperationNameConstructor() { 215 | @Override 216 | public String constructOperationName(MethodDescriptor method) { 217 | return "your-prefix" + method.getFullMethodName(); 218 | } 219 | }) 220 | .with.... 221 | .build() 222 | ``` 223 | 224 | ## Active Span Sources 225 | 226 | If you want your client to continue a trace rather than starting a new one, then you can tell your `TracingClientInterceptor` how to extract the current active span by building it with your own implementation of the interface `ActiveSpanSource`. This interface has one method, `getActiveSpan`, in which you will define how to access the current active span. 227 | 228 | For example, if you're creating the client in an environment that has the active span stored in a global dictionary-style context under `OPENTRACING_SPAN_KEY`, then you could configure your Interceptor as follows: 229 | 230 | ```java 231 | import io.opentracing.Span; 232 | 233 | TracingClientInterceptor interceptor = TracingClientInterceptor 234 | .newBuilder().withTracer(tracer) 235 | ... 236 | .withActiveSpanSource(new ActiveSpanSource() { 237 | @Override 238 | public Span getActiveSpan() { 239 | return Context.get(OPENTRACING_SPAN_KEY); 240 | } 241 | }) 242 | ... 243 | .build(); 244 | ``` 245 | 246 | We also provide two built-in implementations: 247 | 248 | - `ActiveSpanSource.GRPC_CONTEXT` uses the current `io.grpc.Context` and returns the active span for `OpenTracingContextKey`. 249 | - `ActiveSpanSource.NONE` always returns null as the active span, which means the client will retrieve the span from `io.opentracing.Tracer.activeSpan()`. This is the default active span source. 250 | 251 | ## Active Span Context Sources 252 | 253 | Instead of `ActiveSpanSource` it's possible to use `ActiveSpanContextSource` if span is not available 254 | 255 | ```java 256 | import io.opentracing.Span; 257 | 258 | TracingClientInterceptor interceptor = TracingClientInterceptor 259 | .newBuilder().withTracer(tracer) 260 | ... 261 | .withActiveSpanContextSource(new ActiveSpanContextSource() { 262 | @Override 263 | public SpanContext getActiveSpanContext() { 264 | return Context.get(OPENTRACING_SPAN_CONTEXT_KEY); 265 | } 266 | }) 267 | ... 268 | .build(); 269 | ``` 270 | 271 | We also provide two built-in implementations: 272 | 273 | - `ActiveSpanContextSource.GRPC_CONTEXT` uses the current `io.grpc.Context` and returns the active span context for `OpenTracingContextKey`. 274 | - `ActiveSpanContextSource.NONE` always returns null as the active span context, which means the client will retrieve the span from `io.opentracing.Tracer.activeSpan().context()`. This is the default active span context source. 275 | 276 | ## Custom Span Decorators 277 | 278 | If you want to add custom tags or logs to the server and client spans, then you can implement the 279 | `ClientSpanDecorator`, `ClientCloseDecorator`, `ServerSpanDecorator`, and `ServerCloseDecorator` interfaces. 280 | Multiple different decorators may be added to the builder. 281 | 282 | ```java 283 | TracingClientInterceptor clientInterceptor = TracingClientInterceptor 284 | .newBuilder().withTracer(tracer) 285 | ... 286 | .withClientSpanDecorator(new ClientSpanDecorator() { 287 | @Override 288 | public void interceptCall(Span span, MethodDescriptor method, CallOptions callOptions) { 289 | span.setTag("some_tag", "some_value"); 290 | span.log("Example log"); 291 | } 292 | }) 293 | .withClientCloseDecorator(new ClientCloseDecorator() { 294 | @Override 295 | public void close(Span span, Status status, Metadata trailers) { 296 | span.setTag("some_other_tag", "some_other_value"); 297 | } 298 | }) 299 | ... 300 | .build(); 301 | 302 | TracingServerInterceptor serverInterceptor = TracingServerInterceptor 303 | .newBuilder().withTracer(tracer) 304 | ... 305 | .withServerSpanDecorator(new ServerSpanDecorator() { 306 | @Override 307 | public void interceptCall(Span span, ServerCall call, Metadata headers) { 308 | span.setTag("some_tag", "some_value"); 309 | span.log("Intercepting server call"); 310 | } 311 | }) 312 | .withServerCloseDecorator(new ServerCloseDecorator() { 313 | @Override 314 | public void close(Span span, Status status, Metadata trailers) { 315 | span.setTag("some_other_tag", "some_other_value"); 316 | } 317 | }) 318 | ... 319 | .build(); 320 | ``` 321 | 322 | ## Integrating with Other Interceptors 323 | Although we provide `TracingServerInterceptor.intercept(service)` and `TracingClientInterceptor.intercept(channel)` methods, you don't want to use these if you're chaining multiple interceptors. Instead, use the following code (preferably putting the tracing interceptor at the top of the interceptor stack so that it traces the entire request lifecycle, including other interceptors): 324 | 325 | ### Server 326 | ```java 327 | server = ServerBuilder.forPort(port) 328 | .addService(ServerInterceptors.intercept(service, someInterceptor, 329 | someOtherInterceptor, TracingServerInterceptor)) 330 | .build() 331 | .start(); 332 | ``` 333 | 334 | ### Client 335 | ```java 336 | blockingStub = GreeterGrpc.newBlockingStub(ClientInterceptors.intercept(channel, 337 | someInterceptor, someOtherInterceptor, TracingClientInterceptor)); 338 | ``` 339 | 340 | ## License 341 | 342 | [Apache 2.0 License](./LICENSE). 343 | 344 | [ci-img]: https://travis-ci.org/opentracing-contrib/java-grpc.svg?branch=master 345 | [ci]: https://travis-ci.org/opentracing-contrib/java-grpc 346 | [cov-img]: https://coveralls.io/repos/github/opentracing-contrib/java-grpc/badge.svg?branch=master 347 | [cov]: https://coveralls.io/github/opentracing-contrib/java-grpc?branch=master 348 | [maven-img]: https://img.shields.io/maven-central/v/io.opentracing.contrib/opentracing-grpc.svg 349 | [maven]: http://search.maven.org/#search%7Cga%7C1%7Copentracing-grpc 350 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # OpenTracing Release Process 2 | 3 | This repo uses semantic versions. Please keep this in mind when choosing version numbers. 4 | 5 | For the up-to-date release process, please refer the 6 | [release process from the OpenTracing Java API](https://github.com/opentracing/opentracing-java/blob/master/RELEASE.md). -------------------------------------------------------------------------------- /header.txt: -------------------------------------------------------------------------------- 1 | Copyright ${project.inceptionYear} The OpenTracing Authors 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | in compliance with the License. You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software distributed under the License 9 | is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | or implied. See the License for the specific language governing permissions and limitations under 11 | the License. 12 | -------------------------------------------------------------------------------- /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 | # Maven2 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*) 57 | darwin=true 58 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 59 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 60 | if [ -z "$JAVA_HOME" ]; then 61 | if [ -x "/usr/libexec/java_home" ]; then 62 | export JAVA_HOME="$(/usr/libexec/java_home)" 63 | else 64 | export JAVA_HOME="/Library/Java/Home" 65 | fi 66 | fi 67 | ;; 68 | esac 69 | 70 | if [ -z "$JAVA_HOME" ]; then 71 | if [ -r /etc/gentoo-release ]; then 72 | JAVA_HOME=$(java-config --jre-home) 73 | fi 74 | fi 75 | 76 | if [ -z "$M2_HOME" ]; then 77 | ## resolve links - $0 may be a link to maven's home 78 | PRG="$0" 79 | 80 | # need this for relative symlinks 81 | while [ -h "$PRG" ]; do 82 | ls=$(ls -ld "$PRG") 83 | link=$(expr "$ls" : '.*-> \(.*\)$') 84 | if expr "$link" : '/.*' >/dev/null; then 85 | PRG="$link" 86 | else 87 | PRG="$(dirname "$PRG")/$link" 88 | fi 89 | done 90 | 91 | saveddir=$(pwd) 92 | 93 | M2_HOME=$(dirname "$PRG")/.. 94 | 95 | # make it fully qualified 96 | M2_HOME=$(cd "$M2_HOME" && pwd) 97 | 98 | cd "$saveddir" 99 | # echo Using m2 at $M2_HOME 100 | fi 101 | 102 | # For Cygwin, ensure paths are in UNIX format before anything is touched 103 | if $cygwin; then 104 | [ -n "$M2_HOME" ] && 105 | M2_HOME=$(cygpath --unix "$M2_HOME") 106 | [ -n "$JAVA_HOME" ] && 107 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 108 | [ -n "$CLASSPATH" ] && 109 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 110 | fi 111 | 112 | # For Mingw, ensure paths are in UNIX format before anything is touched 113 | if $mingw; then 114 | [ -n "$M2_HOME" ] && 115 | M2_HOME="$( ( 116 | cd "$M2_HOME" 117 | pwd 118 | ))" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="$( ( 121 | cd "$JAVA_HOME" 122 | pwd 123 | ))" 124 | # TODO classpath? 125 | fi 126 | 127 | if [ -z "$JAVA_HOME" ]; then 128 | javaExecutable="$(which javac)" 129 | if [ -n "$javaExecutable" ] && ! [ "$(expr \"$javaExecutable\" : '\([^ ]*\)')" = "no" ]; then 130 | # readlink(1) is not available as standard on Solaris 10. 131 | readLink=$(which readlink) 132 | if [ ! $(expr "$readLink" : '\([^ ]*\)') = "no" ]; then 133 | if $darwin; then 134 | javaHome="$(dirname \"$javaExecutable\")" 135 | javaExecutable="$(cd \"$javaHome\" && pwd -P)/javac" 136 | else 137 | javaExecutable="$(readlink -f \"$javaExecutable\")" 138 | fi 139 | javaHome="$(dirname \"$javaExecutable\")" 140 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 141 | JAVA_HOME="$javaHome" 142 | export JAVA_HOME 143 | fi 144 | fi 145 | fi 146 | 147 | if [ -z "$JAVACMD" ]; then 148 | if [ -n "$JAVA_HOME" ]; then 149 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 150 | # IBM's JDK on AIX uses strange locations for the executables 151 | JAVACMD="$JAVA_HOME/jre/sh/java" 152 | else 153 | JAVACMD="$JAVA_HOME/bin/java" 154 | fi 155 | else 156 | JAVACMD="$(which java)" 157 | fi 158 | fi 159 | 160 | if [ ! -x "$JAVACMD" ]; then 161 | echo "Error: JAVA_HOME is not defined correctly." >&2 162 | echo " We cannot execute $JAVACMD" >&2 163 | exit 1 164 | fi 165 | 166 | if [ -z "$JAVA_HOME" ]; then 167 | echo "Warning: JAVA_HOME environment variable is not set." 168 | fi 169 | 170 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 171 | 172 | # traverses directory structure from process work directory to filesystem root 173 | # first directory with .mvn subdirectory is considered project base directory 174 | find_maven_basedir() { 175 | 176 | if [ -z "$1" ]; then 177 | echo "Path not specified to find_maven_basedir" 178 | return 1 179 | fi 180 | 181 | basedir="$1" 182 | wdir="$1" 183 | while [ "$wdir" != '/' ]; do 184 | if [ -d "$wdir"/.mvn ]; then 185 | basedir=$wdir 186 | break 187 | fi 188 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 189 | if [ -d "${wdir}" ]; then 190 | wdir=$( 191 | cd "$wdir/.." 192 | pwd 193 | ) 194 | fi 195 | # end of workaround 196 | done 197 | echo "${basedir}" 198 | } 199 | 200 | # concatenates all lines of a file 201 | concat_lines() { 202 | if [ -f "$1" ]; then 203 | echo "$(tr -s '\n' ' ' <"$1")" 204 | fi 205 | } 206 | 207 | BASE_DIR=$(find_maven_basedir "$(pwd)") 208 | if [ -z "$BASE_DIR" ]; then 209 | exit 1 210 | fi 211 | 212 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 213 | if [ "$MVNW_VERBOSE" = true ]; then 214 | echo $MAVEN_PROJECTBASEDIR 215 | fi 216 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 217 | 218 | # For Cygwin, switch paths to Windows format before running java 219 | if $cygwin; then 220 | [ -n "$M2_HOME" ] && 221 | M2_HOME=$(cygpath --path --windows "$M2_HOME") 222 | [ -n "$JAVA_HOME" ] && 223 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 224 | [ -n "$CLASSPATH" ] && 225 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 226 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 227 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 228 | fi 229 | 230 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 231 | 232 | exec "$JAVACMD" \ 233 | $MAVEN_OPTS \ 234 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 235 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 236 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 237 | -------------------------------------------------------------------------------- /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 Maven2 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 key stroke 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 my 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 | 121 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 123 | 124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 125 | if ERRORLEVEL 1 goto error 126 | goto end 127 | 128 | :error 129 | set ERROR_CODE=1 130 | 131 | :end 132 | @endlocal & set ERROR_CODE=%ERROR_CODE% 133 | 134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 138 | :skipRcPost 139 | 140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 142 | 143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 144 | 145 | exit /B %ERROR_CODE% 146 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 4.0.0 19 | io.opentracing.contrib 20 | opentracing-grpc 21 | 0.2.4-SNAPSHOT 22 | 23 | ${project.groupId}:${project.artifactId} 24 | OpenTracing Instrumentation for gRPC 25 | https://github.com/opentracing-contrib/java-grpc 26 | 2017-2020 27 | 28 | 29 | http://github.com/opentracing-contrib/java-grpc 30 | scm:git:https://github.com/opentracing-contrib/java-grpc.git 31 | scm:git:https://github.com/opentracing-contrib/java-grpc.git 32 | 33 | HEAD 34 | 35 | 36 | 37 | 38 | The Apache Software License, Version 2.0 39 | http://www.apache.org/licenses/LICENSE-2.0.txt 40 | repo 41 | 42 | 43 | 44 | 45 | 46 | malafeev 47 | Sergei Malafeev 48 | sergeymalafeev@gmail.com 49 | 50 | 51 | 52 | 53 | GitHub 54 | https://github.com/opentracing-contrib/java-grpc/issues 55 | 56 | 57 | 58 | 1.7 59 | UTF-8 60 | UTF-8 61 | 62 | 1.27.1 63 | 3.11.0 64 | 0.33.0 65 | 4.3.0 66 | 0.8.4 67 | 68 | 69 | 70 | 71 | io.opentracing 72 | opentracing-api 73 | ${opentracing.version} 74 | 75 | 76 | 77 | io.opentracing 78 | opentracing-util 79 | ${opentracing.version} 80 | 81 | 82 | 83 | io.opentracing 84 | opentracing-mock 85 | ${opentracing.version} 86 | test 87 | 88 | 89 | 90 | io.opentracing 91 | opentracing-util 92 | ${opentracing.version} 93 | test-jar 94 | test 95 | 96 | 97 | 98 | io.grpc 99 | grpc-core 100 | ${grpc.version} 101 | provided 102 | 103 | 104 | 105 | io.grpc 106 | grpc-stub 107 | ${grpc.version} 108 | test 109 | 110 | 111 | 112 | io.grpc 113 | grpc-protobuf 114 | ${grpc.version} 115 | test 116 | 117 | 118 | 119 | io.grpc 120 | grpc-netty 121 | ${grpc.version} 122 | test 123 | 124 | 125 | 126 | io.grpc 127 | grpc-testing 128 | ${grpc.version} 129 | test 130 | 131 | 132 | 133 | junit 134 | junit 135 | 4.13.1 136 | test 137 | 138 | 139 | 140 | org.assertj 141 | assertj-core 142 | 3.13.2 143 | test 144 | 145 | 146 | 147 | org.awaitility 148 | awaitility 149 | 3.1.6 150 | test 151 | 152 | 153 | 154 | org.mockito 155 | mockito-core 156 | 3.0.0 157 | test 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | kr.motd.maven 166 | os-maven-plugin 167 | 1.6.2 168 | 169 | 170 | 171 | 172 | org.apache.maven.plugins 173 | maven-compiler-plugin 174 | 3.8.1 175 | 176 | ${java.version} 177 | ${java.version} 178 | 179 | 180 | 181 | maven-release-plugin 182 | 2.5.3 183 | 184 | false 185 | release 186 | true 187 | @{project.version} 188 | 189 | 190 | 191 | org.apache.maven.plugins 192 | maven-surefire-plugin 193 | 2.22.2 194 | 195 | false 196 | 1 197 | 198 | 199 | 200 | com.mycila 201 | license-maven-plugin 202 | 3.0 203 | 204 | 205 | SLASHSTAR_STYLE 206 | 207 |
header.txt
208 | true 209 | 210 | LICENSE 211 | mvnw 212 | mvnw.cmd 213 | .mvn/wrapper/maven-wrapper.properties 214 | .coveralls.yml 215 | src/test/proto/helloworld/helloworld.proto 216 | 217 |
218 | 219 | 220 | 221 | check 222 | 223 | compile 224 | 225 | 226 |
227 | 228 | org.eluder.coveralls 229 | coveralls-maven-plugin 230 | ${coveralls-maven-plugin.version} 231 | 232 | 233 | org.jacoco 234 | jacoco-maven-plugin 235 | ${jacoco-maven-plugin.version} 236 | 237 | 238 | prepare-agent 239 | 240 | prepare-agent 241 | 242 | 243 | 244 | 245 | 246 | org.xolstice.maven.plugins 247 | protobuf-maven-plugin 248 | 0.6.1 249 | 250 | 251 | com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} 252 | 253 | grpc-java 254 | 255 | io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} 256 | 257 | 258 | 259 | 260 | proto-test-compile 261 | generate-test-sources 262 | 263 | test-compile 264 | test-compile-custom 265 | 266 | 267 | 268 | 269 |
270 |
271 | 272 | 273 | 274 | bintray 275 | https://api.bintray.com/maven/opentracing/maven/opentracing-grpc/;publish=1 276 | 277 | 278 | jfrog-snapshots 279 | http://oss.jfrog.org/artifactory/oss-snapshot-local 280 | 281 | 282 | 283 | 284 | 285 | release 286 | 287 | 288 | 289 | 290 | org.apache.maven.plugins 291 | maven-source-plugin 292 | 3.1.0 293 | 294 | 295 | attach-sources 296 | 297 | jar 298 | 299 | 300 | 301 | 302 | 303 | 304 | org.apache.maven.plugins 305 | maven-javadoc-plugin 306 | 3.1.1 307 | 308 | false 309 | 310 | 311 | 312 | attach-javadocs 313 | 314 | jar 315 | 316 | package 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 |
326 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/grpc/ActiveSpanContextSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import io.opentracing.SpanContext; 18 | 19 | /** 20 | * An interface that defines how to get the current active span context. 21 | */ 22 | public interface ActiveSpanContextSource { 23 | 24 | /** 25 | * ActiveSpanContextSource implementation that always returns null as the active span context. 26 | */ 27 | ActiveSpanContextSource NONE = 28 | new ActiveSpanContextSource() { 29 | @Override 30 | public SpanContext getActiveSpanContext() { 31 | return null; 32 | } 33 | }; 34 | 35 | /** 36 | * ActiveSpanContextSource implementation that returns the current span context stored in the GRPC 37 | * context under {@link OpenTracingContextKey}. 38 | */ 39 | ActiveSpanContextSource GRPC_CONTEXT = 40 | new ActiveSpanContextSource() { 41 | @Override 42 | public SpanContext getActiveSpanContext() { 43 | return OpenTracingContextKey.activeSpanContext(); 44 | } 45 | }; 46 | 47 | /** 48 | * Retrieves the active {@link SpanContext}. 49 | * 50 | * @return the active span context 51 | */ 52 | SpanContext getActiveSpanContext(); 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/grpc/ActiveSpanSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import io.opentracing.Span; 18 | 19 | /** 20 | * An interface that defines how to get the current active span. 21 | */ 22 | public interface ActiveSpanSource { 23 | 24 | /** 25 | * ActiveSpanSource implementation that always returns null as the active span. 26 | */ 27 | ActiveSpanSource NONE = 28 | new ActiveSpanSource() { 29 | @Override 30 | public Span getActiveSpan() { 31 | return null; 32 | } 33 | }; 34 | 35 | /** 36 | * ActiveSpanSource implementation that returns the current span stored in the GRPC context under 37 | * {@link OpenTracingContextKey}. 38 | */ 39 | ActiveSpanSource GRPC_CONTEXT = 40 | new ActiveSpanSource() { 41 | @Override 42 | public Span getActiveSpan() { 43 | return OpenTracingContextKey.activeSpan(); 44 | } 45 | }; 46 | 47 | /** 48 | * Retieves the active {@link Span}. 49 | * 50 | * @return the active span 51 | */ 52 | Span getActiveSpan(); 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/grpc/ClientCloseDecorator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import io.grpc.ForwardingClientCallListener; 18 | import io.grpc.Metadata; 19 | import io.grpc.Status; 20 | import io.opentracing.Span; 21 | 22 | public interface ClientCloseDecorator { 23 | 24 | /** 25 | * The method of the implementation is executed inside {@link ForwardingClientCallListener#onClose}. 26 | * 27 | * @param span The span created by {@link TracingClientInterceptor} 28 | * @param status The status passed to {@link ForwardingClientCallListener#onClose}. 29 | * @param trailers The trailing headers passed to {@link ForwardingClientCallListener#onClose}. 30 | */ 31 | void close(Span span, Status status, Metadata trailers); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/grpc/ClientSpanDecorator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import io.grpc.CallOptions; 18 | import io.grpc.MethodDescriptor; 19 | import io.grpc.ServerCall; 20 | import io.opentracing.Span; 21 | 22 | /** 23 | * An interface for adding custom span tags to the spans created by {@link 24 | * TracingClientInterceptor}. 25 | */ 26 | public interface ClientSpanDecorator { 27 | 28 | /** 29 | * The method of the implementation is executed inside {@link TracingClientInterceptor#interceptCall}. 30 | * 31 | * @param span The span created by {@link TracingClientInterceptor} 32 | * @param callOptions The {@link ServerCall} parameter of {@link TracingClientInterceptor#interceptCall} 33 | * @param method The {@link MethodDescriptor} parameter of {@link TracingClientInterceptor#interceptCall} 34 | */ 35 | void interceptCall(Span span, MethodDescriptor method, CallOptions callOptions); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/grpc/GrpcFields.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import com.google.common.collect.ImmutableMap; 18 | import io.opentracing.Span; 19 | import io.opentracing.log.Fields; 20 | 21 | final class GrpcFields { 22 | 23 | static final String ERROR = "error"; 24 | static final String HEADERS = "headers"; 25 | 26 | static final String CLIENT_CALL_START = "client-call-start"; 27 | static final String CLIENT_CALL_CANCEL = "client-call-cancel"; 28 | static final String CLIENT_CALL_HALF_CLOSE = "client-call-half-close"; 29 | static final String CLIENT_CALL_SEND_MESSAGE = "client-call-send-message"; 30 | 31 | static final String CLIENT_CALL_LISTENER_ON_HEADERS = "client-call-listener-on-headers"; 32 | static final String CLIENT_CALL_LISTENER_ON_MESSAGE = "client-call-listener-on-message"; 33 | static final String CLIENT_CALL_LISTENER_ON_CLOSE = "client-call-listener-on-close"; 34 | 35 | static final String SERVER_CALL_SEND_HEADERS = "server-call-send-headers"; 36 | static final String SERVER_CALL_SEND_MESSAGE = "server-call-send-message"; 37 | static final String SERVER_CALL_CLOSE = "server-call-close"; 38 | 39 | static final String SERVER_CALL_LISTENER_ON_MESSAGE = "server-call-listener-on-message"; 40 | static final String SERVER_CALL_LISTENER_ON_HALF_CLOSE = "server-call-listener-on-half-close"; 41 | static final String SERVER_CALL_LISTENER_ON_CANCEL = "server-call-listener-on-cancel"; 42 | static final String SERVER_CALL_LISTENER_ON_COMPLETE = "server-call-listener-on-complete"; 43 | 44 | static void logClientCallError(Span span, String message, Throwable cause) { 45 | logCallError(span, message, cause, "Client"); 46 | } 47 | 48 | static void logServerCallError(Span span, String message, Throwable cause) { 49 | logCallError(span, message, cause, "Server"); 50 | } 51 | 52 | private static void logCallError(Span span, String message, Throwable cause, String name) { 53 | ImmutableMap.Builder builder = 54 | ImmutableMap.builder().put(Fields.EVENT, GrpcFields.ERROR); 55 | String causeMessage = null; 56 | if (cause != null) { 57 | builder.put(Fields.ERROR_OBJECT, cause); 58 | causeMessage = cause.getMessage(); 59 | } 60 | if (message != null) { 61 | builder.put(Fields.MESSAGE, message); 62 | } else if (causeMessage != null) { 63 | builder.put(Fields.MESSAGE, causeMessage); 64 | } else { 65 | builder.put(Fields.MESSAGE, name + " call failed"); 66 | } 67 | span.log(builder.build()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/grpc/GrpcTags.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import io.grpc.Attributes; 18 | import io.grpc.CallOptions; 19 | import io.grpc.Deadline; 20 | import io.grpc.Grpc; 21 | import io.grpc.Metadata; 22 | import io.grpc.MethodDescriptor; 23 | import io.grpc.Status; 24 | import io.grpc.inprocess.InProcessSocketAddress; 25 | import io.opentracing.Span; 26 | import io.opentracing.tag.AbstractTag; 27 | import io.opentracing.tag.Tag; 28 | import io.opentracing.tag.Tags; 29 | import java.net.InetSocketAddress; 30 | import java.net.SocketAddress; 31 | import java.util.concurrent.TimeUnit; 32 | 33 | /** 34 | * Package private utility methods for common gRPC tags. 35 | */ 36 | final class GrpcTags { 37 | 38 | /** 39 | * grpc.authority tag. 40 | */ 41 | static final NullableTag GRPC_AUTHORITY = new NullableTag<>("grpc.authority"); 42 | 43 | /** 44 | * grpc.call_attributes tag. 45 | */ 46 | static final NullableTag GRPC_CALL_ATTRIBUTES = 47 | new NullableTag<>("grpc.call_attributes"); 48 | 49 | /** 50 | * grpc.call_options tag. 51 | */ 52 | static final NullableTag GRPC_CALL_OPTIONS = new NullableTag<>("grpc.call_options"); 53 | 54 | /** 55 | * grpc.compressor tag. 56 | */ 57 | static final NullableTag GRPC_COMPRESSOR = new NullableTag<>("grpc.compressor"); 58 | 59 | /** 60 | * grpc.deadline_millis tag. 61 | */ 62 | static final Tag GRPC_DEADLINE = 63 | new AbstractTag("grpc.deadline_millis") { 64 | @Override 65 | public void set(Span span, Deadline deadline) { 66 | if (deadline != null) { 67 | span.setTag(super.key, String.valueOf(deadline.timeRemaining(TimeUnit.MILLISECONDS))); 68 | } 69 | } 70 | }; 71 | 72 | /** 73 | * grpc.headers tag. 74 | */ 75 | static final NullableTag GRPC_HEADERS = new NullableTag<>("grpc.headers"); 76 | 77 | /** 78 | * grpc.method_name tag. 79 | */ 80 | static final Tag GRPC_METHOD_NAME = 81 | new AbstractTag("grpc.method_name") { 82 | @Override 83 | public void set(Span span, MethodDescriptor method) { 84 | if (method != null) { 85 | span.setTag(super.key, method.getFullMethodName()); 86 | } 87 | } 88 | }; 89 | 90 | /** 91 | * grpc.method_type tag. 92 | */ 93 | static final Tag GRPC_METHOD_TYPE = 94 | new AbstractTag("grpc.method_type") { 95 | @Override 96 | public void set(Span span, MethodDescriptor method) { 97 | if (method != null) { 98 | span.setTag(super.key, method.getType().toString()); 99 | } 100 | } 101 | }; 102 | 103 | /** 104 | * grpc.status tag. 105 | */ 106 | static final Tag GRPC_STATUS = 107 | new AbstractTag("grpc.status") { 108 | @Override 109 | public void set(Span span, Status status) { 110 | if (status != null) { 111 | span.setTag(super.key, status.getCode().name()); 112 | } 113 | } 114 | }; 115 | 116 | /** 117 | * peer.address tag. 118 | */ 119 | static final Tag PEER_ADDRESS = 120 | new AbstractTag("peer.address") { 121 | @Override 122 | public void set(Span span, Attributes attributes) { 123 | SocketAddress address = attributes.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR); 124 | if (address instanceof InProcessSocketAddress) { 125 | span.setTag(super.key, ((InProcessSocketAddress) address).getName()); 126 | } else if (address instanceof InetSocketAddress) { 127 | final InetSocketAddress inetAddress = (InetSocketAddress) address; 128 | span.setTag(super.key, inetAddress.getHostString() + ':' + inetAddress.getPort()); 129 | } 130 | } 131 | }; 132 | 133 | /** 134 | * Value for {@link Tags#COMPONENT} for gRPC. 135 | */ 136 | static final String COMPONENT_NAME = "java-grpc"; 137 | 138 | static class NullableTag extends AbstractTag { 139 | 140 | NullableTag(String tagKey) { 141 | super(tagKey); 142 | } 143 | 144 | @Override 145 | public void set(Span span, T tagValue) { 146 | if (tagValue != null) { 147 | span.setTag(super.key, tagValue.toString()); 148 | } 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/grpc/OpenTracingContextKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import io.grpc.Context; 18 | import io.opentracing.Span; 19 | import io.opentracing.SpanContext; 20 | 21 | /** 22 | * A {@link io.grpc.Context} key for the current OpenTracing trace state. 23 | * 24 | *

Can be used to get the active span, or to set the active span for a scoped unit of work. See 25 | * the grpc-java OpenTracing docs for use cases and 26 | * examples. 27 | */ 28 | public class OpenTracingContextKey { 29 | 30 | public static final String KEY_NAME = "io.opentracing.active-span"; 31 | public static final String KEY_CONTEXT_NAME = "io.opentracing.active-span-context"; 32 | private static final Context.Key key = Context.key(KEY_NAME); 33 | private static final Context.Key keyContext = Context.key(KEY_CONTEXT_NAME); 34 | 35 | /** 36 | * Retrieves the active span. 37 | * 38 | * @return the active span for the current request 39 | */ 40 | public static Span activeSpan() { 41 | return key.get(); 42 | } 43 | 44 | /** 45 | * Retrieves the span key. 46 | * 47 | * @return the OpenTracing context key 48 | */ 49 | public static Context.Key getKey() { 50 | return key; 51 | } 52 | 53 | /** 54 | * Retrieves the span context key. 55 | * 56 | * @return the OpenTracing context key for span context 57 | */ 58 | public static Context.Key getSpanContextKey() { 59 | return keyContext; 60 | } 61 | 62 | public static SpanContext activeSpanContext() { 63 | return keyContext.get(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/grpc/OperationNameConstructor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import io.grpc.MethodDescriptor; 18 | 19 | /** 20 | * Interface that allows span operation names to be constructed from an RPC's method descriptor. 21 | */ 22 | public interface OperationNameConstructor { 23 | 24 | /** 25 | * Default span operation name constructor, that will return an RPC's method name when 26 | * constructOperationName is called. 27 | */ 28 | OperationNameConstructor DEFAULT = 29 | new OperationNameConstructor() { 30 | @Override 31 | public String constructOperationName(MethodDescriptor method) { 32 | return method.getFullMethodName(); 33 | } 34 | }; 35 | 36 | /** 37 | * Constructs a span's operation name from the RPC's method. 38 | * 39 | * @param method the rpc method to extract a name from 40 | * @param the rpc request type 41 | * @param the rpc response type 42 | * @return the operation name 43 | */ 44 | String constructOperationName(MethodDescriptor method); 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/grpc/ServerCloseDecorator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import io.grpc.ForwardingServerCall; 18 | import io.grpc.Metadata; 19 | import io.grpc.ServerCall; 20 | import io.grpc.Status; 21 | import io.opentracing.Span; 22 | 23 | /** 24 | * An interface for adding custom span tags to the spans created by {@link TracingServerInterceptor} 25 | * when the gRPC call is closed. 26 | */ 27 | public interface ServerCloseDecorator { 28 | 29 | /** 30 | * The method of the implementation is executed inside {@link ForwardingServerCall#close(Status, 31 | * Metadata)}. 32 | * 33 | * @param span The span created by {@link TracingServerInterceptor} 34 | * @param status The status passed to {@link ServerCall#close(Status, Metadata)}. 35 | * @param trailers The trailing headers passed to {@link ServerCall#close(Status, Metadata)} 36 | */ 37 | void close(Span span, Status status, Metadata trailers); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/grpc/ServerSpanDecorator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import io.grpc.Metadata; 18 | import io.grpc.ServerCall; 19 | import io.opentracing.Span; 20 | 21 | /** 22 | * An interface for adding custom span tags to the spans created by {@link 23 | * TracingServerInterceptor}. 24 | */ 25 | public interface ServerSpanDecorator { 26 | 27 | /** 28 | * The method of the implementation is executed inside {@link TracingServerInterceptor#interceptCall}. 29 | * 30 | * @param span The span created by {@link TracingServerInterceptor} 31 | * @param call The {@link ServerCall} parameter of {@link TracingServerInterceptor#interceptCall} 32 | * @param headers The {@link Metadata} parameter of {@link TracingServerInterceptor#interceptCall} 33 | */ 34 | void interceptCall(Span span, ServerCall call, Metadata headers); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/grpc/TracingClientInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import com.google.common.base.MoreObjects; 18 | import com.google.common.collect.ImmutableList; 19 | import com.google.common.collect.ImmutableMap; 20 | import io.grpc.CallOptions; 21 | import io.grpc.Channel; 22 | import io.grpc.ClientCall; 23 | import io.grpc.ClientInterceptor; 24 | import io.grpc.ClientInterceptors; 25 | import io.grpc.ForwardingClientCall; 26 | import io.grpc.ForwardingClientCallListener; 27 | import io.grpc.Metadata; 28 | import io.grpc.MethodDescriptor; 29 | import io.grpc.Status; 30 | import io.opentracing.Scope; 31 | import io.opentracing.Span; 32 | import io.opentracing.SpanContext; 33 | import io.opentracing.Tracer; 34 | import io.opentracing.log.Fields; 35 | import io.opentracing.propagation.Format; 36 | import io.opentracing.propagation.TextMap; 37 | import io.opentracing.tag.Tags; 38 | import io.opentracing.util.GlobalTracer; 39 | import java.util.Arrays; 40 | import java.util.HashMap; 41 | import java.util.HashSet; 42 | import java.util.Iterator; 43 | import java.util.Map; 44 | import java.util.Map.Entry; 45 | import java.util.Set; 46 | import java.util.concurrent.atomic.AtomicBoolean; 47 | import javax.annotation.Nullable; 48 | 49 | /** 50 | * An interceptor that applies tracing via OpenTracing to all client requests. 51 | */ 52 | public class TracingClientInterceptor implements ClientInterceptor { 53 | 54 | private final Tracer tracer; 55 | private final OperationNameConstructor operationNameConstructor; 56 | private final boolean streaming; 57 | private final boolean verbose; 58 | private final Set tracedAttributes; 59 | private final ActiveSpanSource activeSpanSource; 60 | private final ActiveSpanContextSource activeSpanContextSource; 61 | private final ImmutableList clientSpanDecorators; 62 | private final ImmutableList clientCloseDecorators; 63 | 64 | private TracingClientInterceptor(Builder builder) { 65 | this.tracer = builder.tracer; 66 | this.operationNameConstructor = builder.operationNameConstructor; 67 | this.streaming = builder.streaming; 68 | this.verbose = builder.verbose; 69 | this.tracedAttributes = builder.tracedAttributes; 70 | this.activeSpanSource = builder.activeSpanSource; 71 | this.activeSpanContextSource = builder.activeSpanContextSource; 72 | this.clientSpanDecorators = ImmutableList.copyOf(builder.clientSpanDecorators.values()); 73 | this.clientCloseDecorators = ImmutableList.copyOf(builder.clientCloseDecorators.values()); 74 | } 75 | 76 | /** 77 | * Creates a new {@link TracingClientInterceptor.Builder}. 78 | * 79 | * @return the tracing client interceptor builder 80 | */ 81 | public static Builder newBuilder() { 82 | return new Builder(); 83 | } 84 | 85 | /** 86 | * Use this interceptor to trace all requests made by this client channel. 87 | * 88 | * @param channel to be traced 89 | * @return intercepted channel 90 | */ 91 | public Channel intercept(Channel channel) { 92 | return ClientInterceptors.intercept(channel, this); 93 | } 94 | 95 | @Override 96 | public ClientCall interceptCall( 97 | MethodDescriptor method, CallOptions callOptions, Channel next) { 98 | 99 | final String operationName = operationNameConstructor.constructOperationName(method); 100 | 101 | SpanContext activeSpanContext = getActiveSpanContext(); 102 | final Span span = createSpanFromParent(activeSpanContext, operationName); 103 | 104 | try (Scope ignored = tracer.scopeManager().activate(span)) { 105 | 106 | for (ClientSpanDecorator clientSpanDecorator : clientSpanDecorators) { 107 | clientSpanDecorator.interceptCall(span, method, callOptions); 108 | } 109 | 110 | for (ClientRequestAttribute attr : this.tracedAttributes) { 111 | switch (attr) { 112 | case ALL_CALL_OPTIONS: 113 | GrpcTags.GRPC_CALL_OPTIONS.set(span, callOptions); 114 | break; 115 | case AUTHORITY: 116 | GrpcTags.GRPC_AUTHORITY.set( 117 | span, MoreObjects.firstNonNull(callOptions.getAuthority(), next.authority())); 118 | break; 119 | case COMPRESSOR: 120 | GrpcTags.GRPC_COMPRESSOR.set(span, callOptions.getCompressor()); 121 | break; 122 | case DEADLINE: 123 | GrpcTags.GRPC_DEADLINE.set(span, callOptions.getDeadline()); 124 | break; 125 | case METHOD_NAME: 126 | GrpcTags.GRPC_METHOD_NAME.set(span, method); 127 | break; 128 | case METHOD_TYPE: 129 | GrpcTags.GRPC_METHOD_TYPE.set(span, method); 130 | break; 131 | case HEADERS: 132 | break; 133 | default: 134 | // noop 135 | } 136 | } 137 | 138 | return new ForwardingClientCall.SimpleForwardingClientCall( 139 | next.newCall(method, callOptions)) { 140 | 141 | private AtomicBoolean finished = new AtomicBoolean(false); 142 | 143 | @Override 144 | public void start(Listener responseListener, final Metadata headers) { 145 | if (verbose) { 146 | span.log( 147 | ImmutableMap.builder() 148 | .put(Fields.EVENT, GrpcFields.CLIENT_CALL_START) 149 | .put(Fields.MESSAGE, "Client call started") 150 | .build()); 151 | } 152 | if (tracedAttributes.contains(ClientRequestAttribute.HEADERS)) { 153 | GrpcTags.GRPC_HEADERS.set(span, headers); 154 | } 155 | 156 | tracer.inject( 157 | span.context(), 158 | Format.Builtin.HTTP_HEADERS, 159 | new TextMap() { 160 | @Override 161 | public void put(String key, String value) { 162 | Metadata.Key headerKey = 163 | Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); 164 | headers.put(headerKey, value); 165 | } 166 | 167 | @Override 168 | public Iterator> iterator() { 169 | throw new UnsupportedOperationException( 170 | "TextMapInjectAdapter should only be used with Tracer.inject()"); 171 | } 172 | }); 173 | 174 | Listener tracingResponseListener = 175 | new ForwardingClientCallListener.SimpleForwardingClientCallListener( 176 | responseListener) { 177 | 178 | @Override 179 | public void onHeaders(Metadata headers) { 180 | if (verbose) { 181 | span.log( 182 | ImmutableMap.builder() 183 | .put(Fields.EVENT, GrpcFields.CLIENT_CALL_LISTENER_ON_HEADERS) 184 | .put(Fields.MESSAGE, "Client received response headers") 185 | .put(GrpcFields.HEADERS, headers.toString()) 186 | .build()); 187 | } 188 | super.onHeaders(headers); 189 | } 190 | 191 | @Override 192 | public void onMessage(RespT message) { 193 | if (streaming || verbose) { 194 | span.log( 195 | ImmutableMap.builder() 196 | .put(Fields.EVENT, GrpcFields.CLIENT_CALL_LISTENER_ON_MESSAGE) 197 | .put(Fields.MESSAGE, "Client received response message") 198 | .build()); 199 | } 200 | 201 | try (Scope ignored = tracer.scopeManager().activate(span)) { 202 | super.onMessage(message); 203 | } 204 | } 205 | 206 | @Override 207 | public void onClose(Status status, Metadata trailers) { 208 | if (!finished.compareAndSet(false, true)) { 209 | super.onClose(status, trailers); 210 | return; 211 | } 212 | 213 | if (verbose) { 214 | span.log( 215 | ImmutableMap.builder() 216 | .put(Fields.EVENT, GrpcFields.CLIENT_CALL_LISTENER_ON_CLOSE) 217 | .put(Fields.MESSAGE, "Client call closed") 218 | .build()); 219 | if (!status.isOk()) { 220 | GrpcFields.logClientCallError( 221 | span, status.getDescription(), status.getCause()); 222 | } 223 | } 224 | GrpcTags.GRPC_STATUS.set(span, status); 225 | for (ClientCloseDecorator clientCloseDecorator : clientCloseDecorators) { 226 | clientCloseDecorator.close(span, status, trailers); 227 | } 228 | super.onClose(status, trailers); 229 | span.finish(); 230 | } 231 | }; 232 | 233 | try (Scope ignored = tracer.scopeManager().activate(span)) { 234 | super.start(tracingResponseListener, headers); 235 | } 236 | } 237 | 238 | @Override 239 | public void sendMessage(ReqT message) { 240 | if (streaming || verbose) { 241 | span.log( 242 | ImmutableMap.builder() 243 | .put(Fields.EVENT, GrpcFields.CLIENT_CALL_SEND_MESSAGE) 244 | .put(Fields.MESSAGE, "Client sent message") 245 | .build()); 246 | } 247 | try (Scope ignored = tracer.scopeManager().activate(span)) { 248 | super.sendMessage(message); 249 | } 250 | } 251 | 252 | @Override 253 | public void halfClose() { 254 | if (streaming || verbose) { 255 | span.log( 256 | ImmutableMap.builder() 257 | .put(Fields.EVENT, GrpcFields.CLIENT_CALL_HALF_CLOSE) 258 | .put(Fields.MESSAGE, "Client sent all messages") 259 | .build()); 260 | } 261 | try (Scope ignored = tracer.scopeManager().activate(span)) { 262 | super.halfClose(); 263 | } 264 | } 265 | 266 | @Override 267 | public void cancel(@Nullable String message, @Nullable Throwable cause) { 268 | if (!finished.compareAndSet(false, true)) { 269 | super.cancel(message, cause); 270 | return; 271 | } 272 | 273 | if (verbose) { 274 | span.log( 275 | ImmutableMap.builder() 276 | .put(Fields.EVENT, GrpcFields.CLIENT_CALL_CANCEL) 277 | .put(Fields.MESSAGE, "Client call canceled") 278 | .build()); 279 | GrpcFields.logClientCallError(span, message, cause); 280 | } 281 | Status status = cause == null ? Status.UNKNOWN : Status.fromThrowable(cause); 282 | GrpcTags.GRPC_STATUS.set(span, status.withDescription(message)); 283 | try (Scope ignored = tracer.scopeManager().activate(span)) { 284 | super.cancel(message, cause); 285 | } finally { 286 | span.finish(); 287 | } 288 | } 289 | }; 290 | } 291 | } 292 | 293 | private SpanContext getActiveSpanContext() { 294 | if (activeSpanSource != null) { 295 | Span activeSpan = activeSpanSource.getActiveSpan(); 296 | if (activeSpan != null) { 297 | return activeSpan.context(); 298 | } 299 | } 300 | if (activeSpanContextSource != null) { 301 | final SpanContext spanContext = activeSpanContextSource.getActiveSpanContext(); 302 | if (spanContext != null) { 303 | return spanContext; 304 | } 305 | } 306 | if (tracer.activeSpan() != null) { 307 | return tracer.activeSpan().context(); 308 | } 309 | return null; 310 | } 311 | 312 | private Span createSpanFromParent(SpanContext parentSpanContext, String operationName) { 313 | final Tracer.SpanBuilder spanBuilder; 314 | if (parentSpanContext == null) { 315 | spanBuilder = tracer.buildSpan(operationName); 316 | } else { 317 | spanBuilder = tracer.buildSpan(operationName).asChildOf(parentSpanContext); 318 | } 319 | return spanBuilder 320 | .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT) 321 | .withTag(Tags.COMPONENT.getKey(), GrpcTags.COMPONENT_NAME) 322 | .start(); 323 | } 324 | 325 | /** 326 | * Builds the configuration of a TracingClientInterceptor. 327 | */ 328 | public static class Builder { 329 | 330 | private Tracer tracer; 331 | private OperationNameConstructor operationNameConstructor; 332 | private boolean streaming; 333 | private boolean verbose; 334 | private Set tracedAttributes; 335 | private ActiveSpanSource activeSpanSource; 336 | private ActiveSpanContextSource activeSpanContextSource; 337 | private Map, ClientSpanDecorator> clientSpanDecorators; 338 | private Map, ClientCloseDecorator> clientCloseDecorators; 339 | 340 | /** 341 | * Creates a Builder with GlobalTracer if present else NoopTracer. 342 | */ 343 | private Builder() { 344 | this.tracer = GlobalTracer.get(); 345 | this.operationNameConstructor = OperationNameConstructor.DEFAULT; 346 | this.streaming = false; 347 | this.verbose = false; 348 | this.tracedAttributes = new HashSet<>(); 349 | this.activeSpanSource = ActiveSpanSource.NONE; 350 | this.activeSpanContextSource = ActiveSpanContextSource.NONE; 351 | this.clientSpanDecorators = new HashMap<>(); 352 | this.clientCloseDecorators = new HashMap<>(); 353 | } 354 | 355 | /** 356 | * Provide the {@link Tracer}. 357 | * 358 | * @param tracer the tracer 359 | * @return this Builder with configured tracer 360 | */ 361 | public Builder withTracer(Tracer tracer) { 362 | this.tracer = tracer; 363 | return this; 364 | } 365 | 366 | /** 367 | * Provide operation name constructor. 368 | * 369 | * @param operationNameConstructor to name all spans created by this interceptor 370 | * @return this Builder with configured operation name 371 | */ 372 | public Builder withOperationName(OperationNameConstructor operationNameConstructor) { 373 | this.operationNameConstructor = operationNameConstructor; 374 | return this; 375 | } 376 | 377 | /** 378 | * Logs streaming events to client spans. 379 | * 380 | * @return this Builder configured to log streaming events 381 | */ 382 | public Builder withStreaming() { 383 | this.streaming = true; 384 | return this; 385 | } 386 | 387 | /** 388 | * Provide traced attributes. 389 | * 390 | * @param tracedAttributes to set as tags on client spans created by this interceptor 391 | * @return this Builder configured to trace attributes 392 | */ 393 | public Builder withTracedAttributes(ClientRequestAttribute... tracedAttributes) { 394 | this.tracedAttributes = new HashSet<>(Arrays.asList(tracedAttributes)); 395 | return this; 396 | } 397 | 398 | /** 399 | * Logs all request life-cycle events to client spans. 400 | * 401 | * @return this Builder configured to be verbose 402 | */ 403 | public Builder withVerbosity() { 404 | this.verbose = true; 405 | return this; 406 | } 407 | 408 | /** 409 | * Provide the active span source. 410 | * 411 | * @param activeSpanSource that provides a method of getting the active span before the client 412 | * call 413 | * @return this Builder configured to start client span as children of the span returned by 414 | * activeSpanSource.getActiveSpan() 415 | */ 416 | public Builder withActiveSpanSource(ActiveSpanSource activeSpanSource) { 417 | this.activeSpanSource = activeSpanSource; 418 | return this; 419 | } 420 | 421 | /** 422 | * Provide the active span context source. 423 | * 424 | * @param activeSpanContextSource that provides a method of getting the active span context 425 | * before the client call 426 | * @return this Builder configured to start client span as children of the span context returned 427 | * by activeSpanContextSource.getActiveSpanContext() 428 | */ 429 | public Builder withActiveSpanContextSource(ActiveSpanContextSource activeSpanContextSource) { 430 | this.activeSpanContextSource = activeSpanContextSource; 431 | return this; 432 | } 433 | 434 | /** 435 | * Decorates the client span with custom data. 436 | * 437 | * @param clientSpanDecorator used to decorate the client span 438 | * @return this builder configured to decorate the client span 439 | */ 440 | public Builder withClientSpanDecorator(ClientSpanDecorator clientSpanDecorator) { 441 | this.clientSpanDecorators.put(clientSpanDecorator.getClass(), clientSpanDecorator); 442 | return this; 443 | } 444 | 445 | /** 446 | * Decorates the client span with custom data when the call is closed. 447 | * 448 | * @param clientCloseDecorator used to decorate the client span when the call is closed 449 | * @return this builder configured to decorate the client span when the call is closed 450 | */ 451 | public Builder withClientCloseDecorator(ClientCloseDecorator clientCloseDecorator) { 452 | this.clientCloseDecorators.put(clientCloseDecorator.getClass(), clientCloseDecorator); 453 | return this; 454 | } 455 | 456 | /** 457 | * Build the TracingClientInterceptor. 458 | * 459 | * @return a TracingClientInterceptor with this Builder's configuration 460 | */ 461 | public TracingClientInterceptor build() { 462 | return new TracingClientInterceptor(this); 463 | } 464 | } 465 | 466 | public enum ClientRequestAttribute { 467 | METHOD_TYPE, 468 | METHOD_NAME, 469 | DEADLINE, 470 | COMPRESSOR, 471 | AUTHORITY, 472 | ALL_CALL_OPTIONS, 473 | HEADERS 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/grpc/TracingServerInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import com.google.common.annotations.VisibleForTesting; 18 | import com.google.common.collect.ImmutableList; 19 | import com.google.common.collect.ImmutableMap; 20 | import io.grpc.BindableService; 21 | import io.grpc.Context; 22 | import io.grpc.Contexts; 23 | import io.grpc.ForwardingServerCall; 24 | import io.grpc.ForwardingServerCallListener; 25 | import io.grpc.Metadata; 26 | import io.grpc.ServerCall; 27 | import io.grpc.ServerCallHandler; 28 | import io.grpc.ServerInterceptor; 29 | import io.grpc.ServerInterceptors; 30 | import io.grpc.ServerServiceDefinition; 31 | import io.grpc.Status; 32 | import io.opentracing.Scope; 33 | import io.opentracing.Span; 34 | import io.opentracing.SpanContext; 35 | import io.opentracing.Tracer; 36 | import io.opentracing.log.Fields; 37 | import io.opentracing.propagation.Format; 38 | import io.opentracing.propagation.TextMapAdapter; 39 | import io.opentracing.tag.Tags; 40 | import io.opentracing.util.GlobalTracer; 41 | import java.util.Arrays; 42 | import java.util.HashMap; 43 | import java.util.HashSet; 44 | import java.util.Map; 45 | import java.util.Set; 46 | 47 | /** 48 | * An interceptor that applies tracing via OpenTracing to all requests to the server. 49 | */ 50 | public class TracingServerInterceptor implements ServerInterceptor { 51 | 52 | private final Tracer tracer; 53 | private final OperationNameConstructor operationNameConstructor; 54 | private final boolean streaming; 55 | private final boolean verbose; 56 | private final Set tracedAttributes; 57 | private final ImmutableList serverSpanDecorators; 58 | private final ImmutableList serverCloseDecorators; 59 | 60 | private TracingServerInterceptor(Builder builder) { 61 | this.tracer = builder.tracer; 62 | this.operationNameConstructor = builder.operationNameConstructor; 63 | this.streaming = builder.streaming; 64 | this.verbose = builder.verbose; 65 | this.tracedAttributes = builder.tracedAttributes; 66 | this.serverSpanDecorators = ImmutableList.copyOf(builder.serverSpanDecorators.values()); 67 | this.serverCloseDecorators = ImmutableList.copyOf(builder.serverCloseDecorators.values()); 68 | } 69 | 70 | /** 71 | * Creates a new {@link TracingServerInterceptor.Builder}. 72 | * 73 | * @return the tracing server interceptor builder 74 | */ 75 | public static Builder newBuilder() { 76 | return new Builder(); 77 | } 78 | 79 | /** 80 | * Add tracing to all requests made to this service. 81 | * 82 | * @param serviceDef of the service to intercept 83 | * @return the serviceDef with a tracing interceptor 84 | */ 85 | public ServerServiceDefinition intercept(ServerServiceDefinition serviceDef) { 86 | return ServerInterceptors.intercept(serviceDef, this); 87 | } 88 | 89 | /** 90 | * Add tracing to all requests made to this service. 91 | * 92 | * @param bindableService to intercept 93 | * @return the serviceDef with a tracing interceptor 94 | */ 95 | public ServerServiceDefinition intercept(BindableService bindableService) { 96 | return ServerInterceptors.intercept(bindableService, this); 97 | } 98 | 99 | @Override 100 | public ServerCall.Listener interceptCall( 101 | ServerCall call, Metadata headers, ServerCallHandler next) { 102 | 103 | final Set headerKeys = headers.keys(); 104 | Map headerMap = new HashMap<>(headerKeys.size()); 105 | for (String key : headerKeys) { 106 | if (!key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { 107 | String value = headers.get(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER)); 108 | headerMap.put(key, value); 109 | } 110 | } 111 | 112 | final Span span = 113 | getSpanFromHeaders( 114 | headerMap, operationNameConstructor.constructOperationName(call.getMethodDescriptor())); 115 | 116 | try (Scope ignored = tracer.scopeManager().activate(span)) { 117 | 118 | for (ServerSpanDecorator serverSpanDecorator : serverSpanDecorators) { 119 | serverSpanDecorator.interceptCall(span, call, headers); 120 | } 121 | 122 | for (ServerRequestAttribute attr : this.tracedAttributes) { 123 | switch (attr) { 124 | case METHOD_TYPE: 125 | GrpcTags.GRPC_METHOD_TYPE.set(span, call.getMethodDescriptor()); 126 | break; 127 | case METHOD_NAME: 128 | GrpcTags.GRPC_METHOD_NAME.set(span, call.getMethodDescriptor()); 129 | break; 130 | case CALL_ATTRIBUTES: 131 | GrpcTags.GRPC_CALL_ATTRIBUTES.set(span, call.getAttributes()); 132 | break; 133 | case HEADERS: 134 | GrpcTags.GRPC_HEADERS.set(span, headers); 135 | break; 136 | case PEER_ADDRESS: 137 | GrpcTags.PEER_ADDRESS.set(span, call.getAttributes()); 138 | break; 139 | } 140 | } 141 | 142 | Context ctxWithSpan = 143 | Context.current() 144 | .withValue(OpenTracingContextKey.getKey(), span) 145 | .withValue(OpenTracingContextKey.getSpanContextKey(), span.context()); 146 | 147 | final ServerCall decoratedCall = 148 | new ForwardingServerCall.SimpleForwardingServerCall(call) { 149 | 150 | @Override 151 | public void sendHeaders(Metadata headers) { 152 | if (verbose) { 153 | span.log( 154 | ImmutableMap.builder() 155 | .put(Fields.EVENT, GrpcFields.SERVER_CALL_SEND_HEADERS) 156 | .put(Fields.MESSAGE, "Server sent response headers") 157 | .put(GrpcFields.HEADERS, headers.toString()) 158 | .build()); 159 | } 160 | super.sendHeaders(headers); 161 | } 162 | 163 | @Override 164 | public void sendMessage(RespT message) { 165 | if (streaming || verbose) { 166 | span.log( 167 | ImmutableMap.builder() 168 | .put(Fields.EVENT, GrpcFields.SERVER_CALL_SEND_MESSAGE) 169 | .put(Fields.MESSAGE, "Server sent response message") 170 | .build()); 171 | } 172 | 173 | try (Scope ignored = tracer.scopeManager().activate(span)) { 174 | super.sendMessage(message); 175 | } 176 | } 177 | 178 | @Override 179 | public void close(Status status, Metadata trailers) { 180 | if (verbose) { 181 | span.log( 182 | ImmutableMap.builder() 183 | .put(Fields.EVENT, GrpcFields.SERVER_CALL_CLOSE) 184 | .put(Fields.MESSAGE, "Server call closed") 185 | .build()); 186 | if (!status.isOk()) { 187 | GrpcFields.logServerCallError(span, status.getDescription(), status.getCause()); 188 | } 189 | } 190 | GrpcTags.GRPC_STATUS.set(span, status); 191 | for (ServerCloseDecorator serverCloseDecorator : serverCloseDecorators) { 192 | serverCloseDecorator.close(span, status, trailers); 193 | } 194 | super.close(status, trailers); 195 | } 196 | }; 197 | 198 | ServerCall.Listener listenerWithContext = 199 | Contexts.interceptCall(ctxWithSpan, decoratedCall, headers, next); 200 | 201 | return new ForwardingServerCallListener.SimpleForwardingServerCallListener( 202 | listenerWithContext) { 203 | 204 | @Override 205 | public void onReady() { 206 | try (Scope ignored = tracer.scopeManager().activate(span)) { 207 | super.onReady(); 208 | } 209 | } 210 | 211 | @Override 212 | public void onMessage(ReqT message) { 213 | if (streaming || verbose) { 214 | span.log( 215 | ImmutableMap.builder() 216 | .put(Fields.EVENT, GrpcFields.SERVER_CALL_LISTENER_ON_MESSAGE) 217 | .put(Fields.MESSAGE, "Server received message") 218 | .build()); 219 | } 220 | try (Scope ignored = tracer.scopeManager().activate(span)) { 221 | super.onMessage(message); 222 | } 223 | } 224 | 225 | @Override 226 | public void onHalfClose() { 227 | if (streaming || verbose) { 228 | span.log( 229 | ImmutableMap.builder() 230 | .put(Fields.EVENT, GrpcFields.SERVER_CALL_LISTENER_ON_HALF_CLOSE) 231 | .put(Fields.MESSAGE, "Server received all messages") 232 | .build()); 233 | } 234 | try (Scope ignored = tracer.scopeManager().activate(span)) { 235 | super.onHalfClose(); 236 | } 237 | } 238 | 239 | @Override 240 | public void onCancel() { 241 | if (verbose) { 242 | span.log( 243 | ImmutableMap.builder() 244 | .put(Fields.EVENT, GrpcFields.SERVER_CALL_LISTENER_ON_CANCEL) 245 | .put(Fields.MESSAGE, "Server call cancelled") 246 | .build()); 247 | } 248 | GrpcTags.GRPC_STATUS.set(span, Status.CANCELLED); 249 | try (Scope ignored = tracer.scopeManager().activate(span)) { 250 | super.onCancel(); 251 | } finally { 252 | span.finish(); 253 | } 254 | } 255 | 256 | @Override 257 | public void onComplete() { 258 | if (verbose) { 259 | span.log( 260 | ImmutableMap.builder() 261 | .put(Fields.EVENT, GrpcFields.SERVER_CALL_LISTENER_ON_COMPLETE) 262 | .put(Fields.MESSAGE, "Server call completed") 263 | .build()); 264 | } 265 | // Server span may complete with non-OK ServerCall.close(status). 266 | try (Scope ignored = tracer.scopeManager().activate(span)) { 267 | super.onComplete(); 268 | } finally { 269 | span.finish(); 270 | } 271 | } 272 | }; 273 | } 274 | } 275 | 276 | @VisibleForTesting 277 | Span getSpanFromHeaders(Map headers, String operationName) { 278 | Map fields = null; 279 | Tracer.SpanBuilder spanBuilder = tracer.buildSpan(operationName); 280 | try { 281 | SpanContext parentSpanCtx = 282 | tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(headers)); 283 | if (parentSpanCtx != null) { 284 | spanBuilder = spanBuilder.asChildOf(parentSpanCtx); 285 | } 286 | } catch (IllegalArgumentException iae) { 287 | spanBuilder = spanBuilder.withTag(Tags.ERROR, Boolean.TRUE); 288 | fields = 289 | ImmutableMap.builder() 290 | .put(Fields.EVENT, GrpcFields.ERROR) 291 | .put( 292 | Fields.ERROR_OBJECT, 293 | new RuntimeException("Parent span context extract failed", iae)) 294 | .build(); 295 | } 296 | Span span = 297 | spanBuilder 298 | .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) 299 | .withTag(Tags.COMPONENT.getKey(), GrpcTags.COMPONENT_NAME) 300 | .start(); 301 | if (fields != null) { 302 | span.log(fields); 303 | } 304 | return span; 305 | } 306 | 307 | /** 308 | * Builds the configuration of a TracingServerInterceptor. 309 | */ 310 | public static class Builder { 311 | 312 | private Tracer tracer; 313 | private OperationNameConstructor operationNameConstructor; 314 | private boolean streaming; 315 | private boolean verbose; 316 | private Set tracedAttributes; 317 | private Map, ServerSpanDecorator> serverSpanDecorators; 318 | private Map, ServerCloseDecorator> serverCloseDecorators; 319 | 320 | /** 321 | * Creates a Builder with GlobalTracer if present else NoopTracer. 322 | */ 323 | private Builder() { 324 | this.tracer = GlobalTracer.get(); 325 | this.operationNameConstructor = OperationNameConstructor.DEFAULT; 326 | this.streaming = false; 327 | this.verbose = false; 328 | this.tracedAttributes = new HashSet<>(); 329 | this.serverSpanDecorators = new HashMap<>(); 330 | this.serverCloseDecorators = new HashMap<>(); 331 | } 332 | 333 | /** 334 | * Provide the {@link Tracer}. 335 | * 336 | * @param tracer the tracer 337 | * @return this Builder with configured tracer 338 | */ 339 | public Builder withTracer(Tracer tracer) { 340 | this.tracer = tracer; 341 | return this; 342 | } 343 | 344 | /** 345 | * Provide operation name. 346 | * 347 | * @param operationNameConstructor for all spans created by this interceptor 348 | * @return this Builder with configured operation name 349 | */ 350 | public Builder withOperationName(OperationNameConstructor operationNameConstructor) { 351 | this.operationNameConstructor = operationNameConstructor; 352 | return this; 353 | } 354 | 355 | /** 356 | * Provide traced attributes. 357 | * 358 | * @param attributes to set as tags on server spans created by this interceptor 359 | * @return this Builder configured to trace request attributes 360 | */ 361 | public Builder withTracedAttributes(ServerRequestAttribute... attributes) { 362 | this.tracedAttributes = new HashSet<>(Arrays.asList(attributes)); 363 | return this; 364 | } 365 | 366 | /** 367 | * Logs streaming events to server spans. 368 | * 369 | * @return this Builder configured to log streaming events 370 | */ 371 | public Builder withStreaming() { 372 | this.streaming = true; 373 | return this; 374 | } 375 | 376 | /** 377 | * Logs all request life-cycle events to server spans. 378 | * 379 | * @return this Builder configured to be verbose 380 | */ 381 | public Builder withVerbosity() { 382 | this.verbose = true; 383 | return this; 384 | } 385 | 386 | /** 387 | * Decorates the server span with custom data. 388 | * 389 | * @param serverSpanDecorator used to decorate the server span 390 | * @return this Builder configured to decorate server span 391 | */ 392 | public Builder withServerSpanDecorator(ServerSpanDecorator serverSpanDecorator) { 393 | this.serverSpanDecorators.put(serverSpanDecorator.getClass(), serverSpanDecorator); 394 | return this; 395 | } 396 | 397 | /** 398 | * Decorates the server span with custom data when the gRPC call is closed. 399 | * 400 | * @param serverCloseDecorator used to decorate the server span 401 | * @return this Builder configured to decorate server span when the gRPC call is closed 402 | */ 403 | public Builder withServerCloseDecorator(ServerCloseDecorator serverCloseDecorator) { 404 | this.serverCloseDecorators.put(serverCloseDecorator.getClass(), serverCloseDecorator); 405 | return this; 406 | } 407 | 408 | /** 409 | * Build the TracingServerInterceptor. 410 | * 411 | * @return a TracingServerInterceptor with this Builder's configuration 412 | */ 413 | public TracingServerInterceptor build() { 414 | return new TracingServerInterceptor(this); 415 | } 416 | } 417 | 418 | public enum ServerRequestAttribute { 419 | HEADERS, 420 | METHOD_TYPE, 421 | METHOD_NAME, 422 | CALL_ATTRIBUTES, 423 | PEER_ADDRESS 424 | } 425 | } 426 | -------------------------------------------------------------------------------- /src/test/java/io/opentracing/contrib/grpc/ActiveSpanContextSourceTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import static org.junit.Assert.assertEquals; 18 | import static org.junit.Assert.assertNull; 19 | 20 | import io.grpc.Context; 21 | import io.opentracing.Span; 22 | import io.opentracing.mock.MockTracer; 23 | import org.junit.Before; 24 | import org.junit.Test; 25 | 26 | public class ActiveSpanContextSourceTest { 27 | 28 | private final MockTracer tracer = new MockTracer(); 29 | 30 | @Before 31 | public void setUp() { 32 | tracer.reset(); 33 | } 34 | 35 | @Test 36 | public void testDefaultNone() { 37 | ActiveSpanContextSource ss = ActiveSpanContextSource.NONE; 38 | assertNull("active span context should always be null", ss.getActiveSpanContext()); 39 | 40 | Span span = tracer.buildSpan("s0").start(); 41 | Context ctx = Context.current().withValue(OpenTracingContextKey.getKey(), span); 42 | Context previousCtx = ctx.attach(); 43 | 44 | assertNull("active span context should always be null", ss.getActiveSpanContext()); 45 | 46 | ctx.detach(previousCtx); 47 | span.finish(); 48 | } 49 | 50 | @Test 51 | public void testDefaultGrpc() { 52 | ActiveSpanContextSource ss = ActiveSpanContextSource.GRPC_CONTEXT; 53 | assertNull( 54 | "active span context should be null, no span context in OpenTracingContextKey", 55 | ss.getActiveSpanContext()); 56 | 57 | Span span = tracer.buildSpan("s0").start(); 58 | Context ctx = 59 | Context.current().withValue(OpenTracingContextKey.getSpanContextKey(), span.context()); 60 | Context previousCtx = ctx.attach(); 61 | 62 | assertEquals( 63 | "active span context should be OpenTracingContextKey.activeSpanContext()", 64 | ss.getActiveSpanContext(), 65 | span.context()); 66 | 67 | ctx.detach(previousCtx); 68 | span.finish(); 69 | 70 | assertNull( 71 | "active span context should be null, no span context in OpenTracingContextKey", 72 | ss.getActiveSpanContext()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/io/opentracing/contrib/grpc/ActiveSpanSourceTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import static org.junit.Assert.assertEquals; 18 | import static org.junit.Assert.assertNull; 19 | 20 | import io.grpc.Context; 21 | import io.opentracing.Span; 22 | import io.opentracing.Tracer; 23 | import io.opentracing.mock.MockTracer; 24 | import org.junit.Test; 25 | 26 | public class ActiveSpanSourceTest { 27 | 28 | private Tracer tracer = new MockTracer(); 29 | 30 | @Test 31 | public void testDefaultNone() { 32 | ActiveSpanSource ss = ActiveSpanSource.NONE; 33 | assertNull("active span should always be null", ss.getActiveSpan()); 34 | 35 | Span span = tracer.buildSpan("s0").start(); 36 | Context ctx = Context.current().withValue(OpenTracingContextKey.getKey(), span); 37 | Context previousCtx = ctx.attach(); 38 | 39 | assertNull("active span should always be null", ss.getActiveSpan()); 40 | 41 | ctx.detach(previousCtx); 42 | span.finish(); 43 | } 44 | 45 | @Test 46 | public void testDefaultGrpc() { 47 | ActiveSpanSource ss = ActiveSpanSource.GRPC_CONTEXT; 48 | assertNull("active span should be null, no span in OpenTracingContextKey", ss.getActiveSpan()); 49 | 50 | Span span = tracer.buildSpan("s0").start(); 51 | Context ctx = Context.current().withValue(OpenTracingContextKey.getKey(), span); 52 | Context previousCtx = ctx.attach(); 53 | 54 | assertEquals( 55 | "active span should be OpenTracingContextKey.activeSpan()", ss.getActiveSpan(), span); 56 | 57 | ctx.detach(previousCtx); 58 | span.finish(); 59 | 60 | assertNull("active span should be null, no span in OpenTracingContextKey", ss.getActiveSpan()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/io/opentracing/contrib/grpc/GrpcTagsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import static org.assertj.core.api.Assertions.assertThat; 18 | 19 | import io.grpc.Attributes; 20 | import io.grpc.Grpc; 21 | import io.grpc.Status; 22 | import io.grpc.inprocess.InProcessSocketAddress; 23 | import io.opentracing.mock.MockSpan; 24 | import io.opentracing.mock.MockTracer; 25 | import java.net.InetSocketAddress; 26 | import java.util.UUID; 27 | import java.util.concurrent.ThreadLocalRandom; 28 | import org.assertj.core.data.MapEntry; 29 | import org.junit.Test; 30 | 31 | public class GrpcTagsTest { 32 | 33 | @Test 34 | public void testStatusOk() { 35 | final Status status = Status.OK; 36 | MockSpan span = new MockTracer().buildSpan("").start(); 37 | GrpcTags.GRPC_STATUS.set(span, status); 38 | assertThat(span.tags()) 39 | .containsExactly(MapEntry.entry(GrpcTags.GRPC_STATUS.getKey(), status.getCode().name())); 40 | } 41 | 42 | @Test 43 | public void testStatusError() { 44 | final Status status = Status.INTERNAL; 45 | MockSpan span = new MockTracer().buildSpan("").start(); 46 | GrpcTags.GRPC_STATUS.set(span, status); 47 | assertThat(span.tags()) 48 | .containsOnly(MapEntry.entry(GrpcTags.GRPC_STATUS.getKey(), status.getCode().name())); 49 | } 50 | 51 | @Test 52 | public void testPeerAddressSocket() { 53 | final InetSocketAddress address = 54 | new InetSocketAddress("127.0.0.1", ThreadLocalRandom.current().nextInt(65535)); 55 | final Attributes attributes = 56 | Attributes.newBuilder().set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, address).build(); 57 | MockSpan span = new MockTracer().buildSpan("").start(); 58 | GrpcTags.PEER_ADDRESS.set(span, attributes); 59 | assertThat(span.tags()) 60 | .containsOnly( 61 | MapEntry.entry( 62 | GrpcTags.PEER_ADDRESS.getKey(), address.getHostString() + ':' + address.getPort())); 63 | } 64 | 65 | @Test 66 | public void testPeerAddressInProcess() { 67 | final InProcessSocketAddress address = new InProcessSocketAddress(UUID.randomUUID().toString()); 68 | final Attributes attributes = 69 | Attributes.newBuilder().set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, address).build(); 70 | MockSpan span = new MockTracer().buildSpan("").start(); 71 | GrpcTags.PEER_ADDRESS.set(span, attributes); 72 | assertThat(span.tags()) 73 | .containsOnly(MapEntry.entry(GrpcTags.PEER_ADDRESS.getKey(), address.getName())); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/io/opentracing/contrib/grpc/OpenTracingContextKeyTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import static org.junit.Assert.assertEquals; 18 | import static org.junit.Assert.assertNull; 19 | 20 | import io.grpc.Context; 21 | import io.grpc.Context.Key; 22 | import io.opentracing.Span; 23 | import io.opentracing.SpanContext; 24 | import io.opentracing.mock.MockTracer; 25 | import org.junit.Before; 26 | import org.junit.Test; 27 | 28 | public class OpenTracingContextKeyTest { 29 | 30 | private final MockTracer tracer = new MockTracer(); 31 | 32 | @Before 33 | public void setUp() { 34 | tracer.reset(); 35 | } 36 | 37 | @Test 38 | public void testGetKey() { 39 | Context.Key key = OpenTracingContextKey.getKey(); 40 | assertEquals("Key should have correct name", key.toString(), (OpenTracingContextKey.KEY_NAME)); 41 | } 42 | 43 | @Test 44 | public void testGetSpanContextKey() { 45 | Key key = OpenTracingContextKey.getSpanContextKey(); 46 | assertEquals( 47 | "Key should have correct name", key.toString(), OpenTracingContextKey.KEY_CONTEXT_NAME); 48 | } 49 | 50 | @Test 51 | public void testNoActiveSpan() { 52 | assertNull( 53 | "activeSpan() should return null when no span is active", 54 | OpenTracingContextKey.activeSpan()); 55 | } 56 | 57 | @Test 58 | public void testNoActiveSpanContext() { 59 | assertNull( 60 | "activeSpanContext() should return null when no span context is active", 61 | OpenTracingContextKey.activeSpanContext()); 62 | } 63 | 64 | @Test 65 | public void testGetActiveSpan() { 66 | Span span = tracer.buildSpan("s0").start(); 67 | Context ctx = Context.current().withValue(OpenTracingContextKey.getKey(), span); 68 | Context previousCtx = ctx.attach(); 69 | 70 | assertEquals(OpenTracingContextKey.activeSpan(), span); 71 | 72 | ctx.detach(previousCtx); 73 | span.finish(); 74 | 75 | assertNull(OpenTracingContextKey.activeSpan()); 76 | } 77 | 78 | @Test 79 | public void testGetActiveSpanContext() { 80 | Span span = tracer.buildSpan("s0").start(); 81 | Context ctx = 82 | Context.current().withValue(OpenTracingContextKey.getSpanContextKey(), span.context()); 83 | Context previousCtx = ctx.attach(); 84 | 85 | assertEquals(OpenTracingContextKey.activeSpanContext(), span.context()); 86 | 87 | ctx.detach(previousCtx); 88 | span.finish(); 89 | 90 | assertNull(OpenTracingContextKey.activeSpanContext()); 91 | } 92 | 93 | @Test 94 | public void testMultipleContextLayers() { 95 | Span parentSpan = tracer.buildSpan("s0").start(); 96 | Context parentCtx = Context.current().withValue(OpenTracingContextKey.getKey(), parentSpan); 97 | Context previousCtx = parentCtx.attach(); 98 | 99 | Span childSpan = tracer.buildSpan("s1").start(); 100 | Context childCtx = Context.current().withValue(OpenTracingContextKey.getKey(), childSpan); 101 | parentCtx = childCtx.attach(); 102 | assertEquals(OpenTracingContextKey.activeSpan(), childSpan); 103 | 104 | childCtx.detach(parentCtx); 105 | childSpan.finish(); 106 | assertEquals(OpenTracingContextKey.activeSpan(), parentSpan); 107 | 108 | parentCtx.detach(previousCtx); 109 | parentSpan.finish(); 110 | assertNull(OpenTracingContextKey.activeSpan()); 111 | } 112 | 113 | @Test 114 | public void testMultipleContextLayersForSpanContext() { 115 | Span parentSpan = tracer.buildSpan("s0").start(); 116 | Context parentCtx = 117 | Context.current() 118 | .withValue(OpenTracingContextKey.getSpanContextKey(), parentSpan.context()); 119 | Context previousCtx = parentCtx.attach(); 120 | 121 | Span childSpan = tracer.buildSpan("s1").start(); 122 | Context childCtx = 123 | Context.current().withValue(OpenTracingContextKey.getSpanContextKey(), childSpan.context()); 124 | parentCtx = childCtx.attach(); 125 | assertEquals(OpenTracingContextKey.activeSpanContext(), childSpan.context()); 126 | 127 | childCtx.detach(parentCtx); 128 | childSpan.finish(); 129 | assertEquals(OpenTracingContextKey.activeSpanContext(), parentSpan.context()); 130 | 131 | parentCtx.detach(previousCtx); 132 | parentSpan.finish(); 133 | assertNull(OpenTracingContextKey.activeSpanContext()); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/test/java/io/opentracing/contrib/grpc/SecondClientInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import static org.junit.Assert.assertNotNull; 18 | 19 | import io.grpc.CallOptions; 20 | import io.grpc.Channel; 21 | import io.grpc.ClientCall; 22 | import io.grpc.ClientInterceptor; 23 | import io.grpc.ForwardingClientCall; 24 | import io.grpc.ForwardingClientCallListener; 25 | import io.grpc.Metadata; 26 | import io.grpc.MethodDescriptor; 27 | import io.opentracing.mock.MockTracer; 28 | import javax.annotation.Nullable; 29 | 30 | public class SecondClientInterceptor implements ClientInterceptor { 31 | 32 | private final MockTracer tracer; 33 | 34 | SecondClientInterceptor(MockTracer tracer) { 35 | this.tracer = tracer; 36 | } 37 | 38 | @Override 39 | public ClientCall interceptCall( 40 | MethodDescriptor method, CallOptions callOptions, Channel next) { 41 | 42 | assertNotNull(tracer.activeSpan()); 43 | 44 | return new ForwardingClientCall.SimpleForwardingClientCall( 45 | next.newCall(method, callOptions)) { 46 | 47 | @Override 48 | public void start(Listener responseListener, Metadata headers) { 49 | assertNotNull(tracer.activeSpan()); 50 | super.start( 51 | new ForwardingClientCallListener.SimpleForwardingClientCallListener( 52 | responseListener) { 53 | }, 54 | headers); 55 | } 56 | 57 | @Override 58 | public void sendMessage(ReqT message) { 59 | assertNotNull(tracer.activeSpan()); 60 | super.sendMessage(message); 61 | } 62 | 63 | @Override 64 | public void halfClose() { 65 | assertNotNull(tracer.activeSpan()); 66 | super.halfClose(); 67 | } 68 | 69 | @Override 70 | public void cancel(@Nullable String message, @Nullable Throwable cause) { 71 | assertNotNull(tracer.activeSpan()); 72 | super.cancel(message, cause); 73 | } 74 | }; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/io/opentracing/contrib/grpc/SecondServerInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import static org.junit.Assert.assertNotNull; 18 | 19 | import io.grpc.ForwardingServerCall; 20 | import io.grpc.ForwardingServerCallListener; 21 | import io.grpc.Metadata; 22 | import io.grpc.ServerCall; 23 | import io.grpc.ServerCall.Listener; 24 | import io.grpc.ServerCallHandler; 25 | import io.grpc.ServerInterceptor; 26 | import io.grpc.Status; 27 | import io.opentracing.mock.MockTracer; 28 | 29 | public class SecondServerInterceptor implements ServerInterceptor { 30 | 31 | private final MockTracer tracer; 32 | 33 | SecondServerInterceptor(MockTracer tracer) { 34 | this.tracer = tracer; 35 | } 36 | 37 | @Override 38 | public Listener interceptCall( 39 | ServerCall call, Metadata headers, ServerCallHandler next) { 40 | 41 | assertNotNull(tracer.activeSpan()); 42 | 43 | call = 44 | new ForwardingServerCall.SimpleForwardingServerCall(call) { 45 | 46 | @Override 47 | public void sendHeaders(Metadata headers) { 48 | assertNotNull(tracer.activeSpan()); 49 | super.sendHeaders(headers); 50 | } 51 | 52 | @Override 53 | public void sendMessage(RespT message) { 54 | assertNotNull(tracer.activeSpan()); 55 | super.sendMessage(message); 56 | } 57 | 58 | @Override 59 | public void close(Status status, Metadata trailers) { 60 | assertNotNull(tracer.activeSpan()); 61 | super.close(status, trailers); 62 | } 63 | }; 64 | 65 | return new ForwardingServerCallListener.SimpleForwardingServerCallListener( 66 | next.startCall(call, headers)) { 67 | 68 | @Override 69 | public void onReady() { 70 | assertNotNull(tracer.activeSpan()); 71 | super.onReady(); 72 | } 73 | 74 | @Override 75 | public void onMessage(ReqT message) { 76 | assertNotNull(tracer.activeSpan()); 77 | super.onMessage(message); 78 | } 79 | 80 | @Override 81 | public void onHalfClose() { 82 | assertNotNull(tracer.activeSpan()); 83 | super.onHalfClose(); 84 | } 85 | 86 | @Override 87 | public void onCancel() { 88 | assertNotNull(tracer.activeSpan()); 89 | super.onCancel(); 90 | } 91 | 92 | @Override 93 | public void onComplete() { 94 | assertNotNull(tracer.activeSpan()); 95 | super.onComplete(); 96 | } 97 | }; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/test/java/io/opentracing/contrib/grpc/TracedClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import io.grpc.ClientInterceptor; 18 | import io.grpc.ClientInterceptors; 19 | import io.grpc.ManagedChannel; 20 | import io.opentracing.contrib.grpc.gen.GreeterGrpc; 21 | import io.opentracing.contrib.grpc.gen.HelloReply; 22 | import io.opentracing.contrib.grpc.gen.HelloRequest; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | class TracedClient { 26 | 27 | private final GreeterGrpc.GreeterBlockingStub blockingStub; 28 | 29 | TracedClient(ManagedChannel channel, ClientInterceptor... interceptors) { 30 | blockingStub = GreeterGrpc.newBlockingStub(ClientInterceptors.intercept(channel, interceptors)); 31 | } 32 | 33 | TracedClient( 34 | ManagedChannel channel, 35 | long deadline, 36 | String compression, 37 | ClientInterceptor... interceptors) { 38 | blockingStub = 39 | GreeterGrpc.newBlockingStub(ClientInterceptors.intercept(channel, interceptors)) 40 | .withDeadlineAfter(deadline, TimeUnit.MILLISECONDS) 41 | .withCompression(compression); 42 | } 43 | 44 | HelloReply greet() { 45 | try { 46 | return blockingStub.sayHello(HelloRequest.newBuilder().setName("world").build()); 47 | } catch (Exception ignored) { 48 | return null; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/io/opentracing/contrib/grpc/TracedService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import io.grpc.ServerInterceptor; 18 | import io.grpc.ServerInterceptors; 19 | import io.grpc.stub.StreamObserver; 20 | import io.grpc.util.MutableHandlerRegistry; 21 | import io.opentracing.contrib.grpc.gen.GreeterGrpc; 22 | import io.opentracing.contrib.grpc.gen.HelloReply; 23 | import io.opentracing.contrib.grpc.gen.HelloRequest; 24 | import io.opentracing.util.GlobalTracer; 25 | 26 | class TracedService { 27 | 28 | static void addGeeterService(MutableHandlerRegistry registry) { 29 | registry.addService(new GreeterImpl()); 30 | } 31 | 32 | static void addGeeterService(MutableHandlerRegistry registry, ServerInterceptor... interceptors) { 33 | registry.addService(ServerInterceptors.intercept(new GreeterImpl(), interceptors)); 34 | } 35 | 36 | private static class GreeterImpl extends GreeterGrpc.GreeterImplBase { 37 | 38 | @Override 39 | public void sayHello(HelloRequest req, StreamObserver responseObserver) { 40 | // verify that there is an active span in case of using GlobalTracer: 41 | if (GlobalTracer.isRegistered() && GlobalTracer.get().activeSpan() == null) { 42 | throw new RuntimeException("no active span"); 43 | } 44 | 45 | HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); 46 | responseObserver.onNext(reply); 47 | responseObserver.onCompleted(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/io/opentracing/contrib/grpc/TracingClientInterceptorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import static org.awaitility.Awaitility.await; 18 | import static org.hamcrest.core.IsEqual.equalTo; 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertFalse; 21 | 22 | import com.google.common.collect.ImmutableMap; 23 | import com.google.common.collect.ImmutableSet; 24 | import io.grpc.CallOptions; 25 | import io.grpc.Metadata; 26 | import io.grpc.MethodDescriptor; 27 | import io.grpc.Status; 28 | import io.grpc.testing.GrpcServerRule; 29 | import io.opentracing.Span; 30 | import io.opentracing.SpanContext; 31 | import io.opentracing.log.Fields; 32 | import io.opentracing.mock.MockSpan; 33 | import io.opentracing.mock.MockTracer; 34 | import io.opentracing.tag.Tags; 35 | import io.opentracing.util.GlobalTracerTestUtil; 36 | import java.util.ArrayList; 37 | import java.util.List; 38 | import java.util.Map; 39 | import java.util.Set; 40 | import java.util.concurrent.Callable; 41 | import java.util.concurrent.TimeUnit; 42 | import org.assertj.core.api.Assertions; 43 | import org.junit.Before; 44 | import org.junit.Rule; 45 | import org.junit.Test; 46 | 47 | public class TracingClientInterceptorTest { 48 | 49 | private static final String PREFIX = "testing-"; 50 | 51 | private static final Map BASE_TAGS = 52 | ImmutableMap.builder() 53 | .put(Tags.COMPONENT.getKey(), GrpcTags.COMPONENT_NAME) 54 | .put(GrpcTags.GRPC_STATUS.getKey(), Status.Code.OK.name()) 55 | .build(); 56 | 57 | private static final Map BASE_CLIENT_TAGS = 58 | ImmutableMap.builder() 59 | .putAll(BASE_TAGS) 60 | .put(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT) 61 | .build(); 62 | 63 | private static final Set CLIENT_ATTRIBUTE_TAGS = 64 | ImmutableSet.of( 65 | GrpcTags.GRPC_CALL_OPTIONS.getKey(), 66 | // TODO: @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1767") 67 | // GrpcTags.GRPC_AUTHORITY.getKey(), 68 | GrpcTags.GRPC_COMPRESSOR.getKey(), 69 | GrpcTags.GRPC_DEADLINE.getKey(), 70 | GrpcTags.GRPC_METHOD_NAME.getKey(), 71 | GrpcTags.GRPC_METHOD_TYPE.getKey(), 72 | GrpcTags.GRPC_HEADERS.getKey()); 73 | 74 | private final MockTracer clientTracer = new MockTracer(); 75 | 76 | @Rule 77 | public GrpcServerRule grpcServer = new GrpcServerRule(); 78 | 79 | @Before 80 | public void setUp() { 81 | GlobalTracerTestUtil.resetGlobalTracer(); 82 | clientTracer.reset(); 83 | TracedService.addGeeterService(grpcServer.getServiceRegistry()); 84 | } 85 | 86 | @Test 87 | public void testTracedClientBasic() { 88 | TracingClientInterceptor tracingInterceptor = 89 | TracingClientInterceptor.newBuilder().withTracer(clientTracer).build(); 90 | TracedClient client = new TracedClient(grpcServer.getChannel(), tracingInterceptor); 91 | 92 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 93 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1)); 94 | assertEquals( 95 | "one span should have been created and finished for one client request", 96 | clientTracer.finishedSpans().size(), 97 | 1); 98 | 99 | MockSpan span = clientTracer.finishedSpans().get(0); 100 | assertEquals("span should have prefix", span.operationName(), "helloworld.Greeter/SayHello"); 101 | assertEquals("span should have no parents", span.parentId(), 0); 102 | assertEquals("span should have no logs", span.logEntries().size(), 0); 103 | Assertions.assertThat(span.tags()) 104 | .as("span should have base client tags") 105 | .isEqualTo(BASE_CLIENT_TAGS); 106 | assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext()); 107 | } 108 | 109 | @Test 110 | public void testTracedClientTwoInterceptors() { 111 | SecondClientInterceptor secondInterceptor = new SecondClientInterceptor(clientTracer); 112 | TracingClientInterceptor tracingInterceptor = 113 | TracingClientInterceptor.newBuilder().withTracer(clientTracer).build(); 114 | TracedClient client = 115 | new TracedClient(grpcServer.getChannel(), secondInterceptor, tracingInterceptor); 116 | 117 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 118 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1)); 119 | assertEquals( 120 | "one span should have been created and finished for one client request", 121 | clientTracer.finishedSpans().size(), 122 | 1); 123 | 124 | MockSpan span = clientTracer.finishedSpans().get(0); 125 | assertEquals("span should have prefix", span.operationName(), "helloworld.Greeter/SayHello"); 126 | assertEquals("span should have no parents", span.parentId(), 0); 127 | assertEquals("span should have no logs", span.logEntries().size(), 0); 128 | Assertions.assertThat(span.tags()) 129 | .as("span should have base client tags") 130 | .isEqualTo(BASE_CLIENT_TAGS); 131 | assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext()); 132 | } 133 | 134 | @Test 135 | public void testTracedClientWithVerbosity() { 136 | TracingClientInterceptor tracingInterceptor = 137 | TracingClientInterceptor.newBuilder().withTracer(clientTracer).withVerbosity().build(); 138 | TracedClient client = new TracedClient(grpcServer.getChannel(), tracingInterceptor); 139 | 140 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 141 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1)); 142 | assertEquals( 143 | "one span should have been created and finished for one client request", 144 | clientTracer.finishedSpans().size(), 145 | 1); 146 | 147 | MockSpan span = clientTracer.finishedSpans().get(0); 148 | assertEquals("span should have prefix", span.operationName(), "helloworld.Greeter/SayHello"); 149 | assertEquals("span should have no parents", span.parentId(), 0); 150 | System.out.println(span.logEntries()); 151 | List events = new ArrayList<>(span.logEntries().size()); 152 | for (MockSpan.LogEntry logEntry : span.logEntries()) { 153 | events.add((String) logEntry.fields().get(Fields.EVENT)); 154 | } 155 | Assertions.assertThat(events) 156 | .as("span should contain verbose log fields") 157 | .contains( 158 | GrpcFields.CLIENT_CALL_START, 159 | GrpcFields.CLIENT_CALL_SEND_MESSAGE, 160 | GrpcFields.CLIENT_CALL_HALF_CLOSE, 161 | GrpcFields.CLIENT_CALL_LISTENER_ON_HEADERS, 162 | GrpcFields.CLIENT_CALL_LISTENER_ON_MESSAGE, 163 | GrpcFields.CLIENT_CALL_LISTENER_ON_CLOSE); 164 | Assertions.assertThat(span.tags()) 165 | .as("span should have base client tags") 166 | .isEqualTo(BASE_CLIENT_TAGS); 167 | assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext()); 168 | } 169 | 170 | @Test 171 | public void testTracedClientWithStreaming() { 172 | TracingClientInterceptor tracingInterceptor = 173 | TracingClientInterceptor.newBuilder().withTracer(clientTracer).withStreaming().build(); 174 | TracedClient client = new TracedClient(grpcServer.getChannel(), tracingInterceptor); 175 | 176 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 177 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1)); 178 | assertEquals( 179 | "one span should have been created and finished for one client request", 180 | clientTracer.finishedSpans().size(), 181 | 1); 182 | 183 | MockSpan span = clientTracer.finishedSpans().get(0); 184 | assertEquals("span should have prefix", span.operationName(), "helloworld.Greeter/SayHello"); 185 | assertEquals("span should have no parents", span.parentId(), 0); 186 | List events = new ArrayList<>(span.logEntries().size()); 187 | for (MockSpan.LogEntry logEntry : span.logEntries()) { 188 | events.add((String) logEntry.fields().get(Fields.EVENT)); 189 | } 190 | Assertions.assertThat(events) 191 | .as("span should contain streaming log fields") 192 | .contains( 193 | GrpcFields.CLIENT_CALL_SEND_MESSAGE, 194 | GrpcFields.CLIENT_CALL_HALF_CLOSE, 195 | GrpcFields.CLIENT_CALL_LISTENER_ON_MESSAGE); 196 | Assertions.assertThat(span.tags()) 197 | .as("span should have base client tags") 198 | .isEqualTo(BASE_CLIENT_TAGS); 199 | assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext()); 200 | } 201 | 202 | @Test 203 | public void testTracedClientWithOperationName() { 204 | TracingClientInterceptor tracingInterceptor = 205 | TracingClientInterceptor.newBuilder() 206 | .withTracer(clientTracer) 207 | .withOperationName( 208 | new OperationNameConstructor() { 209 | @Override 210 | public String constructOperationName( 211 | MethodDescriptor method) { 212 | return PREFIX + method.getFullMethodName(); 213 | } 214 | }) 215 | .build(); 216 | TracedClient client = new TracedClient(grpcServer.getChannel(), tracingInterceptor); 217 | 218 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 219 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1)); 220 | assertEquals( 221 | "one span should have been created and finished for one client request", 222 | clientTracer.finishedSpans().size(), 223 | 1); 224 | 225 | MockSpan span = clientTracer.finishedSpans().get(0); 226 | assertEquals( 227 | "span should have prefix", span.operationName(), PREFIX + "helloworld.Greeter/SayHello"); 228 | assertEquals("span should have no parents", span.parentId(), 0); 229 | assertEquals("span should have no logs", span.logEntries().size(), 0); 230 | Assertions.assertThat(span.tags()) 231 | .as("span should have base client tags") 232 | .isEqualTo(BASE_CLIENT_TAGS); 233 | assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext()); 234 | } 235 | 236 | @Test 237 | public void testTracedClientWithTracedAttributes() { 238 | TracingClientInterceptor tracingInterceptor = 239 | TracingClientInterceptor.newBuilder() 240 | .withTracer(clientTracer) 241 | .withTracedAttributes(TracingClientInterceptor.ClientRequestAttribute.values()) 242 | .build(); 243 | TracedClient client = new TracedClient(grpcServer.getChannel(), 50, "gzip", tracingInterceptor); 244 | 245 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 246 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1)); 247 | assertEquals( 248 | "one span should have been created and finished for one client request", 249 | clientTracer.finishedSpans().size(), 250 | 1); 251 | 252 | MockSpan span = clientTracer.finishedSpans().get(0); 253 | assertEquals("span should have prefix", span.operationName(), "helloworld.Greeter/SayHello"); 254 | assertEquals("span should have no parents", span.parentId(), 0); 255 | assertEquals("span should have no logs", span.logEntries().size(), 0); 256 | Assertions.assertThat(span.tags()) 257 | .as("span should have base client tags") 258 | .containsAllEntriesOf(BASE_CLIENT_TAGS); 259 | Assertions.assertThat(span.tags().keySet()) 260 | .as("span should have tags for all client request attributes") 261 | .containsAll(CLIENT_ATTRIBUTE_TAGS); 262 | assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext()); 263 | } 264 | 265 | @Test 266 | public void testTracedClientwithActiveSpanSource() { 267 | final MockSpan parentSpan = clientTracer.buildSpan("parent").start(); 268 | ActiveSpanSource activeSpanSource = 269 | new ActiveSpanSource() { 270 | @Override 271 | public Span getActiveSpan() { 272 | return parentSpan; 273 | } 274 | }; 275 | TracingClientInterceptor tracingInterceptor = 276 | TracingClientInterceptor.newBuilder() 277 | .withTracer(clientTracer) 278 | .withActiveSpanSource(activeSpanSource) 279 | .build(); 280 | TracedClient client = new TracedClient(grpcServer.getChannel(), tracingInterceptor); 281 | 282 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 283 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1)); 284 | assertEquals( 285 | "one span should have been created and finished for one client request", 286 | clientTracer.finishedSpans().size(), 287 | 1); 288 | 289 | MockSpan span = clientTracer.finishedSpans().get(0); 290 | assertEquals("span should have prefix", span.operationName(), "helloworld.Greeter/SayHello"); 291 | assertEquals("span should have parent", span.parentId(), parentSpan.context().spanId()); 292 | assertEquals("span should have no logs", span.logEntries().size(), 0); 293 | Assertions.assertThat(span.tags()) 294 | .as("span should have base client tags") 295 | .isEqualTo(BASE_CLIENT_TAGS); 296 | assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext()); 297 | } 298 | 299 | @Test 300 | public void testTracedClientwithActiveSpanContextSource() { 301 | final MockSpan parentSpan = clientTracer.buildSpan("parent").start(); 302 | ActiveSpanContextSource activeSpanContextSource = 303 | new ActiveSpanContextSource() { 304 | @Override 305 | public SpanContext getActiveSpanContext() { 306 | return parentSpan.context(); 307 | } 308 | }; 309 | TracingClientInterceptor tracingInterceptor = 310 | TracingClientInterceptor.newBuilder() 311 | .withTracer(clientTracer) 312 | .withActiveSpanContextSource(activeSpanContextSource) 313 | .build(); 314 | TracedClient client = new TracedClient(grpcServer.getChannel(), tracingInterceptor); 315 | 316 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 317 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1)); 318 | assertEquals( 319 | "one span should have been created and finished for one client request", 320 | clientTracer.finishedSpans().size(), 321 | 1); 322 | 323 | MockSpan span = clientTracer.finishedSpans().get(0); 324 | assertEquals("span should have prefix", span.operationName(), "helloworld.Greeter/SayHello"); 325 | assertEquals("span should have parent", span.parentId(), parentSpan.context().spanId()); 326 | assertEquals("span should have no logs", span.logEntries().size(), 0); 327 | Assertions.assertThat(span.tags()) 328 | .as("span should have base client tags") 329 | .isEqualTo(BASE_CLIENT_TAGS); 330 | assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext()); 331 | } 332 | 333 | @Test 334 | public void testTracedClientWithClientSpanDecorator() { 335 | ClientSpanDecorator spanTagger = 336 | new ClientSpanDecorator() { 337 | @Override 338 | public void interceptCall(Span span, MethodDescriptor method, CallOptions callOptions) { 339 | span.setTag("test_tag", "test_value"); 340 | span.setTag("tag_from_method", method.getFullMethodName()); 341 | span.setTag("tag_from_call_options", callOptions.getCompressor()); 342 | } 343 | }; 344 | ClientSpanDecorator spanLogger = 345 | new ClientSpanDecorator() { 346 | @Override 347 | public void interceptCall(Span span, MethodDescriptor method, CallOptions callOptions) { 348 | span.log("A span log"); 349 | } 350 | }; 351 | 352 | TracingClientInterceptor tracingInterceptor = 353 | TracingClientInterceptor.newBuilder() 354 | .withTracer(clientTracer) 355 | .withClientSpanDecorator(spanTagger) 356 | .withClientSpanDecorator(spanLogger) 357 | .build(); 358 | TracedClient client = new TracedClient(grpcServer.getChannel(), tracingInterceptor); 359 | 360 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 361 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1)); 362 | assertEquals( 363 | "one span should have been created and finished for one client request", 364 | clientTracer.finishedSpans().size(), 365 | 1); 366 | 367 | MockSpan span = clientTracer.finishedSpans().get(0); 368 | assertEquals("span should have prefix", span.operationName(), "helloworld.Greeter/SayHello"); 369 | assertEquals("span should have no parents", span.parentId(), 0); 370 | assertEquals( 371 | "span should have one log from the decorator", 372 | span.logEntries().get(0).fields().get("event"), 373 | "A span log"); 374 | Assertions.assertThat(span.tags()) 375 | .as("span should have 3 tags from the decorator") 376 | .hasSize(3 + BASE_CLIENT_TAGS.size()); 377 | Assertions.assertThat(span.tags()) 378 | .as("span contains added tags") 379 | .containsEntry("test_tag", "test_value") 380 | .containsKeys("tag_from_method", "tag_from_call_options"); 381 | assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext()); 382 | } 383 | 384 | @Test 385 | public void testTracedClientWithClientCloseDecorator() { 386 | ClientCloseDecorator closeTagger = 387 | new ClientCloseDecorator() { 388 | @Override 389 | public void close(Span span, Status status, Metadata trailers) { 390 | span.setTag("some_tag", "some_value"); 391 | } 392 | }; 393 | ClientCloseDecorator closeLogger = 394 | new ClientCloseDecorator() { 395 | @Override 396 | public void close(Span span, Status status, Metadata trailers) { 397 | span.log("A close log"); 398 | } 399 | }; 400 | 401 | TracingClientInterceptor tracingInterceptor = 402 | TracingClientInterceptor.newBuilder() 403 | .withTracer(clientTracer) 404 | .withClientCloseDecorator(closeTagger) 405 | .withClientCloseDecorator(closeLogger) 406 | .build(); 407 | TracedClient client = new TracedClient(grpcServer.getChannel(), tracingInterceptor); 408 | 409 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 410 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1)); 411 | assertEquals( 412 | "one span should have been created and finished for one client request", 413 | clientTracer.finishedSpans().size(), 414 | 1); 415 | MockSpan span = clientTracer.finishedSpans().get(0); 416 | assertEquals( 417 | "span should have one log from the decorator", 418 | span.logEntries().get(0).fields().get("event"), 419 | "A close log"); 420 | Assertions.assertThat(span.tags()).containsEntry("some_tag", "some_value"); 421 | } 422 | 423 | private Callable reportedSpansSize(final MockTracer mockTracer) { 424 | return new Callable() { 425 | @Override 426 | public Integer call() { 427 | return mockTracer.finishedSpans().size(); 428 | } 429 | }; 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /src/test/java/io/opentracing/contrib/grpc/TracingInterceptorsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import static org.awaitility.Awaitility.await; 18 | import static org.hamcrest.CoreMatchers.equalTo; 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertNull; 21 | import static org.junit.Assert.assertTrue; 22 | 23 | import io.grpc.CallOptions; 24 | import io.grpc.Channel; 25 | import io.grpc.ClientCall; 26 | import io.grpc.ClientInterceptor; 27 | import io.grpc.ForwardingClientCall; 28 | import io.grpc.ForwardingClientCallListener; 29 | import io.grpc.ForwardingServerCall; 30 | import io.grpc.ForwardingServerCallListener; 31 | import io.grpc.Metadata; 32 | import io.grpc.MethodDescriptor; 33 | import io.grpc.ServerCall; 34 | import io.grpc.ServerCallHandler; 35 | import io.grpc.ServerInterceptor; 36 | import io.grpc.Status; 37 | import io.grpc.testing.GrpcServerRule; 38 | import io.opentracing.References; 39 | import io.opentracing.log.Fields; 40 | import io.opentracing.mock.MockSpan; 41 | import io.opentracing.mock.MockTracer; 42 | import io.opentracing.tag.Tags; 43 | import io.opentracing.util.GlobalTracer; 44 | import io.opentracing.util.GlobalTracerTestUtil; 45 | import java.util.ArrayList; 46 | import java.util.List; 47 | import java.util.concurrent.Callable; 48 | import java.util.concurrent.TimeUnit; 49 | import org.assertj.core.api.Assertions; 50 | import org.junit.Before; 51 | import org.junit.Rule; 52 | import org.junit.Test; 53 | 54 | public class TracingInterceptorsTest { 55 | 56 | private final MockTracer clientTracer = new MockTracer(); 57 | private final MockTracer serverTracer = new MockTracer(); 58 | 59 | @Rule 60 | public GrpcServerRule grpcServer = new GrpcServerRule(); 61 | 62 | @Before 63 | public void setUp() { 64 | GlobalTracerTestUtil.resetGlobalTracer(); 65 | clientTracer.reset(); 66 | serverTracer.reset(); 67 | // register server tracer to verify active span on server side 68 | GlobalTracer.registerIfAbsent(serverTracer); 69 | } 70 | 71 | @Test 72 | public void testTracedClientAndServerSuccess() { 73 | TracingClientInterceptor clientInterceptor = 74 | TracingClientInterceptor.newBuilder() 75 | .withTracer(clientTracer) 76 | .withVerbosity() 77 | .build(); 78 | TracedClient client = new TracedClient(grpcServer.getChannel(), clientInterceptor); 79 | 80 | TracingServerInterceptor serverInterceptor = 81 | TracingServerInterceptor.newBuilder() 82 | .withTracer(serverTracer) 83 | .withVerbosity() 84 | .build(); 85 | TracedService.addGeeterService(grpcServer.getServiceRegistry(), serverInterceptor); 86 | 87 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 88 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1)); 89 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(serverTracer), equalTo(1)); 90 | 91 | assertEquals( 92 | "a client span should have been created for the request", 93 | 1, 94 | clientTracer.finishedSpans().size()); 95 | MockSpan clientSpan = clientTracer.finishedSpans().get(0); 96 | List clientEvents = new ArrayList<>(clientSpan.logEntries().size()); 97 | for (MockSpan.LogEntry logEntry : clientSpan.logEntries()) { 98 | clientEvents.add((String) logEntry.fields().get(Fields.EVENT)); 99 | } 100 | Assertions.assertThat(clientEvents) 101 | .as("client span should contain verbose log fields") 102 | .containsExactly( 103 | GrpcFields.CLIENT_CALL_START, 104 | GrpcFields.CLIENT_CALL_SEND_MESSAGE, 105 | GrpcFields.CLIENT_CALL_HALF_CLOSE, 106 | GrpcFields.CLIENT_CALL_LISTENER_ON_HEADERS, 107 | GrpcFields.CLIENT_CALL_LISTENER_ON_MESSAGE, 108 | GrpcFields.CLIENT_CALL_LISTENER_ON_CLOSE); 109 | Assertions.assertThat(clientSpan.tags()) 110 | .as("client span grpc.status tag should equal OK") 111 | .containsEntry(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT) 112 | .containsEntry(Tags.COMPONENT.getKey(), GrpcTags.COMPONENT_NAME) 113 | .containsEntry(GrpcTags.GRPC_STATUS.getKey(), Status.Code.OK.name()); 114 | 115 | assertEquals( 116 | "a server span should have been created for the request", 117 | 1, 118 | serverTracer.finishedSpans().size()); 119 | MockSpan serverSpan = serverTracer.finishedSpans().get(0); 120 | List serverEvents = new ArrayList<>(serverSpan.logEntries().size()); 121 | for (MockSpan.LogEntry logEntry : serverSpan.logEntries()) { 122 | serverEvents.add((String) logEntry.fields().get(Fields.EVENT)); 123 | } 124 | Assertions.assertThat(serverEvents) 125 | .as("server span should contain verbose log fields") 126 | .containsExactly( 127 | GrpcFields.SERVER_CALL_LISTENER_ON_MESSAGE, 128 | GrpcFields.SERVER_CALL_LISTENER_ON_HALF_CLOSE, 129 | GrpcFields.SERVER_CALL_SEND_HEADERS, 130 | GrpcFields.SERVER_CALL_SEND_MESSAGE, 131 | GrpcFields.SERVER_CALL_CLOSE, 132 | GrpcFields.SERVER_CALL_LISTENER_ON_COMPLETE); 133 | Assertions.assertThat(serverSpan.tags()) 134 | .as("server span grpc.status tag should equal OK") 135 | .containsEntry(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) 136 | .containsEntry(Tags.COMPONENT.getKey(), GrpcTags.COMPONENT_NAME) 137 | .containsEntry(GrpcTags.GRPC_STATUS.getKey(), Status.Code.OK.name()); 138 | 139 | assertEquals( 140 | "client and server spans should be part of the same trace", 141 | clientSpan.context().traceId(), 142 | serverSpan.context().traceId()); 143 | assertEquals( 144 | "client span should be parent of server span", 145 | clientSpan.context().spanId(), 146 | serverSpan.parentId()); 147 | assertEquals( 148 | "server span should be child of client span", 149 | References.CHILD_OF, 150 | serverSpan.references().get(0).getReferenceType()); 151 | 152 | assertTrue( 153 | "client span should be longer than the server span", 154 | (clientSpan.finishMicros() - clientSpan.startMicros()) 155 | >= (serverSpan.finishMicros() - serverSpan.startMicros())); 156 | } 157 | 158 | @Test 159 | public void testTracedClientSendError() { 160 | TracingClientInterceptor clientInterceptor = 161 | TracingClientInterceptor.newBuilder() 162 | .withTracer(clientTracer) 163 | .withVerbosity() 164 | .build(); 165 | ClientInterceptor clientSendError = 166 | new ClientInterceptor() { 167 | @Override 168 | public ClientCall interceptCall( 169 | MethodDescriptor method, CallOptions callOptions, Channel next) { 170 | return new ForwardingClientCall.SimpleForwardingClientCall( 171 | next.newCall(method, callOptions)) { 172 | @Override 173 | public void sendMessage(ReqT message) { 174 | throw new RuntimeException("client send error"); 175 | } 176 | }; 177 | } 178 | }; 179 | TracedClient client = 180 | new TracedClient(grpcServer.getChannel(), clientSendError, clientInterceptor); 181 | 182 | TracingServerInterceptor serverInterceptor = 183 | TracingServerInterceptor.newBuilder() 184 | .withTracer(serverTracer) 185 | .withVerbosity() 186 | .build(); 187 | TracedService.addGeeterService(grpcServer.getServiceRegistry(), serverInterceptor); 188 | 189 | assertNull("call should return null", client.greet()); 190 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1)); 191 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(serverTracer), equalTo(1)); 192 | 193 | assertEquals( 194 | "a client span should have been created for the request", 195 | 1, 196 | clientTracer.finishedSpans().size()); 197 | MockSpan clientSpan = clientTracer.finishedSpans().get(0); 198 | List clientEvents = new ArrayList<>(clientSpan.logEntries().size()); 199 | for (MockSpan.LogEntry logEntry : clientSpan.logEntries()) { 200 | clientEvents.add((String) logEntry.fields().get(Fields.EVENT)); 201 | } 202 | Assertions.assertThat(clientEvents) 203 | .as("client span should contain verbose log fields") 204 | .containsExactly( 205 | GrpcFields.CLIENT_CALL_START, 206 | GrpcFields.CLIENT_CALL_SEND_MESSAGE, 207 | GrpcFields.CLIENT_CALL_CANCEL, 208 | // TODO: onClose is not called due to bug: https://github.com/grpc/grpc-java/issues/5576 209 | // GrpcFields.CLIENT_CALL_LISTENER_ON_CLOSE, 210 | GrpcFields.ERROR); 211 | Assertions.assertThat(clientSpan.tags()) 212 | .as("client span grpc.status tag should equal UNKNOWN") 213 | .containsEntry(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT) 214 | .containsEntry(Tags.COMPONENT.getKey(), GrpcTags.COMPONENT_NAME) 215 | .containsEntry(GrpcTags.GRPC_STATUS.getKey(), Status.Code.UNKNOWN.name()); 216 | 217 | assertEquals( 218 | "a server span should have been created for the request", 219 | 1, 220 | serverTracer.finishedSpans().size()); 221 | MockSpan serverSpan = serverTracer.finishedSpans().get(0); 222 | List serverEvents = new ArrayList<>(serverSpan.logEntries().size()); 223 | for (MockSpan.LogEntry logEntry : serverSpan.logEntries()) { 224 | serverEvents.add((String) logEntry.fields().get(Fields.EVENT)); 225 | } 226 | Assertions.assertThat(serverEvents) 227 | .as("server span should contain verbose log fields") 228 | .containsExactly(GrpcFields.SERVER_CALL_LISTENER_ON_CANCEL); 229 | Assertions.assertThat(serverSpan.tags()) 230 | .as("server span grpc.status tag should equal CANCELLED") 231 | .containsEntry(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) 232 | .containsEntry(Tags.COMPONENT.getKey(), GrpcTags.COMPONENT_NAME) 233 | .containsEntry(GrpcTags.GRPC_STATUS.getKey(), Status.Code.CANCELLED.name()); 234 | } 235 | 236 | @Test 237 | public void testTracedClientReceiveError() { 238 | TracingClientInterceptor clientInterceptor = 239 | TracingClientInterceptor.newBuilder() 240 | .withTracer(clientTracer) 241 | .withVerbosity() 242 | .build(); 243 | ClientInterceptor clientReceiveError = 244 | new ClientInterceptor() { 245 | @Override 246 | public ClientCall interceptCall( 247 | MethodDescriptor method, CallOptions callOptions, Channel next) { 248 | return new ForwardingClientCall.SimpleForwardingClientCall( 249 | next.newCall(method, callOptions)) { 250 | @Override 251 | public void start(Listener responseListener, Metadata headers) { 252 | delegate() 253 | .start( 254 | new ForwardingClientCallListener.SimpleForwardingClientCallListener( 255 | responseListener) { 256 | @Override 257 | public void onMessage(RespT message) { 258 | throw new RuntimeException("client receive error"); 259 | } 260 | }, 261 | headers); 262 | } 263 | }; 264 | } 265 | }; 266 | TracedClient client = 267 | new TracedClient(grpcServer.getChannel(), clientReceiveError, clientInterceptor); 268 | 269 | TracingServerInterceptor serverInterceptor = 270 | TracingServerInterceptor.newBuilder() 271 | .withTracer(serverTracer) 272 | .withVerbosity() 273 | .build(); 274 | TracedService.addGeeterService(grpcServer.getServiceRegistry(), serverInterceptor); 275 | 276 | assertNull("call should return null", client.greet()); 277 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1)); 278 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(serverTracer), equalTo(1)); 279 | 280 | assertEquals( 281 | "a client span should have been created for the request", 282 | 1, 283 | clientTracer.finishedSpans().size()); 284 | MockSpan clientSpan = clientTracer.finishedSpans().get(0); 285 | List clientEvents = new ArrayList<>(clientSpan.logEntries().size()); 286 | for (MockSpan.LogEntry logEntry : clientSpan.logEntries()) { 287 | clientEvents.add((String) logEntry.fields().get(Fields.EVENT)); 288 | } 289 | Assertions.assertThat(clientEvents) 290 | .as("client span should contain verbose log fields") 291 | .containsExactly( 292 | GrpcFields.CLIENT_CALL_START, 293 | GrpcFields.CLIENT_CALL_SEND_MESSAGE, 294 | GrpcFields.CLIENT_CALL_HALF_CLOSE, 295 | GrpcFields.CLIENT_CALL_LISTENER_ON_HEADERS, 296 | GrpcFields.CLIENT_CALL_LISTENER_ON_CLOSE, 297 | GrpcFields.ERROR); 298 | Assertions.assertThat(clientSpan.tags()) 299 | .as("client span grpc.status tag should equal CANCELLED") 300 | .containsEntry(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT) 301 | .containsEntry(Tags.COMPONENT.getKey(), GrpcTags.COMPONENT_NAME) 302 | .containsEntry(GrpcTags.GRPC_STATUS.getKey(), Status.Code.CANCELLED.name()); 303 | 304 | assertEquals( 305 | "a server span should have been created for the request", 306 | 1, 307 | serverTracer.finishedSpans().size()); 308 | MockSpan serverSpan = serverTracer.finishedSpans().get(0); 309 | List serverEvents = new ArrayList<>(serverSpan.logEntries().size()); 310 | for (MockSpan.LogEntry logEntry : serverSpan.logEntries()) { 311 | serverEvents.add((String) logEntry.fields().get(Fields.EVENT)); 312 | } 313 | Assertions.assertThat(serverEvents) 314 | .as("server span should contain verbose log fields") 315 | .containsExactly( 316 | GrpcFields.SERVER_CALL_LISTENER_ON_MESSAGE, 317 | GrpcFields.SERVER_CALL_LISTENER_ON_HALF_CLOSE, 318 | GrpcFields.SERVER_CALL_SEND_HEADERS, 319 | GrpcFields.SERVER_CALL_SEND_MESSAGE, 320 | GrpcFields.SERVER_CALL_CLOSE, 321 | GrpcFields.SERVER_CALL_LISTENER_ON_COMPLETE); 322 | Assertions.assertThat(serverSpan.tags()) 323 | .as("server span grpc.status tag should equal OK") 324 | .containsEntry(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) 325 | .containsEntry(Tags.COMPONENT.getKey(), GrpcTags.COMPONENT_NAME) 326 | .containsEntry(GrpcTags.GRPC_STATUS.getKey(), Status.Code.OK.name()); 327 | } 328 | 329 | @Test 330 | public void testTracedServerReceiveError() { 331 | TracingClientInterceptor clientInterceptor = 332 | TracingClientInterceptor.newBuilder() 333 | .withTracer(clientTracer) 334 | .withVerbosity() 335 | .build(); 336 | TracedClient client = new TracedClient(grpcServer.getChannel(), clientInterceptor); 337 | 338 | TracingServerInterceptor serverInterceptor = 339 | TracingServerInterceptor.newBuilder() 340 | .withTracer(serverTracer) 341 | .withVerbosity() 342 | .build(); 343 | ServerInterceptor serverReceiveError = 344 | new ServerInterceptor() { 345 | @Override 346 | public ServerCall.Listener interceptCall( 347 | ServerCall call, Metadata headers, ServerCallHandler next) { 348 | return new ForwardingServerCallListener.SimpleForwardingServerCallListener( 349 | next.startCall(call, headers)) { 350 | @Override 351 | public void onMessage(ReqT message) { 352 | throw new RuntimeException("server receive error"); 353 | } 354 | }; 355 | } 356 | }; 357 | TracedService.addGeeterService( 358 | grpcServer.getServiceRegistry(), serverReceiveError, serverInterceptor); 359 | 360 | assertNull("call should return null", client.greet()); 361 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1)); 362 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(serverTracer), equalTo(1)); 363 | 364 | assertEquals( 365 | "a client span should have been created for the request", 366 | 1, 367 | clientTracer.finishedSpans().size()); 368 | MockSpan clientSpan = clientTracer.finishedSpans().get(0); 369 | List clientEvents = new ArrayList<>(clientSpan.logEntries().size()); 370 | for (MockSpan.LogEntry logEntry : clientSpan.logEntries()) { 371 | clientEvents.add((String) logEntry.fields().get(Fields.EVENT)); 372 | } 373 | Assertions.assertThat(clientEvents) 374 | .as("client span should contain verbose log fields") 375 | .containsExactly( 376 | GrpcFields.CLIENT_CALL_START, 377 | GrpcFields.CLIENT_CALL_SEND_MESSAGE, 378 | GrpcFields.CLIENT_CALL_HALF_CLOSE, 379 | GrpcFields.CLIENT_CALL_LISTENER_ON_CLOSE, 380 | GrpcFields.ERROR); 381 | Assertions.assertThat(clientSpan.tags()) 382 | .as("client span grpc.status tag should equal UNKNOWN") 383 | .containsEntry(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT) 384 | .containsEntry(Tags.COMPONENT.getKey(), GrpcTags.COMPONENT_NAME) 385 | .containsEntry(GrpcTags.GRPC_STATUS.getKey(), Status.Code.UNKNOWN.name()); 386 | 387 | assertEquals( 388 | "a server span should have been created for the request", 389 | 1, 390 | serverTracer.finishedSpans().size()); 391 | MockSpan serverSpan = serverTracer.finishedSpans().get(0); 392 | List serverEvents = new ArrayList<>(serverSpan.logEntries().size()); 393 | for (MockSpan.LogEntry logEntry : serverSpan.logEntries()) { 394 | serverEvents.add((String) logEntry.fields().get(Fields.EVENT)); 395 | } 396 | // The RuntimeException schedules a task to execute the closed/onComplete call path. 397 | // There is a race between when the closed/onComplete call path is executed and when 398 | // the halfClose/close call path is executed. Commenting out the lines below since 399 | // there is no guarantee that halfClose/close will run and write the error status. 400 | Assertions.assertThat(serverEvents) 401 | .as("server span should contain verbose log fields") 402 | .contains( 403 | GrpcFields.SERVER_CALL_LISTENER_ON_MESSAGE, 404 | // GrpcFields.SERVER_CALL_LISTENER_ON_HALF_CLOSE, 405 | // GrpcFields.SERVER_CALL_CLOSE, 406 | // GrpcFields.ERROR, 407 | GrpcFields.SERVER_CALL_LISTENER_ON_COMPLETE); 408 | Assertions.assertThat(serverSpan.tags()) 409 | .as("server span grpc.status tag should equal INTERNAL") 410 | .containsEntry(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) 411 | .containsEntry(Tags.COMPONENT.getKey(), GrpcTags.COMPONENT_NAME); 412 | // .containsEntry(GrpcTags.GRPC_STATUS.getKey(), Status.Code.INTERNAL.name()); 413 | } 414 | 415 | @Test 416 | public void testTracedServerSendError() { 417 | TracingClientInterceptor clientInterceptor = 418 | TracingClientInterceptor.newBuilder() 419 | .withTracer(clientTracer) 420 | .withVerbosity() 421 | .build(); 422 | TracedClient client = new TracedClient(grpcServer.getChannel(), clientInterceptor); 423 | 424 | TracingServerInterceptor serverInterceptor = 425 | TracingServerInterceptor.newBuilder() 426 | .withTracer(serverTracer) 427 | .withVerbosity() 428 | .build(); 429 | ServerInterceptor serverSendError = 430 | new ServerInterceptor() { 431 | @Override 432 | public ServerCall.Listener interceptCall( 433 | ServerCall call, Metadata headers, ServerCallHandler next) { 434 | return next.startCall( 435 | new ForwardingServerCall.SimpleForwardingServerCall(call) { 436 | @Override 437 | public void sendMessage(RespT message) { 438 | throw new RuntimeException("server send error"); 439 | } 440 | }, 441 | headers); 442 | } 443 | }; 444 | TracedService.addGeeterService( 445 | grpcServer.getServiceRegistry(), serverSendError, serverInterceptor); 446 | 447 | assertNull("call should return null", client.greet()); 448 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(clientTracer), equalTo(1)); 449 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(serverTracer), equalTo(1)); 450 | 451 | assertEquals( 452 | "a client span should have been created for the request", 453 | 1, 454 | clientTracer.finishedSpans().size()); 455 | MockSpan clientSpan = clientTracer.finishedSpans().get(0); 456 | List clientEvents = new ArrayList<>(clientSpan.logEntries().size()); 457 | for (MockSpan.LogEntry logEntry : clientSpan.logEntries()) { 458 | clientEvents.add((String) logEntry.fields().get(Fields.EVENT)); 459 | } 460 | Assertions.assertThat(clientEvents) 461 | .as("client span should contain verbose log fields") 462 | .containsExactly( 463 | GrpcFields.CLIENT_CALL_START, 464 | GrpcFields.CLIENT_CALL_SEND_MESSAGE, 465 | GrpcFields.CLIENT_CALL_HALF_CLOSE, 466 | GrpcFields.CLIENT_CALL_LISTENER_ON_HEADERS, 467 | GrpcFields.CLIENT_CALL_LISTENER_ON_CLOSE, 468 | GrpcFields.ERROR); 469 | Assertions.assertThat(clientSpan.tags()) 470 | .as("client span grpc.status tag should equal UNKNOWN") 471 | .containsEntry(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT) 472 | .containsEntry(Tags.COMPONENT.getKey(), GrpcTags.COMPONENT_NAME) 473 | .containsEntry(GrpcTags.GRPC_STATUS.getKey(), Status.Code.UNKNOWN.name()); 474 | 475 | assertEquals( 476 | "a server span should have been created for the request", 477 | 1, 478 | serverTracer.finishedSpans().size()); 479 | MockSpan serverSpan = serverTracer.finishedSpans().get(0); 480 | List serverEvents = new ArrayList<>(serverSpan.logEntries().size()); 481 | for (MockSpan.LogEntry logEntry : serverSpan.logEntries()) { 482 | serverEvents.add((String) logEntry.fields().get(Fields.EVENT)); 483 | } 484 | Assertions.assertThat(serverEvents) 485 | .as("server span should contain verbose log fields") 486 | .containsExactly( 487 | GrpcFields.SERVER_CALL_LISTENER_ON_MESSAGE, 488 | GrpcFields.SERVER_CALL_LISTENER_ON_HALF_CLOSE, 489 | GrpcFields.SERVER_CALL_SEND_HEADERS, 490 | GrpcFields.SERVER_CALL_LISTENER_ON_COMPLETE); 491 | Assertions.assertThat(serverSpan.tags()) 492 | .as("server span grpc.status tag should be missing") 493 | .containsEntry(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) 494 | .containsEntry(Tags.COMPONENT.getKey(), GrpcTags.COMPONENT_NAME) 495 | .doesNotContainKey(GrpcTags.GRPC_STATUS.getKey()); 496 | } 497 | 498 | private Callable reportedSpansSize(final MockTracer mockTracer) { 499 | return new Callable() { 500 | @Override 501 | public Integer call() { 502 | return mockTracer.finishedSpans().size(); 503 | } 504 | }; 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /src/test/java/io/opentracing/contrib/grpc/TracingServerInterceptorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 The OpenTracing Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.opentracing.contrib.grpc; 16 | 17 | import static org.awaitility.Awaitility.await; 18 | import static org.hamcrest.CoreMatchers.instanceOf; 19 | import static org.hamcrest.core.IsEqual.equalTo; 20 | import static org.junit.Assert.assertEquals; 21 | import static org.junit.Assert.assertFalse; 22 | import static org.junit.Assert.assertNotNull; 23 | import static org.junit.Assert.assertThat; 24 | import static org.junit.Assert.assertTrue; 25 | import static org.mockito.ArgumentMatchers.any; 26 | import static org.mockito.ArgumentMatchers.eq; 27 | import static org.mockito.Mockito.doReturn; 28 | import static org.mockito.Mockito.doThrow; 29 | import static org.mockito.Mockito.spy; 30 | 31 | import com.google.common.collect.ImmutableMap; 32 | import com.google.common.collect.ImmutableSet; 33 | import io.grpc.Metadata; 34 | import io.grpc.MethodDescriptor; 35 | import io.grpc.ServerCall; 36 | import io.grpc.Status; 37 | import io.grpc.testing.GrpcServerRule; 38 | import io.opentracing.Span; 39 | import io.opentracing.log.Fields; 40 | import io.opentracing.mock.MockSpan; 41 | import io.opentracing.mock.MockTracer; 42 | import io.opentracing.propagation.Format; 43 | import io.opentracing.propagation.TextMapAdapter; 44 | import io.opentracing.tag.Tags; 45 | import io.opentracing.util.GlobalTracerTestUtil; 46 | import java.util.ArrayList; 47 | import java.util.Collections; 48 | import java.util.List; 49 | import java.util.Map; 50 | import java.util.Set; 51 | import java.util.concurrent.Callable; 52 | import java.util.concurrent.TimeUnit; 53 | import org.assertj.core.api.Assertions; 54 | import org.junit.Before; 55 | import org.junit.Rule; 56 | import org.junit.Test; 57 | 58 | public class TracingServerInterceptorTest { 59 | 60 | private static final String PREFIX = "testing-"; 61 | 62 | private static final Map BASE_TAGS = 63 | ImmutableMap.builder() 64 | .put(Tags.COMPONENT.getKey(), GrpcTags.COMPONENT_NAME) 65 | .put(GrpcTags.GRPC_STATUS.getKey(), Status.Code.OK.name()) 66 | .build(); 67 | 68 | private static final Map BASE_SERVER_TAGS = 69 | ImmutableMap.builder() 70 | .putAll(BASE_TAGS) 71 | .put(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) 72 | .build(); 73 | 74 | private static final Set SERVER_ATTRIBUTE_TAGS = 75 | ImmutableSet.of( 76 | GrpcTags.GRPC_METHOD_TYPE.getKey(), 77 | GrpcTags.GRPC_METHOD_NAME.getKey(), 78 | GrpcTags.GRPC_CALL_ATTRIBUTES.getKey(), 79 | GrpcTags.GRPC_HEADERS.getKey(), 80 | GrpcTags.PEER_ADDRESS.getKey()); 81 | 82 | private final MockTracer serverTracer = new MockTracer(); 83 | 84 | @Rule 85 | public GrpcServerRule grpcServer = new GrpcServerRule(); 86 | 87 | private TracedClient client; 88 | 89 | @Before 90 | public void setUp() { 91 | GlobalTracerTestUtil.resetGlobalTracer(); 92 | serverTracer.reset(); 93 | client = new TracedClient(grpcServer.getChannel()); 94 | } 95 | 96 | @Test 97 | public void testTracedServerBasic() { 98 | TracingServerInterceptor tracingInterceptor = 99 | TracingServerInterceptor.newBuilder().withTracer(serverTracer).build(); 100 | TracedService.addGeeterService(grpcServer.getServiceRegistry(), tracingInterceptor); 101 | 102 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 103 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(serverTracer), equalTo(1)); 104 | assertEquals( 105 | "one span should have been created and finished for one client request", 106 | serverTracer.finishedSpans().size(), 107 | 1); 108 | 109 | MockSpan span = serverTracer.finishedSpans().get(0); 110 | assertEquals( 111 | "span should have default name", span.operationName(), "helloworld.Greeter/SayHello"); 112 | assertEquals("span should have no parents", span.parentId(), 0); 113 | assertTrue("span should have no logs", span.logEntries().isEmpty()); 114 | Assertions.assertThat(span.tags()) 115 | .as("span should have base server tags") 116 | .isEqualTo(BASE_SERVER_TAGS); 117 | assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext()); 118 | } 119 | 120 | @Test 121 | public void testTracedServerTwoInterceptors() { 122 | TracingServerInterceptor tracingInterceptor = 123 | TracingServerInterceptor.newBuilder().withTracer(serverTracer).build(); 124 | SecondServerInterceptor secondServerInterceptor = new SecondServerInterceptor(serverTracer); 125 | TracedService.addGeeterService( 126 | grpcServer.getServiceRegistry(), secondServerInterceptor, tracingInterceptor); 127 | 128 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 129 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(serverTracer), equalTo(1)); 130 | assertEquals( 131 | "one span should have been created and finished for one client request", 132 | serverTracer.finishedSpans().size(), 133 | 1); 134 | 135 | MockSpan span = serverTracer.finishedSpans().get(0); 136 | assertEquals( 137 | "span should have default name", span.operationName(), "helloworld.Greeter/SayHello"); 138 | assertEquals("span should have no parents", span.parentId(), 0); 139 | assertTrue("span should have no logs", span.logEntries().isEmpty()); 140 | Assertions.assertThat(span.tags()) 141 | .as("span should have base server tags") 142 | .isEqualTo(BASE_SERVER_TAGS); 143 | assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext()); 144 | } 145 | 146 | @Test 147 | public void testTracedServerWithVerbosity() { 148 | TracingServerInterceptor tracingInterceptor = 149 | TracingServerInterceptor.newBuilder().withTracer(serverTracer).withVerbosity().build(); 150 | TracedService.addGeeterService(grpcServer.getServiceRegistry(), tracingInterceptor); 151 | 152 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 153 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(serverTracer), equalTo(1)); 154 | assertEquals( 155 | "one span should have been created and finished for one client request", 156 | serverTracer.finishedSpans().size(), 157 | 1); 158 | 159 | MockSpan span = serverTracer.finishedSpans().get(0); 160 | assertEquals( 161 | "span should have default name", span.operationName(), "helloworld.Greeter/SayHello"); 162 | assertEquals("span should have no parents", span.parentId(), 0); 163 | List events = new ArrayList<>(span.logEntries().size()); 164 | for (MockSpan.LogEntry logEntry : span.logEntries()) { 165 | events.add((String) logEntry.fields().get(Fields.EVENT)); 166 | } 167 | Assertions.assertThat(events) 168 | .as("span should contain verbose log fields") 169 | .contains( 170 | GrpcFields.SERVER_CALL_LISTENER_ON_MESSAGE, 171 | GrpcFields.SERVER_CALL_LISTENER_ON_HALF_CLOSE, 172 | GrpcFields.SERVER_CALL_SEND_HEADERS, 173 | GrpcFields.SERVER_CALL_SEND_MESSAGE, 174 | GrpcFields.SERVER_CALL_CLOSE, 175 | GrpcFields.SERVER_CALL_LISTENER_ON_COMPLETE); 176 | Assertions.assertThat(span.tags()) 177 | .as("span should have base server tags") 178 | .isEqualTo(BASE_SERVER_TAGS); 179 | assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext()); 180 | } 181 | 182 | @Test 183 | public void testTracedServerWithStreaming() { 184 | TracingServerInterceptor tracingInterceptor = 185 | TracingServerInterceptor.newBuilder().withTracer(serverTracer).withStreaming().build(); 186 | TracedService.addGeeterService(grpcServer.getServiceRegistry(), tracingInterceptor); 187 | 188 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 189 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(serverTracer), equalTo(1)); 190 | assertEquals( 191 | "one span should have been created and finished for one client request", 192 | serverTracer.finishedSpans().size(), 193 | 1); 194 | 195 | MockSpan span = serverTracer.finishedSpans().get(0); 196 | assertEquals( 197 | "span should have default name", span.operationName(), "helloworld.Greeter/SayHello"); 198 | assertEquals("span should have no parents", span.parentId(), 0); 199 | List events = new ArrayList<>(span.logEntries().size()); 200 | for (MockSpan.LogEntry logEntry : span.logEntries()) { 201 | events.add((String) logEntry.fields().get(Fields.EVENT)); 202 | } 203 | Assertions.assertThat(events) 204 | .as("span should contain streaming log fields") 205 | .contains( 206 | GrpcFields.SERVER_CALL_LISTENER_ON_MESSAGE, 207 | GrpcFields.SERVER_CALL_LISTENER_ON_HALF_CLOSE, 208 | GrpcFields.SERVER_CALL_SEND_MESSAGE); 209 | Assertions.assertThat(span.tags()) 210 | .as("span should have base server tags") 211 | .isEqualTo(BASE_SERVER_TAGS); 212 | assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext()); 213 | } 214 | 215 | @Test 216 | public void testTracedServerWithCustomOperationName() { 217 | TracingServerInterceptor tracingInterceptor = 218 | TracingServerInterceptor.newBuilder() 219 | .withTracer(serverTracer) 220 | .withOperationName( 221 | new OperationNameConstructor() { 222 | @Override 223 | public String constructOperationName( 224 | MethodDescriptor method) { 225 | return PREFIX + method.getFullMethodName(); 226 | } 227 | }) 228 | .build(); 229 | TracedService.addGeeterService(grpcServer.getServiceRegistry(), tracingInterceptor); 230 | 231 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 232 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(serverTracer), equalTo(1)); 233 | assertEquals( 234 | "one span should have been created and finished for one client request", 235 | serverTracer.finishedSpans().size(), 236 | 1); 237 | 238 | MockSpan span = serverTracer.finishedSpans().get(0); 239 | assertEquals( 240 | "span should have prefix", span.operationName(), PREFIX + "helloworld.Greeter/SayHello"); 241 | assertEquals("span should have no parents", span.parentId(), 0); 242 | assertEquals("span should have no logs", span.logEntries().size(), 0); 243 | Assertions.assertThat(span.tags()) 244 | .as("span should have base server tags") 245 | .isEqualTo(BASE_SERVER_TAGS); 246 | assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext()); 247 | } 248 | 249 | @Test 250 | public void testTracedServerWithTracedAttributes() { 251 | TracingServerInterceptor tracingInterceptor = 252 | TracingServerInterceptor.newBuilder() 253 | .withTracer(serverTracer) 254 | .withTracedAttributes(TracingServerInterceptor.ServerRequestAttribute.values()) 255 | .build(); 256 | TracedService.addGeeterService(grpcServer.getServiceRegistry(), tracingInterceptor); 257 | 258 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 259 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(serverTracer), equalTo(1)); 260 | assertEquals( 261 | "one span should have been created and finished for one client request", 262 | serverTracer.finishedSpans().size(), 263 | 1); 264 | 265 | MockSpan span = serverTracer.finishedSpans().get(0); 266 | assertEquals("span should have prefix", span.operationName(), "helloworld.Greeter/SayHello"); 267 | assertEquals("span should have no parents", span.parentId(), 0); 268 | assertEquals("span should have no logs", span.logEntries().size(), 0); 269 | Assertions.assertThat(span.tags()) 270 | .as("span should have base server tags") 271 | .containsAllEntriesOf(BASE_SERVER_TAGS); 272 | Assertions.assertThat(span.tags().keySet()) 273 | .as("span should have tags for all server request attributes") 274 | .containsAll(SERVER_ATTRIBUTE_TAGS); 275 | assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext()); 276 | } 277 | 278 | @Test 279 | public void testTracedServerWithServerSpanDecorator() { 280 | ServerSpanDecorator spanTagger = 281 | new ServerSpanDecorator() { 282 | @Override 283 | public void interceptCall(Span span, ServerCall call, Metadata headers) { 284 | span.setTag("test_tag", "test_value"); 285 | span.setTag("tag_from_call", call.getAuthority()); 286 | span.setTag("tag_from_headers", headers.toString()); 287 | } 288 | }; 289 | ServerSpanDecorator spanLogger = 290 | new ServerSpanDecorator() { 291 | @Override 292 | public void interceptCall(Span span, ServerCall call, Metadata headers) { 293 | span.log("A span log"); 294 | } 295 | }; 296 | 297 | TracingServerInterceptor tracingInterceptor = 298 | TracingServerInterceptor.newBuilder() 299 | .withTracer(serverTracer) 300 | .withServerSpanDecorator(spanTagger) 301 | .withServerSpanDecorator(spanLogger) 302 | .build(); 303 | TracedService.addGeeterService(grpcServer.getServiceRegistry(), tracingInterceptor); 304 | 305 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 306 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(serverTracer), equalTo(1)); 307 | assertEquals( 308 | "one span should have been created and finished for one client request", 309 | serverTracer.finishedSpans().size(), 310 | 1); 311 | 312 | MockSpan span = serverTracer.finishedSpans().get(0); 313 | assertEquals("span should have prefix", span.operationName(), "helloworld.Greeter/SayHello"); 314 | assertEquals("span should have no parents", span.parentId(), 0); 315 | assertEquals( 316 | "span should have one log added in the decorator", 317 | span.logEntries().get(0).fields().get("event"), 318 | "A span log"); 319 | Assertions.assertThat(span.tags()) 320 | .as("span should have 3 tags added in the decorator") 321 | .hasSize(3 + BASE_SERVER_TAGS.size()); 322 | Assertions.assertThat(span.tags()) 323 | .as("span contains added tags") 324 | .containsEntry("test_tag", "test_value") 325 | .containsKeys("tag_from_call", "tag_from_headers"); 326 | assertFalse("span should have no baggage", span.context().baggageItems().iterator().hasNext()); 327 | } 328 | 329 | @Test 330 | public void testTracedServerWithServerCloseDecorator() { 331 | ServerCloseDecorator closeTagger = 332 | new ServerCloseDecorator() { 333 | @Override 334 | public void close(Span span, Status status, Metadata trailers) { 335 | span.setTag("some_tag", "some_value"); 336 | } 337 | }; 338 | ServerCloseDecorator closeLogger = 339 | new ServerCloseDecorator() { 340 | @Override 341 | public void close(Span span, Status status, Metadata trailers) { 342 | span.log("A close log"); 343 | } 344 | }; 345 | TracingServerInterceptor tracingInterceptor = 346 | TracingServerInterceptor.newBuilder() 347 | .withTracer(serverTracer) 348 | .withServerCloseDecorator(closeTagger) 349 | .withServerCloseDecorator(closeLogger) 350 | .build(); 351 | TracedService.addGeeterService(grpcServer.getServiceRegistry(), tracingInterceptor); 352 | 353 | assertEquals("call should complete successfully", "Hello world", client.greet().getMessage()); 354 | await().atMost(5, TimeUnit.SECONDS).until(reportedSpansSize(serverTracer), equalTo(1)); 355 | assertEquals( 356 | "one span should have been created and finished for one client request", 357 | serverTracer.finishedSpans().size(), 358 | 1); 359 | 360 | MockSpan span = serverTracer.finishedSpans().get(0); 361 | assertEquals( 362 | "span should have one log from the decorator", 363 | span.logEntries().get(0).fields().get("event"), 364 | "A close log"); 365 | Assertions.assertThat(span.tags()).containsEntry("some_tag", "some_value"); 366 | } 367 | 368 | @Test 369 | public void testGetSpanFromHeaders() { 370 | long traceID = 1; 371 | long spanID = 2; 372 | MockTracer spyTracer = spy(serverTracer); 373 | doReturn(new MockSpan.MockContext(traceID, spanID, Collections.emptyMap())) 374 | .when(spyTracer) 375 | .extract(eq(Format.Builtin.HTTP_HEADERS), any(TextMapAdapter.class)); 376 | 377 | Span span = 378 | TracingServerInterceptor.newBuilder() 379 | .withTracer(spyTracer) 380 | .build() 381 | .getSpanFromHeaders(Collections.emptyMap(), "operationName"); 382 | assertNotNull("span is not null", span); 383 | MockSpan mockSpan = (MockSpan) span; 384 | assertEquals( 385 | "span parentID is set to extracted span context spanID", spanID, mockSpan.parentId()); 386 | List logEntries = mockSpan.logEntries(); 387 | assertTrue("span contains no log entries", logEntries.isEmpty()); 388 | } 389 | 390 | @Test 391 | public void testGetSpanFromHeadersError() { 392 | MockTracer spyTracer = spy(serverTracer); 393 | doThrow(IllegalArgumentException.class) 394 | .when(spyTracer) 395 | .extract(eq(Format.Builtin.HTTP_HEADERS), any(TextMapAdapter.class)); 396 | 397 | Span span = 398 | TracingServerInterceptor.newBuilder() 399 | .withTracer(spyTracer) 400 | .build() 401 | .getSpanFromHeaders(Collections.emptyMap(), "operationName"); 402 | assertNotNull("span is not null", span); 403 | List logEntries = ((MockSpan) span).logEntries(); 404 | assertEquals("span contains 1 log entry", 1, logEntries.size()); 405 | assertEquals( 406 | "span log contains error field", 407 | GrpcFields.ERROR, 408 | logEntries.get(0).fields().get(Fields.EVENT)); 409 | assertThat( 410 | "span log contains error.object field", 411 | logEntries.get(0).fields().get(Fields.ERROR_OBJECT), 412 | instanceOf(RuntimeException.class)); 413 | } 414 | 415 | private Callable reportedSpansSize(final MockTracer mockTracer) { 416 | return new Callable() { 417 | @Override 418 | public Integer call() { 419 | return mockTracer.finishedSpans().size(); 420 | } 421 | }; 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /src/test/proto/helloworld/helloworld.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The gRPC Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | syntax = "proto3"; 15 | 16 | option java_multiple_files = true; 17 | option java_package = "io.opentracing.contrib.grpc.gen"; 18 | option java_outer_classname = "HelloWorldProto"; 19 | option objc_class_prefix = "HLW"; 20 | 21 | package helloworld; 22 | 23 | // The greeting service definition. 24 | service Greeter { 25 | // Sends a greeting 26 | rpc SayHello (HelloRequest) returns (HelloReply) { 27 | } 28 | } 29 | 30 | // The request message containing the user's name. 31 | message HelloRequest { 32 | string name = 1; 33 | } 34 | 35 | // The response message containing the greetings 36 | message HelloReply { 37 | string message = 1; 38 | } 39 | -------------------------------------------------------------------------------- /travis/publish.sh: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017-2020 The OpenTracing Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | # in compliance with the License. You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License 10 | # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | # or implied. See the License for the specific language governing permissions and limitations under 12 | # the License. 13 | # 14 | 15 | set -euo pipefail 16 | set -x 17 | 18 | build_started_by_tag() { 19 | if [ "${TRAVIS_TAG}" == "" ]; then 20 | echo "[Publishing] This build was not started by a tag, publishing snapshot" 21 | return 1 22 | else 23 | echo "[Publishing] This build was started by the tag ${TRAVIS_TAG}, publishing release" 24 | return 0 25 | fi 26 | } 27 | 28 | is_pull_request() { 29 | if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then 30 | echo "[Not Publishing] This is a Pull Request" 31 | return 0 32 | else 33 | echo "[Publishing] This is not a Pull Request" 34 | return 1 35 | fi 36 | } 37 | 38 | is_travis_branch_master() { 39 | if [ "${TRAVIS_BRANCH}" = master ]; then 40 | echo "[Publishing] Travis branch is master" 41 | return 0 42 | else 43 | echo "[Not Publishing] Travis branch is not master" 44 | return 1 45 | fi 46 | } 47 | 48 | check_travis_branch_equals_travis_tag() { 49 | #Weird comparison comparing branch to tag because when you 'git push --tags' 50 | #the branch somehow becomes the tag value 51 | #github issue: https://github.com/travis-ci/travis-ci/issues/1675 52 | if [ "${TRAVIS_BRANCH}" != "${TRAVIS_TAG}" ]; then 53 | echo "Travis branch does not equal Travis tag, which it should, bailing out." 54 | echo " github issue: https://github.com/travis-ci/travis-ci/issues/1675" 55 | exit 1 56 | else 57 | echo "[Publishing] Branch (${TRAVIS_BRANCH}) same as Tag (${TRAVIS_TAG})" 58 | fi 59 | } 60 | 61 | check_release_tag() { 62 | tag="${TRAVIS_TAG}" 63 | if [[ "$tag" =~ ^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$ ]]; then 64 | echo "Build started by version tag $tag. During the release process tags like this" 65 | echo "are created by the 'release' Maven plugin. Nothing to do here." 66 | exit 0 67 | elif [[ ! "$tag" =~ ^release-[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$ ]]; then 68 | echo "You must specify a tag of the format 'release-0.0.0' to release this project." 69 | echo "The provided tag ${tag} doesn't match that. Aborting." 70 | exit 1 71 | fi 72 | } 73 | 74 | is_release_commit() { 75 | project_version=$(./mvnw help:evaluate -N -Dexpression=project.version | grep -v '\[') 76 | if [[ "$project_version" =~ ^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$ ]]; then 77 | echo "Build started by release commit $project_version. Will synchronize to maven central." 78 | return 0 79 | else 80 | return 1 81 | fi 82 | } 83 | 84 | release_version() { 85 | echo "${TRAVIS_TAG}" | sed 's/^release-//' 86 | } 87 | 88 | safe_checkout_master() { 89 | # We need to be on a branch for release:perform to be able to create commits, and we want that branch to be master. 90 | # But we also want to make sure that we build and release exactly the tagged version, so we verify that the remote 91 | # master is where our tag is. 92 | git checkout -B master 93 | git fetch origin master:origin/master 94 | commit_local_master="$(git show --pretty='format:%H' master)" 95 | commit_remote_master="$(git show --pretty='format:%H' origin/master)" 96 | if [ "$commit_local_master" != "$commit_remote_master" ]; then 97 | echo "Master on remote 'origin' has commits since the version under release, aborting" 98 | exit 1 99 | fi 100 | } 101 | 102 | #---------------------- 103 | # MAIN 104 | #---------------------- 105 | 106 | if ! is_pull_request && build_started_by_tag; then 107 | check_travis_branch_equals_travis_tag 108 | check_release_tag 109 | fi 110 | 111 | ./mvnw install -nsu 112 | 113 | # If we are on a pull request, our only job is to run tests, which happened above via ./mvnw install 114 | if is_pull_request; then 115 | true 116 | # If we are on master, we will deploy the latest snapshot or release version 117 | # - If a release commit fails to deploy for a transient reason, delete the broken version from bintray and click rebuild 118 | elif is_travis_branch_master; then 119 | ./mvnw --batch-mode -s ./.settings.xml -Prelease -nsu -DskipTests deploy 120 | 121 | # If we are on a release tag, the following will update any version references and push a version tag for deployment. 122 | elif build_started_by_tag; then 123 | safe_checkout_master 124 | ./mvnw --batch-mode -s ./.settings.xml -Prelease -nsu -DreleaseVersion="$(release_version)" -Darguments="-DskipTests" release:prepare 125 | fi 126 | --------------------------------------------------------------------------------