summaryrefslogtreecommitdiff
path: root/runtime/js
diff options
context:
space:
mode:
authorhaturau <135221985+haturatu@users.noreply.github.com>2024-11-20 01:20:47 +0900
committerGitHub <noreply@github.com>2024-11-20 01:20:47 +0900
commit85719a67e59c7aa45bead26e4942d7df8b1b42d4 (patch)
treeface0aecaac53e93ce2f23b53c48859bcf1a36ec /runtime/js
parent67697bc2e4a62a9670699fd18ad0dd8efc5bd955 (diff)
parent186b52731c6bb326c4d32905c5e732d082e83465 (diff)
Merge branch 'denoland:main' into main
Diffstat (limited to 'runtime/js')
-rw-r--r--runtime/js/40_fs_events.js2
-rw-r--r--runtime/js/90_deno_ns.js19
-rw-r--r--runtime/js/98_global_scope_shared.js13
-rw-r--r--runtime/js/99_main.js22
-rw-r--r--runtime/js/telemetry.ts720
5 files changed, 762 insertions, 14 deletions
diff --git a/runtime/js/40_fs_events.js b/runtime/js/40_fs_events.js
index ec2474c0a..322ee6b3c 100644
--- a/runtime/js/40_fs_events.js
+++ b/runtime/js/40_fs_events.js
@@ -21,7 +21,7 @@ class FsWatcher {
constructor(paths, options) {
const { recursive } = options;
- this.#rid = op_fs_events_open({ recursive, paths });
+ this.#rid = op_fs_events_open(recursive, paths);
}
unref() {
diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js
index fd2ac00f2..6300f599d 100644
--- a/runtime/js/90_deno_ns.js
+++ b/runtime/js/90_deno_ns.js
@@ -29,6 +29,7 @@ import * as tty from "ext:runtime/40_tty.js";
import * as kv from "ext:deno_kv/01_db.ts";
import * as cron from "ext:deno_cron/01_cron.ts";
import * as webgpuSurface from "ext:deno_webgpu/02_surface.js";
+import * as telemetry from "ext:runtime/telemetry.ts";
const denoNs = {
Process: process.Process,
@@ -134,7 +135,7 @@ const denoNs = {
createHttpClient: httpClient.createHttpClient,
};
-// NOTE(bartlomieju): keep IDs in sync with `cli/main.rs`
+// NOTE(bartlomieju): keep IDs in sync with `runtime/lib.rs`
const unstableIds = {
broadcastChannel: 1,
cron: 2,
@@ -143,11 +144,13 @@ const unstableIds = {
http: 5,
kv: 6,
net: 7,
- process: 8,
- temporal: 9,
- unsafeProto: 10,
- webgpu: 11,
- workerOptions: 12,
+ nodeGlobals: 8,
+ otel: 9,
+ process: 10,
+ temporal: 11,
+ unsafeProto: 12,
+ webgpu: 13,
+ workerOptions: 14,
};
const denoNsUnstableById = { __proto__: null };
@@ -181,4 +184,8 @@ denoNsUnstableById[unstableIds.webgpu] = {
// denoNsUnstableById[unstableIds.workerOptions] = { __proto__: null }
+denoNsUnstableById[unstableIds.otel] = {
+ telemetry: telemetry.telemetry,
+};
+
export { denoNs, denoNsUnstableById, unstableIds };
diff --git a/runtime/js/98_global_scope_shared.js b/runtime/js/98_global_scope_shared.js
index 7a2723899..f8e76b7ce 100644
--- a/runtime/js/98_global_scope_shared.js
+++ b/runtime/js/98_global_scope_shared.js
@@ -32,6 +32,8 @@ import { DOMException } from "ext:deno_web/01_dom_exception.js";
import * as abortSignal from "ext:deno_web/03_abort_signal.js";
import * as imageData from "ext:deno_web/16_image_data.js";
import process from "node:process";
+import Buffer from "node:buffer";
+import { clearImmediate, setImmediate } from "node:timers";
import { loadWebGPU } from "ext:deno_webgpu/00_init.js";
import * as webgpuSurface from "ext:deno_webgpu/02_surface.js";
import { unstableIds } from "ext:runtime/90_deno_ns.js";
@@ -300,4 +302,15 @@ unstableForWindowOrWorkerGlobalScope[unstableIds.net] = {
unstableForWindowOrWorkerGlobalScope[unstableIds.webgpu] = {};
+unstableForWindowOrWorkerGlobalScope[unstableIds.nodeGlobals] = {
+ Buffer: core.propWritable(Buffer),
+ setImmediate: core.propWritable(setImmediate),
+ clearImmediate: core.propWritable(clearImmediate),
+ global: {
+ enumerable: true,
+ configurable: true,
+ get: () => globalThis,
+ },
+};
+
export { unstableForWindowOrWorkerGlobalScope, windowOrWorkerGlobalScope };
diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js
index 56a5b411b..eedca3396 100644
--- a/runtime/js/99_main.js
+++ b/runtime/js/99_main.js
@@ -27,7 +27,6 @@ const {
ArrayPrototypeForEach,
ArrayPrototypeIncludes,
ArrayPrototypeMap,
- DateNow,
Error,
ErrorPrototype,
FunctionPrototypeBind,
@@ -87,6 +86,8 @@ import {
workerRuntimeGlobalProperties,
} from "ext:runtime/98_global_scope_worker.js";
import { SymbolDispose, SymbolMetadata } from "ext:deno_web/00_infra.js";
+import { bootstrap as bootstrapOtel } from "ext:runtime/telemetry.ts";
+
// deno-lint-ignore prefer-primordials
if (Symbol.metadata) {
throw "V8 supports Symbol.metadata now, no need to shim it";
@@ -470,6 +471,8 @@ const NOT_IMPORTED_OPS = [
// Related to `Deno.jupyter` API
"op_jupyter_broadcast",
"op_jupyter_input",
+ // Used in jupyter API
+ "op_base64_encode",
// Related to `Deno.test()` API
"op_test_event_step_result_failed",
@@ -574,6 +577,7 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) {
10: serveHost,
11: serveIsMain,
12: serveWorkerCount,
+ 13: otelConfig,
} = runtimeOptions;
if (mode === executionModes.serve) {
@@ -642,7 +646,7 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) {
removeImportedOps();
- performance.setTimeOrigin(DateNow());
+ performance.setTimeOrigin();
globalThis_ = globalThis;
// Remove bootstrapping data from the global scope
@@ -674,9 +678,10 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) {
});
ObjectSetPrototypeOf(globalThis, Window.prototype);
+ bootstrapOtel(otelConfig);
+
if (inspectFlag) {
- const consoleFromDeno = globalThis.console;
- core.wrapConsole(consoleFromDeno, core.v8Console);
+ core.wrapConsole(globalThis.console, core.v8Console);
}
event.defineEventHandler(globalThis, "error");
@@ -696,6 +701,7 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) {
// are lost.
let jupyterNs = undefined;
ObjectDefineProperty(finalDenoNs, "jupyter", {
+ __proto__: null,
get() {
if (jupyterNs) {
return jupyterNs;
@@ -855,9 +861,10 @@ function bootstrapWorkerRuntime(
5: hasNodeModulesDir,
6: argv0,
7: nodeDebug,
+ 13: otelConfig,
} = runtimeOptions;
- performance.setTimeOrigin(DateNow());
+ performance.setTimeOrigin();
globalThis_ = globalThis;
// Remove bootstrapping data from the global scope
@@ -882,8 +889,9 @@ function bootstrapWorkerRuntime(
}
ObjectSetPrototypeOf(globalThis, DedicatedWorkerGlobalScope.prototype);
- const consoleFromDeno = globalThis.console;
- core.wrapConsole(consoleFromDeno, core.v8Console);
+ bootstrapOtel(otelConfig);
+
+ core.wrapConsole(globalThis.console, core.v8Console);
event.defineEventHandler(self, "message");
event.defineEventHandler(self, "error", undefined, true);
diff --git a/runtime/js/telemetry.ts b/runtime/js/telemetry.ts
new file mode 100644
index 000000000..ecef3b5e6
--- /dev/null
+++ b/runtime/js/telemetry.ts
@@ -0,0 +1,720 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { core, primordials } from "ext:core/mod.js";
+import {
+ op_crypto_get_random_values,
+ op_otel_instrumentation_scope_create_and_enter,
+ op_otel_instrumentation_scope_enter,
+ op_otel_instrumentation_scope_enter_builtin,
+ op_otel_log,
+ op_otel_span_attribute,
+ op_otel_span_attribute2,
+ op_otel_span_attribute3,
+ op_otel_span_continue,
+ op_otel_span_flush,
+ op_otel_span_set_dropped,
+ op_otel_span_start,
+} from "ext:core/ops";
+import { Console } from "ext:deno_console/01_console.js";
+import { performance } from "ext:deno_web/15_performance.js";
+
+const {
+ SafeWeakMap,
+ Array,
+ ObjectEntries,
+ SafeMap,
+ ReflectApply,
+ SymbolFor,
+ Error,
+ Uint8Array,
+ TypedArrayPrototypeSubarray,
+ ObjectAssign,
+ ObjectDefineProperty,
+ WeakRefPrototypeDeref,
+ String,
+ ObjectPrototypeIsPrototypeOf,
+ DataView,
+ DataViewPrototypeSetUint32,
+ SafeWeakRef,
+ TypedArrayPrototypeGetBuffer,
+} = primordials;
+const { AsyncVariable, setAsyncContext } = core;
+
+let TRACING_ENABLED = false;
+let DETERMINISTIC = false;
+
+enum SpanKind {
+ INTERNAL = 0,
+ SERVER = 1,
+ CLIENT = 2,
+ PRODUCER = 3,
+ CONSUMER = 4,
+}
+
+interface TraceState {
+ set(key: string, value: string): TraceState;
+ unset(key: string): TraceState;
+ get(key: string): string | undefined;
+ serialize(): string;
+}
+
+interface SpanContext {
+ traceId: string;
+ spanId: string;
+ isRemote?: boolean;
+ traceFlags: number;
+ traceState?: TraceState;
+}
+
+type HrTime = [number, number];
+
+enum SpanStatusCode {
+ UNSET = 0,
+ OK = 1,
+ ERROR = 2,
+}
+
+interface SpanStatus {
+ code: SpanStatusCode;
+ message?: string;
+}
+
+export type AttributeValue =
+ | string
+ | number
+ | boolean
+ | Array<null | undefined | string>
+ | Array<null | undefined | number>
+ | Array<null | undefined | boolean>;
+
+interface Attributes {
+ [attributeKey: string]: AttributeValue | undefined;
+}
+
+type SpanAttributes = Attributes;
+
+interface Link {
+ context: SpanContext;
+ attributes?: SpanAttributes;
+ droppedAttributesCount?: number;
+}
+
+interface TimedEvent {
+ time: HrTime;
+ name: string;
+ attributes?: SpanAttributes;
+ droppedAttributesCount?: number;
+}
+
+interface IArrayValue {
+ values: IAnyValue[];
+}
+
+interface IAnyValue {
+ stringValue?: string | null;
+ boolValue?: boolean | null;
+ intValue?: number | null;
+ doubleValue?: number | null;
+ arrayValue?: IArrayValue;
+ kvlistValue?: IKeyValueList;
+ bytesValue?: Uint8Array;
+}
+
+interface IKeyValueList {
+ values: IKeyValue[];
+}
+
+interface IKeyValue {
+ key: string;
+ value: IAnyValue;
+}
+interface IResource {
+ attributes: IKeyValue[];
+ droppedAttributesCount: number;
+}
+
+interface InstrumentationLibrary {
+ readonly name: string;
+ readonly version?: string;
+ readonly schemaUrl?: string;
+}
+
+interface ReadableSpan {
+ readonly name: string;
+ readonly kind: SpanKind;
+ readonly spanContext: () => SpanContext;
+ readonly parentSpanId?: string;
+ readonly startTime: HrTime;
+ readonly endTime: HrTime;
+ readonly status: SpanStatus;
+ readonly attributes: SpanAttributes;
+ readonly links: Link[];
+ readonly events: TimedEvent[];
+ readonly duration: HrTime;
+ readonly ended: boolean;
+ readonly resource: IResource;
+ readonly instrumentationLibrary: InstrumentationLibrary;
+ readonly droppedAttributesCount: number;
+ readonly droppedEventsCount: number;
+ readonly droppedLinksCount: number;
+}
+
+enum ExportResultCode {
+ SUCCESS = 0,
+ FAILED = 1,
+}
+
+interface ExportResult {
+ code: ExportResultCode;
+ error?: Error;
+}
+
+function hrToSecs(hr: [number, number]): number {
+ return ((hr[0] * 1e3 + hr[1] / 1e6) / 1000);
+}
+
+const TRACE_FLAG_SAMPLED = 1 << 0;
+
+const instrumentationScopes = new SafeWeakMap<
+ InstrumentationLibrary,
+ { __key: "instrumentation-library" }
+>();
+let activeInstrumentationLibrary: WeakRef<InstrumentationLibrary> | null = null;
+
+function submit(
+ spanId: string | Uint8Array,
+ traceId: string | Uint8Array,
+ traceFlags: number,
+ parentSpanId: string | Uint8Array | null,
+ span: Omit<
+ ReadableSpan,
+ | "spanContext"
+ | "startTime"
+ | "endTime"
+ | "parentSpanId"
+ | "duration"
+ | "ended"
+ | "resource"
+ >,
+ startTime: number,
+ endTime: number,
+) {
+ if (!(traceFlags & TRACE_FLAG_SAMPLED)) return;
+
+ // TODO(@lucacasonato): `resource` is ignored for now, should we implement it?
+
+ const instrumentationLibrary = span.instrumentationLibrary;
+ if (
+ !activeInstrumentationLibrary ||
+ WeakRefPrototypeDeref(activeInstrumentationLibrary) !==
+ instrumentationLibrary
+ ) {
+ activeInstrumentationLibrary = new SafeWeakRef(instrumentationLibrary);
+ if (instrumentationLibrary === BUILTIN_INSTRUMENTATION_LIBRARY) {
+ op_otel_instrumentation_scope_enter_builtin();
+ } else {
+ let instrumentationScope = instrumentationScopes
+ .get(instrumentationLibrary);
+
+ if (instrumentationScope === undefined) {
+ instrumentationScope = op_otel_instrumentation_scope_create_and_enter(
+ instrumentationLibrary.name,
+ instrumentationLibrary.version,
+ instrumentationLibrary.schemaUrl,
+ ) as { __key: "instrumentation-library" };
+ instrumentationScopes.set(
+ instrumentationLibrary,
+ instrumentationScope,
+ );
+ } else {
+ op_otel_instrumentation_scope_enter(
+ instrumentationScope,
+ );
+ }
+ }
+ }
+
+ op_otel_span_start(
+ traceId,
+ spanId,
+ parentSpanId,
+ span.kind,
+ span.name,
+ startTime,
+ endTime,
+ );
+
+ const status = span.status;
+ if (status !== null && status.code !== 0) {
+ op_otel_span_continue(status.code, status.message ?? "");
+ }
+
+ const attributeKvs = ObjectEntries(span.attributes);
+ let i = 0;
+ while (i < attributeKvs.length) {
+ if (i + 2 < attributeKvs.length) {
+ op_otel_span_attribute3(
+ attributeKvs.length,
+ attributeKvs[i][0],
+ attributeKvs[i][1],
+ attributeKvs[i + 1][0],
+ attributeKvs[i + 1][1],
+ attributeKvs[i + 2][0],
+ attributeKvs[i + 2][1],
+ );
+ i += 3;
+ } else if (i + 1 < attributeKvs.length) {
+ op_otel_span_attribute2(
+ attributeKvs.length,
+ attributeKvs[i][0],
+ attributeKvs[i][1],
+ attributeKvs[i + 1][0],
+ attributeKvs[i + 1][1],
+ );
+ i += 2;
+ } else {
+ op_otel_span_attribute(
+ attributeKvs.length,
+ attributeKvs[i][0],
+ attributeKvs[i][1],
+ );
+ i += 1;
+ }
+ }
+
+ // TODO(@lucacasonato): implement links
+ // TODO(@lucacasonato): implement events
+
+ const droppedAttributesCount = span.droppedAttributesCount;
+ const droppedLinksCount = span.droppedLinksCount + span.links.length;
+ const droppedEventsCount = span.droppedEventsCount + span.events.length;
+ if (
+ droppedAttributesCount > 0 || droppedLinksCount > 0 ||
+ droppedEventsCount > 0
+ ) {
+ op_otel_span_set_dropped(
+ droppedAttributesCount,
+ droppedLinksCount,
+ droppedEventsCount,
+ );
+ }
+
+ op_otel_span_flush();
+}
+
+const now = () => (performance.timeOrigin + performance.now()) / 1000;
+
+const SPAN_ID_BYTES = 8;
+const TRACE_ID_BYTES = 16;
+
+const INVALID_TRACE_ID = new Uint8Array(TRACE_ID_BYTES);
+const INVALID_SPAN_ID = new Uint8Array(SPAN_ID_BYTES);
+
+const NO_ASYNC_CONTEXT = {};
+
+let otelLog: (message: string, level: number) => void;
+
+const hexSliceLookupTable = (function () {
+ const alphabet = "0123456789abcdef";
+ const table = new Array(256);
+ for (let i = 0; i < 16; ++i) {
+ const i16 = i * 16;
+ for (let j = 0; j < 16; ++j) {
+ table[i16 + j] = alphabet[i] + alphabet[j];
+ }
+ }
+ return table;
+})();
+
+function bytesToHex(bytes: Uint8Array): string {
+ let out = "";
+ for (let i = 0; i < bytes.length; i += 1) {
+ out += hexSliceLookupTable[bytes[i]];
+ }
+ return out;
+}
+
+const SPAN_KEY = SymbolFor("OpenTelemetry Context Key SPAN");
+
+const BUILTIN_INSTRUMENTATION_LIBRARY: InstrumentationLibrary = {} as never;
+
+let COUNTER = 1;
+
+export let enterSpan: (span: Span) => void;
+export let exitSpan: (span: Span) => void;
+export let endSpan: (span: Span) => void;
+
+export class Span {
+ #traceId: string | Uint8Array;
+ #spanId: Uint8Array;
+ #traceFlags = TRACE_FLAG_SAMPLED;
+
+ #spanContext: SpanContext | null = null;
+
+ #parentSpanId: string | Uint8Array | null = null;
+ #parentSpanIdString: string | null = null;
+
+ #recording = TRACING_ENABLED;
+
+ #kind: number = 0;
+ #name: string;
+ #startTime: number;
+ #status: { code: number; message?: string } | null = null;
+ #attributes: Attributes = { __proto__: null } as never;
+
+ #droppedEventsCount = 0;
+ #droppedLinksCount = 0;
+
+ #asyncContext = NO_ASYNC_CONTEXT;
+
+ static {
+ otelLog = function otelLog(message, level) {
+ let traceId = null;
+ let spanId = null;
+ let traceFlags = 0;
+ const span = CURRENT.get()?.getValue(SPAN_KEY);
+ if (span) {
+ // The lint is wrong, we can not use anything but `in` here because this
+ // is a private field.
+ // deno-lint-ignore prefer-primordials
+ if (#traceId in span) {
+ traceId = span.#traceId;
+ spanId = span.#spanId;
+ traceFlags = span.#traceFlags;
+ } else {
+ const context = span.spanContext();
+ traceId = context.traceId;
+ spanId = context.spanId;
+ traceFlags = context.traceFlags;
+ }
+ }
+ return op_otel_log(message, level, traceId, spanId, traceFlags);
+ };
+
+ enterSpan = (span: Span) => {
+ if (!span.#recording) return;
+ const context = (CURRENT.get() || ROOT_CONTEXT).setValue(SPAN_KEY, span);
+ span.#asyncContext = CURRENT.enter(context);
+ };
+
+ exitSpan = (span: Span) => {
+ if (!span.#recording) return;
+ if (span.#asyncContext === NO_ASYNC_CONTEXT) return;
+ setAsyncContext(span.#asyncContext);
+ span.#asyncContext = NO_ASYNC_CONTEXT;
+ };
+
+ exitSpan = (span: Span) => {
+ const endTime = now();
+ submit(
+ span.#spanId,
+ span.#traceId,
+ span.#traceFlags,
+ span.#parentSpanId,
+ {
+ name: span.#name,
+ kind: span.#kind,
+ status: span.#status ?? { code: 0 },
+ attributes: span.#attributes,
+ events: [],
+ links: [],
+ droppedAttributesCount: 0,
+ droppedEventsCount: span.#droppedEventsCount,
+ droppedLinksCount: span.#droppedLinksCount,
+ instrumentationLibrary: BUILTIN_INSTRUMENTATION_LIBRARY,
+ },
+ span.#startTime,
+ endTime,
+ );
+ };
+ }
+
+ constructor(
+ name: string,
+ attributes?: Attributes,
+ ) {
+ if (!this.isRecording) {
+ this.#name = "";
+ this.#startTime = 0;
+ this.#traceId = INVALID_TRACE_ID;
+ this.#spanId = INVALID_SPAN_ID;
+ this.#traceFlags = 0;
+ return;
+ }
+
+ this.#name = name;
+ this.#startTime = now();
+ this.#attributes = attributes ?? { __proto__: null } as never;
+
+ const currentSpan: Span | {
+ spanContext(): { traceId: string; spanId: string };
+ } = CURRENT.get()?.getValue(SPAN_KEY);
+ if (!currentSpan) {
+ const buffer = new Uint8Array(TRACE_ID_BYTES + SPAN_ID_BYTES);
+ if (DETERMINISTIC) {
+ DataViewPrototypeSetUint32(
+ new DataView(TypedArrayPrototypeGetBuffer(buffer)),
+ TRACE_ID_BYTES - 4,
+ COUNTER,
+ true,
+ );
+ COUNTER += 1;
+ DataViewPrototypeSetUint32(
+ new DataView(TypedArrayPrototypeGetBuffer(buffer)),
+ TRACE_ID_BYTES + SPAN_ID_BYTES - 4,
+ COUNTER,
+ true,
+ );
+ COUNTER += 1;
+ } else {
+ op_crypto_get_random_values(buffer);
+ }
+ this.#traceId = TypedArrayPrototypeSubarray(buffer, 0, TRACE_ID_BYTES);
+ this.#spanId = TypedArrayPrototypeSubarray(buffer, TRACE_ID_BYTES);
+ } else {
+ this.#spanId = new Uint8Array(SPAN_ID_BYTES);
+ if (DETERMINISTIC) {
+ DataViewPrototypeSetUint32(
+ new DataView(TypedArrayPrototypeGetBuffer(this.#spanId)),
+ SPAN_ID_BYTES - 4,
+ COUNTER,
+ true,
+ );
+ COUNTER += 1;
+ } else {
+ op_crypto_get_random_values(this.#spanId);
+ }
+ // deno-lint-ignore prefer-primordials
+ if (#traceId in currentSpan) {
+ this.#traceId = currentSpan.#traceId;
+ this.#parentSpanId = currentSpan.#spanId;
+ } else {
+ const context = currentSpan.spanContext();
+ this.#traceId = context.traceId;
+ this.#parentSpanId = context.spanId;
+ }
+ }
+ }
+
+ spanContext() {
+ if (!this.#spanContext) {
+ this.#spanContext = {
+ traceId: typeof this.#traceId === "string"
+ ? this.#traceId
+ : bytesToHex(this.#traceId),
+ spanId: typeof this.#spanId === "string"
+ ? this.#spanId
+ : bytesToHex(this.#spanId),
+ traceFlags: this.#traceFlags,
+ };
+ }
+ return this.#spanContext;
+ }
+
+ get parentSpanId() {
+ if (!this.#parentSpanIdString && this.#parentSpanId) {
+ if (typeof this.#parentSpanId === "string") {
+ this.#parentSpanIdString = this.#parentSpanId;
+ } else {
+ this.#parentSpanIdString = bytesToHex(this.#parentSpanId);
+ }
+ }
+ return this.#parentSpanIdString;
+ }
+
+ setAttribute(name: string, value: AttributeValue) {
+ if (this.#recording) this.#attributes[name] = value;
+ return this;
+ }
+
+ setAttributes(attributes: Attributes) {
+ if (this.#recording) ObjectAssign(this.#attributes, attributes);
+ return this;
+ }
+
+ setStatus(status: { code: number; message?: string }) {
+ if (this.#recording) {
+ if (status.code === 0) {
+ this.#status = null;
+ } else if (status.code > 2) {
+ throw new Error("Invalid status code");
+ } else {
+ this.#status = status;
+ }
+ }
+ return this;
+ }
+
+ updateName(name: string) {
+ if (this.#recording) this.#name = name;
+ return this;
+ }
+
+ addEvent(_name: never) {
+ // TODO(@lucacasonato): implement events
+ if (this.#recording) this.#droppedEventsCount += 1;
+ return this;
+ }
+
+ addLink(_link: never) {
+ // TODO(@lucacasonato): implement links
+ if (this.#recording) this.#droppedLinksCount += 1;
+ return this;
+ }
+
+ addLinks(links: never[]) {
+ // TODO(@lucacasonato): implement links
+ if (this.#recording) this.#droppedLinksCount += links.length;
+ return this;
+ }
+
+ isRecording() {
+ return this.#recording;
+ }
+}
+
+// Exporter compatible with opentelemetry js library
+class SpanExporter {
+ export(
+ spans: ReadableSpan[],
+ resultCallback: (result: ExportResult) => void,
+ ) {
+ try {
+ for (let i = 0; i < spans.length; i += 1) {
+ const span = spans[i];
+ const context = span.spanContext();
+ submit(
+ context.spanId,
+ context.traceId,
+ context.traceFlags,
+ span.parentSpanId ?? null,
+ span,
+ hrToSecs(span.startTime),
+ hrToSecs(span.endTime),
+ );
+ }
+ resultCallback({ code: 0 });
+ } catch (error) {
+ resultCallback({
+ code: 1,
+ error: ObjectPrototypeIsPrototypeOf(error, Error)
+ ? error as Error
+ : new Error(String(error)),
+ });
+ }
+ }
+
+ async shutdown() {}
+
+ async forceFlush() {}
+}
+
+const CURRENT = new AsyncVariable();
+
+class Context {
+ #data = new SafeMap();
+
+ // deno-lint-ignore no-explicit-any
+ constructor(data?: Iterable<readonly [any, any]> | null | undefined) {
+ this.#data = data ? new SafeMap(data) : new SafeMap();
+ }
+
+ getValue(key: symbol): unknown {
+ return this.#data.get(key);
+ }
+
+ setValue(key: symbol, value: unknown): Context {
+ const c = new Context(this.#data);
+ c.#data.set(key, value);
+ return c;
+ }
+
+ deleteValue(key: symbol): Context {
+ const c = new Context(this.#data);
+ c.#data.delete(key);
+ return c;
+ }
+}
+
+// TODO(lucacasonato): @opentelemetry/api defines it's own ROOT_CONTEXT
+const ROOT_CONTEXT = new Context();
+
+// Context manager for opentelemetry js library
+class ContextManager {
+ active(): Context {
+ return CURRENT.get() ?? ROOT_CONTEXT;
+ }
+
+ with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
+ context: Context,
+ fn: F,
+ thisArg?: ThisParameterType<F>,
+ ...args: A
+ ): ReturnType<F> {
+ const ctx = CURRENT.enter(context);
+ try {
+ return ReflectApply(fn, thisArg, args);
+ } finally {
+ setAsyncContext(ctx);
+ }
+ }
+
+ // deno-lint-ignore no-explicit-any
+ bind<T extends (...args: any[]) => any>(
+ context: Context,
+ target: T,
+ ): T {
+ return ((...args) => {
+ const ctx = CURRENT.enter(context);
+ try {
+ return ReflectApply(target, this, args);
+ } finally {
+ setAsyncContext(ctx);
+ }
+ }) as T;
+ }
+
+ enable() {
+ return this;
+ }
+
+ disable() {
+ return this;
+ }
+}
+
+const otelConsoleConfig = {
+ ignore: 0,
+ capture: 1,
+ replace: 2,
+};
+
+export function bootstrap(
+ config: [] | [
+ typeof otelConsoleConfig[keyof typeof otelConsoleConfig],
+ number,
+ ],
+): void {
+ if (config.length === 0) return;
+ const { 0: consoleConfig, 1: deterministic } = config;
+
+ TRACING_ENABLED = true;
+ DETERMINISTIC = deterministic === 1;
+
+ switch (consoleConfig) {
+ case otelConsoleConfig.capture:
+ core.wrapConsole(globalThis.console, new Console(otelLog));
+ break;
+ case otelConsoleConfig.replace:
+ ObjectDefineProperty(
+ globalThis,
+ "console",
+ core.propNonEnumerable(new Console(otelLog)),
+ );
+ break;
+ default:
+ break;
+ }
+}
+
+export const telemetry = { SpanExporter, ContextManager };