@platformatic/telemetry 0.37.0 → 0.37.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,7 +32,11 @@ class PlatformaticTracerProvider {
32
32
  }
33
33
 
34
34
  addSpanProcessor (spanProcessor) {
35
- this._registeredSpanProcessors.push(spanProcessor)
35
+ if (Array.isArray(spanProcessor)) {
36
+ this._registeredSpanProcessors.push(...spanProcessor)
37
+ } else {
38
+ this._registeredSpanProcessors.push(spanProcessor)
39
+ }
36
40
  this.activeSpanProcessor = new MultiSpanProcessor(
37
41
  this._registeredSpanProcessors
38
42
  )
package/lib/schema.js CHANGED
@@ -1,5 +1,31 @@
1
1
  'use strict'
2
2
 
3
+ const ExporterSchema = {
4
+ type: 'object',
5
+ properties: {
6
+ type: {
7
+ type: 'string',
8
+ enum: ['console', 'otlp', 'zipkin', 'memory'],
9
+ default: 'console'
10
+ },
11
+ options: {
12
+ type: 'object',
13
+ description: 'Options for the exporter. These are passed directly to the exporter.',
14
+ properties: {
15
+ url: {
16
+ type: 'string',
17
+ description: 'The URL to send the traces to. Not used for console or memory exporters.'
18
+ },
19
+ headers: {
20
+ type: 'object',
21
+ description: 'Headers to send to the exporter. Not used for console or memory exporters.'
22
+ }
23
+ }
24
+ },
25
+ additionalProperties: false
26
+ }
27
+ }
28
+
3
29
  const TelemetrySchema = {
4
30
  $id: '/OpenTelemetry',
5
31
  type: 'object',
@@ -20,29 +46,13 @@ const TelemetrySchema = {
20
46
  }
21
47
  },
22
48
  exporter: {
23
- type: 'object',
24
- properties: {
25
- type: {
26
- type: 'string',
27
- enum: ['console', 'otlp', 'zipkin', 'memory'],
28
- default: 'console'
29
- },
30
- options: {
31
- type: 'object',
32
- description: 'Options for the exporter. These are passed directly to the exporter.',
33
- properties: {
34
- url: {
35
- type: 'string',
36
- description: 'The URL to send the traces to. Not used for console or memory exporters.'
37
- },
38
- headers: {
39
- type: 'object',
40
- description: 'Headers to send to the exporter. Not used for console or memory exporters.'
41
- }
42
- }
49
+ anyOf: [
50
+ {
51
+ type: 'array',
52
+ items: ExporterSchema
43
53
  },
44
- additionalProperties: false
45
- }
54
+ ExporterSchema
55
+ ]
46
56
  }
47
57
  },
48
58
  required: ['serviceName'],
package/lib/telemetry.js CHANGED
@@ -81,7 +81,11 @@ const setupProvider = (app, opts) => {
81
81
  app.log.warn('No exporter configured, defaulting to console.')
82
82
  exporter = { type: 'console' }
83
83
  }
84
+
85
+ const exporters = Array.isArray(exporter) ? exporter : [exporter]
86
+
84
87
  app.log.info(`Setting up telemetry for service: ${serviceName}${version ? ' version: ' + version : ''} with exporter of type ${exporter.type}`)
88
+
85
89
  const provider = new PlatformaticTracerProvider({
86
90
  resource: new Resource({
87
91
  [SemanticResourceAttributes.SERVICE_NAME]: serviceName,
@@ -89,33 +93,40 @@ const setupProvider = (app, opts) => {
89
93
  })
90
94
  })
91
95
 
96
+ const exporterObjs = []
97
+ const spanProcessors = []
98
+ for (const exporter of exporters) {
92
99
  // Exporter config:
93
100
  // https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_exporter_zipkin.ExporterConfig.html
94
- const exporterOptions = { ...exporter.options, serviceName }
101
+ const exporterOptions = { ...exporter.options, serviceName }
95
102
 
96
- let exporterObj
97
- if (exporter.type === 'console') {
98
- exporterObj = new ConsoleSpanExporter(exporterOptions)
99
- } else if (exporter.type === 'otlp') {
103
+ let exporterObj
104
+ if (exporter.type === 'console') {
105
+ exporterObj = new ConsoleSpanExporter(exporterOptions)
106
+ } else if (exporter.type === 'otlp') {
100
107
  // We require here because this require (and only the require!) creates some issue with c8 on some mjs tests on other modules. Since we need an assignemet here, we don't use a switch.
101
- const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-proto')
102
- exporterObj = new OTLPTraceExporter(exporterOptions)
103
- } else if (exporter.type === 'zipkin') {
104
- const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin')
105
- exporterObj = new ZipkinExporter(exporterOptions)
106
- } else if (exporter.type === 'memory') {
107
- exporterObj = new InMemorySpanExporter()
108
- } else {
109
- app.log.warn(`Unknown exporter type: ${exporter.type}, defaulting to console.`)
110
- exporterObj = new ConsoleSpanExporter(exporterOptions)
108
+ const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-proto')
109
+ exporterObj = new OTLPTraceExporter(exporterOptions)
110
+ } else if (exporter.type === 'zipkin') {
111
+ const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin')
112
+ exporterObj = new ZipkinExporter(exporterOptions)
113
+ } else if (exporter.type === 'memory') {
114
+ exporterObj = new InMemorySpanExporter()
115
+ } else {
116
+ app.log.warn(`Unknown exporter type: ${exporter.type}, defaulting to console.`)
117
+ exporterObj = new ConsoleSpanExporter(exporterOptions)
118
+ }
119
+
120
+ // We use a SimpleSpanProcessor for the console/memory exporters and a BatchSpanProcessor for the others.
121
+ const spanProcessor = ['memory', 'console'].includes(exporter.type) ? new SimpleSpanProcessor(exporterObj) : new BatchSpanProcessor(exporterObj)
122
+ spanProcessors.push(spanProcessor)
123
+ exporterObjs.push(exporterObj)
111
124
  }
112
125
 
113
- // We use a SimpleSpanProcessor for the console/memory exporters and a BatchSpanProcessor for the others.
114
- const spanProcessor = ['memory', 'console'].includes(exporter.type) ? new SimpleSpanProcessor(exporterObj) : new BatchSpanProcessor(exporterObj)
115
- provider.addSpanProcessor(spanProcessor)
126
+ provider.addSpanProcessor(spanProcessors)
116
127
  const tracer = provider.getTracer(moduleName, moduleVersion)
117
128
  const propagator = provider.getPropagator()
118
- return { tracer, exporter: exporterObj, propagator, provider }
129
+ return { tracer, exporters: exporterObjs, propagator, provider }
119
130
  }
120
131
 
121
132
  async function setupTelemetry (app, opts) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/telemetry",
3
- "version": "0.37.0",
3
+ "version": "0.37.2",
4
4
  "description": "OpenTelemetry integration for Platformatic",
5
5
  "main": "index.js",
6
6
  "author": "Marco Piraccini <marco.piraccini@gmail.com>",
@@ -14,10 +14,12 @@ async function setupApp (pluginOpts, routeHandler, teardown) {
14
14
  app.ready()
15
15
  teardown(async () => {
16
16
  await app.close()
17
- const { exporter } = app.openTelemetry
18
- if (exporter.constructor.name === 'InMemorySpanExporter') {
19
- exporter.reset()
20
- }
17
+ const { exporters } = app.openTelemetry
18
+ exporters.forEach(exporter => {
19
+ if (exporter.constructor.name === 'InMemorySpanExporter') {
20
+ exporter.reset()
21
+ }
22
+ })
21
23
  })
22
24
  return app
23
25
  }
@@ -119,7 +121,8 @@ test('should trace a client request', async ({ equal, same, teardown }) => {
119
121
  const response = await app.inject(args)
120
122
  endSpanClient(span, response)
121
123
 
122
- const { exporter } = app.openTelemetry
124
+ const { exporters } = app.openTelemetry
125
+ const exporter = exporters[0]
123
126
  const finishedSpans = exporter.getFinishedSpans()
124
127
  equal(finishedSpans.length, 2)
125
128
  // We have two one for the client and one for the server
@@ -173,7 +176,9 @@ test('should trace a client request failing', async ({ equal, same, teardown })
173
176
  const response = await app.inject(args)
174
177
  endSpanClient(span, response)
175
178
 
176
- const { exporter } = app.openTelemetry
179
+ const { exporters } = app.openTelemetry
180
+ const exporter = exporters[0]
181
+
177
182
  const finishedSpans = exporter.getFinishedSpans()
178
183
  equal(finishedSpans.length, 2)
179
184
  // We have two one for the client and one for the server
@@ -218,7 +223,8 @@ test('should trace a client request failing (no HTTP error)', async ({ equal, sa
218
223
  endSpanClient(span)
219
224
  }
220
225
 
221
- const { exporter } = app.openTelemetry
226
+ const { exporters } = app.openTelemetry
227
+ const exporter = exporters[0]
222
228
  const finishedSpans = exporter.getFinishedSpans()
223
229
  equal(finishedSpans.length, 1)
224
230
 
@@ -13,10 +13,12 @@ async function setupApp (pluginOpts, routeHandler, teardown) {
13
13
  app.ready()
14
14
  teardown(async () => {
15
15
  await app.close()
16
- const { exporter } = app.openTelemetry
17
- if (exporter.constructor.name === 'InMemorySpanExporter') {
18
- exporter.reset()
19
- }
16
+ const { exporters } = app.openTelemetry
17
+ exporters.forEach(exporter => {
18
+ if (exporter.constructor.name === 'InMemorySpanExporter') {
19
+ exporter.reset()
20
+ }
21
+ })
20
22
  })
21
23
  return app
22
24
  }
@@ -43,7 +45,8 @@ test('should trace a request not failing', async ({ equal, same, teardown }) =>
43
45
  }, handler, teardown)
44
46
 
45
47
  await app.inject(injectArgs)
46
- const { exporter } = app.openTelemetry
48
+ const { exporters } = app.openTelemetry
49
+ const exporter = exporters[0]
47
50
  const finishedSpans = exporter.getFinishedSpans()
48
51
  equal(finishedSpans.length, 1)
49
52
  const span = finishedSpans[0]
@@ -82,7 +85,8 @@ test('should not put query in `url.path', async ({ equal, same, teardown }) => {
82
85
  }, handler, teardown)
83
86
 
84
87
  await app.inject(injectArgs)
85
- const { exporter } = app.openTelemetry
88
+ const { exporters } = app.openTelemetry
89
+ const exporter = exporters[0]
86
90
  const finishedSpans = exporter.getFinishedSpans()
87
91
  equal(finishedSpans.length, 1)
88
92
  const span = finishedSpans[0]
@@ -114,7 +118,8 @@ test('request should add attribute to a span', async ({ equal, same, teardown })
114
118
  }, handler, teardown)
115
119
 
116
120
  await app.inject(injectArgs)
117
- const { exporter } = app.openTelemetry
121
+ const { exporters } = app.openTelemetry
122
+ const exporter = exporters[0]
118
123
  const finishedSpans = exporter.getFinishedSpans()
119
124
  equal(finishedSpans.length, 1)
120
125
  const span = finishedSpans[0]
@@ -164,7 +169,8 @@ test('should trace a request that fails', async ({ equal, same, teardown }) => {
164
169
  }, handler, teardown)
165
170
 
166
171
  await app.inject(injectArgs)
167
- const { exporter } = app.openTelemetry
172
+ const { exporters } = app.openTelemetry
173
+ const exporter = exporters[0]
168
174
  const finishedSpans = exporter.getFinishedSpans()
169
175
  equal(finishedSpans.length, 1)
170
176
  const span = finishedSpans[0]
@@ -189,7 +195,8 @@ test('if no exporter is configured, should default to console', async ({ equal,
189
195
  }, handler, teardown)
190
196
 
191
197
  await app.inject(injectArgs)
192
- const { exporter } = app.openTelemetry
198
+ const { exporters } = app.openTelemetry
199
+ const exporter = exporters[0]
193
200
  same(exporter.constructor.name, 'ConsoleSpanExporter')
194
201
  })
195
202
 
@@ -208,7 +215,8 @@ test('should configure OTLP correctly', async ({ equal, same, teardown }) => {
208
215
  }
209
216
  }, handler, teardown)
210
217
 
211
- const { exporter } = app.openTelemetry
218
+ const { exporters } = app.openTelemetry
219
+ const exporter = exporters[0]
212
220
  same(exporter.constructor.name, 'OTLPTraceExporter')
213
221
  same(exporter.url, 'http://localhost:4317')
214
222
  })
@@ -228,7 +236,8 @@ test('should configure Zipkin correctly', async ({ equal, same, teardown }) => {
228
236
  }
229
237
  }, handler, teardown)
