Skip to content

Commit

Permalink
feat: add InMemorySpanExporter, MultiSpanProcessor
Browse files Browse the repository at this point in the history
  • Loading branch information
mayurkale22 committed Sep 4, 2019
1 parent af5d88a commit ce670c9
Show file tree
Hide file tree
Showing 8 changed files with 303 additions and 3 deletions.
16 changes: 16 additions & 0 deletions packages/opentelemetry-basic-tracer/src/BasicTracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ import {
import { BasicTracerConfig } from '../src/types';
import { ScopeManager } from '@opentelemetry/scope-base';
import { Span } from './Span';
import { SpanProcessor } from './SpanProcessor';
import { NoopSpanProcessor } from './NoopSpanProcessor';
import { MultiSpanProcessor } from './MultiSpanProcessor';

/**
* This class represents a basic tracer.
Expand All @@ -45,6 +48,8 @@ export class BasicTracer implements types.Tracer {
private readonly _sampler: types.Sampler;
private readonly _scopeManager: ScopeManager;
readonly logger: Logger;
private readonly _registeredSpanProcessor: SpanProcessor[] = [];
activeSpanProcessor = new NoopSpanProcessor();

/**
* Constructs a new Tracer instance.
Expand Down Expand Up @@ -155,6 +160,17 @@ export class BasicTracer implements types.Tracer {
return this._httpTextFormat;
}

/**
* Adds a new {@link SpanProcessor} to this tracer.
* @param spanProcessor the new SpanProcessor to be added.
*/
addSpanProcessor(spanProcessor: SpanProcessor): void {
this._registeredSpanProcessor.push(spanProcessor);
this.activeSpanProcessor = new MultiSpanProcessor(
this._registeredSpanProcessor
);
}

private _getParentSpanContext(
parent: types.Span | types.SpanContext | undefined
): types.SpanContext | undefined {
Expand Down
44 changes: 44 additions & 0 deletions packages/opentelemetry-basic-tracer/src/MultiSpanProcessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Copyright 2019, OpenTelemetry 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
*
* https://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 { Span } from '@opentelemetry/types';
import { SpanProcessor } from './SpanProcessor';

/**
* Implementation of the {@link SpanProcessor} that simply forwards all
* received events to a list of {@link SpanProcessor}s.
*/
export class MultiSpanProcessor implements SpanProcessor {
constructor(private readonly _spanProcessors: SpanProcessor[]) {}

onStart(span: Span): void {
for (const spanProcessor of this._spanProcessors) {
spanProcessor.onStart(span);
}
}

onEnd(span: Span): void {
for (const spanProcessor of this._spanProcessors) {
spanProcessor.onEnd(span);
}
}

shutdown(): void {
for (const spanProcessor of this._spanProcessors) {
spanProcessor.shutdown();
}
}
}
25 changes: 25 additions & 0 deletions packages/opentelemetry-basic-tracer/src/NoopSpanProcessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright 2019, OpenTelemetry 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
*
* https://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 { Span } from '@opentelemetry/types';
import { SpanProcessor } from './SpanProcessor';

/** No-op implementation of SpanProcessor */
export class NoopSpanProcessor implements SpanProcessor {
onStart(span: Span): void {}
onEnd(span: Span): void {}
shutdown(): void {}
}
6 changes: 5 additions & 1 deletion packages/opentelemetry-basic-tracer/src/Span.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import * as types from '@opentelemetry/types';
import { performance } from 'perf_hooks';
import { ReadableSpan } from './export/ReadableSpan';
import { BasicTracer } from './BasicTracer';
import { SpanProcessor } from './SpanProcessor';

/**
* This class represents a span.
Expand All @@ -40,6 +41,7 @@ export class Span implements types.Span, ReadableSpan {
endTime = 0;
private _ended = false;
private readonly _logger: types.Logger;
private readonly _spanProcessor: SpanProcessor;

/** Constructs a new Span instance. */
constructor(
Expand All @@ -57,6 +59,8 @@ export class Span implements types.Span, ReadableSpan {
this.kind = kind;
this.startTime = startTime || performance.now();
this._logger = parentTracer.logger;
this._spanProcessor = parentTracer.activeSpanProcessor;
this._spanProcessor.onStart(this);
}

tracer(): types.Tracer {
Expand Down Expand Up @@ -111,7 +115,7 @@ export class Span implements types.Span, ReadableSpan {
}
this._ended = true;
this.endTime = endTime || performance.now();
// @todo: record or export the span
this._spanProcessor.onEnd(this);
}

isRecordingEvents(): boolean {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Copyright 2019, OpenTelemetry 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
*
* https://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 { SpanExporter } from './SpanExporter';
import { ReadableSpan } from './ReadableSpan';
import { ExportResult } from './ExportResult';

/**
* This class can be used for testing purposes. It saves the exported spans
* in a list in memory that can be retrieve using the `getFinishedSpans`
* method.
*/
export class InMemorySpanExporter implements SpanExporter {
private _finishedSpan: ReadableSpan[] = [];
private _stopped = false;

export(
spans: ReadableSpan[],
resultCallback: (result: ExportResult) => void
): void {
if (this._stopped) return resultCallback(ExportResult.FailedNonRetryable);
this._finishedSpan.push(...spans);
return resultCallback(ExportResult.Success);
}

shutdown(): void {
this._stopped = true;
this._finishedSpan = [];
}

reset() {
this._finishedSpan = [];
}

getFinishedSpans(): ReadableSpan[] {
return this._finishedSpan;
}
}
6 changes: 4 additions & 2 deletions packages/opentelemetry-basic-tracer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
* limitations under the License.
*/

export * from './BasicTracer';
export * from './export/ExportResult';
export * from './export/InMemorySpanExporter';
export * from './export/ReadableSpan';
export * from './export/SimpleSpanProcessor';
export * from './export/SpanExporter';
export * from './types';
export * from './BasicTracer';
export * from './Span';
export * from './SpanProcessor';
export * from './types';
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Copyright 2019, OpenTelemetry 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
*
* https://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 assert from 'assert';
import { MultiSpanProcessor } from '../src/MultiSpanProcessor';
import { SpanProcessor, Span, BasicTracer } from '../src';
import { NoopScopeManager } from '@opentelemetry/scope-base';

class TestProcessor implements SpanProcessor {
spans: Span[] = [];
onStart(span: Span): void {}

onEnd(span: Span): void {
this.spans.push(span);
}
shutdown(): void {
this.spans = []
}
}


describe('MultiSpanProcessor', () => {
const tracer = new BasicTracer({
scopeManager: new NoopScopeManager()
});
const span = tracer.startSpan('one');

it('should handle empty span processor', () => {
const multiSpanProcessor = new MultiSpanProcessor([]);
multiSpanProcessor.onStart(span);
multiSpanProcessor.onEnd(span);
multiSpanProcessor.shutdown();
})

it('should handle one span processor', () => {
const processor1 = new TestProcessor();
const multiSpanProcessor = new MultiSpanProcessor([processor1]);
multiSpanProcessor.onStart(span);
assert.strictEqual(processor1.spans.length, 0);
multiSpanProcessor.onEnd(span);
assert.strictEqual(processor1.spans.length, 1);
})

it('should handle two span processor', () => {
const processor1 = new TestProcessor();
const processor2 = new TestProcessor();
const multiSpanProcessor = new MultiSpanProcessor([processor1, processor2]);
multiSpanProcessor.onStart(span);
assert.strictEqual(processor1.spans.length, 0);
assert.strictEqual(processor1.spans.length, processor2.spans.length);
multiSpanProcessor.onEnd(span);
assert.strictEqual(processor1.spans.length, 1);
assert.strictEqual(processor1.spans.length, processor2.spans.length);

multiSpanProcessor.shutdown();
assert.strictEqual(processor1.spans.length, 0);
assert.strictEqual(processor1.spans.length, processor2.spans.length);
})
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Copyright 2019, OpenTelemetry 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
*
* https://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 assert from 'assert';
import { InMemorySpanExporter, SimpleSpanProcessor, BasicTracer, ExportResult } from '../../src';
import { NoopScopeManager } from '@opentelemetry/scope-base';

describe('InMemorySpanExporter', () => {
const memoryExporter = new InMemorySpanExporter();
const tracer = new BasicTracer({
scopeManager: new NoopScopeManager(),
});
tracer.addSpanProcessor(new SimpleSpanProcessor(memoryExporter));

afterEach(() => {
// reset spans in memory.
memoryExporter.reset();
})

it('should get finished spans', () => {
const root = tracer.startSpan('root');
const child = tracer.startSpan('child', {parent: root});
const grandChild = tracer.startSpan('grand-child', {parent: child});

assert.strictEqual(memoryExporter.getFinishedSpans().length, 0);
grandChild.end();
assert.strictEqual(memoryExporter.getFinishedSpans().length, 1);
child.end();
assert.strictEqual(memoryExporter.getFinishedSpans().length, 2);
root.end();
assert.strictEqual(memoryExporter.getFinishedSpans().length, 3);

const [span1, span2, span3] = memoryExporter.getFinishedSpans();
assert.strictEqual(span1.name, 'grand-child');
assert.strictEqual(span2.name, 'child');
assert.strictEqual(span3.name, 'root');
assert.strictEqual(span1.spanContext.traceId, span2.spanContext.traceId);
assert.strictEqual(span2.spanContext.traceId, span3.spanContext.traceId);
assert.strictEqual(span1.parentSpanId, span2.spanContext.spanId);
assert.strictEqual(span2.parentSpanId, span3.spanContext.spanId);
});

it('should shutdown the exorter', () => {
const root = tracer.startSpan('root');
tracer.startSpan('child', {parent: root}).end();
root.end();
assert.strictEqual(memoryExporter.getFinishedSpans().length, 2);
memoryExporter.shutdown();
assert.strictEqual(memoryExporter.getFinishedSpans().length, 0);

// after shutdown no new spans are accepted
tracer.startSpan('child1', {parent: root}).end();
assert.strictEqual(memoryExporter.getFinishedSpans().length, 0);
});

it('should return the success result', () => {
const exorter = new InMemorySpanExporter();
exorter.export([], (result: ExportResult) => {
assert.strictEqual(result, ExportResult.Success);
});
});

it('should return the FailedNonRetryable result after shutdown', () => {
const exorter = new InMemorySpanExporter();
exorter.shutdown();

// after shutdown export should fail
exorter.export([], (result: ExportResult) => {
assert.strictEqual(result, ExportResult.FailedNonRetryable);
});
});

});

0 comments on commit ce670c9

Please sign in to comment.