diff --git a/packages/zipkin-instrumentation-express/src/wrapExpressHttpProxy.js b/packages/zipkin-instrumentation-express/src/wrapExpressHttpProxy.js index c49d336e..03981b3a 100644 --- a/packages/zipkin-instrumentation-express/src/wrapExpressHttpProxy.js +++ b/packages/zipkin-instrumentation-express/src/wrapExpressHttpProxy.js @@ -1,4 +1,4 @@ -const {Request, Annotation} = require('zipkin'); +const {Annotation} = require('zipkin'); const url = require('url'); function getPathnameFromPath(path) { @@ -18,7 +18,7 @@ class ExpressHttpProxyInstrumentation { const clientTraceId = this.tracer.createChildId(); this.tracer.setId(clientTraceId); - const proxyReqWithZipkinHeaders = Request.addZipkinHeaders(proxyReq, clientTraceId); + const proxyReqWithZipkinHeaders = this.tracer.injector(proxyReq); Object.defineProperty(serverReq, '_trace_id_proxy', {configurable: false, get: () => clientTraceId}); diff --git a/packages/zipkin-instrumentation-kafkajs/src/zipkin-instrumentation-kafkajs.js b/packages/zipkin-instrumentation-kafkajs/src/zipkin-instrumentation-kafkajs.js index b9793a93..afa01c38 100644 --- a/packages/zipkin-instrumentation-kafkajs/src/zipkin-instrumentation-kafkajs.js +++ b/packages/zipkin-instrumentation-kafkajs/src/zipkin-instrumentation-kafkajs.js @@ -1,4 +1,3 @@ -const {Request} = require('zipkin'); const { recordConsumeStop, recordConsumeStart, recordProducerStart, recordProducerStop @@ -49,7 +48,7 @@ const instrumentKafkaJs = (kafkaJs, {tracer, remoteServiceName}) => { id = recordProducerStart(tracer, 'send', remoteServiceName, {topic: params.topic}); const withTraceHeaders = Object.assign({}, params, { - messages: params.messages.map(msg => Request.addZipkinHeaders(msg, id)) + messages: params.messages.map(msg => tracer.injector(msg)) }); promise = obj[prop](withTraceHeaders); diff --git a/packages/zipkin/index.d.ts b/packages/zipkin/index.d.ts index e0e45665..15378a1f 100644 --- a/packages/zipkin/index.d.ts +++ b/packages/zipkin/index.d.ts @@ -29,6 +29,14 @@ declare namespace zipkin { const alwaysSample: (traceId: TraceId) => boolean; } + interface Injector { + inject(context: Context, request: R): void; + } + + interface Extractor { + extract(request: R): TraceId; + } + class Tracer { constructor(args: { ctxImpl: Context, @@ -39,7 +47,8 @@ declare namespace zipkin { localServiceName?: string, localEndpoint?: model.Endpoint, log?: Console, - defaultTags?: {} + defaultTags?: {}, + propagation?: propagation.Propagation }); /** Returns the current trace ID or a sentinel value indicating its absence. */ @@ -61,6 +70,11 @@ declare namespace zipkin { recordLocalAddr(inetAddress: InetAddress): void; recordBinary(key: string, value: boolean | string | number): void; writeIdToConsole(message: any): void; + /** Extract propagation ctx from request */ + extractId(readHeader: (header: string) => option.IOption): void; + /** Injector propagation ctx from request */ + injector(request: any): object; + } class TraceId { @@ -271,14 +285,6 @@ declare namespace zipkin { toInt(): number; } - namespace HttpHeaders { - const TraceId: string; - const SpanId: string; - const ParentSpanId: string; - const Sampled: string; - const Flags: string; - } - interface Record { traceId: TraceId; timestamp: number; @@ -339,6 +345,7 @@ declare namespace zipkin { } namespace Instrumentation { + class HttpServer { constructor(args: { tracer: Tracer, @@ -357,7 +364,7 @@ declare namespace zipkin { } class HttpClient { - constructor(args: { tracer: Tracer, serviceName?: string, remoteServiceName?: string }); + constructor(args: { tracer: Tracer, serviceName?: string, remoteServiceName?: string}); recordRequest( request: T, @@ -368,6 +375,24 @@ declare namespace zipkin { recordError(traceId: TraceId, error: Error): void; } } + namespace propagation { + interface Setter { + put(request: R, key: K, value: string): void; + } + interface Getter { + get(request: R, key: K): string; + } + interface Propagation { + keys(): []; + extractor(getter: Getter): Extractor; + injector(setter: Setter): Injector; + } + class B3Propagation implements Propagation { + keys(): []; + extractor(getter: Getter): Extractor; + injector(setter: Setter): Injector; + } + } } export = zipkin; diff --git a/packages/zipkin/src/httpHeaders.js b/packages/zipkin/src/httpHeaders.js deleted file mode 100644 index 5f84f2c6..00000000 --- a/packages/zipkin/src/httpHeaders.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - TraceId: 'X-B3-TraceId', - SpanId: 'X-B3-SpanId', - ParentSpanId: 'X-B3-ParentSpanId', - Sampled: 'X-B3-Sampled', - Flags: 'X-B3-Flags' -}; diff --git a/packages/zipkin/src/index.ts b/packages/zipkin/src/index.ts index 77bb38b6..8f546da8 100644 --- a/packages/zipkin/src/index.ts +++ b/packages/zipkin/src/index.ts @@ -7,7 +7,6 @@ export { default as randomTraceId } from './tracer/randomTraceId'; export { default as sampler } from './tracer/sampler'; export { default as TraceId } from './tracer/TraceId'; -export { default as HttpHeaders } from './httpHeaders'; export { default as InetAddress } from './InetAddress'; export { default as BatchRecorder } from './batch-recorder'; @@ -16,7 +15,7 @@ export { default as ConsoleRecorder } from './console-recorder'; export { default as ExplicitContext } from './explicit-context'; export { default as Instrumentation } from './instrumentation'; -export { default as Request } from './request'; +export { default as B3Propagation } from './propagation/b3propagation'; export { default as jsonEncoder } from './jsonEncoder'; export { default as model } from './model'; diff --git a/packages/zipkin/src/instrumentation/httpClient.js b/packages/zipkin/src/instrumentation/httpClient.js index eb64fa1d..3e2f0d4b 100644 --- a/packages/zipkin/src/instrumentation/httpClient.js +++ b/packages/zipkin/src/instrumentation/httpClient.js @@ -1,5 +1,4 @@ const Annotation = require('../annotation'); -const Request = require('../request'); const parseRequestUrl = require('../parseUrl'); function requiredArg(name) { @@ -19,7 +18,6 @@ class HttpClientInstrumentation { recordRequest(request, url, method) { this.tracer.setId(this.tracer.createChildId()); - const traceId = this.tracer.id; const {path} = parseRequestUrl(url); this.tracer.recordServiceName(this.serviceName); @@ -33,8 +31,7 @@ class HttpClientInstrumentation { serviceName: this.remoteServiceName })); } - - return Request.addZipkinHeaders(request, traceId); + return this.tracer.injector(request); } recordResponse(traceId, statusCode) { diff --git a/packages/zipkin/src/instrumentation/httpServer.js b/packages/zipkin/src/instrumentation/httpServer.js index bcf4da26..11492526 100644 --- a/packages/zipkin/src/instrumentation/httpServer.js +++ b/packages/zipkin/src/instrumentation/httpServer.js @@ -1,25 +1,6 @@ const Annotation = require('../annotation'); -const Header = require('../httpHeaders'); const InetAddress = require('../InetAddress'); -const TraceId = require('../tracer/TraceId'); const parseRequestUrl = require('../parseUrl'); -const {Some, None} = require('../option'); - -function stringToBoolean(str) { - return str === '1' || str === 'true'; -} - -function stringToIntOption(str) { - try { - return new Some(parseInt(str)); - } catch (err) { - return None; - } -} - -function containsRequiredHeaders(readHeader) { - return readHeader(Header.TraceId) !== None && readHeader(Header.SpanId) !== None; -} function requiredArg(name) { throw new Error(`HttpServerInstrumentation: Missing required argument ${name}.`); @@ -38,34 +19,6 @@ class HttpServerInstrumentation { this.port = port; } - _createIdFromHeaders(readHeader) { - if (containsRequiredHeaders(readHeader)) { - const spanId = readHeader(Header.SpanId); - const parentId = spanId.map((sid) => { - const traceId = readHeader(Header.TraceId); - const parentSpanId = readHeader(Header.ParentSpanId); - const sampled = readHeader(Header.Sampled); - const flags = readHeader(Header.Flags).flatMap(stringToIntOption).getOrElse(0); - return new TraceId({ - traceId: traceId.getOrElse(), - parentId: parentSpanId, - spanId: sid, - debug: flags === 1, - sampled: sampled.map(stringToBoolean), - }); - }); - - return new Some(this.tracer.join(parentId.getOrElse())); - } else if (readHeader(Header.Flags) !== None || readHeader(Header.Sampled) !== None) { - const sampled = readHeader(Header.Sampled) === None - ? None : readHeader(Header.Sampled).map(stringToBoolean); - const flags = readHeader(Header.Flags).flatMap(stringToIntOption).getOrElse(0); - return new Some(this.tracer.createRootId(sampled, flags === 1)); - } else { - return new Some(this.tracer.createRootId()); - } - } - spanNameFromRoute(method, route, code) { // eslint-disable-line class-methods-use-this if (code > 299 && code < 400) return `${method} redirected`; if (code === 404) return `${method} not_found`; @@ -74,7 +27,7 @@ class HttpServerInstrumentation { } recordRequest(method, requestUrl, readHeader) { - this._createIdFromHeaders(readHeader).ifPresent(id => this.tracer.setId(id)); + this.tracer.extractId(readHeader); const {id} = this.tracer; const {path} = parseRequestUrl(requestUrl); diff --git a/packages/zipkin/src/propagation/b3propagation.js b/packages/zipkin/src/propagation/b3propagation.js new file mode 100644 index 00000000..2f80b7f7 --- /dev/null +++ b/packages/zipkin/src/propagation/b3propagation.js @@ -0,0 +1,95 @@ +const {Some, None} = require('../option'); +const TraceId = require('../tracer/TraceId'); +const Tracer = require('../tracer'); + +function stringToBoolean(str) { + return str === '1' || str === 'true'; +} + +function stringToIntOption(str) { + try { + return new Some(parseInt(str)); + } catch (err) { + return None; + } +} + +class B3Extractor { + + constructor(b3Propagation, getter) { + this._propagation = b3Propagation + this._getter = getter + } + + extract(request) { + const traceId = this._getter.get(request, this._propagation._TRACE_ID); + const spanId = this._getter.get(request, this._propagation._SPAN_ID); + const flags = this._getter.get(request, this._propagation._FLAGS); + const sampled = this._getter.get(request, this._propagation._SAMPLED); + if(traceId !== None && spanId !== None){ + return spanId.map((sid) => { + const parentSpanId = this._getter.get(request, this._propagation._PARENT_SPAN_ID); + return new TraceId({ + traceId: traceId.getOrElse(), + parentId: parentSpanId, + spanId: sid, + debug: flags.flatMap(stringToIntOption).getOrElse(0) === 1, + sampled: sampled.map(stringToBoolean), + }); + }); + } else if(flags !== None || sampled !== None){ + // TODO Change ?? + return Tracer.createRootId(sampled === None ? None : sampled.map(stringToBoolean), + flags.flatMap(stringToIntOption).getOrElse(0) === 1) + } + return Tracer.createRootId(); + } +} + +class B3Injector { + + constructor(b3Propagation, setter) { + this._propagation = b3Propagation + this._setter = setter + } + + inject(context, request) { + this._setter.put(request, this._propagation._TRACE_ID, context.traceId); + this._setter.put(request, this._propagation._SPAN_ID, context.spanId); + context.sampled.ifPresent((psid) => { this._setter.put(request, this._propagation._PARENT_SPAN_ID, psid) }); + context.sampled.ifPresent((sampled) => { this._setter.put(request, this._propagation._SAMPLED, sampled? '1' : '0') }); + if(context.isDebug()){ + this._setter.put(request, this._propagation._FLAGS, '1'); + } + } + +} + +class B3Propagation { + + _TRACE_ID = 'X-B3-TraceId' + _SPAN_ID = 'X-B3-SpanId' + _PARENT_SPAN_ID = 'X-B3-ParentSpanId' + _SAMPLED = 'X-B3-Sampled' + _FLAGS = 'X-B3-Flags' + + get keys() { + return [ + this._TRACE_ID, + this._SPAN_ID, + this._PARENT_SPAN_ID, + this._SAMPLED, + this._FLAGS + ]; + } + + extractor(getter) { + return new B3Extractor(this, getter); + } + + injector(setter) { + return new B3Injector(this, setter); + } +} + +module.exports = B3Propagation; diff --git a/packages/zipkin/src/propagation/index.js b/packages/zipkin/src/propagation/index.js new file mode 100644 index 00000000..0597506b --- /dev/null +++ b/packages/zipkin/src/propagation/index.js @@ -0,0 +1,5 @@ +const B3Propagation = require('./b3propagation'); + +module.exports = { + B3Propagation +}; diff --git a/packages/zipkin/src/request.js b/packages/zipkin/src/request.js deleted file mode 100644 index c611ddde..00000000 --- a/packages/zipkin/src/request.js +++ /dev/null @@ -1,29 +0,0 @@ -const HttpHeaders = require('./httpHeaders'); - -function appendZipkinHeaders(req, traceId) { - const headers = req.headers || {}; - headers[HttpHeaders.TraceId] = traceId.traceId; - headers[HttpHeaders.SpanId] = traceId.spanId; - - traceId.parentSpanId.ifPresent((psid) => { - headers[HttpHeaders.ParentSpanId] = psid; - }); - traceId.sampled.ifPresent((sampled) => { - headers[HttpHeaders.Sampled] = sampled ? '1' : '0'; - }); - - if (traceId.isDebug()) { - headers[HttpHeaders.Flags] = '1'; - } - - return headers; -} - -function addZipkinHeaders(req, traceId) { - const headers = appendZipkinHeaders(req, traceId); - return Object.assign({}, req, {headers}); -} - -module.exports = { - addZipkinHeaders -}; diff --git a/packages/zipkin/src/tracer/index.js b/packages/zipkin/src/tracer/index.js index de4ca823..43361e78 100644 --- a/packages/zipkin/src/tracer/index.js +++ b/packages/zipkin/src/tracer/index.js @@ -1,3 +1,4 @@ + const isPromise = require('is-promise'); const {None, Some} = require('../option'); const {Sampler, alwaysSample} = require('./sampler'); @@ -8,6 +9,7 @@ const TraceId = require('./TraceId'); const randomTraceId = require('./randomTraceId'); const {now, hrtime} = require('../time'); const {Endpoint} = require('../model'); +const {B3Propagation} = require('../propagation'); function requiredArg(name) { @@ -37,13 +39,15 @@ class Tracer { localEndpoint, /* eslint-disable no-console */ log = console, - defaultTags + defaultTags, + propagation = new B3Propagation() }) { this.log = log; this.recorder = recorder; this.sampler = sampler; this.traceId128Bit = traceId128Bit; this.supportsJoin = supportsJoin; + this._propagation = propagation; if (localEndpoint) { this._localEndpoint = localEndpoint; } else { @@ -265,6 +269,15 @@ class Tracer { } } } + + extractId(readHeader) { + this._propagation.extractor(this, readHeader).ifPresent(id => this.setId(id)); + } + + injector(request) { + const headers = this._propagation.injector(request, this.id); + return Object.assign({}, request, {headers}); + } } module.exports = Tracer; diff --git a/packages/zipkin/test/request.test.js b/packages/zipkin/test/request.test.js deleted file mode 100644 index c0586f68..00000000 --- a/packages/zipkin/test/request.test.js +++ /dev/null @@ -1,41 +0,0 @@ -const Request = require('../src/request.js'); -const HttpHeaders = require('../src/httpHeaders'); -const {Some} = require('../src/option'); -const TraceId = require('../src/tracer/TraceId'); - -describe('Request', () => { - it('should add trace/span and ignore parent span/sampled headers if they do not exist', () => { - const traceId = new TraceId({ - spanId: '48485a3953bb6124' - }); - const req = Request.addZipkinHeaders({}, traceId); - - expect(req.headers[HttpHeaders.TraceId]).to.equal('48485a3953bb6124'); - expect(req.headers[HttpHeaders.SpanId]).to.equal('48485a3953bb6124'); - }); - - it('should add trace, span, parent span, and sampled headers', () => { - const traceId = new TraceId({ - traceId: '48485a3953bb6124', - spanId: '48485a3953bb6124', - parentId: new Some('d56852c923dc9325'), - sampled: new Some(true) - }); - const req = Request.addZipkinHeaders({}, traceId); - - expect(req.headers[HttpHeaders.TraceId]).to.equal('48485a3953bb6124'); - expect(req.headers[HttpHeaders.SpanId]).to.equal('48485a3953bb6124'); - expect(req.headers[HttpHeaders.ParentSpanId]).to.equal('d56852c923dc9325'); - }); - - it('should add flags headers if debug is on', () => { - const traceId = new TraceId({ - spanId: '48485a3953bb6124', - flags: 1 - }); - const req = Request.addZipkinHeaders({}, traceId); - - expect(req.headers[HttpHeaders.Flags]).to.equal('1'); - expect(req.headers[HttpHeaders.SpanId]).to.equal('48485a3953bb6124'); - }); -});