@platformatic/telemetry 2.0.0-alpha.2 → 2.0.0-alpha.3
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/eslint.config.js +3 -0
- package/index.js +1 -1
- package/lib/fastify-text-map.js +3 -3
- package/lib/platformatic-trace-provider.js +4 -4
- package/lib/schema.js +19 -19
- package/lib/telemetry.js +73 -46
- package/package.json +6 -6
package/eslint.config.js
ADDED
package/index.js
CHANGED
package/lib/fastify-text-map.js
CHANGED
|
@@ -7,16 +7,16 @@ const fastifyTextMapGetter = {
|
|
|
7
7
|
/* istanbul ignore next */
|
|
8
8
|
keys (request) {
|
|
9
9
|
return Object.keys(request.headers)
|
|
10
|
-
}
|
|
10
|
+
},
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const fastifyTextMapSetter = {
|
|
14
14
|
set (reply, key, value) {
|
|
15
15
|
reply.headers({ [key]: value })
|
|
16
|
-
}
|
|
16
|
+
},
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
module.exports = {
|
|
20
20
|
fastifyTextMapGetter,
|
|
21
|
-
fastifyTextMapSetter
|
|
21
|
+
fastifyTextMapSetter,
|
|
22
22
|
}
|
|
@@ -16,13 +16,13 @@ class PlatformaticTracerProvider {
|
|
|
16
16
|
const mergedConfig = merge(
|
|
17
17
|
{},
|
|
18
18
|
{
|
|
19
|
-
sampler: new AlwaysOnSampler()
|
|
19
|
+
sampler: new AlwaysOnSampler(),
|
|
20
20
|
},
|
|
21
21
|
config
|
|
22
22
|
)
|
|
23
23
|
this.resource = mergedConfig.resource ?? Resource.empty()
|
|
24
24
|
this._config = Object.assign({}, mergedConfig, {
|
|
25
|
-
resource: this.resource
|
|
25
|
+
resource: this.resource,
|
|
26
26
|
})
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -49,8 +49,8 @@ class PlatformaticTracerProvider {
|
|
|
49
49
|
getPropagator () {
|
|
50
50
|
return new CompositePropagator({
|
|
51
51
|
propagators: [
|
|
52
|
-
new W3CTraceContextPropagator() // see: https://www.w3.org/TR/trace-context/
|
|
53
|
-
]
|
|
52
|
+
new W3CTraceContextPropagator(), // see: https://www.w3.org/TR/trace-context/
|
|
53
|
+
],
|
|
54
54
|
})
|
|
55
55
|
}
|
|
56
56
|
|
package/lib/schema.js
CHANGED
|
@@ -6,7 +6,7 @@ const ExporterSchema = {
|
|
|
6
6
|
type: {
|
|
7
7
|
type: 'string',
|
|
8
8
|
enum: ['console', 'otlp', 'zipkin', 'memory'],
|
|
9
|
-
default: 'console'
|
|
9
|
+
default: 'console',
|
|
10
10
|
},
|
|
11
11
|
options: {
|
|
12
12
|
type: 'object',
|
|
@@ -14,16 +14,16 @@ const ExporterSchema = {
|
|
|
14
14
|
properties: {
|
|
15
15
|
url: {
|
|
16
16
|
type: 'string',
|
|
17
|
-
description: 'The URL to send the traces to. Not used for console or memory exporters.'
|
|
17
|
+
description: 'The URL to send the traces to. Not used for console or memory exporters.',
|
|
18
18
|
},
|
|
19
19
|
headers: {
|
|
20
20
|
type: 'object',
|
|
21
|
-
description: 'Headers to send to the exporter. Not used for console or memory exporters.'
|
|
22
|
-
}
|
|
23
|
-
}
|
|
21
|
+
description: 'Headers to send to the exporter. Not used for console or memory exporters.',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
24
|
},
|
|
25
|
-
additionalProperties: false
|
|
26
|
-
}
|
|
25
|
+
additionalProperties: false,
|
|
26
|
+
},
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const TelemetrySchema = {
|
|
@@ -32,11 +32,11 @@ const TelemetrySchema = {
|
|
|
32
32
|
properties: {
|
|
33
33
|
serviceName: {
|
|
34
34
|
type: 'string',
|
|
35
|
-
description: 'The name of the service. Defaults to the folder name if not specified.'
|
|
35
|
+
description: 'The name of the service. Defaults to the folder name if not specified.',
|
|
36
36
|
},
|
|
37
37
|
version: {
|
|
38
38
|
type: 'string',
|
|
39
|
-
description: 'The version of the service (optional)'
|
|
39
|
+
description: 'The version of the service (optional)',
|
|
40
40
|
},
|
|
41
41
|
skip: {
|
|
42
42
|
type: 'array',
|
|
@@ -46,28 +46,28 @@ const TelemetrySchema = {
|
|
|
46
46
|
properties: {
|
|
47
47
|
path: {
|
|
48
48
|
type: 'string',
|
|
49
|
-
description: 'The path to skip. Can be a string or a regex.'
|
|
49
|
+
description: 'The path to skip. Can be a string or a regex.',
|
|
50
50
|
},
|
|
51
51
|
method: {
|
|
52
52
|
description: 'HTTP method to skip',
|
|
53
53
|
type: 'string',
|
|
54
|
-
enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
54
|
+
enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
58
|
},
|
|
59
59
|
exporter: {
|
|
60
60
|
anyOf: [
|
|
61
61
|
{
|
|
62
62
|
type: 'array',
|
|
63
|
-
items: ExporterSchema
|
|
63
|
+
items: ExporterSchema,
|
|
64
64
|
},
|
|
65
|
-
ExporterSchema
|
|
66
|
-
]
|
|
67
|
-
}
|
|
65
|
+
ExporterSchema,
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
68
|
},
|
|
69
69
|
required: ['serviceName'],
|
|
70
|
-
additionalProperties: false
|
|
70
|
+
additionalProperties: false,
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
module.exports = TelemetrySchema
|
package/lib/telemetry.js
CHANGED
|
@@ -2,12 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
const fp = require('fastify-plugin')
|
|
4
4
|
const { SpanStatusCode, SpanKind } = require('@opentelemetry/api')
|
|
5
|
-
const {
|
|
6
|
-
|
|
5
|
+
const {
|
|
6
|
+
ConsoleSpanExporter,
|
|
7
|
+
BatchSpanProcessor,
|
|
8
|
+
SimpleSpanProcessor,
|
|
9
|
+
InMemorySpanExporter,
|
|
10
|
+
} = require('@opentelemetry/sdk-trace-base')
|
|
11
|
+
const {
|
|
12
|
+
SemanticResourceAttributes,
|
|
13
|
+
} = require('@opentelemetry/semantic-conventions')
|
|
7
14
|
const { Resource } = require('@opentelemetry/resources')
|
|
8
15
|
const { PlatformaticTracerProvider } = require('./platformatic-trace-provider')
|
|
9
16
|
const { PlatformaticContext } = require('./platformatic-context')
|
|
10
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
fastifyTextMapGetter,
|
|
19
|
+
fastifyTextMapSetter,
|
|
20
|
+
} = require('./fastify-text-map')
|
|
11
21
|
const { formatParamUrl } = require('@fastify/swagger')
|
|
12
22
|
const fastUri = require('fast-uri')
|
|
13
23
|
|
|
@@ -58,21 +68,21 @@ const formatSpanAttributes = {
|
|
|
58
68
|
'server.port': urlData.port,
|
|
59
69
|
'http.request.method': method,
|
|
60
70
|
'url.path': path,
|
|
61
|
-
'url.scheme': protocol
|
|
71
|
+
'url.scheme': protocol,
|
|
62
72
|
}
|
|
63
73
|
},
|
|
64
74
|
reply (reply) {
|
|
65
75
|
return {
|
|
66
|
-
'http.response.status_code': reply.statusCode
|
|
76
|
+
'http.response.status_code': reply.statusCode,
|
|
67
77
|
}
|
|
68
78
|
},
|
|
69
79
|
error (error) {
|
|
70
80
|
return {
|
|
71
81
|
'error.name': error.name,
|
|
72
82
|
'error.message': error.message,
|
|
73
|
-
'error.stack': error.stack
|
|
83
|
+
'error.stack': error.stack,
|
|
74
84
|
}
|
|
75
|
-
}
|
|
85
|
+
},
|
|
76
86
|
}
|
|
77
87
|
|
|
78
88
|
const setupProvider = (app, opts) => {
|
|
@@ -85,13 +95,15 @@ const setupProvider = (app, opts) => {
|
|
|
85
95
|
|
|
86
96
|
const exporters = Array.isArray(exporter) ? exporter : [exporter]
|
|
87
97
|
|
|
88
|
-
app.log.info(
|
|
98
|
+
app.log.info(
|
|
99
|
+
`Setting up telemetry for service: ${serviceName}${version ? ' version: ' + version : ''} with exporter of type ${exporter.type}`
|
|
100
|
+
)
|
|
89
101
|
|
|
90
102
|
const provider = new PlatformaticTracerProvider({
|
|
91
103
|
resource: new Resource({
|
|
92
104
|
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
|
|
93
|
-
[SemanticResourceAttributes.SERVICE_VERSION]: version
|
|
94
|
-
})
|
|
105
|
+
[SemanticResourceAttributes.SERVICE_VERSION]: version,
|
|
106
|
+
}),
|
|
95
107
|
})
|
|
96
108
|
|
|
97
109
|
const exporterObjs = []
|
|
@@ -106,7 +118,9 @@ const setupProvider = (app, opts) => {
|
|
|
106
118
|
exporterObj = new ConsoleSpanExporter(exporterOptions)
|
|
107
119
|
} else if (exporter.type === 'otlp') {
|
|
108
120
|
// 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.
|
|
109
|
-
const {
|
|
121
|
+
const {
|
|
122
|
+
OTLPTraceExporter,
|
|
123
|
+
} = require('@opentelemetry/exporter-trace-otlp-proto')
|
|
110
124
|
exporterObj = new OTLPTraceExporter(exporterOptions)
|
|
111
125
|
} else if (exporter.type === 'zipkin') {
|
|
112
126
|
const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin')
|
|
@@ -114,12 +128,16 @@ const setupProvider = (app, opts) => {
|
|
|
114
128
|
} else if (exporter.type === 'memory') {
|
|
115
129
|
exporterObj = new InMemorySpanExporter()
|
|
116
130
|
} else {
|
|
117
|
-
app.log.warn(
|
|
131
|
+
app.log.warn(
|
|
132
|
+
`Unknown exporter type: ${exporter.type}, defaulting to console.`
|
|
133
|
+
)
|
|
118
134
|
exporterObj = new ConsoleSpanExporter(exporterOptions)
|
|
119
135
|
}
|
|
120
136
|
|
|
121
137
|
// We use a SimpleSpanProcessor for the console/memory exporters and a BatchSpanProcessor for the others.
|
|
122
|
-
const spanProcessor = ['memory', 'console'].includes(exporter.type)
|
|
138
|
+
const spanProcessor = ['memory', 'console'].includes(exporter.type)
|
|
139
|
+
? new SimpleSpanProcessor(exporterObj)
|
|
140
|
+
: new BatchSpanProcessor(exporterObj)
|
|
123
141
|
spanProcessors.push(spanProcessor)
|
|
124
142
|
exporterObjs.push(exporterObj)
|
|
125
143
|
}
|
|
@@ -127,35 +145,40 @@ const setupProvider = (app, opts) => {
|
|
|
127
145
|
provider.addSpanProcessor(spanProcessors)
|
|
128
146
|
const tracer = provider.getTracer(moduleName, moduleVersion)
|
|
129
147
|
const propagator = provider.getPropagator()
|
|
148
|
+
|
|
130
149
|
return { tracer, exporters: exporterObjs, propagator, provider }
|
|
131
150
|
}
|
|
132
151
|
|
|
133
152
|
async function setupTelemetry (app, opts) {
|
|
134
153
|
const openTelemetryAPIs = setupProvider(app, opts)
|
|
135
154
|
const { tracer, propagator, provider } = openTelemetryAPIs
|
|
136
|
-
const skipOperations =
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
155
|
+
const skipOperations =
|
|
156
|
+
opts?.skip?.map((skip) => {
|
|
157
|
+
const { method, path } = skip
|
|
158
|
+
return `${method}${path}`
|
|
159
|
+
}) || []
|
|
140
160
|
|
|
141
161
|
// expose the span as a request decorator
|
|
142
162
|
app.decorateRequest('span')
|
|
143
163
|
|
|
144
|
-
const
|
|
164
|
+
const startHTTPSpan = async (request) => {
|
|
145
165
|
if (skipOperations.includes(`${request.method}${request.url}`)) {
|
|
146
|
-
request.log.debug(
|
|
166
|
+
request.log.debug(
|
|
167
|
+
{ operation: `${request.method}${request.url}` },
|
|
168
|
+
'Skipping telemetry'
|
|
169
|
+
)
|
|
147
170
|
return
|
|
148
171
|
}
|
|
149
172
|
|
|
150
173
|
// We populate the context with the incoming request headers
|
|
151
|
-
let context = propagator.extract(
|
|
174
|
+
let context = propagator.extract(
|
|
175
|
+
new PlatformaticContext(),
|
|
176
|
+
request,
|
|
177
|
+
fastifyTextMapGetter
|
|
178
|
+
)
|
|
152
179
|
|
|
153
180
|
const path = extractPath(request)
|
|
154
|
-
const span = tracer.startSpan(
|
|
155
|
-
formatSpanName(request, path),
|
|
156
|
-
{},
|
|
157
|
-
context
|
|
158
|
-
)
|
|
181
|
+
const span = tracer.startSpan(formatSpanName(request, path), {}, context)
|
|
159
182
|
span.kind = SpanKind.SERVER
|
|
160
183
|
// Next 2 lines are needed by W3CTraceContextPropagator
|
|
161
184
|
context = context.setSpan(span)
|
|
@@ -164,7 +187,7 @@ async function setupTelemetry (app, opts) {
|
|
|
164
187
|
request.span = span
|
|
165
188
|
}
|
|
166
189
|
|
|
167
|
-
const setErrorInSpan = async (request,
|
|
190
|
+
const setErrorInSpan = async (request, _reply, error) => {
|
|
168
191
|
const span = request.span
|
|
169
192
|
span.setAttributes(formatSpanAttributes.error(error))
|
|
170
193
|
}
|
|
@@ -176,7 +199,7 @@ async function setupTelemetry (app, opts) {
|
|
|
176
199
|
}
|
|
177
200
|
}
|
|
178
201
|
|
|
179
|
-
const
|
|
202
|
+
const endHTTPSpan = async (request, reply) => {
|
|
180
203
|
const span = request.span
|
|
181
204
|
if (span) {
|
|
182
205
|
const spanStatus = { code: SpanStatusCode.OK }
|
|
@@ -189,9 +212,9 @@ async function setupTelemetry (app, opts) {
|
|
|
189
212
|
}
|
|
190
213
|
}
|
|
191
214
|
|
|
192
|
-
app.addHook('onRequest',
|
|
215
|
+
app.addHook('onRequest', startHTTPSpan)
|
|
193
216
|
app.addHook('onSend', injectPropagationHeadersInReply)
|
|
194
|
-
app.addHook('onResponse',
|
|
217
|
+
app.addHook('onResponse', endHTTPSpan)
|
|
195
218
|
app.addHook('onError', setErrorInSpan)
|
|
196
219
|
app.addHook('onClose', async function () {
|
|
197
220
|
try {
|
|
@@ -206,9 +229,9 @@ async function setupTelemetry (app, opts) {
|
|
|
206
229
|
const context = span.context
|
|
207
230
|
const headers = {}
|
|
208
231
|
propagator.inject(context, headers, {
|
|
209
|
-
set (
|
|
232
|
+
set (_carrier, key, value) {
|
|
210
233
|
headers[key] = value
|
|
211
|
-
}
|
|
234
|
+
},
|
|
212
235
|
})
|
|
213
236
|
return headers
|
|
214
237
|
}
|
|
@@ -218,12 +241,15 @@ async function setupTelemetry (app, opts) {
|
|
|
218
241
|
// So the client caller is responible of:
|
|
219
242
|
// - setting the propagatorHeaders in the client request
|
|
220
243
|
// - closing the span
|
|
221
|
-
const
|
|
244
|
+
const startHTTPSpanClient = (url, method, ctx) => {
|
|
222
245
|
let context = ctx || new PlatformaticContext()
|
|
223
246
|
const urlObj = fastUri.parse(url)
|
|
224
247
|
|
|
225
248
|
if (skipOperations.includes(`${method}${urlObj.path}`)) {
|
|
226
|
-
app.log.debug(
|
|
249
|
+
app.log.debug(
|
|
250
|
+
{ operation: `${method}${urlObj.path}` },
|
|
251
|
+
'Skipping telemetry'
|
|
252
|
+
)
|
|
227
253
|
return
|
|
228
254
|
}
|
|
229
255
|
|
|
@@ -247,7 +273,7 @@ async function setupTelemetry (app, opts) {
|
|
|
247
273
|
'http.request.method': method,
|
|
248
274
|
'url.full': url,
|
|
249
275
|
'url.path': urlObj.path,
|
|
250
|
-
'url.scheme': urlObj.scheme
|
|
276
|
+
'url.scheme': urlObj.scheme,
|
|
251
277
|
}
|
|
252
278
|
: {}
|
|
253
279
|
span.setAttributes(attributes)
|
|
@@ -260,7 +286,7 @@ async function setupTelemetry (app, opts) {
|
|
|
260
286
|
return { span, telemetryHeaders }
|
|
261
287
|
}
|
|
262
288
|
|
|
263
|
-
const
|
|
289
|
+
const endHTTPSpanClient = (span, response) => {
|
|
264
290
|
/* istanbul ignore next */
|
|
265
291
|
if (!span) {
|
|
266
292
|
return
|
|
@@ -271,13 +297,13 @@ async function setupTelemetry (app, opts) {
|
|
|
271
297
|
spanStatus.code = SpanStatusCode.ERROR
|
|
272
298
|
}
|
|
273
299
|
span.setAttributes({
|
|
274
|
-
'http.response.status_code': response.statusCode
|
|
300
|
+
'http.response.status_code': response.statusCode,
|
|
275
301
|
})
|
|
276
302
|
span.setStatus(spanStatus)
|
|
277
303
|
} else {
|
|
278
304
|
span.setStatus({
|
|
279
305
|
code: SpanStatusCode.ERROR,
|
|
280
|
-
message: 'No response received'
|
|
306
|
+
message: 'No response received',
|
|
281
307
|
})
|
|
282
308
|
}
|
|
283
309
|
span.end()
|
|
@@ -291,17 +317,17 @@ async function setupTelemetry (app, opts) {
|
|
|
291
317
|
span.setAttributes(formatSpanAttributes.error(error))
|
|
292
318
|
}
|
|
293
319
|
|
|
294
|
-
//
|
|
295
|
-
const
|
|
320
|
+
// In the generic "startSpan" the attributes here are specified by the caller
|
|
321
|
+
const startSpan = (name, ctx, attributes = {}, kind = SpanKind.INTERNAL) => {
|
|
296
322
|
const context = ctx || new PlatformaticContext()
|
|
297
323
|
const span = tracer.startSpan(name, {}, context)
|
|
298
|
-
span.kind =
|
|
324
|
+
span.kind = kind
|
|
299
325
|
span.setAttributes(attributes)
|
|
300
326
|
span.context = context
|
|
301
327
|
return span
|
|
302
328
|
}
|
|
303
329
|
|
|
304
|
-
const
|
|
330
|
+
const endSpan = (span, error) => {
|
|
305
331
|
/* istanbul ignore next */
|
|
306
332
|
if (!span) {
|
|
307
333
|
return
|
|
@@ -309,7 +335,7 @@ async function setupTelemetry (app, opts) {
|
|
|
309
335
|
if (error) {
|
|
310
336
|
span.setStatus({
|
|
311
337
|
code: SpanStatusCode.ERROR,
|
|
312
|
-
message: error.message
|
|
338
|
+
message: error.message,
|
|
313
339
|
})
|
|
314
340
|
} else {
|
|
315
341
|
const spanStatus = { code: SpanStatusCode.OK }
|
|
@@ -320,11 +346,12 @@ async function setupTelemetry (app, opts) {
|
|
|
320
346
|
|
|
321
347
|
app.decorate('openTelemetry', {
|
|
322
348
|
...openTelemetryAPIs,
|
|
323
|
-
|
|
324
|
-
|
|
349
|
+
startHTTPSpanClient,
|
|
350
|
+
endHTTPSpanClient,
|
|
325
351
|
setErrorInSpanClient,
|
|
326
|
-
|
|
327
|
-
|
|
352
|
+
startSpan,
|
|
353
|
+
endSpan,
|
|
354
|
+
SpanKind,
|
|
328
355
|
})
|
|
329
356
|
}
|
|
330
357
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/telemetry",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.3",
|
|
4
4
|
"description": "OpenTelemetry integration for Platformatic",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Marco Piraccini <marco.piraccini@gmail.com>",
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
},
|
|
11
11
|
"license": "Apache-2.0",
|
|
12
12
|
"devDependencies": {
|
|
13
|
-
"borp": "^0.
|
|
13
|
+
"borp": "^0.17.0",
|
|
14
14
|
"fastify": "^4.26.2",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
15
|
+
"neostandard": "^0.11.1",
|
|
16
|
+
"typescript": "^5.5.4"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@fastify/swagger": "^8.14.0",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"fastify-plugin": "^4.5.1"
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
|
-
"test": "npm run lint
|
|
32
|
-
"lint": "
|
|
31
|
+
"test": "npm run lint && borp",
|
|
32
|
+
"lint": "eslint"
|
|
33
33
|
}
|
|
34
34
|
}
|