@platformatic/runtime 3.13.0 → 3.14.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 CHANGED
@@ -20,7 +20,15 @@ export type PlatformaticRuntimeConfig = {
20
20
  id: string;
21
21
  config?: string;
22
22
  useHttp?: boolean;
23
- workers?: number | string;
23
+ workers?:
24
+ | number
25
+ | string
26
+ | {
27
+ static?: number;
28
+ minimum?: number;
29
+ maximum?: number;
30
+ [k: string]: unknown;
31
+ };
24
32
  health?: {
25
33
  enabled?: boolean | string;
26
34
  interval?: number | string;
@@ -76,7 +84,20 @@ export type PlatformaticRuntimeConfig = {
76
84
  web?: {
77
85
  [k: string]: unknown;
78
86
  }[];
79
- workers?: number | string;
87
+ workers?:
88
+ | number
89
+ | string
90
+ | {
91
+ static?: number;
92
+ dynamic?: boolean;
93
+ minimum?: number;
94
+ maximum?: number;
95
+ total?: number;
96
+ maxMemory?: number;
97
+ cooldown?: number;
98
+ gracePeriod?: number;
99
+ [k: string]: unknown;
100
+ };
80
101
  workersRestartDelay?: number | string;
81
102
  logger?: {
82
103
  level: (
@@ -285,6 +306,37 @@ export type PlatformaticRuntimeConfig = {
285
306
  };
286
307
  plugins?: string[];
287
308
  timeout?: number | string;
309
+ /**
310
+ * Configuration for exporting metrics to an OTLP endpoint
311
+ */
312
+ otlpExporter?: {
313
+ /**
314
+ * Enable or disable OTLP metrics export
315
+ */
316
+ enabled?: boolean | string;
317
+ /**
318
+ * OTLP endpoint URL (e.g., http://collector:4318/v1/metrics)
319
+ */
320
+ endpoint: string;
321
+ /**
322
+ * Interval in milliseconds between metric pushes
323
+ */
324
+ interval?: number | string;
325
+ /**
326
+ * Additional HTTP headers for authentication
327
+ */
328
+ headers?: {
329
+ [k: string]: string;
330
+ };
331
+ /**
332
+ * Service name for OTLP resource attributes
333
+ */
334
+ serviceName?: string;
335
+ /**
336
+ * Service version for OTLP resource attributes
337
+ */
338
+ serviceVersion?: string;
339
+ };
288
340
  };
289
341
  telemetry?: {
290
342
  enabled?: boolean | string;
@@ -368,13 +420,28 @@ export type PlatformaticRuntimeConfig = {
368
420
  maxTotalMemory?: number;
369
421
  minWorkers?: number;
370
422
  maxWorkers?: number;
423
+ cooldownSec?: number;
424
+ gracePeriod?: number;
425
+ /**
426
+ * @deprecated
427
+ */
371
428
  scaleUpELU?: number;
429
+ /**
430
+ * @deprecated
431
+ */
372
432
  scaleDownELU?: number;
433
+ /**
434
+ * @deprecated
435
+ */
373
436
  timeWindowSec?: number;
437
+ /**
438
+ * @deprecated
439
+ */
374
440
  scaleDownTimeWindowSec?: number;
375
- cooldownSec?: number;
441
+ /**
442
+ * @deprecated
443
+ */
376
444
  scaleIntervalSec?: number;
377
- gracePeriod?: number;
378
445
  applications?: {
379
446
  [k: string]: {
380
447
  minWorkers?: number;
package/index.d.ts CHANGED
@@ -55,8 +55,11 @@ export module symbols {
55
55
  export declare const kWorkerId: unique symbol
56
56
  export declare const kITC: unique symbol
57
57
  export declare const kHealthCheckTimer: unique symbol
58
- export declare const kLastELU: unique symbol
58
+ export declare const kHealthMetricsTimer: unique symbol
59
+ export declare const kLastHealthCheckELU: unique symbol
60
+ export declare const kLastVerticalScalerELU: unique symbol
59
61
  export declare const kWorkerStatus: unique symbol
62
+ export declare const kWorkerHealthSignals: unique symbol
60
63
  export declare const kStderrMarker: string
61
64
  export declare const kInterceptors: unique symbol
62
65
  export declare const kWorkersBroadcast: unique symbol
@@ -89,6 +92,8 @@ export function create (
89
92
  context?: ConfigurationOptions
90
93
  ): Promise<Runtime>
91
94
 
95
+ export declare function prepareApplication (config: RuntimeConfiguration, application: object): object
96
+
92
97
  export declare function transform (config: RuntimeConfiguration): Promise<RuntimeConfiguration>
93
98
 
94
99
  export declare function loadApplicationsCommands (): Promise<ApplicationsCommands>
package/index.js CHANGED
@@ -168,7 +168,7 @@ export async function create (configOrRoot, sourceOrConfig, context) {
168
168
  return runtime
169
169
  }
170
170
 
171
- export { transform, wrapInRuntimeConfig } from './lib/config.js'
171
+ export { prepareApplication, transform, wrapInRuntimeConfig } from './lib/config.js'
172
172
  export * as errors from './lib/errors.js'
173
173
  export { RuntimeGenerator as Generator, WrappedGenerator } from './lib/generator.js'
174
174
  export { Runtime } from './lib/runtime.js'
package/lib/config.js CHANGED
@@ -45,6 +45,42 @@ function raiseInvalidWorkersError (location, received, hint) {
45
45
  throw new InvalidArgumentError(`${location} workers must be a positive integer; received "${received}"${extra}`)
46
46
  }
47
47
 
48
+ function parseWorkers (config, prefix, defaultWorkers = 1) {
49
+ if (typeof config.workers !== 'undefined') {
50
+ // Number
51
+ if (typeof config.workers !== 'object') {
52
+ const coerced = coercePositiveInteger(config.workers)
53
+
54
+ if (coerced === null) {
55
+ const raw = config.workers
56
+ const hint = typeof raw === 'string' && /\{.*\}/.test(raw) ? 'check your environment variable' : ''
57
+ raiseInvalidWorkersError(prefix, config.workers, hint)
58
+ } else {
59
+ config.workers = { static: coerced, dynamic: false }
60
+ }
61
+ // Object
62
+ } else {
63
+ for (const key of ['minimum', 'maximum', 'static']) {
64
+ if (typeof config.workers[key] === 'undefined') {
65
+ continue
66
+ }
67
+
68
+ const coerced = coercePositiveInteger(config.workers[key])
69
+ if (coerced === null) {
70
+ const raw = config.workers
71
+ const hint = typeof raw === 'string' && /\{.*\}/.test(raw) ? 'check your environment variable' : ''
72
+ raiseInvalidWorkersError(`${prefix} ${key}`, config.workers, hint)
73
+ } else {
74
+ config.workers[key] = coerced
75
+ }
76
+ }
77
+ }
78
+ // No value, inherit from runtime
79
+ } else {
80
+ config.workers = { static: defaultWorkers }
81
+ }
82
+ }
83
+
48
84
  export function pprofCapturePreloadPath () {
49
85
  const require = createRequire(import.meta.url)
50
86
 
@@ -162,6 +198,64 @@ export function parseInspectorOptions (config, inspect, inspectBreak) {
162
198
  config.watch = false
163
199
  }
164
200
 
201
+ export async function prepareApplication (config, application, defaultWorkers) {
202
+ // We need to have absolute paths here, ot the `loadConfig` will fail
203
+ // Make sure we don't resolve if env var was not replaced
204
+ if (application.path && !isAbsolute(application.path) && !application.path.match(/^\{.*\}$/)) {
205
+ application.path = resolvePath(config[kMetadata].root, application.path)
206
+ }
207
+
208
+ if (application.path && application.config) {
209
+ application.config = resolvePath(application.path, application.config)
210
+ }
211
+
212
+ try {
213
+ let pkg
214
+
215
+ if (application.config) {
216
+ const config = await loadConfiguration(application.config)
217
+ pkg = await loadConfigurationModule(application.path, config)
218
+
219
+ application.type = extractModuleFromSchemaUrl(config, true).module
220
+ application.skipTelemetryHooks = pkg.skipTelemetryHooks
221
+ } else {
222
+ const { moduleName, capability } = await importCapabilityAndConfig(application.path)
223
+ pkg = capability
224
+
225
+ application.type = moduleName
226
+ }
227
+
228
+ application.skipTelemetryHooks = pkg.skipTelemetryHooks
229
+
230
+ // This is needed to work around Rust bug on dylibs:
231
+ // https://github.com/rust-lang/rust/issues/91979
232
+ // https://github.com/rollup/rollup/issues/5761
233
+ const _require = createRequire(application.path)
234
+ for (const m of pkg.modulesToLoad ?? []) {
235
+ const toLoad = _require.resolve(m)
236
+ loadModule(_require, toLoad).catch(() => {})
237
+ }
238
+ } catch (err) {
239
+ // This should not happen, it happens on running some unit tests if we prepare the runtime
240
+ // when not all the applications configs are available. Given that we are running this only
241
+ // to ddetermine the type of the application, it's safe to ignore this error and default to unknown
242
+ application.type = 'unknown'
243
+ }
244
+
245
+ // Validate and coerce per-service workers
246
+ parseWorkers(application, `Service "${application.id}"`, defaultWorkers)
247
+
248
+ application.entrypoint = application.id === config.entrypoint
249
+ application.dependencies ??= []
250
+ application.localUrl = `http://${application.id}.plt.local`
251
+
252
+ if (typeof application.watch === 'undefined') {
253
+ application.watch = config.watch
254
+ }
255
+
256
+ return application
257
+ }
258
+
165
259
  export async function transform (config, _, context) {
166
260
  const production = context?.isProduction ?? context?.production
167
261
  const applications = [...(config.applications ?? []), ...(config.services ?? []), ...(config.web ?? [])]
@@ -173,6 +267,27 @@ export async function transform (config, _, context) {
173
267
  config.watch = !production
174
268
  }
175
269
 
270
+ // Migrate the old verticalScaler property, only applied if the new settings are not set, otherwise workers takes precedence
271
+ // TODO: Remove in the next major version
272
+ if (config.verticalScaler) {
273
+ config.workers ??= {}
274
+ config.workers.dynamic ??= config.verticalScaler.enabled
275
+ config.workers.minimum ??= config.verticalScaler.minWorkers
276
+ config.workers.maximum ??= config.verticalScaler.maxWorkers
277
+ config.workers.total ??= config.verticalScaler.maxTotalWorkers
278
+ config.workers.maxMemory ??= config.verticalScaler.maxTotalMemory
279
+
280
+ if (typeof config.workers.cooldown === 'undefined' && typeof config.verticalScaler.cooldownSec === 'number') {
281
+ config.workers.cooldown = config.verticalScaler.cooldownSec * 1000
282
+ }
283
+
284
+ if (typeof config.workers.gracePeriod === 'undefined' && typeof config.verticalScaler.gracePeriod === 'number') {
285
+ config.workers.gracePeriod = config.verticalScaler.gracePeriod
286
+ }
287
+
288
+ config.verticalScaler = undefined
289
+ }
290
+
176
291
  if (config.autoload) {
177
292
  const { exclude = [], mappings = {} } = config.autoload
178
293
  let { path } = config.autoload
@@ -215,80 +330,11 @@ export async function transform (config, _, context) {
215
330
  let hasValidEntrypoint = false
216
331
 
217
332
  // Root-level workers
218
- if (typeof config.workers !== 'undefined') {
219
- const coerced = coercePositiveInteger(config.workers)
220
- if (coerced === null) {
221
- const raw = config.workers
222
- const hint = typeof raw === 'string' && /\{.*\}/.test(raw) ? 'check your environment variable' : ''
223
- raiseInvalidWorkersError('Runtime', config.workers, hint)
224
- }
225
- config.workers = coerced
226
- }
333
+ parseWorkers(config, 'Runtime')
334
+ const defaultWorkers = config.workers.static
227
335
 
228
336
  for (let i = 0; i < applications.length; ++i) {
229
- const application = applications[i]
230
-
231
- // We need to have absolute paths here, ot the `loadConfig` will fail
232
- // Make sure we don't resolve if env var was not replaced
233
- if (application.path && !isAbsolute(application.path) && !application.path.match(/^\{.*\}$/)) {
234
- application.path = resolvePath(config[kMetadata].root, application.path)
235
- }
236
-
237
- if (application.path && application.config) {
238
- application.config = resolvePath(application.path, application.config)
239
- }
240
-
241
- try {
242
- let pkg
243
-
244
- if (application.config) {
245
- const config = await loadConfiguration(application.config)
246
- pkg = await loadConfigurationModule(application.path, config)
247
-
248
- application.type = extractModuleFromSchemaUrl(config, true).module
249
- application.skipTelemetryHooks = pkg.skipTelemetryHooks
250
- } else {
251
- const { moduleName, capability } = await importCapabilityAndConfig(application.path)
252
- pkg = capability
253
-
254
- application.type = moduleName
255
- }
256
-
257
- application.skipTelemetryHooks = pkg.skipTelemetryHooks
258
-
259
- // This is needed to work around Rust bug on dylibs:
260
- // https://github.com/rust-lang/rust/issues/91979
261
- // https://github.com/rollup/rollup/issues/5761
262
- const _require = createRequire(application.path)
263
- for (const m of pkg.modulesToLoad ?? []) {
264
- const toLoad = _require.resolve(m)
265
- loadModule(_require, toLoad).catch(() => {})
266
- }
267
- } catch (err) {
268
- // This should not happen, it happens on running some unit tests if we prepare the runtime
269
- // when not all the applications configs are available. Given that we are running this only
270
- // to ddetermine the type of the application, it's safe to ignore this error and default to unknown
271
- application.type = 'unknown'
272
- }
273
-
274
- // Validate and coerce per-service workers
275
- if (typeof application.workers !== 'undefined') {
276
- const coerced = coercePositiveInteger(application.workers)
277
- if (coerced === null) {
278
- const raw = config.application?.[i]?.workers
279
- const hint = typeof raw === 'string' && /\{.*\}/.test(raw) ? 'check your environment variable' : ''
280
- raiseInvalidWorkersError(`Service "${application.id}"`, application.workers, hint)
281
- }
282
- application.workers = coerced
283
- }
284
-
285
- application.entrypoint = application.id === config.entrypoint
286
- application.dependencies ??= []
287
- application.localUrl = `http://${application.id}.plt.local`
288
-
289
- if (typeof application.watch === 'undefined') {
290
- application.watch = config.watch
291
- }
337
+ const application = await prepareApplication(config, applications[i], defaultWorkers)
292
338
 
293
339
  if (application.entrypoint) {
294
340
  hasValidEntrypoint = true
@@ -0,0 +1,218 @@
1
+ import { features } from '@platformatic/foundation'
2
+ import { availableParallelism } from 'node:os'
3
+ import { getMemoryInfo } from './metrics.js'
4
+ import { ScalingAlgorithm, scaleUpELUThreshold } from './scaling-algorithm.js'
5
+ import { kApplicationId, kId, kLastVerticalScalerELU, kWorkerStartTime, kWorkerStatus } from './worker/symbols.js'
6
+
7
+ const healthCheckInterval = 1000
8
+ export const kOriginalWorkers = Symbol('plt.runtime.application.dynamicWorkersScalerOriginalWorkers')
9
+
10
+ const defaultCooldown = 60_000
11
+ const defaultGracePeriod = 30_000
12
+ const scaleIntervalPeriod = 60_000
13
+
14
+ export class DynamicWorkersScaler {
15
+ #status
16
+ #runtime
17
+ #algorithm
18
+
19
+ #maxTotalMemory
20
+ #maxTotalWorkers
21
+ #maxWorkers
22
+ #minWorkers
23
+ #cooldown
24
+ #gracePeriod
25
+
26
+ #initialUpdates
27
+ #memoryInfo
28
+ #healthCheckTimeout
29
+ #checkScalingInterval
30
+ #isScaling
31
+ #lastScaling
32
+
33
+ constructor (runtime, config) {
34
+ this.#runtime = runtime
35
+
36
+ this.#maxTotalMemory = config.maxMemory // This is defaulted in start()
37
+ this.#maxTotalWorkers = config.total ?? availableParallelism()
38
+ this.#maxWorkers = config.maximum ?? this.#maxTotalWorkers
39
+ this.#minWorkers = config.minimum ?? 1
40
+ this.#cooldown = config.cooldown ?? defaultCooldown
41
+ this.#gracePeriod = config.gracePeriod ?? defaultGracePeriod
42
+
43
+ this.#algorithm = new ScalingAlgorithm({ maxTotalWorkers: this.#maxTotalWorkers })
44
+
45
+ this.#isScaling = false
46
+ this.#lastScaling = 0
47
+ this.#initialUpdates = []
48
+ this.#status = 'init'
49
+ }
50
+
51
+ getConfig () {
52
+ return {
53
+ maxTotalMemory: this.#maxTotalMemory,
54
+ maxTotalWorkers: this.#maxTotalWorkers,
55
+ maxWorkers: this.#maxWorkers,
56
+ minWorkers: this.#minWorkers,
57
+ cooldown: this.#cooldown,
58
+ gracePeriod: this.#gracePeriod
59
+ }
60
+ }
61
+
62
+ async start () {
63
+ this.#memoryInfo = await getMemoryInfo()
64
+ this.#maxTotalMemory ??= this.#memoryInfo.total * 0.9
65
+
66
+ this.#checkScalingInterval = setInterval(this.#checkScaling.bind(this), scaleIntervalPeriod)
67
+ this.#healthCheckTimeout = setTimeout(this.#chechHealth.bind(this), healthCheckInterval)
68
+
69
+ if (this.#initialUpdates.length > 0) {
70
+ await this.#runtime.updateApplicationsResources(this.#initialUpdates)
71
+ this.#initialUpdates = []
72
+ }
73
+
74
+ this.#status = 'started'
75
+ }
76
+
77
+ stop () {
78
+ clearTimeout(this.#healthCheckTimeout)
79
+ clearInterval(this.#checkScalingInterval)
80
+ this.#status = 'stopped'
81
+ }
82
+
83
+ async add (application) {
84
+ const config = {}
85
+
86
+ if (application.entrypoint && !features.node.reusePort) {
87
+ this.#runtime.logger.warn(
88
+ `The "${application.id}" application cannot be scaled because it is an entrypoint and the "reusePort" feature is not available in your OS.`
89
+ )
90
+
91
+ config.minWorkers = 1
92
+ config.maxWorkers = 1
93
+ } else if (application.workers.dynamic === false) {
94
+ this.#runtime.logger.warn(
95
+ `The "${application.id}" application cannot be scaled because it has a fixed number of workers (${application.workers.static}).`
96
+ )
97
+
98
+ config.minWorkers = application.workers.static
99
+ config.maxWorkers = application.workers.static
100
+ } else {
101
+ config.minWorkers = application.workers.minimum
102
+ config.maxWorkers = application.workers.maximum
103
+ }
104
+
105
+ config.minWorkers ??= this.#minWorkers
106
+ config.maxWorkers ??= this.#maxWorkers
107
+
108
+ if (config.minWorkers > 1) {
109
+ const update = { application: application.id, workers: config.minWorkers }
110
+
111
+ if (!this.#status === 'started') {
112
+ await this.#runtime.updateApplicationsResources([update])
113
+ } else {
114
+ this.#initialUpdates.push(update)
115
+ }
116
+ }
117
+
118
+ this.#algorithm.addApplication(application.id, config)
119
+ }
120
+
121
+ remove (application) {
122
+ this.#algorithm.removeApplication(application.id)
123
+ }
124
+
125
+ async #chechHealth () {
126
+ let shouldCheckForScaling = false
127
+
128
+ const now = Date.now()
129
+
130
+ const workers = await this.#runtime.getWorkers(true)
131
+
132
+ for (const { raw: worker } of Object.values(workers)) {
133
+ if (worker[kWorkerStatus] !== 'started' || worker[kWorkerStartTime] + this.#gracePeriod > now) {
134
+ continue
135
+ }
136
+
137
+ try {
138
+ const health = await this.#runtime.getWorkerHealth(worker, { previousELU: worker[kLastVerticalScalerELU] })
139
+
140
+ if (!health) {
141
+ continue
142
+ }
143
+
144
+ worker[kLastVerticalScalerELU] = health.currentELU
145
+
146
+ this.#algorithm.addWorkerHealthInfo({
147
+ workerId: worker[kId],
148
+ applicationId: worker[kApplicationId],
149
+ elu: health.elu,
150
+ heapUsed: health.heapUsed,
151
+ heapTotal: health.heapTotal
152
+ })
153
+
154
+ if (health.elu > scaleUpELUThreshold) {
155
+ shouldCheckForScaling = true
156
+ }
157
+ } catch (err) {
158
+ this.logger.error({ err }, 'Failed to get health for worker')
159
+ }
160
+ }
161
+
162
+ if (shouldCheckForScaling) {
163
+ await this.#checkScaling()
164
+ }
165
+
166
+ this.#healthCheckTimeout.refresh()
167
+ }
168
+
169
+ async #checkScaling () {
170
+ const isInCooldown = Date.now() < this.#lastScaling + this.#cooldown
171
+ if (this.#isScaling || isInCooldown) {
172
+ return
173
+ }
174
+
175
+ this.#isScaling = true
176
+
177
+ try {
178
+ const workersInfo = await this.#runtime.getWorkers()
179
+ const mem = await getMemoryInfo({ scope: this.#memoryInfo.scope })
180
+
181
+ const appsWorkersInfo = {}
182
+ for (const worker of Object.values(workersInfo)) {
183
+ if (worker.status === 'exited') {
184
+ continue
185
+ }
186
+
187
+ const applicationId = worker.application
188
+ appsWorkersInfo[applicationId] ??= 0
189
+ appsWorkersInfo[applicationId]++
190
+ }
191
+
192
+ const availableMemory = this.#maxTotalMemory - mem.used
193
+ const recommendations = this.#algorithm.getRecommendations(appsWorkersInfo, { availableMemory })
194
+
195
+ if (recommendations.length > 0) {
196
+ await this.#applyRecommendations(recommendations)
197
+ this.#lastScaling = Date.now()
198
+ }
199
+ } catch (err) {
200
+ this.#runtime.logger.error({ err }, 'Failed to scale applications')
201
+ } finally {
202
+ this.#isScaling = false
203
+ }
204
+ }
205
+
206
+ async #applyRecommendations (recommendations) {
207
+ const resourcesUpdates = []
208
+
209
+ for (const recommendation of recommendations) {
210
+ const { applicationId, workersCount, direction } = recommendation
211
+ this.#runtime.logger.info(`Scaling ${direction} the "${applicationId}" app to ${workersCount} workers`)
212
+
213
+ resourcesUpdates.push({ application: applicationId, workers: workersCount })
214
+ }
215
+
216
+ return this.#runtime.updateApplicationsResources(resourcesUpdates)
217
+ }
218
+ }
package/lib/errors.js CHANGED
@@ -127,3 +127,24 @@ export const MissingPprofCapture = createError(
127
127
  `${ERROR_PREFIX}_MISSING_PPROF_CAPTURE`,
128
128
  'Please install @platformatic/wattpm-pprof-capture'
129
129
  )
130
+
131
+ export const GetHeapStatisticUnavailable = createError(
132
+ `${ERROR_PREFIX}_GET_HEAP_STATISTIC_UNAVAILABLE`,
133
+ 'The getHeapStatistics method is not available in your Node version'
134
+ )
135
+ export const FailedToSendHealthSignalsError = createError(
136
+ `${ERROR_PREFIX}_FAILED_TO_SEND_HEALTH_SIGNALS`,
137
+ 'Cannot send health signals from application "%s": %s'
138
+ )
139
+ export const HealthSignalMustBeObjectError = createError(
140
+ `${ERROR_PREFIX}_HEALTH_SIGNAL_MUST_BE_OBJECT`,
141
+ 'Health signal must be an object'
142
+ )
143
+ export const HealthSignalTypeMustBeStringError = createError(
144
+ `${ERROR_PREFIX}_HEALTH_SIGNAL_TYPE_MUST_BE_STRING`,
145
+ 'Health signal type must be a string, received "%s"'
146
+ )
147
+ export const CannotRemoveEntrypointError = createError(
148
+ `${ERROR_PREFIX}_CANNOT_REMOVE_ENTRYPOINT`,
149
+ 'Cannot remove the entrypoint application.'
150
+ )
package/lib/logger.js CHANGED
@@ -1,4 +1,4 @@
1
- import { buildPinoFormatters, buildPinoTimestamp } from '@platformatic/foundation'
1
+ import { buildPinoFormatters, buildPinoTimestamp, usePrettyPrint } from '@platformatic/foundation'
2
2
  import { isatty } from 'node:tty'
3
3
  import pino from 'pino'
4
4
  import pretty from 'pino-pretty'
@@ -27,8 +27,10 @@ export async function createLogger (config) {
27
27
 
28
28
  if (config.logger.transport) {
29
29
  cliStream = pino.transport(config.logger.transport)
30
+ } else if ((process.env.FORCE_TTY || isatty(1)) && usePrettyPrint()) {
31
+ cliStream = pretty({ customPrettifiers })
30
32
  } else {
31
- cliStream = isatty(1) ? pretty({ customPrettifiers }) : pino.destination(1)
33
+ cliStream = pino.destination(1)
32
34
  }
33
35
 
34
36
  if (loggerConfig.formatters) {
@@ -21,19 +21,17 @@ const DEFAULT_LIVENESS_FAIL_BODY = 'ERR'
21
21
 
22
22
  async function checkReadiness (runtime) {
23
23
  const workers = await runtime.getWorkers()
24
+ const applications = await runtime.getApplicationsIds()
24
25
 
25
26
  // Make sure there is at least one started worker
26
- const applications = new Set()
27
27
  const started = new Set()
28
28
  for (const worker of Object.values(workers)) {
29
- applications.add(worker.application)
30
-
31
29
  if (worker.status === 'started') {
32
30
  started.add(worker.application)
33
31
  }
34
32
  }
35
33
 
36
- if (started.size !== applications.size) {
34
+ if (started.size !== applications.length) {
37
35
  return { status: false }
38
36
  }
39
37