├── CODEOWNERS ├── .travis.yml ├── src ├── main │ ├── resources │ │ └── META-INF │ │ │ └── services │ │ │ └── io.opentracing.Tracer │ └── java │ │ └── com │ │ └── instana │ │ └── opentracing │ │ ├── InactiveScopeManager.java │ │ ├── TextMapContext.java │ │ ├── ByteBufferContext.java │ │ ├── BaggageItemUtil.java │ │ ├── InstanaNoopSpan.java │ │ ├── InstanaSpan.java │ │ ├── InstanaSpanBuilder.java │ │ └── InstanaTracer.java └── test │ └── java │ └── com │ └── instana │ └── opentracing │ └── InstanaTracerTest.java ├── release.sh ├── .gitignore ├── LICENSE.md ├── README.md └── pom.xml /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @instana/eng-java 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: false 3 | 4 | script: mvn clean verify 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/io.opentracing.Tracer: -------------------------------------------------------------------------------- 1 | com.instana.opentracing.InstanaTracer 2 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euf -o pipefail 3 | 4 | # Import gpg signing key and password 5 | echo "${TEAM_JAVA_SIGN_PASSWORD}" > pass.txt 6 | echo "${TEAM_JAVA_SIGN_KEY}" > sign.key 7 | gpg --batch --passphrase-file pass.txt --import sign.key 8 | 9 | # Release 10 | mvn -P release clean verify deploy 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | .mvn/timing.properties 10 | 11 | *.class 12 | 13 | # Mobile Tools for Java (J2ME) 14 | .mtj.tmp/ 15 | 16 | # Package Files # 17 | *.jar 18 | *.war 19 | *.ear 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | *.iml 25 | .classpath 26 | .project 27 | .idea/ -------------------------------------------------------------------------------- /src/main/java/com/instana/opentracing/InactiveScopeManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) Copyright IBM Corp. 2021 3 | * (c) Copyright Instana Inc. 4 | */ 5 | package com.instana.opentracing; 6 | 7 | import io.opentracing.Scope; 8 | import io.opentracing.ScopeManager; 9 | import io.opentracing.Span; 10 | 11 | class InactiveScopeManager implements ScopeManager { 12 | 13 | @Override 14 | public Span activeSpan() { 15 | return null; 16 | } 17 | 18 | @Override 19 | public Scope activate(Span span) { 20 | throw new UnsupportedOperationException("The Instana tracer was not configured to use a scope manager"); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/instana/opentracing/TextMapContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) Copyright IBM Corp. 2021 3 | * (c) Copyright Instana Inc. 4 | */ 5 | package com.instana.opentracing; 6 | 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | import io.opentracing.SpanContext; 11 | import io.opentracing.propagation.TextMapExtract; 12 | import io.opentracing.propagation.TextMapExtractAdapter; 13 | 14 | class TextMapContext implements SpanContext { 15 | 16 | private final TextMapExtractAdapter baggageItems; 17 | 18 | TextMapContext(TextMapExtract carrier) { 19 | this.baggageItems = BaggageItemUtil.filterItems(carrier); 20 | } 21 | 22 | @Override 23 | public Iterable> baggageItems() { 24 | return baggageItems; 25 | } 26 | 27 | @Override 28 | public String toSpanId() { 29 | return ""; 30 | } 31 | 32 | @Override 33 | public String toTraceId() { 34 | return ""; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/instana/opentracing/ByteBufferContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) Copyright IBM Corp. 2021 3 | * (c) Copyright Instana Inc. 4 | */ 5 | package com.instana.opentracing; 6 | 7 | import java.nio.charset.Charset; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | import io.opentracing.SpanContext; 12 | import io.opentracing.propagation.BinaryExtract; 13 | 14 | class ByteBufferContext implements SpanContext { 15 | 16 | static final Charset CHARSET = Charset.forName("UTF-8"); 17 | 18 | static final byte NO_ENTRY = 0, ENTRY = 1; 19 | 20 | private final Set> baggageItems; 21 | 22 | ByteBufferContext(BinaryExtract carrier) { 23 | this.baggageItems = BaggageItemUtil.filterItems(carrier.extractionBuffer()); 24 | } 25 | 26 | @Override 27 | public Iterable> baggageItems() { 28 | return baggageItems; 29 | } 30 | 31 | @Override 32 | public String toSpanId() { 33 | return ""; 34 | } 35 | 36 | @Override 37 | public String toTraceId() { 38 | return ""; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Copyright IBM Corp. 2021 4 | Copyright (c) 2016 Instana, Inc. https://www.instana.com/ 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/main/java/com/instana/opentracing/BaggageItemUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) Copyright IBM Corp. 2022 3 | * (c) Copyright Instana Inc. 4 | */ 5 | package com.instana.opentracing; 6 | 7 | import io.opentracing.propagation.TextMapExtract; 8 | import io.opentracing.propagation.TextMapExtractAdapter; 9 | 10 | import java.nio.ByteBuffer; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.Set; 14 | 15 | import static com.instana.opentracing.ByteBufferContext.CHARSET; 16 | import static com.instana.opentracing.ByteBufferContext.ENTRY; 17 | 18 | public class BaggageItemUtil { 19 | 20 | static TextMapExtractAdapter filterItems(TextMapExtract textMapExtract) { 21 | Map baggageItems = new HashMap(); 22 | for (Map.Entry entryItem : textMapExtract) { 23 | if (entryItem.getKey() == null) { 24 | continue; 25 | } 26 | if (canAddItem(entryItem.getKey())) { 27 | baggageItems.put(entryItem.getKey(), entryItem.getValue()); 28 | } 29 | } 30 | return new TextMapExtractAdapter(baggageItems); 31 | } 32 | 33 | static Set> filterItems(ByteBuffer byteBuffer) { 34 | Map baggageItems = new HashMap(); 35 | while (byteBuffer.get() == ENTRY) { 36 | byte[] key = new byte[byteBuffer.getInt()], value = new byte[byteBuffer.getInt()]; 37 | byteBuffer.get(key); 38 | byteBuffer.get(value); 39 | final String stringKey = new String(key, CHARSET); 40 | if (canAddItem(stringKey)) { 41 | baggageItems.put(stringKey, new String(value, CHARSET)); 42 | } 43 | } 44 | return baggageItems.entrySet(); 45 | } 46 | 47 | private static boolean canAddItem(String key) { 48 | String lowerCaseKey = key.toLowerCase(); 49 | return "x-instana-t".equals(lowerCaseKey) // 50 | || "x-instana-s".equals(lowerCaseKey) // 51 | || "x-instana-l".equals(lowerCaseKey) // 52 | || "traceparent".equals(lowerCaseKey) // 53 | || "tracestate".equals(lowerCaseKey); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Instana Java OpenTracing  [![Build Status](https://travis-ci.org/instana/instana-java-opentracing.svg?branch=master)](https://travis-ci.org/instana/instana-java-opentracing) 2 | 3 | Instana is capable of collecting traces that are described via the [OpenTracing](http://opentracing.io) API. In order to collect such traces, this tracer implementation must be used. 4 | 5 | The artifact is available on Maven Central. When using Maven you can include the tracer with: 6 | 7 | ``` 8 | 9 | com.instana 10 | instana-java-opentracing 11 | 0.33.0 12 | 13 | ``` 14 | 15 | The older 0.20.7, 0.30.3, 0.31.0 and 0.32.0 are also available. 16 | 17 | The implementation's version number follows the [OpenTracing API version](https://github.com/opentracing/opentracing-java) that it implements. 18 | 19 | 20 | The tracer is fully compliant with the OpenTracing API and is available via: 21 | 22 | ```java 23 | io.opentracing.Tracer tracer = new InstanaTracer(); 24 | ``` 25 | it will try to load a ScopeManager via the Java Service Loader. 26 | Or when explicitly using a specific ScopeManager: 27 | 28 | ```java 29 | io.opentracing.util.ThreadLocalScopeManager scopeManager = new ThreadLocalScopeManager(); 30 | io.opentracing.Tracer tracer = new InstanaTracer(scopeManager); 31 | ``` 32 | 33 | The Instana tracer supports context propagation using all of OpenTracing's built-in formats, i.e. `Format.Builtin#TEXT_MAP`, `Format.Builtin#HTTP_HEADERS` and `Format.Builtin#BINARY`. 34 | 35 | When the Instana monitoring agent is not attached, the Instana OpenTracing API will act as an inactive tracer, similarly to the [OpenTracing noop-tracer](https://github.com/opentracing/opentracing-java/tree/master/opentracing-noop). To activate opentracing you must activate it in the agent configuation: 36 | 37 | ``` 38 | # Java Tracing 39 | com.instana.plugin.javatrace: 40 | instrumentation: 41 | # Lightweight Bytecode Instrumentation, enabled by default 42 | enabled: true 43 | # OpenTracing instrumentation, disabled by default 44 | opentracing: true 45 | ``` 46 | -------------------------------------------------------------------------------- /src/main/java/com/instana/opentracing/InstanaNoopSpan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) Copyright IBM Corp. 2021 3 | * (c) Copyright Instana Inc. 4 | */ 5 | package com.instana.opentracing; 6 | 7 | import java.util.Collections; 8 | import java.util.Map; 9 | 10 | import io.opentracing.Span; 11 | import io.opentracing.SpanContext; 12 | import io.opentracing.tag.Tag; 13 | 14 | class InstanaNoopSpan implements Span, SpanContext { 15 | 16 | static final Span INSTANCE = new InstanaNoopSpan(); 17 | 18 | @Override 19 | public SpanContext context() { 20 | return this; 21 | } 22 | 23 | @Override 24 | public Iterable> baggageItems() { 25 | return Collections. emptyMap().entrySet(); 26 | } 27 | 28 | @Override 29 | public void finish() { 30 | } 31 | 32 | @Override 33 | public void finish(long finishMicros) { 34 | } 35 | 36 | @Override 37 | public Span setTag(String key, String value) { 38 | return this; 39 | } 40 | 41 | @Override 42 | public Span setTag(String key, boolean value) { 43 | return this; 44 | } 45 | 46 | @Override 47 | public Span setTag(String key, Number value) { 48 | return this; 49 | } 50 | 51 | @Override 52 | public Span setTag(Tag tag, T value) { 53 | return this; 54 | } 55 | 56 | @Override 57 | public Span log(Map fields) { 58 | return this; 59 | } 60 | 61 | @Override 62 | public Span log(long timestampMicroseconds, Map fields) { 63 | return this; 64 | } 65 | 66 | @Override 67 | public Span log(String event) { 68 | return this; 69 | } 70 | 71 | @Override 72 | public Span log(long timestampMicroseconds, String event) { 73 | return this; 74 | } 75 | 76 | @Override 77 | public Span setBaggageItem(String key, String value) { 78 | return this; 79 | } 80 | 81 | @Override 82 | public String getBaggageItem(String key) { 83 | return null; 84 | } 85 | 86 | @Override 87 | public Span setOperationName(String operationName) { 88 | return this; 89 | } 90 | 91 | @Override 92 | public String toSpanId() { 93 | return ""; 94 | } 95 | 96 | @Override 97 | public String toTraceId() { 98 | return ""; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/instana/opentracing/InstanaSpan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) Copyright IBM Corp. 2021 3 | * (c) Copyright Instana Inc. 4 | */ 5 | package com.instana.opentracing; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import io.opentracing.Span; 11 | import io.opentracing.SpanContext; 12 | import io.opentracing.tag.Tag; 13 | 14 | public class InstanaSpan implements Span, SpanContext { 15 | 16 | private static final long NO_TIME = 0L; 17 | 18 | @SuppressWarnings("unused") 19 | private final Object dispatcher; 20 | 21 | private final Map baggageItems; 22 | 23 | InstanaSpan(Object dispatcher, Iterable> baggageItems) { 24 | this.dispatcher = dispatcher; 25 | this.baggageItems = new HashMap(); 26 | if (baggageItems != null) { 27 | for (Map.Entry baggageItem : baggageItems) { 28 | this.baggageItems.put(baggageItem.getKey(), baggageItem.getValue()); 29 | } 30 | } 31 | } 32 | 33 | @SuppressWarnings("unused") 34 | InstanaSpan considerStart(long time) { 35 | if (time == NO_TIME) { 36 | return this; 37 | } else { 38 | return start(time); 39 | } 40 | } 41 | 42 | @SuppressWarnings("unused") 43 | private InstanaSpan start(long time) { 44 | return this; 45 | } 46 | 47 | @Override 48 | public SpanContext context() { 49 | return this; 50 | } 51 | 52 | @Override 53 | public Iterable> baggageItems() { 54 | return baggageItems.entrySet(); 55 | } 56 | 57 | @Override 58 | public void finish() { 59 | } 60 | 61 | @Override 62 | public void finish(long finishMicros) { 63 | } 64 | 65 | @Override 66 | public Span setTag(String key, String value) { 67 | return this; 68 | } 69 | 70 | @Override 71 | public Span setTag(String key, boolean value) { 72 | return this; 73 | } 74 | 75 | @Override 76 | public Span setTag(String key, Number value) { 77 | return this; 78 | } 79 | 80 | @Override 81 | public Span setTag(Tag key, T value) { 82 | if (key != null && value != null) { 83 | setTag(key.getKey(), value.toString()); 84 | } 85 | return this; 86 | } 87 | 88 | @Override 89 | public Span log(Map fields) { 90 | return log(System.currentTimeMillis(), fields); 91 | } 92 | 93 | @Override 94 | public Span log(long timestampMicroseconds, Map fields) { 95 | for (Map.Entry entry : fields.entrySet()) { 96 | log(timestampMicroseconds, entry.getKey(), entry.getValue()); 97 | } 98 | return this; 99 | } 100 | 101 | @Override 102 | public Span log(String event) { 103 | return log(System.currentTimeMillis(), event, event); 104 | } 105 | 106 | @Override 107 | public Span log(long timestampMicroseconds, String event) { 108 | return log(timestampMicroseconds, event, event); 109 | } 110 | 111 | @Override 112 | public Span setBaggageItem(String key, String value) { 113 | baggageItems.put(key, value); 114 | return this; 115 | } 116 | 117 | @Override 118 | public String getBaggageItem(String key) { 119 | return baggageItems.get(key); 120 | } 121 | 122 | @Override 123 | public Span setOperationName(String operationName) { 124 | return this; 125 | } 126 | 127 | private Span log(long timestampMicroseconds, String eventName, Object payload) { 128 | if (payload == null) { 129 | return this; 130 | } 131 | return setTag("log." + timestampMicroseconds + "." + eventName, payload.toString()); 132 | } 133 | 134 | @Override 135 | public String toSpanId() { 136 | return ""; 137 | } 138 | 139 | @Override 140 | public String toTraceId() { 141 | return ""; 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/instana/opentracing/InstanaSpanBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) Copyright IBM Corp. 2021 3 | * (c) Copyright Instana Inc. 4 | */ 5 | package com.instana.opentracing; 6 | 7 | import java.util.Collections; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | import io.opentracing.References; 12 | import io.opentracing.ScopeManager; 13 | import io.opentracing.Span; 14 | import io.opentracing.SpanContext; 15 | import io.opentracing.Tracer; 16 | import io.opentracing.Tracer.SpanBuilder; 17 | import io.opentracing.tag.Tag; 18 | 19 | public class InstanaSpanBuilder implements Tracer.SpanBuilder { 20 | 21 | private final ScopeManager scopeManager; 22 | 23 | private final String operationName; 24 | 25 | private final Map tags; 26 | 27 | private boolean ignoreActiveSpan; 28 | 29 | private SpanContext parentContext; 30 | 31 | private long startTime; 32 | 33 | InstanaSpanBuilder(ScopeManager scopeManager, String operationName) { 34 | this.scopeManager = scopeManager; 35 | this.operationName = operationName; 36 | tags = new HashMap(); 37 | } 38 | 39 | @Override 40 | public Tracer.SpanBuilder asChildOf(SpanContext parent) { 41 | if (parent != null) { 42 | parentContext = parent; 43 | } 44 | return this; 45 | } 46 | 47 | @Override 48 | public Tracer.SpanBuilder asChildOf(Span parent) { 49 | if (parent != null) { 50 | parentContext = parent.context(); 51 | } 52 | return this; 53 | } 54 | 55 | @Override 56 | public Tracer.SpanBuilder ignoreActiveSpan() { 57 | ignoreActiveSpan = true; 58 | return this; 59 | } 60 | 61 | @Override 62 | public Tracer.SpanBuilder addReference(String referenceType, SpanContext referencedContext) { 63 | if (References.CHILD_OF.equals(referenceType)) { 64 | return asChildOf(referencedContext); 65 | } else if (References.FOLLOWS_FROM.equals(referenceType)) { 66 | // TODO: Currently the FOLLOWS_FROM semantic is not fully supported, but its better to have CHILD_OF than nothing 67 | return asChildOf(referencedContext); 68 | } else { 69 | return this; 70 | } 71 | } 72 | 73 | @Override 74 | public Tracer.SpanBuilder withTag(String key, String value) { 75 | if (key != null && value != null) { 76 | tags.put(key, value); 77 | } 78 | return this; 79 | } 80 | 81 | @Override 82 | public Tracer.SpanBuilder withTag(String key, boolean value) { 83 | if (key != null) { 84 | tags.put(key, Boolean.toString(value)); 85 | } 86 | return this; 87 | } 88 | 89 | @Override 90 | public Tracer.SpanBuilder withTag(String key, Number value) { 91 | if (key != null && value != null) { 92 | tags.put(key, value.toString()); 93 | } 94 | return this; 95 | } 96 | 97 | @Override 98 | public SpanBuilder withTag(Tag key, T value) { 99 | if (key != null && value != null) { 100 | tags.put(key.getKey(), value.toString()); 101 | } 102 | return this; 103 | } 104 | 105 | @Override 106 | public Tracer.SpanBuilder withStartTimestamp(long microseconds) { 107 | startTime = microseconds; 108 | return this; 109 | } 110 | 111 | @Override 112 | public Span start() { 113 | return InstanaNoopSpan.INSTANCE; 114 | } 115 | 116 | @SuppressWarnings("unused") 117 | public Span doStart(Object dispatcher) { 118 | Span span = new InstanaSpan(dispatcher, baggageItems()).considerStart(startTime).setOperationName(operationName); 119 | for (Map.Entry tag : tags.entrySet()) { 120 | span.setTag(tag.getKey(), tag.getValue()); 121 | } 122 | return span; 123 | } 124 | 125 | private Iterable> baggageItems() { 126 | if (parentContext != null) { // prefer explicit parent 127 | return parentContext.baggageItems(); 128 | } else if (!ignoreActiveSpan) { 129 | Span span = scopeManager.activeSpan(); 130 | if (span != null) { 131 | return span.context().baggageItems(); 132 | } 133 | } 134 | return Collections. emptyMap().entrySet(); 135 | } 136 | } -------------------------------------------------------------------------------- /src/main/java/com/instana/opentracing/InstanaTracer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) Copyright IBM Corp. 2021 3 | * (c) Copyright Instana Inc. 4 | */ 5 | package com.instana.opentracing; 6 | 7 | import java.nio.ByteBuffer; 8 | import java.util.ArrayList; 9 | import java.util.Iterator; 10 | import java.util.Map; 11 | import java.util.ServiceLoader; 12 | 13 | import io.opentracing.Scope; 14 | import io.opentracing.ScopeManager; 15 | import io.opentracing.Span; 16 | import io.opentracing.SpanContext; 17 | import io.opentracing.Tracer; 18 | import io.opentracing.propagation.BinaryExtract; 19 | import io.opentracing.propagation.BinaryInject; 20 | import io.opentracing.propagation.Format; 21 | import io.opentracing.propagation.TextMapExtract; 22 | import io.opentracing.propagation.TextMapInject; 23 | 24 | /** 25 | * A tracer that becomes active when using Instana OpenTracing. 26 | */ 27 | public class InstanaTracer implements Tracer { 28 | 29 | private final ScopeManager scopeManager; 30 | 31 | /** 32 | * Creates a new Instana tracer with an implicit {@link ScopeManager} that is registered by the Java 33 | * {@link ServiceLoader}. If no scope manager is registered, this tracer will not offer support for active spans. To 34 | * set a scope manager explicitly, use {@link InstanaTracer#InstanaTracer(ScopeManager)}. 35 | */ 36 | public InstanaTracer() { 37 | ScopeManager scopeManager = null; 38 | try { 39 | Iterator it = ServiceLoader.load(ScopeManager.class).iterator(); 40 | if (it.hasNext()) { 41 | scopeManager = it.next(); 42 | } 43 | } catch (Exception ignored) { 44 | } 45 | if (scopeManager == null) { 46 | this.scopeManager = new InactiveScopeManager(); 47 | } else { 48 | this.scopeManager = scopeManager; 49 | } 50 | } 51 | 52 | /** 53 | * Creates a new Instana tracer. 54 | * 55 | * @param scopeManager 56 | * The active span source to use. 57 | */ 58 | public InstanaTracer(ScopeManager scopeManager) { 59 | this.scopeManager = scopeManager; 60 | } 61 | 62 | @Override 63 | public SpanBuilder buildSpan(String operationName) { 64 | return new InstanaSpanBuilder(scopeManager, operationName); 65 | } 66 | 67 | @Override 68 | public ScopeManager scopeManager() { 69 | return scopeManager; 70 | } 71 | 72 | @Override 73 | public Span activeSpan() { 74 | return scopeManager.activeSpan(); 75 | } 76 | 77 | @Override 78 | public Scope activateSpan(Span span) { 79 | return scopeManager.activate(span); 80 | } 81 | 82 | @Override 83 | public void inject(SpanContext spanContext, Format format, C carrier) { 84 | if (format.equals(Format.Builtin.TEXT_MAP) || format.equals(Format.Builtin.TEXT_MAP_INJECT) 85 | || format.equals(Format.Builtin.HTTP_HEADERS)) { 86 | if (!(carrier instanceof TextMapInject)) { 87 | throw new IllegalArgumentException("Expected text map carrier: " + carrier); 88 | } 89 | for (Map.Entry entry : spanContext.baggageItems()) { 90 | ((TextMapInject) carrier).put(entry.getKey(), entry.getValue()); 91 | } 92 | } else if (format.equals(Format.Builtin.BINARY) || format.equals(Format.Builtin.BINARY_INJECT)) { 93 | if (!(carrier instanceof BinaryInject)) { 94 | throw new IllegalArgumentException("Expected a byte buffer carrier: " + carrier); 95 | } 96 | int requiredSize = 1; // we end with a NO_ENTRY marker 97 | ArrayList binary = new ArrayList(); 98 | for (Map.Entry entry : spanContext.baggageItems()) { 99 | requiredSize += 1 + 4 + 4; // ENTRY marker + size of key and size of value 100 | byte[] key = entry.getKey().getBytes(ByteBufferContext.CHARSET); 101 | byte[] value = entry.getValue().getBytes(ByteBufferContext.CHARSET); 102 | requiredSize += key.length + value.length; 103 | binary.add(key); 104 | binary.add(value); 105 | } 106 | ByteBuffer injectionBuffer = ((BinaryInject) carrier).injectionBuffer(requiredSize); 107 | Iterator iterator = binary.iterator(); 108 | while (iterator.hasNext()) { 109 | byte[] key = iterator.next(); 110 | byte[] value = iterator.next(); 111 | injectionBuffer.put(ByteBufferContext.ENTRY); // 1 byte 112 | injectionBuffer.putInt(key.length); // 4 bytes 113 | injectionBuffer.putInt(value.length); // 4 bytes 114 | injectionBuffer.put(key); // key.length 115 | injectionBuffer.put(value); // value.length 116 | 117 | } 118 | injectionBuffer.put(ByteBufferContext.NO_ENTRY); 119 | } else { 120 | throw new IllegalArgumentException("Unsupported format: " + format); 121 | } 122 | } 123 | 124 | @Override 125 | public SpanContext extract(Format format, C carrier) { 126 | SpanContext spanContext = extractContext(format, carrier); 127 | if ("".equals(spanContext.toTraceId())) { 128 | return null; 129 | } 130 | return spanContext; 131 | } 132 | 133 | SpanContext extractContext(Format format, C carrier) { 134 | SpanContext spanContext; 135 | if (format.equals(Format.Builtin.TEXT_MAP) || format.equals(Format.Builtin.TEXT_MAP_EXTRACT) 136 | || format.equals(Format.Builtin.HTTP_HEADERS)) { 137 | if (!(carrier instanceof TextMapExtract)) { 138 | throw new IllegalArgumentException("Unsupported payload: " + carrier); 139 | } 140 | spanContext = new TextMapContext((TextMapExtract) carrier); 141 | } else if (format.equals(Format.Builtin.BINARY) || format.equals(Format.Builtin.BINARY_EXTRACT)) { 142 | if (!(carrier instanceof BinaryExtract)) { 143 | throw new IllegalArgumentException("Unsupported payload: " + carrier); 144 | } 145 | spanContext = new ByteBufferContext((BinaryExtract) carrier); 146 | } else { 147 | throw new IllegalArgumentException("Unsupported format: " + format); 148 | } 149 | return spanContext; 150 | } 151 | 152 | @Override 153 | public void close() { 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.instana 6 | instana-java-opentracing 7 | 0.33.3-SNAPSHOT 8 | 9 | Instana - Java Open Tracing 10 | Implementation of the opentracing.io API that is monitored by Instana. 11 | https://github.com/instana/instana-java-opentracing 12 | 13 | 14 | Instana Inc. 15 | http://www.instana.com 16 | 17 | 18 | 19 | Fabian Lange 20 | fabian.lange@instana.com 21 | http://www.instana.com 22 | 23 | 24 | Mary Reni Rajaian 25 | mary.reni.rajaian@ibm.com 26 | https://www.ibm.com/docs/en/instana-observability/current 27 | 28 | 29 | 30 | 31 | MIT License 32 | https://github.com/instana/instana-java-opentracing/blob/master/LICENSE.md 33 | 34 | 35 | 36 | 37 | scm:git:git://github.com/instana/instana-java-opentracing.git 38 | scm:git:ssh://git@github.com/instana/instana-java-opentracing.git 39 | https://github.com/instana/instana-java-opentracing 40 | instana-java-opentracing-0.33.2 41 | 42 | 43 | 44 | 45 | io.opentracing 46 | opentracing-api 47 | 0.33.0 48 | 49 | 50 | junit 51 | junit 52 | 4.13.2 53 | test 54 | 55 | 56 | org.hamcrest 57 | hamcrest-library 58 | 1.3 59 | test 60 | 61 | 62 | 63 | 64 | 65 | 66 | org.codehaus.mojo 67 | animal-sniffer-maven-plugin 68 | 1.15 69 | 70 | 71 | check-java-api 72 | test 73 | 74 | check 75 | 76 | 77 | 78 | org.codehaus.mojo.signature 79 | java16 80 | 1.1 81 | 82 | 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-compiler-plugin 89 | 3.8.1 90 | 91 | 1.6 92 | 1.6 93 | 94 | 95 | 96 | org.apache.maven.plugins 97 | maven-source-plugin 98 | 3.1.0 99 | 100 | 101 | attach-sources 102 | 103 | jar 104 | 105 | 106 | 107 | 108 | 109 | org.apache.maven.plugins 110 | maven-javadoc-plugin 111 | 3.1.0 112 | 113 | 114 | attach-javadocs 115 | 116 | jar 117 | 118 | 119 | 120 | 121 | 8 122 | 123 | 124 | 125 | org.apache.maven.plugins 126 | maven-release-plugin 127 | 2.5.3 128 | 129 | 130 | 131 | 132 | 133 | 134 | sonatype-nexus-snapshots 135 | Sonatype Nexus Snapshots 136 | https://oss.sonatype.org/content/repositories/snapshots 137 | 138 | 139 | sonatype-nexus-staging 140 | Nexus Release Repository 141 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 142 | 143 | 144 | 145 | 146 | 147 | release 148 | 149 | 150 | performRelease 151 | true 152 | 153 | 154 | 155 | 156 | 157 | org.sonatype.plugins 158 | nexus-staging-maven-plugin 159 | 1.6.8 160 | true 161 | 162 | sonatype-nexus-staging 163 | https://oss.sonatype.org/ 164 | true 165 | 166 | 167 | 168 | org.apache.maven.plugins 169 | maven-gpg-plugin 170 | 1.6 171 | 172 | 173 | sign-artifacts 174 | verify 175 | 176 | sign 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /src/test/java/com/instana/opentracing/InstanaTracerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) Copyright IBM Corp. 2022 3 | * (c) Copyright Instana Inc. 4 | */ 5 | package com.instana.opentracing; 6 | 7 | import io.opentracing.SpanContext; 8 | import io.opentracing.Tracer; 9 | import io.opentracing.propagation.BinaryAdapters; 10 | import io.opentracing.propagation.Format; 11 | import io.opentracing.propagation.TextMap; 12 | import org.hamcrest.Matcher; 13 | import org.junit.Test; 14 | 15 | import java.nio.ByteBuffer; 16 | import java.util.HashMap; 17 | import java.util.Iterator; 18 | import java.util.Map; 19 | import java.util.ServiceLoader; 20 | 21 | import static org.hamcrest.CoreMatchers.allOf; 22 | import static org.hamcrest.CoreMatchers.instanceOf; 23 | import static org.hamcrest.MatcherAssert.assertThat; 24 | import static org.hamcrest.Matchers.*; 25 | import static org.hamcrest.core.Is.is; 26 | 27 | public class InstanaTracerTest { 28 | 29 | private final InstanaTracer tracer = new InstanaTracer(); 30 | 31 | @Test public void testTextMapExtractionIgnoresIrrelevantHeaders() { 32 | testExtractionIgnoresIrrelevantHeaders(Format.Builtin.TEXT_MAP); 33 | } 34 | 35 | @Test public void testHttpHeadersExtractionIgnoresIrrelevantHeaders() { 36 | testExtractionIgnoresIrrelevantHeaders(Format.Builtin.HTTP_HEADERS); 37 | } 38 | 39 | @Test public void testTextMapExtractionKeepsInstanaHeaders() { 40 | testExtractionKeepsInstanaHeaders(Format.Builtin.TEXT_MAP); 41 | } 42 | 43 | @Test public void testHttpHeadersExtractionKeepsInstanaHeaders() { 44 | testExtractionKeepsInstanaHeaders(Format.Builtin.HTTP_HEADERS); 45 | } 46 | 47 | @Test public void testTextMapInjection() { 48 | testInjection(Format.Builtin.TEXT_MAP); 49 | } 50 | 51 | @Test public void testHttpHeadersInjection() { 52 | testInjection(Format.Builtin.HTTP_HEADERS); 53 | } 54 | 55 | private void testExtractionIgnoresIrrelevantHeaders(Format format) { 56 | MapTextMap textMap = new MapTextMap(); 57 | textMap.put("foo", "bar"); // irrelevant custom header 58 | SpanContext spanContext = tracer.extractContext(format, textMap); 59 | assertThat(spanContext.baggageItems(), emptyIterable()); 60 | } 61 | 62 | private void testExtractionKeepsInstanaHeaders(Format format) { 63 | MapTextMap textMap = new MapTextMap(); 64 | textMap.put("foo", "bar"); // irrelevant header 65 | textMap.put("x-instana-t", "123"); 66 | textMap.put("x-instana-s", "456"); 67 | textMap.put("some", "thing"); // irrelevant header 68 | textMap.put("x-instana-l", "789"); 69 | textMap.put("authorization", "bearer 000"); // irrelevant header 70 | textMap.put("traceparent", "abc"); 71 | textMap.put("tracestate", "xyz"); 72 | SpanContext spanContext = tracer.extractContext(format, textMap); 73 | assertThat(spanContext.baggageItems(), 74 | containsInAnyOrder(isEntry("x-instana-t", "123"), isEntry("x-instana-s", "456"), isEntry("x-instana-l", "789"), 75 | isEntry("traceparent", "abc"), isEntry("tracestate", "xyz"))); 76 | } 77 | 78 | private void testExtractionIsCaseInsensitive(Format format) { 79 | MapTextMap textMap = new MapTextMap(); 80 | textMap.put("X-INSTANA-T", "123"); 81 | textMap.put("x-instana-s", "456"); 82 | textMap.put("X-Instana-L", "789"); 83 | SpanContext spanContext = tracer.extractContext(format, textMap); 84 | assertThat(spanContext.baggageItems(), 85 | containsInAnyOrder(isEntry("x-instana-t", "123"), isEntry("x-instana-s", "456"), 86 | isEntry("x-instana-l", "789"))); 87 | } 88 | 89 | private void testInjection(Format format) { 90 | MapTextMap textMap = new MapTextMap(); 91 | MapSpanContext spanContext = new MapSpanContext(); 92 | spanContext.map.put("foo", "bar"); 93 | tracer.inject(spanContext, format, textMap); 94 | assertThat(textMap.map.size(), is(1)); 95 | assertThat(textMap.map.get("foo"), is("bar")); 96 | } 97 | 98 | @Test public void testByteBufferExtractionKeepsInstanaHeaders() { 99 | Map headers = new HashMap(); 100 | headers.put("foo", "bar"); // irrelevant header 101 | headers.put("x-instana-t", "123"); 102 | headers.put("x-instana-s", "456"); 103 | headers.put("some", "thing"); // irrelevant header 104 | headers.put("x-instana-l", "789"); 105 | headers.put("authorization", "bearer 000"); // irrelevant header 106 | headers.put("traceparent", "abc"); 107 | headers.put("tracestate", "xyz"); 108 | ByteBuffer byteBuffer = encodeToByteBuffer(headers); 109 | SpanContext spanContext = tracer.extractContext(Format.Builtin.BINARY_EXTRACT, 110 | BinaryAdapters.extractionCarrier(byteBuffer)); 111 | assertThat(spanContext.baggageItems(), 112 | containsInAnyOrder(isEntry("x-instana-t", "123"), isEntry("x-instana-s", "456"), isEntry("x-instana-l", "789"), 113 | isEntry("traceparent", "abc"), isEntry("tracestate", "xyz"))); 114 | } 115 | 116 | private ByteBuffer encodeToByteBuffer(Map map) { 117 | int totalSize = byteBufferSize(map); 118 | ByteBuffer byteBuffer = ByteBuffer.allocate(totalSize); 119 | for (Map.Entry entry : map.entrySet()) { 120 | byteBuffer.put(ByteBufferContext.ENTRY); 121 | byte[] key = entry.getKey().getBytes(ByteBufferContext.CHARSET); 122 | byte[] value = entry.getValue().getBytes(ByteBufferContext.CHARSET); 123 | byteBuffer.putInt(key.length); 124 | byteBuffer.putInt(value.length); 125 | byteBuffer.put(key); 126 | byteBuffer.put(value); 127 | } 128 | byteBuffer.put(ByteBufferContext.NO_ENTRY); 129 | byteBuffer.flip(); 130 | return byteBuffer; 131 | } 132 | 133 | private int byteBufferSize(Map map) { 134 | int totalSize = 0; 135 | for (Map.Entry entry : map.entrySet()) { 136 | // ENTRY 137 | totalSize += 1; 138 | // key length int = 4 bytes 139 | totalSize += 4; 140 | // value length int = 4 bytes 141 | totalSize += 4; 142 | // key payload bytes 143 | totalSize += entry.getKey().getBytes(ByteBufferContext.CHARSET).length; 144 | // value payload bytes 145 | totalSize += entry.getValue().getBytes(ByteBufferContext.CHARSET).length; 146 | } 147 | // NO_ENTRY 148 | totalSize += 1; 149 | return totalSize; 150 | } 151 | 152 | @Test public void testByteBufferInjection() { 153 | MapSpanContext spanContext = new MapSpanContext(); 154 | spanContext.map.put("foo", "quxbaz"); 155 | byte[] key = "foo".getBytes(ByteBufferContext.CHARSET), value = "quxbaz".getBytes(ByteBufferContext.CHARSET); 156 | ByteBuffer byteBuffer = ByteBuffer.allocate(2 + 2 * 4 + key.length + value.length); 157 | tracer.inject(spanContext, Format.Builtin.BINARY_INJECT, BinaryAdapters.injectionCarrier(byteBuffer)); 158 | byteBuffer.flip(); 159 | assertThat(byteBuffer.get(), is((byte) 1)); 160 | assertThat(byteBuffer.getInt(), is(key.length)); 161 | assertThat(byteBuffer.getInt(), is(value.length)); 162 | byte[] readKey = new byte[key.length], readValue = new byte[value.length]; 163 | byteBuffer.get(readKey); 164 | byteBuffer.get(readValue); 165 | assertThat(readKey, is(key)); 166 | assertThat(readValue, is(value)); 167 | assertThat(byteBuffer.get(), is((byte) 0)); 168 | } 169 | 170 | @Test public void testServiceLoader() { 171 | Iterator services = ServiceLoader.load(Tracer.class).iterator(); 172 | assertThat(services.hasNext(), is(true)); 173 | assertThat(services.next(), instanceOf(InstanaTracer.class)); 174 | assertThat(services.hasNext(), is(false)); 175 | } 176 | 177 | private static class MapSpanContext implements SpanContext { 178 | 179 | final Map map = new HashMap(); 180 | 181 | @Override public Iterable> baggageItems() { 182 | return map.entrySet(); 183 | } 184 | 185 | @Override public String toSpanId() { 186 | return ""; 187 | } 188 | 189 | @Override public String toTraceId() { 190 | return ""; 191 | } 192 | } 193 | 194 | private static class MapTextMap implements TextMap { 195 | 196 | final Map map = new HashMap(); 197 | 198 | @Override public Iterator> iterator() { 199 | return map.entrySet().iterator(); 200 | } 201 | 202 | @Override public void put(String key, String value) { 203 | map.put(key, value); 204 | } 205 | } 206 | 207 | public static Matcher> isEntry(String key, String value) { 208 | return allOf(hasProperty("key", is(key)), hasProperty("value", is(value))); 209 | } 210 | } 211 | --------------------------------------------------------------------------------