w-syslog 1.0.7 → 1.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/docs/WSyslog.html CHANGED
@@ -80,7 +80,7 @@
80
80
 
81
81
  <dt class="tag-source">Source:</dt>
82
82
  <dd class="tag-source"><ul class="dummy"><li>
83
- <a href="WSyslog.mjs.html">WSyslog.mjs</a>, <a href="WSyslog.mjs.html#line48">line 48</a>
83
+ <a href="WSyslog.mjs.html">WSyslog.mjs</a>, <a href="WSyslog.mjs.html#line55">line 55</a>
84
84
  </li></ul></dd>
85
85
 
86
86
 
@@ -301,6 +301,84 @@
301
301
  </tr>
302
302
 
303
303
 
304
+
305
+ <tr>
306
+
307
+ <td class="name"><code>timeZone</code></td>
308
+
309
+
310
+ <td class="type">
311
+
312
+
313
+ <span class="param-type">String</span>
314
+
315
+
316
+
317
+
318
+ </td>
319
+
320
+
321
+ <td class="attributes">
322
+
323
+ &lt;optional><br>
324
+
325
+
326
+
327
+
328
+
329
+ </td>
330
+
331
+
332
+
333
+ <td class="default">
334
+
335
+ <code>null</code>
336
+
337
+ </td>
338
+
339
+
340
+ <td class="description last"><p>輸入時區字串(IANA格式如'Asia/Taipei'、'UTC'),用於決定切檔邊界與檔名,預設null代表使用系統時區</p></td>
341
+ </tr>
342
+
343
+
344
+
345
+ <tr>
346
+
347
+ <td class="name"><code>numKeep</code></td>
348
+
349
+
350
+ <td class="type">
351
+
352
+
353
+ <span class="param-type">Number</span>
354
+
355
+
356
+
357
+
358
+ </td>
359
+
360
+
361
+ <td class="attributes">
362
+
363
+ &lt;optional><br>
364
+
365
+
366
+
367
+
368
+
369
+ </td>
370
+
371
+
372
+
373
+ <td class="default">
374
+
375
+ </td>
376
+
377
+
378
+ <td class="description last"><p>輸入保留log檔案數量整數,超出最舊檔將被自動刪除,預設於day模式為365、hr模式為365*24(約等同保留一年)</p></td>
379
+ </tr>
380
+
381
+
304
382
  </tbody>
305
383
  </table>
306
384
 
@@ -330,7 +408,7 @@
330
408
 
331
409
 
332
410
  <div class="param-desc">
333
- <p>回傳log物件,提供info、warn、error紀錄函數</p>
411
+ <p>回傳log物件,提供fatalerror、warn、info、debug、trace紀錄函數,cleanLogs手動清理函數,及clear清理計時器函數</p>
334
412
  </div>
335
413
 
336
414
 
@@ -389,7 +467,7 @@
389
467
  <br class="clear">
390
468
 
391
469
  <footer>
392
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.4</a> on Mon Aug 25 2025 14:33:18 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
470
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri May 08 2026 16:56:20 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
393
471
  </footer>
394
472
 
395
473
  <script>prettyPrint();</script>
@@ -45,10 +45,15 @@
45
45
 
46
46
  <section>
47
47
  <article>
48
- <pre class="prettyprint source linenums"><code>import path from 'path'
48
+ <pre class="prettyprint source linenums"><code>import fs from 'fs'
49
+ import path from 'path'
49
50
  import { fileURLToPath } from 'url'
50
51
  import get from 'lodash-es/get.js'
52
+ import evem from 'wsemi/src/evem.mjs'
51
53
  import isestr from 'wsemi/src/isestr.mjs'
54
+ import ispint from 'wsemi/src/ispint.mjs'
55
+ import cint from 'wsemi/src/cint.mjs'
56
+ import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
52
57
  import pino from 'pino'
53
58
 
54
59
 
@@ -64,7 +69,9 @@ let __dirname = path.dirname(__filename)
64
69
  * @param {Object} [opt={}] 輸入設定物件,預設{}
