13
13
* See the License for the specific language governing permissions and
14
14
* limitations under the License.
15
15
*/
16
+ import * as root from '../src/generated/root' ;
16
17
import { SpanKind , SpanStatusCode , TraceFlags } from '@opentelemetry/api' ;
17
18
import { TraceState , hexToBinary } from '@opentelemetry/core' ;
18
19
import { Resource } from '@opentelemetry/resources' ;
@@ -23,6 +24,8 @@ import {
23
24
createExportTraceServiceRequest ,
24
25
ESpanKind ,
25
26
EStatusCode ,
27
+ ProtobufTraceSerializer ,
28
+ JsonTraceSerializer ,
26
29
} from '../src' ;
27
30
28
31
function createExpectedSpanJson ( options : OtlpEncodingOptions ) {
@@ -133,69 +136,173 @@ function createExpectedSpanJson(options: OtlpEncodingOptions) {
133
136
} ;
134
137
}
135
138
136
- describe ( 'Trace' , ( ) => {
137
- describe ( 'createExportTraceServiceRequest' , ( ) => {
138
- let resource : Resource ;
139
- let span : ReadableSpan ;
139
+ /**
140
+ * utility function to convert a string representing a hex value to a base64 string
141
+ * that represents the bytes of that hex value. This is needed as we need to support Node.js 14
142
+ * where btoa() does not exist, and the Browser, where Buffer does not exist.
143
+ * @param hexStr
144
+ */
145
+ function toBase64 ( hexStr : string ) {
146
+ if ( typeof btoa !== 'undefined' ) {
147
+ const decoder = new TextDecoder ( 'utf8' ) ;
148
+ return btoa ( decoder . decode ( hexToBinary ( hexStr ) ) ) ;
149
+ }
140
150
141
- beforeEach ( ( ) => {
142
- resource = new Resource ( {
143
- 'resource-attribute' : 'resource attribute value' ,
144
- } ) ;
145
- span = {
146
- spanContext : ( ) => ( {
147
- spanId : '0000000000000002' ,
148
- traceFlags : TraceFlags . SAMPLED ,
149
- traceId : '00000000000000000000000000000001' ,
150
- isRemote : false ,
151
- traceState : new TraceState ( 'span=bar' ) ,
152
- } ) ,
153
- parentSpanId : '0000000000000001' ,
154
- attributes : { 'string-attribute' : 'some attribute value' } ,
155
- duration : [ 1 , 300000000 ] ,
156
- endTime : [ 1640715558 , 642725388 ] ,
157
- ended : true ,
158
- events : [
159
- {
160
- name : 'some event' ,
161
- time : [ 1640715558 , 542725388 ] ,
162
- attributes : {
163
- 'event-attribute' : 'some string value' ,
151
+ return Buffer . from ( hexToBinary ( hexStr ) ) . toString ( 'base64' ) ;
152
+ }
153
+
154
+ function createExpectedSpanProtobuf ( ) {
155
+ const startTime = 1640715557342725400 ;
156
+ const endTime = 1640715558642725400 ;
157
+ const eventTime = 1640715558542725400 ;
158
+
159
+ const traceId = toBase64 ( '00000000000000000000000000000001' ) ;
160
+ const spanId = toBase64 ( '0000000000000002' ) ;
161
+ const parentSpanId = toBase64 ( '0000000000000001' ) ;
162
+ const linkSpanId = toBase64 ( '0000000000000003' ) ;
163
+ const linkTraceId = toBase64 ( '00000000000000000000000000000002' ) ;
164
+
165
+ return {
166
+ resourceSpans : [
167
+ {
168
+ resource : {
169
+ attributes : [
170
+ {
171
+ key : 'resource-attribute' ,
172
+ value : { stringValue : 'resource attribute value' } ,
164
173
} ,
165
- } ,
166
- ] ,
167
- instrumentationLibrary : {
168
- name : 'myLib' ,
169
- version : '0.1.0' ,
170
- schemaUrl : 'http://url.to.schema' ,
174
+ ] ,
175
+ droppedAttributesCount : 0 ,
171
176
} ,
172
- kind : SpanKind . CLIENT ,
173
- links : [
177
+ scopeSpans : [
174
178
{
175
- context : {
176
- spanId : '0000000000000003' ,
177
- traceId : '00000000000000000000000000000002' ,
178
- traceFlags : TraceFlags . SAMPLED ,
179
- isRemote : false ,
180
- traceState : new TraceState ( 'link=foo' ) ,
181
- } ,
182
- attributes : {
183
- 'link-attribute' : 'string value' ,
184
- } ,
179
+ scope : { name : 'myLib' , version : '0.1.0' } ,
180
+ spans : [
181
+ {
182
+ traceId : traceId ,
183
+ spanId : spanId ,
184
+ traceState : 'span=bar' ,
185
+ parentSpanId : parentSpanId ,
186
+ name : 'span-name' ,
187
+ kind : ESpanKind . SPAN_KIND_CLIENT ,
188
+ links : [
189
+ {
190
+ droppedAttributesCount : 0 ,
191
+ spanId : linkSpanId ,
192
+ traceId : linkTraceId ,
193
+ traceState : 'link=foo' ,
194
+ attributes : [
195
+ {
196
+ key : 'link-attribute' ,
197
+ value : {
198
+ stringValue : 'string value' ,
199
+ } ,
200
+ } ,
201
+ ] ,
202
+ } ,
203
+ ] ,
204
+ startTimeUnixNano : startTime ,
205
+ endTimeUnixNano : endTime ,
206
+ events : [
207
+ {
208
+ droppedAttributesCount : 0 ,
209
+ attributes : [
210
+ {
211
+ key : 'event-attribute' ,
212
+ value : {
213
+ stringValue : 'some string value' ,
214
+ } ,
215
+ } ,
216
+ ] ,
217
+ name : 'some event' ,
218
+ timeUnixNano : eventTime ,
219
+ } ,
220
+ ] ,
221
+ attributes : [
222
+ {
223
+ key : 'string-attribute' ,
224
+ value : { stringValue : 'some attribute value' } ,
225
+ } ,
226
+ ] ,
227
+ droppedAttributesCount : 0 ,
228
+ droppedEventsCount : 0 ,
229
+ droppedLinksCount : 0 ,
230
+ status : {
231
+ code : EStatusCode . STATUS_CODE_OK ,
232
+ } ,
233
+ } ,
234
+ ] ,
235
+ schemaUrl : 'http://url.to.schema' ,
185
236
} ,
186
237
] ,
187
- name : 'span-name' ,
188
- resource,
189
- startTime : [ 1640715557 , 342725388 ] ,
190
- status : {
191
- code : SpanStatusCode . OK ,
192
- } ,
193
- droppedAttributesCount : 0 ,
194
- droppedEventsCount : 0 ,
195
- droppedLinksCount : 0 ,
196
- } ;
238
+ } ,
239
+ ] ,
240
+ } ;
241
+ }
242
+
243
+ describe ( 'Trace' , ( ) => {
244
+ let resource : Resource ;
245
+ let span : ReadableSpan ;
246
+
247
+ beforeEach ( ( ) => {
248
+ resource = new Resource ( {
249
+ 'resource-attribute' : 'resource attribute value' ,
197
250
} ) ;
251
+ span = {
252
+ spanContext : ( ) => ( {
253
+ spanId : '0000000000000002' ,
254
+ traceFlags : TraceFlags . SAMPLED ,
255
+ traceId : '00000000000000000000000000000001' ,
256
+ isRemote : false ,
257
+ traceState : new TraceState ( 'span=bar' ) ,
258
+ } ) ,
259
+ parentSpanId : '0000000000000001' ,
260
+ attributes : { 'string-attribute' : 'some attribute value' } ,
261
+ duration : [ 1 , 300000000 ] ,
262
+ endTime : [ 1640715558 , 642725388 ] ,
263
+ ended : true ,
264
+ events : [
265
+ {
266
+ name : 'some event' ,
267
+ time : [ 1640715558 , 542725388 ] ,
268
+ attributes : {
269
+ 'event-attribute' : 'some string value' ,
270
+ } ,
271
+ } ,
272
+ ] ,
273
+ instrumentationLibrary : {
274
+ name : 'myLib' ,
275
+ version : '0.1.0' ,
276
+ schemaUrl : 'http://url.to.schema' ,
277
+ } ,
278
+ kind : SpanKind . CLIENT ,
279
+ links : [
280
+ {
281
+ context : {
282
+ spanId : '0000000000000003' ,
283
+ traceId : '00000000000000000000000000000002' ,
284
+ traceFlags : TraceFlags . SAMPLED ,
285
+ isRemote : false ,
286
+ traceState : new TraceState ( 'link=foo' ) ,
287
+ } ,
288
+ attributes : {
289
+ 'link-attribute' : 'string value' ,
290
+ } ,
291
+ } ,
292
+ ] ,
293
+ name : 'span-name' ,
294
+ resource,
295
+ startTime : [ 1640715557 , 342725388 ] ,
296
+ status : {
297
+ code : SpanStatusCode . OK ,
298
+ } ,
299
+ droppedAttributesCount : 0 ,
300
+ droppedEventsCount : 0 ,
301
+ droppedLinksCount : 0 ,
302
+ } ;
303
+ } ) ;
198
304
305
+ describe ( 'createExportTraceServiceRequest' , ( ) => {
199
306
it ( 'returns null on an empty list' , ( ) => {
200
307
assert . deepStrictEqual (
201
308
createExportTraceServiceRequest ( [ ] , { useHex : true } ) ,
@@ -343,4 +450,100 @@ describe('Trace', () => {
343
450
} ) ;
344
451
} ) ;
345
452
} ) ;
453
+
454
+ describe ( 'ProtobufTracesSerializer' , function ( ) {
455
+ it ( 'serializes an export request' , ( ) => {
456
+ const serialized = ProtobufTraceSerializer . serializeRequest ( [ span ] ) ;
457
+ assert . ok ( serialized , 'serialized response is undefined' ) ;
458
+ const decoded =
459
+ root . opentelemetry . proto . collector . trace . v1 . ExportTraceServiceRequest . decode (
460
+ serialized
461
+ ) ;
462
+
463
+ const expected = createExpectedSpanProtobuf ( ) ;
464
+ const decodedObj =
465
+ root . opentelemetry . proto . collector . trace . v1 . ExportTraceServiceRequest . toObject (
466
+ decoded ,
467
+ {
468
+ // This incurs some precision loss that's taken into account in createExpectedSpanProtobuf()
469
+ // Using String here will incur the same precision loss on browser only, using Number to prevent having to
470
+ // have different assertions for browser and Node.js
471
+ longs : Number ,
472
+ // Convert to String (Base64) as otherwise the type will be different for Node.js (Buffer) and Browser (Uint8Array)
473
+ // and this fails assertions.
474
+ bytes : String ,
475
+ }
476
+ ) ;
477
+
478
+ assert . deepStrictEqual ( decodedObj , expected ) ;
479
+ } ) ;
480
+
481
+ it ( 'deserializes a response' , ( ) => {
482
+ const protobufSerializedResponse =
483
+ root . opentelemetry . proto . collector . trace . v1 . ExportTraceServiceResponse . encode (
484
+ {
485
+ partialSuccess : {
486
+ errorMessage : 'foo' ,
487
+ rejectedSpans : 1 ,
488
+ } ,
489
+ }
490
+ ) . finish ( ) ;
491
+
492
+ const deserializedResponse = ProtobufTraceSerializer . deserializeResponse (
493
+ protobufSerializedResponse
494
+ ) ;
495
+
496
+ assert . ok (
497
+ deserializedResponse . partialSuccess ,
498
+ 'partialSuccess not present in the deserialized message'
499
+ ) ;
500
+ assert . equal ( deserializedResponse . partialSuccess . errorMessage , 'foo' ) ;
501
+ assert . equal (
502
+ Number ( deserializedResponse . partialSuccess . rejectedSpans ) ,
503
+ 1
504
+ ) ;
505
+ } ) ;
506
+ } ) ;
507
+
508
+ describe ( 'JsonTracesSerializer' , function ( ) {
509
+ it ( 'serializes an export request' , ( ) => {
510
+ // stringify, then parse to remove undefined keys in the expected JSON
511
+ const expected = JSON . parse (
512
+ JSON . stringify (
513
+ createExpectedSpanJson ( {
514
+ useHex : true ,
515
+ useLongBits : false ,
516
+ } )
517
+ )
518
+ ) ;
519
+ const serialized = JsonTraceSerializer . serializeRequest ( [ span ] ) ;
520
+
521
+ const decoder = new TextDecoder ( ) ;
522
+ assert . deepStrictEqual ( JSON . parse ( decoder . decode ( serialized ) ) , expected ) ;
523
+ } ) ;
524
+
525
+ it ( 'deserializes a response' , ( ) => {
526
+ const expectedResponse = {
527
+ partialSuccess : {
528
+ errorMessage : 'foo' ,
529
+ rejectedSpans : 1 ,
530
+ } ,
531
+ } ;
532
+ const encoder = new TextEncoder ( ) ;
533
+ const encodedResponse = encoder . encode ( JSON . stringify ( expectedResponse ) ) ;
534
+
535
+ const deserializedResponse =
536
+ JsonTraceSerializer . deserializeResponse ( encodedResponse ) ;
537
+
538
+ assert . ok (
539
+ deserializedResponse . partialSuccess ,
540
+ 'partialSuccess not present in the deserialized message'
541
+ ) ;
542
+ assert . equal ( deserializedResponse . partialSuccess . errorMessage , 'foo' ) ;
543
+ assert . equal (
544
+ Number ( deserializedResponse . partialSuccess . rejectedSpans ) ,
545
+ 1
546
+ ) ;
547
+ } ) ;
548
+ } ) ;
346
549
} ) ;
0 commit comments