@platformatic/telemetry 3.4.1 → 3.5.1
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
package/NOTICE
CHANGED
|
@@ -11,3 +11,8 @@
|
|
|
11
11
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
See the License for the specific language governing permissions and
|
|
13
13
|
limitations under the License.
|
|
14
|
+
|
|
15
|
+
The files in `test/otelserver/opentelemetry` has been created by the OpenTelemetry community and are licensed under the Apache License 2.0. The original files can be found at
|
|
16
|
+
https://github.com/open-telemetry/opentelemetry-proto/tree/1608f92cf08119f9aec237c910b200d1317ec696/opentelemetry
|
|
17
|
+
|
|
18
|
+
|
package/eslint.config.js
CHANGED
package/index.js
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const schema = require('./lib/schema')
|
|
5
|
-
const setupNodeHTTPTelemetry = require('./lib/node-http-telemetry')
|
|
6
|
-
|
|
7
|
-
module.exports = {
|
|
8
|
-
telemetry,
|
|
9
|
-
schema,
|
|
10
|
-
setupNodeHTTPTelemetry
|
|
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
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
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'
|
|
5
|
+
|
|
6
|
+
// Export spans to a file, mostly for testing purposes.
|
|
7
|
+
export class FileSpanExporter {
|
|
8
|
+
#path
|
|
9
|
+
constructor (opts) {
|
|
10
|
+
this.#path = resolvePath(workerData?.dirname ?? process.cwd(), opts.path ?? 'spans.log')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export (spans, resultCallback) {
|
|
14
|
+
for (const span of spans) {
|
|
15
|
+
appendFileSync(this.#path, JSON.stringify(this.#exportInfo(span)) + '\n')
|
|
16
|
+
}
|
|
17
|
+
resultCallback(ExportResultCode.SUCCESS)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
shutdown () {
|
|
21
|
+
return this.forceFlush()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
forceFlush () {
|
|
25
|
+
return Promise.resolve()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#exportInfo (span) {
|
|
29
|
+
return {
|
|
30
|
+
traceId: span.spanContext().traceId,
|
|
31
|
+
// parentId has been removed from otel 2.0, we need to get it from parentSpanContext
|
|
32
|
+
parentSpanContext: {
|
|
33
|
+
traceId: span.parentSpanContext?.traceId,
|
|
34
|
+
spanId: span.parentSpanContext?.spanId
|
|
35
|
+
},
|
|
36
|
+
traceState: span.spanContext().traceState?.serialize(),
|
|
37
|
+
name: span.name,
|
|
38
|
+
id: span.spanContext().spanId,
|
|
39
|
+
kind: span.kind,
|
|
40
|
+
timestamp: hrTimeToMicroseconds(span.startTime),
|
|
41
|
+
duration: hrTimeToMicroseconds(span.duration),
|
|
42
|
+
attributes: span.attributes,
|
|
43
|
+
status: span.status,
|
|
44
|
+
events: span.events,
|
|
45
|
+
links: span.links,
|
|
46
|
+
resource: span.resource,
|
|
47
|
+
// instrumentationLibrary is deprecated in otel 2.0, we need to use instrumentationScope
|
|
48
|
+
instrumentationScope: span.instrumentationLibrary || span.instrumentationScope
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { pathToFileURL } from 'node:url'
|
|
4
|
+
|
|
5
|
+
export async function importOrLocal ({ projectDir, pkg }) {
|
|
6
|
+
try {
|
|
7
|
+
return import(pkg)
|
|
8
|
+
} catch (err) {
|
|
9
|
+
const pkgJsonPath = join(projectDir, 'package.json')
|
|
10
|
+
const _require = createRequire(pkgJsonPath)
|
|
11
|
+
const fileToImport = _require.resolve(pkg)
|
|
12
|
+
return import(pathToFileURL(fileToImport))
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -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 }
|
|
@@ -0,0 +1,142 @@
|
|
|
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 {
|
|
6
|
+
BatchSpanProcessor,
|
|
7
|
+
ConsoleSpanExporter,
|
|
8
|
+
InMemorySpanExporter,
|
|
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'
|
|
23
|
+
|
|
24
|
+
const debuglog = util.debuglog('@platformatic/telemetry')
|
|
25
|
+
const require = createRequire(import.meta.url)
|
|
26
|
+
|
|
27
|
+
// See: https://www.npmjs.com/package/@opentelemetry/instrumentation-http
|
|
28
|
+
// When this is fixed we should set this to 'http' and fixe the tests
|
|
29
|
+
// https://github.com/open-telemetry/opentelemetry-js/issues/5103
|
|
30
|
+
process.env.OTEL_SEMCONV_STABILITY_OPT_IN = 'http/dup'
|
|
31
|
+
|
|
32
|
+
const setupNodeHTTPTelemetry = async (opts, applicationDir) => {
|
|
33
|
+
const { applicationName, instrumentations = [] } = opts
|
|
34
|
+
const additionalInstrumentations = await getInstrumentations(instrumentations, applicationDir)
|
|
35
|
+
|
|
36
|
+
let exporter = opts.exporter
|
|
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
|
+
}
|
|
76
|
+
|
|
77
|
+
const clientSpansAls = new AsyncLocalStorage()
|
|
78
|
+
globalThis.platformatic = globalThis.platformatic || {}
|
|
79
|
+
globalThis.platformatic.clientSpansAls = clientSpansAls
|
|
80
|
+
|
|
81
|
+
const sdk = new opentelemetry.NodeSDK({
|
|
82
|
+
spanProcessors, // https://github.com/open-telemetry/opentelemetry-js/issues/4881#issuecomment-2358059714
|
|
83
|
+
instrumentations: [
|
|
84
|
+
new UndiciInstrumentation({
|
|
85
|
+
responseHook: span => {
|
|
86
|
+
const store = clientSpansAls.getStore()
|
|
87
|
+
if (store) {
|
|
88
|
+
store.span = span
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}),
|
|
92
|
+
new HttpInstrumentation(),
|
|
93
|
+
...additionalInstrumentations
|
|
94
|
+
],
|
|
95
|
+
resource: resourceFromAttributes({
|
|
96
|
+
[ATTR_SERVICE_NAME]: applicationName
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
sdk.start()
|
|
100
|
+
|
|
101
|
+
process.on('SIGTERM', () => {
|
|
102
|
+
sdk
|
|
103
|
+
.shutdown()
|
|
104
|
+
.then(() => debuglog('Tracing terminated'))
|
|
105
|
+
.catch(error => debuglog('Error terminating tracing', error))
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function main () {
|
|
110
|
+
let data = null
|
|
111
|
+
const useWorkerData = !!workerData
|
|
112
|
+
|
|
113
|
+
if (useWorkerData) {
|
|
114
|
+
data = workerData
|
|
115
|
+
} else if (process.env.PLT_MANAGER_ID) {
|
|
116
|
+
try {
|
|
117
|
+
const dataPath = resolve(tmpdir(), 'platformatic', 'runtimes', `${process.env.PLT_MANAGER_ID}.json`)
|
|
118
|
+
statSync(dataPath)
|
|
119
|
+
const jsonData = JSON.parse(readFileSync(dataPath, 'utf8'))
|
|
120
|
+
data = jsonData.data
|
|
121
|
+
debuglog(`Loaded data from ${dataPath}`)
|
|
122
|
+
} catch (e) {
|
|
123
|
+
debuglog('Error reading data from file %o', e)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (data) {
|
|
128
|
+
debuglog('Setting up telemetry %o', data)
|
|
129
|
+
const applicationDir = data.applicationConfig?.path
|
|
130
|
+
const telemetryConfig = useWorkerData ? data?.applicationConfig?.telemetry : data?.telemetryConfig
|
|
131
|
+
if (telemetryConfig) {
|
|
132
|
+
debuglog('telemetryConfig %o', telemetryConfig)
|
|
133
|
+
setupNodeHTTPTelemetry(telemetryConfig, applicationDir)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
main()
|
|
140
|
+
} catch (e) {
|
|
141
|
+
debuglog('Error in main %o', e)
|
|
142
|
+
}
|
|
@@ -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,11 +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
|
-
|
|
5
|
-
const { Tracer } = require('@opentelemetry/sdk-trace-base')
|
|
6
|
-
const { MultiSpanProcessor } = require('./multispan-processor')
|
|
7
|
+
const require = createRequire(import.meta.url)
|
|
8
|
+
// We need to import the Tracer to write our own TracerProvider that does NOT extend the OpenTelemetry one.
|
|
9
|
+
const { Tracer } = require('@opentelemetry/sdk-trace-base/build/src/Tracer')
|
|
7
10
|
|
|
8
|
-
class PlatformaticTracerProvider {
|
|
11
|
+
export class PlatformaticTracerProvider {
|
|
9
12
|
activeSpanProcessor = null
|
|
10
13
|
_registeredSpanProcessors = []
|
|
11
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
|
|
@@ -16,19 +19,19 @@ class PlatformaticTracerProvider {
|
|
|
16
19
|
const mergedConfig = merge(
|
|
17
20
|
{},
|
|
18
21
|
{
|
|
19
|
-
sampler: new AlwaysOnSampler()
|
|
22
|
+
sampler: new AlwaysOnSampler()
|
|
20
23
|
},
|
|
21
24
|
config
|
|
22
25
|
)
|
|
23
|
-
this.resource = mergedConfig.resource ??
|
|
26
|
+
this.resource = mergedConfig.resource ?? emptyResource
|
|
24
27
|
this._config = Object.assign({}, mergedConfig, {
|
|
25
|
-
resource: this.resource
|
|
28
|
+
resource: this.resource
|
|
26
29
|
})
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
// This is the only mandatory API: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#get-a-tracer
|
|
30
|
-
getTracer (name, version
|
|
31
|
-
return new Tracer({ name, version }, this._config, this)
|
|
33
|
+
getTracer (name, version) {
|
|
34
|
+
return new Tracer({ name, version }, this._config, this.resource, this.activeSpanProcessor)
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
addSpanProcessor (spanProcessor) {
|
|
@@ -37,9 +40,7 @@ class PlatformaticTracerProvider {
|
|
|
37
40
|
} else {
|
|
38
41
|
this._registeredSpanProcessors.push(spanProcessor)
|
|
39
42
|
}
|
|
40
|
-
this.activeSpanProcessor = new MultiSpanProcessor(
|
|
41
|
-
this._registeredSpanProcessors
|
|
42
|
-
)
|
|
43
|
+
this.activeSpanProcessor = new MultiSpanProcessor(this._registeredSpanProcessors)
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
getActiveSpanProcessor () {
|
|
@@ -49,14 +50,13 @@ class PlatformaticTracerProvider {
|
|
|
49
50
|
getPropagator () {
|
|
50
51
|
return new CompositePropagator({
|
|
51
52
|
propagators: [
|
|
52
|
-
new W3CTraceContextPropagator()
|
|
53
|
-
]
|
|
53
|
+
new W3CTraceContextPropagator() // see: https://www.w3.org/TR/trace-context/
|
|
54
|
+
]
|
|
54
55
|
})
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
forceFlush () {
|
|
58
59
|
// Let's do a fire-and-forget of forceFlush on all the processor for the time being.
|
|
59
|
-
// TODO: manage errors
|
|
60
60
|
this._registeredSpanProcessors.forEach(spanProcessor => spanProcessor.forceFlush())
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -64,5 +64,3 @@ class PlatformaticTracerProvider {
|
|
|
64
64
|
return this.activeSpanProcessor.shutdown()
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
module.exports = { PlatformaticTracerProvider }
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { importOrLocal } from './import-or-local.js'
|
|
2
|
+
|
|
3
|
+
// These are already set automatically by the runtime, so we throw
|
|
4
|
+
// if set again.
|
|
5
|
+
const defaultInstrumentations = ['@opentelemetry/instrumentation-http', '@opentelemetry/instrumentation-undici']
|
|
6
|
+
|
|
7
|
+
async function getInstrumentationInstance (instrumentationConfig, applicationDir) {
|
|
8
|
+
if (typeof instrumentationConfig === 'string') {
|
|
9
|
+
instrumentationConfig = { package: instrumentationConfig, exportName: 'default', options: {} }
|
|
10
|
+
}
|
|
11
|
+
const { package: packageName, exportName = 'default', options = {} } = instrumentationConfig
|
|
12
|
+
|
|
13
|
+
if (defaultInstrumentations.includes(packageName)) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`Instrumentation package ${packageName} is already included by default, please remove it from your config.`
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let mod
|
|
20
|
+
try {
|
|
21
|
+
mod = await importOrLocal({ pkg: packageName, projectDir: applicationDir })
|
|
22
|
+
} catch (err) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Instrumentation package not found: ${instrumentationConfig.package}, please add it to your dependencies.`
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let Instrumenter = mod[exportName]
|
|
29
|
+
if (!Instrumenter || typeof Instrumenter !== 'function') {
|
|
30
|
+
// Check for for an export that ends with 'Instrumentation'. We need to do that because unfortunately
|
|
31
|
+
// each instrumenttions has different named export. But all of them ends with 'Instrumentation'.
|
|
32
|
+
const possibleExports = Object.keys(mod).filter(key => key.endsWith('Instrumentation'))
|
|
33
|
+
if (possibleExports.length === 0) {
|
|
34
|
+
throw new Error(`Instrumentation export not found: ${exportName} in ${packageName}. Please specify in config`)
|
|
35
|
+
}
|
|
36
|
+
if (possibleExports.length > 1) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Multiple Instrumentation exports found: ${possibleExports} in ${packageName}. Please specify in config`
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
Instrumenter = mod[possibleExports[0]]
|
|
42
|
+
}
|
|
43
|
+
const instance = new Instrumenter(options)
|
|
44
|
+
return instance
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Example of instrumentations config:
|
|
48
|
+
// "instrumentations": [
|
|
49
|
+
// "@opentelemetry/instrumentation-express",
|
|
50
|
+
// {
|
|
51
|
+
// "package": "@opentelemetry/instrumentation-redisjs",
|
|
52
|
+
// "exportName": "RedisInstrumentation",
|
|
53
|
+
// "options": { "foo": "bar" }
|
|
54
|
+
// }
|
|
55
|
+
export async function getInstrumentations (configs = [], applicationDir) {
|
|
56
|
+
const instrumentations = []
|
|
57
|
+
for (const instrumentationConfig of configs) {
|
|
58
|
+
const instance = await getInstrumentationInstance(instrumentationConfig, applicationDir)
|
|
59
|
+
instrumentations.push(instance)
|
|
60
|
+
}
|
|
61
|
+
return instrumentations
|
|
62
|
+
}
|
package/lib/schema.js
CHANGED
|
@@ -1,73 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
import { schemaComponents } from '@platformatic/foundation'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
type: 'object',
|
|
5
|
-
properties: {
|
|
6
|
-
type: {
|
|
7
|
-
type: 'string',
|
|
8
|
-
enum: ['console', 'otlp', 'zipkin', 'memory'],
|
|
9
|
-
default: 'console',
|
|
10
|
-
},
|
|
11
|
-
options: {
|
|
12
|
-
type: 'object',
|
|
13
|
-
description: 'Options for the exporter. These are passed directly to the exporter.',
|
|
14
|
-
properties: {
|
|
15
|
-
url: {
|
|
16
|
-
type: 'string',
|
|
17
|
-
description: 'The URL to send the traces to. Not used for console or memory exporters.',
|
|
18
|
-
},
|
|
19
|
-
headers: {
|
|
20
|
-
type: 'object',
|
|
21
|
-
description: 'Headers to send to the exporter. Not used for console or memory exporters.',
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
additionalProperties: false,
|
|
26
|
-
},
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const TelemetrySchema = {
|
|
30
|
-
$id: '/OpenTelemetry',
|
|
31
|
-
type: 'object',
|
|
32
|
-
properties: {
|
|
33
|
-
serviceName: {
|
|
34
|
-
type: 'string',
|
|
35
|
-
description: 'The name of the service. Defaults to the folder name if not specified.',
|
|
36
|
-
},
|
|
37
|
-
version: {
|
|
38
|
-
type: 'string',
|
|
39
|
-
description: 'The version of the service (optional)',
|
|
40
|
-
},
|
|
41
|
-
skip: {
|
|
42
|
-
type: 'array',
|
|
43
|
-
description: 'An array of paths to skip when creating spans. Useful for health checks and other endpoints that do not need to be traced.',
|
|
44
|
-
items: {
|
|
45
|
-
type: 'object',
|
|
46
|
-
properties: {
|
|
47
|
-
path: {
|
|
48
|
-
type: 'string',
|
|
49
|
-
description: 'The path to skip. Can be a string or a regex.',
|
|
50
|
-
},
|
|
51
|
-
method: {
|
|
52
|
-
description: 'HTTP method to skip',
|
|
53
|
-
type: 'string',
|
|
54
|
-
enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
exporter: {
|
|
60
|
-
anyOf: [
|
|
61
|
-
{
|
|
62
|
-
type: 'array',
|
|
63
|
-
items: ExporterSchema,
|
|
64
|
-
},
|
|
65
|
-
ExporterSchema,
|
|
66
|
-
],
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
required: ['serviceName'],
|
|
70
|
-
additionalProperties: false,
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
module.exports = TelemetrySchema
|
|
3
|
+
export default schemaComponents.telemetry
|