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

Commit

Permalink
Refactor Tracer to check presence of Zone global variable (#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
crdgonzalezca authored and draffensperger committed Jun 24, 2019
1 parent 484fbe7 commit a0d573f
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 68 deletions.
83 changes: 55 additions & 28 deletions packages/opencensus-web-core/src/trace/model/tracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,64 +19,87 @@ import { RootSpan } from './root-span';
import { Span } from './span';
import { TracerBase } from './tracer-base';
import { randomTraceId } from '../../common/id-util';
import { WindowWithZone } from './types';

/** Tracer manages the current root span and trace header propagation. */
export class Tracer extends TracerBase implements webTypes.Tracer {
private static singletonInstance: Tracer;

/** Gets the tracer instance. */
static get instance(): Tracer {
return this.singletonInstance || (this.singletonInstance = new this());
}

// Variable to store current root span in case the Zone global variable is not present.
// For that case we only need to store only one current root span.
private currentRootSpanNoZone = new RootSpan(this);

/**
* Gets the current root span associated to Zone.current.
* If the current zone does not have a root span (e.g. root zone),
* create a new root.
* If the current zone does not have a root span (e.g. root zone) or
* `Zone` is not present return the value store in the unique root span.
*/
get currentRootSpan(): Span {
if (Zone.current.get('data')) {
if (this.isZonePresent() && Zone.current.get('data')) {
return Zone.current.get('data').rootSpan;
}
return new RootSpan(this);
return this.currentRootSpanNoZone;
}

/**
* Sets the current root span to the current Zone.
* If the current zone does not have a 'data' property (e.g. root zone)
* do not set the root span.
* or `Zone` is not present, just assign the root span to a variable.
*/
set currentRootSpan(root: Span) {
if (Zone.current.get('data')) {
if (this.isZonePresent() && Zone.current.get('data')) {
Zone.current.get('data')['rootSpan'] = root;
} else {
this.currentRootSpanNoZone = root as RootSpan;
}
}

/**
* Creates a new Zone and start a new RootSpan to `currentRootSpan` associating
* the new RootSpan to the new Zone. Thus, there might be several root spans
* at the same time.
* Creates a new Zone (in case `Zone` global variable is present) and start
* a new RootSpan to `currentRootSpan` associating the new RootSpan to the
* new Zone. Thus, there might be several root spans at the same time.
* If `Zone` is not present, just create the root span and store it in the current
* root span.
* Currently no sampling decisions are propagated or made here.
* @param options Options for tracer instance
* @param fn Callback function
* @returns The callback return
*/
startRootSpan<T>(options: webTypes.TraceOptions, fn: (root: Span) => T): T {
let traceId = randomTraceId();
if (options.spanContext && options.spanContext.traceId) {
traceId = options.spanContext.traceId;
}
// Create the new zone.
const zoneSpec = {
name: traceId,
properties: {
data: {
isTracingZone: true,
traceId,
if (this.isZonePresent()) {
let traceId = randomTraceId();
if (options.spanContext && options.spanContext.traceId) {
traceId = options.spanContext.traceId;
}
// Create the new zone.
const zoneSpec = {
name: traceId,
properties: {
data: {
isTracingZone: true,
traceId,
},
},
},
};
};

const newZone = Zone.current.fork(zoneSpec);
return newZone.run(() => {
super.startRootSpan(options, root => {
// Set the currentRootSpan to the new created root span.
this.currentRootSpan = root;
return fn(root);
const newZone = Zone.current.fork(zoneSpec);
return newZone.run(() => {
super.startRootSpan(options, root => {
// Set the currentRootSpan to the new created root span.
this.currentRootSpan = root;
return fn(root);
});
});
}
return super.startRootSpan(options, root => {
// Set the currentRootSpan to the new created root span.
this.currentRootSpan = root;
return fn(root);
});
}

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

/** Binds trace context to NodeJS event emitter. No-op for opencensus-web. */
wrapEmitter(emitter: webTypes.NodeJsEventEmitter) {}

isZonePresent(): boolean {
return !!(window as WindowWithZone).Zone;
}
}
2 changes: 1 addition & 1 deletion packages/opencensus-web-core/src/trace/model/tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const NOOP_EXPORTER = new NoopExporter();
/** Main interface for tracing. */
export class Tracing implements webTypes.Tracing {
/** Object responsible for managing a trace. */
readonly tracer = new Tracer();
readonly tracer = Tracer.instance;

/** Service to send collected traces to. */
exporter = NOOP_EXPORTER;
Expand Down
20 changes: 20 additions & 0 deletions packages/opencensus-web-core/src/trace/model/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* 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.
*/

/** Interface to add `Zone` variable, used to check the `Zone` presence. */
export interface WindowWithZone extends Window {
Zone?: Function;
}
140 changes: 101 additions & 39 deletions packages/opencensus-web-core/test/test-tracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@

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

describe('Tracer', () => {
let tracer: Tracer;
let listener: webTypes.SpanEventListener;
const options = { name: 'test' };
let realZone: Function | undefined;
const windowWithZone = window as WindowWithZone;

beforeEach(() => {
tracer = new Tracer();
Expand All @@ -31,37 +34,113 @@ describe('Tracer', () => {
tracer.eventListeners = [listener];
});

/** Should get/set the current RootSpan from tracer instance */
describe('get/set currentRootSpan()', () => {
it('should get the current RootSpan from tracer instance', () => {
tracer.startRootSpan(options, root => {
expect(root).toBeTruthy();
expect(root).toBe(tracer.currentRootSpan);
describe('Zone is not present', () => {
beforeEach(() => {
realZone = windowWithZone.Zone;
windowWithZone.Zone = undefined;
});

afterEach(() => {
windowWithZone.Zone = realZone;
});

it('isZonePresent() is false', () => {
expect(tracer.isZonePresent()).toBeFalsy();
});

describe('get/set currentRootSpan()', () => {
// As `Zone` is not present, the current root span is accesible outside
// the callback as there is only one current root span.
it('should get the current RootSpan from tracer instance', () => {
const onStartFn = jasmine.createSpy('onStartFn');
tracer.startRootSpan(options, onStartFn);
const onStartRoot = onStartFn.calls.argsFor(0)[0];
expect(onStartRoot).toBe(tracer.currentRootSpan);
});
});

describe('startRootSpan', () => {
it('should create start RootSpan', () => {
const onStartFn = jasmine.createSpy('onStartFn');
const oldRoot = tracer.currentRootSpan;

tracer.startRootSpan(options, onStartFn);

expect(onStartFn).toHaveBeenCalled();
const onStartRoot = onStartFn.calls.argsFor(0)[0];
expect(onStartRoot.name).toBe('test');
expect(onStartRoot).not.toBe(oldRoot);
expect(tracer.currentRootSpan).toBe(onStartRoot);
expect(listener.onStartSpan).toHaveBeenCalledWith(onStartRoot);
});
});

describe('startChildSpan', () => {
it('starts a child span of the current root span', () => {
spyOn(tracer.currentRootSpan, 'startChildSpan');
tracer.startChildSpan({
name: 'child1',
kind: webTypes.SpanKind.CLIENT,
});
expect(tracer.currentRootSpan.startChildSpan).toHaveBeenCalledWith({
childOf: tracer.currentRootSpan,
name: 'child1',
kind: webTypes.SpanKind.CLIENT,
});
});
});
});

describe('startRootSpan', () => {
it('should create a new RootSpan instance', () => {
tracer.startRootSpan(options, rootSpan => {
expect(rootSpan).toBeTruthy();
describe('Zone is present', () => {
/** Should get/set the current RootSpan from tracer instance */
describe('get/set currentRootSpan()', () => {
it('should get the current RootSpan from tracer instance', () => {
tracer.startRootSpan(options, root => {
expect(root).toBeTruthy();
expect(root).toBe(tracer.currentRootSpan);
});
});
});
it('sets current root span', () => {
const oldRoot = tracer.currentRootSpan;

tracer.startRootSpan(options, rootSpan => {
expect(rootSpan.name).toBe('test');
expect(rootSpan).not.toBe(oldRoot);
expect(tracer.currentRootSpan).toBe(rootSpan);
expect(listener.onStartSpan).toHaveBeenCalledWith(rootSpan);
describe('startRootSpan', () => {
it('should create a new RootSpan instance', () => {
tracer.startRootSpan(options, rootSpan => {
expect(rootSpan).toBeTruthy();
});
});
it('sets current root span', () => {
const oldRoot = tracer.currentRootSpan;

tracer.startRootSpan(options, rootSpan => {
expect(rootSpan.name).toBe('test');
expect(rootSpan).not.toBe(oldRoot);
expect(tracer.currentRootSpan).toBe(rootSpan);
expect(listener.onStartSpan).toHaveBeenCalledWith(rootSpan);
});
});
it('should create a new Zone and RootSpan associated to the zone', () => {
tracer.startRootSpan(options, rootSpan => {
expect(rootSpan).toBeTruthy();
expect(Zone.current).not.toBe(Zone.root);
expect(Zone.current.get('data').rootSpan).toBe(rootSpan);
});
});
});
it('should create a new Zone and RootSpan an associated to the zone', () => {
tracer.startRootSpan(options, rootSpan => {
expect(rootSpan).toBeTruthy();
expect(Zone.current).not.toBe(Zone.root);
expect(Zone.current.get('data').rootSpan).toBe(rootSpan);

describe('startChildSpan', () => {
let rootSpanLocal: webTypes.Span;
let span: webTypes.Span;
it('starts a child span of the current root span', () => {
tracer.startRootSpan(options, rootSpan => {
rootSpanLocal = rootSpan;
span = tracer.startChildSpan({
name: 'child1',
kind: webTypes.SpanKind.CLIENT,
});
});
expect(span).toBeTruthy();
expect(rootSpanLocal.numberOfChildren).toBe(1);
expect(rootSpanLocal.spans[0]).toBe(span);
});
});
});
Expand All @@ -74,23 +153,6 @@ describe('Tracer', () => {
});
});

describe('startChildSpan', () => {
let rootSpanLocal: webTypes.Span;
let span: webTypes.Span;
it('starts a child span of the current root span', () => {
tracer.startRootSpan(options, rootSpan => {
rootSpanLocal = rootSpan;
span = tracer.startChildSpan({
name: 'child1',
kind: webTypes.SpanKind.CLIENT,
});
});
expect(span).toBeTruthy();
expect(rootSpanLocal.numberOfChildren).toBe(1);
expect(rootSpanLocal.spans[0]).toBe(span);
});
});

describe('wrap', () => {
it('just returns the given function', () => {
const wrapFn = jasmine.createSpy('wrapFn');
Expand Down

0 comments on commit a0d573f

Please sign in to comment.