@platformatic/telemetry 3.0.0-alpha.4 → 3.0.0-alpha.6

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 CHANGED
@@ -1,9 +1,5 @@
1
- 'use strict'
1
+ import neostandard from 'neostandard'
2
2
 
3
- const neostandard = require('neostandard')
4
-
5
- module.exports = neostandard(
6
- {
7
- ignores: ['**/.next', '**/dist', '**/tmp', 'test/fixtures/**'],
8
- }
9
- )
3
+ export default neostandard({
4
+ ignores: ['**/.next', '**/dist', '**/tmp', 'test/fixtures/**']
5
+ })
package/index.js CHANGED
@@ -1,11 +1,3 @@
1
- 'use strict'
2
-
3
- const telemetry = require('./lib/telemetry')
4
- const { createTelemetryThreadInterceptorHooks } = require('./lib/thread-interceptor-hooks')
5
- const schema = require('./lib/schema')
6
-
7
- module.exports = {
8
- telemetry,
9
- createTelemetryThreadInterceptorHooks,
10
- schema,
11
- }
1
+ export * as schema from './lib/schema.js'
2
+ export * as telemetry from './lib/telemetry.js'
3
+ export { createTelemetryThreadInterceptorHooks } from './lib/thread-interceptor-hooks.js'
@@ -1,22 +1,15 @@
1
- 'use strict'
2
-
3
- const fastifyTextMapGetter = {
1
+ export const fastifyTextMapGetter = {
4
2
  get (request, key) {
5
3
  return request.headers[key]
6
4
  },
7
5
  /* istanbul ignore next */
8
6
  keys (request) {
9
7
  return Object.keys(request.headers)
10
- },
8
+ }
11
9
  }
12
10
 
13
- const fastifyTextMapSetter = {
11
+ export const fastifyTextMapSetter = {
14
12
  set (reply, key, value) {
15
13
  reply.headers({ [key]: value })
16
- },
17
- }
18
-
19
- module.exports = {
20
- fastifyTextMapGetter,
21
- fastifyTextMapSetter,
14
+ }
22
15
  }
@@ -1,12 +1,10 @@
1
- 'use strict'
2
-
3
- const { ExportResultCode, hrTimeToMicroseconds } = require('@opentelemetry/core')
4
- const { resolve: resolvePath } = require('node:path')
5
- const { appendFileSync } = require('node:fs')
6
- const { workerData } = require('node:worker_threads')
1
+ import { ExportResultCode, hrTimeToMicroseconds } from '@opentelemetry/core'
2
+ import { appendFileSync } from 'node:fs'
3
+ import { resolve as resolvePath } from 'node:path'
4
+ import { workerData } from 'node:worker_threads'
7
5
 
8
6
  // Export spans to a file, mostly for testing purposes.
