@platformatic/telemetry 3.4.1 → 3.5.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.
@@ -1,21 +1,84 @@
1
- 'use strict'
2
-
3
- const {
4
- ConsoleSpanExporter,
1
+ import { formatParamUrl } from '@fastify/swagger'
2
+ import { SpanKind, SpanStatusCode } from '@opentelemetry/api'
3
+ import { resourceFromAttributes } from '@opentelemetry/resources'
4
+ import {
5
5
  BatchSpanProcessor,
6
- SimpleSpanProcessor,
6
+ ConsoleSpanExporter,
7
7
  InMemorySpanExporter,
8
- } = require('@opentelemetry/sdk-trace-base')
9
- const {
10
- SemanticResourceAttributes,
11
- } = require('@opentelemetry/semantic-conventions')
12
- const { Resource } = require('@opentelemetry/resources')
13
- const { PlatformaticTracerProvider } = require('./platformatic-trace-provider')
8
+ SimpleSpanProcessor
9
+ } from '@opentelemetry/sdk-trace-base'
10
+ import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'
11
+ import fastUri from 'fast-uri'
12
+ import { createRequire } from 'node:module'
13
+ import { fastifyTextMapGetter, fastifyTextMapSetter } from './fastify-text-map.js'
14
+ import { FileSpanExporter } from './file-span-exporter.js'
15
+ import { PlatformaticContext } from './platformatic-context.js'
16
+ import { PlatformaticTracerProvider } from './platformatic-trace-provider.js'
17
+
18
+ import { name as moduleName, version as moduleVersion } from './version.js'
19
+
20
+ // Platformatic telemetry plugin.
21
+ // Supported Exporters:
22
+ // - console
23
+ // - otlp: (which also supports jaeger, see: https://opentelemetry.io/docs/instrumentation/js/exporters/#otlp-endpoint)
24
+ // - zipkin (https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-exporter-zipkin/README.md)
25
+ // - memory: for testing
26
+
27
+ export function formatSpanName (request, route) {
28
+ if (route) {
29
+ route = formatParamUrl(route)
30
+ }
31
+
32
+ const { method, url } = request
33
+ return `${method} ${route ?? url}`
34
+ }
14
35
 
