@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 +4 -8
- package/index.js +3 -11
- package/lib/fastify-text-map.js +4 -11
- package/lib/file-span-exporter.js +5 -9
- package/lib/import-or-local.js +4 -8
- package/lib/multispan-processor.js +1 -5
- package/lib/node-telemetry.js +37 -43
- package/lib/platformatic-context.js +5 -9
- package/lib/platformatic-trace-provider.js +12 -15
- package/lib/pluggable-instrumentations.js +16 -19
- package/lib/schema.js +2 -3
- package/lib/telemetry-config.js +43 -68
- package/lib/telemetry.js +5 -7
- package/lib/thread-interceptor-hooks.js +41 -40
- package/lib/version.js +7 -0
- package/package.json +5 -4
package/eslint.config.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import neostandard from 'neostandard'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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'
|
package/lib/fastify-text-map.js
CHANGED
|
@@ -1,22 +1,15 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
package/lib/import-or-local.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { pathToFileURL } from 'node:url'
|
|
2
4
|
|
|
3
|
-
|
|
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 }
|
package/lib/node-telemetry.js
CHANGED
|
@@ -1,40 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
7
|
+
ConsoleSpanExporter,
|
|
22
8
|
InMemorySpanExporter,
|
|
23
|
-
|
|
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
|
|
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,
|
|
36
|
-
const {
|
|
37
|
-
const additionalInstrumentations = await getInstrumentations(instrumentations,
|
|
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,
|
|
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:
|
|
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]:
|
|
96
|
+
[ATTR_SERVICE_NAME]: applicationName
|
|
104
97
|
})
|
|
105
98
|
})
|
|
106
99
|
sdk.start()
|
|
107
100
|
|
|
108
101
|
process.on('SIGTERM', () => {
|
|
109
|
-
sdk
|
|
102
|
+
sdk
|
|
103
|
+
.shutdown()
|
|
110
104
|
.then(() => debuglog('Tracing terminated'))
|
|
111
|
-
.catch(
|
|
105
|
+
.catch(error => debuglog('Error terminating tracing', error))
|
|
112
106
|
})
|
|
113
107
|
}
|
|
114
108
|
|
|
115
|
-
|
|
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
|
|
136
|
-
const telemetryConfig = useWorkerData ? data?.
|
|
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,
|
|
133
|
+
setupNodeHTTPTelemetry(telemetryConfig, applicationDir)
|
|
140
134
|
}
|
|
141
135
|
}
|
|
142
136
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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()
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
21
|
+
mod = await importOrLocal({ pkg: packageName, projectDir: applicationDir })
|
|
25
22
|
} catch (err) {
|
|
26
|
-
throw new Error(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
55
|
+
export async function getInstrumentations (configs = [], applicationDir) {
|
|
55
56
|
const instrumentations = []
|
|
56
57
|
for (const instrumentationConfig of configs) {
|
|
57
|
-
const instance = await getInstrumentationInstance(instrumentationConfig,
|
|
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
package/lib/telemetry-config.js
CHANGED
|
@@ -1,27 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
6
|
+
ConsoleSpanExporter,
|
|
16
7
|
InMemorySpanExporter,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 {
|
|
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
|
|
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]:
|
|
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,
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
41
|
+
export default fp(telemetry)
|
|
@@ -1,45 +1,46 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
const createTelemetryThreadInterceptorHooks = () => {
|
|
8
|
+
export function createTelemetryThreadInterceptorHooks () {
|
|
12
9
|
const onServerRequest = (req, cb) => {
|
|
13
|
-
const activeContext =
|
|
10
|
+
const activeContext = propagation.extract(context.active(), req.headers)
|
|
14
11
|
|
|
15
12
|
const route = req.routeOptions?.url ?? null
|
|
16
|
-
const span = tracer.startSpan(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
23
|
+
context.with(ctx, cb)
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
const onServerResponse = (_req, _res) => {
|
|
26
|
-
const activeContext =
|
|
27
|
-
const span =
|
|
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 =
|
|
35
|
-
const span =
|
|
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 =
|
|
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(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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 =
|
|
70
|
-
|
|
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.
|
|
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
|
-
"
|
|
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.
|
|
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": "
|
|
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
|
}
|