Skip to content

Commit

Permalink
feat(opencensus-shim) add ShimTracer and ShimSpan implementations (op…
Browse files Browse the repository at this point in the history
…en-telemetry#3756)

Co-authored-by: Marc Pichler <[email protected]>
  • Loading branch information
aabmass and pichlermarc authored May 16, 2023
1 parent 2531263 commit 68eba71
Show file tree
Hide file tree
Showing 9 changed files with 798 additions and 2 deletions.
1 change: 1 addition & 0 deletions experimental/packages/shim-opencensus/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"devDependencies": {
"@opentelemetry/core": "1.13.0",
"@opentelemetry/context-async-hooks": "1.13.0",
"@opentelemetry/sdk-trace-base": "1.13.0",
"@opencensus/core": "0.1.0",
"@opentelemetry/api": "1.4.1",
"@types/mocha": "10.0.0",
Expand Down
188 changes: 188 additions & 0 deletions experimental/packages/shim-opencensus/src/ShimSpan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* Copyright The 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 oc from '@opencensus/core';
import { ShimTracer } from './ShimTracer';
import { AttributeValue, Span, SpanStatusCode, diag } from '@opentelemetry/api';
import { mapMessageEvent, reverseMapSpanContext } from './transform';

// Copied from
// https://github.com/census-instrumentation/opencensus-node/blob/v0.1.0/packages/opencensus-core/src/trace/model/span.ts#L61
export const DEFAULT_SPAN_NAME = 'span';

const STATUS_OK = {
code: oc.CanonicalCode.OK,
};

interface Options {
shimTracer: ShimTracer;
otelSpan: Span;
isRootSpan?: boolean | undefined;
kind?: oc.SpanKind | undefined;
parentSpanId?: string | undefined;
}

export class ShimSpan implements oc.Span {
get id(): string {
return this.otelSpan.spanContext().spanId;
}

get tracer(): oc.TracerBase {
return this._shimTracer;
}

logger: oc.Logger = diag;

/** These are not readable in OTel so we return empty values */
attributes: oc.Attributes = {};
annotations: oc.Annotation[] = [];
messageEvents: oc.MessageEvent[] = [];
spans: oc.Span[] = [];
links: oc.Link[] = [];
name = '';
status: oc.Status = STATUS_OK;
activeTraceParams: oc.TraceParams = {};
droppedAttributesCount = 0;
droppedLinksCount = 0;
droppedAnnotationsCount = 0;
droppedMessageEventsCount = 0;
started = true;
ended = false;
numberOfChildren = 0;
duration = 0;

/** Actual private attributes */
private _shimTracer: ShimTracer;
readonly otelSpan: Span;
private _isRootSpan: boolean;

readonly kind: oc.SpanKind;
readonly parentSpanId: string;

get remoteParent(): boolean {
return this.otelSpan.spanContext().isRemote ?? false;
}

/** Constructs a new SpanBaseModel instance. */
constructor({
shimTracer,
otelSpan,
isRootSpan = false,
kind = oc.SpanKind.UNSPECIFIED,
parentSpanId = '',
}: Options) {
this._shimTracer = shimTracer;
this.otelSpan = otelSpan;
this._isRootSpan = isRootSpan;
this.kind = kind;
this.parentSpanId = parentSpanId;
}

/** Returns whether a span is root or not. */
isRootSpan(): boolean {
return this._isRootSpan;
}

get traceId(): string {
return this.otelSpan.spanContext().traceId;
}

/** Gets the trace state */
get traceState(): oc.TraceState | undefined {
return this.otelSpan.spanContext().traceState?.serialize();
}

/** No-op implementation of this method. */
get startTime(): Date {
return new Date();
}

/** No-op implementation of this method. */
get endTime(): Date {
return new Date();
}

/** No-op implementation of this method. */
allDescendants(): oc.Span[] {
return [];
}

/** Gives the TraceContext of the span. */
get spanContext(): oc.SpanContext {
return reverseMapSpanContext(this.otelSpan.spanContext());
}

addAttribute(key: string, value: string | number | boolean | object) {
this.otelSpan.setAttribute(key, value as AttributeValue);
}

addAnnotation(
description: string,
attributes?: oc.Attributes,
timestamp?: number
) {
this.otelSpan.addEvent(description, attributes, timestamp);
}

/** No-op implementation of this method. */
addLink() {
diag.info(
'Call to OpenCensus Span.addLink() is being ignored. OTel does not support ' +
'adding links after span creation'
);
}

/** No-op implementation of this method. */
addMessageEvent(
type: oc.MessageEventType,
id: number,
timestamp?: number,
uncompressedSize?: number,
compressedSize?: number
) {
this.otelSpan.addEvent(
...mapMessageEvent(type, id, timestamp, uncompressedSize, compressedSize)
);
}

/** No-op implementation of this method. */
setStatus(code: oc.CanonicalCode, message?: string) {
this.otelSpan.setStatus({
code:
code === oc.CanonicalCode.OK ? SpanStatusCode.OK : SpanStatusCode.ERROR,
message,
});
}

/** No-op implementation of this method. */
start() {}

end(): void {
this.otelSpan.end();
}

/** No-op implementation of this method. */
truncate() {}

/** Starts a new Span instance as a child of this instance */
startChildSpan(options?: oc.SpanOptions): oc.Span {
return this._shimTracer.startChildSpan({
name: DEFAULT_SPAN_NAME,
childOf: this,
...options,
});
}
}
162 changes: 162 additions & 0 deletions experimental/packages/shim-opencensus/src/ShimTracer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* Copyright The 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 oc from '@opencensus/core';

import {
Context,
context,
createContextKey,
diag,
INVALID_SPAN_CONTEXT,
trace,
Tracer,
} from '@opentelemetry/api';
import { DEFAULT_SPAN_NAME, ShimSpan } from './ShimSpan';
import { mapSpanContext, mapSpanKind } from './transform';
import { shimPropagation } from './propagation';

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const INVALID_SPAN = trace.getSpan(
trace.setSpanContext(context.active(), INVALID_SPAN_CONTEXT)
)!;
const ROOTSPAN_KEY = createContextKey('rootspan_for_oc_shim');

function setRootSpan(ctx: Context, span: ShimSpan): Context {
return ctx.setValue(ROOTSPAN_KEY, span);
}

export function getRootSpan(ctx: Context): ShimSpan | null {
return ctx.getValue(ROOTSPAN_KEY) as ShimSpan | null;
}

export class ShimTracer implements oc.Tracer {
logger: oc.Logger = diag;
active: boolean = false;

/** Noop implementations */
sampler: oc.Sampler = new oc.AlwaysSampler();
activeTraceParams: oc.TraceParams = {};
eventListeners: oc.SpanEventListener[] = [];
// Uses the global OpenTelemetry propagator by default
propagation: oc.Propagation = shimPropagation;

constructor(private otelTracer: Tracer) {}

start({ propagation }: oc.TracerConfig): this {
this.active = true;
// Pass a propagation here if you want the shim to use an OpenCensus propagation instance
// instead of the OpenTelemetry global propagator.
if (propagation) {
this.propagation = propagation;
}
return this;
}

/** Noop implementations */
stop(): this {
this.active = false;
return this;
}
registerSpanEventListener(): void {}
unregisterSpanEventListener(): void {}
clearCurrentTrace(): void {}
onStartSpan(): void {}
onEndSpan(): void {}
setCurrentRootSpan() {
// This can't be correctly overriden since OTel context does not provide a way to set
// context without a callback. Leave noop for now.
}

/** Gets the current root span. */
get currentRootSpan(): oc.Span {
return (
getRootSpan(context.active()) ??
new ShimSpan({
shimTracer: this,
otelSpan: INVALID_SPAN,
})
);
}

/**
* Starts a root span.
* @param options A TraceOptions object to start a root span.
* @param fn A callback function to run after starting a root span.
*/
startRootSpan<T>(
{ name, kind, spanContext }: oc.TraceOptions,
fn: (root: oc.Span) => T
): T {
const parentCtx =
spanContext === undefined
? context.active()
: trace.setSpanContext(context.active(), mapSpanContext(spanContext));

const otelSpan = this.otelTracer.startSpan(
name,
{ kind: mapSpanKind(kind) },
parentCtx
);
const shimSpan = new ShimSpan({
shimTracer: this,
otelSpan,
isRootSpan: true,
kind,
parentSpanId: trace.getSpanContext(parentCtx)?.spanId,
});

let ctx = trace.setSpan(parentCtx, otelSpan);
ctx = setRootSpan(ctx, shimSpan);
return context.with(ctx, () => fn(shimSpan));
}

startChildSpan(options?: oc.SpanOptions): oc.Span {
const { kind, name = DEFAULT_SPAN_NAME, childOf } = options ?? {};
const rootSpan = getRootSpan(context.active());

let ctx = context.active();
if (childOf) {
ctx = trace.setSpanContext(ctx, mapSpanContext(childOf.spanContext));
} else if (rootSpan) {
ctx = trace.setSpan(ctx, rootSpan.otelSpan);
}

const otelSpan = this.otelTracer.startSpan(
name,
{
kind: mapSpanKind(kind),
},
ctx
);
return new ShimSpan({
shimTracer: this,
otelSpan,
isRootSpan: false,
kind,
parentSpanId: trace.getSpanContext(ctx)?.spanId,
});
}

wrap<T>(fn: oc.Func<T>): oc.Func<T> {
return context.bind(context.active(), fn);
}

wrapEmitter(emitter: NodeJS.EventEmitter): void {
// Not sure if this requires returning the modified emitter
context.bind(context.active(), emitter);
}
}
2 changes: 2 additions & 0 deletions experimental/packages/shim-opencensus/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export { ShimTracer } from './ShimTracer';
Loading

0 comments on commit 68eba71

Please sign in to comment.