This repository has been archived by the owner on Oct 3, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make Stackdriver propagation module consistent with other propagation…
…s interface (#436) * Make Stackdriver propagation module consistent with other propagations * fix review comments 1. Use replace instead of split-join 2. Add helperGetter function
- Loading branch information
1 parent
9c95aa0
commit d3e2d7c
Showing
7 changed files
with
247 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
103 changes: 103 additions & 0 deletions
103
packages/opencensus-propagation-stackdriver/src/stackdriver-format.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
/** | ||
* Copyright 2019 OpenCensus Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
/** | ||
* This file implements propagation for the Stackdriver Trace v1 Trace Context | ||
* format. | ||
* | ||
* The header specification is: | ||
* "X-Cloud-Trace-Context: TRACE_ID/SPAN_ID;o=TRACE_TRUE" | ||
* Where: | ||
* {TRACE_ID} is a 32-character hexadecimal value representing a 128-bit | ||
* number. It should be unique between your requests, unless you | ||
* intentionally want to bundle the requests together. | ||
* {SPAN_ID} is the decimal representation of the (unsigned) span ID. It | ||
* should be 0 for the first span in your trace. For subsequent requests, | ||
* set SPAN_ID to the span ID of the parent request. | ||
* {TRACE_TRUE} must be 1 to trace request. Specify 0 to not trace the request. | ||
*/ | ||
|
||
import * as crypto from 'crypto'; | ||
import {decToHex, hexToDec} from 'hex2dec'; | ||
import * as uuid from 'uuid'; | ||
import {HeaderGetter, HeaderSetter, Propagation, SpanContext} from './index'; | ||
|
||
/** Header that carries span context across Google infrastructure. */ | ||
export const TRACE_CONTEXT_HEADER_NAME = 'x-cloud-trace-context'; | ||
const SPAN_ID_RANDOM_BYTES = 8; | ||
const TRACE_TRUE = 0x1; | ||
|
||
/** Propagates span context through Stackdriver Format propagation. */ | ||
export class StackdriverFormat implements Propagation { | ||
/** | ||
* Gets the span context from a request headers. If there is no span context | ||
* in the headers, null is returned. | ||
* @param getter | ||
*/ | ||
extract(getter: HeaderGetter): SpanContext|null { | ||
const traceContextHeader = getter.getHeader(TRACE_CONTEXT_HEADER_NAME); | ||
if (typeof traceContextHeader !== 'string') { | ||
return null; | ||
} | ||
const matches = | ||
traceContextHeader.match(/^([0-9a-fA-F]+)(?:\/([0-9]+))(?:;o=(.*))?/); | ||
if (!matches || matches.length !== 4 || matches[0] !== traceContextHeader || | ||
(matches[2] && isNaN(Number(matches[2])))) { | ||
return null; | ||
} | ||
return { | ||
traceId: matches[1], | ||
// strip 0x prefix from hex output from decToHex, and and pad so it's | ||
// always a length-16 hex string | ||
spanId: `0000000000000000${decToHex(matches[2]).slice(2)}`.slice(-16), | ||
options: isNaN(Number(matches[3])) ? TRACE_TRUE : Number(matches[3]) | ||
}; | ||
} | ||
|
||
/** | ||
* Adds a span context in a request headers. | ||
* @param setter | ||
* @param spanContext | ||
*/ | ||
inject(setter: HeaderSetter, spanContext: SpanContext): void { | ||
let header = `${spanContext.traceId}/${hexToDec(spanContext.spanId)}`; | ||
if (spanContext.options) { | ||
header += `;o=${spanContext.options}`; | ||
} | ||
|
||
setter.setHeader(TRACE_CONTEXT_HEADER_NAME, header); | ||
} | ||
|
||
/** Generate SpanContexts */ | ||
generate(): SpanContext { | ||
return { | ||
traceId: uuid.v4().replace(/-/g, ''), | ||
spanId: spanRandomBuffer().toString('hex'), | ||
options: TRACE_TRUE | ||
}; | ||
} | ||
} | ||
|
||
// Use the faster crypto.randomFillSync when available (Node 7+) falling back to | ||
// using crypto.randomBytes. | ||
// TODO(ofrobots): Use alternate logic for the browser where crypto and Buffer | ||
// are not available. | ||
const spanIdBuffer = Buffer.alloc(SPAN_ID_RANDOM_BYTES); | ||
const randomFillSync = crypto.randomFillSync; | ||
const randomBytes = crypto.randomBytes; | ||
const spanRandomBuffer = randomFillSync ? | ||
() => randomFillSync(spanIdBuffer) : | ||
() => randomBytes(SPAN_ID_RANDOM_BYTES); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
129 changes: 129 additions & 0 deletions
129
packages/opencensus-propagation-stackdriver/test/test-stackdriver-format.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/** | ||
* Copyright 2019 OpenCensus Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import {HeaderGetter, HeaderSetter} from '@opencensus/core'; | ||
import * as assert from 'assert'; | ||
import {StackdriverFormat, TRACE_CONTEXT_HEADER_NAME} from '../src'; | ||
|
||
const stackdriverFormat = new StackdriverFormat(); | ||
|
||
function helperGetter(value: string|string[]|undefined) { | ||
const headers: {[key: string]: string|string[]|undefined} = {}; | ||
headers[TRACE_CONTEXT_HEADER_NAME] = value; | ||
const getter: HeaderGetter = { | ||
getHeader(name: string) { | ||
return headers[name]; | ||
} | ||
}; | ||
return getter; | ||
} | ||
|
||
describe('StackdriverPropagation', () => { | ||
describe('extract()', () => { | ||
it('should extract context of a sampled span from headers', () => { | ||
const getter = helperGetter('123456/667;o=1'); | ||
assert.deepEqual( | ||
stackdriverFormat.extract(getter), | ||
{traceId: '123456', spanId: '000000000000029b', options: 1}); | ||
}); | ||
|
||
it('should extract context of a span from headers when TRACE_TRUE set to 0', | ||
() => { | ||
const getter = | ||
helperGetter('123456/123456123456123456123456123456123456;o=0'); | ||
assert.deepEqual( | ||
stackdriverFormat.extract(getter), | ||
{traceId: '123456', spanId: 'a89bb45f10f2f240', options: 0}); | ||
}); | ||
|
||
it('should extract context of a span from headers when option is undefined', | ||
() => { | ||
const getter = helperGetter('cafef00d/123'); | ||
assert.deepEqual( | ||
stackdriverFormat.extract(getter), | ||
{traceId: 'cafef00d', spanId: '000000000000007b', options: 1}); | ||
}); | ||
|
||
const inputs = [ | ||
'', undefined, '123456', '123456;o=1', 'o=1;123456', '123;456;o=1', | ||
'123/o=1;456', '123/abc/o=1', 'cafefood/667;o=1' | ||
]; | ||
inputs.forEach(s => { | ||
it(`should reject ${s}`, () => { | ||
const getter = helperGetter(s); | ||
const result = stackdriverFormat.extract(getter); | ||
assert.ok(!result); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('inject', () => { | ||
it('should inject a context of a sampled span', () => { | ||
const spanContext = stackdriverFormat.generate(); | ||
const headers: {[key: string]: string|string[]|undefined} = {}; | ||
const setter: HeaderSetter = { | ||
setHeader(name: string, value: string) { | ||
headers[name] = value; | ||
} | ||
}; | ||
const getter: HeaderGetter = { | ||
getHeader(name: string) { | ||
return headers[name]; | ||
} | ||
}; | ||
|
||
stackdriverFormat.inject(setter, spanContext); | ||
assert.deepEqual(stackdriverFormat.extract(getter), spanContext); | ||
}); | ||
|
||
it('should not inject empty spancontext', () => { | ||
const emptySpanContext = {traceId: '', spanId: ''}; | ||
const headers: {[key: string]: string|string[]|undefined} = {}; | ||
const setter: HeaderSetter = { | ||
setHeader(name: string, value: string) { | ||
headers[name] = value; | ||
} | ||
}; | ||
const getter: HeaderGetter = { | ||
getHeader(name: string) { | ||
return headers[name]; | ||
} | ||
}; | ||
|
||
stackdriverFormat.inject(setter, emptySpanContext); | ||
assert.deepEqual(stackdriverFormat.extract(getter), null); | ||
}); | ||
}); | ||
|
||
describe('generate', () => { | ||
const TIMES = 20; | ||
|
||
// Generate some span contexts. | ||
const GENERATED = Array.from({length: TIMES}) | ||
.fill(0) | ||
.map(_ => stackdriverFormat.generate()); | ||
|
||
it('should generate unique traceIds', () => { | ||
const traceIds = GENERATED.map(c => c.traceId); | ||
assert.strictEqual((new Set(traceIds)).size, TIMES); | ||
}); | ||
|
||
it('should generate unique spanIds', () => { | ||
const spanIds = GENERATED.map(c => c.spanId); | ||
assert.strictEqual((new Set(spanIds)).size, TIMES); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters