@platformatic/telemetry 0.34.1 → 0.35.0

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.
package/lib/schema.js CHANGED
@@ -12,6 +12,13 @@ const TelemetrySchema = {
12
12
  type: 'string',
13
13
  description: 'The version of the service (optional)'
14
14
  },
15
+ skip: {
16
+ type: 'array',
17
+ description: 'An array of paths to skip when creating spans. Useful for health checks and other endpoints that do not need to be traced.',
18
+ items: {
19
+ type: 'string'
20
+ }
21
+ },
15
22
  exporter: {
16
23
  type: 'object',
17
24
  properties: {
package/lib/telemetry.js CHANGED
@@ -33,15 +33,16 @@ function formatSpanName (request) {
33
33
  return routerPath ? `${method} ${routerPath}` : method
34
34
  }
35
35
 
36
- const defaultFormatSpanAttributes = {
36
+ const formatSpanAttributes = {
37
37
  request (request) {
38
38
  const { hostname, method, url, protocol } = request
39
- const urlData = fastUri.parse(`${protocol}://${hostname})`)
39
+ // Inspired by: https://github.com/fastify/fastify-url-data/blob/master/plugin.js#L11
40
+ const urlData = fastUri.parse(`${protocol}://${hostname}${url}`)
40
41
  return {
41
42
  'server.address': hostname,
42
43
  'server.port': urlData.port,
43
44
  'http.request.method': method,
44
- 'url.path': url,
45
+ 'url.path': urlData.path,
45
46
  'url.scheme': protocol
46
47
  }
47
48
  },
@@ -107,16 +108,17 @@ async function setupTelemetry (app, opts) {
107
108
  // const { serviceName, version } = opts
108
109
  const openTelemetryAPIs = setupProvider(app, opts)
109
110
  const { tracer, propagator, provider } = openTelemetryAPIs
110
-
111
- const formatSpanAttributes = {
112
- ...defaultFormatSpanAttributes,
113
- ...(opts.formatSpanAttributes || {})
114
- }
111
+ const skipOperations = opts.skip || []
115
112
 
116
113
  // expose the span as a request decorator
117
114
  app.decorateRequest('span')
118
115
 
119
116
  const startSpan = async (request) => {
117
+ if (skipOperations.includes(`${request.method}${request.url}`)) {
118
+ request.log.debug({ operation: `${request.method}${request.url}` }, 'Skipping telemetry')
119
+ return
120
+ }
121
+
120
122
  // We populate the context with the incoming request headers
121
123
  let context = propagator.extract(new PlatformaticContext(), request, fastifyTextMapGetter)
122
124
 
@@ -139,19 +141,23 @@ async function setupTelemetry (app, opts) {
139
141
  }
140
142
 
141
143
  const injectPropagationHeadersInReply = async (request, reply) => {
142
- const context = request.span.context
143
- propagator.inject(context, reply, fastifyTextMapSetter)
144
+ if (request.span) {
145
+ const context = request.span.context
146
+ propagator.inject(context, reply, fastifyTextMapSetter)
147
+ }
144
148
  }
145
149
 
146
150
  const endSpan = async (request, reply) => {
147
151
  const span = request.span
148
- const spanStatus = { code: SpanStatusCode.OK }
149
- if (reply.statusCode >= 400) {
150
- spanStatus.code = SpanStatusCode.ERROR
152
+ if (span) {
153
+ const spanStatus = { code: SpanStatusCode.OK }
154
+ if (reply.statusCode >= 400) {
155
+ spanStatus.code = SpanStatusCode.ERROR
156
+ }
157
+ span.setAttributes(formatSpanAttributes.reply(reply))
158
+ span.setStatus(spanStatus)
159
+ span.end()
151
160
  }
152
- span.setAttributes(formatSpanAttributes.reply(reply))
153
- span.setStatus(spanStatus)
154
- span.end()
155
161
  }
156
162
 
157
163
  app.addHook('onRequest', startSpan)
@@ -181,12 +187,20 @@ async function setupTelemetry (app, opts) {
181
187
  // - closing the span
182
188
  const startSpanClient = (url, method, ctx) => {
183
189
  let context = ctx || new PlatformaticContext()
190
+ const urlObj = fastUri.parse(url)
191
+
192
+ if (skipOperations.includes(`${method}${urlObj.path}`)) {
193
+ app.log.debug({ operation: `${method}${urlObj.path}` }, 'Skipping telemetry')
194
+ return
195
+ }
196
+
184
197
  /* istanbul ignore next */
185
198
  method = method || ''
186
- const span = tracer.startSpan(`${method} ${url}`, {}, context)
199
+ const name = `${method} ${urlObj.scheme}://${urlObj.host}:${urlObj.port}${urlObj.path}`
200
+
201
+ const span = tracer.startSpan(name, {}, context)
187
202
  span.kind = SpanKind.CLIENT
188
203
 
189
- const urlObj = fastUri.parse(url)
190
204
  /* istanbul ignore next */
191
205
  const attributes = url
192
206
  ? {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/telemetry",
3
- "version": "0.34.1",
3
+ "version": "0.35.0",
4
4
  "description": "OpenTelemetry integration for Platformatic",
5
5
  "main": "index.js",
6
6
  "author": "Marco Piraccini <marco.piraccini@gmail.com>",
@@ -230,3 +230,43 @@ test('should trace a client request failing (no HTTP error)', async ({ equal, sa
230
230
  equal(spanClient.attributes['error.message'], 'KABOOM!!!')
231
231
  equal(spanClient.attributes['error.stack'].includes('Error: KABOOM!!!'), true)
232
232
  })
233
+
234
+ test('should not add the query in span name', async ({ equal, same, teardown }) => {
235
+ const handler = async (request, reply) => {
236
+ return { foo: 'bar' }
237
+ }
238
+
239
+ const app = await setupApp({
240
+ serviceName: 'test-service',
241
+ exporter: {
242
+ type: 'memory'
243
+ }
244
+ }, handler, teardown)
245
+
246
+ const { startSpanClient } = app.openTelemetry
247
+
248
+ const url = 'http://localhost:3000/test?foo=bar'
249
+ const { span } = startSpanClient(url, 'GET')
250
+ same(span.name, 'GET http://localhost:3000/test')
251
+ })
252
+
253
+ test('should ignore the skipped operations', async ({ equal, same, ok, teardown }) => {
254
+ const handler = async (request, reply) => {
255
+ return { foo: 'bar' }
256
+ }
257
+
258
+ const app = await setupApp({
259
+ serviceName: 'test-service',
260
+ skip: ['POST/skipme'],
261
+ exporter: {
262
+ type: 'memory'
263
+ }
264
+ }, handler, teardown)
265
+
266
+ const { startSpanClient } = app.openTelemetry
267
+
268
+ const url = 'http://localhost:3000/skipme'
269
+ const ret = startSpanClient(url, 'POST')
270
+ // no spam should be created
271
+ ok(!ret)
272
+ })
@@ -59,6 +59,45 @@ test('should trace a request not failing', async ({ equal, same, teardown }) =>
59
59
  same(resource.attributes['service.version'], '1.0.0')
60
60
  })
61
61
 
62
+ test('should not put query in `url.path', async ({ equal, same, teardown }) => {
63
+ const handler = async (request, reply) => {
64
+ return { foo: 'bar' }
65
+ }
66
+
67
+ const injectArgs = {
68
+ method: 'GET',
69
+ url: '/test?foo=bar',
70
+ headers: {
71
+ host: 'test'
72
+ }
73
+ }
74
+
75
+ const app = await setupApp({
76
+ serviceName: 'test-service',
77
+ version: '1.0.0',
78
+ exporter: {
79
+ type: 'memory'
80
+ }
81
+ }, handler, teardown)
82
+
83
+ await app.inject(injectArgs)
84
+ const { exporter } = app.openTelemetry
85
+ const finishedSpans = exporter.getFinishedSpans()
86
+ equal(finishedSpans.length, 1)
87
+ const span = finishedSpans[0]
88
+ equal(span.kind, SpanKind.SERVER)
89
+ equal(span.name, 'GET /test')
90
+ equal(span.status.code, SpanStatusCode.OK)
91
+ equal(span.attributes['http.request.method'], 'GET')
92
+ equal(span.attributes['url.path'], '/test')
93
+ equal(span.attributes['http.response.status_code'], 200)
94
+ equal(span.attributes['url.scheme'], 'http')
95
+ equal(span.attributes['server.address'], 'test')
96
+ const resource = span.resource
97
+ same(resource.attributes['service.name'], 'test-service')
98
+ same(resource.attributes['service.version'], '1.0.0')
99
+ })
100
+
62
101
  test('request should add attribute to a span', async ({ equal, same, teardown }) => {
63
102
  const handler = async (request, reply) => {
64
103
  request.span.setAttribute('foo', 'bar')
@@ -209,3 +248,33 @@ test('wrong exporter is configured, should default to console', async ({ equal,
209
248
  const { exporter } = app.openTelemetry
210
249
  same(exporter.constructor.name, 'ConsoleSpanExporter')
211
250
  })
251
+
252
+ test('should not trace if the operation is skipped', async ({ equal, same, teardown }) => {
253
+ const handler = async (request, reply) => {
254
+ return { foo: 'bar' }
255
+ }
256
+
257
+ const app = await setupApp({
258
+ serviceName: 'test-service',
259
+ version: '1.0.0',
260
+ skip: [
261
+ 'GET/documentation/json'
262
+ ],
263
+ exporter: {
264
+ type: 'memory'
265
+ }
266
+ }, handler, teardown)
267
+
268
+ const injectArgs = {
269
+ method: 'GET',
270
+ url: '/documentation/json',
271
+ headers: {
272
+ host: 'test'
273
+ }
274
+ }
275
+
276
+ await app.inject(injectArgs)
277
+ const { exporter } = app.openTelemetry
278
+ const finishedSpans = exporter.getFinishedSpans()
279
+ equal(finishedSpans.length, 0)
280
+ })