Skip to content

Commit 2db68e4

Browse files
committed
fix: replace otlp exporter with local implementation
1 parent c7ccca0 commit 2db68e4

File tree

3 files changed

+106
-178
lines changed

3 files changed

+106
-178
lines changed

package-lock.json

Lines changed: 2 additions & 173 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/otel/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@
120120
"@opentelemetry/api": "1.9.0",
121121
"@opentelemetry/core": "1.30.1",
122122
"@opentelemetry/instrumentation": "^0.203.0",
123-
"@opentelemetry/otlp-transformer": "0.57.2",
124123
"@opentelemetry/resources": "1.30.1",
125124
"@opentelemetry/sdk-trace-node": "1.30.1"
126125
}

packages/otel/src/exporters/netlify.ts

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
import { diag, type DiagLogger } from '@opentelemetry/api'
1+
import { diag, SpanKind, type DiagLogger } from '@opentelemetry/api'
22
import { BindOnceFuture, ExportResult, ExportResultCode } from '@opentelemetry/core'
3-
import { JsonTraceSerializer } from '@opentelemetry/otlp-transformer'
43
import type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-node'
54
import { TRACE_PREFIX } from '../constants.ts'
65

76
export class NetlifySpanExporter implements SpanExporter {
87
#shutdownOnce: BindOnceFuture<void>
98
#logger: DiagLogger
10-
static #decoder = new TextDecoder()
119

1210
constructor() {
1311
this.#shutdownOnce = new BindOnceFuture(this.#shutdown, this)
@@ -27,7 +25,7 @@ export class NetlifySpanExporter implements SpanExporter {
2725
return
2826
}
2927

30-
console.log(TRACE_PREFIX, NetlifySpanExporter.#decoder.decode(JsonTraceSerializer.serializeRequest(spans)))
28+
console.log(TRACE_PREFIX, JSON.stringify(serialize(spans)))
3129
resultCallback({ code: ExportResultCode.SUCCESS })
3230
}
3331

@@ -46,3 +44,105 @@ export class NetlifySpanExporter implements SpanExporter {
4644
return Promise.resolve()
4745
}
4846
}
47+
48+
// Replaces JsonTraceSerializer.serializeRequest(spans)
49+
const serialize = (spans: ReadableSpan[]): Record<string, unknown> => {
50+
return {
51+
resourceSpans: spans.map((span) => {
52+
const spanContext = span.spanContext()
53+
54+
return {
55+
resource: {
56+
attributes: toAttributes(span.resource.attributes),
57+
droppedAttributesCount: span.droppedAttributesCount,
58+
},
59+
scopeSpans: [
60+
{
61+
scope: {
62+
name: span.instrumentationLibrary.name,
63+
version: span.instrumentationLibrary.version,
64+
},
65+
spans: [
66+
{
67+
traceId: spanContext.traceId,
68+
spanId: spanContext.spanId,
69+
parentSpanId: span.parentSpanId,
70+
71+
name: span.name,
72+
kind: span.kind || SpanKind.SERVER,
73+
74+
startTimeUnixNano: hrTimeToNanos(span.startTime),
75+
endTimeUnixNano: hrTimeToNanos(span.endTime),
76+
77+
attributes: toAttributes(span.attributes),
78+
droppedAttributesCount: span.droppedAttributesCount,
79+
80+
events: span.events.map((event) => ({
81+
name: event.name,
82+
timeUnixNano: hrTimeToNanos(event.time),
83+
attributes: toAttributes(event.attributes ?? {}),
84+
droppedAttributesCount: event.droppedAttributesCount ?? 0,
85+
})),
86+
droppedEventsCount: span.droppedEventsCount,
87+
88+
status: {
89+
code: span.status.code,
90+
message: span.status.message,
91+
},
92+
93+
links: span.links.map((link) => ({
94+
spanId: link.context.spanId,
95+
traceId: link.context.traceId,
96+
attributes: toAttributes(link.attributes ?? {}),
97+
droppedAttributesCount: link.droppedAttributesCount ?? 0,
98+
})),
99+
droppedLinksCount: span.droppedLinksCount,
100+
},
101+
],
102+
},
103+
],
104+
}
105+
}),
106+
}
107+
}
108+
109+
// Reference: opentelemetry-js/experimental/packages/otlp-transformer/src/common/internal.ts
110+
111+
type IAnyValue = Record<string, number | boolean | string | object>
112+
113+
export function toAttributes(attributes: Record<string, unknown>): IAnyValue[] {
114+
return Object.keys(attributes).map((key) => toKeyValue(key, attributes[key]))
115+
}
116+
117+
function toKeyValue(key: string, value: unknown): IAnyValue {
118+
return {
119+
key: key,
120+
value: toAnyValue(value),
121+
}
122+
}
123+
124+
function toAnyValue(value: unknown): IAnyValue {
125+
const t = typeof value
126+
if (t === 'string') return { stringValue: value as string }
127+
if (t === 'number') {
128+
if (!Number.isInteger(value)) return { doubleValue: value as number }
129+
return { intValue: value as number }
130+
}
131+
if (t === 'boolean') return { boolValue: value as boolean }
132+
if (value instanceof Uint8Array) return { bytesValue: value }
133+
if (Array.isArray(value)) return { arrayValue: { values: value.map(toAnyValue) } }
134+
if (t === 'object' && value != null)
135+
return {
136+
kvlistValue: {
137+
values: Object.entries(value as object).map(([k, v]) => toKeyValue(k, v)),
138+
},
139+
}
140+
141+
return {}
142+
}
143+
144+
function hrTimeToNanos(hrTime: [number, number]) {
145+
const NANOSECONDS = BigInt(1_000_000_000)
146+
const nanos = BigInt(Math.trunc(hrTime[0])) * NANOSECONDS + BigInt(Math.trunc(hrTime[1]))
147+
return nanos.toString()
148+
}

0 commit comments

Comments
 (0)