@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 +7 -0
- package/lib/telemetry.js +32 -18
- package/package.json +1 -1
- package/test/client.test.js +40 -0
- package/test/telemetry.test.js +69 -0
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
|
|
36
|
+
const formatSpanAttributes = {
|
|
37
37
|
request (request) {
|
|
38
38
|
const { hostname, method, url, protocol } = request
|
|
39
|
-
|
|
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':
|
|
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
|
-
|
|
143
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
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
package/test/client.test.js
CHANGED
|
@@ -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
|
+
})
|
package/test/telemetry.test.js
CHANGED
|
@@ -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
|
+
})
|