w-syslog 1.0.6 → 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.
@@ -0,0 +1,215 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import assert from 'assert'
4
+ import w from 'wsemi'
5
+ import WSyslog from '../src/WSyslog.mjs'
6
+
7
+
8
+ //獨立測試資料夾, 避免與basic.test.mjs在parallel模式下競用'./_logs'
9
+ let fdLog = './_logs_clean_test'
10
+ let fdNoExist = './_logs_clean_test_nonexistent'
11
+
12
+
13
+ function seedFiles(folder, names) {
14
+ //已存在則視為成功, 不存在則自動建立
15
+ w.fsCreateFolder(folder)
16
+ for (let fn of names) {
17
+ fs.writeFileSync(path.join(folder, fn), '')
18
+ }
19
+ }
20
+
21
+
22
+ function listSeed(folder, prefix) {
23
+ if (!w.fsIsFolder(folder)) {
24
+ return []
25
+ }
26
+ return fs.readdirSync(folder).filter((fn) => fn.startsWith(prefix)).sort()
27
+ }
28
+
29
+
30
+ describe('WSyslog cleanLogs', function() {
31
+
32
+ beforeEach(function() {
33
+ //每個test開始前完全移除舊資料夾, 從乾淨狀態開始
34
+ w.fsDeleteFolder(fdLog)
35
+ })
36
+
37
+ afterEach(async function() {
38
+ //稍候pino worker flush, 避免Windows上EBUSY
39
+ await w.delay(100)
40
+ //完全移除測試資料夾, 不留空殼
41
+ w.fsDeleteFolder(fdLog)
42
+ })
43
+
44
+ after(async function() {
45
+ //最終把'資料夾不存在時不崩'測試可能由pino formatter建立的資料夾也清掉
46
+ await w.delay(100)
47
+ w.fsDeleteFolder(fdNoExist)
48
+ })
49
+
50
+ it('day模式: 保留最新numKeep個檔, 最舊的被刪', function() {
51
+ seedFiles(fdLog, [
52
+ '2020-01-01.log',
53
+ '2020-01-02.log',
54
+ '2020-01-03.log',
55
+ '2020-01-04.log',
56
+ '2020-01-05.log',
57
+ ])
58
+
59
+ //init同步階段會跑一次cleanLogs(), 看到5個2020檔, numKeep=2, 刪3個最舊
60
+ let log = WSyslog({ fdLog, numKeep: 2, interval: 'day' })
61
+
62
+ let kept = listSeed(fdLog, '2020-')
63
+ assert.strict.deepEqual(kept, [
64
+ '2020-01-04.log',
65
+ '2020-01-05.log',
66
+ ])
67
+
68
+ log.clear()
69
+ })
70
+
71
+ it('當檔數小於等於numKeep時不刪任何檔', function() {
72
+ seedFiles(fdLog, [
73
+ '2020-01-01.log',
74
+ '2020-01-02.log',
75
+ ])
76
+
77
+ let log = WSyslog({ fdLog, numKeep: 5, interval: 'day' })
78
+ log.cleanLogs()
79
+
80
+ let kept = listSeed(fdLog, '2020-')
81
+ assert.strict.deepEqual(kept, [
82
+ '2020-01-01.log',
83
+ '2020-01-02.log',
84
+ ])
85
+
86
+ log.clear()
87
+ })
88
+
89
+ it('hr模式: 只匹配hr命名, 不誤刪day命名', function() {
90
+ seedFiles(fdLog, [
91
+ //day命名(不該被hr模式cleanLogs動到)
92
+ '2020-01-01.log',
93
+ '2020-01-02.log',
94
+ //hr命名
95
+ '2020-01-01T01.log',
96
+ '2020-01-01T02.log',
97
+ '2020-01-01T03.log',
98
+ '2020-01-01T04.log',
99
+ '2020-01-01T05.log',
100
+ ])
101
+
102
+ //numKeep=2, hr模式應只看hr檔, 5刪3, 留04, 05
103
+ let log = WSyslog({ fdLog, numKeep: 2, interval: 'hr' })
104
+ log.cleanLogs()
105
+
106
+ let dayFiles = listSeed(fdLog, '2020-').filter((fn) => /^\d{4}-\d{2}-\d{2}\.log$/.test(fn))
107
+ let hrFiles = listSeed(fdLog, '2020-').filter((fn) => /^\d{4}-\d{2}-\d{2}T\d{2}\.log$/.test(fn))
108
+
109
+ assert.strict.deepEqual(dayFiles, [
110
+ '2020-01-01.log',
111
+ '2020-01-02.log',
112
+ ])
113
+ assert.strict.deepEqual(hrFiles, [
114
+ '2020-01-01T04.log',
115
+ '2020-01-01T05.log',
116
+ ])
117
+
118
+ log.clear()
119
+ })
120
+
121
+ it('刪除時觸發delete事件, payload含fp與fn', function() {
122
+ //先啟動空資料夾的logger, 此時init的cleanLogs不會刪任何檔
123
+ let log = WSyslog({ fdLog, numKeep: 5, interval: 'hr' })
124
+
125
+ //listener在init之後掛上, 不會錯過後續手動觸發的事件
126
+ let deleted = []
127
+ log.on('delete', ({ fp, fn }) => {
128
+ deleted.push({ fp, fn })
129
+ })
130
+
131
+ //seed 8個未來日期的hr檔, 確保任何今日檔(若pino async建立)都會被視為最舊優先刪除
132
+ //如此過濾'2099-'前綴後, 必然看到T01-T03被刪, 不受今日檔存在與否影響
133
+ seedFiles(fdLog, [
134
+ '2099-01-01T01.log',
135
+ '2099-01-01T02.log',
136
+ '2099-01-01T03.log',
137
+ '2099-01-01T04.log',
138
+ '2099-01-01T05.log',
139
+ '2099-01-01T06.log',
140
+ '2099-01-01T07.log',
141
+ '2099-01-01T08.log',
142
+ ])
143
+
144
+ log.cleanLogs()
145
+
146
+ //過濾seed檔, 必為被刪的最舊3個
147
+ let names2099 = deleted
148
+ .map((d) => d.fn)
149
+ .filter((fn) => fn.startsWith('2099-'))
150
+ .sort()
151
+ assert.strict.deepEqual(names2099, [
152
+ '2099-01-01T01.log',
153
+ '2099-01-01T02.log',
154
+ '2099-01-01T03.log',
155
+ ])
156
+
157
+ //每筆事件的fp必為path.join(fdLog, fn)
158
+ for (let d of deleted) {
159
+ assert.strict.equal(d.fp, path.join(fdLog, d.fn))
160
+ }
161
+
162
+ log.clear()
163
+ })
164
+
165
+ it('資料夾不存在時不崩', function() {
166
+ //確保起始狀態為不存在
167
+ w.fsDeleteFolder(fdNoExist)
168
+
169
+ assert.doesNotThrow(() => {
170
+ let log = WSyslog({ fdLog: fdNoExist, numKeep: 5, interval: 'day' })
171
+ log.cleanLogs()
172
+ log.clear()
173
+ })
174
+ //pino formatter可能async建立資料夾, 由describe區塊的after hook統一清理
175
+ })
176
+
177
+ it('忽略不符regex命名的檔', function() {
178
+ seedFiles(fdLog, [
179
+ '2020-01-01.log',
180
+ '2020-01-02.log',
181
+ '2020-01-03.log',
182
+ //不符regex(注意cleanLogs regex僅檢查格式不檢查日期合法性, 故'2020-13-99.log'這類仍會匹配)
183
+ 'readme.txt',
184
+ 'app.config',
185
+ 'log.json',
186
+ '2020-1-1.log', //月份/日期僅1碼, 不符\d{2}
187
+ 'YYYY-MM-DD.log', //非數字
188
+ ])
189
+
190
+ let log = WSyslog({ fdLog, numKeep: 1, interval: 'day' })
191
+ log.cleanLogs()
192
+
193
+ //不符的檔全保留
194
+ let nonMatching = fs.readdirSync(fdLog).filter((fn) =>
195
+ ['readme.txt', 'app.config', 'log.json', '2020-1-1.log', 'YYYY-MM-DD.log'].includes(fn)
196
+ ).sort()
197
+ assert.strict.deepEqual(nonMatching, [
198
+ '2020-1-1.log',
199
+ 'YYYY-MM-DD.log',
200
+ 'app.config',
201
+ 'log.json',
202
+ 'readme.txt',
203
+ ])
204
+
205
+ //符合regex的: 3個2020檔, numKeep=1, 留03
206
+ let matching = fs.readdirSync(fdLog)
207
+ .filter((fn) => /^\d{4}-\d{2}-\d{2}\.log$/.test(fn))
208
+ .filter((fn) => fn.startsWith('2020-'))
209
+ .sort()
210
+ assert.strict.deepEqual(matching, ['2020-01-03.log'])
211
+
212
+ log.clear()
213
+ })
214
+
215
+ })
@@ -0,0 +1,89 @@
1
+ import fs from 'fs'
2
+ import assert from 'assert'
3
+ import w from 'wsemi'
4
+ import WSyslog from '../src/WSyslog.mjs'
5
+
6
+
7
+ //每個test各自獨立資料夾, 避免parallel/重跑干擾
8
+ let fdLogUtc = './_logs_tz_utc'
9
+ let fdLogTpe = './_logs_tz_tpe'
10
+
11
+
12
+ function readHrKey(folder) {
13
+ if (!w.fsIsFolder(folder)) {
14
+ return null
15
+ }
16
+ let files = fs.readdirSync(folder).filter((fn) => /^\d{4}-\d{2}-\d{2}T\d{2}\.log$/.test(fn))
17
+ if (files.length !== 1) {
18
+ return null
19
+ }
20
+ return files[0].replace('.log', '') //例如'2025-12-08T15'
21
+ }
22
+
23
+
24
+ describe('WSyslog timeZone', function() {
25
+
26
+ beforeEach(function() {
27
+ w.fsDeleteFolder(fdLogUtc)
28
+ w.fsDeleteFolder(fdLogTpe)
29
+ })
30
+
31
+ afterEach(async function() {
32
+ //稍候pino worker flush, 避免Windows上EBUSY
33
+ await w.delay(200)
34
+ w.fsDeleteFolder(fdLogUtc)
35
+ w.fsDeleteFolder(fdLogTpe)
36
+ })
37
+
38
+ it('timeZone="UTC" 寫log應使用UTC時區的小時作檔名 (模擬GitHub Actions UTC+0)', async function() {
39
+
40
+ let log = WSyslog({ fdLog: fdLogUtc, interval: 'hr', timeZone: 'UTC' })
41
+ log.info({ event: 'tz-utc', msg: 'check' })
42
+
43
+ await w.delay(2000) //等pino flush
44
+
45
+ let actualKey = readHrKey(fdLogUtc)
46
+ assert.ok(actualKey !== null, `應有1個hr log檔, 取得actualKey=${actualKey}`)
47
+
48
+ //內建Date.toISOString()永遠輸出UTC時區之ISO字串, 取前13碼即'YYYY-MM-DDTHH'
49
+ let utcKey = new Date().toISOString().slice(0, 13)
50
+ let utcKeyPrev = new Date(Date.now() - 60 * 60 * 1000).toISOString().slice(0, 13)
51
+
52
+ //容忍跨小時boundary: 寫入與assert之間若橫跨整點, 接受前一小時
53
+ assert.ok(
54
+ actualKey === utcKey || actualKey === utcKeyPrev,
55
+ `actualKey="${actualKey}" 應為UTC當前小時"${utcKey}"或前一小時"${utcKeyPrev}"`
56
+ )
57
+
58
+ log.clear()
59
+ })
60
+
61
+ it('timeZone="UTC" 與 "Asia/Taipei" (UTC+8) 寫log應產生不同小時檔名', async function() {
62
+
63
+ let logUtc = WSyslog({ fdLog: fdLogUtc, interval: 'hr', timeZone: 'UTC' })
64
+ let logTpe = WSyslog({ fdLog: fdLogTpe, interval: 'hr', timeZone: 'Asia/Taipei' })
65
+
66
+ //同步呼叫兩者, 縮短時間差以避免跨小時boundary影響
67
+ logUtc.info({ msg: 'x' })
68
+ logTpe.info({ msg: 'x' })
69
+
70
+ await w.delay(2000)
71
+
72
+ let keyUtc = readHrKey(fdLogUtc)
73
+ let keyTpe = readHrKey(fdLogTpe)
74
+
75
+ assert.ok(keyUtc !== null, `UTC側應有1個hr log檔, 取得keyUtc=${keyUtc}`)
76
+ assert.ok(keyTpe !== null, `Taipei側應有1個hr log檔, 取得keyTpe=${keyTpe}`)
77
+
78
+ //UTC+0與UTC+8必差8小時, 兩個小時檔名永不相等(此assert不論系統時區為UTC+8或UTC+0皆成立)
79
+ assert.notStrictEqual(
80
+ keyUtc,
81
+ keyTpe,
82
+ `UTC與Asia/Taipei檔名應不同: keyUtc="${keyUtc}", keyTpe="${keyTpe}"`
83
+ )
84
+
85
+ logUtc.clear()
86
+ logTpe.clear()
87
+ })
88
+
89
+ })
@@ -1,3 +0,0 @@
1
- {"level":30,"time":1751895983722,"pid":17068,"hostname":"DESKTOP-6R7USAO","event":"runner","msg":"start"}
2
- {"level":40,"time":1751895983723,"pid":17068,"hostname":"DESKTOP-6R7USAO","event":"monitor-memory","msg":"usage-high","ratio":85.4}
3
- {"level":50,"time":1751895983723,"pid":17068,"hostname":"DESKTOP-6R7USAO","event":"crash","msg":"db connection","code":500}