1- import { diag , type DiagLogger } from '@opentelemetry/api'
1+ import { diag , SpanKind , type DiagLogger } from '@opentelemetry/api'
22import { BindOnceFuture , ExportResult , ExportResultCode } from '@opentelemetry/core'
3- import { JsonTraceSerializer } from '@opentelemetry/otlp-transformer'
43import type { SpanExporter , ReadableSpan } from '@opentelemetry/sdk-trace-node'
54import { TRACE_PREFIX } from '../constants.ts'
65
76export 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