bajo 2.0.1 → 2.1.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/.github/FUNDING.yml +13 -0
- package/.github/workflows/repo-lockdown.yml +24 -0
- package/.jsdoc.conf.json +7 -6
- package/LICENSE +1 -1
- package/README.md +50 -18
- package/class/app.js +378 -54
- package/class/bajo.js +228 -149
- package/class/base.js +106 -0
- package/class/helper/bajo.js +146 -90
- package/class/helper/{plugin.js → base.js} +96 -22
- package/class/{base → misc}/err.js +44 -14
- package/class/misc/log.js +255 -0
- package/class/misc/print.js +264 -0
- package/class/plugin.js +120 -68
- package/docs/App.html +23 -1
- package/docs/Bajo.html +2 -9
- package/docs/Base.html +3 -0
- package/docs/Err.html +3 -1
- package/docs/Log.html +5 -1
- package/docs/Plugin.html +1 -1
- package/docs/Print.html +1 -1
- package/docs/class_app.js.html +378 -56
- package/docs/class_bajo.js.html +230 -151
- package/docs/class_base.js.html +109 -0
- package/docs/class_helper_bajo.js.html +138 -87
- package/docs/class_helper_base.js.html +246 -0
- package/docs/class_misc_err.js.html +129 -0
- package/docs/class_misc_log.js.html +210 -0
- package/docs/class_misc_print.js.html +267 -0
- package/docs/class_plugin.js.html +122 -70
- package/docs/data/search.json +1 -1
- package/docs/global.html +4 -1
- package/docs/index.html +2 -2
- package/docs/index.js.html +35 -0
- package/docs/lib_current-loc.js.html +36 -0
- package/docs/lib_formats.js.html +8 -8
- package/docs/lib_import-module.js.html +59 -0
- package/docs/lib_log-levels.js.html +17 -7
- package/docs/lib_parse-args-argv.js.html +83 -0
- package/docs/lib_parse-env.js.html +53 -0
- package/docs/lib_resolve-path.js.html +3 -6
- package/docs/lib_shim.js.html +8 -3
- package/docs/module-Helper_Bajo.html +3 -0
- package/docs/module-Helper_Base.html +3 -0
- package/docs/module-Lib.html +15 -0
- package/docs/static/home.md +32 -0
- package/docs/static/logo-ecosystem.png +0 -0
- package/docs/static/logo.png +0 -0
- package/extend/bajo/intl/en-US.json +9 -2
- package/extend/bajo/intl/id.json +9 -2
- package/index.js +22 -2
- package/lib/current-loc.js +24 -2
- package/lib/formats.js +6 -6
- package/lib/import-module.js +29 -0
- package/lib/log-levels.js +15 -5
- package/lib/parse-args-argv.js +20 -12
- package/lib/parse-env.js +18 -7
- package/lib/resolve-path.js +1 -4
- package/lib/shim.js +6 -1
- package/package.json +4 -7
- package/wiki/CONFIG.md +36 -0
- package/wiki/CONTRIBUTING.md +5 -0
- package/wiki/DEV_GUIDE.md +3 -0
- package/wiki/ECOSYSTEM.md +93 -0
- package/wiki/GETTING-STARTED.md +356 -0
- package/wiki/USER-GUIDE.md +256 -0
- package/class/base/log.js +0 -205
- package/class/base/plugin.js +0 -168
- package/class/base/print.js +0 -272
- package/docs/BasePlugin.html +0 -5
- package/docs/class_base_err.js.html +0 -99
- package/docs/class_base_log.js.html +0 -208
- package/docs/class_base_plugin.js.html +0 -180
- package/docs/class_base_print.js.html +0 -275
- package/docs/class_helper_plugin.js.html +0 -172
- package/docs/lib_create-method.js.html +0 -42
- package/docs/module-class_helper_bajo.html +0 -3
- package/docs/module-class_helper_plugin.html +0 -3
- package/docs/module-lib_create-method.html +0 -3
- package/docs/module-lib_formats.html +0 -3
- package/docs/module-lib_log-levels.html +0 -3
- package/docs/module-lib_resolve-path.html +0 -3
- package/docs/module-lib_shim.html +0 -3
- package/docs/tutorial-contribution.html +0 -3
- package/docs/tutorial-ecosystem.html +0 -3
- package/docs/tutorial-getting-started.html +0 -13
- package/docs/tutorial-plugin-dev.html +0 -3
- package/docs/tutorial-user-guide.html +0 -3
- package/lib/create-method.js +0 -39
- package/lib/dayjs.js +0 -8
- package/lib/omitted-plugin-keys.js +0 -3
- package/lib/read-all-configs.js +0 -19
- package/misc-docs/.hook.md +0 -11
- package/misc-docs/bitcoin.jpeg +0 -0
- package/misc-docs/contribution.md +0 -20
- package/misc-docs/ecosystem.md +0 -94
- package/misc-docs/getting-started.md +0 -142
- package/misc-docs/plugin-dev.md +0 -0
- package/misc-docs/toc.json +0 -17
- package/misc-docs/user-guide.md +0 -1
- /package/docs/{bitcoin.jpeg → static/bitcoin.jpeg} +0 -0
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import createMethod from '../../lib/create-method.js'
|
|
2
1
|
import semver from 'semver'
|
|
3
2
|
import lodash from 'lodash'
|
|
3
|
+
import Print from '../misc/print.js'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import resolvePath from '../../lib/resolve-path.js'
|
|
4
6
|
|
|
5
7
|
const {
|
|
8
|
+
isFunction,
|
|
6
9
|
merge,
|
|
7
10
|
forOwn,
|
|
8
11
|
groupBy,
|
|
@@ -18,7 +21,10 @@ const {
|
|
|
18
21
|
} = lodash
|
|
19
22
|
|
|
20
23
|
/**
|
|
21
|
-
*
|
|
24
|
+
* Internal helpers called by Bajo & plugins that only used once for bootstrapping purpose.
|
|
25
|
+
* It should remains hidden and not to be imported by any program.
|
|
26
|
+
*
|
|
27
|
+
* @module Helper/Base
|
|
22
28
|
*/
|
|
23
29
|
|
|
24
30
|
/**
|
|
@@ -27,12 +33,30 @@ const {
|
|
|
27
33
|
* @async
|
|
28
34
|
*/
|
|
29
35
|
export async function attachMethods () {
|
|
36
|
+
const { fastGlob } = this.lib
|
|
37
|
+
|
|
38
|
+
async function createMethod (dir) {
|
|
39
|
+
dir = resolvePath(dir)
|
|
40
|
+
const files = await fastGlob([`!${dir}/**/_*.{js,json}`, `${dir}/**/*.{js,json}`])
|
|
41
|
+
for (const f of files) {
|
|
42
|
+
const ext = path.extname(f)
|
|
43
|
+
const base = f.replace(dir, '').slice(0, -ext.length)
|
|
44
|
+
const name = camelCase(base)
|
|
45
|
+
let mod
|
|
46
|
+
if (ext === '.json') mod = this.app.bajo.readJson(f)
|
|
47
|
+
else mod = await this.app.bajo.importModule(f)
|
|
48
|
+
if (isFunction(mod)) mod = mod.bind(this)
|
|
49
|
+
this[name] = mod
|
|
50
|
+
}
|
|
51
|
+
return files.length
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
const { eachPlugins } = this.bajo
|
|
31
55
|
const me = this // the app
|
|
32
56
|
me.bajo.log.debug('attachMethods')
|
|
33
57
|
await eachPlugins(async function () {
|
|
34
|
-
const {
|
|
35
|
-
const dir = ns === me.
|
|
58
|
+
const { ns, pkgName } = this
|
|
59
|
+
const dir = ns === me.mainNs ? (`${me.bajo.dir.base}/${me.mainNs}`) : me.bajo.getModuleDir(pkgName)
|
|
36
60
|
const num = await createMethod.call(me[ns], `${dir}/method`, pkgName)
|
|
37
61
|
me.bajo.log.trace('- %s (%d)', ns, num)
|
|
38
62
|
})
|
|
@@ -45,11 +69,10 @@ export async function attachMethods () {
|
|
|
45
69
|
*/
|
|
46
70
|
export async function buildConfigs () {
|
|
47
71
|
this.bajo.log.debug('readConfigs')
|
|
48
|
-
for (const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
plugin.initLog()
|
|
72
|
+
for (const ns of this.getAllNs()) {
|
|
73
|
+
await this[ns].loadConfig()
|
|
74
|
+
this[ns].print = new Print(this[ns])
|
|
75
|
+
this.loadIntl(ns)
|
|
53
76
|
}
|
|
54
77
|
}
|
|
55
78
|
|
|
@@ -63,7 +86,8 @@ export async function checkNameAliases () {
|
|
|
63
86
|
this.bajo.log.debug('checkAliasNameClash')
|
|
64
87
|
const refs = []
|
|
65
88
|
await eachPlugins(async function () {
|
|
66
|
-
const {
|
|
89
|
+
const { ns, pkgName } = this
|
|
90
|
+
const { alias } = this.constructor
|
|
67
91
|
let item = find(refs, { ns })
|
|
68
92
|
if (item) throw this.error('pluginNameClash%s%s%s%s', ns, pkgName, item.ns, item.pkgName, { code: 'BAJO_NAME_CLASH' })
|
|
69
93
|
item = find(refs, { alias })
|
|
@@ -79,10 +103,11 @@ export async function checkNameAliases () {
|
|
|
79
103
|
*/
|
|
80
104
|
export async function checkDependencies () {
|
|
81
105
|
async function runner () {
|
|
82
|
-
const {
|
|
106
|
+
const { ns, pkgName } = this
|
|
83
107
|
const { join } = this.app.bajo
|
|
84
108
|
this.app.bajo.log.trace('- %s', ns)
|
|
85
|
-
const
|
|
109
|
+
const { dependencies } = this.app.pluginClass[this.ns]
|
|
110
|
+
const odep = reduce(dependencies, (o, k) => {
|
|
86
111
|
const item = map(k.split('@'), m => trim(m))
|
|
87
112
|
if (k[0] === '@') o['@' + item[1]] = item[2]
|
|
88
113
|
else o[item[0]] = item[1]
|
|
@@ -90,7 +115,7 @@ export async function checkDependencies () {
|
|
|
90
115
|
}, {})
|
|
91
116
|
const deps = keys(odep)
|
|
92
117
|
if (deps.length > 0) {
|
|
93
|
-
if (intersection(this.app.
|
|
118
|
+
if (intersection(this.app.pluginPkgs, deps).length !== deps.length) {
|
|
94
119
|
throw this.error('dependencyUnfulfilled%s%s', pkgName, join(deps), { code: 'BAJO_DEPENDENCY' })
|
|
95
120
|
}
|
|
96
121
|
each(deps, d => {
|
|
@@ -115,6 +140,7 @@ export async function checkDependencies () {
|
|
|
115
140
|
* Collect and build hooks and push them to the bajo's hook system
|
|
116
141
|
*
|
|
117
142
|
* @async
|
|
143
|
+
* @fires bajo:afterCollectHooks
|
|
118
144
|
*/
|
|
119
145
|
export async function collectHooks () {
|
|
120
146
|
const { eachPlugins, runHook, isLogInRange, importModule, breakNsPathFromFile } = this.bajo
|
|
@@ -123,13 +149,13 @@ export async function collectHooks () {
|
|
|
123
149
|
me.bajo.log.debug('collectHooks')
|
|
124
150
|
// collects
|
|
125
151
|
await eachPlugins(async function ({ dir, file }) {
|
|
126
|
-
const {
|
|
152
|
+
const { ns } = this
|
|
127
153
|
const { fullNs, path } = breakNsPathFromFile({ file, dir, baseNs: ns, suffix: '/hook/' })
|
|
128
154
|
const mod = await importModule(file, { asHandler: true })
|
|
129
155
|
if (!mod) return undefined
|
|
130
156
|
merge(mod, { ns: fullNs, path, src: ns })
|
|
131
157
|
me.bajo.hooks.push(mod)
|
|
132
|
-
}, { glob: 'hook/**/*.js', prefix: me.bajo.
|
|
158
|
+
}, { glob: 'hook/**/*.js', prefix: me.bajo.ns })
|
|
133
159
|
// for log trace purpose only
|
|
134
160
|
if (!isLogInRange('trace')) return
|
|
135
161
|
const items = groupBy(me.bajo.hooks, 'ns')
|
|
@@ -139,31 +165,79 @@ export async function collectHooks () {
|
|
|
139
165
|
me.bajo.log.trace('- %s:%s (%d)', k, k1, v1.length)
|
|
140
166
|
})
|
|
141
167
|
})
|
|
142
|
-
|
|
143
|
-
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Run after hooks are collected
|
|
171
|
+
*
|
|
172
|
+
* @global
|
|
173
|
+
* @event bajo:afterCollectHooks
|
|
174
|
+
* @param {Object[]} hooks - Array of hook objects
|
|
175
|
+
* @see {@tutorial hook}
|
|
176
|
+
* @see module:Helper/Base.collectHooks
|
|
177
|
+
*/
|
|
178
|
+
await runHook('bajo:afterCollectHooks', this.bajo.hooks)
|
|
144
179
|
}
|
|
145
180
|
|
|
146
181
|
/**
|
|
147
182
|
* Finally, run all plugins
|
|
148
183
|
*
|
|
149
184
|
* @async
|
|
185
|
+
* @fires bajo:beforeAll{method}
|
|
186
|
+
* @fires {ns}:before{method}
|
|
187
|
+
* @fires {ns}:after{method}
|
|
188
|
+
* @fires bajo:afterAll{method}
|
|
150
189
|
*/
|
|
151
190
|
export async function run () {
|
|
152
191
|
const me = this
|
|
153
192
|
const { runHook, eachPlugins, join } = me.bajo
|
|
154
193
|
const { freeze } = me.bajo
|
|
155
194
|
const methods = ['init']
|
|
156
|
-
if (!me.
|
|
195
|
+
if (!me.applet) methods.push('start')
|
|
196
|
+
me.bajo.log.debug('loadedPlugins%s', join(map(me.bajo.app.pluginPkgs, b => camelCase(b))))
|
|
157
197
|
for (const method of methods) {
|
|
158
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Run before all ```{method}``` executed. Accepted ```{method}```: ```Init``` or ```Start```
|
|
200
|
+
*
|
|
201
|
+
* @global
|
|
202
|
+
* @event bajo:beforeAll{method}
|
|
203
|
+
* @param {string} method - Accepted methods: ```Init```, ```Start```
|
|
204
|
+
* @see module:Helper/Base.run
|
|
205
|
+
*/
|
|
206
|
+
await runHook(`bajo:${camelCase(`before all ${method}`)}`)
|
|
159
207
|
await eachPlugins(async function () {
|
|
160
|
-
const {
|
|
208
|
+
const { ns } = this
|
|
161
209
|
if (method === 'start') freeze(me[ns].config)
|
|
210
|
+
/**
|
|
211
|
+
* Run before ```{method}``` is executed within ```{ns}``` context
|
|
212
|
+
*
|
|
213
|
+
* - ```{ns}``` - namespace
|
|
214
|
+
* - ```{method}``` - Accepted methods: ```Init``` or ```Start```
|
|
215
|
+
*
|
|
216
|
+
* @global
|
|
217
|
+
* @event {ns}:before{method}
|
|
218
|
+
* @see module:Helper/Base.run
|
|
219
|
+
*/
|
|
162
220
|
await runHook(`${ns}:${camelCase(`before ${method}`)}`)
|
|
163
221
|
await me[ns][method]()
|
|
222
|
+
/**
|
|
223
|
+
* Run after ```{method}``` is executed within ```{ns}``` context
|
|
224
|
+
*
|
|
225
|
+
* - ```{ns}``` - namespace
|
|
226
|
+
* - ```{method}``` - Accepted methods: ```Init``` or ```Start```
|
|
227
|
+
*
|
|
228
|
+
* @global
|
|
229
|
+
* @event {ns}:after{method}
|
|
230
|
+
* @see module:Helper/Base.run
|
|
231
|
+
*/
|
|
164
232
|
await runHook(`${ns}:${camelCase(`after ${method}`)}`)
|
|
165
233
|
})
|
|
166
|
-
|
|
234
|
+
/**
|
|
235
|
+
* Run after all ```{method}``` executed. Accepted ```{method}```: ```Init``` or ```Start```
|
|
236
|
+
*
|
|
237
|
+
* @global
|
|
238
|
+
* @event bajo:afterAll{method}
|
|
239
|
+
* @see module:Helper/Base.run
|
|
240
|
+
*/
|
|
241
|
+
await runHook(`bajo:${camelCase(`after all ${method}`)}`)
|
|
167
242
|
}
|
|
168
|
-
me.bajo.log.debug('loadedPlugins%s', join(map(me.bajo.pluginPkgs, b => camelCase(b))))
|
|
169
243
|
}
|
|
@@ -4,28 +4,60 @@ const { isPlainObject, each, isArray, get, isEmpty, merge } = lodash
|
|
|
4
4
|
Error.stackTraceLimit = 15
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Bajo error class, a thin wrapper of node's Error object.
|
|
8
|
-
*
|
|
7
|
+
* Bajo error class, a thin wrapper of node's Error object.
|
|
8
|
+
*
|
|
9
|
+
* Every Bajo {@link Plugin|plugin} has a built-in method called ```error``` which basically the shortcut to create a new Err instance.
|
|
10
|
+
* It helps you create this instance anywhere in your code quickly without the hassle of importing & instantiating:
|
|
11
|
+
*
|
|
12
|
+
* ```javascript
|
|
13
|
+
* ... anywhere inside your code
|
|
14
|
+
* if (notfound) throw this.error('Sorry, item is nowhere to be found!')
|
|
15
|
+
* ```
|
|
9
16
|
*/
|
|
10
17
|
class Err {
|
|
11
18
|
/**
|
|
12
|
-
* @param {
|
|
19
|
+
* @param {Plugin} plugin - Plugin instance
|
|
13
20
|
* @param {string} msg - Error message
|
|
14
|
-
* @param {...any} [args] - Variables to interpolate with error message. Payload object can be pushed
|
|
21
|
+
* @param {...any} [args] - Variables to interpolate with error message. Payload object can be pushed at the very last argument
|
|
15
22
|
*/
|
|
16
23
|
constructor (plugin, msg, ...args) {
|
|
24
|
+
/**
|
|
25
|
+
* Attached plugin
|
|
26
|
+
* @type {Plugin}
|
|
27
|
+
*/
|
|
17
28
|
this.plugin = plugin
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The app instance
|
|
32
|
+
* @type {App}
|
|
33
|
+
*/
|
|
34
|
+
this.app = plugin.app
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Error payload extracted from the last arguments
|
|
38
|
+
* @type {Object}
|
|
39
|
+
*/
|
|
18
40
|
this.payload = args.length > 0 && isPlainObject(args[args.length - 1]) ? args[args.length - 1] : {}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Original message before translation
|
|
44
|
+
* @type {string}
|
|
45
|
+
*/
|
|
19
46
|
this.orgMessage = msg
|
|
20
|
-
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Translated message
|
|
50
|
+
* @type {string}
|
|
51
|
+
*/
|
|
52
|
+
this.message = this.payload.noTrans ? msg : this.plugin.t(msg, ...args)
|
|
21
53
|
this.write()
|
|
22
54
|
}
|
|
23
55
|
|
|
24
56
|
/**
|
|
25
|
-
*
|
|
57
|
+
* Write message to the console
|
|
26
58
|
*
|
|
27
59
|
* @method
|
|
28
|
-
* @returns {
|
|
60
|
+
* @returns {Err} Error object, usefull for chaining
|
|
29
61
|
*/
|
|
30
62
|
write = () => {
|
|
31
63
|
let err
|
|
@@ -46,20 +78,20 @@ class Err {
|
|
|
46
78
|
err[key] = value
|
|
47
79
|
}
|
|
48
80
|
if (!isEmpty(values)) err.values = values
|
|
49
|
-
err.ns = this.plugin.
|
|
81
|
+
err.ns = this.plugin.ns
|
|
50
82
|
err.orgMessage = this.orgMessage
|
|
51
83
|
return err
|
|
52
84
|
}
|
|
53
85
|
|
|
54
86
|
/**
|
|
55
|
-
* Print
|
|
87
|
+
* Print instance on console and terminate process
|
|
56
88
|
*
|
|
57
89
|
* @method
|
|
58
90
|
*/
|
|
59
91
|
fatal = () => {
|
|
60
92
|
const err = this.write()
|
|
61
93
|
console.error(err)
|
|
62
|
-
|
|
94
|
+
this.app.exit()
|
|
63
95
|
}
|
|
64
96
|
|
|
65
97
|
/**
|
|
@@ -70,21 +102,19 @@ class Err {
|
|
|
70
102
|
* @returns {Object}
|
|
71
103
|
*/
|
|
72
104
|
formatErrorDetails = (value) => {
|
|
73
|
-
const { isString } = this.
|
|
105
|
+
const { isString } = this.app.lib._
|
|
74
106
|
const result = {}
|
|
75
107
|
const me = this
|
|
76
108
|
each(value, (v, i) => {
|
|
77
|
-
const print = me.plugin.print
|
|
78
109
|
if (isString(v)) v = { error: v }
|
|
79
110
|
if (!v.context) return undefined
|
|
80
111
|
v.context.message = v.message
|
|
81
|
-
// if (v.type === 'any.only') v.context.ref = print.write(`field.${get(v, 'context.valids.0.key')}`)
|
|
82
112
|
if (v.type === 'any.only') v.context.ref = get(v, 'context.valids', []).join(', ')
|
|
83
113
|
const field = get(v, 'context.key')
|
|
84
114
|
const val = get(v, 'context.value')
|
|
85
115
|
value[i] = {
|
|
86
116
|
field,
|
|
87
|
-
error:
|
|
117
|
+
error: me.plugin.t(`validation.${v.type}`, v.context ?? {}, {}),
|
|
88
118
|
value: val,
|
|
89
119
|
ext: { type: v.type, context: v.context }
|
|
90
120
|
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import os from 'os'
|
|
2
|
+
import logLevels from '../../lib/log-levels.js'
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
import { stripVTControlCharacters } from 'node:util'
|
|
5
|
+
import dayjs from 'dayjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Log output in stringified JSON format. Returned when app run in ```prod``` environment
|
|
9
|
+
*
|
|
10
|
+
* @typedef TLogJson
|
|
11
|
+
* @property {string} prefix - Message prefix
|
|
12
|
+
* @property {string} message - The message itself
|
|
13
|
+
* @property {string} level - Log level
|
|
14
|
+
* @property {number} time - Time in millisecond
|
|
15
|
+
* @property {number} pid - Process ID
|
|
16
|
+
* @property {string} hostname - Hostname
|
|
17
|
+
* @property {Object} [data] - Payload data, if any
|
|
18
|
+
* @see Log#formatMsg
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A thin & fast logger system.
|
|
23
|
+
*
|
|
24
|
+
* An instance is created by the {@link App|app} and available to use anywhere like this:
|
|
25
|
+
*
|
|
26
|
+
* ```javascript
|
|
27
|
+
* ... anywhere inside your code
|
|
28
|
+
* this.app.log.debug(...)
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* Shortcuts to log's methods are also available on every Bajo {@link Plugin|plugin}. Call on
|
|
32
|
+
* these shortcuts will be prefixed with it's plugin name automatically:
|
|
33
|
+
*
|
|
34
|
+
* ```javascript
|
|
35
|
+
* ... anywhere inside your code
|
|
36
|
+
* if (!isValid) this.log.error('Invalid value!')
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @class
|
|
40
|
+
*/
|
|
41
|
+
class Log {
|
|
42
|
+
/**
|
|
43
|
+
* @param {App} app - App instance
|
|
44
|
+
*/
|
|
45
|
+
constructor (app) {
|
|
46
|
+
this.lastDelta = 0
|
|
47
|
+
/**
|
|
48
|
+
* The app instance
|
|
49
|
+
* @type {App}
|
|
50
|
+
*/
|
|
51
|
+
this.app = app
|
|
52
|
+
|
|
53
|
+
const { fs } = this.app.lib
|
|
54
|
+
this.logDir = `${this.app.bajo.dir.data}/log`
|
|
55
|
+
if (this.app.bajo.config.log.save) fs.ensureDirSync(this.logDir)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Display & format message according to one of these rules:
|
|
60
|
+
* 1. ```level``` ```prefix``` ```text``` ```var 1``` ```var 2``` ```...var n``` - Translate ```text``` and interpolate with ```vars``` for level ```level```
|
|
61
|
+
* 2. ```level``` ```prefix``` ```data``` ```text``` ```var 1``` ```var 2``` ```...var n``` - As above, and append stringified ```data```
|
|
62
|
+
* 3. ```level``` ```prefix``` ```error``` - Format as {@link Err} object. If current log level is _trace_, dump it on screen
|
|
63
|
+
*
|
|
64
|
+
* In ```prod``` environment, log will be delivered as JSON stringified object. See {@link TLogJson} for more info
|
|
65
|
+
*
|
|
66
|
+
* @method
|
|
67
|
+
* @param {string} level - Log level to use
|
|
68
|
+
* @param {string} prefix - Prefix to the message
|
|
69
|
+
* @param {...any} params - See format above
|
|
70
|
+
* @see Err
|
|
71
|
+
* @see TLogJson
|
|
72
|
+
*/
|
|
73
|
+
formatMsg = (level, prefix, ...params) => {
|
|
74
|
+
const { dayjs } = this.app.lib
|
|
75
|
+
const { isEmpty, merge, without } = this.app.lib._
|
|
76
|
+
|
|
77
|
+
if (this.app.bajo.config.log.level === 'silent') return
|
|
78
|
+
if (!this.app.bajo.isLogInRange(level)) return
|
|
79
|
+
const { useUtc, timeTaken, dateFormat, pretty } = this.app.bajo.config.log
|
|
80
|
+
let [data, msg, ...args] = params
|
|
81
|
+
if (typeof data === 'string') {
|
|
82
|
+
args.unshift(msg)
|
|
83
|
+
msg = data
|
|
84
|
+
data = null
|
|
85
|
+
}
|
|
86
|
+
args = without(args, undefined)
|
|
87
|
+
if (data instanceof Error) {
|
|
88
|
+
msg = 'error%s'
|
|
89
|
+
args = [data.message]
|
|
90
|
+
}
|
|
91
|
+
msg = this.app.t(prefix, msg, ...args)
|
|
92
|
+
let text
|
|
93
|
+
const dt = dayjs()
|
|
94
|
+
let diff = null
|
|
95
|
+
if (timeTaken) {
|
|
96
|
+
const delta = dt.diff(this.app.runAt, 'ms')
|
|
97
|
+
diff = delta - this.lastDelta
|
|
98
|
+
this.lastDelta = delta
|
|
99
|
+
}
|
|
100
|
+
if (this.app.bajo.config.env === 'prod') {
|
|
101
|
+
const json = { prefix, msg, level: logLevels[level].number, time: dt.valueOf(), pid: process.pid, hostname: os.hostname() }
|
|
102
|
+
if (!isEmpty(data)) merge(json, { data })
|
|
103
|
+
if (timeTaken) merge(json, { timeTaken: diff })
|
|
104
|
+
text = JSON.stringify(json)
|
|
105
|
+
} else {
|
|
106
|
+
let date = dt.clone()
|
|
107
|
+
if (useUtc) date = dayjs.utc(dt)
|
|
108
|
+
date = date.format(dateFormat)
|
|
109
|
+
let tdate = pretty ? chalk.cyan(date) : `[${date}]`
|
|
110
|
+
if (timeTaken) {
|
|
111
|
+
const tdiff = pretty ? chalk.cyan(`+${diff}ms`) : `[+${diff}ms]`
|
|
112
|
+
tdate += ` ${tdiff}`
|
|
113
|
+
}
|
|
114
|
+
const tlevel = pretty ? `${chalk[logLevels[level].color](level.toUpperCase())}:` : `[${level.toUpperCase()}]`
|
|
115
|
+
const tprefix = pretty ? chalk.bgBlue(`${prefix}`) : `[${prefix}]`
|
|
116
|
+
text = `${tdate} ${tlevel} ${tprefix} ${msg}`
|
|
117
|
+
if (!isEmpty(data)) text += '\n' + JSON.stringify(data)
|
|
118
|
+
}
|
|
119
|
+
console.log(text)
|
|
120
|
+
if (data instanceof Error && level === 'trace') console.error(data)
|
|
121
|
+
if (this.app.bajo.config.log.save) this.save(text, prefix)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Calculate pattern used for log rotation
|
|
126
|
+
*
|
|
127
|
+
* @method
|
|
128
|
+
* @param {boolean} isPrev - If true, calculate previous rotation pattern
|
|
129
|
+
* @returns {string} Calculated pattern
|
|
130
|
+
*/
|
|
131
|
+
getRotationPattern = (isPrev) => {
|
|
132
|
+
const { dayjs } = this.app.lib
|
|
133
|
+
const { cycle } = this.app.bajo.config.log.rotation
|
|
134
|
+
if (cycle === 'none') return
|
|
135
|
+
let pattern
|
|
136
|
+
const now = dayjs()
|
|
137
|
+
switch (cycle) {
|
|
138
|
+
case 'monthly': {
|
|
139
|
+
const dt = isPrev ? now.subtract(1, 'month') : now
|
|
140
|
+
pattern = dt.format('YYYY-MM')
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
case 'weekly': {
|
|
144
|
+
const dt = isPrev ? now.subtract(1, 'week') : now
|
|
145
|
+
pattern = dt.format(`YYYY-W${dt.week()}`)
|
|
146
|
+
break
|
|
147
|
+
}
|
|
148
|
+
case 'daily': {
|
|
149
|
+
const dt = isPrev ? now.subtract(1, 'day') : now
|
|
150
|
+
pattern = dt.format('YYYY-MM-DD')
|
|
151
|
+
break
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return pattern
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Save log to file in {dataDir}/log
|
|
159
|
+
*
|
|
160
|
+
* @method
|
|
161
|
+
* @param {string} text - Log message to save
|
|
162
|
+
* @param {string} prefix - Use prefix as basename. Defaults to 'bajo'
|
|
163
|
+
*/
|
|
164
|
+
save = (text, prefix = 'bajo') => {
|
|
165
|
+
const { fs } = this.app.lib
|
|
166
|
+
let fname = this.app.bajo.config.log.rotation.byPlugin ? prefix : 'bajo'
|
|
167
|
+
let file = `${this.logDir}/${fname}.log`
|
|
168
|
+
const content = stripVTControlCharacters(text)
|
|
169
|
+
const pattern = this.getRotationPattern()
|
|
170
|
+
if (pattern) {
|
|
171
|
+
file = `${this.logDir}/${fname}.${pattern}.log`
|
|
172
|
+
}
|
|
173
|
+
fs.appendFileSync(file, `${content}\n`, 'utf8')
|
|
174
|
+
// TODO: symlink bajo.log to target
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Display & format message in ```trace``` level. See {@link Log#formatMsg|formatMsg} for details
|
|
179
|
+
*
|
|
180
|
+
* @method
|
|
181
|
+
* @param {string} prefix - Message prefix
|
|
182
|
+
* @param {...any} params - Parameters
|
|
183
|
+
*/
|
|
184
|
+
trace = (prefix, ...params) => {
|
|
185
|
+
this.formatMsg('trace', prefix, ...params)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Display & format message in ```debug``` level. See {@link Log#formatMsg|formatMsg} for details
|
|
190
|
+
*
|
|
191
|
+
* @method
|
|
192
|
+
* @param {string} prefix - Message prefix
|
|
193
|
+
* @param {...any} params - Parameters
|
|
194
|
+
*/
|
|
195
|
+
debug = (prefix, ...params) => {
|
|
196
|
+
this.formatMsg('debug', prefix, ...params)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Display & format message in ```info``` level. See {@link Log#formatMsg|formatMsg} for details
|
|
201
|
+
*
|
|
202
|
+
* @method
|
|
203
|
+
* @param {string} prefix - Message prefix
|
|
204
|
+
* @param {...any} params - Parameters
|
|
205
|
+
*/
|
|
206
|
+
info = (prefix, ...params) => {
|
|
207
|
+
this.formatMsg('info', prefix, ...params)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Display & format message in ```warn``` level. See {@link Log#formatMsg|formatMsg} for details
|
|
212
|
+
*
|
|
213
|
+
* @method
|
|
214
|
+
* @param {string} prefix - Message prefix
|
|
215
|
+
* @param {...any} params - Parameters
|
|
216
|
+
*/
|
|
217
|
+
warn = (prefix, ...params) => {
|
|
218
|
+
this.formatMsg('warn', prefix, ...params)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Display & format message in ```error``` level. See {@link Log#formatMsg|formatMsg} for details
|
|
223
|
+
*
|
|
224
|
+
* @method
|
|
225
|
+
* @param {string} prefix - Message prefix
|
|
226
|
+
* @param {...any} params - Parameters
|
|
227
|
+
*/
|
|
228
|
+
error = (prefix, ...params) => {
|
|
229
|
+
this.formatMsg('error', prefix, ...params)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Display & format message in ```fatal``` level. See {@link Log#formatMsg|formatMsg} for details
|
|
234
|
+
*
|
|
235
|
+
* @method
|
|
236
|
+
* @param {string} prefix - Message prefix
|
|
237
|
+
* @param {...any} params - Parameters
|
|
238
|
+
*/
|
|
239
|
+
fatal = (prefix, ...params) => {
|
|
240
|
+
this.formatMsg('fatal', prefix, ...params)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Display & format message in ```silent``` level. See {@link Log#formatMsg|formatMsg} for details
|
|
245
|
+
*
|
|
246
|
+
* @method
|
|
247
|
+
* @param {string} prefix - Message prefix
|
|
248
|
+
* @param {...any} params - Parameters
|
|
249
|
+
*/
|
|
250
|
+
silent = (prefix, ...params) => {
|
|
251
|
+
this.formatMsg('silent', prefix, ...params)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export default Log
|