230
238
 
231
- const { exporter } = app.openTelemetry
239
+ const { exporters } = app.openTelemetry
240
+ const exporter = exporters[0]
232
241
  same(exporter.constructor.name, 'ZipkinExporter')
233
242
  same(exporter._urlStr, 'http://localhost:9876')
234
243
  })
@@ -246,7 +255,8 @@ test('wrong exporter is configured, should default to console', async ({ equal,
246
255
  }, handler, teardown)
247
256
 
248
257
  await app.inject(injectArgs)
249
- const { exporter } = app.openTelemetry
258
+ const { exporters } = app.openTelemetry
259
+ const exporter = exporters[0]
250
260
  same(exporter.constructor.name, 'ConsoleSpanExporter')
251
261
  })
252
262
 
@@ -275,7 +285,8 @@ test('should not trace if the operation is skipped', async ({ equal, same, teard
275
285
  }
276
286
 
277
287
  await app.inject(injectArgs)
278
- const { exporter } = app.openTelemetry
288
+ const { exporters } = app.openTelemetry
289
+ const exporter = exporters[0]
279
290
  const finishedSpans = exporter.getFinishedSpans()
280
291
  equal(finishedSpans.length, 0)
281
292
  })
@@ -302,7 +313,8 @@ test('should not put the URL param in path', async ({ equal, same, teardown }) =
302
313
  }, handler, teardown)