9
- class FileSpanExporter {
7
+ export class FileSpanExporter {
10
8
  #path
11
9
  constructor (opts) {
12
10
  this.#path = resolvePath(workerData?.dirname ?? process.cwd(), opts.path ?? 'spans.log')
@@ -51,5 +49,3 @@ class FileSpanExporter {
51
49
  }
52
50
  }
53
51
  }
54
-
55
- module.exports = FileSpanExporter
@@ -1,10 +1,8 @@
1
- 'use strict'
1
+ import { createRequire } from 'node:module'
2
+ import { join } from 'node:path'
3
+ import { pathToFileURL } from 'node:url'
2
4
 
3
- const { pathToFileURL } = require('node:url')
4
- const { createRequire } = require('node:module')
5
- const { join } = require('node:path')
6
-
7
- async function importOrLocal ({ projectDir, pkg }) {
5
+ export async function importOrLocal ({ projectDir, pkg }) {
8
6
  try {
9
7
  return import(pkg)
10
8
  } catch (err) {
@@ -14,5 +12,3 @@ async function importOrLocal ({ projectDir, pkg }) {
14
12
  return import(pathToFileURL(fileToImport))
15
13
  }
16
14
  }
17
-
18
- module.exports = importOrLocal
@@ -1,8 +1,6 @@
1
- 'use strict'
2
-
3
1
  // This implements the SpanProcessor interface:
4
2
  // https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-sdk-trace-base/src/SpanProcessor.ts
5
- class MultiSpanProcessor {
3
+ export class MultiSpanProcessor {
6
4
  constructor (_spanProcessors = []) {
7
5
  this._spanProcessors = _spanProcessors
8
6
  }
@@ -35,5 +33,3 @@ class MultiSpanProcessor {
35
33
  return Promise.all(promises)
36
34
  }
37
35
  }
38
-
39
- module.exports = { MultiSpanProcessor }
@@ -1,40 +1,37 @@
1
- 'use strict'
2
-
3
- const process = require('node:process')
4
- const { resourceFromAttributes } = require('@opentelemetry/resources')
5
- const { AsyncLocalStorage } = require('node:async_hooks')
6
- const opentelemetry = require('@opentelemetry/sdk-node')
7
- const FileSpanExporter = require('./file-span-exporter')
8
- const { ATTR_SERVICE_NAME } = require('@opentelemetry/semantic-conventions')
9
- const { workerData } = require('node:worker_threads')
10
- const { resolve } = require('node:path')
11
- const { tmpdir } = require('node:os')
12
- const { abstractLogger } = require('@platformatic/foundation')
13
- const { statSync, readFileSync } = require('node:fs') // We want to have all this synch
14
- const util = require('node:util')
15
- const { getInstrumentations } = require('./pluggable-instrumentations')
16
-
17
- const debuglog = util.debuglog('@platformatic/telemetry')
18
- const {
19
- ConsoleSpanExporter,
1
+ import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'
2
+ import { UndiciInstrumentation } from '@opentelemetry/instrumentation-undici'
3
+ import { resourceFromAttributes } from '@opentelemetry/resources'
4
+ import * as opentelemetry from '@opentelemetry/sdk-node'
5
+ import {
20
6
  BatchSpanProcessor,
21
- SimpleSpanProcessor,
7
+ ConsoleSpanExporter,
22
8
  InMemorySpanExporter,
23
- } = require('@opentelemetry/sdk-trace-base')
9
+ SimpleSpanProcessor
10
+ } from '@opentelemetry/sdk-trace-base'
11
+ import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'
12
+ import { abstractLogger } from '@platformatic/foundation'
13
+ import { AsyncLocalStorage } from 'node:async_hooks'
14
+ import { readFileSync, statSync } from 'node:fs'
15
+ import { createRequire } from 'node:module'
16
+ import { tmpdir } from 'node:os'
17
+ import { resolve } from 'node:path'
18
+ import process from 'node:process'
19
+ import util from 'node:util'
20
+ import { workerData } from 'node:worker_threads'
21
+ import { FileSpanExporter } from './file-span-exporter.js'
22
+ import { getInstrumentations } from './pluggable-instrumentations.js'
24
23
 
25
- const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http')
26
- const {
27
- UndiciInstrumentation,
28
- } = require('@opentelemetry/instrumentation-undici')
24
+ const debuglog = util.debuglog('@platformatic/telemetry')
25
+ const require = createRequire(import.meta.url)
29
26
 
30
27
  // See: https://www.npmjs.com/package/@opentelemetry/instrumentation-http
31
28
  // When this is fixed we should set this to 'http' and fixe the tests
32
29
  // https://github.com/open-telemetry/opentelemetry-js/issues/5103
33
30
  process.env.OTEL_SEMCONV_STABILITY_OPT_IN = 'http/dup'
34
31
 
35
- const setupNodeHTTPTelemetry = async (opts, serviceDir) => {
36
- const { serviceName, instrumentations = [] } = opts
37
- const additionalInstrumentations = await getInstrumentations(instrumentations, serviceDir)
32
+ const setupNodeHTTPTelemetry = async (opts, applicationDir) => {
33
+ const { applicationName, instrumentations = [] } = opts
34
+ const additionalInstrumentations = await getInstrumentations(instrumentations, applicationDir)
38
35
 
39
36
  let exporter = opts.exporter
40
37
  if (!exporter) {
@@ -46,15 +43,13 @@ const setupNodeHTTPTelemetry = async (opts, serviceDir) => {
46
43
  for (const exporter of exporters) {
47
44
  // Exporter config:
48
45
  // https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_exporter_zipkin.ExporterConfig.html
49
- const exporterOptions = { ...exporter.options, serviceName }
46
+ const exporterOptions = { ...exporter.options, applicationName }
50
47
 
51
48
  let exporterObj
52
49
  if (exporter.type === 'console') {
53
50
  exporterObj = new ConsoleSpanExporter(exporterOptions)
54
51
  } else if (exporter.type === 'otlp') {
55
- const {
56
- OTLPTraceExporter,
57
- } = require('@opentelemetry/exporter-trace-otlp-proto')
52
+ const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-proto')
58
53
  exporterObj = new OTLPTraceExporter(exporterOptions)
59
54
  } else if (exporter.type === 'zipkin') {
60
55
  const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin')
@@ -64,9 +59,7 @@ const setupNodeHTTPTelemetry = async (opts, serviceDir) => {
64
59
  } else if (exporter.type === 'file') {
65
60
  exporterObj = new FileSpanExporter(exporterOptions)
66
61
  } else {
67
- abstractLogger.warn(
68
- `Unknown exporter type: ${exporter.type}, defaulting to console.`
69
- )
62
+ abstractLogger.warn(`Unknown exporter type: ${exporter.type}, defaulting to console.`)
70
63
  exporterObj = new ConsoleSpanExporter(exporterOptions)
71
64
  }
72
65
 
@@ -89,7 +82,7 @@ const setupNodeHTTPTelemetry = async (opts, serviceDir) => {
89
82
  spanProcessors, // https://github.com/open-telemetry/opentelemetry-js/issues/4881#issuecomment-2358059714
90
83
  instrumentations: [
91
84
  new UndiciInstrumentation({
92
- responseHook: (span) => {
85
+ responseHook: span => {
93
86
  const store = clientSpansAls.getStore()
94
87
  if (store) {
95
88
  store.span = span
@@ -100,19 +93,20 @@ const setupNodeHTTPTelemetry = async (opts, serviceDir) => {
100
93
  ...additionalInstrumentations
101
94
  ],
102
95
  resource: resourceFromAttributes({
103
- [ATTR_SERVICE_NAME]: serviceName
96
+ [ATTR_SERVICE_NAME]: applicationName
104
97
  })
105
98
  })
106
99
  sdk.start()
107
100
 
108
101
  process.on('SIGTERM', () => {
109
- sdk.shutdown()
102
+ sdk
103
+ .shutdown()
110
104
  .then(() => debuglog('Tracing terminated'))
111
- .catch((error) => debuglog('Error terminating tracing', error))
105
+ .catch(error => debuglog('Error terminating tracing', error))
112
106
  })
113
107
  }
114
108
 
115
- const main = async () => {
109
+ async function main () {
116
110
  let data = null
117
111
  const useWorkerData = !!workerData
118
112
 
@@ -132,11 +126,11 @@ const main = async () => {
132
126
 
133
127
  if (data) {
134
128
  debuglog('Setting up telemetry %o', data)
135
- const serviceDir = data.serviceConfig?.path
136
- const telemetryConfig = useWorkerData ? data?.serviceConfig?.telemetry : data?.telemetryConfig
129
+ const applicationDir = data.applicationConfig?.path
130
+ const telemetryConfig = useWorkerData ? data?.applicationConfig?.telemetry : data?.telemetryConfig
137
131
  if (telemetryConfig) {
138
132
  debuglog('telemetryConfig %o', telemetryConfig)
139
- setupNodeHTTPTelemetry(telemetryConfig, serviceDir)
133
+ setupNodeHTTPTelemetry(telemetryConfig, applicationDir)
140
134
  }
141
135
  }
142
136
  }
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- const { createContextKey } = require('@opentelemetry/api')
1
+ import { createContextKey } from '@opentelemetry/api'
4
2
 
5
3
  // Unfortunately, these kesy are not exported by the OpenTelemetry API :()
6
4
  // And we HAVE to use these keys because are used by the propagators
@@ -9,13 +7,13 @@ const SPAN_KEY = createContextKey('OpenTelemetry Context Key SPAN')
9
7
  // This is basicaklly the same as https://github.com/open-telemetry/opentelemetry-js/blob/main/api/src/context/context.ts#L85
10
8
  // (so just a wrapper around a Map)
11
9
  // Note that mutating the context is not allowed by the OpenTelemetry spec.
12
- class PlatformaticContext {
10
+ export class PlatformaticContext {
13
11
  _currentContext
14
12
 
15
13
  constructor (parentContext) {
16
14
  this._currentContext = parentContext ? new Map(parentContext) : new Map()
17
15
 
18
- this.getValue = (key) => this._currentContext.get(key)
16
+ this.getValue = key => this._currentContext.get(key)
19
17
 
20
18
  // Must create and return a new context
21
19
  this.setValue = (key, value) => {
@@ -26,16 +24,14 @@ class PlatformaticContext {
26
24
 
27
25
  // Must return a new context
28
26
  /* istanbul ignore next */
29
- this.deleteValue = (key) => {
27
+ this.deleteValue = key => {
30
28
  const context = new PlatformaticContext(this._currentContext)
31
29
  context._currentContext.delete(key)
32
30
  return context
33
31
  }
34
32
 
35
- this.setSpan = (span) => {
33
+ this.setSpan = span => {
36
34
  return this.setValue(SPAN_KEY, span)
37
35
  }
38
36
  }
39
37
  }
40
-
41
- module.exports = { PlatformaticContext }
@@ -1,13 +1,14 @@
1
- 'use strict'
1
+ import { CompositePropagator, merge, W3CTraceContextPropagator } from '@opentelemetry/core'
2
+ import { emptyResource } from '@opentelemetry/resources'
3
+ import { AlwaysOnSampler } from '@opentelemetry/sdk-trace-base'
4
+ import { createRequire } from 'node:module'
5
+ import { MultiSpanProcessor } from './multispan-processor.js'
2
6
 
3
- const { emptyResource } = require('@opentelemetry/resources')
4
- const { merge, CompositePropagator, W3CTraceContextPropagator } = require('@opentelemetry/core')
5
- const { AlwaysOnSampler } = require('@opentelemetry/sdk-trace-base')
7
+ const require = createRequire(import.meta.url)
6
8
  // We need to import the Tracer to write our own TracerProvider that does NOT extend the OpenTelemetry one.
7
9
  const { Tracer } = require('@opentelemetry/sdk-trace-base/build/src/Tracer')
8
- const { MultiSpanProcessor } = require('./multispan-processor')
9
10
 
10
- class PlatformaticTracerProvider {
11
+ export class PlatformaticTracerProvider {
11
12
  activeSpanProcessor = null
12
13
  _registeredSpanProcessors = []
13
14
  // This MUST be called `resource`, see: https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-sdk-trace-base/src/Tracer.ts#L57
@@ -18,13 +19,13 @@ class PlatformaticTracerProvider {
18
19
  const mergedConfig = merge(
19
20
  {},
20
21
  {
21
- sampler: new AlwaysOnSampler(),
22
+ sampler: new AlwaysOnSampler()
22
23
  },
23
24
  config
24
25
  )
25
26
  this.resource = mergedConfig.resource ?? emptyResource
26
27
  this._config = Object.assign({}, mergedConfig, {
27
- resource: this.resource,
28
+ resource: this.resource
28
29
  })
29
30
  }
30
31
 
@@ -39,9 +40,7 @@ class PlatformaticTracerProvider {
39
40
  } else {
40
41
  this._registeredSpanProcessors.push(spanProcessor)
41
42
  }
42
- this.activeSpanProcessor = new MultiSpanProcessor(
43
- this._registeredSpanProcessors
44
- )
43
+ this.activeSpanProcessor = new MultiSpanProcessor(this._registeredSpanProcessors)
45
44
  }
46
45
 
47
46
  getActiveSpanProcessor () {
@@ -51,8 +50,8 @@ class PlatformaticTracerProvider {
51
50
  getPropagator () {
52
51
  return new CompositePropagator({
53
52
  propagators: [
54
- new W3CTraceContextPropagator(), // see: https://www.w3.org/TR/trace-context/
55
- ],
53
+ new W3CTraceContextPropagator() // see: https://www.w3.org/TR/trace-context/
54
+ ]
56
55
  })
57
56
  }
58
57
 
@@ -65,5 +64,3 @@ class PlatformaticTracerProvider {
65
64
  return this.activeSpanProcessor.shutdown()
66
65
  }
67
66
  }
68
-
69
- module.exports = { PlatformaticTracerProvider }
@@ -1,41 +1,42 @@
1
- 'use strict'
2
-
3
- const importOrLocal = require('./import-or-local')
1
+ import { importOrLocal } from './import-or-local.js'
4
2
 
5
3
  // These are already set automatically by the runtime, so we throw
6
4
  // if set again.
7
- const defaultInstrumentations = [
8
- '@opentelemetry/instrumentation-http',
9
- '@opentelemetry/instrumentation-undici'
10
- ]
5
+ const defaultInstrumentations = ['@opentelemetry/instrumentation-http', '@opentelemetry/instrumentation-undici']
11
6
 
12
- const getInstrumentationInstance = async (instrumentationConfig, serviceDir) => {
7
+ async function getInstrumentationInstance (instrumentationConfig, applicationDir) {
13
8
  if (typeof instrumentationConfig === 'string') {
14
9
  instrumentationConfig = { package: instrumentationConfig, exportName: 'default', options: {} }
15
10
  }
16
11
  const { package: packageName, exportName = 'default', options = {} } = instrumentationConfig
17
12
 
18
13
  if (defaultInstrumentations.includes(packageName)) {
19
- throw new Error(`Instrumentation package ${packageName} is already included by default, please remove it from your config.`)
14
+ throw new Error(
15
+ `Instrumentation package ${packageName} is already included by default, please remove it from your config.`
16
+ )
20
17
  }
21
18
 
22
19
  let mod
23
20
  try {
24
- mod = await importOrLocal({ pkg: packageName, projectDir: serviceDir })
21
+ mod = await importOrLocal({ pkg: packageName, projectDir: applicationDir })
25
22
  } catch (err) {
26
- throw new Error(`Instrumentation package not found: ${instrumentationConfig.package}, please add it to your dependencies.`)
23
+ throw new Error(
24
+ `Instrumentation package not found: ${instrumentationConfig.package}, please add it to your dependencies.`
25
+ )
27
26
  }
28
27
 
29
28
  let Instrumenter = mod[exportName]
30
29
  if (!Instrumenter || typeof Instrumenter !== 'function') {
31
30
  // Check for for an export that ends with 'Instrumentation'. We need to do that because unfortunately
32
31
  // each instrumenttions has different named export. But all of them ends with 'Instrumentation'.
33
- const possibleExports = Object.keys(mod).filter((key) => key.endsWith('Instrumentation'))
32
+ const possibleExports = Object.keys(mod).filter(key => key.endsWith('Instrumentation'))
34
33
  if (possibleExports.length === 0) {
35
34
  throw new Error(`Instrumentation export not found: ${exportName} in ${packageName}. Please specify in config`)
36
35
  }
37
36
  if (possibleExports.length > 1) {
38
- throw new Error(`Multiple Instrumentation exports found: ${possibleExports} in ${packageName}. Please specify in config`)
37
+ throw new Error(
38
+ `Multiple Instrumentation exports found: ${possibleExports} in ${packageName}. Please specify in config`
39
+ )
39
40
  }
40
41
  Instrumenter = mod[possibleExports[0]]
41
42
  }
@@ -51,15 +52,11 @@ const getInstrumentationInstance = async (instrumentationConfig, serviceDir) =>
51
52
  // "exportName": "RedisInstrumentation",
52
53
  // "options": { "foo": "bar" }
53
54
  // }
54
- const getInstrumentations = async (configs = [], serviceDir) => {
55
+ export async function getInstrumentations (configs = [], applicationDir) {
55
56
  const instrumentations = []
56
57
  for (const instrumentationConfig of configs) {
57
- const instance = await getInstrumentationInstance(instrumentationConfig, serviceDir)
58
+ const instance = await getInstrumentationInstance(instrumentationConfig, applicationDir)
58
59
  instrumentations.push(instance)
59
60
  }
60
61
  return instrumentations
61
62
  }
62
-
63
- module.exports = {
64
- getInstrumentations,
65
- }
package/lib/schema.js CHANGED
@@ -1,4 +1,3 @@
1
- 'use strict'
1
+ import { schemaComponents } from '@platformatic/foundation'
2
2
 
3
- const { schemaComponents } = require('@platformatic/foundation')
4
- module.exports = schemaComponents.telemetry
3
+ export default schemaComponents.telemetry
@@ -1,27 +1,21 @@
1
- 'use strict'
2
-
3
- const { SpanStatusCode, SpanKind } = require('@opentelemetry/api')
4
- const { PlatformaticContext } = require('./platformatic-context')
5
- const {
6
- fastifyTextMapGetter,
7
- fastifyTextMapSetter,
8
- } = require('./fastify-text-map')
9
- const { formatParamUrl } = require('@fastify/swagger')
10
- const fastUri = require('fast-uri')
11
- const { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } = require('@opentelemetry/semantic-conventions')
12
- const {
13
- ConsoleSpanExporter,
1
+ import { formatParamUrl } from '@fastify/swagger'
2
+ import { SpanKind, SpanStatusCode } from '@opentelemetry/api'
3
+ import { resourceFromAttributes } from '@opentelemetry/resources'
4
+ import {
14
5
  BatchSpanProcessor,
15
- SimpleSpanProcessor,
6
+ ConsoleSpanExporter,
16
7
  InMemorySpanExporter,
17
- } = require('@opentelemetry/sdk-trace-base')
18
-
19
- const FileSpanExporter = require('./file-span-exporter')
20
-
21
- const { resourceFromAttributes } = require('@opentelemetry/resources')
22
- const { PlatformaticTracerProvider } = require('./platformatic-trace-provider')
23
-
24
- const { name: moduleName, version: moduleVersion } = require('../package.json')
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'
25
19
 
26
20
  // Platformatic telemetry plugin.
27
21
  // Supported Exporters:
@@ -30,7 +24,7 @@ const { name: moduleName, version: moduleVersion } = require('../package.json')
30
24
  // - zipkin (https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-exporter-zipkin/README.md)
31
25
  // - memory: for testing
32
26
 
33
- function formatSpanName (request, route) {
27
+ export function formatSpanName (request, route) {
34
28
  if (route) {
35
29
  route = formatParamUrl(route)
36
30
  }
@@ -39,7 +33,7 @@ function formatSpanName (request, route) {
39
33
  return `${method} ${route ?? url}`
40
34
  }
41
35
 
42
- const formatSpanAttributes = {
36
+ export const formatSpanAttributes = {
43
37
  request (request, route) {
44
38
  const { hostname, method, url, protocol = 'http' } = request
45
39
  // Inspired by: https://github.com/fastify/fastify-url-data/blob/master/plugin.js#L11
@@ -52,7 +46,7 @@ const formatSpanAttributes = {
52
46
  'http.request.method': method,
53
47
  'url.full': fullUrl,
54
48
  'url.path': urlData.path,
55
- 'url.scheme': protocol,
49
+ 'url.scheme': protocol
56
50
  }
57
51
 
58
52
  if (route) {
@@ -71,20 +65,20 @@ const formatSpanAttributes = {
71
65
  },
72
66
  reply (reply) {
73
67
  return {
74
- 'http.response.status_code': reply.statusCode,
68
+ 'http.response.status_code': reply.statusCode
75
69
  }
76
70
  },
77
71
  error (error) {
78
72
  return {
79
73
  'error.name': error.name,
80
74
  'error.message': error.message,
81
- 'error.stack': error.stack,
75
+ 'error.stack': error.stack
82
76
  }
83
- },
77
+ }
84
78
  }
85
79
 
86
80
  const initTelemetry = (opts, logger) => {
87
- const { serviceName, version } = opts
81
+ const { applicationName, version } = opts
88
82
  let exporter = opts.exporter
89
83
  if (!exporter) {
90
84
  logger.warn('No exporter configured, defaulting to console.')
@@ -94,31 +88,30 @@ const initTelemetry = (opts, logger) => {
94
88
  const exporters = Array.isArray(exporter) ? exporter : [exporter]
95
89
 
96
90
  logger.debug(
97
- `Setting up platformatic 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}`
98
92
  )
99
93
 
100
94
  const provider = new PlatformaticTracerProvider({
101
95
  resource: resourceFromAttributes({
102
- [ATTR_SERVICE_NAME]: serviceName,
103
- [ATTR_SERVICE_VERSION]: version,
104
- }),
96
+ [ATTR_SERVICE_NAME]: applicationName,
97
+ [ATTR_SERVICE_VERSION]: version
98
+ })
105
99
  })
106
100
 
107
101
  const exporterObjs = []
108
102
  const spanProcessors = []
103
+ const require = createRequire(import.meta.url)
109
104
  for (const exporter of exporters) {
110
105
  // Exporter config:
111
106
  // https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_exporter_zipkin.ExporterConfig.html
112
- const exporterOptions = { ...exporter.options, serviceName }
107
+ const exporterOptions = { ...exporter.options, applicationName }
113
108
 
114
109
  let exporterObj
115
110
  if (exporter.type === 'console') {
116
111
  exporterObj = new ConsoleSpanExporter(exporterOptions)
117
112
  } else if (exporter.type === 'otlp') {
118
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.
119
- const {
120
- OTLPTraceExporter,
121
- } = require('@opentelemetry/exporter-trace-otlp-proto')
114
+ const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-proto')
122
115
  exporterObj = new OTLPTraceExporter(exporterOptions)
123
116
  } else if (exporter.type === 'zipkin') {
124
117
  const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin')
@@ -128,9 +121,7 @@ const initTelemetry = (opts, logger) => {
128
121
  } else if (exporter.type === 'file') {
129
122
  exporterObj = new FileSpanExporter(exporterOptions)
130
123
  } else {
131
- logger.warn(
132
- `Unknown exporter type: ${exporter.type}, defaulting to console.`
133
- )
124
+ logger.warn(`Unknown exporter type: ${exporter.type}, defaulting to console.`)
134
125
  exporterObj = new ConsoleSpanExporter(exporterOptions)
135
126
  }
136
127
 
@@ -153,30 +144,23 @@ const initTelemetry = (opts, logger) => {
153
144
  return { tracer, exporters: exporterObjs, propagator, provider, spanProcessors }
154
145
  }
155
146
 
156
- function setupTelemetry (opts, logger) {
147
+ export function setupTelemetry (opts, logger) {
157
148
  const openTelemetryAPIs = initTelemetry(opts, logger)
158
149
  const { tracer, propagator, provider } = openTelemetryAPIs
159
150
  const skipOperations =
160
- opts?.skip?.map((skip) => {
151
+ opts?.skip?.map(skip => {
161
152
  const { method, path } = skip
162
153
  return `${method}${path}`
163
154
  }) || []
164
155
 
165
156
  const startHTTPSpan = async (request, reply) => {
166
157
  if (skipOperations.includes(`${request.method}${request.url}`)) {
167
- request.log.debug(
168
- { operation: `${request.method}${request.url}` },
169
- 'Skipping telemetry'
170
- )
158
+ request.log.debug({ operation: `${request.method}${request.url}` }, 'Skipping telemetry')
171
159
  return
172
160
  }
173
161
 
174
162
  // We populate the context with the incoming request headers
175
- let context = propagator.extract(
176
- new PlatformaticContext(),
177
- request,
178
- fastifyTextMapGetter
179
- )
163
+ let context = propagator.extract(new PlatformaticContext(), request, fastifyTextMapGetter)
180
164
 
181
165
  const route = request.routeOptions?.url ?? null
182
166
  const span = tracer.startSpan(formatSpanName(request, route), {}, context)
@@ -210,13 +194,13 @@ function setupTelemetry (opts, logger) {
210
194
  }
211
195
 
212
196
  //* Client APIs
213
- const getSpanPropagationHeaders = (span) => {
197
+ const getSpanPropagationHeaders = span => {
214
198
  const context = span.context
215
199
  const headers = {}
216
200
  propagator.inject(context, headers, {
217
201
  set (_carrier, key, value) {
218
202
  headers[key] = value
219
- },
203
+ }
220
204
  })
221
205
  return headers
222
206
  }
@@ -231,10 +215,7 @@ function setupTelemetry (opts, logger) {
231
215
  const urlObj = fastUri.parse(url)
232
216
 
233
217
  if (skipOperations.includes(`${method}${urlObj.path}`)) {
234
- logger.debug(
235
- { operation: `${method}${urlObj.path}` },
236
- 'Skipping telemetry'
237
- )
218
+ logger.debug({ operation: `${method}${urlObj.path}` }, 'Skipping telemetry')
238
219
  return
239
220
  }
240
221
 
@@ -258,7 +239,7 @@ function setupTelemetry (opts, logger) {
258
239
  'http.request.method': method,
259
240
  'url.full': url,
260
241
  'url.path': urlObj.path,
261
- 'url.scheme': urlObj.scheme,
242
+ 'url.scheme': urlObj.scheme
262
243
  }
263
244
  : {}
264
245
  span.setAttributes(attributes)
@@ -282,7 +263,7 @@ function setupTelemetry (opts, logger) {
282
263
  spanStatus.code = SpanStatusCode.ERROR
283
264
  }
284
265
  span.setAttributes({
285
- 'http.response.status_code': response.statusCode,
266
+ 'http.response.status_code': response.statusCode
286
267
  })
287
268
 
288
269
  const httpCacheId = response.headers?.['x-plt-http-cache-id']
@@ -298,7 +279,7 @@ function setupTelemetry (opts, logger) {
298
279
  } else {
299
280
  span.setStatus({
300
281
  code: SpanStatusCode.ERROR,
301
- message: 'No response received',
282
+ message: 'No response received'
302
283
  })
303
284
  }
304
285
  span.end()
@@ -330,7 +311,7 @@ function setupTelemetry (opts, logger) {
330
311
  if (error) {
331
312
  span.setStatus({
332
313
  code: SpanStatusCode.ERROR,
333
- message: error.message,
314
+ message: error.message
334
315
  })
335
316
  } else {
336
317
  const spanStatus = { code: SpanStatusCode.OK }
@@ -358,12 +339,6 @@ function setupTelemetry (opts, logger) {
358
339
  startSpan,
359
340
  endSpan,
360
341
  shutdown,
361
- openTelemetryAPIs,
342
+ openTelemetryAPIs
362
343
  }
363
344
  }
364
-
365
- module.exports = {
366
- setupTelemetry,
367
- formatSpanName,
368
- formatSpanAttributes
369
- }
package/lib/telemetry.js CHANGED
@@ -1,8 +1,6 @@
1
- 'use strict'
2
-
3
- const fp = require('fastify-plugin')
4
- const { SpanKind } = require('@opentelemetry/api')
5
- const { setupTelemetry } = require('./telemetry-config')
1
+ import { SpanKind } from '@opentelemetry/api'
2
+ import fp from 'fastify-plugin'
3
+ import { setupTelemetry } from './telemetry-config.js'
6
4
 
7
5
  // Telemetry fastify plugin
8
6
  async function telemetry (app, opts) {
@@ -36,8 +34,8 @@ async function telemetry (app, opts) {
36
34
  startSpan,
37
35
  endSpan,
38
36
  shutdown,
39
- SpanKind,
37
+ SpanKind
40
38
  })
41
39
  }
42
40
 
43
- module.exports = fp(telemetry)
41
+ export default fp(telemetry)
@@ -1,45 +1,46 @@
1
- 'use strict'
1
+ import { context, propagation, SpanKind, SpanStatusCode, trace } from '@opentelemetry/api'
2
+ import fastUri from 'fast-uri'
3
+ import { formatSpanAttributes, formatSpanName } from './telemetry-config.js'
4
+ import { name as moduleName, version as moduleVersion } from './version.js'
2
5
 
3
- const { SpanStatusCode, SpanKind } = require('@opentelemetry/api')
4
- const { formatSpanName, formatSpanAttributes } = require('./telemetry-config')
5
- const api = require('@opentelemetry/api')
6
- const fastUri = require('fast-uri')
7
- const packageJson = require('../package.json')
6
+ const tracer = trace.getTracer(moduleName, moduleVersion)
8
7
 
9
- const tracer = api.trace.getTracer(packageJson.name, packageJson.version)
10
-
11
- const createTelemetryThreadInterceptorHooks = () => {
8
+ export function createTelemetryThreadInterceptorHooks () {
12
9
  const onServerRequest = (req, cb) => {
13
- const activeContext = api.propagation.extract(api.context.active(), req.headers)
10
+ const activeContext = propagation.extract(context.active(), req.headers)
14
11
 
15
12
  const route = req.routeOptions?.url ?? null
16
- const span = tracer.startSpan(formatSpanName(req, route), {
17
- attributes: formatSpanAttributes.request(req, route),
18
- kind: SpanKind.SERVER
19
- }, activeContext)
20
- const ctx = api.trace.setSpan(activeContext, span)
13
+ const span = tracer.startSpan(
14
+ formatSpanName(req, route),
15
+ {
16
+ attributes: formatSpanAttributes.request(req, route),
17
+ kind: SpanKind.SERVER
18
+ },
19
+ activeContext
20
+ )
21
+ const ctx = trace.setSpan(activeContext, span)
21
22
 
22
- api.context.with(ctx, cb)
23
+ context.with(ctx, cb)
23
24
  }
24
25
 
25
26
  const onServerResponse = (_req, _res) => {
26
- const activeContext = api.context.active()
27
- const span = api.trace.getSpan(activeContext)
27
+ const activeContext = context.active()
28
+ const span = trace.getSpan(activeContext)
28
29
  if (span) {
29
30
  span.end()
30
31
  }
31
32
  }
32
33
 
33
34
  const onServerError = (_req, _res, error) => {
34
- const activeContext = api.context.active()
35
- const span = api.trace.getSpan(activeContext)
35
+ const activeContext = context.active()
36
+ const span = trace.getSpan(activeContext)
36
37
  if (span) {
37
38
  span.setAttributes(formatSpanAttributes.error(error))
38
39
  }
39
40
  }
40
41
 
41
42
  const onClientRequest = (req, ctx) => {
42
- const activeContext = api.context.active()
43
+ const activeContext = context.active()
43
44
 
44
45
  const { origin, method = '', path } = req
45
46
  const targetUrl = `${origin}${path}`
@@ -51,26 +52,30 @@ const createTelemetryThreadInterceptorHooks = () => {
51
52
  } else {
52
53
  name = `${method} ${urlObj.scheme}://${urlObj.host}${urlObj.path}`
53
54
  }
54
- const span = tracer.startSpan(name, {
55
- attributes: {
56
- 'server.address': urlObj.host,
57
- 'server.port': urlObj.port,
58
- 'http.request.method': method,
59
- 'url.full': targetUrl,
60
- 'url.path': urlObj.path,
61
- 'url.scheme': urlObj.scheme,
55
+ const span = tracer.startSpan(
56
+ name,
57
+ {
58
+ attributes: {
59
+ 'server.address': urlObj.host,
60
+ 'server.port': urlObj.port,
61
+ 'http.request.method': method,
62
+ 'url.full': targetUrl,
63
+ 'url.path': urlObj.path,
64
+ 'url.scheme': urlObj.scheme
65
+ },
66
+ kind: SpanKind.CLIENT
62
67
  },
63
- kind: SpanKind.CLIENT
64
- }, activeContext)
68
+ activeContext
69
+ )
65
70
 
66
71
  // Headers propagation
67
72
  const headers = {}
68
73
  // This line is important, otherwise it will use the old context
69
- const newCtx = api.trace.setSpan(activeContext, span)
70
- api.propagation.inject(newCtx, headers, {
74
+ const newCtx = trace.setSpan(activeContext, span)
75
+ propagation.inject(newCtx, headers, {
71
76
  set (_carrier, key, value) {
72
77
  headers[key] = value
73
- },
78
+ }
74
79
  })
75
80
  req.headers = {
76
81
  ...req.headers,
@@ -91,7 +96,7 @@ const createTelemetryThreadInterceptorHooks = () => {
91
96
  spanStatus.code = SpanStatusCode.ERROR
92
97
  }
93
98
  span.setAttributes({
94
- 'http.response.status_code': res.statusCode,
99
+ 'http.response.status_code': res.statusCode
95
100
  })
96
101
 
97
102
  const httpCacheId = res.headers?.['x-plt-http-cache-id']
@@ -107,7 +112,7 @@ const createTelemetryThreadInterceptorHooks = () => {
107
112
  } else {
108
113
  span.setStatus({
109
114
  code: SpanStatusCode.ERROR,
110
- message: 'No response received',
115
+ message: 'No response received'
111
116
  })
112
117
  }
113
118
  span.end()
@@ -130,7 +135,3 @@ const createTelemetryThreadInterceptorHooks = () => {
130
135
  onClientError
131
136
  }
132
137
  }
133
-
134
- module.exports = {
135
- createTelemetryThreadInterceptorHooks
136
- }
package/lib/version.js ADDED
@@ -0,0 +1,7 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { resolve } from 'node:path'
3
+
4
+ const packageJson = JSON.parse(readFileSync(resolve(import.meta.dirname, '../package.json'), 'utf-8'))
5
+
6
+ export const name = packageJson.name
7
+ export const version = packageJson.version
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@platformatic/telemetry",
3
- "version": "3.0.0-alpha.4",
3
+ "version": "3.0.0-alpha.6",
4
4
  "description": "OpenTelemetry integration for Platformatic",
5
5
  "main": "index.js",
6
+ "type": "module",
6
7
  "author": "Platformatic Inc. <oss@platformatic.dev> (https://platformatic.dev)",
7
8
  "repository": {
8
9
  "type": "git",
@@ -13,7 +14,7 @@
13
14
  "@databases/pg": "^5.5.0",
14
15
  "@opentelemetry/instrumentation-express": "^0.52.0",
15
16
  "@opentelemetry/instrumentation-pg": "^0.55.0",
16
- "borp": "^0.20.1",
17
+ "cleaner-spec-reporter": "^0.5.0",
17
18
  "express": "^5.1.0",
18
19
  "fastify": "^5.4.0",
19
20
  "neostandard": "^0.12.2",
@@ -35,13 +36,13 @@
35
36
  "@opentelemetry/semantic-conventions": "1.36.0",
36
37
  "fast-uri": "^3.0.6",
37
38
  "fastify-plugin": "^5.0.1",
38
- "@platformatic/foundation": "3.0.0-alpha.4"
39
+ "@platformatic/foundation": "3.0.0-alpha.6"
39
40
  },
40
41
  "engines": {
41
42
  "node": ">=22.18.0"
42
43
  },
43
44
  "scripts": {
44
- "test": "npm run lint && borp --timeout=120000 --concurrency=1",
45
+ "test": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js test/**/*.test.js",
45
46
  "lint": "eslint"
46
47
  }
47
48
  }