@embrace-io/react-native-tracer-provider
Version:
A React Native for the Embrace SDK that conforms to the OpenTelemetry TracerProvider interface
173 lines (172 loc) • 7.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmbraceNativeSpan = void 0;
const api_1 = require("@opentelemetry/api");
const util_1 = require("./util");
const TracerProviderModule_1 = require("./TracerProviderModule");
/**
* EmbraceNativeSpan implements a Span over the native Embrace Android and iOS SDKs.
*
* A handful of simple attributes are maintained on the JS side for each span including a unique ID based on the
* tracer's name and version which is used in calls over the bridge to the native modules to perform the actual operations
* on the span
*
* The JS side of this implementation is modelled after [opentelemetry-sdk-trace-base](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-base)
*/
// Need an ID that spans from JS can use to communicate with their stored instances on the native side. A uuid would
// work but did not want to introduce extra work or another dependency here. Instead, it should be enough to keep a
// global count of the spans created by any tracer or tracer provider and use a given span's createdIndex as their ID
let spansCreated = 0;
class EmbraceNativeSpan {
constructor(tracerName, tracerVersion, tracerSchemaUrl, spanContextSyncBehaviour) {
this.recording = true;
this.savedSpanContext = null;
this.creating = null;
this.tracerName = tracerName;
this.tracerVersion = tracerVersion;
this.tracerSchemaUrl = tracerSchemaUrl;
this.spanContextSyncBehaviour = spanContextSyncBehaviour;
this.createdIndex = spansCreated++;
}
nativeID() {
// Only `this.createdIndex` is strictly needed for uniqueness, keeping the other values for debugging purposes
return `${this.tracerName}_${this.tracerVersion}_${this.tracerSchemaUrl}_${this.createdIndex}`;
}
creatingNativeSide(creating) {
this.creating = creating;
this.creating
.then((spanContext) => {
this.savedSpanContext = spanContext;
})
.catch(reason => {
(0, util_1.logWarning)(`Failed to create span: ${reason}`);
});
}
isReadonly() {
return !this.recording;
}
/**
* The fact that native module calls are asynchronous and the @opentelemetry/api is synchronous doesn't matter for
* most methods as they are generally fire and forget. Getting spanContext is an exception since this could be asked
* for immediately after having started a span and if the value is not yet ready on the native side we will either:
* - return a blank span context
* - throw an error
*
* Behaviour can be configured on the EmbraceNativeTracerProvider by supplying spanContextSyncBehaviour
*/
spanContext() {
if (this.savedSpanContext) {
return this.savedSpanContext;
}
const msg = "Span context is not yet available on the native side." +
" You can wait on it with: `await (span as EmbraceNativeSpan).spanContextAsync()`.";
switch (this.spanContextSyncBehaviour) {
case "return_empty":
(0, util_1.logWarning)(msg + " Returning a blank SpanContext");
return {
traceId: "",
spanId: "",
traceFlags: 0,
};
case "throw":
throw new Error(msg);
}
}
/**
* spanContextAsync provides an async alternative to spanContext by wrapping the response in a promise
*/
spanContextAsync() {
return this.creating || Promise.reject("failed to retrieve span context");
}
setAttribute(key, value) {
return this.setAttributes({ [key]: value });
}
setAttributes(attributes) {
if (this.isReadonly()) {
return this;
}
TracerProviderModule_1.TracerProviderModule.setAttributes(this.nativeID(), (0, util_1.normalizeAttributes)(attributes));
return this;
}
addEvent(name, attributesOrStartTime, timeStamp) {
if (this.isReadonly()) {
return this;
}
if ((0, util_1.isAttributes)(attributesOrStartTime)) {
TracerProviderModule_1.TracerProviderModule.addEvent(this.nativeID(), name, (0, util_1.normalizeAttributes)(attributesOrStartTime), (0, util_1.normalizeTime)(timeStamp));
}
else if (timeStamp) {
TracerProviderModule_1.TracerProviderModule.addEvent(this.nativeID(), name, (0, util_1.normalizeAttributes)({}), (0, util_1.normalizeTime)(timeStamp));
}
else {
TracerProviderModule_1.TracerProviderModule.addEvent(this.nativeID(), name, (0, util_1.normalizeAttributes)({}), (0, util_1.normalizeTime)(attributesOrStartTime));
}
return this;
}
addLink(link) {
return this.addLinks([link]);
}
addLinks(links) {
if (this.isReadonly()) {
return this;
}
(0, util_1.logWarning)("Adding span links is not currently supported by the Embrace SDK");
TracerProviderModule_1.TracerProviderModule.addLinks(this.nativeID(), (0, util_1.normalizeLinks)(links));
return this;
}
setStatus(status) {
if (this.isReadonly()) {
return this;
}
TracerProviderModule_1.TracerProviderModule.setStatus(this.nativeID(), {
code: api_1.SpanStatusCode[status.code],
message: status.message,
});
return this;
}
updateName(name) {
if (this.isReadonly()) {
return this;
}
TracerProviderModule_1.TracerProviderModule.updateName(this.nativeID(), name);
return this;
}
end(endTime) {
if (this.isReadonly()) {
return;
}
TracerProviderModule_1.TracerProviderModule.endSpan(this.nativeID(), (0, util_1.normalizeTime)(endTime));
this.recording = false;
}
isRecording() {
return this.recording;
}
recordException(exception, time) {
if (this.isReadonly()) {
return;
}
// See:
// https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/
// https://github.com/open-telemetry/opentelemetry-js/blob/4fa7c1358e84287079a5cba95313d42b50cfcb91/packages/opentelemetry-sdk-trace-base/src/Span.ts#L303
const attributes = {};
if (typeof exception === "string") {
attributes["exception.message"] = exception;
}
else if (exception) {
if (exception.code) {
attributes["exception.type"] = exception.code.toString();
}
else if (exception.name) {
attributes["exception.type"] = exception.name;
}
if (exception.message) {
attributes["exception.message"] = exception.message;
}
if (exception.stack) {
attributes["exception.stacktrace"] = exception.stack;
}
}
TracerProviderModule_1.TracerProviderModule.addEvent(this.nativeID(), "exception", (0, util_1.normalizeAttributes)(attributes), (0, util_1.normalizeTime)(time));
}
}
exports.EmbraceNativeSpan = EmbraceNativeSpan;