Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.

Commit d3e2d7c

Browse files
authored
Make Stackdriver propagation module consistent with other propagations interface (#436)
* Make Stackdriver propagation module consistent with other propagations * fix review comments 1. Use replace instead of split-join 2. Add helperGetter function
1 parent 9c95aa0 commit d3e2d7c

7 files changed

Lines changed: 247 additions & 5 deletions

File tree

packages/opencensus-propagation-stackdriver/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@opencensus/propagation-stackdriver",
33
"version": "0.0.9",
4-
"description": "",
4+
"description": "Opencensus propagation package for Stackdriver format.",
55
"main": "build/src/index.js",
66
"types": "build/src/index.d.ts",
77
"repository": "census-instrumentation/opencensus-node",
@@ -38,6 +38,7 @@
3838
"typescript": "~3.2.0"
3939
},
4040
"dependencies": {
41+
"@opencensus/core": "^0.0.9",
4142
"hex2dec": "^1.0.1",
4243
"uuid": "^3.2.1"
4344
},

packages/opencensus-propagation-stackdriver/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
export * from './stackdriver-format';
18+
1719
import * as v1API from './v1';
1820

1921
export interface SpanContext {
@@ -46,6 +48,11 @@ export interface Propagation {
4648
generate(): SpanContext;
4749
}
4850

51+
/**
52+
* @deprecated since version 0.0.10 - use {@link StackdriverFormat} instead
53+
* All other propagation exports a class constructor, while Stackdriver v1
54+
* propagation exports an implementation of Propagation.
55+
*/
4956
export const v1: Propagation = {
5057
extract: v1API.extract,
5158
inject: v1API.inject,
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* Copyright 2019 OpenCensus Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* This file implements propagation for the Stackdriver Trace v1 Trace Context
19+
* format.
20+
*
21+
* The header specification is:
22+
* "X-Cloud-Trace-Context: TRACE_ID/SPAN_ID;o=TRACE_TRUE"
23+
* Where:
24+
* {TRACE_ID} is a 32-character hexadecimal value representing a 128-bit
25+
* number. It should be unique between your requests, unless you
26+
* intentionally want to bundle the requests together.
27+
* {SPAN_ID} is the decimal representation of the (unsigned) span ID. It
28+
* should be 0 for the first span in your trace. For subsequent requests,
29+
* set SPAN_ID to the span ID of the parent request.
30+
* {TRACE_TRUE} must be 1 to trace request. Specify 0 to not trace the request.
31+
*/
32+
33+
import * as crypto from 'crypto';
34+
import {decToHex, hexToDec} from 'hex2dec';
35+
import * as uuid from 'uuid';
36+
import {HeaderGetter, HeaderSetter, Propagation, SpanContext} from './index';
37+
38+
/** Header that carries span context across Google infrastructure. */
39+
export const TRACE_CONTEXT_HEADER_NAME = 'x-cloud-trace-context';
40+
const SPAN_ID_RANDOM_BYTES = 8;
41+
const TRACE_TRUE = 0x1;
42+
43+
/** Propagates span context through Stackdriver Format propagation. */
44+
export class StackdriverFormat implements Propagation {
45+
/**
46+
* Gets the span context from a request headers. If there is no span context
47+
* in the headers, null is returned.
48+
* @param getter
49+
*/
50+
extract(getter: HeaderGetter): SpanContext|null {
51+
const traceContextHeader = getter.getHeader(TRACE_CONTEXT_HEADER_NAME);
52+
if (typeof traceContextHeader !== 'string') {
53+
return null;
54+
}
55+
const matches =
56+
traceContextHeader.match(/^([0-9a-fA-F]+)(?:\/([0-9]+))(?:;o=(.*))?/);
57+
if (!matches || matches.length !== 4 || matches[0] !== traceContextHeader ||
58+
(matches[2] && isNaN(Number(matches[2])))) {
59+
return null;
60+
}
61+
return {
62+
traceId: matches[1],
63+
// strip 0x prefix from hex output from decToHex, and and pad so it's
64+
// always a length-16 hex string
65+
spanId: `0000000000000000${decToHex(matches[2]).slice(2)}`.slice(-16),
66+
options: isNaN(Number(matches[3])) ? TRACE_TRUE : Number(matches[3])
67+
};
68+
}
69+
70+
/**
71+
* Adds a span context in a request headers.
72+
* @param setter
73+
* @param spanContext
74+
*/
75+
inject(setter: HeaderSetter, spanContext: SpanContext): void {
76+
let header = `${spanContext.traceId}/${hexToDec(spanContext.spanId)}`;
77+
if (spanContext.options) {
78+
header += `;o=${spanContext.options}`;
79+
}
80+
81+
setter.setHeader(TRACE_CONTEXT_HEADER_NAME, header);
82+
}
83+
84+
/** Generate SpanContexts */
85+
generate(): SpanContext {
86+
return {
87+
traceId: uuid.v4().replace(/-/g, ''),
88+
spanId: spanRandomBuffer().toString('hex'),
89+
options: TRACE_TRUE
90+
};
91+
}
92+
}
93+
94+
// Use the faster crypto.randomFillSync when available (Node 7+) falling back to
95+
// using crypto.randomBytes.
96+
// TODO(ofrobots): Use alternate logic for the browser where crypto and Buffer
97+
// are not available.
98+
const spanIdBuffer = Buffer.alloc(SPAN_ID_RANDOM_BYTES);
99+
const randomFillSync = crypto.randomFillSync;
100+
const randomBytes = crypto.randomBytes;
101+
const spanRandomBuffer = randomFillSync ?
102+
() => randomFillSync(spanIdBuffer) :
103+
() => randomBytes(SPAN_ID_RANDOM_BYTES);

packages/opencensus-propagation-stackdriver/src/v1.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import * as crypto from 'crypto';
2424
import {decToHex, hexToDec} from 'hex2dec';
2525
import * as uuid from 'uuid';
26-
2726
import {HeaderGetter, HeaderSetter, SpanContext} from './index';
2827

2928
const TRACE_CONTEXT_HEADER_NAME = 'x-cloud-trace-context';
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/**
2+
* Copyright 2019 OpenCensus Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {HeaderGetter, HeaderSetter} from '@opencensus/core';
18+
import * as assert from 'assert';
19+
import {StackdriverFormat, TRACE_CONTEXT_HEADER_NAME} from '../src';
20+
21+
const stackdriverFormat = new StackdriverFormat();
22+
23+
function helperGetter(value: string|string[]|undefined) {
24+
const headers: {[key: string]: string|string[]|undefined} = {};
25+
headers[TRACE_CONTEXT_HEADER_NAME] = value;
26+
const getter: HeaderGetter = {
27+
getHeader(name: string) {
28+
return headers[name];
29+
}
30+
};
31+
return getter;
32+
}
33+
34+
describe('StackdriverPropagation', () => {
35+
describe('extract()', () => {
36+
it('should extract context of a sampled span from headers', () => {
37+
const getter = helperGetter('123456/667;o=1');
38+
assert.deepEqual(
39+
stackdriverFormat.extract(getter),
40+
{traceId: '123456', spanId: '000000000000029b', options: 1});
41+
});
42+
43+
it('should extract context of a span from headers when TRACE_TRUE set to 0',
44+
() => {
45+
const getter =
46+
helperGetter('123456/123456123456123456123456123456123456;o=0');
47+
assert.deepEqual(
48+
stackdriverFormat.extract(getter),
49+
{traceId: '123456', spanId: 'a89bb45f10f2f240', options: 0});
50+
});
51+
52+
it('should extract context of a span from headers when option is undefined',
53+
() => {
54+
const getter = helperGetter('cafef00d/123');
55+
assert.deepEqual(
56+
stackdriverFormat.extract(getter),
57+
{traceId: 'cafef00d', spanId: '000000000000007b', options: 1});
58+
});
59+
60+
const inputs = [
61+
'', undefined, '123456', '123456;o=1', 'o=1;123456', '123;456;o=1',
62+
'123/o=1;456', '123/abc/o=1', 'cafefood/667;o=1'
63+
];
64+
inputs.forEach(s => {
65+
it(`should reject ${s}`, () => {
66+
const getter = helperGetter(s);
67+
const result = stackdriverFormat.extract(getter);
68+
assert.ok(!result);
69+
});
70+
});
71+
});
72+
73+
describe('inject', () => {
74+
it('should inject a context of a sampled span', () => {
75+
const spanContext = stackdriverFormat.generate();
76+
const headers: {[key: string]: string|string[]|undefined} = {};
77+
const setter: HeaderSetter = {
78+
setHeader(name: string, value: string) {
79+
headers[name] = value;
80+
}
81+
};
82+
const getter: HeaderGetter = {
83+
getHeader(name: string) {
84+
return headers[name];
85+
}
86+
};
87+
88+
stackdriverFormat.inject(setter, spanContext);
89+
assert.deepEqual(stackdriverFormat.extract(getter), spanContext);
90+
});
91+
92+
it('should not inject empty spancontext', () => {
93+
const emptySpanContext = {traceId: '', spanId: ''};
94+
const headers: {[key: string]: string|string[]|undefined} = {};
95+
const setter: HeaderSetter = {
96+
setHeader(name: string, value: string) {
97+
headers[name] = value;
98+
}
99+
};
100+
const getter: HeaderGetter = {
101+
getHeader(name: string) {
102+
return headers[name];
103+
}
104+
};
105+
106+
stackdriverFormat.inject(setter, emptySpanContext);
107+
assert.deepEqual(stackdriverFormat.extract(getter), null);
108+
});
109+
});
110+
111+
describe('generate', () => {
112+
const TIMES = 20;
113+
114+
// Generate some span contexts.
115+
const GENERATED = Array.from({length: TIMES})
116+
.fill(0)
117+
.map(_ => stackdriverFormat.generate());
118+
119+
it('should generate unique traceIds', () => {
120+
const traceIds = GENERATED.map(c => c.traceId);
121+
assert.strictEqual((new Set(traceIds)).size, TIMES);
122+
});
123+
124+
it('should generate unique spanIds', () => {
125+
const spanIds = GENERATED.map(c => c.spanId);
126+
assert.strictEqual((new Set(spanIds)).size, TIMES);
127+
});
128+
});
129+
});

packages/opencensus-propagation-stackdriver/test/test-v1.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515
*/
1616

1717
import * as assert from 'assert';
18-
import * as mocha from 'mocha';
1918
import {inspect} from 'util';
20-
2119
import {SpanContext} from '../src/index';
2220
import {extract, generate, inject, parseContextFromHeader, serializeSpanContext} from '../src/v1';
2321

packages/opencensus-propagation-stackdriver/tsconfig.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
"extends": "./node_modules/gts/tsconfig-google.json",
33
"compilerOptions": {
44
"rootDir": ".",
5-
"outDir": "build"
5+
"outDir": "build",
6+
"pretty": true,
7+
"module": "commonjs",
8+
"target": "es6",
9+
"strictNullChecks": true,
10+
"noUnusedLocals": true
611
},
712
"include": [
813
"src/*.ts",

0 commit comments

Comments
 (0)