@pbvision/fastify-firestore-service 0.0.6 → 0.0.7
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/README.md +0 -1
- package/package.json +5 -5
- package/src/make-app.js +35 -18
- package/src/make-logger.js +10 -99
- package/test/environment.js +1 -1
package/README.md
CHANGED
|
@@ -81,7 +81,6 @@ export default async (params) => makeService({
|
|
|
81
81
|
},
|
|
82
82
|
logging: {
|
|
83
83
|
reportErrorDetail: true, // process.env.NODE_ENV === 'localhost',
|
|
84
|
-
useUnitTestLogFormat: true, // process.env.NODE_ENV === 'localhost',
|
|
85
84
|
reportAllErrors: true // process.env.NODE_ENV !== 'prod'
|
|
86
85
|
},
|
|
87
86
|
swagger: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pbvision/fastify-firestore-service",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Web Framework using Fastify and Firestore ORM",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"@fastify/static": "^6.12",
|
|
42
42
|
"@fastify/swagger": "^8.1.0",
|
|
43
43
|
"@fastify/swagger-ui": "^2.0.1",
|
|
44
|
-
"@pbvision/firestore-orm": "^0.0.
|
|
45
|
-
"@pbvision/schema": "^0.
|
|
44
|
+
"@pbvision/firestore-orm": "^0.0.6",
|
|
45
|
+
"@pbvision/schema": "^0.2.15",
|
|
46
46
|
"@sentry/node": "^7.91.0",
|
|
47
47
|
"fastify": "^4.10.0",
|
|
48
48
|
"fastify-plugin": "^4.5.1",
|
|
@@ -55,10 +55,11 @@
|
|
|
55
55
|
"@babel/eslint-parser": "^7.17.0",
|
|
56
56
|
"@babel/plugin-transform-class-properties": "^7.12.1",
|
|
57
57
|
"@babel/preset-env": "^7.17.12",
|
|
58
|
-
"@pbvision/jest-unit-test": "^0.2.
|
|
58
|
+
"@pbvision/jest-unit-test": "^0.2.3",
|
|
59
59
|
"babel-loader": "^9.1.3",
|
|
60
60
|
"eslint": "^8.22.0",
|
|
61
61
|
"eslint-config-standard": "17.1.0",
|
|
62
|
+
"eslint-import-resolver-webpack": "^0.13.8",
|
|
62
63
|
"eslint-plugin-import": "^2.22.0",
|
|
63
64
|
"eslint-plugin-node": "^11.1.0",
|
|
64
65
|
"eslint-plugin-promise": "^6.0.0",
|
|
@@ -67,7 +68,6 @@
|
|
|
67
68
|
"jsdoc": "3.6.11",
|
|
68
69
|
"license-webpack-plugin": "^4.0.2",
|
|
69
70
|
"nodemon": "^3.0.2",
|
|
70
|
-
"pino-pretty": "^10.3.1",
|
|
71
71
|
"standard": "^17.1.0",
|
|
72
72
|
"superagent-defaults": "^0.1.14",
|
|
73
73
|
"supertest": "^6.3.3",
|
package/src/make-app.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Ajv from 'ajv/dist/2020'
|
|
1
2
|
import fastify from 'fastify'
|
|
2
3
|
import { v4 as uuidv4 } from 'uuid'
|
|
3
4
|
|
|
@@ -23,17 +24,19 @@ const COOKIE_CONFIG = {
|
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* @typedef {object} LoggingConfig
|
|
26
|
-
* @property {boolean} [useUnitTestLogFormat=false] Whether output logs to console with
|
|
27
|
-
* pretty printing
|
|
28
27
|
* @property {boolean} [reportAllErrors=false] Whether include all API
|
|
29
28
|
* validation errors in error logging. Recommend to keep it off for production,
|
|
30
29
|
* on for testing.
|
|
31
30
|
* @property {boolean} [reportErrorDetail=false] Whether include all details
|
|
32
31
|
* of an error. Recommend to keep it off for remote testing, on for local
|
|
33
32
|
* testing.
|
|
33
|
+
* @property {String} [sentryDSN] The Sentry DSN to report errors to
|
|
34
|
+
* @property {function} [customizePinoOpts] An optional function customize the
|
|
35
|
+
* arguments to pass to Pino. Takes as input the options that will be sent
|
|
36
|
+
* to be Pino by default (can be modified). Return the new options.
|
|
34
37
|
*/
|
|
35
38
|
const LOGGING_CONFIG = {
|
|
36
|
-
|
|
39
|
+
customizePinoOpts: null,
|
|
37
40
|
reportAllErrors: false,
|
|
38
41
|
reportErrorDetail: false,
|
|
39
42
|
sentryDSN: null
|
|
@@ -67,9 +70,10 @@ const SWAGGER_CONFIG = {
|
|
|
67
70
|
|
|
68
71
|
/**
|
|
69
72
|
* @typedef {object} LatencyTrackerConfig
|
|
70
|
-
* @property {boolean} [disabled=false] Whether to
|
|
71
|
-
*
|
|
72
|
-
* @property {string} [
|
|
73
|
+
* @property {boolean} [disabled=false] Whether to record how long from when we
|
|
74
|
+
* start to process a request to when we start to send the response
|
|
75
|
+
* @property {string} [header='x-latency-ms'] response header to send the
|
|
76
|
+
* latency info back in
|
|
73
77
|
*/
|
|
74
78
|
const LATENCY_TRACKER_CONFIG = {
|
|
75
79
|
disabled: false,
|
|
@@ -142,21 +146,34 @@ export default async function makeService (params = {}) {
|
|
|
142
146
|
} = params
|
|
143
147
|
const fastifyServerId = uuidv4()
|
|
144
148
|
let requestCount = 0
|
|
149
|
+
const ajvParams = {
|
|
150
|
+
removeAdditional: false,
|
|
151
|
+
allErrors: logging.reportAllErrors,
|
|
152
|
+
useDefaults: true,
|
|
153
|
+
strictSchema: false,
|
|
154
|
+
strictRequired: true
|
|
155
|
+
}
|
|
156
|
+
const ajvObjs = {
|
|
157
|
+
coerceTypes: new Ajv({ ...ajvParams, coerceTypes: true }),
|
|
158
|
+
noCoerceTypes: new Ajv({ ...ajvParams, coerceTypes: false })
|
|
159
|
+
}
|
|
145
160
|
const app = fastify({
|
|
146
161
|
ignoreTrailingSlash: true,
|
|
147
|
-
disableRequestLogging:
|
|
148
|
-
logger: makeLogger(logging.
|
|
149
|
-
genReqId: () => `${fastifyServerId}-${++requestCount}
|
|
150
|
-
ajv: {
|
|
151
|
-
customOptions: {
|
|
152
|
-
removeAdditional: false,
|
|
153
|
-
allErrors: logging.reportAllErrors,
|
|
154
|
-
useDefaults: true,
|
|
155
|
-
strictSchema: false,
|
|
156
|
-
strictRequired: true
|
|
157
|
-
}
|
|
158
|
-
}
|
|
162
|
+
disableRequestLogging: false,
|
|
163
|
+
logger: makeLogger(logging.customizePinoOpts),
|
|
164
|
+
genReqId: () => `${fastifyServerId}-${++requestCount}`
|
|
159
165
|
})
|
|
166
|
+
.setValidatorCompiler(({ httpPart, schema }) => {
|
|
167
|
+
// header, route, and query string always come as strings; coerce them
|
|
168
|
+
// to the correct types if possible; body should come exactly in the
|
|
169
|
+
// right type so don't allow type coercion there
|
|
170
|
+
const useCoerce = httpPart !== 'body'
|
|
171
|
+
const ajv = useCoerce ? ajvObjs.coerceTypes : ajvObjs.noCoerceTypes
|
|
172
|
+
const validate = ajv.compile(schema)
|
|
173
|
+
return (value) => !validate(value)
|
|
174
|
+
? ({ value, error: validate.errors })
|
|
175
|
+
: ({ value })
|
|
176
|
+
})
|
|
160
177
|
|
|
161
178
|
const registrar = new RegistrarCls(app, service)
|
|
162
179
|
|
package/src/make-logger.js
CHANGED
|
@@ -1,119 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
import querystring from 'node:querystring'
|
|
3
|
-
|
|
4
|
-
import pino from 'pino'
|
|
5
|
-
import wrap from 'word-wrap'
|
|
6
|
-
|
|
7
|
-
// for unit testing, output to the console INSTEAD of to stdout
|
|
8
|
-
// via pino (so that jest captures the output and groups it with the right test
|
|
9
|
-
// suite)
|
|
10
|
-
function getUnitTestLogFormatOverrides () {
|
|
11
|
-
const prefixText = '/' + process.env.SERVICE + '/src/'
|
|
12
|
-
function prettifier () {
|
|
13
|
-
return (obj) => {
|
|
14
|
-
if (obj.req) {
|
|
15
|
-
const indent = ''
|
|
16
|
-
if (!obj.status) {
|
|
17
|
-
console.log(indent, obj.req.method, obj.req.path)
|
|
18
|
-
} else {
|
|
19
|
-
// output the status code and (if any) error message
|
|
20
|
-
const msgs = wrap(obj.msg || '', { width: 80, indent }).split('\n')
|
|
21
|
-
console.log(indent, ' \u2514 HTTP', obj.status, msgs[0])
|
|
22
|
-
msgs.slice(1).forEach(msg => {
|
|
23
|
-
console.log(indent, msg)
|
|
24
|
-
})
|
|
25
|
-
if (obj.error && obj.status >= 500) {
|
|
26
|
-
console.log(`${indent}Error: ${JSON.stringify(obj.error, null, 2)}`)
|
|
27
|
-
}
|
|
28
|
-
if (obj.stack) {
|
|
29
|
-
for (let i = 0; i < obj.stack.length; i++) {
|
|
30
|
-
console.log(' ', obj.stack[i])
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
} else {
|
|
35
|
-
// 1) Get the filename and line number the log message is from
|
|
36
|
-
// the index of the first non-pino line may change in future versions
|
|
37
|
-
// of fastify or pino; might be better to search each line until we
|
|
38
|
-
// find /<service>/src (if present)
|
|
39
|
-
const firstNonPinoLine = (new Error()).stack.split('\n')[5]
|
|
40
|
-
const appFolderIdx = firstNonPinoLine.indexOf(prefixText)
|
|
41
|
-
let prefix = ''
|
|
42
|
-
if (appFolderIdx !== -1) {
|
|
43
|
-
// prefix log message with filename and line number when logs
|
|
44
|
-
// originated in our app's source code
|
|
45
|
-
prefix = firstNonPinoLine.substring(
|
|
46
|
-
appFolderIdx + 9,
|
|
47
|
-
firstNonPinoLine.lastIndexOf(':')) + ' '
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 2) Create the logm msg: file, line, log message and log object
|
|
51
|
-
const indent = ' \u2502 '
|
|
52
|
-
const logObj = {}
|
|
53
|
-
const ignoredKeys = ['level', 'msg', 'reqId', 'time', 'v']
|
|
54
|
-
Object.getOwnPropertyNames(obj).forEach(key => {
|
|
55
|
-
if (ignoredKeys.indexOf(key) === -1) {
|
|
56
|
-
logObj[key] = obj[key]
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
let msg = indent + prefix + (obj.msg || '')
|
|
60
|
-
if (Object.keys(logObj).length) {
|
|
61
|
-
msg += (obj.msg ? ' ' : '') + JSON.stringify(logObj)
|
|
62
|
-
}
|
|
63
|
-
if (obj.level < 30) {
|
|
64
|
-
console.debug(msg)
|
|
65
|
-
} else if (obj.level >= 50) {
|
|
66
|
-
console.error(msg)
|
|
67
|
-
} else if (obj.level >= 40) {
|
|
68
|
-
console.warn(msg)
|
|
69
|
-
} else {
|
|
70
|
-
console.log(msg)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return '' // skip pino logging
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
prettifier,
|
|
78
|
-
transport: {
|
|
79
|
-
target: 'pino-pretty',
|
|
80
|
-
options: {
|
|
81
|
-
colorize: true
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export default function makeCustomLogger (useUnitTestLogFormat) {
|
|
1
|
+
export default function makeCustomLogger (customizeOpts) {
|
|
88
2
|
function serializeReq (req) {
|
|
89
3
|
const q = req.query
|
|
4
|
+
// istanbul ignore else
|
|
90
5
|
if (req.raw) {
|
|
91
6
|
req = req.raw
|
|
92
7
|
}
|
|
93
8
|
const path = req.path
|
|
94
|
-
const qs = req.qs
|
|
95
9
|
return {
|
|
96
10
|
app: req.headers['x-app'] || '',
|
|
97
11
|
uid: req.headers['x-uid'] || '',
|
|
98
12
|
method: req.method,
|
|
99
13
|
ua: req.headers['user-agent'] || '',
|
|
100
14
|
path,
|
|
101
|
-
q
|
|
15
|
+
q
|
|
102
16
|
}
|
|
103
17
|
}
|
|
104
|
-
|
|
18
|
+
customizeOpts = customizeOpts ?? (x => x)
|
|
19
|
+
const options = customizeOpts({
|
|
105
20
|
base: null, // omit pino default fields like pid and hostname
|
|
106
|
-
level:
|
|
21
|
+
level: 'debug',
|
|
107
22
|
serializers: {
|
|
108
23
|
req: serializeReq,
|
|
109
|
-
res:
|
|
110
|
-
return { status: res.statusCode, req: serializeReq(res.
|
|
24
|
+
res: res => {
|
|
25
|
+
return { status: res.statusCode, req: serializeReq(res.request) }
|
|
111
26
|
}
|
|
112
27
|
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (useUnitTestLogFormat) {
|
|
116
|
-
Object.assign(options, getUnitTestLogFormatOverrides())
|
|
117
|
-
}
|
|
118
|
-
return pino(options)
|
|
28
|
+
})
|
|
29
|
+
return options
|
|
119
30
|
}
|
package/test/environment.js
CHANGED