watchfix 0.3.0 → 0.4.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/dist/cli/commands/init.js +13 -0
- package/dist/config/schema.d.ts +227 -36
- package/dist/config/schema.js +18 -2
- package/dist/watcher/sources/file.d.ts +3 -0
- package/dist/watcher/sources/file.js +34 -1
- package/dist/watcher/sources/ndjson.d.ts +16 -0
- package/dist/watcher/sources/ndjson.js +108 -0
- package/dist/watcher/sources/types.d.ts +8 -0
- package/package.json +1 -1
|
@@ -27,6 +27,19 @@ logs:
|
|
|
27
27
|
type: file
|
|
28
28
|
path: ./logs/app.log
|
|
29
29
|
|
|
30
|
+
# Example NDJSON source (for structured JSON logs like Pino, Bunyan, Winston):
|
|
31
|
+
# - name: app-json
|
|
32
|
+
# type: file
|
|
33
|
+
# path: ./logs/app.ndjson
|
|
34
|
+
# format: ndjson
|
|
35
|
+
# ndjson:
|
|
36
|
+
# messageField: msg # Required: field containing log message
|
|
37
|
+
# timestampField: time # Optional: field with timestamp
|
|
38
|
+
# levelField: level # Optional: field with log level
|
|
39
|
+
# levelFilter: # Optional: only process these levels
|
|
40
|
+
# - error
|
|
41
|
+
# - fatal
|
|
42
|
+
|
|
30
43
|
# Example docker source:
|
|
31
44
|
# - name: api
|
|
32
45
|
# type: docker
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -1,29 +1,76 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
declare const durationSchema: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
|
|
3
|
+
declare const ndjsonConfigSchema: z.ZodObject<{
|
|
4
|
+
messageField: z.ZodString;
|
|
5
|
+
timestampField: z.ZodOptional<z.ZodString>;
|
|
6
|
+
levelField: z.ZodOptional<z.ZodString>;
|
|
7
|
+
levelFilter: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
8
|
+
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
messageField: string;
|
|
10
|
+
timestampField?: string | undefined;
|
|
11
|
+
levelField?: string | undefined;
|
|
12
|
+
levelFilter?: string[] | undefined;
|
|
13
|
+
}, {
|
|
14
|
+
messageField: string;
|
|
15
|
+
timestampField?: string | undefined;
|
|
16
|
+
levelField?: string | undefined;
|
|
17
|
+
levelFilter?: string[] | undefined;
|
|
18
|
+
}>;
|
|
3
19
|
declare const fileSourceSchema: z.ZodObject<{
|
|
4
20
|
name: z.ZodString;
|
|
5
21
|
type: z.ZodLiteral<"file">;
|
|
6
22
|
path: z.ZodString;
|
|
23
|
+
format: z.ZodOptional<z.ZodEnum<["text", "ndjson"]>>;
|
|
24
|
+
ndjson: z.ZodOptional<z.ZodObject<{
|
|
25
|
+
messageField: z.ZodString;
|
|
26
|
+
timestampField: z.ZodOptional<z.ZodString>;
|
|
27
|
+
levelField: z.ZodOptional<z.ZodString>;
|
|
28
|
+
levelFilter: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
29
|
+
}, "strip", z.ZodTypeAny, {
|
|
30
|
+
messageField: string;
|
|
31
|
+
timestampField?: string | undefined;
|
|
32
|
+
levelField?: string | undefined;
|
|
33
|
+
levelFilter?: string[] | undefined;
|
|
34
|
+
}, {
|
|
35
|
+
messageField: string;
|
|
36
|
+
timestampField?: string | undefined;
|
|
37
|
+
levelField?: string | undefined;
|
|
38
|
+
levelFilter?: string[] | undefined;
|
|
39
|
+
}>>;
|
|
7
40
|
}, "strip", z.ZodTypeAny, {
|
|
8
41
|
path: string;
|
|
9
|
-
name: string;
|
|
10
42
|
type: "file";
|
|
43
|
+
name: string;
|
|
44
|
+
ndjson?: {
|
|
45
|
+
messageField: string;
|
|
46
|
+
timestampField?: string | undefined;
|
|
47
|
+
levelField?: string | undefined;
|
|
48
|
+
levelFilter?: string[] | undefined;
|
|
49
|
+
} | undefined;
|
|
50
|
+
format?: "text" | "ndjson" | undefined;
|
|
11
51
|
}, {
|
|
12
52
|
path: string;
|
|
13
|
-
name: string;
|
|
14
53
|
type: "file";
|
|
54
|
+
name: string;
|
|
55
|
+
ndjson?: {
|
|
56
|
+
messageField: string;
|
|
57
|
+
timestampField?: string | undefined;
|
|
58
|
+
levelField?: string | undefined;
|
|
59
|
+
levelFilter?: string[] | undefined;
|
|
60
|
+
} | undefined;
|
|
61
|
+
format?: "text" | "ndjson" | undefined;
|
|
15
62
|
}>;
|
|
16
63
|
declare const dockerSourceSchema: z.ZodObject<{
|
|
17
64
|
name: z.ZodString;
|
|
18
65
|
type: z.ZodLiteral<"docker">;
|
|
19
66
|
container: z.ZodString;
|
|
20
67
|
}, "strip", z.ZodTypeAny, {
|
|
21
|
-
name: string;
|
|
22
68
|
type: "docker";
|
|
69
|
+
name: string;
|
|
23
70
|
container: string;
|
|
24
71
|
}, {
|
|
25
|
-
name: string;
|
|
26
72
|
type: "docker";
|
|
73
|
+
name: string;
|
|
27
74
|
container: string;
|
|
28
75
|
}>;
|
|
29
76
|
declare const commandSourceSchema: z.ZodObject<{
|
|
@@ -32,13 +79,13 @@ declare const commandSourceSchema: z.ZodObject<{
|
|
|
32
79
|
run: z.ZodString;
|
|
33
80
|
interval: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
|
|
34
81
|
}, "strip", z.ZodTypeAny, {
|
|
35
|
-
name: string;
|
|
36
82
|
type: "command";
|
|
83
|
+
name: string;
|
|
37
84
|
run: string;
|
|
38
85
|
interval: string;
|
|
39
86
|
}, {
|
|
40
|
-
name: string;
|
|
41
87
|
type: "command";
|
|
88
|
+
name: string;
|
|
42
89
|
run: string;
|
|
43
90
|
interval: string;
|
|
44
91
|
}>;
|
|
@@ -46,25 +93,56 @@ declare const logSourceSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
|
46
93
|
name: z.ZodString;
|
|
47
94
|
type: z.ZodLiteral<"file">;
|
|
48
95
|
path: z.ZodString;
|
|
96
|
+
format: z.ZodOptional<z.ZodEnum<["text", "ndjson"]>>;
|
|
97
|
+
ndjson: z.ZodOptional<z.ZodObject<{
|
|
98
|
+
messageField: z.ZodString;
|
|
99
|
+
timestampField: z.ZodOptional<z.ZodString>;
|
|
100
|
+
levelField: z.ZodOptional<z.ZodString>;
|
|
101
|
+
levelFilter: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
102
|
+
}, "strip", z.ZodTypeAny, {
|
|
103
|
+
messageField: string;
|
|
104
|
+
timestampField?: string | undefined;
|
|
105
|
+
levelField?: string | undefined;
|
|
106
|
+
levelFilter?: string[] | undefined;
|
|
107
|
+
}, {
|
|
108
|
+
messageField: string;
|
|
109
|
+
timestampField?: string | undefined;
|
|
110
|
+
levelField?: string | undefined;
|
|
111
|
+
levelFilter?: string[] | undefined;
|
|
112
|
+
}>>;
|
|
49
113
|
}, "strip", z.ZodTypeAny, {
|
|
50
114
|
path: string;
|
|
51
|
-
name: string;
|
|
52
115
|
type: "file";
|
|
116
|
+
name: string;
|
|
117
|
+
ndjson?: {
|
|
118
|
+
messageField: string;
|
|
119
|
+
timestampField?: string | undefined;
|
|
120
|
+
levelField?: string | undefined;
|
|
121
|
+
levelFilter?: string[] | undefined;
|
|
122
|
+
} | undefined;
|
|
123
|
+
format?: "text" | "ndjson" | undefined;
|
|
53
124
|
}, {
|
|
54
125
|
path: string;
|
|
55
|
-
name: string;
|
|
56
126
|
type: "file";
|
|
127
|
+
name: string;
|
|
128
|
+
ndjson?: {
|
|
129
|
+
messageField: string;
|
|
130
|
+
timestampField?: string | undefined;
|
|
131
|
+
levelField?: string | undefined;
|
|
132
|
+
levelFilter?: string[] | undefined;
|
|
133
|
+
} | undefined;
|
|
134
|
+
format?: "text" | "ndjson" | undefined;
|
|
57
135
|
}>, z.ZodObject<{
|
|
58
136
|
name: z.ZodString;
|
|
59
137
|
type: z.ZodLiteral<"docker">;
|
|
60
138
|
container: z.ZodString;
|
|
61
139
|
}, "strip", z.ZodTypeAny, {
|
|
62
|
-
name: string;
|
|
63
140
|
type: "docker";
|
|
141
|
+
name: string;
|
|
64
142
|
container: string;
|
|
65
143
|
}, {
|
|
66
|
-
name: string;
|
|
67
144
|
type: "docker";
|
|
145
|
+
name: string;
|
|
68
146
|
container: string;
|
|
69
147
|
}>, z.ZodObject<{
|
|
70
148
|
name: z.ZodString;
|
|
@@ -72,13 +150,13 @@ declare const logSourceSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
|
72
150
|
run: z.ZodString;
|
|
73
151
|
interval: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
|
|
74
152
|
}, "strip", z.ZodTypeAny, {
|
|
75
|
-
name: string;
|
|
76
153
|
type: "command";
|
|
154
|
+
name: string;
|
|
77
155
|
run: string;
|
|
78
156
|
interval: string;
|
|
79
157
|
}, {
|
|
80
|
-
name: string;
|
|
81
158
|
type: "command";
|
|
159
|
+
name: string;
|
|
82
160
|
run: string;
|
|
83
161
|
interval: string;
|
|
84
162
|
}>]>;
|
|
@@ -117,29 +195,60 @@ declare const configSchema: z.ZodObject<{
|
|
|
117
195
|
stderr_is_progress?: boolean | undefined;
|
|
118
196
|
}>;
|
|
119
197
|
logs: z.ZodObject<{
|
|
120
|
-
sources: z.ZodEffects<z.ZodArray<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
198
|
+
sources: z.ZodEffects<z.ZodEffects<z.ZodArray<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
121
199
|
name: z.ZodString;
|
|
122
200
|
type: z.ZodLiteral<"file">;
|
|
123
201
|
path: z.ZodString;
|
|
202
|
+
format: z.ZodOptional<z.ZodEnum<["text", "ndjson"]>>;
|
|
203
|
+
ndjson: z.ZodOptional<z.ZodObject<{
|
|
204
|
+
messageField: z.ZodString;
|
|
205
|
+
timestampField: z.ZodOptional<z.ZodString>;
|
|
206
|
+
levelField: z.ZodOptional<z.ZodString>;
|
|
207
|
+
levelFilter: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
208
|
+
}, "strip", z.ZodTypeAny, {
|
|
209
|
+
messageField: string;
|
|
210
|
+
timestampField?: string | undefined;
|
|
211
|
+
levelField?: string | undefined;
|
|
212
|
+
levelFilter?: string[] | undefined;
|
|
213
|
+
}, {
|
|
214
|
+
messageField: string;
|
|
215
|
+
timestampField?: string | undefined;
|
|
216
|
+
levelField?: string | undefined;
|
|
217
|
+
levelFilter?: string[] | undefined;
|
|
218
|
+
}>>;
|
|
124
219
|
}, "strip", z.ZodTypeAny, {
|
|
125
220
|
path: string;
|
|
126
|
-
name: string;
|
|
127
221
|
type: "file";
|
|
222
|
+
name: string;
|
|
223
|
+
ndjson?: {
|
|
224
|
+
messageField: string;
|
|
225
|
+
timestampField?: string | undefined;
|
|
226
|
+
levelField?: string | undefined;
|
|
227
|
+
levelFilter?: string[] | undefined;
|
|
228
|
+
} | undefined;
|
|
229
|
+
format?: "text" | "ndjson" | undefined;
|
|
128
230
|
}, {
|
|
129
231
|
path: string;
|
|
130
|
-
name: string;
|
|
131
232
|
type: "file";
|
|
233
|
+
name: string;
|
|
234
|
+
ndjson?: {
|
|
235
|
+
messageField: string;
|
|
236
|
+
timestampField?: string | undefined;
|
|
237
|
+
levelField?: string | undefined;
|
|
238
|
+
levelFilter?: string[] | undefined;
|
|
239
|
+
} | undefined;
|
|
240
|
+
format?: "text" | "ndjson" | undefined;
|
|
132
241
|
}>, z.ZodObject<{
|
|
133
242
|
name: z.ZodString;
|
|
134
243
|
type: z.ZodLiteral<"docker">;
|
|
135
244
|
container: z.ZodString;
|
|
136
245
|
}, "strip", z.ZodTypeAny, {
|
|
137
|
-
name: string;
|
|
138
246
|
type: "docker";
|
|
247
|
+
name: string;
|
|
139
248
|
container: string;
|
|
140
249
|
}, {
|
|
141
|
-
name: string;
|
|
142
250
|
type: "docker";
|
|
251
|
+
name: string;
|
|
143
252
|
container: string;
|
|
144
253
|
}>, z.ZodObject<{
|
|
145
254
|
name: z.ZodString;
|
|
@@ -147,39 +256,93 @@ declare const configSchema: z.ZodObject<{
|
|
|
147
256
|
run: z.ZodString;
|
|
148
257
|
interval: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
|
|
149
258
|
}, "strip", z.ZodTypeAny, {
|
|
150
|
-
name: string;
|
|
151
259
|
type: "command";
|
|
260
|
+
name: string;
|
|
152
261
|
run: string;
|
|
153
262
|
interval: string;
|
|
154
263
|
}, {
|
|
155
|
-
name: string;
|
|
156
264
|
type: "command";
|
|
265
|
+
name: string;
|
|
157
266
|
run: string;
|
|
158
267
|
interval: string;
|
|
159
268
|
}>]>, "many">, ({
|
|
160
269
|
path: string;
|
|
161
|
-
name: string;
|
|
162
270
|
type: "file";
|
|
163
|
-
} | {
|
|
164
271
|
name: string;
|
|
272
|
+
ndjson?: {
|
|
273
|
+
messageField: string;
|
|
274
|
+
timestampField?: string | undefined;
|
|
275
|
+
levelField?: string | undefined;
|
|
276
|
+
levelFilter?: string[] | undefined;
|
|
277
|
+
} | undefined;
|
|
278
|
+
format?: "text" | "ndjson" | undefined;
|
|
279
|
+
} | {
|
|
165
280
|
type: "docker";
|
|
281
|
+
name: string;
|
|
166
282
|
container: string;
|
|
167
283
|
} | {
|
|
168
|
-
name: string;
|
|
169
284
|
type: "command";
|
|
285
|
+
name: string;
|
|
170
286
|
run: string;
|
|
171
287
|
interval: string;
|
|
172
288
|
})[], ({
|
|
173
289
|
path: string;
|
|
174
|
-
name: string;
|
|
175
290
|
type: "file";
|
|
291
|
+
name: string;
|
|
292
|
+
ndjson?: {
|
|
293
|
+
messageField: string;
|
|
294
|
+
timestampField?: string | undefined;
|
|
295
|
+
levelField?: string | undefined;
|
|
296
|
+
levelFilter?: string[] | undefined;
|
|
297
|
+
} | undefined;
|
|
298
|
+
format?: "text" | "ndjson" | undefined;
|
|
299
|
+
} | {
|
|
300
|
+
type: "docker";
|
|
301
|
+
name: string;
|
|
302
|
+
container: string;
|
|
176
303
|
} | {
|
|
304
|
+
type: "command";
|
|
305
|
+
name: string;
|
|
306
|
+
run: string;
|
|
307
|
+
interval: string;
|
|
308
|
+
})[]>, ({
|
|
309
|
+
path: string;
|
|
310
|
+
type: "file";
|
|
177
311
|
name: string;
|
|
312
|
+
ndjson?: {
|
|
313
|
+
messageField: string;
|
|
314
|
+
timestampField?: string | undefined;
|
|
315
|
+
levelField?: string | undefined;
|
|
316
|
+
levelFilter?: string[] | undefined;
|
|
317
|
+
} | undefined;
|
|
318
|
+
format?: "text" | "ndjson" | undefined;
|
|
319
|
+
} | {
|
|
178
320
|
type: "docker";
|
|
321
|
+
name: string;
|
|
179
322
|
container: string;
|
|
180
323
|
} | {
|
|
324
|
+
type: "command";
|
|
181
325
|
name: string;
|
|
326
|
+
run: string;
|
|
327
|
+
interval: string;
|
|
328
|
+
})[], ({
|
|
329
|
+
path: string;
|
|
330
|
+
type: "file";
|
|
331
|
+
name: string;
|
|
332
|
+
ndjson?: {
|
|
333
|
+
messageField: string;
|
|
334
|
+
timestampField?: string | undefined;
|
|
335
|
+
levelField?: string | undefined;
|
|
336
|
+
levelFilter?: string[] | undefined;
|
|
337
|
+
} | undefined;
|
|
338
|
+
format?: "text" | "ndjson" | undefined;
|
|
339
|
+
} | {
|
|
340
|
+
type: "docker";
|
|
341
|
+
name: string;
|
|
342
|
+
container: string;
|
|
343
|
+
} | {
|
|
182
344
|
type: "command";
|
|
345
|
+
name: string;
|
|
183
346
|
run: string;
|
|
184
347
|
interval: string;
|
|
185
348
|
})[]>;
|
|
@@ -189,15 +352,22 @@ declare const configSchema: z.ZodObject<{
|
|
|
189
352
|
}, "strip", z.ZodTypeAny, {
|
|
190
353
|
sources: ({
|
|
191
354
|
path: string;
|
|
192
|
-
name: string;
|
|
193
355
|
type: "file";
|
|
194
|
-
} | {
|
|
195
356
|
name: string;
|
|
357
|
+
ndjson?: {
|
|
358
|
+
messageField: string;
|
|
359
|
+
timestampField?: string | undefined;
|
|
360
|
+
levelField?: string | undefined;
|
|
361
|
+
levelFilter?: string[] | undefined;
|
|
362
|
+
} | undefined;
|
|
363
|
+
format?: "text" | "ndjson" | undefined;
|
|
364
|
+
} | {
|
|
196
365
|
type: "docker";
|
|
366
|
+
name: string;
|
|
197
367
|
container: string;
|
|
198
368
|
} | {
|
|
199
|
-
name: string;
|
|
200
369
|
type: "command";
|
|
370
|
+
name: string;
|
|
201
371
|
run: string;
|
|
202
372
|
interval: string;
|
|
203
373
|
})[];
|
|
@@ -207,15 +377,22 @@ declare const configSchema: z.ZodObject<{
|
|
|
207
377
|
}, {
|
|
208
378
|
sources: ({
|
|
209
379
|
path: string;
|
|
210
|
-
name: string;
|
|
211
380
|
type: "file";
|
|
212
|
-
} | {
|
|
213
381
|
name: string;
|
|
382
|
+
ndjson?: {
|
|
383
|
+
messageField: string;
|
|
384
|
+
timestampField?: string | undefined;
|
|
385
|
+
levelField?: string | undefined;
|
|
386
|
+
levelFilter?: string[] | undefined;
|
|
387
|
+
} | undefined;
|
|
388
|
+
format?: "text" | "ndjson" | undefined;
|
|
389
|
+
} | {
|
|
214
390
|
type: "docker";
|
|
391
|
+
name: string;
|
|
215
392
|
container: string;
|
|
216
393
|
} | {
|
|
217
|
-
name: string;
|
|
218
394
|
type: "command";
|
|
395
|
+
name: string;
|
|
219
396
|
run: string;
|
|
220
397
|
interval: string;
|
|
221
398
|
})[];
|
|
@@ -295,15 +472,22 @@ declare const configSchema: z.ZodObject<{
|
|
|
295
472
|
logs: {
|
|
296
473
|
sources: ({
|
|
297
474
|
path: string;
|
|
298
|
-
name: string;
|
|
299
475
|
type: "file";
|
|
300
|
-
} | {
|
|
301
476
|
name: string;
|
|
477
|
+
ndjson?: {
|
|
478
|
+
messageField: string;
|
|
479
|
+
timestampField?: string | undefined;
|
|
480
|
+
levelField?: string | undefined;
|
|
481
|
+
levelFilter?: string[] | undefined;
|
|
482
|
+
} | undefined;
|
|
483
|
+
format?: "text" | "ndjson" | undefined;
|
|
484
|
+
} | {
|
|
302
485
|
type: "docker";
|
|
486
|
+
name: string;
|
|
303
487
|
container: string;
|
|
304
488
|
} | {
|
|
305
|
-
name: string;
|
|
306
489
|
type: "command";
|
|
490
|
+
name: string;
|
|
307
491
|
run: string;
|
|
308
492
|
interval: string;
|
|
309
493
|
})[];
|
|
@@ -349,15 +533,22 @@ declare const configSchema: z.ZodObject<{
|
|
|
349
533
|
logs: {
|
|
350
534
|
sources: ({
|
|
351
535
|
path: string;
|
|
352
|
-
name: string;
|
|
353
536
|
type: "file";
|
|
354
|
-
} | {
|
|
355
537
|
name: string;
|
|
538
|
+
ndjson?: {
|
|
539
|
+
messageField: string;
|
|
540
|
+
timestampField?: string | undefined;
|
|
541
|
+
levelField?: string | undefined;
|
|
542
|
+
levelFilter?: string[] | undefined;
|
|
543
|
+
} | undefined;
|
|
544
|
+
format?: "text" | "ndjson" | undefined;
|
|
545
|
+
} | {
|
|
356
546
|
type: "docker";
|
|
547
|
+
name: string;
|
|
357
548
|
container: string;
|
|
358
549
|
} | {
|
|
359
|
-
name: string;
|
|
360
550
|
type: "command";
|
|
551
|
+
name: string;
|
|
361
552
|
run: string;
|
|
362
553
|
interval: string;
|
|
363
554
|
})[];
|
|
@@ -389,5 +580,5 @@ declare const configSchema: z.ZodObject<{
|
|
|
389
580
|
} | undefined;
|
|
390
581
|
}>;
|
|
391
582
|
type Config = z.infer<typeof configSchema>;
|
|
392
|
-
export { commandSourceSchema, configSchema, dockerSourceSchema, durationSchema, fileSourceSchema, logSourceSchema, patternSchema, };
|
|
583
|
+
export { commandSourceSchema, configSchema, dockerSourceSchema, durationSchema, fileSourceSchema, logSourceSchema, ndjsonConfigSchema, patternSchema, };
|
|
393
584
|
export type { Config };
|
package/dist/config/schema.js
CHANGED
|
@@ -17,10 +17,18 @@ const durationSchema = z
|
|
|
17
17
|
};
|
|
18
18
|
return amount * msByUnit[unit] <= MAX_DURATION_MS;
|
|
19
19
|
}, 'Duration cannot exceed 24 hours');
|
|
20
|
+
const ndjsonConfigSchema = z.object({
|
|
21
|
+
messageField: z.string().min(1),
|
|
22
|
+
timestampField: z.string().min(1).optional(),
|
|
23
|
+
levelField: z.string().min(1).optional(),
|
|
24
|
+
levelFilter: z.array(z.string().min(1)).optional(),
|
|
25
|
+
});
|
|
20
26
|
const fileSourceSchema = z.object({
|
|
21
27
|
name: z.string().min(1),
|
|
22
28
|
type: z.literal('file'),
|
|
23
29
|
path: z.string().min(1),
|
|
30
|
+
format: z.enum(['text', 'ndjson']).optional(),
|
|
31
|
+
ndjson: ndjsonConfigSchema.optional(),
|
|
24
32
|
});
|
|
25
33
|
const dockerSourceSchema = z.object({
|
|
26
34
|
name: z.string().min(1),
|
|
@@ -59,7 +67,15 @@ const configSchema = z.object({
|
|
|
59
67
|
.refine((sources) => {
|
|
60
68
|
const names = sources.map((source) => source.name);
|
|
61
69
|
return names.length === new Set(names).size;
|
|
62
|
-
}, { message: 'Log source names must be unique' })
|
|
70
|
+
}, { message: 'Log source names must be unique' })
|
|
71
|
+
.refine((sources) => {
|
|
72
|
+
return sources.every((source) => {
|
|
73
|
+
if (source.type === 'file' && source.format === 'ndjson') {
|
|
74
|
+
return source.ndjson !== undefined;
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
});
|
|
78
|
+
}, { message: 'ndjson config is required when format is "ndjson"' }),
|
|
63
79
|
context_lines_before: z.number().int().min(0).default(10),
|
|
64
80
|
context_lines_after: z.number().int().min(0).default(5),
|
|
65
81
|
max_line_buffer: z.number().int().min(100).default(10000),
|
|
@@ -102,4 +118,4 @@ const configSchema = z.object({
|
|
|
102
118
|
})
|
|
103
119
|
.default({}),
|
|
104
120
|
});
|
|
105
|
-
export { commandSourceSchema, configSchema, dockerSourceSchema, durationSchema, fileSourceSchema, logSourceSchema, patternSchema, };
|
|
121
|
+
export { commandSourceSchema, configSchema, dockerSourceSchema, durationSchema, fileSourceSchema, logSourceSchema, ndjsonConfigSchema, patternSchema, };
|
|
@@ -9,6 +9,8 @@ export declare class FileSource implements LogSource {
|
|
|
9
9
|
private readonly filePath;
|
|
10
10
|
private readonly logger;
|
|
11
11
|
private readonly emitter;
|
|
12
|
+
private readonly format;
|
|
13
|
+
private readonly ndjsonConfig;
|
|
12
14
|
private watcher;
|
|
13
15
|
private position;
|
|
14
16
|
private partialLine;
|
|
@@ -26,5 +28,6 @@ export declare class FileSource implements LogSource {
|
|
|
26
28
|
private readLoop;
|
|
27
29
|
private readNewLines;
|
|
28
30
|
private processChunk;
|
|
31
|
+
private handleLine;
|
|
29
32
|
}
|
|
30
33
|
export {};
|
|
@@ -3,11 +3,14 @@ import path from 'node:path';
|
|
|
3
3
|
import { EventEmitter } from 'node:events';
|
|
4
4
|
import chokidar from 'chokidar';
|
|
5
5
|
import { Logger } from '../../utils/logger.js';
|
|
6
|
+
import { parseNdjsonLine } from './ndjson.js';
|
|
6
7
|
export class FileSource {
|
|
7
8
|
config;
|
|
8
9
|
filePath;
|
|
9
10
|
logger;
|
|
10
11
|
emitter;
|
|
12
|
+
format;
|
|
13
|
+
ndjsonConfig;
|
|
11
14
|
watcher = null;
|
|
12
15
|
position = 0;
|
|
13
16
|
partialLine = '';
|
|
@@ -23,6 +26,8 @@ export class FileSource {
|
|
|
23
26
|
this.logger =
|
|
24
27
|
options?.logger ?? new Logger({ terminalEnabled: false, verbosity: 'normal' });
|
|
25
28
|
this.emitter = new EventEmitter();
|
|
29
|
+
this.format = config.format ?? 'text';
|
|
30
|
+
this.ndjsonConfig = config.ndjson ?? null;
|
|
26
31
|
}
|
|
27
32
|
async start() {
|
|
28
33
|
if (this.watcher) {
|
|
@@ -164,12 +169,40 @@ export class FileSource {
|
|
|
164
169
|
const lines = combined.split('\n');
|
|
165
170
|
this.partialLine = lines.pop() ?? '';
|
|
166
171
|
for (const rawLine of lines) {
|
|
167
|
-
|
|
172
|
+
this.handleLine(rawLine.replace(/\r$/, ''));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
handleLine(line) {
|
|
176
|
+
if (this.format !== 'ndjson' || !this.ndjsonConfig) {
|
|
168
177
|
this.emitter.emit('line', {
|
|
169
178
|
source: this.config.name,
|
|
170
179
|
line,
|
|
171
180
|
timestamp: new Date(),
|
|
172
181
|
});
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const result = parseNdjsonLine(line, this.ndjsonConfig);
|
|
185
|
+
if (result.success) {
|
|
186
|
+
this.emitter.emit('line', {
|
|
187
|
+
source: this.config.name,
|
|
188
|
+
line: result.data.message,
|
|
189
|
+
timestamp: result.data.timestamp,
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
// Handle different failure reasons
|
|
194
|
+
if (result.reason === 'filtered') {
|
|
195
|
+
// Line was filtered by level - silently skip
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (result.reason === 'missing_message') {
|
|
199
|
+
this.logger.debug(`NDJSON line missing message field "${this.ndjsonConfig.messageField}": ${line.slice(0, 100)}`);
|
|
173
200
|
}
|
|
201
|
+
// For parse errors or missing message, fall back to emitting raw line
|
|
202
|
+
this.emitter.emit('line', {
|
|
203
|
+
source: this.config.name,
|
|
204
|
+
line,
|
|
205
|
+
timestamp: new Date(),
|
|
206
|
+
});
|
|
174
207
|
}
|
|
175
208
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { NdjsonConfig } from './types.js';
|
|
2
|
+
export type ParsedNdjsonLine = {
|
|
3
|
+
message: string;
|
|
4
|
+
timestamp: Date;
|
|
5
|
+
};
|
|
6
|
+
export type NdjsonParseResult = {
|
|
7
|
+
success: true;
|
|
8
|
+
data: ParsedNdjsonLine;
|
|
9
|
+
} | {
|
|
10
|
+
success: false;
|
|
11
|
+
reason: 'parse_error' | 'missing_message' | 'filtered';
|
|
12
|
+
};
|
|
13
|
+
export declare function getNestedValue(obj: unknown, path: string): unknown;
|
|
14
|
+
export declare function parseTimestamp(value: unknown): Date | null;
|
|
15
|
+
export declare function shouldProcessLevel(value: unknown, levelFilter: string[] | undefined): boolean;
|
|
16
|
+
export declare function parseNdjsonLine(line: string, config: NdjsonConfig): NdjsonParseResult;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export function getNestedValue(obj, path) {
|
|
2
|
+
const parts = path.split('.');
|
|
3
|
+
let current = obj;
|
|
4
|
+
for (const part of parts) {
|
|
5
|
+
if (current === null || current === undefined) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
if (typeof current !== 'object') {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
current = current[part];
|
|
12
|
+
}
|
|
13
|
+
return current;
|
|
14
|
+
}
|
|
15
|
+
export function parseTimestamp(value) {
|
|
16
|
+
if (value === null || value === undefined) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
if (typeof value === 'string') {
|
|
20
|
+
const date = new Date(value);
|
|
21
|
+
if (!Number.isNaN(date.getTime())) {
|
|
22
|
+
return date;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
if (typeof value === 'number') {
|
|
27
|
+
// Handle Unix timestamps in seconds or milliseconds
|
|
28
|
+
// If the number is less than 10^12, treat as seconds; otherwise as milliseconds
|
|
29
|
+
const MS_THRESHOLD = 1e12;
|
|
30
|
+
const timestamp = value < MS_THRESHOLD ? value * 1000 : value;
|
|
31
|
+
const date = new Date(timestamp);
|
|
32
|
+
if (!Number.isNaN(date.getTime())) {
|
|
33
|
+
return date;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
export function shouldProcessLevel(value, levelFilter) {
|
|
40
|
+
if (!levelFilter || levelFilter.length === 0) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (value === null || value === undefined) {
|
|
44
|
+
// If no level is present but filter is configured, skip the line
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
// Handle string levels (most common)
|
|
48
|
+
if (typeof value === 'string') {
|
|
49
|
+
const lowerValue = value.toLowerCase();
|
|
50
|
+
return levelFilter.some((filter) => filter.toLowerCase() === lowerValue);
|
|
51
|
+
}
|
|
52
|
+
// Handle numeric levels (Bunyan style: 10=trace, 20=debug, 30=info, 40=warn, 50=error, 60=fatal)
|
|
53
|
+
if (typeof value === 'number') {
|
|
54
|
+
const bunyanLevels = {
|
|
55
|
+
10: 'trace',
|
|
56
|
+
20: 'debug',
|
|
57
|
+
30: 'info',
|
|
58
|
+
40: 'warn',
|
|
59
|
+
50: 'error',
|
|
60
|
+
60: 'fatal',
|
|
61
|
+
};
|
|
62
|
+
const levelName = bunyanLevels[value];
|
|
63
|
+
if (levelName) {
|
|
64
|
+
return levelFilter.some((filter) => filter.toLowerCase() === levelName.toLowerCase());
|
|
65
|
+
}
|
|
66
|
+
// Unknown numeric level - don't match
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
export function parseNdjsonLine(line, config) {
|
|
72
|
+
let parsed;
|
|
73
|
+
try {
|
|
74
|
+
parsed = JSON.parse(line);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return { success: false, reason: 'parse_error' };
|
|
78
|
+
}
|
|
79
|
+
if (parsed === null || typeof parsed !== 'object') {
|
|
80
|
+
return { success: false, reason: 'parse_error' };
|
|
81
|
+
}
|
|
82
|
+
// Check level filter first (before extracting message)
|
|
83
|
+
if (config.levelField) {
|
|
84
|
+
const levelValue = getNestedValue(parsed, config.levelField);
|
|
85
|
+
if (!shouldProcessLevel(levelValue, config.levelFilter)) {
|
|
86
|
+
return { success: false, reason: 'filtered' };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Extract message
|
|
90
|
+
const messageValue = getNestedValue(parsed, config.messageField);
|
|
91
|
+
if (messageValue === null || messageValue === undefined) {
|
|
92
|
+
return { success: false, reason: 'missing_message' };
|
|
93
|
+
}
|
|
94
|
+
const message = String(messageValue);
|
|
95
|
+
// Extract timestamp
|
|
96
|
+
let timestamp = new Date();
|
|
97
|
+
if (config.timestampField) {
|
|
98
|
+
const timestampValue = getNestedValue(parsed, config.timestampField);
|
|
99
|
+
const parsedTimestamp = parseTimestamp(timestampValue);
|
|
100
|
+
if (parsedTimestamp) {
|
|
101
|
+
timestamp = parsedTimestamp;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
success: true,
|
|
106
|
+
data: { message, timestamp },
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -3,10 +3,18 @@ export type LogEvent = {
|
|
|
3
3
|
line: string;
|
|
4
4
|
timestamp: Date;
|
|
5
5
|
};
|
|
6
|
+
export type NdjsonConfig = {
|
|
7
|
+
messageField: string;
|
|
8
|
+
timestampField?: string;
|
|
9
|
+
levelField?: string;
|
|
10
|
+
levelFilter?: string[];
|
|
11
|
+
};
|
|
6
12
|
export type FileSourceConfig = {
|
|
7
13
|
name: string;
|
|
8
14
|
type: 'file';
|
|
9
15
|
path: string;
|
|
16
|
+
format?: 'text' | 'ndjson';
|
|
17
|
+
ndjson?: NdjsonConfig;
|
|
10
18
|
};
|
|
11
19
|
export type DockerSourceConfig = {
|
|
12
20
|
name: string;
|