@platformatic/runtime 3.65.0 → 3.66.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/management-api.js +97 -28
- package/lib/runtime.js +7 -6
- package/package.json +18 -17
- package/schema.json +1 -1
package/lib/management-api.js
CHANGED
|
@@ -18,6 +18,53 @@ import { prepareApplication } from './config.js'
|
|
|
18
18
|
|
|
19
19
|
const PLATFORMATIC_TMP_DIR = join(tmpdir(), 'platformatic', 'runtimes')
|
|
20
20
|
|
|
21
|
+
const loggerLevels = ['fatal', 'error', 'warn', 'info', 'debug', 'trace']
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
Endpoints which are frequently polled by clients (the CLI, the dashboard or any other
|
|
25
|
+
monitoring tool) do not log the request/response pair to avoid flooding the runtime logs.
|
|
26
|
+
*/
|
|
27
|
+
const quiet = { logLevel: 'warn' }
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
The management API is closed after the runtime has been closed (see the "closed" event handler
|
|
31
|
+
in startManagementApi), so that the POST /stop endpoint can reply. By then the runtime logger
|
|
32
|
+
has been destroyed and writing to its destination throws.
|
|
33
|
+
|
|
34
|
+
To avoid that, the logger is never captured: it is resolved - and its children are recreated -
|
|
35
|
+
on each call, so that logging becomes a no-op as soon as the runtime replaces its logger with
|
|
36
|
+
the abstract one.
|
|
37
|
+
*/
|
|
38
|
+
function createRuntimeLoggerProxy (getParent, bindings, options) {
|
|
39
|
+
let parent
|
|
40
|
+
let logger
|
|
41
|
+
|
|
42
|
+
function resolve () {
|
|
43
|
+
const current = getParent()
|
|
44
|
+
|
|
45
|
+
if (current !== parent) {
|
|
46
|
+
parent = current
|
|
47
|
+
logger = bindings ? parent.child(bindings, options) : parent
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return logger
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const proxy = {
|
|
54
|
+
child (childBindings, childOptions) {
|
|
55
|
+
return createRuntimeLoggerProxy(resolve, childBindings, childOptions)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const level of loggerLevels) {
|
|
60
|
+
proxy[level] = function (...args) {
|
|
61
|
+
return resolve()[level](...args)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return proxy
|
|
66
|
+
}
|
|
67
|
+
|
|
21
68
|
export async function managementApiPlugin (app, opts) {
|
|
22
69
|
app.register(fastifyAccepts)
|
|
23
70
|
|
|
@@ -43,16 +90,16 @@ export async function managementApiPlugin (app, opts) {
|
|
|
43
90
|
return removed
|
|
44
91
|
}
|
|
45
92
|
|
|
46
|
-
app.get('/status', async () => {
|
|
93
|
+
app.get('/status', quiet, async () => {
|
|
47
94
|
const status = runtime.getRuntimeStatus()
|
|
48
95
|
return { status }
|
|
49
96
|
})
|
|
50
97
|
|
|
51
|
-
app.get('/metadata', async () => {
|
|
98
|
+
app.get('/metadata', quiet, async () => {
|
|
52
99
|
return runtime.getRuntimeMetadata()
|
|
53
100
|
})
|
|
54
101
|
|
|
55
|
-
app.get('/config', async request => {
|
|
102
|
+
app.get('/config', quiet, async request => {
|
|
56
103
|
const metadata = request.query.metadata === 'true'
|
|
57
104
|
const rawConfig = await runtime.getRuntimeConfig(metadata)
|
|
58
105
|
|
|
@@ -64,7 +111,7 @@ export async function managementApiPlugin (app, opts) {
|
|
|
64
111
|
return rawConfig
|
|
65
112
|
})
|
|
66
113
|
|
|
67
|
-
app.get('/env', async () => {
|
|
114
|
+
app.get('/env', quiet, async () => {
|
|
68
115
|
return { ...process.env, ...runtime.getRuntimeEnv() }
|
|
69
116
|
})
|
|
70
117
|
|
|
@@ -96,7 +143,7 @@ export async function managementApiPlugin (app, opts) {
|
|
|
96
143
|
return runtime.runSchedulerJob(request.params.name)
|
|
97
144
|
})
|
|
98
145
|
|
|
99
|
-
app.get('/applications', async () => {
|
|
146
|
+
app.get('/applications', quiet, async () => {
|
|
100
147
|
return runtime.getApplications()
|
|
101
148
|
})
|
|
102
149
|
|
|
@@ -144,9 +191,9 @@ export async function managementApiPlugin (app, opts) {
|
|
|
144
191
|
return deleteApplications(request.body, reply)
|
|
145
192
|
})
|
|
146
193
|
|
|
147
|
-
app.get('/applications/:id', async request => {
|
|
194
|
+
app.get('/applications/:id', quiet, async request => {
|
|
148
195
|
const { id } = request.params
|
|
149
|
-
app.log.debug('get application details'
|
|
196
|
+
app.log.debug({ id }, 'get application details')
|
|
150
197
|
return runtime.getApplicationDetails(id)
|
|
151
198
|
})
|
|
152
199
|
|
|
@@ -154,45 +201,45 @@ export async function managementApiPlugin (app, opts) {
|
|
|
154
201
|
return deleteApplications([request.params.id], reply)
|
|
155
202
|
})
|
|
156
203
|
|
|
157
|
-
app.get('/applications/:id/config', async request => {
|
|
204
|
+
app.get('/applications/:id/config', quiet, async request => {
|
|
158
205
|
const { id } = request.params
|
|
159
|
-
app.log.debug('get application config'
|
|
206
|
+
app.log.debug({ id }, 'get application config')
|
|
160
207
|
return runtime.getApplicationConfig(id)
|
|
161
208
|
})
|
|
162
209
|
|
|
163
|
-
app.get('/applications/:id/env', async request => {
|
|
210
|
+
app.get('/applications/:id/env', quiet, async request => {
|
|
164
211
|
const { id } = request.params
|
|
165
|
-
app.log.debug('get application
|
|
212
|
+
app.log.debug({ id }, 'get application env')
|
|
166
213
|
return runtime.getApplicationEnv(id)
|
|
167
214
|
})
|
|
168
215
|
|
|
169
|
-
app.get('/applications/:id/openapi-schema', async request => {
|
|
216
|
+
app.get('/applications/:id/openapi-schema', quiet, async request => {
|
|
170
217
|
const { id } = request.params
|
|
171
|
-
app.log.debug('get openapi-schema'
|
|
218
|
+
app.log.debug({ id }, 'get openapi-schema')
|
|
172
219
|
return runtime.getApplicationOpenapiSchema(id)
|
|
173
220
|
})
|
|
174
221
|
|
|
175
|
-
app.get('/applications/:id/graphql-schema', async request => {
|
|
222
|
+
app.get('/applications/:id/graphql-schema', quiet, async request => {
|
|
176
223
|
const { id } = request.params
|
|
177
|
-
app.log.debug('get graphql-schema'
|
|
224
|
+
app.log.debug({ id }, 'get graphql-schema')
|
|
178
225
|
return runtime.getApplicationGraphqlSchema(id)
|
|
179
226
|
})
|
|
180
227
|
|
|
181
228
|
app.post('/applications/:id/start', async request => {
|
|
182
229
|
const { id } = request.params
|
|
183
|
-
app.log.debug('start application'
|
|
230
|
+
app.log.debug({ id }, 'start application')
|
|
184
231
|
await runtime.startApplication(id)
|
|
185
232
|
})
|
|
186
233
|
|
|
187
234
|
app.post('/applications/:id/stop', async request => {
|
|
188
235
|
const { id } = request.params
|
|
189
|
-
app.log.debug('stop application'
|
|
236
|
+
app.log.debug({ id }, 'stop application')
|
|
190
237
|
await runtime.stopApplication(id)
|
|
191
238
|
})
|
|
192
239
|
|
|
193
|
-
app.all('/applications/:id/proxy/*', async (request, reply) => {
|
|
240
|
+
app.all('/applications/:id/proxy/*', quiet, async (request, reply) => {
|
|
194
241
|
const { id, '*': requestUrl } = request.params
|
|
195
|
-
app.log.debug(
|
|
242
|
+
app.log.debug({ id, requestUrl }, 'proxy request')
|
|
196
243
|
|
|
197
244
|
delete request.headers.connection
|
|
198
245
|
delete request.headers['content-length']
|
|
@@ -217,7 +264,7 @@ export async function managementApiPlugin (app, opts) {
|
|
|
217
264
|
|
|
218
265
|
app.post('/applications/:id/pprof/start', async (request, reply) => {
|
|
219
266
|
const { id } = request.params
|
|
220
|
-
app.log.debug('start profiling'
|
|
267
|
+
app.log.debug({ id }, 'start profiling')
|
|
221
268
|
|
|
222
269
|
const options = request.body || {}
|
|
223
270
|
const result = await runtime.startApplicationProfiling(id, options)
|
|
@@ -226,7 +273,7 @@ export async function managementApiPlugin (app, opts) {
|
|
|
226
273
|
|
|
227
274
|
app.post('/applications/:id/pprof/stop', async (request, reply) => {
|
|
228
275
|
const { id } = request.params
|
|
229
|
-
app.log.debug('stop profiling'
|
|
276
|
+
app.log.debug({ id }, 'stop profiling')
|
|
230
277
|
|
|
231
278
|
const options = request.body || {}
|
|
232
279
|
const profileData = await runtime.stopApplicationProfiling(id, options)
|
|
@@ -248,14 +295,14 @@ export async function managementApiPlugin (app, opts) {
|
|
|
248
295
|
|
|
249
296
|
app.post('/applications/:id/heap-snapshot', async (request, reply) => {
|
|
250
297
|
const { id } = request.params
|
|
251
|
-
app.log.debug('take heap snapshot'
|
|
298
|
+
app.log.debug({ id }, 'take heap snapshot')
|
|
252
299
|
|
|
253
300
|
const stream = await runtime.takeApplicationHeapSnapshot(id)
|
|
254
301
|
reply.type('application/octet-stream').send(stream)
|
|
255
302
|
return reply
|
|
256
303
|
})
|
|
257
304
|
|
|
258
|
-
app.get('/metrics',
|
|
305
|
+
app.get('/metrics', quiet, async (req, reply) => {
|
|
259
306
|
const config = await runtime.getRuntimeConfig()
|
|
260
307
|
|
|
261
308
|
if (config.metrics?.enabled === false) {
|
|
@@ -280,7 +327,7 @@ export async function managementApiPlugin (app, opts) {
|
|
|
280
327
|
})
|
|
281
328
|
|
|
282
329
|
// TODO: Remove in next major version - deprecated endpoint
|
|
283
|
-
app.get('/metrics/live', { websocket: true }, async socket => {
|
|
330
|
+
app.get('/metrics/live', { ...quiet, websocket: true }, async socket => {
|
|
284
331
|
const config = await runtime.getRuntimeConfig()
|
|
285
332
|
|
|
286
333
|
if (config.metrics?.enabled === false) {
|
|
@@ -326,7 +373,7 @@ export async function managementApiPlugin (app, opts) {
|
|
|
326
373
|
socket.on('close', cleanup)
|
|
327
374
|
})
|
|
328
375
|
|
|
329
|
-
app.get('/logs/live', { websocket: true }, async socket => {
|
|
376
|
+
app.get('/logs/live', { ...quiet, websocket: true }, async socket => {
|
|
330
377
|
runtime.addLoggerDestination(createWebSocketStream(socket))
|
|
331
378
|
})
|
|
332
379
|
|
|
@@ -394,7 +441,10 @@ export async function startManagementApi (runtime, config) {
|
|
|
394
441
|
socketPath = join(runtimePIDDir, 'socket')
|
|
395
442
|
}
|
|
396
443
|
|
|
397
|
-
const managementApi = fastify(
|
|
444
|
+
const managementApi = fastify({
|
|
445
|
+
loggerInstance: createRuntimeLoggerProxy(() => runtime.logger, { name: 'management-api' })
|
|
446
|
+
})
|
|
447
|
+
|
|
398
448
|
managementApi.register(fastifyWebsocket)
|
|
399
449
|
managementApi.register(managementApiPlugin, { runtime, prefix: '/api/v1' })
|
|
400
450
|
|
|
@@ -416,10 +466,29 @@ export async function startManagementApi (runtime, config) {
|
|
|
416
466
|
*/
|
|
417
467
|
for (let i = 0; i < 5; i++) {
|
|
418
468
|
try {
|
|
419
|
-
|
|
469
|
+
// Fastify always logs listen() at info. The management API binds a private unix
|
|
470
|
+
// socket/pipe, so that message is noise and also breaks helpers that treat the first
|
|
471
|
+
// "listening at" log as the application URL. Downgrade it to debug for this call.
|
|
472
|
+
const { log } = managementApi
|
|
473
|
+
const originalInfo = log.info
|
|
474
|
+
log.info = function (...args) {
|
|
475
|
+
const text = typeof args[0] === 'string' ? args[0] : args[1]
|
|
476
|
+
if (typeof text === 'string' && text.startsWith('Server listening at')) {
|
|
477
|
+
return log.debug(...args)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return originalInfo.apply(this, args)
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
try {
|
|
484
|
+
await managementApi.listen({ path: socketPath })
|
|
485
|
+
} finally {
|
|
486
|
+
log.info = originalInfo
|
|
487
|
+
}
|
|
488
|
+
|
|
420
489
|
break
|
|
421
490
|
} catch (e) {
|
|
422
|
-
if (i ===
|
|
491
|
+
if (i === 4) {
|
|
423
492
|
throw e
|
|
424
493
|
}
|
|
425
494
|
|
package/lib/runtime.js
CHANGED
|
@@ -320,10 +320,6 @@ export class Runtime extends EventEmitter {
|
|
|
320
320
|
|
|
321
321
|
const config = this.#config
|
|
322
322
|
|
|
323
|
-
if (config.managementApi) {
|
|
324
|
-
this.#managementApi = await startManagementApi(this, config.managementApi)
|
|
325
|
-
}
|
|
326
|
-
|
|
327
323
|
if (config.metrics) {
|
|
328
324
|
// Use the configured application label name for metrics (defaults to 'applicationId')
|
|
329
325
|
this.#metricsLabelName = config.metrics.applicationLabel || 'applicationId'
|
|
@@ -340,12 +336,17 @@ export class Runtime extends EventEmitter {
|
|
|
340
336
|
collectProcessMetrics(this.#processMetricsRegistry)
|
|
341
337
|
}
|
|
342
338
|
|
|
343
|
-
// Create the logger before extensions and health/metrics servers
|
|
339
|
+
// Create the logger before the management API, the extensions and the health/metrics servers
|
|
340
|
+
// so that all of them can use it.
|
|
344
341
|
const [logger, destination, context] = await createLogger(config)
|
|
345
342
|
this.logger = logger
|
|
346
343
|
this.#loggerDestination = destination
|
|
347
344
|
this.#loggerContext = context
|
|
348
345
|
|
|
346
|
+
if (config.managementApi) {
|
|
347
|
+
this.#managementApi = await startManagementApi(this, config.managementApi)
|
|
348
|
+
}
|
|
349
|
+
|
|
349
350
|
await this.#startOpenTelemetryMetricsForwarder(config.metrics?.opentelemetry)
|
|
350
351
|
|
|
351
352
|
this.#createWorkersBroadcastChannel()
|
|
@@ -2824,7 +2825,7 @@ export class Runtime extends EventEmitter {
|
|
|
2824
2825
|
if (attempt === MAX_BOOTSTRAP_ATTEMPTS) {
|
|
2825
2826
|
const error = new RuntimeAbortedError({ cause: e })
|
|
2826
2827
|
error.message = `Unable to initialize the ${errorLabel}.`
|
|
2827
|
-
throw
|
|
2828
|
+
throw error
|
|
2828
2829
|
}
|
|
2829
2830
|
|
|
2830
2831
|
if (e.code !== 'PLT_RUNTIME_APPLICATION_WORKER_EXIT') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.66.0",
|
|
4
4
|
"description": "Platformatic runtime",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
"typescript": "^5.9.3",
|
|
40
40
|
"undici-oidc-interceptor": "^0.9.0",
|
|
41
41
|
"why-is-node-running": "^2.2.2",
|
|
42
|
-
"@platformatic/composer": "3.
|
|
43
|
-
"@platformatic/gateway": "3.
|
|
44
|
-
"@platformatic/
|
|
45
|
-
"@platformatic/
|
|
46
|
-
"@platformatic/db": "3.
|
|
47
|
-
"@platformatic/sql-
|
|
48
|
-
"@platformatic/
|
|
49
|
-
"@platformatic/
|
|
42
|
+
"@platformatic/composer": "3.66.0",
|
|
43
|
+
"@platformatic/gateway": "3.66.0",
|
|
44
|
+
"@platformatic/service": "3.66.0",
|
|
45
|
+
"@platformatic/sql-graphql": "3.66.0",
|
|
46
|
+
"@platformatic/db": "3.66.0",
|
|
47
|
+
"@platformatic/sql-mapper": "3.66.0",
|
|
48
|
+
"@platformatic/wattpm-pprof-capture": "3.66.0",
|
|
49
|
+
"@platformatic/node": "3.66.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@fastify/accepts": "^5.0.0",
|
|
@@ -77,13 +77,13 @@
|
|
|
77
77
|
"undici": "^7.27.2",
|
|
78
78
|
"undici-thread-interceptor": "^1.3.1",
|
|
79
79
|
"ws": "^8.16.0",
|
|
80
|
-
"@platformatic/basic": "3.
|
|
81
|
-
"@platformatic/
|
|
82
|
-
"@platformatic/
|
|
83
|
-
"@platformatic/
|
|
84
|
-
"@platformatic/
|
|
85
|
-
"@platformatic/
|
|
86
|
-
"@platformatic/
|
|
80
|
+
"@platformatic/basic": "3.66.0",
|
|
81
|
+
"@platformatic/foundation": "3.66.0",
|
|
82
|
+
"@platformatic/itc": "3.66.0",
|
|
83
|
+
"@platformatic/metrics": "3.66.0",
|
|
84
|
+
"@platformatic/globals": "3.66.0",
|
|
85
|
+
"@platformatic/generators": "3.66.0",
|
|
86
|
+
"@platformatic/telemetry": "3.66.0"
|
|
87
87
|
},
|
|
88
88
|
"engines": {
|
|
89
89
|
"node": ">=22.19.0"
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"gen-schema": "node lib/schema.js > schema.json",
|
|
100
100
|
"gen-types": "json2ts > config.d.ts < schema.json",
|
|
101
101
|
"build": "npm run gen-schema && npm run gen-types",
|
|
102
|
-
"lint": "eslint"
|
|
102
|
+
"lint": "eslint",
|
|
103
|
+
"lint:fix": "eslint --fix"
|
|
103
104
|
}
|
|
104
105
|
}
|
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.66.0.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"title": "Platformatic Runtime Config",
|
|
5
5
|
"type": "object",
|