@platformatic/runtime 3.58.1 → 3.60.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/config.d.ts +72 -1
- package/index.js +2 -2
- package/lib/config.js +25 -0
- package/lib/logger.js +7 -4
- package/lib/management-api.js +10 -1
- package/lib/opentelemetry-metrics.js +93 -0
- package/lib/prom-server.js +65 -3
- package/lib/runtime.js +79 -12
- package/lib/schema.js +19 -0
- package/package.json +18 -16
- package/schema.json +244 -2
package/config.d.ts
CHANGED
|
@@ -18,6 +18,12 @@ export type PlatformaticRuntimeConfig = {
|
|
|
18
18
|
mappings?: {
|
|
19
19
|
[k: string]: {
|
|
20
20
|
id: string;
|
|
21
|
+
enabled?:
|
|
22
|
+
| boolean
|
|
23
|
+
| string
|
|
24
|
+
| {
|
|
25
|
+
[k: string]: boolean;
|
|
26
|
+
};
|
|
21
27
|
config?: string;
|
|
22
28
|
useHttp?: boolean;
|
|
23
29
|
reuseTcpPorts?: boolean;
|
|
@@ -180,6 +186,11 @@ export type PlatformaticRuntimeConfig = {
|
|
|
180
186
|
url: string;
|
|
181
187
|
};
|
|
182
188
|
captureStdio?: boolean;
|
|
189
|
+
pino?: {
|
|
190
|
+
level?: string;
|
|
191
|
+
time?: string;
|
|
192
|
+
message?: string;
|
|
193
|
+
};
|
|
183
194
|
[k: string]: unknown;
|
|
184
195
|
};
|
|
185
196
|
server?: {
|
|
@@ -243,7 +254,40 @@ export type PlatformaticRuntimeConfig = {
|
|
|
243
254
|
bufferPoolSize?: number | string;
|
|
244
255
|
defaultHighWaterMark?: number | string;
|
|
245
256
|
};
|
|
246
|
-
healthProbes?:
|
|
257
|
+
healthProbes?:
|
|
258
|
+
| boolean
|
|
259
|
+
| string
|
|
260
|
+
| {
|
|
261
|
+
enabled?: boolean | string;
|
|
262
|
+
hostname?: string;
|
|
263
|
+
port?: number | string;
|
|
264
|
+
readiness?:
|
|
265
|
+
| boolean
|
|
266
|
+
| {
|
|
267
|
+
endpoint?: string;
|
|
268
|
+
success?: {
|
|
269
|
+
statusCode?: number;
|
|
270
|
+
body?: string;
|
|
271
|
+
};
|
|
272
|
+
fail?: {
|
|
273
|
+
statusCode?: number;
|
|
274
|
+
body?: string;
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
liveness?:
|
|
278
|
+
| boolean
|
|
279
|
+
| {
|
|
280
|
+
endpoint?: string;
|
|
281
|
+
success?: {
|
|
282
|
+
statusCode?: number;
|
|
283
|
+
body?: string;
|
|
284
|
+
};
|
|
285
|
+
fail?: {
|
|
286
|
+
statusCode?: number;
|
|
287
|
+
body?: string;
|
|
288
|
+
};
|
|
289
|
+
};
|
|
290
|
+
};
|
|
247
291
|
undici?: {
|
|
248
292
|
agentOptions?: {
|
|
249
293
|
[k: string]: unknown;
|
|
@@ -402,6 +446,10 @@ export type PlatformaticRuntimeConfig = {
|
|
|
402
446
|
body?: string;
|
|
403
447
|
};
|
|
404
448
|
};
|
|
449
|
+
/**
|
|
450
|
+
* @deprecated
|
|
451
|
+
* Deprecated. Health probe timeout configuration is no longer used.
|
|
452
|
+
*/
|
|
405
453
|
healthChecksTimeouts?: number | string;
|
|
406
454
|
plugins?: string[];
|
|
407
455
|
timeout?: number | string;
|
|
@@ -436,6 +484,29 @@ export type PlatformaticRuntimeConfig = {
|
|
|
436
484
|
*/
|
|
437
485
|
serviceVersion?: string;
|
|
438
486
|
};
|
|
487
|
+
/**
|
|
488
|
+
* Configuration for forwarding user OpenTelemetry metrics to an OTLP endpoint
|
|
489
|
+
*/
|
|
490
|
+
opentelemetry?: {
|
|
491
|
+
/**
|
|
492
|
+
* Enable or disable OpenTelemetry metrics forwarding
|
|
493
|
+
*/
|
|
494
|
+
enabled?: boolean | string;
|
|
495
|
+
/**
|
|
496
|
+
* OTLP metrics endpoint URL (e.g., http://collector:4318/v1/metrics)
|
|
497
|
+
*/
|
|
498
|
+
endpoint: string;
|
|
499
|
+
/**
|
|
500
|
+
* Interval in milliseconds between metric forwards
|
|
501
|
+
*/
|
|
502
|
+
interval?: number | string;
|
|
503
|
+
/**
|
|
504
|
+
* Additional HTTP headers for authentication
|
|
505
|
+
*/
|
|
506
|
+
headers?: {
|
|
507
|
+
[k: string]: string;
|
|
508
|
+
};
|
|
509
|
+
};
|
|
439
510
|
/**
|
|
440
511
|
* Custom labels to add to HTTP metrics (http_request_all_duration_seconds). Each label extracts its value from an HTTP request header.
|
|
441
512
|
*/
|
package/index.js
CHANGED
|
@@ -87,7 +87,7 @@ export async function loadConfiguration (configOrRoot, sourceOrConfig, context)
|
|
|
87
87
|
})
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
export async function loadApplicationsCommands (executableName = '') {
|
|
90
|
+
export async function loadApplicationsCommands (executableName = '', configurationFile = null) {
|
|
91
91
|
const applications = {}
|
|
92
92
|
const commands = {}
|
|
93
93
|
const help = {}
|
|
@@ -97,7 +97,7 @@ export async function loadApplicationsCommands (executableName = '') {
|
|
|
97
97
|
const file = await findRuntimeConfigurationFile(
|
|
98
98
|
abstractLogger,
|
|
99
99
|
process.cwd(),
|
|
100
|
-
|
|
100
|
+
configurationFile,
|
|
101
101
|
false,
|
|
102
102
|
false,
|
|
103
103
|
true,
|
package/lib/config.js
CHANGED
|
@@ -295,8 +295,27 @@ export async function prepareApplication (config, application, defaultWorkers) {
|
|
|
295
295
|
return application
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
function isApplicationEnabled (application, environment) {
|
|
299
|
+
const { enabled } = application
|
|
300
|
+
|
|
301
|
+
if (typeof enabled === 'undefined') {
|
|
302
|
+
return true
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (typeof enabled === 'string') {
|
|
306
|
+
return enabled !== 'false'
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (typeof enabled === 'object' && enabled !== null) {
|
|
310
|
+
return enabled[environment] ?? true
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return enabled
|
|
314
|
+
}
|
|
315
|
+
|
|
298
316
|
export async function transform (config, _, context) {
|
|
299
317
|
const production = context?.isProduction ?? context?.production
|
|
318
|
+
const environment = production ? 'production' : 'development'
|
|
300
319
|
const applications = [...(config.applications ?? []), ...(config.services ?? []), ...(config.web ?? [])]
|
|
301
320
|
|
|
302
321
|
const watchType = typeof config.watch
|
|
@@ -391,6 +410,12 @@ export async function transform (config, _, context) {
|
|
|
391
410
|
}
|
|
392
411
|
}
|
|
393
412
|
|
|
413
|
+
for (let i = applications.length - 1; i >= 0; --i) {
|
|
414
|
+
if (!isApplicationEnabled(applications[i], environment)) {
|
|
415
|
+
applications.splice(i, 1)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
394
419
|
config.inspectorOptions = undefined
|
|
395
420
|
parseInspectorOptions(config, context?.inspect, context?.inspectBreak)
|
|
396
421
|
|
package/lib/logger.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildPinoFormatters,
|
|
3
3
|
buildPinoTimestamp,
|
|
4
|
+
kMetadata,
|
|
4
5
|
} from '@platformatic/foundation'
|
|
5
6
|
import { isatty } from 'node:tty'
|
|
6
7
|
import pino from 'pino'
|
|
@@ -63,7 +64,8 @@ const colors = [
|
|
|
63
64
|
226 // bright yellow
|
|
64
65
|
]
|
|
65
66
|
|
|
66
|
-
function createLoggerContext () {
|
|
67
|
+
function createLoggerContext (config) {
|
|
68
|
+
const env = config[kMetadata].env
|
|
67
69
|
const context = {
|
|
68
70
|
colors: {},
|
|
69
71
|
closeables: [],
|
|
@@ -76,7 +78,7 @@ function createLoggerContext () {
|
|
|
76
78
|
context.maxLength = Math.max(context.maxLength, id.length)
|
|
77
79
|
let hash = 0
|
|
78
80
|
|
|
79
|
-
if (!pretty.isColorSupported &&
|
|
81
|
+
if (!pretty.isColorSupported && env.FORCE_COLOR !== 'true') {
|
|
80
82
|
context.colors[id] = ''
|
|
81
83
|
continue
|
|
82
84
|
}
|
|
@@ -151,7 +153,8 @@ function createPrettifier (context) {
|
|
|
151
153
|
|
|
152
154
|
// Create the runtime logger
|
|
153
155
|
export async function createLogger (config) {
|
|
154
|
-
const
|
|
156
|
+
const env = config[kMetadata].env
|
|
157
|
+
const context = createLoggerContext(config)
|
|
155
158
|
|
|
156
159
|
const loggerConfig = { ...config.logger, transport: undefined }
|
|
157
160
|
if (config.logger.base === null) {
|
|
@@ -168,7 +171,7 @@ export async function createLogger (config) {
|
|
|
168
171
|
}
|
|
169
172
|
|
|
170
173
|
cliStream = pino.transport(config.logger.transport)
|
|
171
|
-
} else if ((
|
|
174
|
+
} else if ((env.FORCE_TTY || isatty(1)) && env.PLT_PRETTY_PRINT !== 'false') {
|
|
172
175
|
cliStream = createPrettifier(context)
|
|
173
176
|
} else {
|
|
174
177
|
cliStream = pino.destination(1)
|
package/lib/management-api.js
CHANGED
|
@@ -3,11 +3,13 @@ import fastifyWebsocket from '@fastify/websocket'
|
|
|
3
3
|
import {
|
|
4
4
|
applications as applicationSchema,
|
|
5
5
|
createDirectory,
|
|
6
|
+
createSharedTemporaryDirectory,
|
|
6
7
|
kMetadata,
|
|
7
8
|
safeRemove,
|
|
8
9
|
validate
|
|
9
10
|
} from '@platformatic/foundation'
|
|
10
11
|
import fastify from 'fastify'
|
|
12
|
+
import { chmod } from 'node:fs/promises'
|
|
11
13
|
import { platform, tmpdir } from 'node:os'
|
|
12
14
|
import { dirname, join } from 'node:path'
|
|
13
15
|
import { setTimeout as sleep } from 'node:timers/promises'
|
|
@@ -343,7 +345,14 @@ export async function startManagementApi (runtime, config) {
|
|
|
343
345
|
|
|
344
346
|
const runtimePIDDir = join(PLATFORMATIC_TMP_DIR, runtimePID.toString())
|
|
345
347
|
if (platform() !== 'win32') {
|
|
346
|
-
|
|
348
|
+
if (customSocket) {
|
|
349
|
+
await createDirectory(dirname(customSocket))
|
|
350
|
+
} else {
|
|
351
|
+
await createSharedTemporaryDirectory('platformatic', 'runtimes')
|
|
352
|
+
await createDirectory(runtimePIDDir, true)
|
|
353
|
+
// The socket must only be accessible by the current user
|
|
354
|
+
await chmod(runtimePIDDir, 0o700)
|
|
355
|
+
}
|
|
347
356
|
}
|
|
348
357
|
|
|
349
358
|
let socketPath = null
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export class OpenTelemetryMetricsForwarder {
|
|
2
|
+
#config
|
|
3
|
+
#exporter
|
|
4
|
+
#logger
|
|
5
|
+
#queue
|
|
6
|
+
#timer
|
|
7
|
+
#flushing
|
|
8
|
+
|
|
9
|
+
constructor (config, logger) {
|
|
10
|
+
this.#config = config
|
|
11
|
+
this.#logger = logger
|
|
12
|
+
this.#queue = []
|
|
13
|
+
this.#timer = null
|
|
14
|
+
this.#flushing = false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async start () {
|
|
18
|
+
if (!this.#config || this.#config.enabled === false || this.#config.enabled === 'false' || !this.#config.endpoint) {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { OTLPMetricExporter } = await import('@opentelemetry/exporter-metrics-otlp-proto')
|
|
23
|
+
this.#exporter = new OTLPMetricExporter({
|
|
24
|
+
url: this.#config.endpoint,
|
|
25
|
+
headers: this.#config.headers
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const interval = this.#config.interval ?? 60000
|
|
29
|
+
this.#timer = setInterval(() => {
|
|
30
|
+
this.flush().catch(error => {
|
|
31
|
+
this.#logger.error({ err: error }, 'OpenTelemetry metrics export failed.')
|
|
32
|
+
})
|
|
33
|
+
}, interval)
|
|
34
|
+
this.#timer.unref()
|
|
35
|
+
|
|
36
|
+
return true
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
collect (resourceMetrics) {
|
|
40
|
+
if (!this.#exporter) {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.#queue.push(resourceMetrics)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async flush () {
|
|
48
|
+
if (!this.#exporter || this.#flushing || this.#queue.length === 0) {
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.#flushing = true
|
|
53
|
+
const batch = this.#queue.splice(0)
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
await Promise.all(batch.map(resourceMetrics => this.#export(resourceMetrics)))
|
|
57
|
+
} finally {
|
|
58
|
+
this.#flushing = false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async close () {
|
|
63
|
+
if (this.#timer) {
|
|
64
|
+
clearInterval(this.#timer)
|
|
65
|
+
this.#timer = null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await this.flush()
|
|
70
|
+
} catch (error) {
|
|
71
|
+
this.#logger.error({ err: error }, 'OpenTelemetry metrics export failed.')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this.#exporter) {
|
|
75
|
+
await this.#exporter.shutdown()
|
|
76
|
+
this.#exporter = null
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#export (resourceMetrics) {
|
|
81
|
+
const { promise, resolve, reject } = Promise.withResolvers()
|
|
82
|
+
|
|
83
|
+
this.#exporter.export(resourceMetrics, result => {
|
|
84
|
+
if (result.code === 0) {
|
|
85
|
+
resolve()
|
|
86
|
+
} else {
|
|
87
|
+
reject(result.error ?? new Error('OpenTelemetry metrics export failed.'))
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
return promise
|
|
92
|
+
}
|
|
93
|
+
}
|
package/lib/prom-server.js
CHANGED
|
@@ -76,13 +76,59 @@ async function checkLiveness (runtime) {
|
|
|
76
76
|
return { response, status }
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
function isHealthProbesServerEnabled (healthProbes) {
|
|
80
|
+
return typeof healthProbes === 'object' && healthProbes !== null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isHealthProbesEnabled (healthProbes) {
|
|
84
|
+
return healthProbes !== false && healthProbes?.enabled !== false
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function resolveServerAddress (opts, fallback) {
|
|
88
|
+
opts = typeof opts === 'object' && opts !== null ? opts : {}
|
|
89
|
+
fallback = typeof fallback === 'object' && fallback !== null ? fallback : {}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
host: opts.hostname ?? fallback.hostname ?? DEFAULT_HOSTNAME,
|
|
93
|
+
port: opts.port ?? fallback.port ?? DEFAULT_PORT
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function useSharedServer (metricsOpts, healthProbes) {
|
|
98
|
+
if (!isHealthProbesServerEnabled(healthProbes)) {
|
|
99
|
+
return true
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const metricsAddress = resolveServerAddress(metricsOpts)
|
|
103
|
+
const healthProbesAddress = resolveServerAddress(healthProbes, metricsOpts)
|
|
104
|
+
|
|
105
|
+
return metricsAddress.host === healthProbesAddress.host && String(metricsAddress.port) === String(healthProbesAddress.port)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function resolveHealthProbesOptions (metricsOpts, healthProbes) {
|
|
109
|
+
metricsOpts = typeof metricsOpts === 'object' && metricsOpts !== null ? metricsOpts : {}
|
|
110
|
+
|
|
111
|
+
if (!isHealthProbesServerEnabled(healthProbes)) {
|
|
112
|
+
return metricsOpts
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const { host, port } = resolveServerAddress(healthProbes, metricsOpts)
|
|
116
|
+
return {
|
|
117
|
+
...healthProbes,
|
|
118
|
+
hostname: host,
|
|
119
|
+
port,
|
|
120
|
+
readiness: healthProbes.readiness ?? metricsOpts.readiness,
|
|
121
|
+
liveness: healthProbes.liveness ?? metricsOpts.liveness
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function startServer (runtime, opts, metricsEnabled, healthProbesEnabled) {
|
|
126
|
+
opts = typeof opts === 'object' && opts !== null ? opts : {}
|
|
82
127
|
|
|
83
128
|
if (!metricsEnabled && !healthProbesEnabled) {
|
|
84
129
|
return
|
|
85
130
|
}
|
|
131
|
+
|
|
86
132
|
const host = opts.hostname ?? DEFAULT_HOSTNAME
|
|
87
133
|
const port = opts.port ?? DEFAULT_PORT
|
|
88
134
|
const metricsEndpoint = opts.endpoint ?? DEFAULT_METRICS_ENDPOINT
|
|
@@ -238,3 +284,19 @@ export async function startPrometheusServer (runtime, opts, healthProbes) {
|
|
|
238
284
|
await promServer.listen({ port, host })
|
|
239
285
|
return promServer
|
|
240
286
|
}
|
|
287
|
+
|
|
288
|
+
export async function startPrometheusServer (runtime, opts, healthProbes) {
|
|
289
|
+
const metricsEnabled = opts !== false && opts?.enabled !== false
|
|
290
|
+
const healthProbesEnabled = isHealthProbesEnabled(healthProbes) && useSharedServer(opts, healthProbes)
|
|
291
|
+
const serverOpts = healthProbesEnabled && isHealthProbesServerEnabled(healthProbes) ? resolveHealthProbesOptions(opts, healthProbes) : opts
|
|
292
|
+
|
|
293
|
+
return startServer(runtime, serverOpts, metricsEnabled, healthProbesEnabled)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export async function startHealthProbesServer (runtime, metricsOpts, healthProbes) {
|
|
297
|
+
if (!isHealthProbesEnabled(healthProbes) || useSharedServer(metricsOpts, healthProbes)) {
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return startServer(runtime, resolveHealthProbesOptions(metricsOpts, healthProbes), false, true)
|
|
302
|
+
}
|
package/lib/runtime.js
CHANGED
|
@@ -11,7 +11,11 @@ import {
|
|
|
11
11
|
} from '@platformatic/foundation'
|
|
12
12
|
import { getExecutable } from '@platformatic/globals'
|
|
13
13
|
import { ITC } from '@platformatic/itc'
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
collectProcessMetrics,
|
|
16
|
+
client as metricsClient,
|
|
17
|
+
openTelemetryITCMessage
|
|
18
|
+
} from '@platformatic/metrics'
|
|
15
19
|
import fastify from 'fastify'
|
|
16
20
|
import { EventEmitter, once } from 'node:events'
|
|
17
21
|
import { existsSync } from 'node:fs'
|
|
@@ -45,8 +49,9 @@ import {
|
|
|
45
49
|
import { abstractLogger, createLogger } from './logger.js'
|
|
46
50
|
import { startManagementApi } from './management-api.js'
|
|
47
51
|
import { createManagementHandlers } from './management-handlers.js'
|
|
52
|
+
import { OpenTelemetryMetricsForwarder } from './opentelemetry-metrics.js'
|
|
48
53
|
import { createChannelCreationHook } from './policies.js'
|
|
49
|
-
import { startPrometheusServer } from './prom-server.js'
|
|
54
|
+
import { startHealthProbesServer, startPrometheusServer } from './prom-server.js'
|
|
50
55
|
import { startScheduler } from './scheduler.js'
|
|
51
56
|
import { createSharedStore } from './shared-http-cache.js'
|
|
52
57
|
import { topologicalLevels, topologicalSort } from './utils.js'
|
|
@@ -116,6 +121,10 @@ export class Runtime extends EventEmitter {
|
|
|
116
121
|
#root
|
|
117
122
|
#config
|
|
118
123
|
#env
|
|
124
|
+
#pinoLevelKey
|
|
125
|
+
#pinoTimeKey
|
|
126
|
+
#pinoMessageKey
|
|
127
|
+
#pinoCustomizedKeys
|
|
119
128
|
#context
|
|
120
129
|
#sharedContext
|
|
121
130
|
#isProduction
|
|
@@ -131,6 +140,8 @@ export class Runtime extends EventEmitter {
|
|
|
131
140
|
|
|
132
141
|
#managementApi
|
|
133
142
|
#prometheusServer
|
|
143
|
+
#healthProbesServer
|
|
144
|
+
#opentelemetryMetricsForwarder
|
|
134
145
|
#inspectorServer
|
|
135
146
|
#metricsLabelName
|
|
136
147
|
|
|
@@ -159,6 +170,11 @@ export class Runtime extends EventEmitter {
|
|
|
159
170
|
this.#config = config
|
|
160
171
|
this.#root = config[kMetadata].root
|
|
161
172
|
this.#env = config[kMetadata].env
|
|
173
|
+
this.#pinoLevelKey = config.logger.pino?.level ?? 'level'
|
|
174
|
+
this.#pinoTimeKey = config.logger.pino?.time ?? 'time'
|
|
175
|
+
this.#pinoMessageKey = config.logger.pino?.message ?? 'msg'
|
|
176
|
+
this.#pinoCustomizedKeys =
|
|
177
|
+
this.#pinoLevelKey !== 'level' || this.#pinoTimeKey !== 'time' || this.#pinoMessageKey !== 'msg'
|
|
162
178
|
this.#context = context ?? {}
|
|
163
179
|
this.#isProduction = this.#context.isProduction ?? this.#context.production ?? false
|
|
164
180
|
this.#concurrency = Math.max(1, config.startupConcurrency ?? this.#context.concurrency ?? DEFAULT_CONCURRENCY)
|
|
@@ -229,12 +245,17 @@ export class Runtime extends EventEmitter {
|
|
|
229
245
|
if (config.metrics) {
|
|
230
246
|
// Use the configured application label name for metrics (defaults to 'applicationId')
|
|
231
247
|
this.#metricsLabelName = config.metrics.applicationLabel || 'applicationId'
|
|
232
|
-
this.#prometheusServer = await startPrometheusServer(this, config.metrics, config.healthProbes)
|
|
233
248
|
} else {
|
|
234
249
|
// Default to applicationId if metrics are not configured
|
|
235
250
|
this.#metricsLabelName = 'applicationId'
|
|
236
251
|
}
|
|
237
252
|
|
|
253
|
+
if (config.metrics || (typeof config.healthProbes === 'object' && config.healthProbes !== null)) {
|
|
254
|
+
this.#prometheusServer = await startPrometheusServer(this, config.metrics ?? false, config.healthProbes)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
this.#healthProbesServer = await startHealthProbesServer(this, config.metrics, config.healthProbes)
|
|
258
|
+
|
|
238
259
|
// Initialize process-level metrics registry in the main thread if metrics or management API is enabled
|
|
239
260
|
// These metrics are the same across all workers and only need to be collected once
|
|
240
261
|
// We need this for management API as it can request metrics even without explicit metrics config
|
|
@@ -249,6 +270,8 @@ export class Runtime extends EventEmitter {
|
|
|
249
270
|
this.#loggerDestination = destination
|
|
250
271
|
this.#loggerContext = context
|
|
251
272
|
|
|
273
|
+
await this.#startOpenTelemetryMetricsForwarder(config.metrics?.opentelemetry)
|
|
274
|
+
|
|
252
275
|
this.#createWorkersBroadcastChannel()
|
|
253
276
|
|
|
254
277
|
if (this.#config.workers.dynamic) {
|
|
@@ -398,6 +421,15 @@ export class Runtime extends EventEmitter {
|
|
|
398
421
|
await this.#prometheusServer.close()
|
|
399
422
|
}
|
|
400
423
|
|
|
424
|
+
if (this.#healthProbesServer) {
|
|
425
|
+
await this.#healthProbesServer.close()
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (this.#opentelemetryMetricsForwarder) {
|
|
429
|
+
await this.#opentelemetryMetricsForwarder.close()
|
|
430
|
+
this.#opentelemetryMetricsForwarder = null
|
|
431
|
+
}
|
|
432
|
+
|
|
401
433
|
// Clean up process metrics registry
|
|
402
434
|
if (this.#processMetricsRegistry) {
|
|
403
435
|
this.#processMetricsRegistry.clear()
|
|
@@ -855,12 +887,24 @@ export class Runtime extends EventEmitter {
|
|
|
855
887
|
this.#prometheusServer = null
|
|
856
888
|
}
|
|
857
889
|
|
|
890
|
+
if (this.#healthProbesServer) {
|
|
891
|
+
await this.#healthProbesServer.close()
|
|
892
|
+
this.#healthProbesServer = null
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (this.#opentelemetryMetricsForwarder) {
|
|
896
|
+
await this.#opentelemetryMetricsForwarder.close()
|
|
897
|
+
this.#opentelemetryMetricsForwarder = null
|
|
898
|
+
}
|
|
899
|
+
|
|
858
900
|
this.#config.metrics = metricsConfig
|
|
859
901
|
this.#metricsLabelName = metricsConfig?.applicationLabel || 'applicationId'
|
|
860
902
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
903
|
+
this.#prometheusServer = await startPrometheusServer(this, metricsConfig, this.#config.healthProbes)
|
|
904
|
+
|
|
905
|
+
this.#healthProbesServer = await startHealthProbesServer(this, metricsConfig, this.#config.healthProbes)
|
|
906
|
+
|
|
907
|
+
await this.#startOpenTelemetryMetricsForwarder(metricsConfig?.opentelemetry)
|
|
864
908
|
|
|
865
909
|
const promises = []
|
|
866
910
|
for (const worker of this.#workers.values()) {
|
|
@@ -880,6 +924,17 @@ export class Runtime extends EventEmitter {
|
|
|
880
924
|
return { success: true, config: metricsConfig }
|
|
881
925
|
}
|
|
882
926
|
|
|
927
|
+
async #startOpenTelemetryMetricsForwarder (config) {
|
|
928
|
+
if (!config?.endpoint || config.enabled === false || config.enabled === 'false') {
|
|
929
|
+
return
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
const forwarder = new OpenTelemetryMetricsForwarder(config, this.logger)
|
|
933
|
+
if (await forwarder.start()) {
|
|
934
|
+
this.#opentelemetryMetricsForwarder = forwarder
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
883
938
|
// TODO: Remove in next major version
|
|
884
939
|
startCollectingMetrics () {
|
|
885
940
|
this.logger.warn(
|
|
@@ -2017,6 +2072,10 @@ export class Runtime extends EventEmitter {
|
|
|
2017
2072
|
worker[kIsSubprocessHost] = true
|
|
2018
2073
|
})
|
|
2019
2074
|
|
|
2075
|
+
worker[kITC].on(openTelemetryITCMessage, resourceMetrics => {
|
|
2076
|
+
this.#opentelemetryMetricsForwarder?.collect(resourceMetrics)
|
|
2077
|
+
})
|
|
2078
|
+
|
|
2020
2079
|
worker[kITC].on('request:restart', async () => {
|
|
2021
2080
|
// Do not restart applications that are not fully started yet or when the runtime is still starting.
|
|
2022
2081
|
// The gateway sends request:restart when it receives application:added events,
|
|
@@ -2884,13 +2943,16 @@ export class Runtime extends EventEmitter {
|
|
|
2884
2943
|
}
|
|
2885
2944
|
|
|
2886
2945
|
let pinoLog
|
|
2946
|
+
let pinoLevel
|
|
2887
2947
|
|
|
2888
2948
|
if (message !== null && typeof message === 'object') {
|
|
2949
|
+
pinoLevel = message[this.#pinoLevelKey]
|
|
2950
|
+
|
|
2889
2951
|
pinoLog =
|
|
2890
|
-
typeof
|
|
2952
|
+
(typeof pinoLevel === 'number' || (this.#pinoCustomizedKeys && typeof pinoLevel === 'string')) &&
|
|
2891
2953
|
// We want to accept both pino raw time (number) and time as formatted string
|
|
2892
|
-
(typeof message
|
|
2893
|
-
typeof message
|
|
2954
|
+
(typeof message[this.#pinoTimeKey] === 'number' || typeof message[this.#pinoTimeKey] === 'string') &&
|
|
2955
|
+
typeof message[this.#pinoMessageKey] === 'string'
|
|
2894
2956
|
}
|
|
2895
2957
|
|
|
2896
2958
|
// Directly write to the Pino destination
|
|
@@ -2899,9 +2961,14 @@ export class Runtime extends EventEmitter {
|
|
|
2899
2961
|
continue
|
|
2900
2962
|
}
|
|
2901
2963
|
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2964
|
+
if (typeof pinoLevel === 'string') {
|
|
2965
|
+
pinoLevel =
|
|
2966
|
+
logger.levels.values[pinoLevel] ?? logger.levels.values[pinoLevel.toLowerCase()] ?? logger.levels.values[level]
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
this.#loggerDestination.lastLevel = pinoLevel
|
|
2970
|
+
this.#loggerDestination.lastTime = message[this.#pinoTimeKey]
|
|
2971
|
+
this.#loggerDestination.lastMsg = message[this.#pinoMessageKey]
|
|
2905
2972
|
this.#loggerDestination.lastObj = message
|
|
2906
2973
|
this.#loggerDestination.lastLogger = logger
|
|
2907
2974
|
this.#loggerDestination.write(raw + '\n')
|
package/lib/schema.js
CHANGED
|
@@ -10,6 +10,25 @@ const runtimeLogger = {
|
|
|
10
10
|
captureStdio: {
|
|
11
11
|
type: 'boolean',
|
|
12
12
|
default: true
|
|
13
|
+
},
|
|
14
|
+
pino: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
default: {},
|
|
17
|
+
properties: {
|
|
18
|
+
level: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
default: 'level'
|
|
21
|
+
},
|
|
22
|
+
time: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
default: 'time'
|
|
25
|
+
},
|
|
26
|
+
message: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
default: 'msg'
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
additionalProperties: false
|
|
13
32
|
}
|
|
14
33
|
}
|
|
15
34
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.60.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"@fastify/compress": "^8.0.0",
|
|
19
19
|
"@fastify/express": "^4.0.0",
|
|
20
20
|
"@fastify/formbody": "^8.0.0",
|
|
21
|
+
"@opentelemetry/sdk-metrics": "^2.8.0",
|
|
21
22
|
"@sentry/node": "^10.58.0",
|
|
22
23
|
"atomic-sleep": "^1.0.0",
|
|
23
24
|
"autocannon": "^8.0.0",
|
|
@@ -38,14 +39,14 @@
|
|
|
38
39
|
"typescript": "^5.5.4",
|
|
39
40
|
"undici-oidc-interceptor": "^0.9.0",
|
|
40
41
|
"why-is-node-running": "^2.2.2",
|
|
41
|
-
"@platformatic/composer": "3.
|
|
42
|
-
"@platformatic/
|
|
43
|
-
"@platformatic/
|
|
44
|
-
"@platformatic/
|
|
45
|
-
"@platformatic/
|
|
46
|
-
"@platformatic/
|
|
47
|
-
"@platformatic/
|
|
48
|
-
"@platformatic/wattpm-pprof-capture": "3.
|
|
42
|
+
"@platformatic/composer": "3.60.0",
|
|
43
|
+
"@platformatic/db": "3.60.0",
|
|
44
|
+
"@platformatic/gateway": "3.60.0",
|
|
45
|
+
"@platformatic/node": "3.60.0",
|
|
46
|
+
"@platformatic/service": "3.60.0",
|
|
47
|
+
"@platformatic/sql-graphql": "3.60.0",
|
|
48
|
+
"@platformatic/sql-mapper": "3.60.0",
|
|
49
|
+
"@platformatic/wattpm-pprof-capture": "3.60.0"
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
52
|
"@fastify/accepts": "^5.0.0",
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
"@fastify/error": "^4.0.0",
|
|
54
55
|
"@fastify/websocket": "^11.0.0",
|
|
55
56
|
"@opentelemetry/api": "^1.9.0",
|
|
57
|
+
"@opentelemetry/exporter-metrics-otlp-proto": "^0.219.0",
|
|
56
58
|
"@platformatic/prom-client": "^1.0.0",
|
|
57
59
|
"@platformatic/undici-cache-memory": "^0.9.0",
|
|
58
60
|
"@watchable/unpromise": "^1.0.2",
|
|
@@ -75,13 +77,13 @@
|
|
|
75
77
|
"undici": "^7.27.2",
|
|
76
78
|
"undici-thread-interceptor": "^1.3.1",
|
|
77
79
|
"ws": "^8.16.0",
|
|
78
|
-
"@platformatic/basic": "3.
|
|
79
|
-
"@platformatic/foundation": "3.
|
|
80
|
-
"@platformatic/
|
|
81
|
-
"@platformatic/
|
|
82
|
-
"@platformatic/
|
|
83
|
-
"@platformatic/
|
|
84
|
-
"@platformatic/
|
|
80
|
+
"@platformatic/basic": "3.60.0",
|
|
81
|
+
"@platformatic/foundation": "3.60.0",
|
|
82
|
+
"@platformatic/generators": "3.60.0",
|
|
83
|
+
"@platformatic/globals": "3.60.0",
|
|
84
|
+
"@platformatic/itc": "3.60.0",
|
|
85
|
+
"@platformatic/telemetry": "3.60.0",
|
|
86
|
+
"@platformatic/metrics": "3.60.0"
|
|
85
87
|
},
|
|
86
88
|
"engines": {
|
|
87
89
|
"node": ">=22.19.0"
|
package/schema.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.
|
|
2
|
+
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.60.0.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"title": "Platformatic Runtime Config",
|
|
5
5
|
"type": "object",
|
|
@@ -58,6 +58,23 @@
|
|
|
58
58
|
"id": {
|
|
59
59
|
"type": "string"
|
|
60
60
|
},
|
|
61
|
+
"enabled": {
|
|
62
|
+
"anyOf": [
|
|
63
|
+
{
|
|
64
|
+
"type": "boolean"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"type": "string"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"type": "object",
|
|
71
|
+
"additionalProperties": {
|
|
72
|
+
"type": "boolean"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"default": true
|
|
77
|
+
},
|
|
61
78
|
"config": {
|
|
62
79
|
"type": "string"
|
|
63
80
|
},
|
|
@@ -427,6 +444,23 @@
|
|
|
427
444
|
"id": {
|
|
428
445
|
"type": "string"
|
|
429
446
|
},
|
|
447
|
+
"enabled": {
|
|
448
|
+
"anyOf": [
|
|
449
|
+
{
|
|
450
|
+
"type": "boolean"
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
"type": "string"
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
"type": "object",
|
|
457
|
+
"additionalProperties": {
|
|
458
|
+
"type": "boolean"
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
],
|
|
462
|
+
"default": true
|
|
463
|
+
},
|
|
430
464
|
"path": {
|
|
431
465
|
"type": "string",
|
|
432
466
|
"allowEmptyPaths": true,
|
|
@@ -806,6 +840,23 @@
|
|
|
806
840
|
"id": {
|
|
807
841
|
"type": "string"
|
|
808
842
|
},
|
|
843
|
+
"enabled": {
|
|
844
|
+
"anyOf": [
|
|
845
|
+
{
|
|
846
|
+
"type": "boolean"
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
"type": "string"
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
"type": "object",
|
|
853
|
+
"additionalProperties": {
|
|
854
|
+
"type": "boolean"
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
],
|
|
858
|
+
"default": true
|
|
859
|
+
},
|
|
809
860
|
"path": {
|
|
810
861
|
"type": "string",
|
|
811
862
|
"allowEmptyPaths": true,
|
|
@@ -1185,6 +1236,23 @@
|
|
|
1185
1236
|
"id": {
|
|
1186
1237
|
"type": "string"
|
|
1187
1238
|
},
|
|
1239
|
+
"enabled": {
|
|
1240
|
+
"anyOf": [
|
|
1241
|
+
{
|
|
1242
|
+
"type": "boolean"
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
"type": "string"
|
|
1246
|
+
},
|
|
1247
|
+
{
|
|
1248
|
+
"type": "object",
|
|
1249
|
+
"additionalProperties": {
|
|
1250
|
+
"type": "boolean"
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
],
|
|
1254
|
+
"default": true
|
|
1255
|
+
},
|
|
1188
1256
|
"path": {
|
|
1189
1257
|
"type": "string",
|
|
1190
1258
|
"allowEmptyPaths": true,
|
|
@@ -1781,6 +1849,25 @@
|
|
|
1781
1849
|
"captureStdio": {
|
|
1782
1850
|
"type": "boolean",
|
|
1783
1851
|
"default": true
|
|
1852
|
+
},
|
|
1853
|
+
"pino": {
|
|
1854
|
+
"type": "object",
|
|
1855
|
+
"default": {},
|
|
1856
|
+
"properties": {
|
|
1857
|
+
"level": {
|
|
1858
|
+
"type": "string",
|
|
1859
|
+
"default": "level"
|
|
1860
|
+
},
|
|
1861
|
+
"time": {
|
|
1862
|
+
"type": "string",
|
|
1863
|
+
"default": "time"
|
|
1864
|
+
},
|
|
1865
|
+
"message": {
|
|
1866
|
+
"type": "string",
|
|
1867
|
+
"default": "msg"
|
|
1868
|
+
}
|
|
1869
|
+
},
|
|
1870
|
+
"additionalProperties": false
|
|
1784
1871
|
}
|
|
1785
1872
|
},
|
|
1786
1873
|
"default": {},
|
|
@@ -2124,6 +2211,115 @@
|
|
|
2124
2211
|
},
|
|
2125
2212
|
{
|
|
2126
2213
|
"type": "string"
|
|
2214
|
+
},
|
|
2215
|
+
{
|
|
2216
|
+
"type": "object",
|
|
2217
|
+
"properties": {
|
|
2218
|
+
"enabled": {
|
|
2219
|
+
"anyOf": [
|
|
2220
|
+
{
|
|
2221
|
+
"type": "boolean"
|
|
2222
|
+
},
|
|
2223
|
+
{
|
|
2224
|
+
"type": "string"
|
|
2225
|
+
}
|
|
2226
|
+
]
|
|
2227
|
+
},
|
|
2228
|
+
"hostname": {
|
|
2229
|
+
"type": "string"
|
|
2230
|
+
},
|
|
2231
|
+
"port": {
|
|
2232
|
+
"anyOf": [
|
|
2233
|
+
{
|
|
2234
|
+
"type": "integer"
|
|
2235
|
+
},
|
|
2236
|
+
{
|
|
2237
|
+
"type": "string"
|
|
2238
|
+
}
|
|
2239
|
+
]
|
|
2240
|
+
},
|
|
2241
|
+
"readiness": {
|
|
2242
|
+
"anyOf": [
|
|
2243
|
+
{
|
|
2244
|
+
"type": "boolean"
|
|
2245
|
+
},
|
|
2246
|
+
{
|
|
2247
|
+
"type": "object",
|
|
2248
|
+
"properties": {
|
|
2249
|
+
"endpoint": {
|
|
2250
|
+
"type": "string"
|
|
2251
|
+
},
|
|
2252
|
+
"success": {
|
|
2253
|
+
"type": "object",
|
|
2254
|
+
"properties": {
|
|
2255
|
+
"statusCode": {
|
|
2256
|
+
"type": "number"
|
|
2257
|
+
},
|
|
2258
|
+
"body": {
|
|
2259
|
+
"type": "string"
|
|
2260
|
+
}
|
|
2261
|
+
},
|
|
2262
|
+
"additionalProperties": false
|
|
2263
|
+
},
|
|
2264
|
+
"fail": {
|
|
2265
|
+
"type": "object",
|
|
2266
|
+
"properties": {
|
|
2267
|
+
"statusCode": {
|
|
2268
|
+
"type": "number"
|
|
2269
|
+
},
|
|
2270
|
+
"body": {
|
|
2271
|
+
"type": "string"
|
|
2272
|
+
}
|
|
2273
|
+
},
|
|
2274
|
+
"additionalProperties": false
|
|
2275
|
+
}
|
|
2276
|
+
},
|
|
2277
|
+
"additionalProperties": false
|
|
2278
|
+
}
|
|
2279
|
+
]
|
|
2280
|
+
},
|
|
2281
|
+
"liveness": {
|
|
2282
|
+
"anyOf": [
|
|
2283
|
+
{
|
|
2284
|
+
"type": "boolean"
|
|
2285
|
+
},
|
|
2286
|
+
{
|
|
2287
|
+
"type": "object",
|
|
2288
|
+
"properties": {
|
|
2289
|
+
"endpoint": {
|
|
2290
|
+
"type": "string"
|
|
2291
|
+
},
|
|
2292
|
+
"success": {
|
|
2293
|
+
"type": "object",
|
|
2294
|
+
"properties": {
|
|
2295
|
+
"statusCode": {
|
|
2296
|
+
"type": "number"
|
|
2297
|
+
},
|
|
2298
|
+
"body": {
|
|
2299
|
+
"type": "string"
|
|
2300
|
+
}
|
|
2301
|
+
},
|
|
2302
|
+
"additionalProperties": false
|
|
2303
|
+
},
|
|
2304
|
+
"fail": {
|
|
2305
|
+
"type": "object",
|
|
2306
|
+
"properties": {
|
|
2307
|
+
"statusCode": {
|
|
2308
|
+
"type": "number"
|
|
2309
|
+
},
|
|
2310
|
+
"body": {
|
|
2311
|
+
"type": "string"
|
|
2312
|
+
}
|
|
2313
|
+
},
|
|
2314
|
+
"additionalProperties": false
|
|
2315
|
+
}
|
|
2316
|
+
},
|
|
2317
|
+
"additionalProperties": false
|
|
2318
|
+
}
|
|
2319
|
+
]
|
|
2320
|
+
}
|
|
2321
|
+
},
|
|
2322
|
+
"additionalProperties": false
|
|
2127
2323
|
}
|
|
2128
2324
|
],
|
|
2129
2325
|
"default": true
|
|
@@ -2600,7 +2796,9 @@
|
|
|
2600
2796
|
"type": "string"
|
|
2601
2797
|
}
|
|
2602
2798
|
],
|
|
2603
|
-
"default": 5000
|
|
2799
|
+
"default": 5000,
|
|
2800
|
+
"deprecated": true,
|
|
2801
|
+
"description": "Deprecated. Health probe timeout configuration is no longer used."
|
|
2604
2802
|
},
|
|
2605
2803
|
"plugins": {
|
|
2606
2804
|
"type": "array",
|
|
@@ -2676,6 +2874,50 @@
|
|
|
2676
2874
|
],
|
|
2677
2875
|
"additionalProperties": false
|
|
2678
2876
|
},
|
|
2877
|
+
"opentelemetry": {
|
|
2878
|
+
"type": "object",
|
|
2879
|
+
"description": "Configuration for forwarding user OpenTelemetry metrics to an OTLP endpoint",
|
|
2880
|
+
"properties": {
|
|
2881
|
+
"enabled": {
|
|
2882
|
+
"anyOf": [
|
|
2883
|
+
{
|
|
2884
|
+
"type": "boolean"
|
|
2885
|
+
},
|
|
2886
|
+
{
|
|
2887
|
+
"type": "string"
|
|
2888
|
+
}
|
|
2889
|
+
],
|
|
2890
|
+
"description": "Enable or disable OpenTelemetry metrics forwarding"
|
|
2891
|
+
},
|
|
2892
|
+
"endpoint": {
|
|
2893
|
+
"type": "string",
|
|
2894
|
+
"description": "OTLP metrics endpoint URL (e.g., http://collector:4318/v1/metrics)"
|
|
2895
|
+
},
|
|
2896
|
+
"interval": {
|
|
2897
|
+
"anyOf": [
|
|
2898
|
+
{
|
|
2899
|
+
"type": "integer"
|
|
2900
|
+
},
|
|
2901
|
+
{
|
|
2902
|
+
"type": "string"
|
|
2903
|
+
}
|
|
2904
|
+
],
|
|
2905
|
+
"default": 60000,
|
|
2906
|
+
"description": "Interval in milliseconds between metric forwards"
|
|
2907
|
+
},
|
|
2908
|
+
"headers": {
|
|
2909
|
+
"type": "object",
|
|
2910
|
+
"additionalProperties": {
|
|
2911
|
+
"type": "string"
|
|
2912
|
+
},
|
|
2913
|
+
"description": "Additional HTTP headers for authentication"
|
|
2914
|
+
}
|
|
2915
|
+
},
|
|
2916
|
+
"required": [
|
|
2917
|
+
"endpoint"
|
|
2918
|
+
],
|
|
2919
|
+
"additionalProperties": false
|
|
2920
|
+
},
|
|
2679
2921
|
"httpCustomLabels": {
|
|
2680
2922
|
"type": "array",
|
|
2681
2923
|
"description": "Custom labels to add to HTTP metrics (http_request_all_duration_seconds). Each label extracts its value from an HTTP request header.",
|