@pbvision/fastify-firestore-service 0.0.6 → 0.0.8
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 +56 -19
- package/src/make-logger.js +9 -100
- package/src/plugins/latency-tracker.js +0 -11
- 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.8",
|
|
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,8 +1,9 @@
|
|
|
1
|
+
import Ajv from 'ajv/dist/2020.js'
|
|
1
2
|
import fastify from 'fastify'
|
|
2
3
|
import { v4 as uuidv4 } from 'uuid'
|
|
3
4
|
|
|
4
5
|
import ComponentRegistrar from './component-registrar.js'
|
|
5
|
-
import
|
|
6
|
+
import { makePinoLoggerOptions } from './make-logger.js'
|
|
6
7
|
import compressPlugin from './plugins/compress.js'
|
|
7
8
|
import contentParserPlugin from './plugins/content-parser.js'
|
|
8
9
|
import cookiePlugin from './plugins/cookie.js'
|
|
@@ -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,54 @@ 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
|
+
}
|
|
160
|
+
const logger = makePinoLoggerOptions(logging.customizePinoOpts)
|
|
145
161
|
const app = fastify({
|
|
146
|
-
ignoreTrailingSlash: true,
|
|
147
162
|
disableRequestLogging: true,
|
|
148
|
-
logger
|
|
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
|
-
}
|
|
163
|
+
logger,
|
|
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
|
+
})
|
|
177
|
+
.addHook('onResponse', (req, reply, done) => {
|
|
178
|
+
let objToLog = {}
|
|
179
|
+
try {
|
|
180
|
+
objToLog = logger.serializers.res(reply.raw)
|
|
181
|
+
Object.assign(objToLog, logger.serializers.req(req))
|
|
182
|
+
// istanbul ignore else
|
|
183
|
+
if (latencyTracker.header) {
|
|
184
|
+
// latency in milliseconds
|
|
185
|
+
objToLog.latency = reply.getHeader(latencyTracker.header)
|
|
186
|
+
}
|
|
187
|
+
} catch (err) /* istanbul ignore next */ {
|
|
188
|
+
// crashes in onResponse() aren't reported or caught unless we do this
|
|
189
|
+
if (typeof objToLog !== 'object') {
|
|
190
|
+
objToLog = {}
|
|
191
|
+
}
|
|
192
|
+
objToLog.failedToLog = JSON.stringify(err)
|
|
193
|
+
}
|
|
194
|
+
req.log.info(objToLog)
|
|
195
|
+
done()
|
|
196
|
+
})
|
|
160
197
|
|
|
161
198
|
const registrar = new RegistrarCls(app, service)
|
|
162
199
|
|
package/src/make-logger.js
CHANGED
|
@@ -1,119 +1,28 @@
|
|
|
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 function makePinoLoggerOptions (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.req) }
|
|
111
|
-
}
|
|
24
|
+
res: res => ({ status: res.statusCode })
|
|
112
25
|
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (useUnitTestLogFormat) {
|
|
116
|
-
Object.assign(options, getUnitTestLogFormatOverrides())
|
|
117
|
-
}
|
|
118
|
-
return pino(options)
|
|
26
|
+
})
|
|
27
|
+
return options
|
|
119
28
|
}
|
|
@@ -27,17 +27,6 @@ export default fp(function (fastify, options, next) {
|
|
|
27
27
|
return payload
|
|
28
28
|
})
|
|
29
29
|
|
|
30
|
-
fastify.addHook('onResponse', (req, reply, done) => {
|
|
31
|
-
// if errored, then it was already logged
|
|
32
|
-
if (!reply.raw.logged) {
|
|
33
|
-
req.log.info({
|
|
34
|
-
req,
|
|
35
|
-
status: reply.raw.statusCode,
|
|
36
|
-
latency: reply.getHeader(header)
|
|
37
|
-
})
|
|
38
|
-
}
|
|
39
|
-
done()
|
|
40
|
-
})
|
|
41
30
|
next()
|
|
42
31
|
}, {
|
|
43
32
|
fastify: '>=3.x',
|
package/test/environment.js
CHANGED