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

Commit bf1ecf9

Browse files
Use traceparent from window for initial load context (#34)
1 parent f6c8afa commit bf1ecf9

11 files changed

Lines changed: 232 additions & 5 deletions

File tree

packages/opencensus-web-all/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@
6666
"dependencies": {
6767
"@opencensus/web-core": "^0.0.1",
6868
"@opencensus/web-exporter-ocagent": "^0.0.1",
69-
"@opencensus/web-instrumentation-perf": "^0.0.1"
69+
"@opencensus/web-instrumentation-perf": "^0.0.1",
70+
"@opencensus/web-propagation-tracecontext": "^0.0.1"
7071
},
7172
"sideEffects": [
7273
"./src/entrypoints/*.ts"

packages/opencensus-web-all/src/export-initial-load.ts

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

17-
import {tracing} from '@opencensus/web-core';
17+
import {isSampled, tracing} from '@opencensus/web-core';
1818
import {OCAgentExporter} from '@opencensus/web-exporter-ocagent';
1919
import {clearPerfEntries, getInitialLoadRootSpan, getPerfEntries} from '@opencensus/web-instrumentation-perf';
2020

21+
import {getInitialLoadSpanContext} from './initial-load-context';
2122
import {WindowWithOcwGlobals} from './types';
2223

2324
const windowWithOcwGlobals = window as WindowWithOcwGlobals;
@@ -57,9 +58,13 @@ export function exportRootSpanAfterLoadEvent() {
5758

5859
function exportInitialLoadSpans() {
5960
setTimeout(() => {
61+
const spanContext = getInitialLoadSpanContext();
62+
if (!isSampled(spanContext)) return; // Don't export if not sampled.
63+
6064
const perfEntries = getPerfEntries();
6165

62-
const root = getInitialLoadRootSpan(tracing.tracer, perfEntries);
66+
const root = getInitialLoadRootSpan(
67+
tracing.tracer, perfEntries, spanContext.spanId, spanContext.traceId);
6368

6469
clearPerfEntries();
6570
// Notify that the span has ended to trigger export.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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 {randomSpanId, randomTraceId, SpanContext} from '@opencensus/web-core';
18+
import {traceParentToSpanContext} from '@opencensus/web-propagation-tracecontext';
19+
20+
import {WindowWithOcwGlobals} from './types';
21+
22+
const windowWithOcwGlobals = window as WindowWithOcwGlobals;
23+
24+
/**
25+
* Gets a span context for the initial page load from the `window.traceparent`,
26+
* or generates a new random span context if it is missing. For now the new
27+
* random span context generated if `window.traceparent` is missing is always
28+
* marked sampled.
29+
*/
30+
export function getInitialLoadSpanContext(): SpanContext {
31+
if (!windowWithOcwGlobals.traceparent) return alwaysSampledSpanContext();
32+
const spanContext =
33+
traceParentToSpanContext(windowWithOcwGlobals.traceparent);
34+
if (!spanContext) {
35+
console.log(`Invalid traceparent: ${windowWithOcwGlobals.traceparent}`);
36+
return alwaysSampledSpanContext();
37+
}
38+
return spanContext;
39+
}
40+
41+
function alwaysSampledSpanContext() {
42+
return {
43+
traceId: randomTraceId(),
44+
spanId: randomSpanId(),
45+
options: 0x1, // Sampled
46+
};
47+
}

packages/opencensus-web-all/test/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@
1515
*/
1616

1717
import './test-export-initial-load';
18+
import './test-initial-load-context';

packages/opencensus-web-all/test/test-export-initial-load.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,48 @@ import {WindowWithOcwGlobals} from '../src/types';
1919

2020
const windowWithOcwGlobals = window as WindowWithOcwGlobals;
2121

22+
/**
23+
* Converts bytes in a hexadecimal encoded string to base64 encoded string.
24+
* This is needed because the JSON proto formatting that the OC agent expects
25+
* (via grpc-gateway) formats trace and span IDs in base64. This helper function
26+
* allows matching trace/span IDs that are sent in the XHR body to the agent.
27+
*/
28+
function hexToBase64(hexString: string): string {
29+
const match = hexString.match(/\w{2}/g);
30+
if (!match) return '';
31+
return window.btoa(
32+
match
33+
.map(
34+
(hexByteChars) =>
35+
// tslint:disable-next-line:ban Needed to parse hexadecimal.
36+
String.fromCharCode(parseInt(hexByteChars, 16)))
37+
.join(''));
38+
}
39+
2240
describe('exportRootSpanAfterLoadEvent', () => {
2341
let realOcwAgent: string|undefined;
42+
let realTraceparent: string|undefined;
43+
let sendSpy: jasmine.Spy;
2444
beforeEach(() => {
2545
jasmine.clock().install();
2646
spyOn(XMLHttpRequest.prototype, 'open');
27-
spyOn(XMLHttpRequest.prototype, 'send');
47+
sendSpy = spyOn(XMLHttpRequest.prototype, 'send');
2848
spyOn(XMLHttpRequest.prototype, 'setRequestHeader');
2949
realOcwAgent = windowWithOcwGlobals.ocwAgent;
50+
realTraceparent = windowWithOcwGlobals.traceparent;
3051
});
3152
afterEach(() => {
3253
jasmine.clock().uninstall();
3354
windowWithOcwGlobals.ocwAgent = realOcwAgent;
55+
windowWithOcwGlobals.traceparent = realTraceparent;
3456
});
3557

3658
it('exports spans to agent if agent is configured', () => {
3759
windowWithOcwGlobals.ocwAgent = 'http://agent';
60+
windowWithOcwGlobals.traceparent = undefined;
61+
3862
exportRootSpanAfterLoadEvent();
63+
3964
jasmine.clock().tick(300000);
4065
expect(XMLHttpRequest.prototype.open)
4166
.toHaveBeenCalledWith('POST', 'http://agent/v1/trace');
@@ -44,7 +69,40 @@ describe('exportRootSpanAfterLoadEvent', () => {
4469

4570
it('does not export if agent not configured', () => {
4671
windowWithOcwGlobals.ocwAgent = undefined;
72+
windowWithOcwGlobals.traceparent = undefined;
73+
4774
exportRootSpanAfterLoadEvent();
75+
76+
jasmine.clock().tick(300000);
77+
expect(XMLHttpRequest.prototype.open).not.toHaveBeenCalled();
78+
expect(XMLHttpRequest.prototype.send).not.toHaveBeenCalled();
79+
});
80+
81+
it('uses trace and span ID from window.traceparent if specified', () => {
82+
windowWithOcwGlobals.ocwAgent = 'http://agent';
83+
const traceId = '0af7651916cd43dd8448eb211c80319c';
84+
const spanId = 'b7ad6b7169203331';
85+
windowWithOcwGlobals.traceparent = `00-${traceId}-${spanId}-01`;
86+
87+
exportRootSpanAfterLoadEvent();
88+
89+
jasmine.clock().tick(300000);
90+
expect(XMLHttpRequest.prototype.open)
91+
.toHaveBeenCalledWith('POST', 'http://agent/v1/trace');
92+
expect(XMLHttpRequest.prototype.send).toHaveBeenCalledTimes(1);
93+
// Check that trace and span ID from `window.traceparent` are in body sent.
94+
const sendBody = sendSpy.calls.argsFor(0)[0];
95+
expect(sendBody).toContain(hexToBase64(traceId));
96+
expect(sendBody).toContain(hexToBase64(traceId));
97+
});
98+
99+
it('does not export spans if traceparent sampling hint not set', () => {
100+
windowWithOcwGlobals.ocwAgent = 'http://agent';
101+
windowWithOcwGlobals.traceparent =
102+
'00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-00';
103+
104+
exportRootSpanAfterLoadEvent();
105+
48106
jasmine.clock().tick(300000);
49107
expect(XMLHttpRequest.prototype.open).not.toHaveBeenCalled();
50108
expect(XMLHttpRequest.prototype.send).not.toHaveBeenCalled();
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 {getInitialLoadSpanContext} from '../src/initial-load-context';
18+
import {WindowWithOcwGlobals} from '../src/types';
19+
20+
const windowWithOcwGlobals = window as WindowWithOcwGlobals;
21+
22+
const SPAN_ID_REGEX = /[0-9a-f]{16}/;
23+
const TRACE_ID_REGEX = /[0-9a-f]{32}/;
24+
25+
describe('getInitialLoadSpanContext', () => {
26+
let realTraceparent: string|undefined;
27+
beforeEach(() => {
28+
realTraceparent = windowWithOcwGlobals.traceparent;
29+
});
30+
afterEach(() => {
31+
windowWithOcwGlobals.traceparent = realTraceparent;
32+
});
33+
34+
it('sets trace and span ID from global `traceparent` when specified', () => {
35+
windowWithOcwGlobals.traceparent =
36+
`00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-00`;
37+
expect(getInitialLoadSpanContext()).toEqual({
38+
traceId: '0af7651916cd43dd8448eb211c80319c',
39+
spanId: 'b7ad6b7169203331',
40+
options: 0,
41+
});
42+
});
43+
44+
it('generates a new random span context if `traceparent` unspecified', () => {
45+
windowWithOcwGlobals.traceparent = undefined;
46+
const spanContext = getInitialLoadSpanContext();
47+
expect(spanContext.traceId).toMatch(TRACE_ID_REGEX);
48+
expect(spanContext.spanId).toMatch(SPAN_ID_REGEX);
49+
expect(spanContext.options).toBe(1);
50+
});
51+
52+
it('generates a new random span context if `traceparent` is invalid', () => {
53+
windowWithOcwGlobals.traceparent = 'invalid trace parent header!';
54+
const spanContext = getInitialLoadSpanContext();
55+
expect(spanContext.traceId).toMatch(TRACE_ID_REGEX);
56+
expect(spanContext.spanId).toMatch(SPAN_ID_REGEX);
57+
expect(spanContext.options).toBe(1);
58+
});
59+
});

packages/opencensus-web-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export {RootSpan} from './trace/model/root-span';
1919
export {Span} from './trace/model/span';
2020
export {Tracer} from './trace/model/tracer';
2121
export {Tracing} from './trace/model/tracing';
22+
export * from './trace/model/util';
2223
export * from './trace/model/attribute-keys';
2324

2425
// Re-export types this uses from @opencensus/web-types.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 {SpanContext} from '@opencensus/web-types';
18+
19+
const IS_SAMPLED_BIT = 0x1;
20+
21+
/** Returns whether sampling hint bit of the span context `options` is set. */
22+
export function isSampled(spanContext: SpanContext) {
23+
const options = spanContext.options;
24+
if (!options) return false;
25+
return !!(options & IS_SAMPLED_BIT);
26+
}

packages/opencensus-web-core/test/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import './test-id-util';
2121
import './test-root-span';
2222
import './test-span';
2323
import './test-time-util';
24+
import './test-trace-model-util';
2425
import './test-tracer';
2526
import './test-tracing';
2627
import './test-url-util';
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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 {isSampled} from '../src/trace/model/util';
18+
19+
describe('isSampled', () => {
20+
it('returns true if sampling bit is set', () => {
21+
expect(isSampled({traceId: '', spanId: '', options: 1})).toBe(true);
22+
expect(isSampled({traceId: '', spanId: '', options: 5})).toBe(true);
23+
});
24+
it('returns false if sampling bit is not set', () => {
25+
expect(isSampled({traceId: '', spanId: '', options: 0})).toBe(false);
26+
expect(isSampled({traceId: '', spanId: '', options: 4})).toBe(false);
27+
});
28+
});

0 commit comments

Comments
 (0)