65
70
  * @param {String} [opt.fdLog='./_logs'] 輸入儲存log資料夾字串,預設'./_logs'
66
71
  * @param {String} [opt.interval='day'] 輸入儲存log時分檔模式字串,可選'day'、'hr',分別代表每日或每時分檔,預設'day'
67
- * @returns {Object} 回傳log物件,提供infowarn、error紀錄函數
72
+ * @param {String} [opt.timeZone=null] 輸入時區字串(IANA格式如'Asia/Taipei''UTC'),用於決定切檔邊界與檔名,預設null代表使用系統時區
73
+ * @param {Number} [opt.numKeep] 輸入保留log檔案數量整數,超出最舊檔將被自動刪除,預設於day模式為365、hr模式為365*24(約等同保留一年)
74
+ * @returns {Object} 回傳log物件,提供fatal、error、warn、info、debug、trace紀錄函數,cleanLogs手動清理函數,及clear清理計時器函數
68
75
  * @example
69
76
  * import fs from 'fs'
70
77
  * import _ from 'lodash-es'
@@ -94,6 +101,12 @@ let __dirname = path.dirname(__filename)
94
101
  */
95
102
  function WSyslog(opt = {}) {
96
103
 
104
+ //ev
105
+ let ev = evem()
106
+
107
+ //timeZone, 'Asia/Taipei'
108
+ let timeZone = get(opt, 'timeZone', null)
109
+
97
110
  //fdLog
98
111
  let fdLog = get(opt, 'fdLog')
99
112
  if (!isestr(fdLog)) {
@@ -106,6 +119,18 @@ function WSyslog(opt = {}) {
106
119
  interval = 'day'
107
120
  }
108
121
 
122
+ //numKeep
123
+ let numKeep = get(opt, 'numKeep')
124
+ if (!ispint(numKeep)) {
125
+ if (interval === 'day') {
126
+ numKeep = 365
127
+ }
128
+ else {
129
+ numKeep = 365 * 24
130
+ }
131
+ }
132
+ numKeep = cint(numKeep)
133
+
109
134
  // 建立 logger,使用自訂 transport
110
135
  let transport = pino.transport({
111
136
  targets: [
@@ -124,6 +149,7 @@ function WSyslog(opt = {}) {
124
149
  options: {
125
150
  interval,
126
151
  fdLog,
152
+ timeZone,
127
153
  },
128
154
 
129
155
  },
@@ -134,7 +160,82 @@ function WSyslog(opt = {}) {
134
160
  let log = pino(transport)
135
161
  // console.log('log', log)
136
162
 
137
- return log
163
+ //cleanLogs, 依numKeep保留最新N個檔, 超出的最舊檔刪除
164
+ let cleanLogs = () => {
165
+ try {
166
+ if (!fsIsFolder(fdLog)) {
167
+ return
168
+ }
169
+ //pattern, day模式為'YYYY-MM-DD.log', hr模式為'YYYY-MM-DDTHH.log'
170
+ let pattern = (interval === 'hr')
171
+ ? /^\d{4}-\d{2}-\d{2}T\d{2}\.log$/
172
+ : /^\d{4}-\d{2}-\d{2}\.log$/
173
+ let files = fs.readdirSync(fdLog).filter((fn) => pattern.test(fn))
174
+ //ISO日期格式字典序等同時間序, 排序後最舊在前
175
+ files.sort()
176
+ let nDel = files.length - numKeep
177
+ if (nDel &lt;= 0) {
178
+ return
179
+ }
180
+ let toDelete = files.slice(0, nDel)
181
+ for (let fn of toDelete) {
182
+ let fp = path.join(fdLog, fn)
183
+ try {
184
+ fs.unlinkSync(fp)
185
+ ev.emit('delete', { fp, fn })
186
+ }
187
+ catch (err) {
188
+ //容忍其他process同時刪除或檔案被佔用之情況
189
+ }
190
+ }
191
+ }
192
+ catch (err) {
193
+ console.error('WSyslog cleanLogs error:', err)
194
+ }
195
+ }
196
+
197
+ //啟動時立刻清理一次, 確保短命腳本也能執行清理
198
+ cleanLogs()
199
+
200
+ //之後每小時清理一次, 足以跟上day/hr切檔節奏
201
+ let t = setInterval(cleanLogs, 60 * 60 * 1000)
202
+ //unref不阻塞process結束
203
+ t.unref()
204
+
205
+ //clear
206
+ let clear = () => {
207
+ clearInterval(t)
208
+ }
209
+
210
+ //wrap, 包裝pino各等級method, 於寫log前先觸發'log'事件, 使外部可監聽進行console輸出或轉送其他記錄器
211
+ // fatal: 60
212
+ // error: 50
213
+ // warn: 40
214
+ // info: 30
215
+ // debug: 20
216
+ // trace: 10
217
+ let wrap = (level) => {
218
+ let fn = log[level].bind(log)
219
+ return (...args) => {
220
+ try {
221
+ ev.emit(level, { args })
222
+ }
223
+ catch (err) {
224
+ //容忍listener內部錯誤, 避免影響pino寫log
225
+ }
226
+ fn(...args)
227
+ }
228
+ }
229
+ ev.fatal = wrap('fatal')
230
+ ev.error = wrap('error')
231
+ ev.warn = wrap('warn')
232
+ ev.info = wrap('info')
233
+ ev.debug = wrap('debug')
234
+ ev.trace = wrap('trace')
235
+ ev.cleanLogs = cleanLogs
236
+ ev.clear = clear
237
+
238
+ return ev
138
239
  }
139
240
 
140
241
 
@@ -153,7 +254,7 @@ export default WSyslog
153
254
  <br class="clear">
154
255
 
155
256
  <footer>
156
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.4</a> on Mon Aug 25 2025 14:33:18 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
257
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri May 08 2026 16:56:20 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
157
258
  </footer>
158
259
 
159
260
  <script>prettyPrint();</script>
package/docs/index.html CHANGED
@@ -71,7 +71,7 @@
71
71
  <br class="clear">
72
72
 
73
73
  <footer>
74
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.4</a> on Mon Aug 25 2025 14:33:18 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
74
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri May 08 2026 16:56:20 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
75
75
  </footer>
76
76
 
77
77
  <script>prettyPrint();</script>
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "w-syslog",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "main": "dist/w-syslog.umd.js",
5
5
  "dependencies": {
6
- "lodash-es": "^4.17.21",
7
- "pino": "^9.9.0",
8
- "wsemi": "^1.8.19"
6
+ "lodash-es": "^4.18.1",
7
+ "pino": "^10.3.1",
8
+ "wsemi": "^1.8.51"
9
9
  },
10
10
  "devDependencies": {
11
- "w-package-tools": "^1.1.2"
11
+ "w-package-tools": "^1.1.5"
12
12
  },
13
13
  "scripts": {
14
14
  "test": "mocha --parallel --timeout 60000",
package/src/WSyslog.mjs CHANGED
@@ -1,7 +1,12 @@
1
+ import fs from 'fs'
1
2
  import path from 'path'
2
3
  import { fileURLToPath } from 'url'
3
4
  import get from 'lodash-es/get.js'
5
+ import evem from 'wsemi/src/evem.mjs'
4
6
  import isestr from 'wsemi/src/isestr.mjs'
7
+ import ispint from 'wsemi/src/ispint.mjs'
8
+ import cint from 'wsemi/src/cint.mjs'
9
+ import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
5
10
  import pino from 'pino'
6
11
 
7
12
 
@@ -17,7 +22,9 @@ let __dirname = path.dirname(__filename)
17
22
  * @param {Object} [opt={}] 輸入設定物件,預設{}
18
23
  * @param {String} [opt.fdLog='./_logs'] 輸入儲存log資料夾字串,預設'./_logs'
19
24
  * @param {String} [opt.interval='day'] 輸入儲存log時分檔模式字串,可選'day'、'hr',分別代表每日或每時分檔,預設'day'
20
- * @returns {Object} 回傳log物件,提供infowarn、error紀錄函數
25
+ * @param {String} [opt.timeZone=null] 輸入時區字串(IANA格式如'Asia/Taipei''UTC'),用於決定切檔邊界與檔名,預設null代表使用系統時區
26
+ * @param {Number} [opt.numKeep] 輸入保留log檔案數量整數,超出最舊檔將被自動刪除,預設於day模式為365、hr模式為365*24(約等同保留一年)
27
+ * @returns {Object} 回傳log物件,提供fatal、error、warn、info、debug、trace紀錄函數,cleanLogs手動清理函數,及clear清理計時器函數
21
28
  * @example
22
29
  * import fs from 'fs'
23
30
  * import _ from 'lodash-es'
@@ -47,6 +54,12 @@ let __dirname = path.dirname(__filename)
47
54
  */
48
55
  function WSyslog(opt = {}) {
49
56
 
57
+ //ev
58
+ let ev = evem()
59
+
60
+ //timeZone, 'Asia/Taipei'
61
+ let timeZone = get(opt, 'timeZone', null)
62
+
50
63
  //fdLog
51
64
  let fdLog = get(opt, 'fdLog')
52
65
  if (!isestr(fdLog)) {
@@ -59,6 +72,18 @@ function WSyslog(opt = {}) {
59
72
  interval = 'day'
60
73
  }
61
74
 
75
+ //numKeep
76
+ let numKeep = get(opt, 'numKeep')
77
+ if (!ispint(numKeep)) {
78
+ if (interval === 'day') {
79
+ numKeep = 365
80
+ }
81
+ else {
82
+ numKeep = 365 * 24
83
+ }
84
+ }
85
+ numKeep = cint(numKeep)
86
+
62
87
  // 建立 logger,使用自訂 transport
63
88
  let transport = pino.transport({
64
89
  targets: [
@@ -77,6 +102,7 @@ function WSyslog(opt = {}) {
77
102
  options: {
78
103
  interval,
79
104
  fdLog,
105
+ timeZone,
80
106
  },
81
107
 
82
108
  },
@@ -87,7 +113,82 @@ function WSyslog(opt = {}) {
87
113
  let log = pino(transport)
88
114
  // console.log('log', log)
89
115
 
90
- return log
116
+ //cleanLogs, 依numKeep保留最新N個檔, 超出的最舊檔刪除
117
+ let cleanLogs = () => {
118
+ try {
119
+ if (!fsIsFolder(fdLog)) {
120
+ return
121
+ }
122
+ //pattern, day模式為'YYYY-MM-DD.log', hr模式為'YYYY-MM-DDTHH.log'
123
+ let pattern = (interval === 'hr')
124
+ ? /^\d{4}-\d{2}-\d{2}T\d{2}\.log$/
125
+ : /^\d{4}-\d{2}-\d{2}\.log$/
126
+ let files = fs.readdirSync(fdLog).filter((fn) => pattern.test(fn))
127
+ //ISO日期格式字典序等同時間序, 排序後最舊在前
128
+ files.sort()
129
+ let nDel = files.length - numKeep
130
+ if (nDel <= 0) {
131
+ return
132
+ }
133
+ let toDelete = files.slice(0, nDel)
134
+ for (let fn of toDelete) {
135
+ let fp = path.join(fdLog, fn)
136
+ try {
137
+ fs.unlinkSync(fp)
138
+ ev.emit('delete', { fp, fn })
139
+ }
140
+ catch (err) {
141
+ //容忍其他process同時刪除或檔案被佔用之情況
142
+ }
143
+ }
144
+ }
145
+ catch (err) {
146
+ console.error('WSyslog cleanLogs error:', err)
147
+ }
148
+ }
149
+
150
+ //啟動時立刻清理一次, 確保短命腳本也能執行清理
151
+ cleanLogs()
152
+
153
+ //之後每小時清理一次, 足以跟上day/hr切檔節奏
154
+ let t = setInterval(cleanLogs, 60 * 60 * 1000)
155
+ //unref不阻塞process結束
156
+ t.unref()
157
+
158
+ //clear
159
+ let clear = () => {
160
+ clearInterval(t)
161
+ }
162
+
163
+ //wrap, 包裝pino各等級method, 於寫log前先觸發'log'事件, 使外部可監聽進行console輸出或轉送其他記錄器
164
+ // fatal: 60
165
+ // error: 50
166
+ // warn: 40
167
+ // info: 30
168
+ // debug: 20
169
+ // trace: 10
170
+ let wrap = (level) => {
171
+ let fn = log[level].bind(log)
172
+ return (...args) => {
173
+ try {
174
+ ev.emit(level, { args })
175
+ }
176
+ catch (err) {
177
+ //容忍listener內部錯誤, 避免影響pino寫log
178
+ }
179
+ fn(...args)
180
+ }
181
+ }
182
+ ev.fatal = wrap('fatal')
183
+ ev.error = wrap('error')
184
+ ev.warn = wrap('warn')
185
+ ev.info = wrap('info')
186
+ ev.debug = wrap('debug')
187
+ ev.trace = wrap('trace')
188
+ ev.cleanLogs = cleanLogs
189
+ ev.clear = clear
190
+
191
+ return ev
91
192
  }
92
193
 
93
194
 
package/src/formatter.mjs CHANGED
@@ -2,9 +2,16 @@ import fs from 'fs'
2
2
  import path from 'path'
3
3
  import { Writable } from 'stream'
4
4
  import ot from 'dayjs'
5
+ import utc from 'dayjs/plugin/utc.js'
6
+ import timezone from 'dayjs/plugin/timezone.js'
5
7
  import get from 'lodash-es/get.js'
6
8
  import isestr from 'wsemi/src/isestr.mjs'
7
9
  import strleft from 'wsemi/src/strleft.mjs'
10
+ import fsCreateFolder from 'wsemi/src/fsCreateFolder.mjs'
11
+
12
+
13
+ ot.extend(utc)
14
+ ot.extend(timezone)
8
15
 
9
16
 
10
17
  async function formatter(opt) {
@@ -22,17 +29,19 @@ async function formatter(opt) {
22
29
  interval = 'day'
23
30
  }
24
31
 
25
- // 建立 logs 資料夾
26
- if (!fs.existsSync(fdLog)) {
27
- fs.mkdirSync(fdLog, { recursive: true })
28
- }
32
+ //timeZone, IANA時區字串如'Asia/Taipei', 為null時使用系統時區
33
+ let timeZone = get(opt, 'timeZone', null)
34
+
35
+ //建立logs資料夾, 已存在則視為成功, 不存在則自動建立
36
+ fsCreateFolder(fdLog)
29
37
 
30
38
  let streamCurr = null
31
39
  let keyCurr = ''
32
40
 
33
41
  let getTimeKey = () => {
34
- // let d = new Date()
35
- let t = ot().format('YYYY-MM-DDTHH:mm:ss.SSSZ')
42
+ //依timeZone決定時區基準, 確保跨機器切檔邊界與檔名一致
43
+ let d = isestr(timeZone) ? ot().tz(timeZone) : ot()
44
+ let t = d.format('YYYY-MM-DDTHH:mm:ss.SSSZ')
36
45
  if (interval === 'hr') {
37
46
  // return d.toISOString().slice(0, 13).replace(/:/g, '-') //例如2025-07-06T14
38
47
  t = strleft(t, 13)
@@ -47,4 +47,11 @@ describe('WSyslog', function() {
47
47
  assert.strict.deepEqual(r, rr)
48
48
  })
49
49
 
50
+ after(async function() {
51
+ //稍候pino worker flush, 避免Windows上EBUSY
52
+ await w.delay(200)
53
+ //完全移除測試資料夾, 不留空殼
54
+ w.fsDeleteFolder('./_logs')
55
+ })
56
+
50
57
  })