-
Notifications
You must be signed in to change notification settings - Fork 65
feat: Add Envoy Propagator #264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
42631c9
a56be65
5bf98a3
2007b93
0bb5cae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // Underscore.js-like wrapper to left pad a string to a certain length with a character | ||
| export default function _leftpad(str, len, ch) { | ||
| str = String(str); | ||
| let i = -1; | ||
| if (!ch && ch !== 0) ch = ' '; | ||
| len -= str.length; | ||
| while (++i < len) { | ||
| str = ch + str; | ||
| } | ||
| return str; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| import pb from 'protobufjs'; | ||
| import long from 'long'; | ||
| import _each from '../_each'; | ||
| import _leftpad from '../_leftpad'; | ||
| import SpanContextImp from './span_context_imp'; | ||
| import LightStepPropagator from './propagator_ls'; | ||
|
|
||
| const CARRIER_ENVOY_HEADER_KEY = 'x-ot-span-context'; | ||
|
|
||
| const BINARY_PROTO = { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if this should be in here like this. I tried using a local file but it doesn't get generated with webpack. Any tips or recommendations for proto?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think we should generate the lightstep.proto in the same way that we generate the collector.proto. The resulting file would end up in the If we do that, we can probably avoid the dependency on |
||
| nested : { | ||
| lightstep : { | ||
| options : { go_package : 'lightsteppb' }, | ||
| nested : { | ||
| BinaryCarrier : { | ||
| fields : { | ||
| deprecated_text_ctx : { | ||
| rule : 'repeated', | ||
| type : 'bytes', | ||
| id : 1, | ||
| }, | ||
| basic_ctx : { type : 'BasicTracerCarrier', id : 2 }, | ||
| }, | ||
| }, | ||
| BasicTracerCarrier : { | ||
| fields : { | ||
| trace_id : { type : 'fixed64', id : 1 }, | ||
| span_id : { type : 'fixed64', id : 2 }, | ||
| sampled : { type : 'bool', id : 3 }, | ||
| baggage_items : { keyType : 'string', type : 'string', id : 4 }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
|
|
||
| export default class EnvoyPropagator extends LightStepPropagator { | ||
| constructor(tracer) { | ||
| super(tracer); | ||
| this._tracer = tracer; | ||
| this._envoyHeaderKey = CARRIER_ENVOY_HEADER_KEY; | ||
| this._carrierPb = pb.Root.fromJSON(BINARY_PROTO); | ||
| } | ||
|
|
||
| inject(spanContext, carrier) { | ||
| if (!carrier) { | ||
| this._tracer._error('Unexpected null carrier in call to inject'); | ||
| return; | ||
| } | ||
| if (typeof carrier !== 'object') { | ||
| this._tracer._error( | ||
| `Unexpected '${typeof carrier}' FORMAT_BINARY carrier in call to inject`, | ||
| ); | ||
| return; | ||
| } | ||
|
|
||
| let basicContext = { | ||
| span_id : long.fromString(spanContext._guid, 16), | ||
| trace_id : long.fromString(spanContext._traceGUID, 16), | ||
| sampled : true, | ||
| baggage_items : {}, | ||
| }; | ||
| spanContext.forEachBaggageItem((key, value) => { | ||
| basicContext.baggage_items[key] = value; | ||
| }); | ||
|
|
||
| let binaryCarrier = this._carrierPb.lookupType('BinaryCarrier'); | ||
|
|
||
| let payload = { | ||
| basic_ctx : basicContext, | ||
| }; | ||
|
|
||
| let err = binaryCarrier.verify(payload); | ||
| if (err) { | ||
| this._tracer._error(`Invalid Span Context: ${err}`); | ||
| return null; | ||
| } | ||
| let msg = binaryCarrier.create(payload); | ||
| let buffer = binaryCarrier.encode(msg).finish(); | ||
| let bufferString = pb.util.base64.encode(buffer, 0, buffer.length); | ||
| carrier[this._envoyHeaderKey] = bufferString; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could be mistaken, but for binary, isn't the carrier just a byte array? If so, I don't think there is a notion of a string key, with a binary value. I think it just writes bytes into the array.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it still needs a key in the headers, and you still need to base64 the output. maybe this would be one-step with google protos? keep in mind that adding another proto will cause the tracer file size to creep up again, to your above point
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do any of the other Lightstep tracers have an EnvoyPropagator? As I look around I only see BinaryPropagator (this is true for Go and Python anyway).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. C++ has one too that just uses the binary propagator to do the encoding but then puts it in the envoy key header.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking that we should probably try to do the same. Would it be possible to split to this work into a BinaryPropagator and an Envoy propagator (or instrumentation) that uses it to do the header manipulation? |
||
|
|
||
| return carrier; | ||
| } | ||
|
|
||
| extract(carrier) { | ||
| // Iterate over the contents of the carrier and set the properties | ||
| // accordingly. | ||
| let foundFields = 0; | ||
| let spanGUID = null; | ||
| let traceGUID = null; | ||
| let sampled = true; | ||
|
|
||
| if (carrier[this._envoyHeaderKey] === undefined) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe the same comment for inject about the carrier being a byte array applies here as well. |
||
| // This is not an error per se, there was simply no SpanContext | ||
| // in the carrier. | ||
| return null; | ||
| } | ||
|
|
||
| // Decode context | ||
| const binaryData = Buffer.from(carrier[this._envoyHeaderKey], 'base64'); | ||
| let binaryCarrier = this._carrierPb.lookupType('BinaryCarrier'); | ||
| let msg = binaryCarrier.decode(binaryData); | ||
| let basicContext = msg.basic_ctx.toJSON(); | ||
|
|
||
| if (basicContext === undefined) { | ||
| // This is not an error per se, there was simply no SpanContext | ||
| // in the carrier. | ||
| return null; | ||
| } | ||
|
|
||
| // Validate span context | ||
| _each(basicContext, (value, key) => { | ||
| key = key.toLowerCase(); | ||
| if (key === 'baggage_items') { | ||
| // We will address baggage after span context is verified | ||
| return; | ||
| } | ||
|
|
||
| switch (key) { | ||
| case 'trace_id': | ||
| foundFields++; | ||
| // left pad to length of 16 | ||
| // long is used because JS only supports up to 53 bit integers | ||
| traceGUID = _leftpad( | ||
| long.fromValue(value).toString(16), | ||
| 16, | ||
| '0', | ||
| ); | ||
| break; | ||
| case 'span_id': | ||
| foundFields++; | ||
| // left pad to length of 16 | ||
| // long is used because JS only supports up to 53 bit integers | ||
| spanGUID = _leftpad( | ||
| long.fromValue(value).toString(16), | ||
| 16, | ||
| '0', | ||
| ); | ||
| // left pad | ||
|
|
||
| break; | ||
| case 'sampled': | ||
| switch (value) { | ||
| case 0: | ||
| case '0': | ||
| case false: | ||
| case 'false': | ||
| sampled = false; | ||
| break; | ||
| default: | ||
| sampled = true; | ||
| break; | ||
| } | ||
| break; | ||
| default: | ||
| this._tracer._error( | ||
| `Unrecognized carrier key '${key}'. Ignoring.`, | ||
| ); | ||
| break; | ||
| } | ||
| }); | ||
|
|
||
| if (foundFields === 0) { | ||
| // This is not an error per se, there was simply no SpanContext | ||
| // in the carrier. | ||
| return null; | ||
| } | ||
| if (foundFields < 2) { | ||
| // A partial SpanContext suggests some sort of data corruption. | ||
| this._tracer._error(`Only found a partial SpanContext: ${carrier}`); | ||
| return null; | ||
| } | ||
|
|
||
| let spanContext = new SpanContextImp(spanGUID, traceGUID, sampled); | ||
|
|
||
| if (basicContext.baggage_items !== undefined) { | ||
| _each(basicContext.baggage_items, (value, key) => { | ||
| spanContext.setBaggageItem(key.toLowerCase(), value); | ||
| }); | ||
| } | ||
|
|
||
| return spanContext; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,7 +12,6 @@ import { Platform, ProtoTransport, ThriftTransport } from '../platform_abstracti | |
| import AuthImp from './auth_imp'; | ||
| import RuntimeImp from './runtime_imp'; | ||
| import ReportImp from './report_imp'; | ||
| import UnsupportedPropagator from './propagator'; | ||
| import LightStepPropagator from './propagator_ls'; | ||
|
|
||
| const ClockState = require('./util/clock_state'); | ||
|
|
@@ -81,7 +80,7 @@ export default class Tracer extends opentracing.Tracer { | |
| this._propagators = {}; | ||
| this._propagators[this._opentracing.FORMAT_HTTP_HEADERS] = new LightStepPropagator(this); | ||
| this._propagators[this._opentracing.FORMAT_TEXT_MAP] = new LightStepPropagator(this); | ||
| this._propagators[this._opentracing.FORMAT_BINARY] = new UnsupportedPropagator(this, | ||
| this._propagators[this._opentracing.FORMAT_BINARY] = new LightStepPropagator(this, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it make sense to have the LightstepPropagator now have a default for FORMAT_BINARY?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like go uses the I think this makes sense, but have we thought about how this will work with or impact the no-protobuf version of this library?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FORMAT_BINARY should correspond to the binary propagator. BinaryPropagator/EnvoyPropagator/whatever are all the same. Theoretically this shouldn't impact the no-pb version (as FORMAT_BINARY should map to UnsupportedPropagator correctly over there) |
||
| this._opentracing.FORMAT_BINARY); | ||
|
|
||
| if (opts && opts.propagators) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this going to be used in node.js only ? , if so then use String.prototype.repeat instead