Skip to content

Commit 6ecd7b8

Browse files
Merge pull request #25 from ExpediaDotCom/support-single-dual-span-modes
adding single(sharable) and dual-span(non-sharable) support, default …
2 parents 0f4307d + 8ddb3df commit 6ecd7b8

File tree

9 files changed

+227
-50
lines changed

9 files changed

+227
-50
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,4 @@ src/proto_idl_codegen/
6565
dist/
6666
.idea/typescript-compiler.xml
6767
*.iml
68+
ws2/

examples/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ const config = {
4545
// or
4646

4747
// type: 'haystack_agent'
48+
49+
// or
50+
// type: 'http_collector'
4851
},
4952
logger: logger
5053
};

integration-tests/docker-compose.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ services:
1414
- /bin/sh
1515
- -c
1616
- 'sleep 10 && java -jar /app/bin/haystack-agent.jar --config-provider file --file-path /app/bin/dev.conf'
17+
haystack_collector:
18+
image: expediadotcom/haystack-http-span-collector:1.1
19+
depends_on:
20+
- zookeeper
21+
- kafkasvc
22+
environment:
23+
HAYSTACK_PROP_KAFKA_PRODUCER_PROPS_BOOTSTRAP_SERVERS: "kafkasvc:9092"
24+
ports:
25+
- "8080:8080"
1726
zookeeper:
1827
image: wurstmeister/zookeeper
1928
ports:

