@platformatic/telemetry 3.4.1 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +5 -0
- package/eslint.config.js +4 -2
- package/index.js +3 -11
- package/lib/fastify-text-map.js +4 -11
- package/lib/file-span-exporter.js +51 -0
- package/lib/import-or-local.js +14 -0
- package/lib/multispan-processor.js +1 -5
- package/lib/node-telemetry.js +142 -0
- package/lib/platformatic-context.js +5 -9
- package/lib/platformatic-trace-provider.js +17 -19
- package/lib/pluggable-instrumentations.js +62 -0
- package/lib/schema.js +2 -72
- package/lib/telemetry-config.js +293 -30
- package/lib/telemetry.js +23 -263
- package/lib/thread-interceptor-hooks.js +137 -0
- package/lib/version.js +7 -0
- package/package.json +31 -20
- package/lib/node-http-telemetry.js +0 -33
|
@@ -0,0 +1,137 @@
|
|
|
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'
|
|
5
|
+
|
|
6
|
+
const tracer = trace.getTracer(moduleName, moduleVersion)
|
|
7
|
+
|
|
8
|
+
export function createTelemetryThreadInterceptorHooks () {
|
|
9
|
+
const onServerRequest = (req, cb) => {
|
|
10
|
+
const activeContext = propagation.extract(context.active(), req.headers)
|
|
11
|
+
|
|
12
|
+
const route = req.routeOptions?.url ?? null
|
|
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)
|
|
22
|
+
|
|
23
|
+
context.with(ctx, cb)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const onServerResponse = (_req, _res) => {
|
|
27
|
+
const activeContext = context.active()
|
|
28
|
+
const span = trace.getSpan(activeContext)
|
|
29
|
+
if (span) {
|
|
30
|
+
span.end()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const onServerError = (_req, _res, error) => {
|
|
35
|
+
const activeContext = context.active()
|
|
36
|
+
const span = trace.getSpan(activeContext)
|
|
37
|
+
if (span) {
|
|
38
|
+
span.setAttributes(formatSpanAttributes.error(error))
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const onClientRequest = (req, ctx) => {
|
|
43
|
+
const activeContext = context.active()
|
|
44
|
+
|
|
45
|
+
const { origin, method = '', path } = req
|
|
46
|
+
const targetUrl = `${origin}${path}`
|
|
47
|
+
const urlObj = fastUri.parse(targetUrl)
|
|
48
|
+
|
|
49
|
+
let name
|
|
50
|
+
if (urlObj.port) {
|
|
51
|
+
name = `${method} ${urlObj.scheme}://${urlObj.host}:${urlObj.port}${urlObj.path}`
|
|
52
|
+
} else {
|
|
53
|
+
name = `${method} ${urlObj.scheme}://${urlObj.host}${urlObj.path}`
|
|
54
|
+
}
|
|
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
|
|
67
|
+
},
|
|
68
|
+
activeContext
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
// Headers propagation
|
|
72
|
+
const headers = {}
|
|
73
|
+
// This line is important, otherwise it will use the old context
|
|
74
|
+
const newCtx = trace.setSpan(activeContext, span)
|
|
75
|
+
propagation.inject(newCtx, headers, {
|
|
76
|
+
set (_carrier, key, value) {
|
|
77
|
+
headers[key] = value
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
req.headers = {
|
|
81
|
+
...req.headers,
|
|
82
|
+
...headers
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
ctx.span = span
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const onClientResponseEnd = (_req, res, ctx) => {
|
|
89
|
+
const span = ctx.span ?? null
|
|
90
|
+
if (!span) {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
if (res) {
|
|
94
|
+
const spanStatus = { code: SpanStatusCode.OK }
|
|
95
|
+
if (res.statusCode >= 400) {
|
|
96
|
+
spanStatus.code = SpanStatusCode.ERROR
|
|
97
|
+
}
|
|
98
|
+
span.setAttributes({
|
|
99
|
+
'http.response.status_code': res.statusCode
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const httpCacheId = res.headers?.['x-plt-http-cache-id']
|
|
103
|
+
const isCacheHit = res.headers?.age !== undefined
|
|
104
|
+
if (httpCacheId) {
|
|
105
|
+
span.setAttributes({
|
|
106
|
+
'http.cache.id': httpCacheId,
|
|
107
|
+
'http.cache.hit': isCacheHit.toString()
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
span.setStatus(spanStatus)
|
|
112
|
+
} else {
|
|
113
|
+
span.setStatus({
|
|
114
|
+
code: SpanStatusCode.ERROR,
|
|
115
|
+
message: 'No response received'
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
span.end()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const onClientError = (_req, _res, ctx, error) => {
|
|
122
|
+
const span = ctx.span ?? null
|
|
123
|
+
if (!span) {
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
span.setAttributes(formatSpanAttributes.error(error))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
onServerRequest,
|
|
131
|
+
onServerResponse,
|
|
132
|
+
onServerError,
|
|
133
|
+
onClientRequest,
|
|
134
|
+
onClientResponseEnd,
|
|
135
|
+
onClientError
|
|
136
|
+
}
|
|
137
|
+
}
|
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,37 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/telemetry",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "OpenTelemetry integration for Platformatic",
|
|
5
5
|
"main": "index.js",
|
|
6
|
-
"
|
|
6
|
+
"type": "module",
|
|
7
|
+
"author": "Platformatic Inc. <oss@platformatic.dev> (https://platformatic.dev)",
|
|
7
8
|
"repository": {
|
|
8
9
|
"type": "git",
|
|
9
10
|
"url": "git+https://github.com/platformatic/platformatic.git"
|
|
10
11
|
},
|
|
11
12
|
"license": "Apache-2.0",
|
|
12
13
|
"devDependencies": {
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
14
|
+
"@databases/pg": "^5.5.0",
|
|
15
|
+
"@opentelemetry/instrumentation-express": "^0.52.0",
|
|
16
|
+
"@opentelemetry/instrumentation-pg": "^0.55.0",
|
|
17
|
+
"cleaner-spec-reporter": "^0.5.0",
|
|
18
|
+
"express": "^5.1.0",
|
|
19
|
+
"fastify": "^5.4.0",
|
|
20
|
+
"neostandard": "^0.12.2",
|
|
21
|
+
"protobufjs": "^7.5.3",
|
|
22
|
+
"typescript": "^5.9.2"
|
|
18
23
|
},
|
|
19
24
|
"dependencies": {
|
|
20
|
-
"@fastify/swagger": "^9.
|
|
21
|
-
"@opentelemetry/api": "^1.
|
|
22
|
-
"@opentelemetry/
|
|
23
|
-
"@opentelemetry/
|
|
24
|
-
"@opentelemetry/exporter-
|
|
25
|
-
"@opentelemetry/
|
|
26
|
-
"@opentelemetry/
|
|
27
|
-
"@opentelemetry/
|
|
28
|
-
"@opentelemetry/
|
|
29
|
-
"@opentelemetry/
|
|
30
|
-
"
|
|
31
|
-
"
|
|
25
|
+
"@fastify/swagger": "^9.5.1",
|
|
26
|
+
"@opentelemetry/api": "^1.9.0",
|
|
27
|
+
"@opentelemetry/core": "^2.0.1",
|
|
28
|
+
"@opentelemetry/exporter-trace-otlp-proto": "^0.203.0",
|
|
29
|
+
"@opentelemetry/exporter-zipkin": "^2.0.1",
|
|
30
|
+
"@opentelemetry/instrumentation": "^0.203.0",
|
|
31
|
+
"@opentelemetry/instrumentation-http": "^0.203.0",
|
|
32
|
+
"@opentelemetry/instrumentation-undici": "^0.14.0",
|
|
33
|
+
"@opentelemetry/resources": "^2.0.1",
|
|
34
|
+
"@opentelemetry/sdk-node": "^0.203.0",
|
|
35
|
+
"@opentelemetry/sdk-trace-base": "^2.0.1",
|
|
36
|
+
"@opentelemetry/semantic-conventions": "1.36.0",
|
|
37
|
+
"fast-uri": "^3.0.6",
|
|
38
|
+
"fastify-plugin": "^5.0.1",
|
|
39
|
+
"@platformatic/foundation": "3.5.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=22.19.0"
|
|
32
43
|
},
|
|
33
44
|
"scripts": {
|
|
34
|
-
"test": "
|
|
45
|
+
"test": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js test/**/*.test.js",
|
|
35
46
|
"lint": "eslint"
|
|
36
47
|
}
|
|
37
48
|
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
const process = require('node:process')
|
|
3
|
-
const opentelemetry = require('@opentelemetry/sdk-node')
|
|
4
|
-
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http')
|
|
5
|
-
const { Resource } = require('@opentelemetry/resources')
|
|
6
|
-
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions')
|
|
7
|
-
const setupTelemetry = require('./telemetry-config')
|
|
8
|
-
|
|
9
|
-
const setupNodeHTTPTelemetry = (opts, logger) => {
|
|
10
|
-
const { serviceName } = opts
|
|
11
|
-
logger.info(`Setting up Node.js HTTP telemetry for service: ${serviceName}`)
|
|
12
|
-
// We setup the telemetry to init the spanProcessors, then we pass them to the SDK
|
|
13
|
-
const { spanProcessors } = setupTelemetry(opts, logger)
|
|
14
|
-
const sdk = new opentelemetry.NodeSDK({
|
|
15
|
-
spanProcessors, // https://github.com/open-telemetry/opentelemetry-js/issues/4881#issuecomment-2358059714
|
|
16
|
-
instrumentations: [
|
|
17
|
-
new HttpInstrumentation(),
|
|
18
|
-
],
|
|
19
|
-
resource: new Resource({
|
|
20
|
-
[SemanticResourceAttributes.SERVICE_NAME]: serviceName
|
|
21
|
-
})
|
|
22
|
-
})
|
|
23
|
-
sdk.start()
|
|
24
|
-
|
|
25
|
-
// gracefully shut down the SDK on process exit
|
|
26
|
-
process.on('SIGTERM', () => {
|
|
27
|
-
sdk.shutdown()
|
|
28
|
-
.then(() => console.log('Tracing terminated'))
|
|
29
|
-
.catch((error) => console.log('Error terminating tracing', error))
|
|
30
|
-
})
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
module.exports = setupNodeHTTPTelemetry
|