-
Notifications
You must be signed in to change notification settings - Fork 825
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(opencensus-shim) add ShimTracer and ShimSpan implementations (#3756
) Co-authored-by: Marc Pichler <[email protected]>
- Loading branch information
1 parent
2531263
commit 68eba71
Showing
9 changed files
with
798 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
162
experimental/packages/shim-opencensus/src/ShimTracer.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.