src/dispatchers/http_collector.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ export default class HttpCollectorDispatcher implements Dispatcher {
2727
_headers: { [key: string]: any };
2828
_logger: Logger;
2929

30-
constructor(_collectorUrl: string = 'http://localhost:8080/span', headers: { [key: string]: any } = {}, logger: Logger = new NullLogger()) {
31-
this._collectorUrl = _collectorUrl;
32-
this._headers = headers;
30+
constructor(collectorUrl: string = 'http://haystack-collector:8080/span', headers: { [key: string]: any } = {}, logger: Logger = new NullLogger()) {
31+
this._collectorUrl = collectorUrl;
32+
this._headers = headers || {};
3333
this._logger = logger;
34-
this._logger.info(`Initializing the http collector dispatcher, connecting at ${_collectorUrl}`);
34+
this._logger.info(`Initializing the http collector dispatcher, connecting at ${this._collectorUrl}`);
3535
}
3636

3737
name(): string {

src/span_context.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,20 @@ export default class SpanContext extends opentracing.SpanContext {
2222
spanId: string;
2323
parentSpanId: string;
2424
baggage: any;
25+
extractedContext: boolean;
2526

2627
constructor(
2728
traceId: string,
2829
spanId: string,
2930
parentSpanId: string,
30-
baggage = {}) {
31+
baggage = {},
32+
extractedContext: boolean = false) {
3133
super();
3234
this.traceId = traceId;
3335
this.spanId = spanId;
3436
this.parentSpanId = parentSpanId;
3537
this.baggage = baggage;
38+
this.extractedContext = extractedContext;
3639
}
3740

3841
setTraceId(traceId: string): void {
@@ -56,7 +59,15 @@ export default class SpanContext extends opentracing.SpanContext {
5659
newBaggage);
5760
}
5861

62+
setExtractedContext(): void {
63+
this.extractedContext = true;
64+
}
65+
5966
isValid(): boolean {
6067
return !!(this.traceId && this.spanId);
6168
}
69+
70+
isExtractedContext(): boolean {
71+
return this.extractedContext;
72+
}
6273
}

src/start_span_fields.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,4 @@ export default class StartSpanFields implements opentracing.SpanOptions {
2323
references?: opentracing.Reference[];
2424
tags?: any;
2525
startTime?: number;
26-
callerSpanContext?: SpanContext;
2726
}

src/tracer.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,14 @@ export default class Tracer extends opentracing.Tracer {
3838
_commonTags: { [key: string]: any };
3939
_logger: any;
4040
_registry: PropagationRegistry;
41+
_useDualSpanMode: boolean;
4142

4243
constructor(serviceName: string,
4344
dispatcher: Dispatcher = new NoopDispatcher(),
4445
commonTags: { [key: string]: any } = {},
4546
logger: Logger = new NullLogger(),
46-
idGenerator: Generator = new UUIDGenerator()) {
47+
idGenerator: Generator = new UUIDGenerator(),
48+
useDualSpanMode: boolean = false) {
4749
super();
4850
this._commonTags = commonTags || {};
4951
this._serviceName = serviceName;
@@ -54,6 +56,7 @@ export default class Tracer extends opentracing.Tracer {
5456
this._registry.register(opentracing.FORMAT_BINARY, new BinaryPropagator());
5557
this._registry.register(opentracing.FORMAT_HTTP_HEADERS, new TextMapPropagator(new URLCodex()));
5658
this._idGenerator = idGenerator;
59+
this._useDualSpanMode = useDualSpanMode;
5760
}
5861

5962
startSpan(operationName: string, fields?: StartSpanFields): Span {
@@ -87,7 +90,7 @@ export default class Tracer extends opentracing.Tracer {
8790
}
8891
}
8992

90-
const ctx = this._createSpanContext(parent, fields.callerSpanContext);
93+
const ctx = this._createSpanContext(parent, spanTags);
9194
return this._spanStart(operationName, ctx, startTime, references, spanTags);
9295
}
9396

@@ -102,16 +105,29 @@ export default class Tracer extends opentracing.Tracer {
102105
return span;
103106
}
104107

105-
_createSpanContext(parent: SpanContext, callerContext: SpanContext): SpanContext {
108+
private isServerSpan(spanTags: { [key: string]: any }): boolean {
109+
const spanKind = spanTags[opentracing.Tags.SPAN_KIND];
110+
return spanKind && (spanKind === 'server');
111+
}
112+
113+
// This is a check to see if the tracer is configured to support single
114+
// single span type (Zipkin style shared span id) or
115+
// dual span type (client and server having their own span ids ).
116+
// a. If tracer is not of dualSpanType and if it is a server span then we
117+
// just return the parent context with the same shared span ids
118+
// b. If tracer is not of dualSpanType and if the parent context is an extracted one from the wire
119+
// then we assume this is the first span in the server and so just return the parent context
120+
// with the same shared span ids
121+
private _createSpanContext(parent: SpanContext, spanTags: { [key: string]: any }): SpanContext {
106122
if (!parent || !parent.isValid) {
107-
if (callerContext) {
108-
return new SpanContext(callerContext.traceId, callerContext.spanId, callerContext.parentSpanId, callerContext.baggage);
123+
const parentBaggage = parent && parent.baggage;
124+
return new SpanContext(this._idGenerator.generate(), this._idGenerator.generate(), '', parentBaggage);
125+
} else {
126+
if (!this._useDualSpanMode && (this.isServerSpan(spanTags) || parent.isExtractedContext())) {
127+
return new SpanContext(parent.traceId, parent.spanId, parent.parentSpanId, parent.baggage);
109128
} else {
110-
const parentBaggage = parent && parent.baggage;
111-
return new SpanContext(this._idGenerator.generate(), this._idGenerator.generate(), parentBaggage);
129+
return new SpanContext(parent.traceId, this._idGenerator.generate(), parent.spanId, parent.baggage);
112130
}
113-
} else {
114-
return new SpanContext(parent.traceId, this._idGenerator.generate(), parent.spanId, parent.baggage);
115131
}
116132
}
117133

@@ -165,7 +181,11 @@ export default class Tracer extends opentracing.Tracer {
165181
throw new Error('extractor is not supported for format=' + format);
166182
}
167183

168-
return propagator.extract(carrier);
184+
const ctx = propagator.extract(carrier);
185+
if (ctx) {
186+
ctx.setExtractedContext();
187+
}
188+
return ctx;
169189
}
170190

171191
static initTracer(config: TracerConfig): opentracing.Tracer {

tests/integration/agent-integration.spec.ts

Lines changed: 72 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616

1717
import Tracer from '../../src/tracer';
1818
import * as opentracing from 'opentracing';
19-
import SpanContext from '../../src/span_context';
2019
import * as kafka from 'kafka-node';
2120
import { expect } from 'chai';
2221
import { Logger } from '../../src/logger'
22+
const messages = require('../../src/proto_idl_codegen/span_pb');
2323

2424
class ConsoleLogger implements Logger {
2525
log(msg) { console.log(msg); }
@@ -29,28 +29,24 @@ class ConsoleLogger implements Logger {
2929
error(msg: string): void { this.log(msg); }
3030
}
3131

32-
describe('Haystack Integration Tests', () => {
33-
describe('Tracer Test', () => {
34-
return it("should generate spans and push to haystack-agent", function(done) {
35-
this.timeout(6000);
36-
const TraceId = '1848fadd-fa16-4b3e-8ad1-6d73339bbee7'
37-
const tracer = Tracer.initTracer({
38-
serviceName: 'my-service',
39-
commonTags: {
40-
'my-service-version': '0.1.0'
41-
},
42-
dispatcher: {
43-
type: 'haystack_agent',
44-
agentHost: 'haystack_agent'
45-
},
46-
logger: new ConsoleLogger(),
47-
});
32+
const TraceId = '1848fadd-fa16-4b3e-8ad1-6d73339bbee7';
33+
const SpanId = '7a7cc5bf-796e-4527-9b42-13ae5766c6fd';
34+
const ParentSpanId = 'e96de653-ad6e-4ad5-b437-e81fd9d2d61d';
35+
36+
const options = {
37+
groupId: 'integration-test',
38+
kafkaHost: 'kafkasvc:9092',
39+
fromOffset: 'earliest' as ("earliest" | "latest" | "none"),
40+
encoding: 'buffer',
41+
keyEncoding: 'utf8'
42+
};
4843

44+
const consumer = new kafka.ConsumerGroup(options, 'proto-spans');
45+
46+
const executeTest = (consumer: kafka.ConsumerGroup, tracer: opentracing.Tracer, done) => {
47+
const carrier = {'Trace-ID': TraceId , 'Span-ID': SpanId, 'Parent-ID': ParentSpanId, 'Baggage-myKey': 'myVal'};
4948
const serverSpan = tracer.startSpan('my-operation', {
50-
childOf: new SpanContext(
51-
TraceId,
52-
'7a7cc5bf-796e-4527-9b42-13ae5766c6fd',
53-
'e96de653-ad6e-4ad5-b437-e81fd9d2d61d')
49+
childOf: tracer.extract(opentracing.FORMAT_TEXT_MAP, carrier)
5450
})
5551
.setTag(opentracing.Tags.SPAN_KIND, 'server')
5652
.setTag(opentracing.Tags.HTTP_METHOD, 'GET');
@@ -70,30 +66,75 @@ describe('Haystack Integration Tests', () => {
7066

7167
var serverSpanReceived = 0;
7268
var clientSpanReceived = 0;
73-
74-
const options = {
75-
groupId: 'integration-test',
76-
kafkaHost: 'kafkasvc:9092',
77-
fromOffset: 'earliest' as ("earliest" | "latest" | "none")
78-
};
79-
const consumer = new kafka.ConsumerGroup(options, 'proto-spans');
69+
8070
consumer.on('message', (kafkaMessage) => {
8171
expect(kafkaMessage.key).eq(TraceId);
8272

8373
const spanBuffer = kafkaMessage.value as Buffer;
84-
//TODO: figure out why Buffer is failing to deserialize into a proto object
85-
if (spanBuffer.includes('7a7cc5bf-796e-4527-9b42-13ae5766c6fd')) {
74+
const protoSpan = messages.Span.deserializeBinary(spanBuffer);
75+
76+
var isServerSpan = false;
77+
protoSpan.getTagsList().forEach((tag) => {
78+
if (tag.getKey() === 'span.kind' && tag.getVstr() === 'server') {
79+
isServerSpan = true;
80+
}
81+
});
82+
if (isServerSpan) {
8683
serverSpanReceived = serverSpanReceived + 1;
84+
expect(protoSpan.getTraceid()).eq(TraceId);
85+
expect(protoSpan.getSpanid()).eq(SpanId);
86+
expect(protoSpan.getParentspanid()).eq(ParentSpanId);
8787
} else {
8888
clientSpanReceived = clientSpanReceived + 1;
89-
}
89+
expect(protoSpan.getTraceid()).eq(TraceId);
90+
expect(protoSpan.getSpanid() === SpanId).eq(false);
91+
expect(protoSpan.getParentspanid()).eq(SpanId);
92+
}
9093
});
9194

9295
setTimeout(() => {
9396
expect(serverSpanReceived).eq(1);
9497
expect(clientSpanReceived).eq(1);
9598
done();
9699
}, 5000);
100+
}
101+
102+
describe('Haystack Integration Tests', () => {
103+
describe('Tracer Test with haystack agent', () => {
104+
return it("should generate spans and push to haystack-agent", function(done) {
105+
this.timeout(6000);
106+
107+
const tracer = Tracer.initTracer({
108+
serviceName: 'my-service',
109+
commonTags: {
110+
'my-service-version': '0.1.0'
111+
},
112+
dispatcher: {
113+
type: 'haystack_agent',
114+
agentHost: 'haystack_agent'
115+
},
116+
logger: new ConsoleLogger(),
117+
});
118+
executeTest(consumer, tracer, done);
119+
120+
});
121+
});
122+
123+
describe('Tracer Test with haystack collector', () => {
124+
return it("should generate spans and push to haystack-collector", function(done) {
125+
this.timeout(6000);
126+
const tracer = Tracer.initTracer({
127+
serviceName: 'my-service',
128+
commonTags: {
129+
'my-service-version': '0.1.0'
130+
},
131+
dispatcher: {
132+
type: 'http_collector',
133+
collectorUrl: 'http://haystack_collector:8080/span'
134+
},
135+
logger: new ConsoleLogger(),
97136
});
137+
executeTest(consumer, tracer, done);
138+
});
98139
});
99140
});

0 commit comments

Comments
 (0)