15
- const { name: moduleName, version: moduleVersion } = require('../package.json')
36
+ export const formatSpanAttributes = {
37
+ request (request, route) {
38
+ const { hostname, method, url, protocol = 'http' } = request
39
+ // Inspired by: https://github.com/fastify/fastify-url-data/blob/master/plugin.js#L11
40
+ const fullUrl = `${protocol}://${hostname}${url}`
41
+ const urlData = fastUri.parse(fullUrl)
16
42
 
17
- const setupTelemetry = (opts, logger) => {
18
- const { serviceName, version } = opts
43
+ const attributes = {
44
+ 'server.address': hostname,
45
+ 'server.port': urlData.port,
46
+ 'http.request.method': method,
47
+ 'url.full': fullUrl,
48
+ 'url.path': urlData.path,
49
+ 'url.scheme': protocol
50
+ }
51
+
52
+ if (route) {
53
+ attributes['http.route'] = route
54
+ }
55
+
56
+ if (urlData.query) {
57
+ attributes['url.query'] = urlData.query
58
+ }
59
+
60
+ if (urlData.fragment) {
61
+ attributes['url.fragment'] = urlData.fragment
62
+ }
63
+
64
+ return attributes
65
+ },
66
+ reply (reply) {
67
+ return {
68
+ 'http.response.status_code': reply.statusCode
69
+ }
70
+ },
71
+ error (error) {
72
+ return {
73
+ 'error.name': error.name,
74
+ 'error.message': error.message,
75
+ 'error.stack': error.stack
76
+ }
77
+ }
78
+ }
79
+
80
+ const initTelemetry = (opts, logger) => {
81
+ const { applicationName, version } = opts
19
82
  let exporter = opts.exporter
20
83
  if (!exporter) {
21
84
  logger.warn('No exporter configured, defaulting to console.')
@@ -25,48 +88,51 @@ const setupTelemetry = (opts, logger) => {
25
88
  const exporters = Array.isArray(exporter) ? exporter : [exporter]
26
89
 
27
90
  logger.debug(
28
- `Setting up telemetry for service: ${serviceName}${version ? ' version: ' + version : ''} with exporter of type ${exporter.type}`
91
+ `Setting up platformatic telemetry for application: ${applicationName}${version ? ' version: ' + version : ''} with exporter of type ${exporter.type}`
29
92
  )
30
93
 
31
94
  const provider = new PlatformaticTracerProvider({
32
- resource: new Resource({
33
- [SemanticResourceAttributes.SERVICE_NAME]: serviceName,
34
- [SemanticResourceAttributes.SERVICE_VERSION]: version,
35
- }),
95
+ resource: resourceFromAttributes({
96
+ [ATTR_SERVICE_NAME]: applicationName,
97
+ [ATTR_SERVICE_VERSION]: version
98
+ })
36
99
  })
37
100
 
38
101
  const exporterObjs = []
39
102
  const spanProcessors = []
103
+ const require = createRequire(import.meta.url)
40
104
  for (const exporter of exporters) {
41
105
  // Exporter config:
42
106
  // https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_exporter_zipkin.ExporterConfig.html
43
- const exporterOptions = { ...exporter.options, serviceName }
107
+ const exporterOptions = { ...exporter.options, applicationName }
44
108
 
45
109
  let exporterObj
46
110
  if (exporter.type === 'console') {
47
111
  exporterObj = new ConsoleSpanExporter(exporterOptions)
48
112
  } else if (exporter.type === 'otlp') {
49
113
  // 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.
50
- const {
51
- OTLPTraceExporter,
52
- } = require('@opentelemetry/exporter-trace-otlp-proto')
114
+ const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-proto')
53
115
  exporterObj = new OTLPTraceExporter(exporterOptions)
54
116
  } else if (exporter.type === 'zipkin') {
55
117
  const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin')
56
118
  exporterObj = new ZipkinExporter(exporterOptions)
57
119
  } else if (exporter.type === 'memory') {
58
120
  exporterObj = new InMemorySpanExporter()
121
+ } else if (exporter.type === 'file') {
122
+ exporterObj = new FileSpanExporter(exporterOptions)
59
123
  } else {
60
- logger.warn(
61
- `Unknown exporter type: ${exporter.type}, defaulting to console.`
62
- )
124
+ logger.warn(`Unknown exporter type: ${exporter.type}, defaulting to console.`)
63
125
  exporterObj = new ConsoleSpanExporter(exporterOptions)
64
126
  }
65
127
 
128
+ let spanProcessor
66
129
  // We use a SimpleSpanProcessor for the console/memory exporters and a BatchSpanProcessor for the others.
67
- const spanProcessor = ['memory', 'console'].includes(exporter.type)
68
- ? new SimpleSpanProcessor(exporterObj)
69
- : new BatchSpanProcessor(exporterObj)
130
+ // , unless "processor" is set to "simple" (used only in tests)
131
+ if (exporter.processor === 'simple' || ['memory', 'console', 'file'].includes(exporter.type)) {
132
+ spanProcessor = new SimpleSpanProcessor(exporterObj)
133
+ } else {
134
+ spanProcessor = new BatchSpanProcessor(exporterObj)
135
+ }
70
136
  spanProcessors.push(spanProcessor)
71
137
  exporterObjs.push(exporterObj)
72
138
  }
@@ -78,4 +144,201 @@ const setupTelemetry = (opts, logger) => {
78
144
  return { tracer, exporters: exporterObjs, propagator, provider, spanProcessors }
79
145
  }
80
146
 
81
- module.exports = setupTelemetry
147
+ export function setupTelemetry (opts, logger) {
148
+ const openTelemetryAPIs = initTelemetry(opts, logger)
149
+ const { tracer, propagator, provider } = openTelemetryAPIs
150
+ const skipOperations =
151
+ opts?.skip?.map(skip => {
152
+ const { method, path } = skip
153
+ return `${method}${path}`
154
+ }) || []
155
+
156
+ const startHTTPSpan = async (request, reply) => {
157
+ if (skipOperations.includes(`${request.method}${request.url}`)) {
158
+ request.log.debug({ operation: `${request.method}${request.url}` }, 'Skipping telemetry')
159
+ return
160
+ }
161
+
162
+ // We populate the context with the incoming request headers
163
+ let context = propagator.extract(new PlatformaticContext(), request, fastifyTextMapGetter)
164
+
165
+ const route = request.routeOptions?.url ?? null
166
+ const span = tracer.startSpan(formatSpanName(request, route), {}, context)
167
+ span.kind = SpanKind.SERVER
168
+ // Next 2 lines are needed by W3CTraceContextPropagator
169
+ context = context.setSpan(span)
170
+ span.setAttributes(formatSpanAttributes.request(request, route))
171
+ span.context = context
172
+ // Inject the propagation headers
173
+ propagator.inject(context, reply, fastifyTextMapSetter)
174
+ request.span = span
175
+ }
176
+
177
+ const setErrorInSpan = async (request, _reply, error) => {
178
+ const span = request.span
179
+ span.setAttributes(formatSpanAttributes.error(error))
180
+ }
181
+
182
+ const endHTTPSpan = async (request, reply) => {
183
+ const span = request.span
184
+ if (span) {
185
+ propagator.inject(span.context, reply, fastifyTextMapSetter)
186
+ const spanStatus = { code: SpanStatusCode.OK }
187
+ if (reply.statusCode >= 400) {
188
+ spanStatus.code = SpanStatusCode.ERROR
189
+ }
190
+ span.setAttributes(formatSpanAttributes.reply(reply))
191
+ span.setStatus(spanStatus)
192
+ span.end()
193
+ }
194
+ }
195
+
196
+ //* Client APIs
197
+ const getSpanPropagationHeaders = span => {
198
+ const context = span.context
199
+ const headers = {}
200
+ propagator.inject(context, headers, {
201
+ set (_carrier, key, value) {
202
+ headers[key] = value
203
+ }
204
+ })
205
+ return headers
206
+ }
207
+
208
+ // If a context is passed, is used. Otherwise is a new one is created.
209
+ // Note that in this case we don't set the span in request, as this is a client call.
210
+ // So the client caller is responible of:
211
+ // - setting the propagatorHeaders in the client request
212
+ // - closing the span
213
+ const startHTTPSpanClient = (url, method, ctx) => {
214
+ let context = ctx || new PlatformaticContext()
215
+ const urlObj = fastUri.parse(url)
216
+
217
+ if (skipOperations.includes(`${method}${urlObj.path}`)) {
218
+ logger.debug({ operation: `${method}${urlObj.path}` }, 'Skipping telemetry')
219
+ return
220
+ }
221
+
222
+ /* istanbul ignore next */
223
+ method = method || ''
224
+ let name
225
+ if (urlObj.port) {
226
+ name = `${method} ${urlObj.scheme}://${urlObj.host}:${urlObj.port}${urlObj.path}`
227
+ } else {
228
+ name = `${method} ${urlObj.scheme}://${urlObj.host}${urlObj.path}`
229
+ }
230
+
231
+ const span = tracer.startSpan(name, {}, context)
232
+ span.kind = SpanKind.CLIENT
233
+
234
+ /* istanbul ignore next */
235
+ const attributes = url
236
+ ? {
237
+ 'server.address': urlObj.host,
238
+ 'server.port': urlObj.port,
239
+ 'http.request.method': method,
240
+ 'url.full': url,
241
+ 'url.path': urlObj.path,
242
+ 'url.scheme': urlObj.scheme
243
+ }
244
+ : {}
245
+ span.setAttributes(attributes)
246
+
247
+ // Next 2 lines are needed by W3CTraceContextPropagator
248
+ context = context.setSpan(span)
249
+ span.context = context
250
+
251
+ const telemetryHeaders = getSpanPropagationHeaders(span)
252
+ return { span, telemetryHeaders }
253
+ }
254
+
255
+ const endHTTPSpanClient = (span, response) => {
256
+ /* istanbul ignore next */
257
+ if (!span) {
258
+ return
259
+ }
260
+ if (response) {
261
+ const spanStatus = { code: SpanStatusCode.OK }
262
+ if (response.statusCode >= 400) {
263
+ spanStatus.code = SpanStatusCode.ERROR
264
+ }
265
+ span.setAttributes({
266
+ 'http.response.status_code': response.statusCode
267
+ })
268
+
269
+ const httpCacheId = response.headers?.['x-plt-http-cache-id']
270
+ const isCacheHit = response.headers?.age !== undefined
271
+ if (httpCacheId) {
272
+ span.setAttributes({
273
+ 'http.cache.id': httpCacheId,
274
+ 'http.cache.hit': isCacheHit.toString()
275
+ })
276
+ }
277
+
278
+ span.setStatus(spanStatus)
279
+ } else {
280
+ span.setStatus({
281
+ code: SpanStatusCode.ERROR,
282
+ message: 'No response received'
283
+ })
284
+ }
285
+ span.end()
286
+ }
287
+
288
+ const setErrorInSpanClient = (span, error) => {
289
+ /* istanbul ignore next */
290
+ if (!span) {
291
+ return
292
+ }
293
+ span.setAttributes(formatSpanAttributes.error(error))
294
+ }
295
+
296
+ // In the generic "startSpan" the attributes here are specified by the caller
297
+ const startSpan = (name, ctx, attributes = {}, kind = SpanKind.INTERNAL) => {
298
+ const context = ctx || new PlatformaticContext()
299
+ const span = tracer.startSpan(name, {}, context)
300
+ span.kind = kind
301
+ span.setAttributes(attributes)
302
+ span.context = context
303
+ return span
304
+ }
305
+
306
+ const endSpan = (span, error) => {
307
+ /* istanbul ignore next */
308
+ if (!span) {
309
+ return
310
+ }
311
+ if (error) {
312
+ span.setStatus({
313
+ code: SpanStatusCode.ERROR,
314
+ message: error.message
315
+ })
316
+ } else {
317
+ const spanStatus = { code: SpanStatusCode.OK }
318
+ span.setStatus(spanStatus)
319
+ }
320
+ span.end()
321
+ }
322
+
323
+ // Unfortunately, this must be async, because of: https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_sdk_trace_base.SpanProcessor.html#shutdown
324
+ const shutdown = async () => {
325
+ try {
326
+ await provider.shutdown()
327
+ } catch (err) {
328
+ logger.error({ err }, 'Error shutting down telemetry provider')
329
+ }
330
+ }
331
+
332
+ return {
333
+ startHTTPSpan,
334
+ endHTTPSpan,
335
+ setErrorInSpan,
336
+ startHTTPSpanClient,
337
+ endHTTPSpanClient,
338
+ setErrorInSpanClient,
339
+ startSpan,
340
+ endSpan,
341
+ shutdown,
342
+ openTelemetryAPIs
343
+ }
344
+ }
package/lib/telemetry.js CHANGED
@@ -1,271 +1,30 @@
1
- 'use strict'
1
+ import { SpanKind } from '@opentelemetry/api'
2
+ import fp from 'fastify-plugin'
3
+ import { setupTelemetry } from './telemetry-config.js'
2
4
 
3
- const fp = require('fastify-plugin')
4
- const { SpanStatusCode, SpanKind } = require('@opentelemetry/api')
5
- const { PlatformaticContext } = require('./platformatic-context')
6
- const {
7
- fastifyTextMapGetter,
8
- fastifyTextMapSetter,
9
- } = require('./fastify-text-map')
10
- const setup = require('./telemetry-config')
11
- const { formatParamUrl } = require('@fastify/swagger')
12
- const fastUri = require('fast-uri')
13
-
14
- // Platformatic telemetry plugin.
15
- // Supported Exporters:
16
- // - console
17
- // - otlp: (which also supports jaeger, see: https://opentelemetry.io/docs/instrumentation/js/exporters/#otlp-endpoint)
18
- // - zipkin (https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-exporter-zipkin/README.md)
19
- // - memory: for testing
20
-
21
- // This has been partially copied and modified from @autotelic/opentelemetry: https://github.com/autotelic/fastify-opentelemetry/blob/main/index.js
22
- // , according with [MIT license](https://github.com/autotelic/fastify-opentelemetry/blob/main/LICENSE.md):
23
- // MIT License
24
- // Copyright (c) 2020 autotelic
25
- // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
26
- // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
27
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
- const extractPath = (request) => {
29
- // We must user RouterPath, because otherwise `/test/123` will be considered as
30
- // a different operation than `/test/321`. In case is not set (this should actually happen only for HTTP/404) we fallback to the path.
31
- const { routeOptions, url } = request
32
- let path
33
- const routerPath = routeOptions && routeOptions.url
34
- if (routerPath) {
35
- path = formatParamUrl(routerPath)
36
- } else {
37
- path = url
5
+ // Telemetry fastify plugin
6
+ async function telemetry (app, opts) {
7
+ if (opts.enabled === false) {
8
+ return
38
9
  }
39
- return path
40
- }
41
-
42
- function formatSpanName (request, path) {
43
- const { method } = request
44
- /* istanbul ignore next */
45
- return path ? `${method} ${path}` : method
46
- }
47
-
48
- const formatSpanAttributes = {
49
- request (request, path) {
50
- const { hostname, method, url, protocol } = request
51
- // Inspired by: https://github.com/fastify/fastify-url-data/blob/master/plugin.js#L11
52
- const urlData = fastUri.parse(`${protocol}://${hostname}${url}`)
53
- return {
54
- 'server.address': hostname,
55
- 'server.port': urlData.port,
56
- 'http.request.method': method,
57
- 'url.path': path,
58
- 'url.scheme': protocol,
59
- }
60
- },
61
- reply (reply) {
62
- return {
63
- 'http.response.status_code': reply.statusCode,
64
- }
65
- },
66
- error (error) {
67
- return {
68
- 'error.name': error.name,
69
- 'error.message': error.message,
70
- 'error.stack': error.stack,
71
- }
72
- },
73
- }
74
-
75
- async function setupTelemetry (app, opts) {
76
- const openTelemetryAPIs = setup(opts, app.log)
77
- const { tracer, propagator, provider } = openTelemetryAPIs
78
- const skipOperations =
79
- opts?.skip?.map((skip) => {
80
- const { method, path } = skip
81
- return `${method}${path}`
82
- }) || []
83
-
84
- // expose the span as a request decorator
85
- app.decorateRequest('span')
86
-
87
- const startHTTPSpan = async (request) => {
88
- if (skipOperations.includes(`${request.method}${request.url}`)) {
89
- request.log.debug(
90
- { operation: `${request.method}${request.url}` },
91
- 'Skipping telemetry'
92
- )
93
- return
94
- }
95
-
96
- // We populate the context with the incoming request headers
97
- let context = propagator.extract(
98
- new PlatformaticContext(),
99
- request,
100
- fastifyTextMapGetter
101
- )
102
10
 
103
- const path = extractPath(request)
104
- const span = tracer.startSpan(formatSpanName(request, path), {}, context)
105
- span.kind = SpanKind.SERVER
106
- // Next 2 lines are needed by W3CTraceContextPropagator
107
- context = context.setSpan(span)
108
- span.setAttributes(formatSpanAttributes.request(request, path))
109
- span.context = context
110
- request.span = span
111
- }
112
-
113
- const setErrorInSpan = async (request, _reply, error) => {
114
- const span = request.span
115
- span.setAttributes(formatSpanAttributes.error(error))
116
- }
117
-
118
- const injectPropagationHeadersInReply = async (request, reply) => {
119
- if (request.span) {
120
- const context = request.span.context
121
- propagator.inject(context, reply, fastifyTextMapSetter)
122
- }
123
- }
124
-
125
- const endHTTPSpan = async (request, reply) => {
126
- const span = request.span
127
- if (span) {
128
- const spanStatus = { code: SpanStatusCode.OK }
129
- if (reply.statusCode >= 400) {
130
- spanStatus.code = SpanStatusCode.ERROR
131
- }
132
- span.setAttributes(formatSpanAttributes.reply(reply))
133
- span.setStatus(spanStatus)
134
- span.end()
135
- }
136
- }
11
+ const {
12
+ startHTTPSpan,
13
+ endHTTPSpan,
14
+ setErrorInSpan,
15
+ startSpan,
16
+ endSpan,
17
+ startHTTPSpanClient,
18
+ endHTTPSpanClient,
19
+ setErrorInSpanClient,
20
+ shutdown,
21
+ openTelemetryAPIs
22
+ } = setupTelemetry(opts, app.log)
137
23
 
138
24
  app.addHook('onRequest', startHTTPSpan)
139
- app.addHook('onSend', injectPropagationHeadersInReply)
140
25
  app.addHook('onResponse', endHTTPSpan)
141
26
  app.addHook('onError', setErrorInSpan)
142
- app.addHook('onClose', async function () {
143
- try {
144
- await provider.shutdown()
145
- } catch (err) {
146
- app.log.error({ err }, 'Error shutting down telemetry provider')
147
- }
148
- })
149
-
150
- //* Client APIs
151
- const getSpanPropagationHeaders = (span) => {
152
- const context = span.context
153
- const headers = {}
154
- propagator.inject(context, headers, {
155
- set (_carrier, key, value) {
156
- headers[key] = value
157
- },
158
- })
159
- return headers
160
- }
161
-
162
- // If a context is passed, is used. Otherwise is a new one is created.
163
- // Note that in this case we don't set the span in request, as this is a client call.
164
- // So the client caller is responible of:
165
- // - setting the propagatorHeaders in the client request
166
- // - closing the span
167
- const startHTTPSpanClient = (url, method, ctx) => {
168
- let context = ctx || new PlatformaticContext()
169
- const urlObj = fastUri.parse(url)
170
-
171
- if (skipOperations.includes(`${method}${urlObj.path}`)) {
172
- app.log.debug(
173
- { operation: `${method}${urlObj.path}` },
174
- 'Skipping telemetry'
175
- )
176
- return
177
- }
178
-
179
- /* istanbul ignore next */
180
- method = method || ''
181
- let name
182
- if (urlObj.port) {
183
- name = `${method} ${urlObj.scheme}://${urlObj.host}:${urlObj.port}${urlObj.path}`
184
- } else {
185
- name = `${method} ${urlObj.scheme}://${urlObj.host}${urlObj.path}`
186
- }
187
-
188
- const span = tracer.startSpan(name, {}, context)
189
- span.kind = SpanKind.CLIENT
190
-
191
- /* istanbul ignore next */
192
- const attributes = url
193
- ? {
194
- 'server.address': urlObj.host,
195
- 'server.port': urlObj.port,
196
- 'http.request.method': method,
197
- 'url.full': url,
198
- 'url.path': urlObj.path,
199
- 'url.scheme': urlObj.scheme,
200
- }
201
- : {}
202
- span.setAttributes(attributes)
203
-
204
- // Next 2 lines are needed by W3CTraceContextPropagator
205
- context = context.setSpan(span)
206
- span.context = context
207
-
208
- const telemetryHeaders = getSpanPropagationHeaders(span)
209
- return { span, telemetryHeaders }
210
- }
211
-
212
- const endHTTPSpanClient = (span, response) => {
213
- /* istanbul ignore next */
214
- if (!span) {
215
- return
216
- }
217
- if (response) {
218
- const spanStatus = { code: SpanStatusCode.OK }
219
- if (response.statusCode >= 400) {
220
- spanStatus.code = SpanStatusCode.ERROR
221
- }
222
- span.setAttributes({
223
- 'http.response.status_code': response.statusCode,
224
- })
225
- span.setStatus(spanStatus)
226
- } else {
227
- span.setStatus({
228
- code: SpanStatusCode.ERROR,
229
- message: 'No response received',
230
- })
231
- }
232
- span.end()
233
- }
234
-
235
- const setErrorInSpanClient = (span, error) => {
236
- /* istanbul ignore next */
237
- if (!span) {
238
- return
239
- }
240
- span.setAttributes(formatSpanAttributes.error(error))
241
- }
242
-
243
- // In the generic "startSpan" the attributes here are specified by the caller
244
- const startSpan = (name, ctx, attributes = {}, kind = SpanKind.INTERNAL) => {
245
- const context = ctx || new PlatformaticContext()
246
- const span = tracer.startSpan(name, {}, context)
247
- span.kind = kind
248
- span.setAttributes(attributes)
249
- span.context = context
250
- return span
251
- }
252
-
253
- const endSpan = (span, error) => {
254
- /* istanbul ignore next */
255
- if (!span) {
256
- return
257
- }
258
- if (error) {
259
- span.setStatus({
260
- code: SpanStatusCode.ERROR,
261
- message: error.message,
262
- })
263
- } else {
264
- const spanStatus = { code: SpanStatusCode.OK }
265
- span.setStatus(spanStatus)
266
- }
267
- span.end()
268
- }
27
+ app.addHook('onClose', shutdown)
269
28
 
270
29
  app.decorate('openTelemetry', {
271
30
  ...openTelemetryAPIs,
@@ -274,8 +33,9 @@ async function setupTelemetry (app, opts) {
274
33
  setErrorInSpanClient,
275
34
  startSpan,
276
35
  endSpan,
277
- SpanKind,
36
+ shutdown,
37
+ SpanKind
278
38
  })
279
39
  }
280
40
 
281
- module.exports = fp(setupTelemetry)
41
+ export default fp(telemetry)