From 1140dd315b05e96dbc801337a6e2f4125b397929 Mon Sep 17 00:00:00 2001 From: Mayur Kale Date: Tue, 5 Mar 2019 18:42:21 -0800 Subject: [PATCH] Add a NoRecord RootSpan and Span (#389) * Add a no-op RootSpan and Span This will allow user to instrument a library without enabling sampling (i.e. using ```NeverSampler```) or using ```ProbabilitySampler``` (Sampler that samples a given fraction of traces). * Rename: NoopSpan -> NoRecordSpan, NoopRootSpan -> NoRecordRootSpan --- CHANGELOG.md | 1 + .../model/no-record/no-record-root-span.ts | 116 ++++++++++++++ .../model/no-record/no-record-span-base.ts | 149 ++++++++++++++++++ .../trace/model/no-record/no-record-span.ts | 52 ++++++ .../opencensus-core/src/trace/model/tracer.ts | 10 +- .../test/test-no-record-root-span.ts | 36 +++++ .../test/test-no-record-span.ts | 36 +++++ packages/opencensus-core/test/test-tracer.ts | 11 +- 8 files changed, 401 insertions(+), 10 deletions(-) create mode 100644 packages/opencensus-core/src/trace/model/no-record/no-record-root-span.ts create mode 100644 packages/opencensus-core/src/trace/model/no-record/no-record-span-base.ts create mode 100644 packages/opencensus-core/src/trace/model/no-record/no-record-span.ts create mode 100644 packages/opencensus-core/test/test-no-record-root-span.ts create mode 100644 packages/opencensus-core/test/test-no-record-span.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 9780cf21c..ee58b28bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file. - Add support for recording HTTP stats. - Enforce `--strictNullChecks` and `--noUnusedLocals` Compiler Options on [opencensus-instrumentation-http], [opencensus-instrumentation-grpc] and [opencensus-propagation-tracecontext] packages. - Enforce `--strictNullChecks` and `--noUnusedLocals` Compiler Options on [opencensus-exporter-zipkin] packages. +- Add NoRecordRootSpan, NoRecordSpan and NoRecordSpanBase. ## 0.0.9 - 2019-02-12 - Add Metrics API. diff --git a/packages/opencensus-core/src/trace/model/no-record/no-record-root-span.ts b/packages/opencensus-core/src/trace/model/no-record/no-record-root-span.ts new file mode 100644 index 000000000..9f47a0e93 --- /dev/null +++ b/packages/opencensus-core/src/trace/model/no-record/no-record-root-span.ts @@ -0,0 +1,116 @@ +/** + * 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 * as uuid from 'uuid'; +import * as logger from '../../../common/console-logger'; +import * as types from '../types'; +import {NoRecordSpan} from './no-record-span'; +import {NoRecordSpanBase} from './no-record-span-base'; + +/** Implementation for the RootSpan class that does not record trace events. */ +export class NoRecordRootSpan extends NoRecordSpanBase implements + types.RootSpan { + /** A tracer object */ + private tracer: types.Tracer; + /** Its trace ID. */ + private traceIdLocal: string; + /** Its trace state. */ + private traceStateLocal: types.TraceState; + /** set isRootSpan = true */ + readonly isRootSpan = true; + + /** + * Constructs a new NoRecordRootSpanImpl instance. + * @param tracer A tracer object. + * @param context A trace options object to build the no-record root span. + */ + constructor(tracer: types.Tracer, context?: types.TraceOptions) { + super(); + this.tracer = tracer; + this.traceIdLocal = + context && context.spanContext && context.spanContext.traceId ? + context.spanContext.traceId : + (uuid.v4().split('-').join('')); + this.name = context && context.name ? context.name : 'undefined'; + if (context && context.spanContext) { + this.parentSpanId = context.spanContext.spanId || ''; + this.traceStateLocal = context.spanContext.traceState; + } + this.kind = + context && context.kind ? context.kind : types.SpanKind.UNSPECIFIED; + this.logger = this.tracer.logger || logger.logger(); + } + + /** No-op implementation of this method. */ + get spans(): types.Span[] { + return []; + } + + /** No-op implementation of this method. */ + get traceId(): string { + return this.traceIdLocal; + } + + /** No-op implementation of this method. */ + get traceState(): types.TraceState { + return this.traceStateLocal; + } + + /** No-op implementation of this method. */ + get numberOfChildren(): number { + return 0; + } + + /** No-op implementation of this method. */ + start() { + super.start(); + } + + /** No-op implementation of this method. */ + end() { + super.end(); + } + + /** + * Starts a new child span in the noop root span. + * @param name Span name. + * @param kind Span kind. + * @param parentSpanId Span parent ID. + */ + startChildSpan( + nameOrOptions?: string|types.SpanOptions, kind?: types.SpanKind, + parentSpanId?: string): types.Span { + const newSpan = new NoRecordSpan(this); + let spanName; + let spanKind; + if (typeof nameOrOptions === 'object') { + spanName = nameOrOptions.name; + spanKind = nameOrOptions.kind; + } else { + spanName = nameOrOptions; + spanKind = kind; + } + + if (spanName) { + newSpan.name = spanName; + } + if (spanKind) { + newSpan.kind = spanKind; + } + newSpan.start(); + return newSpan; + } +} diff --git a/packages/opencensus-core/src/trace/model/no-record/no-record-span-base.ts b/packages/opencensus-core/src/trace/model/no-record/no-record-span-base.ts new file mode 100644 index 000000000..ca1d36765 --- /dev/null +++ b/packages/opencensus-core/src/trace/model/no-record/no-record-span-base.ts @@ -0,0 +1,149 @@ +/** + * 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 {Logger} from '../../../common/types'; +import {randomSpanId} from '../../../internal/util'; +import * as configTypes from '../../config/types'; +import * as types from '../types'; + +const STATUS_OK = { + code: types.CanonicalCode.OK +}; + +/** Implementation for the SpanBase class that does not record trace events. */ +export abstract class NoRecordSpanBase implements types.Span { + /** Indicates if this span was started */ + private startedLocal = false; + /** Indicates if this span was ended */ + private endedLocal = false; + /** Indicates if this span was forced to end */ + // @ts-ignore + private truncated = false; + /** The Span ID of this span */ + readonly id: string; + /** An object to log information to */ + logger: Logger; + /** A set of attributes, each in the format [KEY]:[VALUE] */ + attributes: types.Attributes = {}; + /** A text annotation with a set of attributes. */ + annotations: types.Annotation[] = []; + /** An event describing a message sent/received between Spans */ + messageEvents: types.MessageEvent[] = []; + /** Pointers from the current span to another span */ + links: types.Link[] = []; + /** If the parent span is in another process. */ + remoteParent: boolean; + /** The span ID of this span's parent. If it's a root span, must be empty */ + parentSpanId: string = null; + /** The resource name of the span */ + name: string = null; + /** Kind of span. */ + kind: types.SpanKind = types.SpanKind.UNSPECIFIED; + /** A final status for this span */ + status: types.Status = STATUS_OK; + /** set isRootSpan */ + abstract get isRootSpan(): boolean; + /** Trace Parameters */ + activeTraceParams: configTypes.TraceParams; + + /** The number of dropped attributes. */ + droppedAttributesCount = 0; + /** The number of dropped links. */ + droppedLinksCount = 0; + /** The number of dropped annotations. */ + droppedAnnotationsCount = 0; + /** The number of dropped message events. */ + droppedMessageEventsCount = 0; + + /** Constructs a new SpanBaseModel instance. */ + constructor() { + this.id = randomSpanId(); + } + + /** Gets the trace ID. */ + abstract get traceId(): string; + + /** Gets the trace state */ + abstract get traceState(): types.TraceState; + + /** Indicates if span was started. */ + get started(): boolean { + return this.startedLocal; + } + + /** Indicates if span was ended. */ + get ended(): boolean { + return this.endedLocal; + } + + /** No-op implementation of this method. */ + get startTime(): Date { + return new Date(); + } + + /** No-op implementation of this method. */ + get endTime(): Date { + return new Date(); + } + + /** Gives the TraceContext of the span. */ + get spanContext(): types.SpanContext { + return { + traceId: this.traceId, + spanId: this.id, + options: 0, + traceState: this.traceState + }; + } + + /** No-op implementation of this method. */ + get duration(): number { + return 0; + } + + /** No-op implementation of this method. */ + addAttribute(key: string, value: string|number|boolean) {} + + /** No-op implementation of this method. */ + addAnnotation( + description: string, attributes?: types.Attributes, timestamp = 0) {} + + /** No-op implementation of this method. */ + addLink( + traceId: string, spanId: string, type: types.LinkType, + attributes?: types.Attributes) {} + + /** No-op implementation of this method. */ + addMessageEvent( + type: types.MessageEventType, id: string, timestamp = 0, + uncompressedSize?: number, compressedSize?: number) {} + + /** No-op implementation of this method. */ + setStatus(code: types.CanonicalCode, message?: string) {} + + /** No-op implementation of this method. */ + start() { + this.startedLocal = true; + } + + /** No-op implementation of this method. */ + end(): void { + this.startedLocal = false; + this.endedLocal = true; + } + + /** No-op implementation of this method. */ + truncate() {} +} diff --git a/packages/opencensus-core/src/trace/model/no-record/no-record-span.ts b/packages/opencensus-core/src/trace/model/no-record/no-record-span.ts new file mode 100644 index 000000000..15f9bb29e --- /dev/null +++ b/packages/opencensus-core/src/trace/model/no-record/no-record-span.ts @@ -0,0 +1,52 @@ +/** + * 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 * as logger from '../../../common/console-logger'; +import * as types from '../types'; +import {NoRecordSpanBase} from './no-record-span-base'; + +/** Implementation for the Span class that does not record trace events. */ +export class NoRecordSpan extends NoRecordSpanBase implements types.Span { + private root: types.RootSpan; + /** set isRootSpan = false */ + readonly isRootSpan = false; + + /** + * Constructs a new NoRecordSpanImpl instance. + * @param root + */ + constructor(root: types.RootSpan) { + super(); + this.root = root; + this.logger = this.root.logger || logger.logger(); + this.parentSpanId = root.id; + } + + /** Gets trace id of no-record span. */ + get traceId(): string { + return this.root.traceId; + } + + get traceState(): string { + return this.root.traceState; + } + + /** No-op implementation of this method. */ + start() {} + + /** No-op implementation of this method. */ + end() {} +} diff --git a/packages/opencensus-core/src/trace/model/tracer.ts b/packages/opencensus-core/src/trace/model/tracer.ts index a7bf05bdd..ce6c4fc71 100644 --- a/packages/opencensus-core/src/trace/model/tracer.ts +++ b/packages/opencensus-core/src/trace/model/tracer.ts @@ -23,6 +23,7 @@ import {Propagation} from '../propagation/types'; import {SamplerBuilder, TraceParamsBuilder} from '../sampler/sampler'; import * as samplerTypes from '../sampler/types'; +import {NoRecordRootSpan} from './no-record/no-record-root-span'; import {RootSpan} from './root-span'; import * as types from './types'; @@ -124,11 +125,10 @@ export class CoreTracer implements types.Tracer { startRootSpan( options: types.TraceOptions, fn: (root: types.RootSpan) => T): T { return this.contextManager.runAndReturn((root) => { - let newRoot = null; if (this.active) { let propagatedSample = null; - // if there is a context propagation, keep the decistion + // if there is a context propagation, keep the decision if (options && options.spanContext) { if (options.spanContext.options) { propagatedSample = @@ -148,12 +148,14 @@ export class CoreTracer implements types.Tracer { if (sampleDecision) { this.currentRootSpan = aRoot; aRoot.start(); - newRoot = aRoot; + return fn(aRoot); } } else { this.logger.debug('Tracer is inactive, can\'t start new RootSpan'); } - return fn(newRoot); + const noRecordRootSpan = new NoRecordRootSpan(this, options); + this.currentRootSpan = noRecordRootSpan; + return fn(noRecordRootSpan); }); } diff --git a/packages/opencensus-core/test/test-no-record-root-span.ts b/packages/opencensus-core/test/test-no-record-root-span.ts new file mode 100644 index 000000000..f96d5ea13 --- /dev/null +++ b/packages/opencensus-core/test/test-no-record-root-span.ts @@ -0,0 +1,36 @@ +/** + * 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 {CanonicalCode, CoreTracer, LinkType, MessageEventType} from '../src'; +import {NoRecordRootSpan} from '../src/trace/model/no-record/no-record-root-span'; + +const tracer = new CoreTracer(); + +describe('NoRecordRootSpan()', () => { + it('do not crash', () => { + const noRecordRootSpan = new NoRecordRootSpan(tracer); + noRecordRootSpan.addAnnotation('MyAnnotation'); + noRecordRootSpan.addAnnotation('MyAnnotation', {myString: 'bar'}); + noRecordRootSpan.addAnnotation( + 'MyAnnotation', {myString: 'bar', myNumber: 123, myBoolean: true}); + noRecordRootSpan.addLink('aaaaa', 'aaa', LinkType.CHILD_LINKED_SPAN); + noRecordRootSpan.addMessageEvent( + MessageEventType.RECEIVED, 'aaaa', 123456789); + noRecordRootSpan.addAttribute('my_first_attribute', 'foo'); + noRecordRootSpan.setStatus(CanonicalCode.OK); + noRecordRootSpan.startChildSpan(); + }); +}); diff --git a/packages/opencensus-core/test/test-no-record-span.ts b/packages/opencensus-core/test/test-no-record-span.ts new file mode 100644 index 000000000..d2148ddb7 --- /dev/null +++ b/packages/opencensus-core/test/test-no-record-span.ts @@ -0,0 +1,36 @@ +/** + * 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 {CanonicalCode, CoreTracer, LinkType, MessageEventType} from '../src'; +import {NoRecordRootSpan} from '../src/trace/model/no-record/no-record-root-span'; +import {NoRecordSpan} from '../src/trace/model/no-record/no-record-span'; + +const tracer = new CoreTracer(); + +describe('NoRecordSpan()', () => { + it('do not crash', () => { + const root = new NoRecordRootSpan(tracer); + const noRecordSpan = new NoRecordSpan(root); + noRecordSpan.addAnnotation('MyAnnotation'); + noRecordSpan.addAnnotation('MyAnnotation', {myString: 'bar'}); + noRecordSpan.addAnnotation( + 'MyAnnotation', {myString: 'bar', myNumber: 123, myBoolean: true}); + noRecordSpan.addLink('aaaaa', 'aaa', LinkType.CHILD_LINKED_SPAN); + noRecordSpan.addMessageEvent(MessageEventType.RECEIVED, 'aaaa', 123456789); + noRecordSpan.addAttribute('my_first_attribute', 'foo'); + noRecordSpan.setStatus(CanonicalCode.OK); + }); +}); diff --git a/packages/opencensus-core/test/test-tracer.ts b/packages/opencensus-core/test/test-tracer.ts index 92294fcea..fcd166bc9 100644 --- a/packages/opencensus-core/test/test-tracer.ts +++ b/packages/opencensus-core/test/test-tracer.ts @@ -20,6 +20,7 @@ import * as uuid from 'uuid'; import {randomSpanId} from '../src/internal/util'; import {TracerConfig} from '../src/trace/config/types'; import {TraceParams} from '../src/trace/config/types'; +import {NoRecordRootSpan} from '../src/trace/model/no-record/no-record-root-span'; import {RootSpan} from '../src/trace/model/root-span'; import {Span} from '../src/trace/model/span'; import {CoreTracer} from '../src/trace/model/tracer'; @@ -169,26 +170,24 @@ describe('Tracer', () => { }); }); - /** Should not start the new RootSpan instance */ describe('startRootSpan() with sampler never', () => { - it('should not start the new RootSpan instance', () => { + it('should start the new NoRecordRootSpan instance', () => { const tracer = new CoreTracer(); const config = {samplingRate: 0} as TracerConfig; tracer.start(config); tracer.startRootSpan(options, (rootSpan) => { - assert.strictEqual(rootSpan, null); + assert.ok(rootSpan instanceof NoRecordRootSpan); }); }); }); - /** Should not create the new RootSpan instance */ describe('startRootSpan() before start()', () => { - it('should not create the new RootSpan instance, tracer not started', + it('should start the new NoRecordRootSpan instance, tracer not started', () => { const tracer = new CoreTracer(); assert.strictEqual(tracer.active, false); tracer.startRootSpan(options, (rootSpan) => { - assert.equal(rootSpan, null); + assert.ok(rootSpan instanceof NoRecordRootSpan); }); }); });