@platformatic/telemetry 3.29.0 → 3.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/file-span-exporter.js +9 -1
- package/lib/node-telemetry.js +36 -63
- package/lib/span-processors.js +65 -0
- package/lib/telemetry-config.js +3 -59
- package/lib/thread-interceptor-hooks.js +11 -3
- package/package.json +3 -3
|
@@ -26,6 +26,14 @@ export class FileSpanExporter {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
#exportInfo (span) {
|
|
29
|
+
// OpenTelemetry 2.0+ resources need to be serialized with their attributes
|
|
30
|
+
// The resource.attributes property contains a map of attribute values
|
|
31
|
+
// We need to convert it to the format expected by tests (_rawAttributes array)
|
|
32
|
+
const resource = {
|
|
33
|
+
attributes: span.resource?.attributes || {},
|
|
34
|
+
_rawAttributes: Object.entries(span.resource?.attributes || {})
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
return {
|
|
30
38
|
traceId: span.spanContext().traceId,
|
|
31
39
|
// parentId has been removed from otel 2.0, we need to get it from parentSpanContext
|
|
@@ -43,7 +51,7 @@ export class FileSpanExporter {
|
|
|
43
51
|
status: span.status,
|
|
44
52
|
events: span.events,
|
|
45
53
|
links: span.links,
|
|
46
|
-
resource
|
|
54
|
+
resource,
|
|
47
55
|
// instrumentationLibrary is deprecated in otel 2.0, we need to use instrumentationScope
|
|
48
56
|
instrumentationScope: span.instrumentationLibrary || span.instrumentationScope
|
|
49
57
|
}
|
package/lib/node-telemetry.js
CHANGED
|
@@ -1,85 +1,60 @@
|
|
|
1
|
+
import { context, propagation } from '@opentelemetry/api'
|
|
2
|
+
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks'
|
|
3
|
+
import { registerInstrumentations } from '@opentelemetry/instrumentation'
|
|
1
4
|
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'
|
|
2
5
|
import { UndiciInstrumentation } from '@opentelemetry/instrumentation-undici'
|
|
6
|
+
import { W3CTraceContextPropagator } from '@opentelemetry/core'
|
|
7
|
+
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
|
|
3
8
|
import { resourceFromAttributes } from '@opentelemetry/resources'
|
|
4
|
-
import * as opentelemetry from '@opentelemetry/sdk-node'
|
|
5
|
-
import {
|
|
6
|
-
BatchSpanProcessor,
|
|
7
|
-
ConsoleSpanExporter,
|
|
8
|
-
InMemorySpanExporter,
|
|
9
|
-
SimpleSpanProcessor
|
|
10
|
-
} from '@opentelemetry/sdk-trace-base'
|
|
11
9
|
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'
|
|
12
|
-
import { abstractLogger } from '@platformatic/foundation'
|
|
13
10
|
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
14
11
|
import { readFileSync, statSync } from 'node:fs'
|
|
15
|
-
import { createRequire } from 'node:module'
|
|
16
12
|
import { tmpdir } from 'node:os'
|
|
17
13
|
import { resolve } from 'node:path'
|
|
18
14
|
import process from 'node:process'
|
|
19
15
|
import util from 'node:util'
|
|
20
16
|
import { workerData } from 'node:worker_threads'
|
|
21
|
-
import { FileSpanExporter } from './file-span-exporter.js'
|
|
22
17
|
import { getInstrumentations } from './pluggable-instrumentations.js'
|
|
18
|
+
import { getSpanProcessors } from './span-processors.js'
|
|
23
19
|
|
|
24
20
|
const debuglog = util.debuglog('@platformatic/telemetry')
|
|
25
|
-
const require = createRequire(import.meta.url)
|
|
26
21
|
|
|
27
22
|
// See: https://www.npmjs.com/package/@opentelemetry/instrumentation-http
|
|
28
|
-
// When this is fixed we should set this to 'http' and
|
|
23
|
+
// When this is fixed we should set this to 'http' and fix the tests
|
|
29
24
|
// https://github.com/open-telemetry/opentelemetry-js/issues/5103
|
|
30
25
|
process.env.OTEL_SEMCONV_STABILITY_OPT_IN = 'http/dup'
|
|
31
26
|
|
|
27
|
+
// Set up global propagator EARLY for trace context propagation via HTTP headers
|
|
28
|
+
// This must be set before any HTTP operations occur
|
|
29
|
+
propagation.setGlobalPropagator(new W3CTraceContextPropagator())
|
|
30
|
+
|
|
31
|
+
// Set up global context manager for async context propagation
|
|
32
|
+
// Context manager MUST be global for instrumentations to work
|
|
33
|
+
const contextManager = new AsyncLocalStorageContextManager()
|
|
34
|
+
contextManager.enable()
|
|
35
|
+
context.setGlobalContextManager(contextManager)
|
|
36
|
+
|
|
32
37
|
const setupNodeHTTPTelemetry = async (opts, applicationDir) => {
|
|
33
38
|
const { applicationName, instrumentations = [] } = opts
|
|
34
39
|
const additionalInstrumentations = await getInstrumentations(instrumentations, applicationDir)
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
if (!exporter) {
|
|
38
|
-
abstractLogger.warn('No exporter configured, defaulting to console.')
|
|
39
|
-
exporter = { type: 'console' }
|
|
40
|
-
}
|
|
41
|
-
const exporters = Array.isArray(exporter) ? exporter : [exporter]
|
|
42
|
-
const spanProcessors = []
|
|
43
|
-
for (const exporter of exporters) {
|
|
44
|
-
// Exporter config:
|
|
45
|
-
// https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_exporter_zipkin.ExporterConfig.html
|
|
46
|
-
const exporterOptions = { ...exporter.options, applicationName }
|
|
47
|
-
|
|
48
|
-
let exporterObj
|
|
49
|
-
if (exporter.type === 'console') {
|
|
50
|
-
exporterObj = new ConsoleSpanExporter(exporterOptions)
|
|
51
|
-
} else if (exporter.type === 'otlp') {
|
|
52
|
-
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-proto')
|
|
53
|
-
exporterObj = new OTLPTraceExporter(exporterOptions)
|
|
54
|
-
} else if (exporter.type === 'zipkin') {
|
|
55
|
-
const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin')
|
|
56
|
-
exporterObj = new ZipkinExporter(exporterOptions)
|
|
57
|
-
} else if (exporter.type === 'memory') {
|
|
58
|
-
exporterObj = new InMemorySpanExporter()
|
|
59
|
-
} else if (exporter.type === 'file') {
|
|
60
|
-
exporterObj = new FileSpanExporter(exporterOptions)
|
|
61
|
-
} else {
|
|
62
|
-
abstractLogger.warn(`Unknown exporter type: ${exporter.type}, defaulting to console.`)
|
|
63
|
-
exporterObj = new ConsoleSpanExporter(exporterOptions)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
let spanProcessor
|
|
67
|
-
// We use a SimpleSpanProcessor for the console/memory exporters and a BatchSpanProcessor for the others.
|
|
68
|
-
// , unless "processor" is set to "simple" (used only in tests)
|
|
69
|
-
if (exporter.processor === 'simple' || ['memory', 'console', 'file'].includes(exporter.type)) {
|
|
70
|
-
spanProcessor = new SimpleSpanProcessor(exporterObj)
|
|
71
|
-
} else {
|
|
72
|
-
spanProcessor = new BatchSpanProcessor(exporterObj)
|
|
73
|
-
}
|
|
74
|
-
spanProcessors.push(spanProcessor)
|
|
75
|
-
}
|
|
41
|
+
const { spanProcessors } = getSpanProcessors(opts)
|
|
76
42
|
|
|
77
43
|
const clientSpansAls = new AsyncLocalStorage()
|
|
78
44
|
globalThis.platformatic = globalThis.platformatic || {}
|
|
79
45
|
globalThis.platformatic.clientSpansAls = clientSpansAls
|
|
80
46
|
|
|
81
|
-
const
|
|
47
|
+
const tracerProvider = new NodeTracerProvider({
|
|
82
48
|
spanProcessors, // https://github.com/open-telemetry/opentelemetry-js/issues/4881#issuecomment-2358059714
|
|
49
|
+
resource: resourceFromAttributes({
|
|
50
|
+
[ATTR_SERVICE_NAME]: applicationName
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
globalThis.platformatic.tracerProvider = tracerProvider
|
|
54
|
+
|
|
55
|
+
// Register instrumentations with our TracerProvider
|
|
56
|
+
registerInstrumentations({
|
|
57
|
+
tracerProvider,
|
|
83
58
|
instrumentations: [
|
|
84
59
|
new UndiciInstrumentation({
|
|
85
60
|
responseHook: span => {
|
|
@@ -91,18 +66,16 @@ const setupNodeHTTPTelemetry = async (opts, applicationDir) => {
|
|
|
91
66
|
}),
|
|
92
67
|
new HttpInstrumentation(),
|
|
93
68
|
...additionalInstrumentations
|
|
94
|
-
]
|
|
95
|
-
resource: resourceFromAttributes({
|
|
96
|
-
[ATTR_SERVICE_NAME]: applicationName
|
|
97
|
-
})
|
|
69
|
+
]
|
|
98
70
|
})
|
|
99
|
-
sdk.start()
|
|
100
71
|
|
|
101
|
-
process.on('SIGTERM', () => {
|
|
102
|
-
|
|
103
|
-
.shutdown()
|
|
104
|
-
|
|
105
|
-
|
|
72
|
+
process.on('SIGTERM', async () => {
|
|
73
|
+
try {
|
|
74
|
+
await tracerProvider.shutdown()
|
|
75
|
+
debuglog('Tracing terminated')
|
|
76
|
+
} catch (error) {
|
|
77
|
+
debuglog('Error terminating tracing', error)
|
|
78
|
+
}
|
|
106
79
|
})
|
|
107
80
|
}
|
|
108
81
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BatchSpanProcessor,
|
|
3
|
+
ConsoleSpanExporter,
|
|
4
|
+
InMemorySpanExporter,
|
|
5
|
+
SimpleSpanProcessor
|
|
6
|
+
} from '@opentelemetry/sdk-trace-base'
|
|
7
|
+
import { abstractLogger } from '@platformatic/foundation'
|
|
8
|
+
import { createRequire } from 'node:module'
|
|
9
|
+
import { FileSpanExporter } from './file-span-exporter.js'
|
|
10
|
+
|
|
11
|
+
const require = createRequire(import.meta.url)
|
|
12
|
+
|
|
13
|
+
export function getSpanProcessors (opts = {}, logger = abstractLogger) {
|
|
14
|
+
const { applicationName, version } = opts
|
|
15
|
+
|
|
16
|
+
// Set up exporters
|
|
17
|
+
let exporter = opts.exporter
|
|
18
|
+
if (!exporter) {
|
|
19
|
+
logger.warn?.('No exporter configured, defaulting to console.')
|
|
20
|
+
exporter = { type: 'console' }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const exporters = Array.isArray(exporter) ? exporter : [exporter]
|
|
24
|
+
|
|
25
|
+
logger.debug?.(
|
|
26
|
+
`Setting up platformatic telemetry for application: ${applicationName}${version ? ' version: ' + version : ''} with exporter of type ${exporter.type}`
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const exporterObjs = []
|
|
30
|
+
const spanProcessors = []
|
|
31
|
+
for (const exp of exporters) {
|
|
32
|
+
const exporterOptions = { ...exp.options, applicationName }
|
|
33
|
+
|
|
34
|
+
let exporterObj
|
|
35
|
+
if (exp.type === 'console') {
|
|
36
|
+
exporterObj = new ConsoleSpanExporter(exporterOptions)
|
|
37
|
+
} else if (exp.type === 'otlp') {
|
|
38
|
+
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-proto')
|
|
39
|
+
exporterObj = new OTLPTraceExporter(exporterOptions)
|
|
40
|
+
} else if (exp.type === 'zipkin') {
|
|
41
|
+
const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin')
|
|
42
|
+
exporterObj = new ZipkinExporter(exporterOptions)
|
|
43
|
+
} else if (exp.type === 'memory') {
|
|
44
|
+
exporterObj = new InMemorySpanExporter()
|
|
45
|
+
} else if (exp.type === 'file') {
|
|
46
|
+
exporterObj = new FileSpanExporter(exporterOptions)
|
|
47
|
+
} else {
|
|
48
|
+
logger.warn?.(`Unknown exporter type: ${exp.type}, defaulting to console.`)
|
|
49
|
+
exporterObj = new ConsoleSpanExporter(exporterOptions)
|
|
50
|
+
}
|
|
51
|
+
exporterObjs.push(exporterObj)
|
|
52
|
+
|
|
53
|
+
let spanProcessor
|
|
54
|
+
// We use a SimpleSpanProcessor for the console/memory exporters and a BatchSpanProcessor for the others.
|
|
55
|
+
// unless "processor" is set to "simple" (used only in tests)
|
|
56
|
+
if (exp.processor === 'simple' || ['memory', 'console', 'file'].includes(exp.type)) {
|
|
57
|
+
spanProcessor = new SimpleSpanProcessor(exporterObj)
|
|
58
|
+
} else {
|
|
59
|
+
spanProcessor = new BatchSpanProcessor(exporterObj)
|
|
60
|
+
}
|
|
61
|
+
spanProcessors.push(spanProcessor)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { exporters: exporterObjs, spanProcessors }
|
|
65
|
+
}
|
package/lib/telemetry-config.js
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { formatParamUrl } from '@fastify/swagger'
|
|
2
2
|
import { SpanKind, SpanStatusCode } from '@opentelemetry/api'
|
|
3
3
|
import { resourceFromAttributes } from '@opentelemetry/resources'
|
|
4
|
-
import {
|
|
5
|
-
BatchSpanProcessor,
|
|
6
|
-
ConsoleSpanExporter,
|
|
7
|
-
InMemorySpanExporter,
|
|
8
|
-
SimpleSpanProcessor
|
|
9
|
-
} from '@opentelemetry/sdk-trace-base'
|
|
10
4
|
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'
|
|
11
5
|
import fastUri from 'fast-uri'
|
|
12
|
-
import { createRequire } from 'node:module'
|
|
13
6
|
import { fastifyTextMapGetter, fastifyTextMapSetter } from './fastify-text-map.js'
|
|
14
|
-
import { FileSpanExporter } from './file-span-exporter.js'
|
|
15
7
|
import { PlatformaticContext } from './platformatic-context.js'
|
|
16
8
|
import { PlatformaticTracerProvider } from './platformatic-trace-provider.js'
|
|
9
|
+
import { getSpanProcessors } from './span-processors.js'
|
|
17
10
|
|
|
18
11
|
import { name as moduleName, version as moduleVersion } from './version.js'
|
|
19
12
|
|
|
@@ -78,18 +71,8 @@ export const formatSpanAttributes = {
|
|
|
78
71
|
}
|
|
79
72
|
|
|
80
73
|
const initTelemetry = (opts, logger) => {
|
|
74
|
+
const { exporters, spanProcessors } = getSpanProcessors(opts, logger)
|
|
81
75
|
const { applicationName, version } = opts
|
|
82
|
-
let exporter = opts.exporter
|
|
83
|
-
if (!exporter) {
|
|
84
|
-
logger.warn('No exporter configured, defaulting to console.')
|
|
85
|
-
exporter = { type: 'console' }
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const exporters = Array.isArray(exporter) ? exporter : [exporter]
|
|
89
|
-
|
|
90
|
-
logger.debug(
|
|
91
|
-
`Setting up platformatic telemetry for application: ${applicationName}${version ? ' version: ' + version : ''} with exporter of type ${exporter.type}`
|
|
92
|
-
)
|
|
93
76
|
|
|
94
77
|
const provider = new PlatformaticTracerProvider({
|
|
95
78
|
resource: resourceFromAttributes({
|
|
@@ -98,50 +81,11 @@ const initTelemetry = (opts, logger) => {
|
|
|
98
81
|
})
|
|
99
82
|
})
|
|
100
83
|
|
|
101
|
-
const exporterObjs = []
|
|
102
|
-
const spanProcessors = []
|
|
103
|
-
const require = createRequire(import.meta.url)
|
|
104
|
-
for (const exporter of exporters) {
|
|
105
|
-
// Exporter config:
|
|
106
|
-
// https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_exporter_zipkin.ExporterConfig.html
|
|
107
|
-
const exporterOptions = { ...exporter.options, applicationName }
|
|
108
|
-
|
|
109
|
-
let exporterObj
|
|
110
|
-
if (exporter.type === 'console') {
|
|
111
|
-
exporterObj = new ConsoleSpanExporter(exporterOptions)
|
|
112
|
-
} else if (exporter.type === 'otlp') {
|
|
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.
|
|
114
|
-
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-proto')
|
|
115
|
-
exporterObj = new OTLPTraceExporter(exporterOptions)
|
|
116
|
-
} else if (exporter.type === 'zipkin') {
|
|
117
|
-
const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin')
|
|
118
|
-
exporterObj = new ZipkinExporter(exporterOptions)
|
|
119
|
-
} else if (exporter.type === 'memory') {
|
|
120
|
-
exporterObj = new InMemorySpanExporter()
|
|
121
|
-
} else if (exporter.type === 'file') {
|
|
122
|
-
exporterObj = new FileSpanExporter(exporterOptions)
|
|
123
|
-
} else {
|
|
124
|
-
logger.warn(`Unknown exporter type: ${exporter.type}, defaulting to console.`)
|
|
125
|
-
exporterObj = new ConsoleSpanExporter(exporterOptions)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
let spanProcessor
|
|
129
|
-
// We use a SimpleSpanProcessor for the console/memory exporters and a BatchSpanProcessor for the others.
|
|
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
|
-
}
|
|
136
|
-
spanProcessors.push(spanProcessor)
|
|
137
|
-
exporterObjs.push(exporterObj)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
84
|
provider.addSpanProcessor(spanProcessors)
|
|
141
85
|
const tracer = provider.getTracer(moduleName, moduleVersion)
|
|
142
86
|
const propagator = provider.getPropagator()
|
|
143
87
|
|
|
144
|
-
return { tracer, exporters
|
|
88
|
+
return { tracer, exporters, propagator, provider, spanProcessors }
|
|
145
89
|
}
|
|
146
90
|
|
|
147
91
|
export function setupTelemetry (opts, logger) {
|
|
@@ -3,14 +3,22 @@ import fastUri from 'fast-uri'
|
|
|
3
3
|
import { formatSpanAttributes, formatSpanName } from './telemetry-config.js'
|
|
4
4
|
import { name as moduleName, version as moduleVersion } from './version.js'
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
// Get tracer from our isolated TracerProvider stored in globalThis.platformatic
|
|
7
|
+
function getTracer () {
|
|
8
|
+
const tracerProvider = globalThis.platformatic?.tracerProvider
|
|
9
|
+
if (tracerProvider) {
|
|
10
|
+
return tracerProvider.getTracer(moduleName, moduleVersion)
|
|
11
|
+
}
|
|
12
|
+
// Fallback to global tracer if our provider isn't set up yet
|
|
13
|
+
return trace.getTracer(moduleName, moduleVersion)
|
|
14
|
+
}
|
|
7
15
|
|
|
8
16
|
export function createTelemetryThreadInterceptorHooks () {
|
|
9
17
|
const onServerRequest = (req, cb) => {
|
|
10
18
|
const activeContext = propagation.extract(context.active(), req.headers)
|
|
11
19
|
|
|
12
20
|
const route = req.routeOptions?.url ?? null
|
|
13
|
-
const span =
|
|
21
|
+
const span = getTracer().startSpan(
|
|
14
22
|
formatSpanName(req, route),
|
|
15
23
|
{
|
|
16
24
|
attributes: formatSpanAttributes.request(req, route),
|
|
@@ -52,7 +60,7 @@ export function createTelemetryThreadInterceptorHooks () {
|
|
|
52
60
|
} else {
|
|
53
61
|
name = `${method} ${urlObj.scheme}://${urlObj.host}${urlObj.path}`
|
|
54
62
|
}
|
|
55
|
-
const span =
|
|
63
|
+
const span = getTracer().startSpan(
|
|
56
64
|
name,
|
|
57
65
|
{
|
|
58
66
|
attributes: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/telemetry",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.30.0",
|
|
4
4
|
"description": "OpenTelemetry integration for Platformatic",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"@opentelemetry/instrumentation-pg": "^0.55.0",
|
|
17
17
|
"cleaner-spec-reporter": "^0.5.0",
|
|
18
18
|
"express": "^5.1.0",
|
|
19
|
-
"fastify": "^5.
|
|
19
|
+
"fastify": "^5.7.0",
|
|
20
20
|
"neostandard": "^0.12.2",
|
|
21
21
|
"protobufjs": "^7.5.3",
|
|
22
22
|
"typescript": "^5.9.2"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@opentelemetry/semantic-conventions": "1.36.0",
|
|
37
37
|
"fast-uri": "^3.0.6",
|
|
38
38
|
"fastify-plugin": "^5.0.1",
|
|
39
|
-
"@platformatic/foundation": "3.
|
|
39
|
+
"@platformatic/foundation": "3.30.0"
|
|
40
40
|
},
|
|
41
41
|
"engines": {
|
|
42
42
|
"node": ">=22.19.0"
|