feat(otel): basic event recording (#28552)

This commit is contained in:
Lach 2025-03-20 21:58:08 +01:00 committed by GitHub
parent 60b502db80
commit 7ac8130854
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 185 additions and 21 deletions

View file

@ -45,6 +45,7 @@ pub use opentelemetry::metrics::MeterProvider;
pub use opentelemetry::metrics::UpDownCounter;
use opentelemetry::otel_debug;
use opentelemetry::otel_error;
use opentelemetry::trace::Event;
use opentelemetry::trace::Link;
use opentelemetry::trace::SpanContext;
use opentelemetry::trace::SpanId;
@ -1381,6 +1382,32 @@ impl OtelSpan {
Ok(())
}
#[fast]
fn add_event(
&self,
#[string] name: String,
start_time: f64,
#[smi] dropped_attributes_count: u32,
) {
let start_time = if start_time.is_nan() {
SystemTime::now()
} else {
SystemTime::UNIX_EPOCH
.checked_add(Duration::from_secs_f64(start_time / 1000.0))
.unwrap()
};
let mut state = self.0.borrow_mut();
let OtelSpanState::Recording(span) = &mut **state else {
return;
};
span.events.events.push(Event::new(
name,
start_time,
vec![],
dropped_attributes_count,
));
}
#[fast]
fn drop_event(&self) {
let mut state = self.0.borrow_mut();

View file

@ -169,6 +169,25 @@ function hrToMs(hr: [number, number]): number {
return (hr[0] * 1e3 + hr[1] / 1e6);
}
function isTimeInput(input: unknown): input is TimeInput {
return typeof input === "number" ||
(input && (ArrayIsArray(input) || isDate(input)));
}
function timeInputToMs(input?: TimeInput): number | undefined {
if (input === undefined) return;
if (ArrayIsArray(input)) {
return hrToMs(input);
} else if (isDate(input)) {
return DatePrototypeGetTime(input);
}
return input;
}
function countAttributes(attributes?: Attributes): number {
return attributes ? ObjectKeys(attributes).length : 0;
}
interface AsyncContextSnapshot {
__brand: "AsyncContextSnapshot";
}
@ -183,7 +202,7 @@ export const currentSnapshot = getAsyncContext;
export const restoreSnapshot = setAsyncContext;
function isDate(value: unknown): value is Date {
return ObjectPrototypeIsPrototypeOf(value, DatePrototype);
return ObjectPrototypeIsPrototypeOf(DatePrototype, value);
}
interface OtelTracer {
@ -215,6 +234,11 @@ interface OtelSpan {
spanContext(): SpanContext;
setStatus(status: SpanStatusCode, errorDescription: string): void;
addEvent(
name: string,
startTime: number,
droppedAttributeCount: number,
): void;
dropEvent(): void;
end(endTime: number): void;
}
@ -303,20 +327,13 @@ class Tracer {
context = context ?? CURRENT.get();
}
let startTime = options?.startTime;
if (startTime && ArrayIsArray(startTime)) {
startTime = hrToMs(startTime);
} else if (startTime && isDate(startTime)) {
startTime = DatePrototypeGetTime(startTime);
}
const startTime = timeInputToMs(options?.startTime);
const parentSpan = context?.getValue(SPAN_KEY) as
| Span
| { spanContext(): SpanContext }
| undefined;
const attributesCount = options?.attributes
? ObjectKeys(options.attributes).length
: 0;
const attributesCount = countAttributes(options?.attributes);
const parentOtelSpan: OtelSpan | null | undefined = parentSpan !== undefined
? getOtelSpan(parentSpan) ?? undefined
: undefined;
@ -380,17 +397,27 @@ class Span {
}
addEvent(
_name: string,
_attributesOrStartTime?: Attributes | TimeInput,
_startTime?: TimeInput,
name: string,
attributesOrStartTime?: Attributes | TimeInput,
startTime?: TimeInput,
): this {
this.#otelSpan?.dropEvent();
if (isTimeInput(attributesOrStartTime)) {
startTime = attributesOrStartTime;
attributesOrStartTime = undefined;
}
const startTimeMs = timeInputToMs(startTime);
this.#otelSpan?.addEvent(
name,
startTimeMs ?? NaN,
countAttributes(attributesOrStartTime),
);
return this;
}
addLink(link: Link): this {
const droppedAttributeCount = (link.droppedAttributesCount ?? 0) +
(link.attributes ? ObjectKeys(link.attributes).length : 0);
countAttributes(link.attributes);
const valid = op_otel_span_add_link(
this.#otelSpan,
link.context.traceId,
@ -411,12 +438,7 @@ class Span {
}
end(endTime?: TimeInput): void {
if (endTime && ArrayIsArray(endTime)) {
endTime = hrToMs(endTime);
} else if (endTime && isDate(endTime)) {
endTime = DatePrototypeGetTime(endTime);
}
this.#otelSpan?.end(endTime || NaN);
this.#otelSpan?.end(timeInputToMs(endTime) || NaN);
}
isRecording(): boolean {

View file

@ -56,6 +56,10 @@
"node_http_request": {
"args": "run -A main.ts node_http_request.ts",
"output": "node_http_request.out"
},
"events": {
"args": "run -A main.ts events.ts",
"output": "events.out"
}
}
}

View file

@ -0,0 +1,90 @@
{
"spans": [
{
"traceId": "00000000000000000000000000000001",
"spanId": "0000000000000001",
"traceState": "",
"parentSpanId": "",
"flags": 1,
"name": "example span",
"kind": 1,
"startTimeUnixNano": "[WILDCARD]",
"endTimeUnixNano": "[WILDCARD]",
"attributes": [],
"droppedAttributesCount": 0,
"events": [
{
"timeUnixNano": "[WILDCARD]",
"name": "example event",
"attributes": [],
"droppedAttributesCount": 0
}
],
"droppedEventsCount": 0,
"links": [],
"droppedLinksCount": 0,
"status": {
"message": "",
"code": 0
}
},
{
"traceId": "00000000000000000000000000000002",
"spanId": "0000000000000002",
"traceState": "",
"parentSpanId": "",
"flags": 1,
"name": "example span",
"kind": 1,
"startTimeUnixNano": "[WILDCARD]",
"endTimeUnixNano": "[WILDCARD]",
"attributes": [],
"droppedAttributesCount": 0,
"events": [
{
"timeUnixNano": "[WILDCARD]",
"name": "example event",
"attributes": [],
"droppedAttributesCount": 1
}
],
"droppedEventsCount": 0,
"links": [],
"droppedLinksCount": 0,
"status": {
"message": "",
"code": 0
}
},
{
"traceId": "00000000000000000000000000000003",
"spanId": "0000000000000003",
"traceState": "",
"parentSpanId": "",
"flags": 1,
"name": "example span",
"kind": 1,
"startTimeUnixNano": "[WILDCARD]",
"endTimeUnixNano": "[WILDCARD]",
"attributes": [],
"droppedAttributesCount": 0,
"events": [
{
"timeUnixNano": "[WILDCARD]",
"name": "example event",
"attributes": [],
"droppedAttributesCount": 1
}
],
"droppedEventsCount": 0,
"links": [],
"droppedLinksCount": 0,
"status": {
"message": "",
"code": 0
}
}
],
"logs": [],
"metrics": []
}

View file

@ -0,0 +1,21 @@
// Copyright 2018-2025 the Deno authors. MIT license.
import { trace } from "npm:@opentelemetry/api@1.9.0";
const tracer = trace.getTracer("example-tracer");
const span1 = tracer.startSpan("example span");
span1.addEvent("example event");
span1.end();
const span2 = tracer.startSpan("example span");
span2.addEvent("example event", {
key: "value",
});
span2.end();
const span3 = tracer.startSpan("example span");
span3.addEvent("example event", {
key: "value",
}, new Date());
span3.end();