@platformatic/runtime 2.62.0 → 2.63.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
@@ -5,7 +5,7 @@
5
5
  * and run json-schema-to-typescript to regenerate this file.
6
6
  */
7
7
 
8
- export type HttpsSchemasPlatformaticDevPlatformaticRuntime2620Json = {
8
+ export type HttpsSchemasPlatformaticDevPlatformaticRuntime2630Json = {
9
9
  [k: string]: unknown;
10
10
  } & {
11
11
  $schema?: string;
@@ -77,6 +77,14 @@ export type HttpsSchemasPlatformaticDevPlatformaticRuntime2620Json = {
77
77
  [k: string]: unknown;
78
78
  };
79
79
  };
80
+ formatters?: {
81
+ path: string;
82
+ };
83
+ timestamp?: "epochTime" | "unixTime" | "nullTime" | "isoTime";
84
+ redact?: {
85
+ paths: string[];
86
+ censor?: string;
87
+ };
80
88
  [k: string]: unknown;
81
89
  };
82
90
  server?: {
package/lib/errors.js CHANGED
@@ -21,6 +21,7 @@ module.exports = {
21
21
  FailedToRetrieveMetricsError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_METRICS`, 'Failed to retrieve metrics for service with id "%s": %s'),
22
22
  FailedToRetrieveHealthError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_HEALTH`, 'Failed to retrieve health for service with id "%s": %s'),
23
23
  FailedToPerformCustomHealthCheckError: createError(`${ERROR_PREFIX}_FAILED_TO_PERFORM_CUSTOM_HEALTH_CHECK`, 'Failed to perform custom healthcheck for service with id "%s": %s'),
24
+ FailedToPerformCustomReadinessCheckError: createError(`${ERROR_PREFIX}_FAILED_TO_PERFORM_CUSTOM_READINESS_CHECK`, 'Failed to perform custom readiness check for service with id "%s": %s'),
24
25
  ApplicationAlreadyStartedError: createError(`${ERROR_PREFIX}_APPLICATION_ALREADY_STARTED`, 'Application is already started'),
25
26
  ApplicationNotStartedError: createError(`${ERROR_PREFIX}_APPLICATION_NOT_STARTED`, 'Application has not been started'),
26
27
  ConfigPathMustBeStringError: createError(`${ERROR_PREFIX}_CONFIG_PATH_MUST_BE_STRING`, 'Config path must be a string'),
package/lib/logger.js CHANGED
@@ -3,9 +3,9 @@
3
3
  const { once } = require('node:events')
4
4
  const { join } = require('node:path')
5
5
  const { isatty } = require('node:tty')
6
-
7
6
  const pino = require('pino')
8
7
  const pretty = require('pino-pretty')
8
+ const { buildPinoFormatters, buildPinoTimestamp } = require('@platformatic/utils')
9
9
 
10
10
  const customPrettifiers = {
11
11
  name (name, _, obj) {
@@ -18,6 +18,7 @@ const customPrettifiers = {
18
18
  }
19
19
  }
20
20
 
21
+ // Create the runtime logger
21
22
  async function createLogger (config, runtimeLogsDir) {
22
23
  const loggerConfig = { ...config.logger }
23
24
 
@@ -28,6 +29,13 @@ async function createLogger (config, runtimeLogsDir) {
28
29
  ? pretty({ customPrettifiers })
29
30
  : pino.destination(1)
30
31
 
32
+ if (loggerConfig.formatters) {
33
+ loggerConfig.formatters = buildPinoFormatters(loggerConfig.formatters)
34
+ }
35
+ if (loggerConfig.timestamp) {
36
+ loggerConfig.timestamp = buildPinoTimestamp(loggerConfig.timestamp)
37
+ }
38
+
31
39
  if (!config.managementApi) {
32
40
  return [pino(loggerConfig, cliStream), cliStream]
33
41
  }
@@ -19,22 +19,52 @@ const DEFAULT_LIVENESS_FAIL_BODY = 'ERR'
19
19
  async function checkReadiness (runtime) {
20
20
  const workers = await runtime.getWorkers()
21
21
 
22
+ // check if all workers are started
22
23
  for (const worker of Object.values(workers)) {
23
24
  if (worker.status !== 'started') {
24
- return false
25
+ return { status: false }
25
26
  }
26
27
  }
27
- return true
28
+
29
+ // perform custom readiness checks, get custom response content if any
30
+ const checks = await runtime.getCustomReadinessChecks()
31
+
32
+ let response
33
+ const status = Object.values(checks).every(check => {
34
+ if (typeof check === 'boolean') {
35
+ return check
36
+ } else if (typeof check === 'object') {
37
+ response = check
38
+ return check.status
39
+ }
40
+ return false
41
+ })
42
+
43
+ return { response, status }
28
44
  }
29
45
 
30
46
  async function checkLiveness (runtime) {
31
- if (!(await checkReadiness(runtime))) {
32
- return false
47
+ const { status: ready, response: readinessResponse } = await checkReadiness(runtime)
48
+ if (!ready) {
49
+ return { status: false, readiness: readinessResponse }
33
50
  }
51
+ // TODO test, doc
52
+ // in case of readiness check failure, if custom readiness response is set, we return the readiness check response on health check endpoint
34
53
 
35
54
  const checks = await runtime.getCustomHealthChecks()
36
55
 
37
- return Object.values(checks).every(check => check)
56
+ let response
57
+ const status = Object.values(checks).every(check => {
58
+ if (typeof check === 'boolean') {
59
+ return check
60
+ } else if (typeof check === 'object') {
61
+ response = check
62
+ return check.status
63
+ }
64
+ return false
65
+ })
66
+
67
+ return { response, status }
38
68
  }
39
69
 
40
70
  async function startPrometheusServer (runtime, opts) {
@@ -88,12 +118,27 @@ async function startPrometheusServer (runtime, opts) {
88
118
  handler: async (req, reply) => {
89
119
  reply.type('text/plain')
90
120
 
91
- const ready = await checkReadiness(runtime)
92
-
93
- if (ready) {
94
- reply.status(successStatusCode).send(successBody)
95
- } else {
96
- reply.status(failStatusCode).send(failBody)
121
+ const { status, response } = await checkReadiness(runtime)
122
+
123
+ if (typeof response === 'boolean') {
124
+ if (status) {
125
+ reply.status(successStatusCode).send(successBody)
126
+ } else {
127
+ reply.status(failStatusCode).send(failBody)
128
+ }
129
+ } else if (typeof response === 'object') {
130
+ const { status, body, statusCode } = response
131
+ if (status) {
132
+ reply.status(statusCode || successStatusCode).send(body || successBody)
133
+ } else {
134
+ reply.status(statusCode || failStatusCode).send(body || failBody)
135
+ }
136
+ } else if (!response) {
137
+ if (status) {
138
+ reply.status(successStatusCode).send(successBody)
139
+ } else {
140
+ reply.status(failStatusCode).send(failBody)
141
+ }
97
142
  }
98
143
  },
99
144
  })
@@ -112,12 +157,27 @@ async function startPrometheusServer (runtime, opts) {
112
157
  handler: async (req, reply) => {
113
158
  reply.type('text/plain')
114
159
 
115
- const live = await checkLiveness(runtime)
116
-
117
- if (live) {
118
- reply.status(successStatusCode).send(successBody)
119
- } else {
120
- reply.status(failStatusCode).send(failBody)
160
+ const { status, response, readiness } = await checkLiveness(runtime)
161
+
162
+ if (typeof response === 'boolean') {
163
+ if (status) {
164
+ reply.status(successStatusCode).send(successBody)
165
+ } else {
166
+ reply.status(failStatusCode).send(readiness?.body || failBody)
167
+ }
168
+ } else if (typeof response === 'object') {
169
+ const { status, body, statusCode } = response
170
+ if (status) {
171
+ reply.status(statusCode || successStatusCode).send(body || successBody)
172
+ } else {
173
+ reply.status(statusCode || failStatusCode).send(body || readiness?.body || failBody)
174
+ }
175
+ } else if (!response) {
176
+ if (status) {
177
+ reply.status(successStatusCode).send(successBody)
178
+ } else {
179
+ reply.status(failStatusCode).send(readiness?.body || failBody)
180
+ }
121
181
  }
122
182
  },
123
183
  })
package/lib/runtime.js CHANGED
@@ -672,6 +672,21 @@ class Runtime extends EventEmitter {
672
672
  return status
673
673
  }
674
674
 
675
+ async getCustomReadinessChecks () {
676
+ const status = {}
677
+
678
+ for (const [service, { count }] of Object.entries(this.#workers.configuration)) {
679
+ for (let i = 0; i < count; i++) {
680
+ const label = `${service}:${i}`
681
+ const worker = this.#workers.get(label)
682
+
683
+ status[label] = await sendViaITC(worker, 'getCustomReadinessCheck')
684
+ }
685
+ }
686
+
687
+ return status
688
+ }
689
+
675
690
  async getServiceDetails (id, allowUnloaded = false) {
676
691
  let service
677
692
 
@@ -17,6 +17,7 @@ const defaultStackable = {
17
17
  getOpenapiSchema: () => null,
18
18
  getGraphqlSchema: () => null,
19
19
  getCustomHealthCheck: () => null,
20
+ getCustomReadinessCheck: () => null,
20
21
  getMeta: () => ({}),
21
22
  getMetrics: () => null,
22
23
  inject: () => {
package/lib/worker/itc.js CHANGED
@@ -174,6 +174,14 @@ function setupITC (app, service, dispatcher) {
174
174
  } catch (err) {
175
175
  throw new errors.FailedToPerformCustomHealthCheckError(service.id, err.message)
176
176
  }
177
+ },
178
+
179
+ async getCustomReadinessCheck () {
180
+ try {
181
+ return await app.stackable.getCustomReadinessCheck()
182
+ } catch (err) {
183
+ throw new errors.FailedToPerformCustomReadinessCheckError(service.id, err.message)
184
+ }
177
185
  }
178
186
  }
179
187
  })
@@ -16,7 +16,9 @@ const {
16
16
  ensureFlushedWorkerStdio,
17
17
  executeWithTimeout,
18
18
  ensureLoggableError,
19
- getPrivateSymbol
19
+ getPrivateSymbol,
20
+ buildPinoFormatters,
21
+ buildPinoTimestamp
20
22
  } = require('@platformatic/utils')
21
23
  const dotenv = require('dotenv')
22
24
  const pino = require('pino')
@@ -69,12 +71,23 @@ function patchLogging () {
69
71
  }
70
72
 
71
73
  function createLogger () {
72
- const pinoOptions = { level: 'trace', name: workerData.serviceConfig.id }
74
+ const pinoOptions = {
75
+ level: 'trace',
76
+ name: workerData.serviceConfig.id,
77
+ ...workerData.config.logger
78
+ }
73
79
 
74
80
  if (workerData.worker?.count > 1) {
75
81
  pinoOptions.base = { pid: process.pid, hostname: hostname(), worker: workerData.worker.index }
76
82
  }
77
83
 
84
+ if (pinoOptions.formatters) {
85
+ pinoOptions.formatters = buildPinoFormatters(pinoOptions.formatters)
86
+ }
87
+ if (pinoOptions.timestamp) {
88
+ pinoOptions.timestamp = buildPinoTimestamp(pinoOptions.timestamp)
89
+ }
90
+
78
91
  return pino(pinoOptions)
79
92
  }
80
93
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "2.62.0",
3
+ "version": "2.63.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -21,7 +21,7 @@
21
21
  "@fastify/express": "^4.0.0",
22
22
  "@fastify/formbody": "^8.0.0",
23
23
  "autocannon": "^8.0.0",
24
- "borp": "^0.19.0",
24
+ "borp": "^0.20.0",
25
25
  "c8": "^10.0.0",
26
26
  "eslint": "9",
27
27
  "execa": "^9.0.0",
@@ -37,12 +37,12 @@
37
37
  "typescript": "^5.5.4",
38
38
  "undici-oidc-interceptor": "^0.5.0",
39
39
  "why-is-node-running": "^2.2.2",
40
- "@platformatic/db": "2.62.0",
41
- "@platformatic/node": "2.62.0",
42
- "@platformatic/sql-mapper": "2.62.0",
43
- "@platformatic/sql-graphql": "2.62.0",
44
- "@platformatic/composer": "2.62.0",
45
- "@platformatic/service": "2.62.0"
40
+ "@platformatic/composer": "2.63.0",
41
+ "@platformatic/db": "2.63.0",
42
+ "@platformatic/node": "2.63.0",
43
+ "@platformatic/service": "2.63.0",
44
+ "@platformatic/sql-graphql": "2.63.0",
45
+ "@platformatic/sql-mapper": "2.63.0"
46
46
  },
47
47
  "dependencies": {
48
48
  "@fastify/accepts": "^5.0.0",
@@ -76,13 +76,13 @@
76
76
  "undici": "^7.0.0",
77
77
  "undici-thread-interceptor": "^0.13.1",
78
78
  "ws": "^8.16.0",
79
- "@platformatic/basic": "2.62.0",
80
- "@platformatic/config": "2.62.0",
81
- "@platformatic/generators": "2.62.0",
82
- "@platformatic/itc": "2.62.0",
83
- "@platformatic/telemetry": "2.62.0",
84
- "@platformatic/ts-compiler": "2.62.0",
85
- "@platformatic/utils": "2.62.0"
79
+ "@platformatic/config": "2.63.0",
80
+ "@platformatic/basic": "2.63.0",
81
+ "@platformatic/generators": "2.63.0",
82
+ "@platformatic/itc": "2.63.0",
83
+ "@platformatic/telemetry": "2.63.0",
84
+ "@platformatic/ts-compiler": "2.63.0",
85
+ "@platformatic/utils": "2.63.0"
86
86
  },
87
87
  "scripts": {
88
88
  "test": "pnpm run lint && borp --concurrency=1 --timeout=300000 && tsd",
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.62.0.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.63.0.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "type": "object",
5
5
  "properties": {
@@ -733,6 +733,46 @@
733
733
  }
734
734
  },
735
735
  "additionalProperties": false
736
+ },
737
+ "formatters": {
738
+ "type": "object",
739
+ "properties": {
740
+ "path": {
741
+ "type": "string",
742
+ "resolvePath": true
743
+ }
744
+ },
745
+ "required": [
746
+ "path"
747
+ ],
748
+ "additionalProperties": false
749
+ },
750
+ "timestamp": {
751
+ "enum": [
752
+ "epochTime",
753
+ "unixTime",
754
+ "nullTime",
755
+ "isoTime"
756
+ ]
757
+ },
758
+ "redact": {
759
+ "type": "object",
760
+ "properties": {
761
+ "paths": {
762
+ "type": "array",
763
+ "items": {
764
+ "type": "string"
765
+ }
766
+ },
767
+ "censor": {
768
+ "type": "string",
769
+ "default": "[redacted]"
770
+ }
771
+ },
772
+ "required": [
773
+ "paths"
774
+ ],
775
+ "additionalProperties": false
736
776
  }
737
777
  },
738
778
  "required": [