@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.
@@ -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', { id })
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', { id })
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 config', { id })
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', { id })
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', { id })
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', { id })
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', { id })
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('proxy request', { id, requestUrl })
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', { id })
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', { id })
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', { id })
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', { logLevel: 'debug' }, async (req, reply) => {
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
- await managementApi.listen({ path: socketPath })
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 === 5) {
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 so that both can use it.
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 e
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.65.0",
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.65.0",
43
- "@platformatic/gateway": "3.65.0",
44
- "@platformatic/node": "3.65.0",
45
- "@platformatic/service": "3.65.0",
46
- "@platformatic/db": "3.65.0",
47
- "@platformatic/sql-graphql": "3.65.0",
48
- "@platformatic/sql-mapper": "3.65.0",
49
- "@platformatic/wattpm-pprof-capture": "3.65.0"
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.65.0",
81
- "@platformatic/generators": "3.65.0",
82
- "@platformatic/globals": "3.65.0",
83
- "@platformatic/itc": "3.65.0",
84
- "@platformatic/telemetry": "3.65.0",
85
- "@platformatic/metrics": "3.65.0",
86
- "@platformatic/foundation": "3.65.0"
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.65.0.json",
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",