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

Commit a0d573f

Browse files
crdgonzalezcadraffensperger
authored andcommitted
Refactor Tracer to check presence of Zone global variable (#103)
1 parent 484fbe7 commit a0d573f

4 files changed

Lines changed: 177 additions & 68 deletions

File tree

packages/opencensus-web-core/src/trace/model/tracer.ts

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,64 +19,87 @@ import { RootSpan } from './root-span';
1919
import { Span } from './span';
2020
import { TracerBase } from './tracer-base';
2121
import { randomTraceId } from '../../common/id-util';
22+
import { WindowWithZone } from './types';
2223

2324
/** Tracer manages the current root span and trace header propagation. */
2425
export class Tracer extends TracerBase implements webTypes.Tracer {
26+
private static singletonInstance: Tracer;
27+
28+
/** Gets the tracer instance. */
29+
static get instance(): Tracer {
30+
return this.singletonInstance || (this.singletonInstance = new this());
31+
}
32+
33+
// Variable to store current root span in case the Zone global variable is not present.
34+
// For that case we only need to store only one current root span.
35+
private currentRootSpanNoZone = new RootSpan(this);
36+
2537
/**
2638
* Gets the current root span associated to Zone.current.
27-
* If the current zone does not have a root span (e.g. root zone),
28-
* create a new root.
39+
* If the current zone does not have a root span (e.g. root zone) or
40+
* `Zone` is not present return the value store in the unique root span.
2941
*/
3042
get currentRootSpan(): Span {
31-
if (Zone.current.get('data')) {
43+
if (this.isZonePresent() && Zone.current.get('data')) {
3244
return Zone.current.get('data').rootSpan;
3345
}
34-
return new RootSpan(this);
46+
return this.currentRootSpanNoZone;
3547
}
3648

3749
/**
3850
* Sets the current root span to the current Zone.
3951
* If the current zone does not have a 'data' property (e.g. root zone)
40-
* do not set the root span.
52+
* or `Zone` is not present, just assign the root span to a variable.
4153
*/
4254
set currentRootSpan(root: Span) {
43-
if (Zone.current.get('data')) {
55+
if (this.isZonePresent() && Zone.current.get('data')) {
4456
Zone.current.get('data')['rootSpan'] = root;
57+
} else {
58+
this.currentRootSpanNoZone = root as RootSpan;
4559
}
4660
}
4761

4862
/**
49-
* Creates a new Zone and start a new RootSpan to `currentRootSpan` associating
50-
* the new RootSpan to the new Zone. Thus, there might be several root spans
51-
* at the same time.
63+
* Creates a new Zone (in case `Zone` global variable is present) and start
64+
* a new RootSpan to `currentRootSpan` associating the new RootSpan to the
65+
* new Zone. Thus, there might be several root spans at the same time.
66+
* If `Zone` is not present, just create the root span and store it in the current
67+
* root span.
5268
* Currently no sampling decisions are propagated or made here.
5369
* @param options Options for tracer instance
5470
* @param fn Callback function
5571
* @returns The callback return
5672
*/
5773
startRootSpan<T>(options: webTypes.TraceOptions, fn: (root: Span) => T): T {
58-
let traceId = randomTraceId();
59-
if (options.spanContext && options.spanContext.traceId) {
60-
traceId = options.spanContext.traceId;
61-
}
62-
// Create the new zone.
63-
const zoneSpec = {
64-
name: traceId,
65-
properties: {
66-
data: {
67-
isTracingZone: true,
68-
traceId,
74+
if (this.isZonePresent()) {
75+
let traceId = randomTraceId();
76+
if (options.spanContext && options.spanContext.traceId) {
77+
traceId = options.spanContext.traceId;
78+
}
79+
// Create the new zone.
80+
const zoneSpec = {
81+
name: traceId,
82+
properties: {
83+
data: {
84+
isTracingZone: true,
85+
traceId,
86+
},
6987
},
70-
},
71-
};
88+
};
7289

73-
const newZone = Zone.current.fork(zoneSpec);
74-
return newZone.run(() => {
75-
super.startRootSpan(options, root => {
76-
// Set the currentRootSpan to the new created root span.
77-
this.currentRootSpan = root;
78-
return fn(root);
90+
const newZone = Zone.current.fork(zoneSpec);
91+
return newZone.run(() => {
92+
super.startRootSpan(options, root => {
93+
// Set the currentRootSpan to the new created root span.
94+
this.currentRootSpan = root;
95+
return fn(root);
96+
});
7997
});
98+
}
99+
return super.startRootSpan(options, root => {
100+
// Set the currentRootSpan to the new created root span.
101+
this.currentRootSpan = root;
102+
return fn(root);
80103
});
81104
}
82105

@@ -108,4 +131,8 @@ export class Tracer extends TracerBase implements webTypes.Tracer {
108131

109132
/** Binds trace context to NodeJS event emitter. No-op for opencensus-web. */
110133
wrapEmitter(emitter: webTypes.NodeJsEventEmitter) {}
134+
135+
isZonePresent(): boolean {
136+
return !!(window as WindowWithZone).Zone;
137+
}
111138
}

packages/opencensus-web-core/src/trace/model/tracing.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const NOOP_EXPORTER = new NoopExporter();
2323
/** Main interface for tracing. */
2424
export class Tracing implements webTypes.Tracing {
2525
/** Object responsible for managing a trace. */
26-
readonly tracer = new Tracer();
26+
readonly tracer = Tracer.instance;
2727

2828
/** Service to send collected traces to. */
2929
exporter = NOOP_EXPORTER;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
/** Interface to add `Zone` variable, used to check the `Zone` presence. */
18+
export interface WindowWithZone extends Window {
19+
Zone?: Function;
20+
}

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

Lines changed: 101 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@
1616

1717
import * as webTypes from '@opencensus/web-types';
1818
import { Tracer } from '../src/trace/model/tracer';
19+
import { WindowWithZone } from '../src/trace/model/types';
1920

2021
describe('Tracer', () => {
2122
let tracer: Tracer;
2223
let listener: webTypes.SpanEventListener;
2324
const options = { name: 'test' };
25+
let realZone: Function | undefined;
26+
const windowWithZone = window as WindowWithZone;
2427

2528
beforeEach(() => {
2629
tracer = new Tracer();
@@ -31,37 +34,113 @@ describe('Tracer', () => {
3134
tracer.eventListeners = [listener];
3235
});
3336

34-
/** Should get/set the current RootSpan from tracer instance */
35-
describe('get/set currentRootSpan()', () => {
36-
it('should get the current RootSpan from tracer instance', () => {
37-
tracer.startRootSpan(options, root => {
38-
expect(root).toBeTruthy();
39-
expect(root).toBe(tracer.currentRootSpan);
37+
describe('Zone is not present', () => {
38+
beforeEach(() => {
39+
realZone = windowWithZone.Zone;
40+
windowWithZone.Zone = undefined;
41+
});
42+
43+
afterEach(() => {
44+
windowWithZone.Zone = realZone;
45+
});
46+
47+
it('isZonePresent() is false', () => {
48+
expect(tracer.isZonePresent()).toBeFalsy();
49+
});
50+
51+
describe('get/set currentRootSpan()', () => {
52+
// As `Zone` is not present, the current root span is accesible outside
53+
// the callback as there is only one current root span.
54+
it('should get the current RootSpan from tracer instance', () => {
55+
const onStartFn = jasmine.createSpy('onStartFn');
56+
tracer.startRootSpan(options, onStartFn);
57+
const onStartRoot = onStartFn.calls.argsFor(0)[0];
58+
expect(onStartRoot).toBe(tracer.currentRootSpan);
59+
});
60+
});
61+
62+
describe('startRootSpan', () => {
63+
it('should create start RootSpan', () => {
64+
const onStartFn = jasmine.createSpy('onStartFn');
65+
const oldRoot = tracer.currentRootSpan;
66+
67+
tracer.startRootSpan(options, onStartFn);
68+
69+
expect(onStartFn).toHaveBeenCalled();
70+
const onStartRoot = onStartFn.calls.argsFor(0)[0];
71+
expect(onStartRoot.name).toBe('test');
72+
expect(onStartRoot).not.toBe(oldRoot);
73+
expect(tracer.currentRootSpan).toBe(onStartRoot);
74+
expect(listener.onStartSpan).toHaveBeenCalledWith(onStartRoot);
75+
});
76+
});
77+
78+
describe('startChildSpan', () => {
79+
it('starts a child span of the current root span', () => {
80+
spyOn(tracer.currentRootSpan, 'startChildSpan');
81+
tracer.startChildSpan({
82+
name: 'child1',
83+
kind: webTypes.SpanKind.CLIENT,
84+
});
85+
expect(tracer.currentRootSpan.startChildSpan).toHaveBeenCalledWith({
86+
childOf: tracer.currentRootSpan,
87+
name: 'child1',
88+
kind: webTypes.SpanKind.CLIENT,
89+
});
4090
});
4191
});
4292
});
4393

44-
describe('startRootSpan', () => {
45-
it('should create a new RootSpan instance', () => {
46-
tracer.startRootSpan(options, rootSpan => {
47-
expect(rootSpan).toBeTruthy();
94+
describe('Zone is present', () => {
95+
/** Should get/set the current RootSpan from tracer instance */
96+
describe('get/set currentRootSpan()', () => {
97+
it('should get the current RootSpan from tracer instance', () => {
98+
tracer.startRootSpan(options, root => {
99+
expect(root).toBeTruthy();
100+
expect(root).toBe(tracer.currentRootSpan);
101+
});
48102
});
49103
});
50-
it('sets current root span', () => {
51-
const oldRoot = tracer.currentRootSpan;
52104

53-
tracer.startRootSpan(options, rootSpan => {
54-
expect(rootSpan.name).toBe('test');
55-
expect(rootSpan).not.toBe(oldRoot);
56-
expect(tracer.currentRootSpan).toBe(rootSpan);
57-
expect(listener.onStartSpan).toHaveBeenCalledWith(rootSpan);
105+
describe('startRootSpan', () => {
106+
it('should create a new RootSpan instance', () => {
107+
tracer.startRootSpan(options, rootSpan => {
108+
expect(rootSpan).toBeTruthy();
109+
});
110+
});
111+
it('sets current root span', () => {
112+
const oldRoot = tracer.currentRootSpan;
113+
114+
tracer.startRootSpan(options, rootSpan => {
115+
expect(rootSpan.name).toBe('test');
116+
expect(rootSpan).not.toBe(oldRoot);
117+
expect(tracer.currentRootSpan).toBe(rootSpan);
118+
expect(listener.onStartSpan).toHaveBeenCalledWith(rootSpan);
119+
});
120+
});
121+
it('should create a new Zone and RootSpan associated to the zone', () => {
122+
tracer.startRootSpan(options, rootSpan => {
123+
expect(rootSpan).toBeTruthy();
124+
expect(Zone.current).not.toBe(Zone.root);
125+
expect(Zone.current.get('data').rootSpan).toBe(rootSpan);
126+
});
58127
});
59128
});
60-
it('should create a new Zone and RootSpan an associated to the zone', () => {
61-
tracer.startRootSpan(options, rootSpan => {
62-
expect(rootSpan).toBeTruthy();
63-
expect(Zone.current).not.toBe(Zone.root);
64-
expect(Zone.current.get('data').rootSpan).toBe(rootSpan);
129+
130+
describe('startChildSpan', () => {
131+
let rootSpanLocal: webTypes.Span;
132+
let span: webTypes.Span;
133+
it('starts a child span of the current root span', () => {
134+
tracer.startRootSpan(options, rootSpan => {
135+
rootSpanLocal = rootSpan;
136+
span = tracer.startChildSpan({
137+
name: 'child1',
138+
kind: webTypes.SpanKind.CLIENT,
139+
});
140+
});
141+
expect(span).toBeTruthy();
142+
expect(rootSpanLocal.numberOfChildren).toBe(1);
143+
expect(rootSpanLocal.spans[0]).toBe(span);
65144
});
66145
});
67146
});
@@ -74,23 +153,6 @@ describe('Tracer', () => {
74153
});
75154
});
76155

77-
describe('startChildSpan', () => {
78-
let rootSpanLocal: webTypes.Span;
79-
let span: webTypes.Span;
80-
it('starts a child span of the current root span', () => {
81-
tracer.startRootSpan(options, rootSpan => {
82-
rootSpanLocal = rootSpan;
83-
span = tracer.startChildSpan({
84-
name: 'child1',
85-
kind: webTypes.SpanKind.CLIENT,
86-
});
87-
});
88-
expect(span).toBeTruthy();
89-
expect(rootSpanLocal.numberOfChildren).toBe(1);
90-
expect(rootSpanLocal.spans[0]).toBe(span);
91-
});
92-
});
93-
94156
describe('wrap', () => {
95157
it('just returns the given function', () => {
96158
const wrapFn = jasmine.createSpy('wrapFn');

0 commit comments

Comments
 (0)