@nxtedition/lib 28.0.0 → 28.0.2
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/app.d.ts +2 -0
- package/app.js +42 -10
- package/errors.js +11 -14
- package/logger.js +21 -14
- package/memory.js +43 -0
- package/package.json +3 -2
- package/serializers.js +7 -3
package/app.d.ts
CHANGED
package/app.js
CHANGED
|
@@ -41,6 +41,7 @@ import { isTimeBetween } from './time.js'
|
|
|
41
41
|
import makeUnderPressure from './under-pressure.js'
|
|
42
42
|
import { nice } from '@nxtedition/sched'
|
|
43
43
|
import { setAffinity } from './numa.js'
|
|
44
|
+
import { getContainerMemoryLimit, getContainerMemoryUsage } from './memory.js'
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
47
|
* @param {object} appConfig
|
|
@@ -670,6 +671,8 @@ export function makeApp(appConfig, onTerminateOrMeta, metaOrNull) {
|
|
|
670
671
|
heapUsed: mem.heapUsed,
|
|
671
672
|
external: mem.external,
|
|
672
673
|
arrayBuffers: mem.arrayBuffers,
|
|
674
|
+
containerLimit: getContainerMemoryLimit(),
|
|
675
|
+
containerUsage: getContainerMemoryUsage(),
|
|
673
676
|
totalHeapTotal: 0,
|
|
674
677
|
totalHeapUsed: 0,
|
|
675
678
|
totalExternal: 0,
|
|
@@ -776,16 +779,45 @@ export function makeApp(appConfig, onTerminateOrMeta, metaOrNull) {
|
|
|
776
779
|
rx.repeatWhen((complete$) => complete$.pipe(rx.delay(10e3))),
|
|
777
780
|
),
|
|
778
781
|
stats$.pipe(
|
|
779
|
-
rx.map(({ undici
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
782
|
+
rx.map(({ memory, heap, utilization, undici }) => {
|
|
783
|
+
const messages = []
|
|
784
|
+
|
|
785
|
+
if (memory?.containerLimit) {
|
|
786
|
+
const usagePercent = (memory.containerUsage / memory.containerLimit) * 100
|
|
787
|
+
messages.push({
|
|
788
|
+
id: 'app:container_memory_usage',
|
|
789
|
+
level: usagePercent > 90 ? 50 : usagePercent > 70 ? 40 : 30,
|
|
790
|
+
msg: `Memory Usage: ${usagePercent.toFixed(2)}%`,
|
|
791
|
+
})
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (heap) {
|
|
795
|
+
const usagePercent = (heap.used_heap_size / heap.heap_size_limit) * 100
|
|
796
|
+
messages.push({
|
|
797
|
+
id: 'app:heap_memory_usage',
|
|
798
|
+
level: usagePercent > 90 ? 50 : usagePercent > 70 ? 40 : 30,
|
|
799
|
+
msg: `Heap Usage: ${usagePercent.toFixed(2)}%`,
|
|
800
|
+
})
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (utilization) {
|
|
804
|
+
const elp = utilization.utilization * 100
|
|
805
|
+
messages.push({
|
|
806
|
+
id: 'app:event_loop_utilization',
|
|
807
|
+
level: elp > 95 ? 50 : elp > 80 ? 40 : 30,
|
|
808
|
+
msg: `Event Loop Utilization: ${elp.toFixed(2)}%`,
|
|
809
|
+
})
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (undici) {
|
|
813
|
+
messages.push({
|
|
814
|
+
id: 'app:undici_upstream_sockets',
|
|
815
|
+
level: undici.sockets > 8192 ? 50 : undici.sockets > 4096 ? 40 : 30,
|
|
816
|
+
msg: `Undici: ${undici.sockets} upstream connected`,
|
|
817
|
+
})
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
return messages
|
|
789
821
|
}),
|
|
790
822
|
),
|
|
791
823
|
toobusy?.appLag$.pipe(
|
package/errors.js
CHANGED
|
@@ -102,17 +102,14 @@ function _serializeError(error, { depth }) {
|
|
|
102
102
|
let {
|
|
103
103
|
msg,
|
|
104
104
|
message = msg,
|
|
105
|
-
errors,
|
|
106
105
|
code,
|
|
107
106
|
exitCode = Number(code),
|
|
108
107
|
signal,
|
|
109
108
|
signalCode = Number(signal),
|
|
110
|
-
cause,
|
|
111
109
|
body,
|
|
112
110
|
status,
|
|
113
111
|
statusCode = status,
|
|
114
112
|
headers,
|
|
115
|
-
aggregateErrors,
|
|
116
113
|
...properties
|
|
117
114
|
} = error
|
|
118
115
|
|
|
@@ -144,12 +141,12 @@ function _serializeError(error, { depth }) {
|
|
|
144
141
|
signalCode = SIGNALS[signalCode] ?? signalCode
|
|
145
142
|
}
|
|
146
143
|
|
|
147
|
-
errors = [errors, aggregateErrors]
|
|
148
|
-
.flat()
|
|
144
|
+
const errors = [error.errors, error.aggregateErrors]
|
|
145
|
+
.flat(Infinity)
|
|
149
146
|
.filter(Boolean)
|
|
150
147
|
.flatMap((x) => _serializeError(x, { depth: depth + 1 }))
|
|
151
148
|
.filter(Boolean)
|
|
152
|
-
cause = cause ? _serializeError(cause, { depth: depth + 1 }) : undefined
|
|
149
|
+
const cause = error.cause ? _serializeError(error.cause, { depth: depth + 1 }) : undefined
|
|
153
150
|
|
|
154
151
|
delete error[kSeen]
|
|
155
152
|
|
|
@@ -160,14 +157,14 @@ function _serializeError(error, { depth }) {
|
|
|
160
157
|
...properties,
|
|
161
158
|
message: message ?? 'Unknown error',
|
|
162
159
|
type: type !== 'Object' ? type : undefined,
|
|
163
|
-
code: code ? `${code}
|
|
164
|
-
exitCode: exitCode
|
|
165
|
-
signalCode: signalCode
|
|
166
|
-
statusCode: statusCode
|
|
167
|
-
headers: headers
|
|
168
|
-
data: data
|
|
169
|
-
cause: cause
|
|
170
|
-
errors: fp.isEmpty(errors) ? undefined :
|
|
160
|
+
code: fp.isEmpty(code) ? undefined : `${code}`,
|
|
161
|
+
exitCode: fp.isEmpty(exitCode) ? undefined : exitCode,
|
|
162
|
+
signalCode: fp.isEmpty(signalCode) ? undefined : signalCode,
|
|
163
|
+
statusCode: fp.isEmpty(statusCode) ? undefined : statusCode,
|
|
164
|
+
headers: fp.isEmpty(headers) ? undefined : headers,
|
|
165
|
+
data: fp.isEmpty(data) ? undefined : data,
|
|
166
|
+
cause: fp.isEmpty(cause) ? undefined : cause,
|
|
167
|
+
errors: fp.isEmpty(errors) ? undefined : errors,
|
|
171
168
|
},
|
|
172
169
|
(k, v) => (typeof v === 'bigint' ? v.toString() : v),
|
|
173
170
|
),
|
package/logger.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
1
|
import { isMainThread } from 'node:worker_threads'
|
|
3
2
|
import serializers from './serializers.js'
|
|
4
3
|
import pino from 'pino'
|
|
5
4
|
|
|
6
5
|
const isProduction = process.env.NODE_ENV === 'production'
|
|
7
6
|
|
|
8
|
-
export function createLogger(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
export function createLogger({
|
|
8
|
+
level = isProduction ? 'debug' : 'trace',
|
|
9
|
+
flushInterval = 1e3,
|
|
10
|
+
stream,
|
|
11
|
+
...options
|
|
12
|
+
} = {}) {
|
|
14
13
|
if (!stream) {
|
|
15
14
|
if (
|
|
16
15
|
process.stdout.write !== process.stdout.constructor.prototype.write ||
|
|
@@ -23,15 +22,24 @@ export function createLogger(
|
|
|
23
22
|
if (stream) {
|
|
24
23
|
// Do nothing...
|
|
25
24
|
} else if (!isProduction) {
|
|
26
|
-
stream = pino.destination({ fd: process.stdout.fd ?? 1, sync: true })
|
|
25
|
+
stream = pino.destination({ fd: process.stdout.fd ?? 1, sync: true, fsync: false })
|
|
27
26
|
} else if (!isMainThread) {
|
|
28
27
|
// TODO (perf): Async mode doesn't work super well in workers.
|
|
29
|
-
stream = pino.destination({ fd: 1, sync: true })
|
|
28
|
+
stream = pino.destination({ fd: 1, sync: true, fsync: false })
|
|
30
29
|
} else {
|
|
31
|
-
stream = pino.destination({
|
|
30
|
+
stream = pino.destination({
|
|
31
|
+
sync: false,
|
|
32
|
+
fsync: false,
|
|
33
|
+
minLength: 4 * 1024,
|
|
34
|
+
maxWrite: 32 * 1024,
|
|
35
|
+
})
|
|
32
36
|
|
|
33
37
|
let flushing = 0
|
|
34
|
-
|
|
38
|
+
const onFlush = () => {
|
|
39
|
+
flushing--
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const flushTimeout = setInterval(() => {
|
|
35
43
|
if (flushing >= 10) {
|
|
36
44
|
try {
|
|
37
45
|
logger.warn('logger is flushing too slow')
|
|
@@ -41,10 +49,9 @@ export function createLogger(
|
|
|
41
49
|
}
|
|
42
50
|
} else {
|
|
43
51
|
flushing++
|
|
44
|
-
stream.flush(
|
|
45
|
-
flushing--
|
|
46
|
-
})
|
|
52
|
+
stream.flush(onFlush)
|
|
47
53
|
}
|
|
54
|
+
flushTimeout.refresh()
|
|
48
55
|
}, flushInterval).unref()
|
|
49
56
|
}
|
|
50
57
|
|
package/memory.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
|
|
3
|
+
function readFile(path) {
|
|
4
|
+
try {
|
|
5
|
+
return readFileSync(path, 'utf8').trim()
|
|
6
|
+
} catch {
|
|
7
|
+
return null
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getContainerMemoryLimit() {
|
|
12
|
+
// cgroups v2
|
|
13
|
+
const v2Limit = readFile('/sys/fs/cgroup/memory.max')
|
|
14
|
+
if (v2Limit) {
|
|
15
|
+
return v2Limit === 'max' ? null : Number(v2Limit)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// cgroups v1
|
|
19
|
+
const v1Limit = readFile('/sys/fs/cgroup/memory/memory.limit_in_bytes')
|
|
20
|
+
if (v1Limit) {
|
|
21
|
+
const limit = Number(v1Limit)
|
|
22
|
+
// Very large number usually means "no limit"
|
|
23
|
+
return limit > 1e15 ? null : limit
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getContainerMemoryUsage() {
|
|
30
|
+
// cgroups v2
|
|
31
|
+
const v2Usage = readFile('/sys/fs/cgroup/memory.current')
|
|
32
|
+
if (v2Usage) {
|
|
33
|
+
return Number(v2Usage)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// cgroups v1
|
|
37
|
+
const v1Usage = readFile('/sys/fs/cgroup/memory/memory.usage_in_bytes')
|
|
38
|
+
if (v1Usage) {
|
|
39
|
+
return Number(v1Usage)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return undefined
|
|
43
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/lib",
|
|
3
|
-
"version": "28.0.
|
|
3
|
+
"version": "28.0.2",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"author": "Robert Nagy <robert.nagy@boffins.se>",
|
|
6
6
|
"type": "module",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"shared.js",
|
|
25
25
|
"logger.js",
|
|
26
26
|
"logger.d.ts",
|
|
27
|
+
"memory.js",
|
|
27
28
|
"mime.js",
|
|
28
29
|
"numa.js",
|
|
29
30
|
"proxy.js",
|
|
@@ -90,5 +91,5 @@
|
|
|
90
91
|
"pino": ">=7.0.0",
|
|
91
92
|
"rxjs": "^7.0.0"
|
|
92
93
|
},
|
|
93
|
-
"gitHead": "
|
|
94
|
+
"gitHead": "10cdafd06260ca5abfabbf08f503ccd78819c3e2"
|
|
94
95
|
}
|
package/serializers.js
CHANGED
|
@@ -118,6 +118,7 @@ export default {
|
|
|
118
118
|
req: (req) =>
|
|
119
119
|
req && {
|
|
120
120
|
id: req.id || getHeader(req, 'request-id'),
|
|
121
|
+
httpVersion: req.httpVersion,
|
|
121
122
|
method: req.method,
|
|
122
123
|
target: getTarget(req),
|
|
123
124
|
url: getUrl(req),
|
|
@@ -138,6 +139,7 @@ export default {
|
|
|
138
139
|
ureq: (ureq) =>
|
|
139
140
|
ureq && {
|
|
140
141
|
id: ureq.id || getHeader(ureq, 'request-id'),
|
|
142
|
+
httpVersion: ureq.httpVersion,
|
|
141
143
|
method: ureq.method,
|
|
142
144
|
target: getTarget(ureq),
|
|
143
145
|
url: getUrl(ureq),
|
|
@@ -154,6 +156,7 @@ export default {
|
|
|
154
156
|
res: (res) =>
|
|
155
157
|
res && {
|
|
156
158
|
id: res.id || getHeader(res, 'request-id') || getHeader(res.req, 'request-id'),
|
|
159
|
+
httpVersion: res.httpVersion,
|
|
157
160
|
headers: getHeaders(res),
|
|
158
161
|
statusCode: res.statusCode || res.status,
|
|
159
162
|
timing: res.timing,
|
|
@@ -171,6 +174,7 @@ export default {
|
|
|
171
174
|
ures: (ures) =>
|
|
172
175
|
ures && {
|
|
173
176
|
id: ures.id || getHeader(ures, 'request-id') || getHeader(ures.req, 'request-id'),
|
|
177
|
+
httpVersion: ures.httpVersion,
|
|
174
178
|
headers: getHeaders(ures),
|
|
175
179
|
statusCode: ures.statusCode ?? ures.status,
|
|
176
180
|
timing: ures.timing,
|
|
@@ -191,7 +195,7 @@ export default {
|
|
|
191
195
|
// Based on: https://github.com/pinojs/pino-std-serializers
|
|
192
196
|
|
|
193
197
|
const seen = Symbol('circular-ref-tag')
|
|
194
|
-
const rawSymbol = Symbol('pino-raw-err-ref')
|
|
198
|
+
export const rawSymbol = Symbol('pino-raw-err-ref')
|
|
195
199
|
|
|
196
200
|
const pinoErrProto = Object.create(
|
|
197
201
|
{},
|
|
@@ -211,7 +215,7 @@ const pinoErrProto = Object.create(
|
|
|
211
215
|
writable: true,
|
|
212
216
|
value: undefined,
|
|
213
217
|
},
|
|
214
|
-
|
|
218
|
+
errors: {
|
|
215
219
|
enumerable: true,
|
|
216
220
|
writable: true,
|
|
217
221
|
value: undefined,
|
|
@@ -262,7 +266,7 @@ function errSerializer(err) {
|
|
|
262
266
|
_err.stack = err.stack
|
|
263
267
|
|
|
264
268
|
if (errors != null) {
|
|
265
|
-
_err.
|
|
269
|
+
_err.errors = errors
|
|
266
270
|
}
|
|
267
271
|
|
|
268
272
|
if (isErrorLike(err.cause) && !Object.prototype.hasOwnProperty.call(err.cause, seen)) {
|