303
314
 
304
315
  await app.inject(injectArgs)
305
- const { exporter } = app.openTelemetry
316
+ const { exporters } = app.openTelemetry
317
+ const exporter = exporters[0]
306
318
  const finishedSpans = exporter.getFinishedSpans()
307
319
  equal(finishedSpans.length, 1)
308
320
  const span = finishedSpans[0]
@@ -318,3 +330,53 @@ test('should not put the URL param in path', async ({ equal, same, teardown }) =
318
330
  same(resource.attributes['service.name'], 'test-service')
319
331
  same(resource.attributes['service.version'], '1.0.0')
320
332
  })
333
+
334
+ test('should configure an exporter as an array', async ({ equal, same, teardown }) => {
335
+ const handler = async (request, reply) => {
336
+ return {}
337
+ }
338
+ const app = await setupApp({
339
+ serviceName: 'test-service',
340
+ version: '1.0.0',
341
+ exporter: [{
342
+ type: 'otlp',
343
+ options: {
344
+ url: 'http://localhost:4317'
345
+ }
346
+ }]
347
+ }, handler, teardown)
348
+ const { exporters } = app.openTelemetry
349
+ const exporter = exporters[0]
350
+ same(exporter.constructor.name, 'OTLPTraceExporter')
351
+ same(exporter.url, 'http://localhost:4317')
352
+ })
353
+
354
+ test('should use multiple exporters and sent traces to all the exporters', async ({ equal, same, teardown }) => {
355
+ const handler = async (request, reply) => {
356
+ return {}
357
+ }
358
+ const app = await setupApp({
359
+ serviceName: 'test-service',
360
+ version: '1.0.0',
361
+ exporter: [{
362
+ type: 'memory'
363
+ }, {
364
+ type: 'memory'
365
+ }]
366
+ }, handler, teardown)
367
+ const { exporters } = app.openTelemetry
368
+
369
+ await app.inject(injectArgs)
370
+
371
+ const finishedSpans0 = exporters[0].getFinishedSpans()
372
+ equal(finishedSpans0.length, 1)
373
+ const span0 = finishedSpans0[0]
374
+ equal(span0.name, 'GET /test')
375
+ equal(span0.status.code, SpanStatusCode.OK)
376
+
377
+ const finishedSpans1 = exporters[1].getFinishedSpans()
378
+ equal(finishedSpans1.length, 1)
379
+ const span1 = finishedSpans1[0]
380
+ equal(span1.name, 'GET /test')
381
+ equal(span1.status.code, SpanStatusCode.OK)
382
+ })