├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .settings.xml ├── .travis.yml ├── LICENSE ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml ├── src ├── main │ └── java │ │ └── io │ │ └── opentracing │ │ └── contrib │ │ ├── aws │ │ └── xray │ │ │ ├── AWSXRayMetadataNamespaces.java │ │ │ ├── AWSXRayScope.java │ │ │ ├── AWSXRayScopeManager.java │ │ │ ├── AWSXRaySpan.java │ │ │ ├── AWSXRaySpanContext.java │ │ │ ├── AWSXRayTags.java │ │ │ ├── AWSXRayTracer.java │ │ │ └── AWSXRayUtils.java │ │ └── tag │ │ └── ExtraTags.java └── test │ ├── java │ └── io │ │ └── opentracing │ │ ├── contrib │ │ └── aws │ │ │ └── xray │ │ │ ├── AWSXRaySpanBuilderTests.java │ │ │ ├── AWSXRaySpanTests.java │ │ │ ├── AWSXRayTestParent.java │ │ │ └── AWSXRayTracerTests.java │ │ └── propagation │ │ └── TextMapAdapter.java │ └── resources │ └── logback.xml └── travis └── publish.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Java / maven build artifacts 2 | *.class 3 | *.log 4 | target/ 5 | 6 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 7 | hs_err_pid* 8 | 9 | # IDE artifacts 10 | .idea/ 11 | *.iml 12 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import java.net.*; 17 | import java.io.*; 18 | import java.nio.channels.*; 19 | import java.util.Properties; 20 | 21 | public class MavenWrapperDownloader { 22 | 23 | private static final String WRAPPER_VERSION = "0.5.5"; 24 | /** 25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 26 | */ 27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 29 | 30 | /** 31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 32 | * use instead of the default one. 33 | */ 34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 35 | ".mvn/wrapper/maven-wrapper.properties"; 36 | 37 | /** 38 | * Path where the maven-wrapper.jar will be saved to. 39 | */ 40 | private static final String MAVEN_WRAPPER_JAR_PATH = 41 | ".mvn/wrapper/maven-wrapper.jar"; 42 | 43 | /** 44 | * Name of the property which should be used to override the default download url for the wrapper. 45 | */ 46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 47 | 48 | public static void main(String args[]) { 49 | System.out.println("- Downloader started"); 50 | File baseDirectory = new File(args[0]); 51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 52 | 53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 54 | // wrapperUrl parameter. 55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 56 | String url = DEFAULT_DOWNLOAD_URL; 57 | if(mavenWrapperPropertyFile.exists()) { 58 | FileInputStream mavenWrapperPropertyFileInputStream = null; 59 | try { 60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 61 | Properties mavenWrapperProperties = new Properties(); 62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 64 | } catch (IOException e) { 65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 66 | } finally { 67 | try { 68 | if(mavenWrapperPropertyFileInputStream != null) { 69 | mavenWrapperPropertyFileInputStream.close(); 70 | } 71 | } catch (IOException e) { 72 | // Ignore ... 73 | } 74 | } 75 | } 76 | System.out.println("- Downloading from: " + url); 77 | 78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 79 | if(!outputFile.getParentFile().exists()) { 80 | if(!outputFile.getParentFile().mkdirs()) { 81 | System.out.println( 82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 83 | } 84 | } 85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 86 | try { 87 | downloadFileFromURL(url, outputFile); 88 | System.out.println("Done"); 89 | System.exit(0); 90 | } catch (Throwable e) { 91 | System.out.println("- Error downloading"); 92 | e.printStackTrace(); 93 | System.exit(1); 94 | } 95 | } 96 | 97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 99 | String username = System.getenv("MVNW_USERNAME"); 100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 101 | Authenticator.setDefault(new Authenticator() { 102 | @Override 103 | protected PasswordAuthentication getPasswordAuthentication() { 104 | return new PasswordAuthentication(username, password); 105 | } 106 | }); 107 | } 108 | URL website = new URL(urlString); 109 | ReadableByteChannel rbc; 110 | rbc = Channels.newChannel(website.openStream()); 111 | FileOutputStream fos = new FileOutputStream(destination); 112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 113 | fos.close(); 114 | rbc.close(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentracing-contrib/java-xray-tracer/c8cc506679be76604b91fa369bc6f87ce5942f87/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.1/apache-maven-3.6.1-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar 3 | -------------------------------------------------------------------------------- /.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 | 44 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | language: java 5 | jdk: 6 | - openjdk8 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 | # setup https authentication credentials, used by ./mvnw release:prepare 17 | - git config credential.helper "store --file=.git/credentials" 18 | - echo "https://$GH_TOKEN:@github.com" > .git/credentials 19 | 20 | script: 21 | - ./travis/publish.sh 22 | 23 | install: 24 | - ./mvnw install -DskipTests=true -Dmaven.javadoc.skip=true -B -V 25 | 26 | branches: 27 | only: 28 | - master 29 | 30 | after_success: 31 | - mvn jacoco:report coveralls:report 32 | -------------------------------------------------------------------------------- /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](https://travis-ci.org/opentracing-contrib/java-xray-tracer.svg?branch=master)](https://travis-ci.org/opentracing-contrib/java-xray-tracer) [![Coverage Status](https://coveralls.io/repos/github/opentracing-contrib/java-xray-tracer/badge.svg?branch=master)](https://coveralls.io/github/opentracing-contrib/java-xray-tracer?branch=master) [![GitHub](https://img.shields.io/github/license/opentracing-contrib/java-xray-tracer.svg)](https://opensource.org/licenses/Apache-2.0) 2 | 3 | # opentracing-java-aws-xray 4 | Java OpenTracing implementation backed by AWS X-Ray. 5 | 6 | **WARNING: this code is currently in beta: please test thoroughly before deploying to production, and report any issues.** 7 | 8 | ## Overview 9 | 10 | The [OpenTracing specification](https://opentracing.io) is a vendor-neutral API for instrumentation and distributed 11 | tracing. This library provides an implementation which is backed by the [AWS X-Ray Java SDK](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-java.html), 12 | and for the most part just provides a thin wrapper around the underlying X-Ray classes. 13 | 14 | ## Using this library 15 | 16 | The code is not yet being deployed to Maven central (we're working on that!), so you'll have to build your own version. This should be as simple as: 17 | 18 | - cloning this repository locally 19 | - running `mvn package` [1] 20 | - including the resulting JAR file in your `/lib` folder (or similar) 21 | 22 | [1] If you don't already have Maven installed, you can use the `mvnw` command (or `mvnw.cmd` for Windows) instead which uses the [Maven wrapper plugin](https://github.com/takari/maven-wrapper) to download and start the correct version of Maven. 23 | 24 | ## AWS compatibility 25 | 26 | #### Integrating with AWS systems 27 | 28 | Since this library mostly just wraps the standard X-Ray classes, it *should* work seamlessly in code which makes use of 29 | multiple AWS services: the standard AWS SDK will add the necessary trace headers automatically, and recover them on 30 | remote servers (e.g. when invoking lambda functions). However, this hasn't yet been extensively tested, so feedback 31 | and bug reports are very welcome! 32 | 33 | 34 | #### Naming conventions 35 | 36 | The OpenTracing standard and the AWS X-Ray system each use different naming conventions for some of the same concepts 37 | (e.g. HTTP response codes). Since the goal of this project is to largely hide the fact that we're using X-Ray under the 38 | hood, and to only expose the OpenTracing API: 39 | 40 | - client code should prefer using the [OpenTracing naming conventions](https://opentracing.io/specification/conventions/) 41 | for tag names (however, if you supply the X-Ray-specific names, values will still end up in the right place) 42 | - this library will silently convert *some* known tag names values to their X-Ray equivalents 43 | - X-Ray traces further subdivide tagged values into separate sub-objects for e.g. HTTP request and response data: 44 | - where possible, ensure values end up in the correct place on the trace 45 | - all other values are stored in the `metadata` section, under a `default` namespace if not specified 46 | - see the [X-Ray Segment Documents](https://docs.aws.amazon.com/xray/latest/devguide/xray-api-segmentdocuments.html) 47 | for further details 48 | 49 | The following OpenTracing names will be translated to fit the X-Ray names: 50 | 51 | | OpenTracing tag name | X-Ray trace name | 52 | |-----------------------|---------------------------------| 53 | | `version` | `service.version` | 54 | | `db.instance` | `sql.url` | 55 | | `db.statement` | `sql.sanitized_query` | 56 | | `db.type` | `sql.database_type` | 57 | | `db.user` | `sql.user` | 58 | | `db.driver` | `sql.driver` | 59 | | `db.version` | `sql.version` | 60 | | `http.method` | `http.request.method` | 61 | | `http.url` | `http.request.url` | 62 | | `http.client_ip` | `http.request.client_ip` | 63 | | `http.user_agent` | `http.request.user_agent` | 64 | | `http.status_code` | `http.response.status` | 65 | | `http.content_length` | `http.response.content_length` | 66 | | `foo` | `metadata.default.foo` | 67 | | `widget.foo` | `metadata.widget.foo` | 68 | 69 | Additionally, the following special tag names are defined in `AWSXRayTags` and can be used to directly modify the 70 | behaviour of the underlying X-Ray trace `Entity` (NB some of these only work for `Segment`, i.e. top-level spans): 71 | 72 | | | Behaviour | `Segment` | `Subsegment` | 73 | |-----------------------|---------------------------------|-----------|--------------| 74 | | `error` | sets the `isError()` flag | Y | Y | 75 | | `fault` | sets the `isFault()` flag | Y | Y | 76 | | `throttle` | sets the `isThrottle()` flag | Y | Y | 77 | | `isSampled` | sets the `isSampled()` flag | Y | - | 78 | | `user` | sets the `user` value | Y | - | 79 | | `origin` | sets the `origin` value | Y | - | 80 | | `parentId` | sets the `parentId` value | Y | Y | 81 | 82 | #### Context injection / extraction 83 | 84 | This library supports basic [injection and extraction of SpanContext](https://opentracing.io/specification/#inject-a-spancontext-into-a-carrier): 85 | 86 | - in most cases it is expected that this library will be used in AWS-hosted systems, and calls between AWS services 87 | using the official SDK will already handle passing trace IDs across so no further work is required 88 | 89 | - for non-AWS systems, the current `SpanContext` can be converted into e.g. a set of HTTP 90 | headers using `Tracer.inject` and sent over the wire; on the other side, `Tracer.extract` can be used to convert 91 | the headers back into a `SpanContext` 92 | 93 | - for compatibility between AWS and non-AWS services, we store the trace context information in a single header `X-Amzn-Trace-Id` - see the 94 | [Amazon trace header documentation](https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader) 95 | for more details 96 | 97 | ## Known limitations 98 | 99 | This library does not currently provide a full implementation of the OpenTracing API: the X-Ray classes themselves 100 | already provide some of the same features, and in other cases the APIs are incompatible. The following limitations 101 | currently apply: 102 | 103 | #### References 104 | 105 | OpenTracing provides for arbitrary [references between spans](https://opentracing.io/specification/#references-between-spans), 106 | including parent-child and follows-from relationships. In practice X-Ray only supports parent-child relationships, and 107 | each span can have at most one parent. Calls to add references of different types, or multiple parent-child relationships, 108 | will generally be ignored. 109 | 110 | #### Logging 111 | 112 | OpenTracing provides methods to add [logs to the trace](https://opentracing.io/specification/#log-structured-data). 113 | These methods will work as expected: structured data are stored in X-Ray metadata under a "log" namespace, but this 114 | approach isn't advised since the resulting JSON format is clunky. A better approach in AWS is to make use of 115 | [CloudWatch](https://aws.amazon.com/cloudwatch/). 116 | 117 | ## License 118 | 119 | This project is licensed under the [Apache 2.0](/LICENSE) License. 120 | -------------------------------------------------------------------------------- /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*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | fi 118 | 119 | if [ -z "$JAVA_HOME" ]; then 120 | javaExecutable="`which javac`" 121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 122 | # readlink(1) is not available as standard on Solaris 10. 123 | readLink=`which readlink` 124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 125 | if $darwin ; then 126 | javaHome="`dirname \"$javaExecutable\"`" 127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 128 | else 129 | javaExecutable="`readlink -f \"$javaExecutable\"`" 130 | fi 131 | javaHome="`dirname \"$javaExecutable\"`" 132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 133 | JAVA_HOME="$javaHome" 134 | export JAVA_HOME 135 | fi 136 | fi 137 | fi 138 | 139 | if [ -z "$JAVACMD" ] ; then 140 | if [ -n "$JAVA_HOME" ] ; then 141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 142 | # IBM's JDK on AIX uses strange locations for the executables 143 | JAVACMD="$JAVA_HOME/jre/sh/java" 144 | else 145 | JAVACMD="$JAVA_HOME/bin/java" 146 | fi 147 | else 148 | JAVACMD="`which java`" 149 | fi 150 | fi 151 | 152 | if [ ! -x "$JAVACMD" ] ; then 153 | echo "Error: JAVA_HOME is not defined correctly." >&2 154 | echo " We cannot execute $JAVACMD" >&2 155 | exit 1 156 | fi 157 | 158 | if [ -z "$JAVA_HOME" ] ; then 159 | echo "Warning: JAVA_HOME environment variable is not set." 160 | fi 161 | 162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 163 | 164 | # traverses directory structure from process work directory to filesystem root 165 | # first directory with .mvn subdirectory is considered project base directory 166 | find_maven_basedir() { 167 | 168 | if [ -z "$1" ] 169 | then 170 | echo "Path not specified to find_maven_basedir" 171 | return 1 172 | fi 173 | 174 | basedir="$1" 175 | wdir="$1" 176 | while [ "$wdir" != '/' ] ; do 177 | if [ -d "$wdir"/.mvn ] ; then 178 | basedir=$wdir 179 | break 180 | fi 181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 182 | if [ -d "${wdir}" ]; then 183 | wdir=`cd "$wdir/.."; pwd` 184 | fi 185 | # end of workaround 186 | done 187 | echo "${basedir}" 188 | } 189 | 190 | # concatenates all lines of a file 191 | concat_lines() { 192 | if [ -f "$1" ]; then 193 | echo "$(tr -s '\n' ' ' < "$1")" 194 | fi 195 | } 196 | 197 | BASE_DIR=`find_maven_basedir "$(pwd)"` 198 | if [ -z "$BASE_DIR" ]; then 199 | exit 1; 200 | fi 201 | 202 | ########################################################################################## 203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 204 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 205 | ########################################################################################## 206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 207 | if [ "$MVNW_VERBOSE" = true ]; then 208 | echo "Found .mvn/wrapper/maven-wrapper.jar" 209 | fi 210 | else 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 213 | fi 214 | if [ -n "$MVNW_REPOURL" ]; then 215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" 216 | else 217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" 218 | fi 219 | while IFS="=" read key value; do 220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 221 | esac 222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 223 | if [ "$MVNW_VERBOSE" = true ]; then 224 | echo "Downloading from: $jarUrl" 225 | fi 226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 227 | if $cygwin; then 228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 229 | fi 230 | 231 | if command -v wget > /dev/null; then 232 | if [ "$MVNW_VERBOSE" = true ]; then 233 | echo "Found wget ... using wget" 234 | fi 235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 236 | wget "$jarUrl" -O "$wrapperJarPath" 237 | else 238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" 239 | fi 240 | elif command -v curl > /dev/null; then 241 | if [ "$MVNW_VERBOSE" = true ]; then 242 | echo "Found curl ... using curl" 243 | fi 244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 245 | curl -o "$wrapperJarPath" "$jarUrl" -f 246 | else 247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 248 | fi 249 | 250 | else 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo "Falling back to using Java to download" 253 | fi 254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 255 | # For Cygwin, switch paths to Windows format before running javac 256 | if $cygwin; then 257 | javaClass=`cygpath --path --windows "$javaClass"` 258 | fi 259 | if [ -e "$javaClass" ]; then 260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 261 | if [ "$MVNW_VERBOSE" = true ]; then 262 | echo " - Compiling MavenWrapperDownloader.java ..." 263 | fi 264 | # Compiling the Java class 265 | ("$JAVA_HOME/bin/javac" "$javaClass") 266 | fi 267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 268 | # Running the downloader 269 | if [ "$MVNW_VERBOSE" = true ]; then 270 | echo " - Running MavenWrapperDownloader.java ..." 271 | fi 272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 273 | fi 274 | fi 275 | fi 276 | fi 277 | ########################################################################################## 278 | # End of extension 279 | ########################################################################################## 280 | 281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 282 | if [ "$MVNW_VERBOSE" = true ]; then 283 | echo $MAVEN_PROJECTBASEDIR 284 | fi 285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 286 | 287 | # For Cygwin, switch paths to Windows format before running java 288 | if $cygwin; then 289 | [ -n "$M2_HOME" ] && 290 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 291 | [ -n "$JAVA_HOME" ] && 292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 293 | [ -n "$CLASSPATH" ] && 294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 295 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 297 | fi 298 | 299 | # Provide a "standardized" way to retrieve the CLI args that will 300 | # work with both Windows and non-Windows executions. 301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 302 | export MAVEN_CMD_LINE_ARGS 303 | 304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 305 | 306 | exec "$JAVACMD" \ 307 | $MAVEN_OPTS \ 308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 311 | -------------------------------------------------------------------------------- /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 by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | io.opentracing.contrib 7 | java-xray-tracer 8 | 1.0.0-SNAPSHOT 9 | 10 | https://github.com/opentracing-contrib/java-xray-tracer 11 | OpenTracing implementation for AWS X-Ray 12 | 13 | 14 | http://github.com/opentracing-contrib/java-xray-tracer 15 | scm:git:https://github.com/opentracing-contrib/java-xray-tracer.git 16 | scm:git:https://github.com/opentracing-contrib/java-xray-tracer.git 17 | HEAD 18 | 19 | 20 | 21 | GitHub 22 | https://github.com/opentracing-contrib/java-xray-tracer/issues 23 | 24 | 25 | 26 | 27 | The Apache Software License, Version 2.0 28 | http://www.apache.org/licenses/LICENSE-2.0.txt 29 | repo 30 | 31 | 32 | 33 | 34 | 35 | ashleymercer 36 | Ashley Mercer 37 | https://github.com/ashleymercer 38 | 39 | 40 | 41 | 42 | 1.8 43 | 1.8 44 | UTF-8 45 | UTF-8 46 | 47 | 50 | 2.2.1 51 | 5.4.0 52 | 2.28.2 53 | 0.31.0 54 | 1.7.26 55 | 56 | 57 | 58 | 59 | 60 | com.amazonaws 61 | aws-xray-recorder-sdk-core 62 | ${aws.xray.sdk.version} 63 | 64 | 65 | io.opentracing 66 | opentracing-api 67 | ${opentracing.version} 68 | 69 | 70 | org.slf4j 71 | slf4j-api 72 | ${slf4j.version} 73 | 74 | 75 | 76 | 77 | ch.qos.logback 78 | logback-classic 79 | 1.2.3 80 | test 81 | 82 | 83 | org.mockito 84 | mockito-core 85 | ${mockito.version} 86 | 87 | 88 | org.slf4j 89 | jcl-over-slf4j 90 | ${slf4j.version} 91 | 92 | 93 | io.opentracing 94 | opentracing-util 95 | ${opentracing.version} 96 | test 97 | 98 | 99 | org.junit.jupiter 100 | junit-jupiter-api 101 | ${junit.version} 102 | test 103 | 104 | 105 | org.junit.jupiter 106 | junit-jupiter-engine 107 | ${junit.version} 108 | test 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | maven-compiler-plugin 117 | 3.8.0 118 | 119 | true 120 | 121 | -Xlint:all 122 | -Werror 123 | 124 | 125 | 126 | 127 | 128 | 129 | maven-surefire-plugin 130 | 2.22.0 131 | 132 | 133 | maven-failsafe-plugin 134 | 2.22.0 135 | 136 | 137 | 138 | 139 | org.eluder.coveralls 140 | coveralls-maven-plugin 141 | 4.3.0 142 | 143 | 144 | org.jacoco 145 | jacoco-maven-plugin 146 | 0.8.3 147 | 148 | 149 | prepare-agent 150 | 151 | prepare-agent 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | maven-release-plugin 160 | 2.5.3 161 | 162 | false 163 | release 164 | true 165 | @{project.version} 166 | 167 | 168 | 169 | io.zipkin.centralsync-maven-plugin 170 | centralsync-maven-plugin 171 | 0.1.0 172 | 173 | opentracing 174 | maven 175 | opentracing-xray-tracer 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | bintray 185 | https://api.bintray.com/maven/opentracing/maven/opentracing-xray-tracer/;publish=1 186 | 187 | 188 | jfrog-snapshots 189 | http://oss.jfrog.org/artifactory/oss-snapshot-local 190 | 191 | 192 | 193 | 194 | 195 | release 196 | 197 | 198 | 199 | 200 | 201 | org.apache.maven.plugins 202 | maven-source-plugin 203 | 3.1.0 204 | 205 | 206 | attach-sources 207 | 208 | jar 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | org.apache.maven.plugins 217 | maven-javadoc-plugin 218 | 3.1.0 219 | 220 | false 221 | 222 | 223 | 224 | attach-javadocs 225 | 226 | jar 227 | 228 | package 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/aws/xray/AWSXRayMetadataNamespaces.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib.aws.xray; 2 | 3 | import com.amazonaws.xray.entities.Entity; 4 | import io.opentracing.Span; 5 | 6 | /** 7 | * When storing tagged values on X-Ray traces, we try to use the 8 | * standard X-Ray naming conventions where possible; other values 9 | * will end up being stored in the "metadata" part of the trace 10 | * which requires at least a three-part key: 11 | * 12 | * 17 | * 18 | * This class provides some different metadata namespaces which can 19 | * be used externally if required. 20 | * 21 | * When storing values on the {@link Span}, if the tag name does not 22 | * correspond to one of the standard "known" names, and also does not 23 | * conform to the above pattern, then it will be coerced to the above 24 | * by being placed in the {@link #DEFAULT} namespace. Examples: 25 | * 26 | * 27 | * 28 | * 29 | * 30 | * 31 | * 32 | * 33 | * 34 | * 35 | * 36 | * 37 | * 38 | * 39 | * 40 | * 41 | * 42 | * 43 | * 44 | * 45 | * 46 | * 47 | *
Original keyX-Ray keyComments
db.usersql.userX-Ray traces use a "sql" element for database info
foo_valuemetadata.default.foo_valueStored in "metadata" under the "default" namespace
widget.bar_valuemetadata.widget.bar_valueStored in "metadata" under the custom "widget" namespace
48 | * 49 | * @author ashley.mercer@skylightipv.com 50 | * @see Entity#getMetadata() 51 | * @see X-Ray Segment documentation 52 | */ 53 | @SuppressWarnings("WeakerAccess") 54 | public final class AWSXRayMetadataNamespaces { 55 | 56 | private AWSXRayMetadataNamespaces(){} 57 | 58 | /** 59 | * The default namespace for anything which doesn't explicitly 60 | * specify its own namespace. 61 | */ 62 | public static final String DEFAULT = "default"; 63 | 64 | /** 65 | * Log statements are also stored in the metadata since X-Ray doesn't 66 | * have another mechanism to expose them currently. 67 | */ 68 | public static final String LOG = "log"; 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/aws/xray/AWSXRayScope.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib.aws.xray; 2 | 3 | import io.opentracing.Scope; 4 | 5 | /** 6 | * @see Scope 7 | * @author ashley.mercer@skylightipv.com 8 | */ 9 | class AWSXRayScope implements Scope { 10 | 11 | private final AWSXRayScopeManager scopeManager; 12 | private final AWSXRayScope previousScope; 13 | 14 | private final AWSXRaySpan span; 15 | private final boolean finishOnClose; 16 | 17 | AWSXRayScope(AWSXRayScopeManager scopeManager, AWSXRayScope previousScope, AWSXRaySpan span, boolean finishOnClose) { 18 | this.scopeManager = scopeManager; 19 | this.previousScope = previousScope; 20 | this.span = span; 21 | this.finishOnClose = finishOnClose; 22 | } 23 | 24 | @Override 25 | public void close() { 26 | if (finishOnClose) span.finish(); 27 | scopeManager.setCurrentScope(previousScope); 28 | } 29 | 30 | @Override 31 | public AWSXRaySpan span() { 32 | return span; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/aws/xray/AWSXRayScopeManager.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib.aws.xray; 2 | 3 | import com.amazonaws.xray.AWSXRayRecorder; 4 | import com.amazonaws.xray.entities.Entity; 5 | import io.opentracing.Scope; 6 | import io.opentracing.ScopeManager; 7 | import io.opentracing.Span; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | /** 12 | * Implementation of {@link ScopeManager} when tracing with AWS X-Ray. 13 | * The X-Ray libraries also have their own lifecycle management and 14 | * reference counting for the underlying trace {@link Entity}s, so we 15 | * need to hook in to these to keep OpenTracing and X-Ray in sync. 16 | * 17 | * @author ashley.mercer@skylightipv.com 18 | */ 19 | class AWSXRayScopeManager implements ScopeManager { 20 | 21 | private static final Logger log = LoggerFactory.getLogger(AWSXRayScopeManager.class); 22 | 23 | /** 24 | * The {@link AWSXRayRecorder} class keeps track of the current trace 25 | * {@link Entity} on this thread, and we need to keep its view and our 26 | * view of the world in sync. 27 | */ 28 | private final AWSXRayRecorder xRayRecorder; 29 | 30 | /** 31 | * X-Ray already keeps track of the current active {@link Entity}, but 32 | * additionally track here the whole {@link Scope} in order to be able 33 | * to recover the previous state on this thread once the current span 34 | * is finished / closed. 35 | */ 36 | private final ThreadLocal currentScope; 37 | 38 | /** 39 | * Set the current {@link Scope} back to the given value. All changes to 40 | * the {@link #currentScope} value should pass through this method, since 41 | * it also hooks into the underlying X-Ray classes. 42 | * 43 | * @param scope the new current scope 44 | */ 45 | void setCurrentScope(AWSXRayScope scope) { 46 | currentScope.set(scope); 47 | xRayRecorder.setTraceEntity(scope == null ? null : scope.span().getEntity()); 48 | } 49 | 50 | AWSXRayScopeManager(AWSXRayRecorder xRayRecorder) { 51 | this.xRayRecorder = xRayRecorder; 52 | this.currentScope = new ThreadLocal<>(); 53 | } 54 | 55 | @Override 56 | public AWSXRayScope active() { 57 | return this.currentScope.get(); 58 | } 59 | 60 | AWSXRaySpan activeSpan() { 61 | final AWSXRayScope activeScope = this.currentScope.get(); 62 | return activeScope == null ? null : activeScope.span(); 63 | } 64 | 65 | @Override 66 | public Scope activate(Span span, boolean finishSpanOnClose) { 67 | if (span instanceof AWSXRaySpan) { 68 | final AWSXRayScope oldScope = currentScope.get(); 69 | final AWSXRayScope newScope = new AWSXRayScope(this, oldScope, (AWSXRaySpan) span, finishSpanOnClose); 70 | setCurrentScope(newScope); 71 | return newScope; 72 | } 73 | else { 74 | if (span != null) { 75 | log.warn("Cannot activate Span: expected AWSXRaySpan but got type " + span.getClass().getSimpleName()); 76 | } 77 | return currentScope.get(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/aws/xray/AWSXRaySpan.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib.aws.xray; 2 | 3 | import com.amazonaws.xray.entities.Entity; 4 | import com.amazonaws.xray.entities.Segment; 5 | import com.amazonaws.xray.entities.Subsegment; 6 | import io.opentracing.Span; 7 | import io.opentracing.contrib.tag.ExtraTags; 8 | import io.opentracing.log.Fields; 9 | import io.opentracing.tag.Tags; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.time.Instant; 14 | import java.util.*; 15 | import java.util.concurrent.ConcurrentHashMap; 16 | import java.util.concurrent.atomic.AtomicBoolean; 17 | 18 | /** 19 | * A {@link Span} in the OpenTracing API corresponds more or less directly 20 | * to a trace {@link Entity} in the X-Ray API, with a couple of major differences: 21 | * 22 | * 40 | * 41 | * @see Span 42 | * @see Segment 43 | * @see Subsegment 44 | * @author ashley.mercer@skylightipv.com 45 | */ 46 | class AWSXRaySpan implements Span { 47 | 48 | private static final Logger log = LoggerFactory.getLogger(AWSXRaySpan.class); 49 | 50 | /** 51 | * A map of synonyms from OpenTracing naming conventions to X-Ray. This is 52 | * necessary because X-Ray traces store some information (e.g. HTTP request 53 | * and response data) in different places on the resulting object, but we 54 | * want users to be able to continue to use the standard OpenTracing names. 55 | */ 56 | private static final Map TAG_SYNONYMS = new HashMap<>(); 57 | static { 58 | TAG_SYNONYMS.put(Tags.DB_INSTANCE.getKey(), "sql.url"); 59 | TAG_SYNONYMS.put(Tags.DB_STATEMENT.getKey(), "sql.sanitized_query"); 60 | TAG_SYNONYMS.put(Tags.DB_TYPE.getKey(), "sql.database_type"); 61 | TAG_SYNONYMS.put(Tags.DB_USER.getKey(), "sql.user"); 62 | TAG_SYNONYMS.put(ExtraTags.DB_DRIVER.getKey(), "sql.driver_version"); 63 | TAG_SYNONYMS.put(ExtraTags.DB_VERSION.getKey(), "sql.database_version"); 64 | 65 | TAG_SYNONYMS.put(Tags.HTTP_METHOD.getKey(), "http.request.method"); 66 | TAG_SYNONYMS.put(Tags.HTTP_STATUS.getKey(), "http.response.status"); 67 | TAG_SYNONYMS.put(Tags.HTTP_URL.getKey(), "http.request.url"); 68 | TAG_SYNONYMS.put(ExtraTags.HTTP_CLIENT_IP.getKey(), "http.request.client_ip"); 69 | TAG_SYNONYMS.put(ExtraTags.HTTP_USER_AGENT.getKey(), "http.request.user_agent"); 70 | TAG_SYNONYMS.put(ExtraTags.HTTP_CONTENT_LENGTH.getKey(), "http.response.content_length"); 71 | 72 | TAG_SYNONYMS.put(ExtraTags.VERSION.getKey(), "service.version"); 73 | } 74 | 75 | /** 76 | * Reference to the underlying X-Ray trace {@link Entity} 77 | */ 78 | private final Entity entity; 79 | 80 | /** 81 | * @see io.opentracing.SpanContext 82 | */ 83 | private final AWSXRaySpanContext context; 84 | 85 | /** 86 | * Keep track of whether finish() has been called yet 87 | */ 88 | private final AtomicBoolean isFinished; 89 | 90 | AWSXRaySpan(Entity entity, AWSXRaySpanContext context) { 91 | this.entity = entity; 92 | this.context = context; 93 | this.isFinished = new AtomicBoolean(false); 94 | } 95 | 96 | /** 97 | * @return the underlying X-Ray trace {@link Entity} 98 | */ 99 | Entity getEntity() { 100 | return entity; 101 | } 102 | 103 | @Override 104 | public AWSXRaySpanContext context() { 105 | return context; 106 | } 107 | 108 | @Override 109 | public Span setTag(String key, String value) { 110 | 111 | // Match for known tags which are handled differently in X-Ray 112 | if (AWSXRayTags.USER.getKey().equals(key) && entity instanceof Segment) { 113 | ((Segment) entity).setUser(value); 114 | } 115 | else if (AWSXRayTags.ORIGIN.getKey().equals(key) && entity instanceof Segment) { 116 | ((Segment) entity).setOrigin(value); 117 | } 118 | else if (AWSXRayTags.PARENT_ID.getKey().equals(key)) { 119 | entity.setParentId(value); 120 | } 121 | else { 122 | setTagAny(key, value); 123 | } 124 | return this; 125 | } 126 | 127 | @Override 128 | public Span setTag(String key, boolean value) { 129 | 130 | // Match for known tags which are handled differently in X-Ray 131 | if (Tags.ERROR.getKey().equals(key)) { 132 | entity.setError(value); 133 | } 134 | else if (AWSXRayTags.FAULT.getKey().equals(key)) { 135 | entity.setFault(value); 136 | } 137 | else if (AWSXRayTags.THROTTLE.getKey().equals(key)) { 138 | entity.setThrottle(value); 139 | } 140 | else if (AWSXRayTags.IS_SAMPLED.getKey().equals(key) && entity instanceof Segment) { 141 | ((Segment) entity).setSampled(value); 142 | } 143 | else { 144 | setTagAny(key, value); 145 | } 146 | return this; 147 | } 148 | 149 | @Override 150 | public Span setTag(String key, Number value) { 151 | setTagAny(key, value); 152 | return this; 153 | } 154 | 155 | @Override 156 | public Span log(String event) { 157 | return log(Collections.singletonMap(Fields.MESSAGE, event)); 158 | } 159 | 160 | @Override 161 | public Span log(long timestampMicroseconds, String event) { 162 | return log(timestampMicroseconds, Collections.singletonMap(Fields.MESSAGE, event)); 163 | } 164 | 165 | @Override 166 | public Span log(Map fields) { 167 | return log(Instant.now(), fields); 168 | } 169 | 170 | @Override 171 | public Span log(long timestampMicroseconds, Map fields) { 172 | return log(Instant.ofEpochMilli(timestampMicroseconds / 1000L), fields); 173 | } 174 | 175 | /** 176 | * Log arbitrary data to the X-Ray trace. Since X-Ray doesn't really have its own 177 | * built-in log mechanism (CloudWatch would be a more suitable place for this) 178 | * we just store the provided data in a special "metadata.logs" area. 179 | * 180 | * @param timestamp the timestamp of the provided log 181 | * @param fields arbitrary fields to store against this timestamp 182 | */ 183 | private Span log(Instant timestamp, Map fields) { 184 | 185 | // If the provided map contains an exception, use X-Ray's Cause 186 | // object to record the full stack trace 187 | final Object errorObject = fields.get(Fields.ERROR_OBJECT); 188 | if (errorObject instanceof Throwable) { 189 | entity.addException((Throwable) errorObject); 190 | } 191 | else { 192 | entity.putMetadata(AWSXRayMetadataNamespaces.LOG, timestamp.toString(), fields); 193 | } 194 | return this; 195 | } 196 | 197 | @Override 198 | public Span setBaggageItem(String key, String value) { 199 | context.setBaggageItem(key, value); 200 | return this; 201 | } 202 | 203 | @Override 204 | public String getBaggageItem(String key) { 205 | return context.getBaggageItem(key); 206 | } 207 | 208 | @Override 209 | public Span setOperationName(String operationName) { 210 | throw new UnsupportedOperationException("The AWS X-Ray API does not permit segment names to be changed after creation"); 211 | } 212 | 213 | /** 214 | * For an arbitrary key and value, store it in the correct place on 215 | * the underlying X-Ray {@link Entity}: 216 | * 217 | *
    218 | *
  • X-Ray traces use different naming conventions from OpenTracing 219 | * so some OpenTracing names are automatically to X-Ray format
  • 220 | *
  • X-Ray traces have special subsections for certain types of 221 | * data, e.g. HTTP request and response data
  • 222 | *
  • all other fields are stored in the general-purpose "metadata" 223 | * part of the X-Ray trace
  • 224 | *
225 | * 226 | * @param key the tag (name) for the value to be stored 227 | * @param value the value to be stored 228 | */ 229 | private void setTagAny(String key, Object value) { 230 | 231 | // First translate the key from OpenTracing names to X-Ray names 232 | final String awsKey = TAG_SYNONYMS.getOrDefault(key, key); 233 | if (awsKey != null) { 234 | 235 | // OpenTracing keys are '.'-separated by convention 236 | final List allKeyParts = Arrays.asList(awsKey.split("\\.")); 237 | final Iterator remainingKeyParts = allKeyParts.iterator(); 238 | 239 | // String.split is guaranteed to return at least one element (if the 240 | // separator didn't appear at all) so we're always safe to get the 241 | // first element from this iterator 242 | final String awsKeyPart1 = remainingKeyParts.next(); 243 | 244 | // X-Ray Entity uses different Maps to store different types of 245 | // information and the first part of the key will tell us whether to 246 | // use one these Maps or not 247 | if ("annotations".equals(awsKeyPart1)) { 248 | setTagAny(remainingKeyParts, value, entity.getAnnotations()); 249 | } 250 | else if ("aws".equals(awsKeyPart1)) { 251 | setTagAny(remainingKeyParts, value, entity.getAws()); 252 | } 253 | else if ("http".equals(awsKeyPart1)) { 254 | setTagAny(remainingKeyParts, value, entity.getHttp()); 255 | } 256 | // Service-level information is only available on top-level trace Segments 257 | else if ("service".equals(awsKeyPart1) && entity instanceof Segment) { 258 | setTagAny(remainingKeyParts, value, ((Segment) entity).getService()); 259 | } 260 | else if ("sql".equals(awsKeyPart1)) { 261 | setTagAny(remainingKeyParts, value, entity.getSql()); 262 | } 263 | 264 | // Store everything else in the "metadata" part of the trace 265 | else { 266 | final Map> metadata = entity.getMetadata(); 267 | 268 | // 269 | // Figure out which namespace to use in the metadata using the following rules: 270 | // 271 | // "foo" -> "metadata.default.foo" 272 | // "metadata.foo" -> "metadata.default.foo" 273 | // "namespace.foo" -> "metadata.namespace.foo" 274 | // "metadata.namespace.foo" -> "metadata.namespace.foo" 275 | // 276 | 277 | // If the key started with "metadata" chomp it; otherwise assume "metadata" 278 | // implicitly and revert to using the whole of supplied key for namespacing 279 | final Iterator metadataKeyParts = "metadata".equals(awsKeyPart1) && remainingKeyParts.hasNext() ? 280 | remainingKeyParts : 281 | allKeyParts.iterator(); 282 | 283 | // Look at the next part of the key: if there are more key parts after this, 284 | // then awsKeyPart2 is the namespace; otherwise assume the default namespace 285 | final String awsKeyPart2 = metadataKeyParts.next(); 286 | final String metadataNamespace = metadataKeyParts.hasNext() ? 287 | awsKeyPart2 : 288 | AWSXRayMetadataNamespaces.DEFAULT; 289 | 290 | // If there are remaining key parts (after the second part) then they are the 291 | // nested keys; otherwise we only had a 2-part key (and will have used the 292 | // DEFAULT namespace) so just use the second part as the nested key 293 | final Iterator namespaceKeyParts = metadataKeyParts.hasNext() ? 294 | metadataKeyParts : 295 | Collections.singletonList(awsKeyPart2).iterator(); 296 | 297 | final Map targetMap = metadata.computeIfAbsent(metadataNamespace, __ -> new ConcurrentHashMap<>()); 298 | setTagAny(namespaceKeyParts, value, targetMap); 299 | } 300 | } 301 | } 302 | 303 | /** 304 | *

305 | * Store an arbitrary value in a nested {@link Map} structure, assuming 306 | * that at each level of nesting we either have a key-value pair, or else 307 | * a key pointing to a nested Map. This mimics the JSON structure used 308 | * by X-Ray to transmit tag and baggage data. 309 | *

310 | * 311 | * For example, given the following existing value for targetMap: 312 | * 313 | *
314 |      * {
315 |      *     "http": {
316 |      *         "request": {
317 |      *             "method": "GET",
318 |      *             "url": "http://www.example.com"
319 |      *         }
320 |      *     }
321 |      * }
322 |      * 
323 | * 324 | * and the inputs: 325 | * 326 | *
327 |      * keyParts: ["http", "response", "code"],
328 |      * value: 200
329 |      * 
330 | * 331 | * we return the value: 332 | * 333 | *
334 |      * {
335 |      *     "http": {
336 |      *         "request": {
337 |      *             "method": "GET",
338 |      *             "url": "http://www.example.com"
339 |      *         },
340 |      *         "response": {
341 |      *             "code": 200
342 |      *         }
343 |      *     }
344 |      * }
345 |      * 
346 | * 347 | * NB this method mutates the underlying value of targetMap so care should 348 | * be taken to only ever pass thread-safe instances (and this method in turn will 349 | * create thread-safe sub-Maps for nested values). 350 | * 351 | * @param remainingKeyParts the list of individual parts of the tag (name) for the value to be stored 352 | * @param value the value to be stored 353 | * @param targetMap the target Map instance 354 | */ 355 | private void setTagAny(Iterator remainingKeyParts, Object value, Map targetMap) { 356 | 357 | // The iterator should never be empty here, but just for safety 358 | if (remainingKeyParts.hasNext()) { 359 | final String nextKeyPart = remainingKeyParts.next(); 360 | 361 | // The key is further nested so recurse to the next level of the map 362 | if (remainingKeyParts.hasNext()) { 363 | 364 | @SuppressWarnings("unchecked") 365 | final Map targetSubMap = (Map) targetMap.computeIfAbsent(nextKeyPart, __ -> new ConcurrentHashMap<>()); 366 | setTagAny(remainingKeyParts, value, targetSubMap); 367 | 368 | // This is the last key, so store at this level 369 | } else { 370 | targetMap.put(nextKeyPart, value); 371 | } 372 | } 373 | } 374 | 375 | @Override 376 | public void finish() { 377 | finish(Instant.now().toEpochMilli() / 1000.0); 378 | } 379 | 380 | @Override 381 | public void finish(long finishMicros) { 382 | finish(finishMicros / 1000.0 / 1000.0); 383 | } 384 | 385 | /** 386 | * Set the end time for this span and close it - this will usually trigger 387 | * sending the trace data back to AWS. 388 | * 389 | * @param finishSeconds timestamp of the end of this span as a number of 390 | * seconds since the UNIX 391 | */ 392 | private void finish(double finishSeconds) { 393 | if (isFinished.compareAndSet(false, true)) { 394 | try { 395 | entity.setEndTime(finishSeconds); 396 | entity.close(); 397 | } catch (Exception e) { 398 | log.error("Failed to close underlying AWS trace Entity: " + e.getMessage(), e); 399 | } 400 | } 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/aws/xray/AWSXRaySpanContext.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib.aws.xray; 2 | 3 | import io.opentracing.SpanContext; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * In AWS, the span context usually only needs to hold the trace header 10 | * information (current trace ID, parent segment ID, sampling decision) such 11 | * that this can be propagated to child processes. 12 | * 13 | * @author ashley.mercer@skylightipv.com 14 | * @see io.opentracing.SpanContext 15 | * @see com.amazonaws.xray.entities.TraceHeader 16 | */ 17 | class AWSXRaySpanContext implements SpanContext { 18 | 19 | private final Map baggage; 20 | 21 | AWSXRaySpanContext(Map baggage) { 22 | this.baggage = new HashMap<>(baggage); 23 | } 24 | 25 | @Override 26 | public Iterable> baggageItems() { 27 | return baggage.entrySet(); 28 | } 29 | 30 | Map getBaggage() { 31 | return baggage; 32 | } 33 | 34 | void setBaggageItem(String key, String value) { 35 | baggage.put(key, value); 36 | } 37 | 38 | String getBaggageItem(String key) { 39 | return baggage.get(key); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/aws/xray/AWSXRayTags.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib.aws.xray; 2 | 3 | import com.amazonaws.xray.entities.Entity; 4 | import com.amazonaws.xray.entities.Segment; 5 | import io.opentracing.tag.BooleanTag; 6 | import io.opentracing.tag.StringTag; 7 | 8 | /** 9 | * Additional known tags which are specific to AWS X-Ray. In general, users 10 | * should prefer the standard OpenTracing tag names and conventions, but it 11 | * can be useful to sometimes take advantage of X-Ray specific functionality. 12 | * 13 | * @author ashley.mercer@skylightipv.com 14 | */ 15 | @SuppressWarnings("WeakerAccess") 16 | public final class AWSXRayTags { 17 | 18 | private AWSXRayTags(){} 19 | 20 | /** 21 | * FAULT indicates whether or not a Span ended in a fault 22 | * (non-recoverable exception) state. 23 | * 24 | * @see Entity#isFault() 25 | */ 26 | public static final BooleanTag FAULT = new BooleanTag("fault"); 27 | 28 | /** 29 | * THROTTLE indicates that the underlying request was throttled, 30 | * usually caused by resource constraints. 31 | * 32 | * @see Entity#isThrottle() 33 | */ 34 | public static final BooleanTag THROTTLE = new BooleanTag("throttle"); 35 | 36 | /** 37 | * IS_SAMPLED indicates that the current trace segment should be 38 | * subject to sampling by X-Ray. 39 | * 40 | * @see Segment#isSampled() 41 | */ 42 | public static final BooleanTag IS_SAMPLED = new BooleanTag("isSampled"); 43 | 44 | /** 45 | * USER is the identifier for the user who initiated this request, 46 | * typically a logged-in website user or IAM username. Should only be 47 | * set on top-level trace Spans. 48 | * 49 | * @see Segment#getUser() 50 | */ 51 | public static final StringTag USER = new StringTag("user"); 52 | 53 | /** 54 | * ORIGIN indicates the type of AWS resource running the application, 55 | * value is typically something like "AWS::EC2::Instance". Should only 56 | * be set on top-level trace Spans. 57 | * 58 | * @see Segment#getOrigin() 59 | */ 60 | public static final StringTag ORIGIN = new StringTag("origin"); 61 | 62 | /** 63 | * PARENT_ID is the unique identifier for the parent span (as 64 | * distinct from the ID of the whole trace). 65 | * 66 | * @see Entity#getParentId() 67 | */ 68 | public static final StringTag PARENT_ID = new StringTag("parentId"); 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/aws/xray/AWSXRayTracer.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib.aws.xray; 2 | 3 | import com.amazonaws.xray.AWSXRayRecorder; 4 | import com.amazonaws.xray.contexts.LambdaSegmentContext; 5 | import com.amazonaws.xray.entities.*; 6 | import io.opentracing.*; 7 | import io.opentracing.propagation.Format; 8 | import io.opentracing.propagation.TextMap; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.time.Instant; 13 | import java.util.Collections; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.concurrent.ConcurrentHashMap; 17 | import java.util.concurrent.atomic.AtomicReference; 18 | 19 | /** 20 | * Top-level OpenTracing {@link Tracer} implementation which is backed 21 | * by the AWS X-Ray client libraries. 22 | * 23 | * @see https://opentracing.io 24 | * @see https://docs.aws.amazon.com/xray/latest/devguide/aws-xray.html 25 | * @author ashley.mercer@skylightipv.com 26 | */ 27 | @SuppressWarnings("WeakerAccess") 28 | public class AWSXRayTracer implements Tracer { 29 | 30 | private static final Logger log = LoggerFactory.getLogger(AWSXRayTracer.class); 31 | 32 | private final AWSXRayRecorder xRayRecorder; 33 | private final AWSXRayScopeManager scopeManager; 34 | 35 | public AWSXRayTracer(AWSXRayRecorder xRayRecorder) { 36 | this.xRayRecorder = xRayRecorder; 37 | this.scopeManager = new AWSXRayScopeManager(xRayRecorder); 38 | } 39 | 40 | @Override 41 | public ScopeManager scopeManager() { 42 | return scopeManager; 43 | } 44 | 45 | @Override 46 | public Span activeSpan() { 47 | return scopeManager.activeSpan(); 48 | } 49 | 50 | @Override 51 | public SpanBuilder buildSpan(String operationName) { 52 | return new AWSXRaySpanBuilderImpl(operationName); 53 | } 54 | 55 | @Override 56 | public void inject(SpanContext spanContext, Format format, C carrier) { 57 | if (format == Format.Builtin.TEXT_MAP || format == Format.Builtin.HTTP_HEADERS) { 58 | final TextMap textMap = (TextMap) carrier; 59 | spanContext.baggageItems().forEach(e -> textMap.put(e.getKey(), e.getValue())); 60 | } 61 | else { 62 | throw new UnsupportedOperationException("Format " + format.toString() + " is not currently supported"); 63 | } 64 | } 65 | 66 | @Override 67 | public SpanContext extract(Format format, C carrier) { 68 | if (format == Format.Builtin.TEXT_MAP || format == Format.Builtin.HTTP_HEADERS) { 69 | final TextMap textMap = (TextMap) carrier; 70 | final Map baggage = new HashMap<>(); 71 | for (Map.Entry e : textMap) { baggage.put(e.getKey(), e.getValue()); } 72 | return new AWSXRaySpanContext(baggage); 73 | } 74 | else { 75 | throw new UnsupportedOperationException("Format " + format.toString() + " is not currently supported"); 76 | } 77 | } 78 | 79 | /** 80 | * AWS-specific {@link io.opentracing.Tracer.SpanBuilder} implementation 81 | */ 82 | private final class AWSXRaySpanBuilderImpl implements SpanBuilder { 83 | 84 | private final String operationName; 85 | 86 | private final Map stringTags; 87 | private final Map booleanTags; 88 | private final Map numberTags; 89 | 90 | /** 91 | * AWS X-Ray timestamps are stored a number of seconds since 92 | * the UNIX epoch, with the fractional part giving sub-second 93 | * precision. Defaults to creation time of this builder. 94 | * 95 | * @see #withStartTimestamp(long) 96 | * @see Entity#getStartTime() 97 | */ 98 | private final AtomicReference startTimestampEpochSeconds; 99 | 100 | /** 101 | * @see SpanBuilder#ignoreActiveSpan() 102 | */ 103 | private final AtomicReference ignoreActiveSpan; 104 | 105 | /** 106 | * Currently only support a single reference to the parent Span (if 107 | * it exists). Other references are not supported. 108 | * 109 | * @see References 110 | */ 111 | private final Map references; 112 | 113 | private AWSXRaySpanBuilderImpl(String operationName) { 114 | this.operationName = operationName; 115 | 116 | this.stringTags = new HashMap<>(); 117 | this.booleanTags = new HashMap<>(); 118 | this.numberTags = new HashMap<>(); 119 | 120 | this.startTimestampEpochSeconds = new AtomicReference<>(); 121 | this.ignoreActiveSpan = new AtomicReference<>(false); 122 | this.references = new ConcurrentHashMap<>(); 123 | } 124 | 125 | @Override 126 | public SpanBuilder asChildOf(SpanContext parent) { 127 | return addReference(References.CHILD_OF, parent); 128 | } 129 | 130 | @Override 131 | public SpanBuilder asChildOf(Span parent) { 132 | if (parent == null) { 133 | return this; 134 | } 135 | else if (parent instanceof AWSXRaySpan) { 136 | return addReference(References.CHILD_OF, new CapturingSpanContext((AWSXRaySpan) parent)); 137 | } 138 | else { 139 | return addReference(References.CHILD_OF, parent.context()); 140 | } 141 | } 142 | 143 | @Override 144 | public SpanBuilder addReference(String referenceType, SpanContext referencedContext) { 145 | if (references.containsKey(referenceType)) { 146 | log.warn("Replacing reference of type '" + referenceType + "': multiple references of the same type are not supported by X-Ray"); 147 | } 148 | references.put(referenceType, referencedContext); 149 | return this; 150 | } 151 | 152 | @Override 153 | public SpanBuilder ignoreActiveSpan() { 154 | ignoreActiveSpan.set(true); 155 | return this; 156 | } 157 | 158 | @Override 159 | public SpanBuilder withTag(String key, String value) { 160 | stringTags.put(key, value); 161 | return this; 162 | } 163 | 164 | @Override 165 | public SpanBuilder withTag(String key, boolean value) { 166 | booleanTags.put(key, value); 167 | return this; 168 | } 169 | 170 | @Override 171 | public SpanBuilder withTag(String key, Number value) { 172 | numberTags.put(key, value); 173 | return this; 174 | } 175 | 176 | @Override 177 | public SpanBuilder withStartTimestamp(long microseconds) { 178 | startTimestampEpochSeconds.set(microseconds / 1000.0 / 1000.0); 179 | return this; 180 | } 181 | 182 | @Override 183 | @Deprecated 184 | public Span startManual() { 185 | return start(); 186 | } 187 | 188 | @Override 189 | public Scope startActive(boolean finishSpanOnClose) { 190 | final Span span = start(); 191 | return scopeManager.activate(span, finishSpanOnClose); 192 | } 193 | 194 | @Override 195 | public Span start() { 196 | 197 | // X-Ray only supports parent-child relationships between spans 198 | // (OpenTracing allows for other references e.g. FOLLOWS_FROM) 199 | references.forEach((key, value) -> { 200 | if (!References.CHILD_OF.equals(key)) { 201 | log.warn("Ignoring reference of type '" + key + "': references of this type are not supported by X-Ray"); 202 | } 203 | }); 204 | 205 | // If an explicit CHILD_OF reference is set, this should override 206 | // any (implicit) reference to the current trace entity 207 | final Entity originalTraceEntity = xRayRecorder.getTraceEntity(); 208 | final SpanContext explicitParentContext = references.get(References.CHILD_OF); 209 | 210 | final Entity parentEntity; 211 | final Map parentBaggage; 212 | 213 | // Because X-Ray an OpenTracing maintain their references to the 214 | // "current" trace separately, we can be in one of four possible states: 215 | // 216 | // 1. an explicit parent is set, and it has captured a full AWSXRaySpan 217 | // i.e. this is an in-memory Span with a real X-Ray Entity 218 | // 219 | if (explicitParentContext instanceof CapturingSpanContext) { 220 | parentEntity = ((CapturingSpanContext) explicitParentContext).span.getEntity(); 221 | parentBaggage = AWSXRayUtils.extract(explicitParentContext.baggageItems()); 222 | } 223 | 224 | // 2. an explicit parent is set but it doesn't have an X-Ray Entity 225 | // attached: we can present a FacadeSegment to X-Ray 226 | // 227 | else if (explicitParentContext != null) { 228 | 229 | // If the parent context has a valid AWS trace ID in its baggage 230 | // (e.g. it came from some remote upstream server) then extract 231 | // the trace and parent segment IDs here 232 | TraceHeader traceHeader = null; 233 | for (Map.Entry e : explicitParentContext.baggageItems()) { 234 | if (TraceHeader.HEADER_KEY.equals(e.getKey())) { 235 | traceHeader = TraceHeader.fromString(e.getValue()); 236 | } 237 | } 238 | final TraceID traceId = null == traceHeader ? null : traceHeader.getRootTraceId(); 239 | final String parentId = null == traceHeader ? null : traceHeader.getParentId(); 240 | final TraceHeader.SampleDecision sampleDecision = traceHeader == null ? null : traceHeader.getSampled(); 241 | 242 | // NB the default FacadeSegment class throws exceptions but we want 243 | // to allow subsegments to be added and removed (even though both 244 | // of these are ultimately a no-op) 245 | parentEntity = new FacadeSegment(xRayRecorder, traceId, parentId, sampleDecision) { 246 | @Override public void addSubsegment(Subsegment subsegment) {} 247 | @Override public void removeSubsegment(Subsegment subsegment) {} 248 | }; 249 | parentBaggage = AWSXRayUtils.extract(explicitParentContext.baggageItems()); 250 | } 251 | 252 | // 3. no explicit parent is set, but ignoreActiveSpan has been set so 253 | // make sure the parent Entity is null (i.e. we'll create a new 254 | // Segment in X-Ray terms) 255 | // 256 | else if (ignoreActiveSpan.get()) { 257 | parentEntity = null; 258 | parentBaggage = Collections.emptyMap(); 259 | } 260 | 261 | // 4. no explicit parent, and ignoreActiveSpan is not set so create an 262 | // implicit reference to the current trace entity (if it exists: 263 | // if it's null we'll instead end up creating a top-level Segment 264 | // instead) 265 | // 266 | else { 267 | parentEntity = originalTraceEntity; 268 | parentBaggage = Collections.emptyMap(); 269 | } 270 | 271 | // X-Ray automatically maintains internal references between Segments and 272 | // Subsegments - rather than trying to replicate that logic here, we cheat 273 | // by (temporarily) overwriting the parent trace Entity, creating the new 274 | // Entity, then setting it back once we're done 275 | xRayRecorder.setTraceEntity(parentEntity); 276 | 277 | // Special case when running in AWS Lambda: the Lambda infrastructure 278 | // creates a top-level trace Segment to which we do not have access, so 279 | // creating another Segment here would be an error. Instead, we need to 280 | // forcibly create a Subsegment. 281 | final boolean isAwsLambda = xRayRecorder.getSegmentContextResolverChain().resolve() instanceof LambdaSegmentContext; 282 | 283 | final Entity childEntity = (xRayRecorder.getTraceEntity() == null && !isAwsLambda) ? 284 | xRayRecorder.beginSegment(operationName) : 285 | xRayRecorder.beginSubsegment(operationName); 286 | 287 | // Set the original trace entity back on AWSXRayRecorder as soon as possible 288 | xRayRecorder.setTraceEntity(originalTraceEntity); 289 | 290 | // AWS X-Ray doesn't support the notion of "not-yet-started" segments 291 | // so set the Entity to be "in progress" 292 | childEntity.setInProgress(true); 293 | 294 | // Default to "now" if an explicit start time wasn't set 295 | startTimestampEpochSeconds.compareAndSet(null, Instant.now().toEpochMilli() / 1000.0); 296 | childEntity.setStartTime(startTimestampEpochSeconds.get()); 297 | 298 | // Baggage items should mostly be carried over from the parent Span's 299 | // context (if it exists) to the child Span; however, the TraceHeader 300 | // should be replaced with the new value for the child span 301 | final TraceHeader traceHeader = new TraceHeader( 302 | childEntity.getParentSegment().getTraceId(), 303 | null == parentEntity ? null : parentEntity.getId(), 304 | childEntity.getParentSegment().isSampled() ? TraceHeader.SampleDecision.SAMPLED : TraceHeader.SampleDecision.NOT_SAMPLED 305 | ); 306 | 307 | final Map childBaggage = new HashMap<>(parentBaggage); 308 | childBaggage.put(TraceHeader.HEADER_KEY, traceHeader.toString()); 309 | 310 | final AWSXRaySpanContext newSpanContext = new AWSXRaySpanContext(childBaggage); 311 | 312 | // Defer to AWSXRaySpan to set tag values since this will handle 313 | // converting to X-Ray's naming conventions and format 314 | final AWSXRaySpan newSpan = new AWSXRaySpan(childEntity, newSpanContext); 315 | stringTags.forEach(newSpan::setTag); 316 | booleanTags.forEach(newSpan::setTag); 317 | numberTags.forEach(newSpan::setTag); 318 | 319 | return newSpan; 320 | } 321 | } 322 | 323 | /** 324 | * Parent-child relationships between Spans are typically only defined in 325 | * terms of the SpanContext (i.e. we only need to know the parent span's 326 | * trace and span ID). However, X-Ray also holds directly object references 327 | * to the underlying Segment and Subsegment instances, so try to capture 328 | * the full AWSXRaySpan instance here if we can. 329 | */ 330 | private static final class CapturingSpanContext implements SpanContext { 331 | private final AWSXRaySpan span; 332 | 333 | public CapturingSpanContext(AWSXRaySpan span) { 334 | this.span = span; 335 | } 336 | 337 | @Override 338 | public Iterable> baggageItems() { 339 | return span.context().baggageItems(); 340 | } 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/aws/xray/AWSXRayUtils.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib.aws.xray; 2 | 3 | import io.opentracing.SpanContext; 4 | 5 | import java.util.Collections; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * @author ashley.mercer@skylightipv.com 11 | */ 12 | @SuppressWarnings("WeakerAccess") 13 | public final class AWSXRayUtils { 14 | 15 | private AWSXRayUtils(){} 16 | 17 | /** 18 | * Utility method to copy baggage values from a {@link SpanContext} 19 | * back into a vanilla Map, which is much easier to work with. 20 | * 21 | * @param baggage the view of underlying baggage 22 | * @return a new, unmodifiable Map instance containing all 23 | * of the values copied from the underlying baggage 24 | */ 25 | public static Map extract(Iterable> baggage) { 26 | final Map targetMap = new HashMap<>(); 27 | baggage.forEach(e -> targetMap.put(e.getKey(), e.getValue())); 28 | return Collections.unmodifiableMap(targetMap); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/io/opentracing/contrib/tag/ExtraTags.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib.tag; 2 | 3 | import io.opentracing.tag.IntTag; 4 | import io.opentracing.tag.StringTag; 5 | 6 | /** 7 | * Additional suggested standard tag names that conform to 8 | * the OpenTracing naming patterns. 9 | * 10 | * @author ashley.mercer@skylightipv.com 11 | */ 12 | public final class ExtraTags { 13 | 14 | private ExtraTags(){} 15 | 16 | /** 17 | * VERSION records the software version of the current application 18 | */ 19 | public static final StringTag VERSION = new StringTag("version"); 20 | 21 | /** 22 | * DB_DRIVER records the name and / or version number of the client-side 23 | * database driver 24 | */ 25 | public static final StringTag DB_DRIVER = new StringTag("db.driver"); 26 | 27 | /** 28 | * DB_VERSION captures the database server-side version String 29 | */ 30 | public static final StringTag DB_VERSION = new StringTag("db.version"); 31 | 32 | /** 33 | * HTTP_CLIENT_IP records the IP address of the requester, could be captured 34 | * e.g. from the IP packet's 'Source Address' or the X-Forwarded-For header 35 | */ 36 | public static final StringTag HTTP_CLIENT_IP = new StringTag("http.client_ip"); 37 | 38 | /** 39 | * HTTP_USER_AGENT records the HTTP client's User-Agent header 40 | */ 41 | public static final StringTag HTTP_USER_AGENT = new StringTag("http.user_agent"); 42 | 43 | /** 44 | * HTTP_CONTENT_LENGTH records the number of bytes returned to the client 45 | * by the server over the course of the request 46 | */ 47 | public static final IntTag HTTP_CONTENT_LENGTH = new IntTag("http.content_length"); 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/io/opentracing/contrib/aws/xray/AWSXRaySpanBuilderTests.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib.aws.xray; 2 | 3 | import com.amazonaws.xray.AWSXRayRecorder; 4 | import com.amazonaws.xray.AWSXRayRecorderBuilder; 5 | import com.amazonaws.xray.entities.Entity; 6 | import com.amazonaws.xray.entities.Segment; 7 | import com.amazonaws.xray.entities.TraceHeader; 8 | import io.opentracing.Scope; 9 | import io.opentracing.Span; 10 | import io.opentracing.SpanContext; 11 | import org.junit.jupiter.api.DisplayName; 12 | import org.junit.jupiter.api.Test; 13 | 14 | import java.util.Collections; 15 | import java.util.Map; 16 | 17 | import static org.junit.jupiter.api.Assertions.*; 18 | 19 | /** 20 | * @author ashley.mercer@skylightipv.com 21 | */ 22 | class AWSXRaySpanBuilderTests extends AWSXRayTestParent { 23 | 24 | private final AWSXRayRecorder recorder = AWSXRayRecorderBuilder.defaultRecorder(); 25 | private final AWSXRayTracer tracer = new AWSXRayTracer(recorder); 26 | 27 | @Test 28 | @DisplayName("set span name") 29 | void setSpanName() { 30 | final Span span = tracer 31 | .buildSpan("test-span-name") 32 | .start(); 33 | 34 | assertEquals("test-span-name", ((AWSXRaySpan) span).getEntity().getName()); 35 | } 36 | 37 | @Test 38 | @DisplayName("set active span") 39 | void setActiveSpan() { 40 | final Scope activeScope = tracer 41 | .buildSpan("test-active-span") 42 | .startActive(true); 43 | 44 | assertEquals(activeScope.span(), tracer.activeSpan()); 45 | 46 | activeScope.close(); 47 | } 48 | 49 | @Test 50 | @DisplayName("set trace header in baggage") 51 | void setTraceHeaderInBaggage() { 52 | final Scope activeScope = tracer 53 | .buildSpan("test-trace-header") 54 | .startActive(true); 55 | 56 | final String activeTraceHeader = activeScope.span().getBaggageItem(TraceHeader.HEADER_KEY); 57 | assertNotNull(activeTraceHeader); 58 | assertTrue(activeTraceHeader.contains(((AWSXRaySpan) activeScope.span()).getEntity().getTraceId().toString())); 59 | 60 | activeScope.close(); 61 | } 62 | 63 | @Test 64 | @DisplayName("set implicit active span as parent") 65 | void setImplicitParentSpan() { 66 | final Scope parentScope = tracer 67 | .buildSpan("parent-span") 68 | .startActive(true); 69 | 70 | final Scope childScope = tracer 71 | .buildSpan("child-span") 72 | .startActive(true); 73 | 74 | final Entity parentEntity = ((AWSXRayScope) parentScope).span().getEntity(); 75 | final Entity childEntity = ((AWSXRayScope) childScope).span().getEntity(); 76 | 77 | assertFalse(parentEntity.getSubsegments().isEmpty()); 78 | assertEquals(parentEntity, childEntity.getParent()); 79 | assertEquals(parentEntity.getTraceId(), childEntity.getParent().getTraceId()); 80 | 81 | // Check that trace header is correctly set in the child 82 | final String childTraceHeader = childScope.span().getBaggageItem(TraceHeader.HEADER_KEY); 83 | assertNotNull(childTraceHeader); 84 | assertTrue(childTraceHeader.contains(parentEntity.getTraceId().toString())); 85 | assertTrue(childTraceHeader.contains(parentEntity.getId())); 86 | 87 | childScope.close(); 88 | parentScope.close(); 89 | } 90 | 91 | @Test 92 | @DisplayName("set explicit span as parent") 93 | void setExplicitParentSpan() { 94 | 95 | // NB we *don't* startActive here - assume this Span 96 | // object came from somewhere else in the code 97 | final AWSXRaySpan explicitParentSpan = mockSpan("explicit-parent-span"); 98 | 99 | // This implicit parent should be ignored by SpanBuilder 100 | // when we set the explicit parent 101 | final Scope implicitParentScope = tracer 102 | .buildSpan("implicit-parent-span") 103 | .startActive(true); 104 | 105 | final Scope childScope = tracer 106 | .buildSpan("child-span") 107 | .asChildOf(explicitParentSpan) 108 | .startActive(true); 109 | 110 | final Entity explicitParentEntity = explicitParentSpan.getEntity(); 111 | final Entity implicitParentEntity = ((AWSXRayScope) implicitParentScope).span().getEntity(); 112 | final Entity childEntity = ((AWSXRayScope) childScope).span().getEntity(); 113 | 114 | assertFalse(explicitParentEntity.getSubsegments().isEmpty()); 115 | assertTrue(implicitParentEntity.getSubsegments().isEmpty()); 116 | 117 | assertEquals(explicitParentEntity, childEntity.getParent()); 118 | assertNotEquals(explicitParentEntity.getId(), childEntity.getId()); 119 | 120 | // Check that trace header is correctly set in the child 121 | final String childTraceHeader = childScope.span().getBaggageItem(TraceHeader.HEADER_KEY); 122 | assertNotNull(childTraceHeader); 123 | assertTrue(childTraceHeader.contains(explicitParentEntity.getTraceId().toString())); 124 | assertTrue(childTraceHeader.contains(explicitParentEntity.getId())); 125 | 126 | childScope.close(); 127 | implicitParentScope.close(); 128 | } 129 | 130 | @Test 131 | @DisplayName("set explicit span as parent from remote server") 132 | void setExplicitParentSpanFromRemote() { 133 | 134 | // SpanContext can be passed to remote servers using inject() and 135 | // extract(), so assume we read this in from e.g. HTTP headers 136 | final SpanContext remoteContext = new AWSXRaySpanContext(Collections.singletonMap( 137 | TraceHeader.HEADER_KEY, 138 | traceHeader.toString() 139 | )); 140 | 141 | final Scope childScope = tracer 142 | .buildSpan("child-span") 143 | .asChildOf(remoteContext) 144 | .startActive(true); 145 | 146 | final Entity childEntity = ((AWSXRayScope) childScope).span().getEntity(); 147 | 148 | assertEquals(childEntity.getParentSegment().getTraceId(), traceHeader.getRootTraceId()); 149 | assertEquals(childEntity.getParentSegment().getId(), traceHeader.getParentId()); 150 | 151 | // Check that trace header is correctly set in the child 152 | final String childTraceHeader = childScope.span().getBaggageItem(TraceHeader.HEADER_KEY); 153 | assertNotNull(childTraceHeader); 154 | 155 | childScope.close(); 156 | } 157 | 158 | @Test 159 | @DisplayName("ignore implicit active span on ignoreActiveSpan") 160 | void ignoreImplicitParentSpan() { 161 | final Scope parentScope = tracer 162 | .buildSpan("parent-span") 163 | .startActive(true); 164 | 165 | final Scope childScope = tracer 166 | .buildSpan("child-span") 167 | .ignoreActiveSpan() 168 | .startActive(true); 169 | 170 | final Entity parentEntity = ((AWSXRayScope) parentScope).span().getEntity(); 171 | final Entity childEntity = ((AWSXRayScope) childScope).span().getEntity(); 172 | 173 | assertTrue(parentEntity.getSubsegments().isEmpty()); 174 | assertNull(childEntity.getParent()); 175 | assertNotEquals(parentEntity.getParentSegment().getTraceId(), childEntity.getParentSegment().getTraceId()); 176 | 177 | // Check that trace header is correctly set in the child 178 | final String childTraceHeader = childScope.span().getBaggageItem(TraceHeader.HEADER_KEY); 179 | assertNotNull(childTraceHeader); 180 | assertTrue(childTraceHeader.contains(childEntity.getParentSegment().getTraceId().toString())); 181 | assertFalse(childTraceHeader.contains(parentEntity.getParentSegment().getTraceId().toString())); 182 | assertFalse(childTraceHeader.contains(parentEntity.getId())); 183 | 184 | childScope.close(); 185 | parentScope.close(); 186 | } 187 | 188 | /** 189 | * In systems where the surrounding code is using X-Ray directly, but 190 | * not the OpenTracing API, we should detect if a trace is already in 191 | * progress. For example, in AWS Lambda functions, the lambda server 192 | * creates a top-level trace segment for the whole function call 193 | * 194 | * @see https://docs.aws.amazon.com/xray/latest/devguide/xray-services-lambda.html 195 | */ 196 | @Test 197 | @DisplayName("detect a pre-existing X-Ray trace") 198 | void detectPreExisting() { 199 | final Segment parentEntity = awsxRayRecorder.beginSegment("pre-existing-trace"); 200 | 201 | final Scope childScope = tracer 202 | .buildSpan("child-of-pre-existing-trace") 203 | .startActive(true); 204 | 205 | final Entity childEntity = ((AWSXRayScope) childScope).span().getEntity(); 206 | 207 | assertFalse(parentEntity.getSubsegments().isEmpty()); 208 | assertEquals(parentEntity, childEntity.getParent()); 209 | 210 | // Check that trace header is correctly set in the child 211 | final String childTraceHeader = childScope.span().getBaggageItem(TraceHeader.HEADER_KEY); 212 | assertNotNull(childTraceHeader); 213 | assertTrue(childTraceHeader.contains(parentEntity.getTraceId().toString())); 214 | assertTrue(childTraceHeader.contains(parentEntity.getId())); 215 | 216 | childScope.close(); 217 | } 218 | 219 | @Test 220 | @DisplayName("set tags correctly") 221 | @SuppressWarnings("unchecked") 222 | void setTags() { 223 | final Scope scope = tracer 224 | .buildSpan("test-set-tags") 225 | .withTag("http.request.method", "POST") 226 | .withTag("http.response.status_code", 503) 227 | .withTag("fault", true) 228 | .startActive(true); 229 | 230 | final Entity entity = ((AWSXRayScope) scope).span().getEntity(); 231 | assertEquals("POST", ((Map) entity.getHttp().get("request")).get("method")); 232 | assertEquals(503, ((Map) entity.getHttp().get("response")).get("status_code")); 233 | assertTrue(entity.isFault()); 234 | 235 | scope.close(); 236 | } 237 | 238 | @Test 239 | @DisplayName("set start timestamp correctly") 240 | void setStartTimestamp() { 241 | final Scope scope = tracer 242 | .buildSpan("test-set-start-timestamp") 243 | .withStartTimestamp(1551016321000000L) 244 | .startActive(true); 245 | 246 | final Entity entity = ((AWSXRayScope) scope).span().getEntity(); 247 | assertEquals(1551016321.0, entity.getStartTime()); 248 | 249 | scope.close(); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/test/java/io/opentracing/contrib/aws/xray/AWSXRaySpanTests.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib.aws.xray; 2 | 3 | import com.amazonaws.xray.entities.Segment; 4 | import io.opentracing.log.Fields; 5 | import io.opentracing.tag.Tags; 6 | import org.junit.jupiter.api.DisplayName; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.time.Instant; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.function.Function; 13 | 14 | import static org.junit.jupiter.api.Assertions.*; 15 | 16 | /** 17 | * @author ashley.mercer@skylightipv.com 18 | */ 19 | class AWSXRaySpanTests extends AWSXRayTestParent { 20 | 21 | @Test 22 | @DisplayName("set ERROR tag on underlying Entity") 23 | void tagError() { 24 | final AWSXRaySpan span = mockSpan("test-tag-is-error"); 25 | span.setTag(Tags.ERROR.getKey(), true); 26 | 27 | assertTrue(span.getEntity().isError()); 28 | assertFalse(span.getEntity().isFault()); 29 | } 30 | 31 | @Test 32 | @DisplayName("set FAULT tag on underlying Entity") 33 | void tagFault() { 34 | final AWSXRaySpan span = mockSpan("test-tag-is-fault"); 35 | span.setTag(AWSXRayTags.FAULT.getKey(), true); 36 | 37 | assertFalse(span.getEntity().isError()); 38 | assertTrue(span.getEntity().isFault()); 39 | } 40 | 41 | @Test 42 | @DisplayName("set THROTTLE tag on underlying Entity") 43 | void tagThrottle() { 44 | final AWSXRaySpan span = mockSpan("test-tag-is-throttle"); 45 | span.setTag(AWSXRayTags.THROTTLE.getKey(), true); 46 | 47 | assertFalse(span.getEntity().isFault()); 48 | assertTrue(span.getEntity().isThrottle()); 49 | } 50 | 51 | @Test 52 | @DisplayName("set IS_SAMPLED tag on underlying Entity") 53 | void tagSampled() { 54 | final AWSXRaySpan span = mockSpan("test-tag-is-sampled"); 55 | assertTrue(span.getEntity() instanceof Segment); 56 | 57 | span.setTag(AWSXRayTags.IS_SAMPLED.getKey(), false); 58 | assertFalse(((Segment) span.getEntity()).isSampled()); 59 | 60 | span.setTag(AWSXRayTags.IS_SAMPLED.getKey(), true); 61 | assertTrue(((Segment) span.getEntity()).isSampled()); 62 | } 63 | 64 | @Test 65 | @DisplayName("set USER tag on underlying Entity") 66 | void tagUser() { 67 | final String expectedUser = "test.user@example.com"; 68 | 69 | final AWSXRaySpan span = mockSpan("test-tag-user"); 70 | span.setTag(AWSXRayTags.USER.getKey(), expectedUser); 71 | 72 | assertTrue(span.getEntity() instanceof Segment); 73 | assertEquals(expectedUser, ((Segment) span.getEntity()).getUser()); 74 | } 75 | 76 | @Test 77 | @DisplayName("set ORIGIN tag on underlying Entity") 78 | void tagOrigin() { 79 | final String expectedOrigin = "AWS::EC2::Instance"; 80 | 81 | final AWSXRaySpan span = mockSpan("test-tag-origin"); 82 | span.setTag(AWSXRayTags.ORIGIN.getKey(), expectedOrigin); 83 | 84 | assertTrue(span.getEntity() instanceof Segment); 85 | assertEquals(expectedOrigin, ((Segment) span.getEntity()).getOrigin()); 86 | } 87 | 88 | @Test 89 | @DisplayName("set PARENT_ID tag on underlying Entity") 90 | void tagParentId() { 91 | final String expectedParentId = ""; 92 | 93 | final AWSXRaySpan span = mockSpan("test-tag-parent-id"); 94 | span.setTag(AWSXRayTags.PARENT_ID.getKey(), expectedParentId); 95 | 96 | assertEquals(expectedParentId, span.getEntity().getParentId()); 97 | } 98 | 99 | @Test 100 | @DisplayName("tag annotations values") 101 | void tagAnnotations() { 102 | testSpecialTags("annotations", s -> s.getEntity().getAnnotations()); 103 | } 104 | 105 | @Test 106 | @DisplayName("tag AWS values") 107 | void tagAws() { 108 | testSpecialTags("aws", s -> s.getEntity().getAws()); 109 | } 110 | 111 | @Test 112 | @DisplayName("tag HTTP values") 113 | void tagHTTP() { 114 | testSpecialTags("http", s -> s.getEntity().getHttp()); 115 | } 116 | 117 | @Test 118 | @DisplayName("tag service values") 119 | void tagService() { 120 | testSpecialTags("service", s -> ((Segment) s.getEntity()).getService()); 121 | } 122 | 123 | @Test 124 | @DisplayName("tag SQL values") 125 | void tagSQL() { 126 | testSpecialTags("sql", s -> s.getEntity().getSql()); 127 | } 128 | 129 | @Test 130 | @DisplayName("tag metadata with empty key") 131 | void tagMetadataEmptyKey() { 132 | testMetadataTags("", AWSXRayMetadataNamespaces.DEFAULT, ""); 133 | } 134 | 135 | @Test 136 | @DisplayName("tag metadata with no prefix, no namespace") 137 | void tagMetadataNoPrefixNoNamespace() { 138 | testMetadataTags("foo", AWSXRayMetadataNamespaces.DEFAULT, "foo"); 139 | } 140 | @Test 141 | @DisplayName("tag metadata with no prefix, no namespace, key=metadata") 142 | void tagMetadataNoPrefixNoNamespaceAsNamespace() { 143 | testMetadataTags("metadata", AWSXRayMetadataNamespaces.DEFAULT, "metadata"); 144 | } 145 | 146 | @Test 147 | @DisplayName("tag metadata with no namespace") 148 | void tagMetadataNoNamespace() { 149 | testMetadataTags("metadata.foo", AWSXRayMetadataNamespaces.DEFAULT, "foo"); 150 | } 151 | 152 | @Test 153 | @DisplayName("tag metadata with no prefix") 154 | void tagMetadataNoPrefix() { 155 | testMetadataTags("bar.foo", "bar", "foo"); 156 | } 157 | 158 | @Test 159 | @DisplayName("tag metadata with full name") 160 | void tagMetadataFullName() { 161 | testMetadataTags("metadata.bar.foo", "bar", "foo"); 162 | } 163 | 164 | @Test 165 | @DisplayName("tag metadata with nested key") 166 | void tagMetadataNestedKey() { 167 | final AWSXRaySpan span = mockSpan("test-tag-metadata-nested-key"); 168 | span.setTag("metadata.default.nested.is_test", true); 169 | span.setTag("metadata.default.nested.counter", 42); 170 | span.setTag("metadata.default.nested.service", "backend"); 171 | 172 | @SuppressWarnings("unchecked") 173 | final Map targetMap = (Map) span.getEntity().getMetadata().get(AWSXRayMetadataNamespaces.DEFAULT).get("nested"); 174 | assertEquals(true, targetMap.get("is_test")); 175 | assertEquals(42, targetMap.get("counter")); 176 | assertEquals("backend", targetMap.get("service")); 177 | } 178 | 179 | private void testSpecialTags(String keyPrefix, Function> getTargetMap) { 180 | final AWSXRaySpan span = mockSpan("test-tag-" + keyPrefix); 181 | span.setTag(keyPrefix + ".is_test", true); 182 | span.setTag(keyPrefix + ".counter", 42); 183 | span.setTag(keyPrefix + ".service", "backend"); 184 | 185 | final Map targetMap = getTargetMap.apply(span); 186 | assertEquals(true, targetMap.get("is_test")); 187 | assertEquals(42, targetMap.get("counter")); 188 | assertEquals("backend", targetMap.get("service")); 189 | } 190 | 191 | private void testMetadataTags(String fullKey, String expectedNamespace, String expectedKey) { 192 | final AWSXRaySpan span = mockSpan("test-tag-metadata-" + fullKey.replaceAll("\\.", "-")); 193 | span.setTag(fullKey + "_boolean", true); 194 | span.setTag(fullKey + "_number", 42); 195 | span.setTag(fullKey + "_string", "backend"); 196 | 197 | final Map targetMap = span.getEntity().getMetadata().get(expectedNamespace); 198 | assertEquals(true, targetMap.get(expectedKey + "_boolean")); 199 | assertEquals(42, targetMap.get(expectedKey + "_number")); 200 | assertEquals("backend", targetMap.get(expectedKey + "_string")); 201 | } 202 | 203 | private final String LOG_MESSAGE = "This is a log message"; 204 | private final Map LOG_OBJECT = new HashMap<>(); 205 | { 206 | LOG_OBJECT.put(Fields.MESSAGE, LOG_MESSAGE); 207 | LOG_OBJECT.put(Fields.EVENT, "timeout"); 208 | } 209 | 210 | @Test 211 | @DisplayName("log raw event") 212 | void logRawEvent() { 213 | final AWSXRaySpan span = mockSpan("test-log-raw-event"); 214 | span.log(LOG_MESSAGE); 215 | 216 | assertNotNull(span.getEntity().getMetadata().get(AWSXRayMetadataNamespaces.LOG)); 217 | assertEquals(1, span.getEntity().getMetadata().get(AWSXRayMetadataNamespaces.LOG).size()); 218 | } 219 | 220 | @Test 221 | @DisplayName("log raw event with timestamp") 222 | void logRawEventWithTimestamp() { 223 | final AWSXRaySpan span = mockSpan("test-log-raw-event-with-timestamp"); 224 | span.log(Instant.now().toEpochMilli() * 1000, LOG_MESSAGE); 225 | 226 | assertNotNull(span.getEntity().getMetadata().get(AWSXRayMetadataNamespaces.LOG)); 227 | assertEquals(1, span.getEntity().getMetadata().get(AWSXRayMetadataNamespaces.LOG).size()); 228 | } 229 | 230 | @Test 231 | @DisplayName("log structured event") 232 | void logStructuredEvent() { 233 | final AWSXRaySpan span = mockSpan("test-log-structured-event"); 234 | span.log(LOG_OBJECT); 235 | 236 | assertNotNull(span.getEntity().getMetadata().get(AWSXRayMetadataNamespaces.LOG)); 237 | assertEquals(1, span.getEntity().getMetadata().get(AWSXRayMetadataNamespaces.LOG).size()); 238 | } 239 | 240 | @Test 241 | @DisplayName("log structured event with timestamp") 242 | void logStructuredEventWithTimestamp() { 243 | final AWSXRaySpan span = mockSpan("test-log-structured-event"); 244 | span.log(Instant.now().toEpochMilli() * 1000, LOG_OBJECT); 245 | 246 | assertNotNull(span.getEntity().getMetadata().get(AWSXRayMetadataNamespaces.LOG)); 247 | assertEquals(1, span.getEntity().getMetadata().get(AWSXRayMetadataNamespaces.LOG).size()); 248 | } 249 | 250 | @Test 251 | @DisplayName("log multiple events") 252 | void logMultipleEvents() throws InterruptedException { 253 | final AWSXRaySpan span = mockSpan("test-log-multiple-events"); 254 | 255 | // Currently only support millisecond precision for logs, so 256 | // multiple logs can have the same timestamp which fails the test 257 | span.log(LOG_MESSAGE); 258 | Thread.sleep(5); 259 | span.log(LOG_MESSAGE); 260 | Thread.sleep(5); 261 | span.log(LOG_MESSAGE); 262 | 263 | assertEquals(3, span.getEntity().getMetadata().get(AWSXRayMetadataNamespaces.LOG).size()); 264 | } 265 | 266 | @Test 267 | @DisplayName("set baggage") 268 | void setBaggage() { 269 | final AWSXRaySpan span = mockSpan("test-set-baggage"); 270 | span.setBaggageItem("baggage_key", "value"); 271 | 272 | assertNotNull(span.context().getBaggage()); 273 | assertFalse(span.context().getBaggage().isEmpty()); 274 | assertTrue(span.context().baggageItems().iterator().hasNext()); 275 | assertEquals("value", span.getBaggageItem("baggage_key")); 276 | assertEquals("value", span.context().getBaggageItem("baggage_key")); 277 | } 278 | 279 | @Test 280 | @DisplayName("refuse to set operation name after construction") 281 | void setOperationName() { 282 | final AWSXRaySpan span = mockSpan("test-set-operation-name"); 283 | assertThrows(Exception.class, () -> span.setOperationName("some-other-name")); 284 | } 285 | 286 | @Test 287 | @DisplayName("close the underlying X-Ray Entity on finish") 288 | void finish() { 289 | 290 | // Fake scope management here by setting the current trace Entity 291 | final AWSXRaySpan span = mockSpan("test-finish"); 292 | awsxRayRecorder.setTraceEntity(span.getEntity()); 293 | assertTrue(span.getEntity().isInProgress()); 294 | 295 | span.finish(); 296 | 297 | // X-Ray automatically unsets the current trace Entity on completion 298 | assertFalse(span.getEntity().isInProgress()); 299 | assertNull(awsxRayRecorder.getTraceEntity()); 300 | } 301 | 302 | @Test 303 | @DisplayName("ignore repeated calls to finish") 304 | void finishMultiple() { 305 | 306 | // Fake scope management here by setting the current trace entity 307 | final AWSXRaySpan span = mockSpan("test-finish"); 308 | awsxRayRecorder.setTraceEntity(span.getEntity()); 309 | assertTrue(span.getEntity().isInProgress()); 310 | 311 | // If the second call to finish() proceeded, X-Ray would throw 312 | // an exception because the underlying Entity has already completed 313 | span.finish(); 314 | span.finish(); 315 | 316 | // X-Ray automatically unsets the current trace Entity on completion 317 | assertFalse(span.getEntity().isInProgress()); 318 | assertNull(awsxRayRecorder.getTraceEntity()); 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /src/test/java/io/opentracing/contrib/aws/xray/AWSXRayTestParent.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib.aws.xray; 2 | 3 | import com.amazonaws.xray.AWSXRayRecorder; 4 | import com.amazonaws.xray.AWSXRayRecorderBuilder; 5 | import com.amazonaws.xray.entities.SegmentImpl; 6 | import com.amazonaws.xray.entities.TraceHeader; 7 | import com.amazonaws.xray.entities.TraceID; 8 | import io.opentracing.Tracer; 9 | 10 | import java.util.Collections; 11 | 12 | /** 13 | * @author ashley.mercer@skylightipv.com 14 | */ 15 | abstract class AWSXRayTestParent { 16 | 17 | final AWSXRayRecorder awsxRayRecorder = AWSXRayRecorderBuilder.defaultRecorder(); 18 | 19 | /** 20 | * Make sure this reference stays as pure {@link Tracer} since we don't 21 | * want to rely on implementation-specific details or return types. 22 | */ 23 | final Tracer tracer = new AWSXRayTracer(awsxRayRecorder); 24 | 25 | /** 26 | * A sample {@link com.amazonaws.xray.entities.TraceHeader} value to use 27 | * for testing. NB we use all parts (root, parent, sampling decision) to 28 | * ensure that they all get propagated correctly. 29 | */ 30 | final TraceHeader traceHeader = new TraceHeader( 31 | new TraceID(), 32 | "0f15eadda7879f1d", 33 | TraceHeader.SampleDecision.SAMPLED 34 | ); 35 | 36 | /** 37 | * @param operationName the operation name 38 | */ 39 | AWSXRaySpan mockSpan(String operationName) { 40 | return new AWSXRaySpan( 41 | new SegmentImpl(awsxRayRecorder, operationName), 42 | new AWSXRaySpanContext(Collections.emptyMap())); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/io/opentracing/contrib/aws/xray/AWSXRayTracerTests.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.contrib.aws.xray; 2 | 3 | import com.amazonaws.xray.entities.TraceHeader; 4 | import io.opentracing.Scope; 5 | import io.opentracing.SpanContext; 6 | import io.opentracing.propagation.*; 7 | import org.junit.jupiter.api.DisplayName; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.util.Collections; 11 | import java.util.HashMap; 12 | 13 | import static org.junit.jupiter.api.Assertions.*; 14 | 15 | /** 16 | * @author ashley.mercer@skylightipv.com 17 | */ 18 | class AWSXRayTracerTests extends AWSXRayTestParent { 19 | 20 | @Test 21 | @DisplayName("store a reference to the current span") 22 | void storeReference() { 23 | final Scope scope = tracer 24 | .buildSpan("simple-span") 25 | .startActive(true); 26 | 27 | assertNotNull(tracer.activeSpan()); 28 | assertNotNull(tracer.scopeManager().active().span()); 29 | 30 | assertEquals(tracer.activeSpan(), scope.span()); 31 | assertEquals(tracer.scopeManager().active().span(), scope.span()); 32 | 33 | scope.close(); 34 | } 35 | 36 | @Test 37 | @DisplayName("succeed on SpanContext injection (empty)") 38 | void contextInjectEmpty() { 39 | final TextMap textMap = new TextMapAdapter(new HashMap<>()); 40 | final SpanContext context = new AWSXRaySpanContext(new HashMap<>()); 41 | 42 | tracer.inject(context, Format.Builtin.TEXT_MAP, textMap); 43 | assertFalse(textMap.iterator().hasNext()); 44 | } 45 | 46 | @Test 47 | @DisplayName("succeed on SpanContext injection (TraceID)") 48 | void contextInjectTraceId() { 49 | final TextMap textMap = new TextMapAdapter(new HashMap<>()); 50 | final SpanContext context = new AWSXRaySpanContext(Collections.singletonMap( 51 | TraceHeader.HEADER_KEY, 52 | traceHeader.toString() 53 | )); 54 | 55 | tracer.inject(context, Format.Builtin.TEXT_MAP, textMap); 56 | assertTrue(textMap.iterator().hasNext()); 57 | 58 | final TraceHeader extractedTraceHeader = TraceHeader.fromString(textMap.iterator().next().getValue()); 59 | assertEquals(traceHeader.getRootTraceId(), extractedTraceHeader.getRootTraceId()); 60 | assertEquals(traceHeader.getParentId(), extractedTraceHeader.getParentId()); 61 | assertEquals(traceHeader.getSampled(), extractedTraceHeader.getSampled()); 62 | } 63 | 64 | @Test 65 | @DisplayName("succeed on SpanContext extraction (empty)") 66 | void contextExtractEmpty() { 67 | final TextMap textMap = new TextMapAdapter(new HashMap<>()); 68 | final SpanContext context = tracer.extract(Format.Builtin.TEXT_MAP, textMap); 69 | assertFalse(context.baggageItems().iterator().hasNext()); 70 | } 71 | 72 | @Test 73 | @DisplayName("succeed on SpanContext extraction (TraceID)") 74 | void contextExtractTraceId() { 75 | final TextMap textMap = new TextMapAdapter(Collections.singletonMap( 76 | TraceHeader.HEADER_KEY, 77 | traceHeader.toString() 78 | )); 79 | 80 | final SpanContext context = tracer.extract(Format.Builtin.TEXT_MAP, textMap); 81 | assertTrue(context.baggageItems().iterator().hasNext()); 82 | 83 | final TraceHeader extractedTraceHeader = TraceHeader.fromString(context.baggageItems().iterator().next().getValue()); 84 | assertEquals(traceHeader.getRootTraceId(), extractedTraceHeader.getRootTraceId()); 85 | assertEquals(traceHeader.getParentId(), extractedTraceHeader.getParentId()); 86 | assertEquals(traceHeader.getSampled(), extractedTraceHeader.getSampled()); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/io/opentracing/propagation/TextMapAdapter.java: -------------------------------------------------------------------------------- 1 | package io.opentracing.propagation; 2 | 3 | import java.util.Iterator; 4 | import java.util.Map; 5 | 6 | /** 7 | * @author ashley.mercer@skylightipv.com 8 | */ 9 | public class TextMapAdapter implements TextMap { 10 | 11 | private final Map map; 12 | 13 | public TextMapAdapter(Map map) { 14 | this.map = map; 15 | } 16 | 17 | @Override 18 | public Iterator> iterator() { 19 | return map.entrySet().iterator(); 20 | } 21 | 22 | @Override 23 | public void put(String key, String value) { 24 | map.put(key, value); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | [%level] %m%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /travis/publish.sh: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016-2017 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_or_release() { 39 | # release to bintray si done from master or release-X.Y maintenance branches 40 | if [[ "${TRAVIS_BRANCH}" = master || "${TRAVIS_BRANCH}" =~ ^release-[0-9]+\.[0-9]+$ ]]; then 41 | echo "[Publishing] Travis branch is master" 42 | return 0 43 | else 44 | echo "[Not Publishing] Travis branch is not master" 45 | return 1 46 | fi 47 | } 48 | 49 | check_travis_branch_equals_travis_tag() { 50 | #Weird comparison comparing branch to tag because when you 'git push --tags' 51 | #the branch somehow becomes the tag value 52 | #github issue: https://github.com/travis-ci/travis-ci/issues/1675 53 | if [ "${TRAVIS_BRANCH}" != "${TRAVIS_TAG}" ]; then 54 | echo "Travis branch does not equal Travis tag, which it should, bailing out." 55 | echo " github issue: https://github.com/travis-ci/travis-ci/issues/1675" 56 | exit 1 57 | else 58 | echo "[Publishing] Branch (${TRAVIS_BRANCH}) same as Tag (${TRAVIS_TAG})" 59 | fi 60 | } 61 | 62 | check_release_tag() { 63 | tag="${TRAVIS_TAG}" 64 | if [[ "$tag" =~ ^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+(\.RC[[:digit:]]+)?$ ]]; then 65 | echo "Build started by version tag $tag. During the release process tags like this" 66 | echo "are created by the 'release' Maven plugin. Nothing to do here." 67 | exit 0 68 | elif [[ ! "$tag" =~ ^release-[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+(\.RC[[:digit:]]+)?$ ]]; then 69 | echo "You must specify a tag of the format 'release-0.0.0' or 'release-0.0.0.RC0' to release this project." 70 | echo "The provided tag ${tag} doesn't match that. Aborting." 71 | exit 1 72 | fi 73 | } 74 | 75 | is_release_commit() { 76 | project_version=$(./mvnw help:evaluate -N -Dexpression=project.version|grep -v '\[') 77 | if [[ "$project_version" =~ ^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+(\.RC[[:digit:]]+)?$ ]]; then 78 | echo "Build started by release commit $project_version. Will synchronize to maven central." 79 | return 0 80 | else 81 | return 1 82 | fi 83 | } 84 | 85 | release_version() { 86 | echo "${TRAVIS_TAG}" | sed 's/^release-//' 87 | } 88 | 89 | safe_checkout_master() { 90 | # We need to be on a branch for release:perform to be able to create commits, 91 | # and we want that branch to be master or release-X.Y, which has been checked before. 92 | # But we also want to make sure that we build and release exactly the tagged version, so we verify that the remote 93 | # branch is where our tag is. 94 | checkoutBranch=release-`release_version | sed 's/.[[:digit:]]\+$//'` 95 | if ! git ls-remote --exit-code --heads origin "$checkoutBranch" ; then 96 | checkoutBranch=master 97 | fi 98 | git checkout -B "${checkoutBranch}" 99 | git fetch origin "${checkoutBranch}":origin/"${checkoutBranch}" 100 | commit_local_master="$(git show --pretty='format:%H' ${checkoutBranch})" 101 | commit_remote_master="$(git show --pretty='format:%H' origin/${checkoutBranch})" 102 | if [ "$commit_local_master" != "$commit_remote_master" ]; then 103 | echo "${checkoutBranch} on remote 'origin' has commits since the version under release, aborting" 104 | exit 1 105 | fi 106 | } 107 | 108 | #---------------------- 109 | # MAIN 110 | #---------------------- 111 | 112 | if ! is_pull_request && build_started_by_tag; then 113 | check_travis_branch_equals_travis_tag 114 | check_release_tag 115 | fi 116 | 117 | ./mvnw clean install -nsu 118 | 119 | # If we are on a pull request, our only job is to run tests, which happened above via ./mvnw install 120 | if is_pull_request; then 121 | true 122 | # If we are on master, we will deploy the latest snapshot or release version 123 | # - If a release commit fails to deploy for a transient reason, delete the broken version from bintray and click rebuild 124 | elif is_travis_branch_master_or_release; then 125 | ./mvnw --batch-mode -s ./.settings.xml -Prelease -nsu -DskipTests deploy 126 | 127 | # If the deployment succeeded, sync it to Maven Central. Note: this needs to be done once per project, not module, hence -N 128 | if is_release_commit; then 129 | ./mvnw --batch-mode -s ./.settings.xml -nsu -N io.zipkin.centralsync-maven-plugin:centralsync-maven-plugin:sync 130 | fi 131 | 132 | # If we are on a release tag, the following will update any version references and push a version tag for deployment. 133 | elif build_started_by_tag; then 134 | safe_checkout_master 135 | ./mvnw --batch-mode -s ./.settings.xml -Prelease -nsu -DreleaseVersion="$(release_version)" -Darguments="-DskipTests" release:prepare 136 | fi 137 | 138 | --------------------------------------------------------------------------------