@unrdf/kgc-probe 26.4.2
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 +414 -0
- package/package.json +81 -0
- package/src/agents/index.mjs +1402 -0
- package/src/artifact.mjs +405 -0
- package/src/cli.mjs +932 -0
- package/src/config.mjs +115 -0
- package/src/guards.mjs +1213 -0
- package/src/index.mjs +347 -0
- package/src/merge.mjs +196 -0
- package/src/observation.mjs +193 -0
- package/src/orchestrator.mjs +315 -0
- package/src/probe.mjs +58 -0
- package/src/probes/CONCURRENCY-PROBE.md +256 -0
- package/src/probes/README.md +275 -0
- package/src/probes/concurrency.mjs +1175 -0
- package/src/probes/filesystem.mjs +731 -0
- package/src/probes/filesystem.test.mjs +244 -0
- package/src/probes/network.mjs +503 -0
- package/src/probes/performance.mjs +816 -0
- package/src/probes/persistence.mjs +785 -0
- package/src/probes/runtime.mjs +589 -0
- package/src/probes/tooling.mjs +454 -0
- package/src/probes/tooling.test.mjs +372 -0
- package/src/probes/verify-execution.mjs +131 -0
- package/src/probes/verify-guards.mjs +73 -0
- package/src/probes/wasm.mjs +715 -0
- package/src/receipt.mjs +197 -0
- package/src/receipts/index.mjs +813 -0
- package/src/reporter.example.mjs +223 -0
- package/src/reporter.mjs +555 -0
- package/src/reporters/markdown.mjs +355 -0
- package/src/reporters/rdf.mjs +383 -0
- package/src/storage/index.mjs +827 -0
- package/src/types.mjs +1028 -0
- package/src/utils/errors.mjs +397 -0
- package/src/utils/index.mjs +32 -0
- package/src/utils/logger.mjs +236 -0
- package/src/vocabulary.ttl +169 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview KGC Probe - Custom Error Classes
|
|
3
|
+
*
|
|
4
|
+
* Provides structured error types with:
|
|
5
|
+
* - Error codes for programmatic handling
|
|
6
|
+
* - Context information for debugging
|
|
7
|
+
* - Recovery suggestions for users
|
|
8
|
+
*
|
|
9
|
+
* @module @unrdf/kgc-probe/utils/errors
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Base error for KGC Probe operations
|
|
14
|
+
*
|
|
15
|
+
* @class ProbeError
|
|
16
|
+
* @extends Error
|
|
17
|
+
* @example
|
|
18
|
+
* throw new ProbeError('Operation failed', 'OP_FAILED', { operation: 'scan' });
|
|
19
|
+
*/
|
|
20
|
+
export class ProbeError extends Error {
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} message - Error message
|
|
23
|
+
* @param {string} [code='PROBE_ERROR'] - Error code
|
|
24
|
+
* @param {Object} [context] - Additional context
|
|
25
|
+
* @param {string} [recovery] - Recovery suggestion
|
|
26
|
+
*/
|
|
27
|
+
constructor(message, code = 'PROBE_ERROR', context = {}, recovery = undefined) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = 'ProbeError';
|
|
30
|
+
|
|
31
|
+
/** @type {string} */
|
|
32
|
+
this.code = code;
|
|
33
|
+
|
|
34
|
+
/** @type {Object} */
|
|
35
|
+
this.context = context;
|
|
36
|
+
|
|
37
|
+
/** @type {string | undefined} */
|
|
38
|
+
this.recovery = recovery;
|
|
39
|
+
|
|
40
|
+
/** @type {string} */
|
|
41
|
+
this.timestamp = new Date().toISOString();
|
|
42
|
+
|
|
43
|
+
// Capture stack trace
|
|
44
|
+
if (Error.captureStackTrace) {
|
|
45
|
+
Error.captureStackTrace(this, this.constructor);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Convert to JSON for logging
|
|
51
|
+
* @returns {Object}
|
|
52
|
+
*/
|
|
53
|
+
toJSON() {
|
|
54
|
+
return {
|
|
55
|
+
name: this.name,
|
|
56
|
+
code: this.code,
|
|
57
|
+
message: this.message,
|
|
58
|
+
context: this.context,
|
|
59
|
+
recovery: this.recovery,
|
|
60
|
+
timestamp: this.timestamp,
|
|
61
|
+
stack: this.stack
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Create from unknown error
|
|
67
|
+
* @param {unknown} error - Unknown error
|
|
68
|
+
* @param {string} [code] - Override code
|
|
69
|
+
* @returns {ProbeError}
|
|
70
|
+
*/
|
|
71
|
+
static from(error, code) {
|
|
72
|
+
if (error instanceof ProbeError) {
|
|
73
|
+
return error;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (error instanceof Error) {
|
|
77
|
+
return new ProbeError(error.message, code || 'UNKNOWN_ERROR', {
|
|
78
|
+
originalName: error.name,
|
|
79
|
+
originalStack: error.stack
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return new ProbeError(String(error), code || 'UNKNOWN_ERROR');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Error thrown when a guard blocks an operation
|
|
89
|
+
*
|
|
90
|
+
* @class GuardViolationError
|
|
91
|
+
* @extends ProbeError
|
|
92
|
+
* @example
|
|
93
|
+
* throw new GuardViolationError(
|
|
94
|
+
* 'Access to AWS credentials forbidden',
|
|
95
|
+
* 'G-H1-ENV-TOKEN',
|
|
96
|
+
* { variable: 'AWS_SECRET_KEY', pattern: 'AWS_*' },
|
|
97
|
+
* 'receipt-123'
|
|
98
|
+
* );
|
|
99
|
+
*/
|
|
100
|
+
export class GuardViolationError extends ProbeError {
|
|
101
|
+
/**
|
|
102
|
+
* @param {string} message - Error message
|
|
103
|
+
* @param {string} guardId - Guard that blocked the operation
|
|
104
|
+
* @param {Object} [context] - Additional context
|
|
105
|
+
* @param {string} [receiptId] - Denial receipt ID
|
|
106
|
+
*/
|
|
107
|
+
constructor(message, guardId, context = {}, receiptId = undefined) {
|
|
108
|
+
super(message, 'GUARD_VIOLATION', {
|
|
109
|
+
...context,
|
|
110
|
+
guardId,
|
|
111
|
+
receiptId
|
|
112
|
+
}, 'Check guard policy configuration or request access exemption');
|
|
113
|
+
|
|
114
|
+
this.name = 'GuardViolationError';
|
|
115
|
+
|
|
116
|
+
/** @type {string} */
|
|
117
|
+
this.guardId = guardId;
|
|
118
|
+
|
|
119
|
+
/** @type {string | undefined} */
|
|
120
|
+
this.receiptId = receiptId;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Error thrown when input validation fails
|
|
126
|
+
*
|
|
127
|
+
* @class ValidationError
|
|
128
|
+
* @extends ProbeError
|
|
129
|
+
* @example
|
|
130
|
+
* throw new ValidationError('Invalid universe ID', { field: 'universe_id', value: null });
|
|
131
|
+
*/
|
|
132
|
+
export class ValidationError extends ProbeError {
|
|
133
|
+
/**
|
|
134
|
+
* @param {string} message - Error message
|
|
135
|
+
* @param {Object} [context] - Validation context
|
|
136
|
+
* @param {string} [field] - Field that failed validation
|
|
137
|
+
*/
|
|
138
|
+
constructor(message, context = {}, field = undefined) {
|
|
139
|
+
super(message, 'VALIDATION_ERROR', {
|
|
140
|
+
...context,
|
|
141
|
+
field
|
|
142
|
+
}, 'Check input format and required fields');
|
|
143
|
+
|
|
144
|
+
this.name = 'ValidationError';
|
|
145
|
+
|
|
146
|
+
/** @type {string | undefined} */
|
|
147
|
+
this.field = field;
|
|
148
|
+
|
|
149
|
+
/** @type {any} */
|
|
150
|
+
this.issues = context.issues || [];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Create from Zod error
|
|
155
|
+
* @param {import('zod').ZodError} zodError - Zod validation error
|
|
156
|
+
* @returns {ValidationError}
|
|
157
|
+
*/
|
|
158
|
+
static fromZod(zodError) {
|
|
159
|
+
const issues = zodError.issues.map(i => ({
|
|
160
|
+
path: i.path.join('.'),
|
|
161
|
+
message: i.message,
|
|
162
|
+
code: i.code
|
|
163
|
+
}));
|
|
164
|
+
|
|
165
|
+
const firstIssue = issues[0];
|
|
166
|
+
return new ValidationError(
|
|
167
|
+
`Validation failed: ${firstIssue?.message || 'Invalid input'}`,
|
|
168
|
+
{ issues },
|
|
169
|
+
firstIssue?.path
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Error thrown when shard merge conflicts occur
|
|
176
|
+
*
|
|
177
|
+
* @class MergeConflictError
|
|
178
|
+
* @extends ProbeError
|
|
179
|
+
* @example
|
|
180
|
+
* throw new MergeConflictError('Conflicting claims detected', [
|
|
181
|
+
* { claimId: 'cap-1', agents: ['agent-1', 'agent-2'] }
|
|
182
|
+
* ]);
|
|
183
|
+
*/
|
|
184
|
+
export class MergeConflictError extends ProbeError {
|
|
185
|
+
/**
|
|
186
|
+
* @param {string} message - Error message
|
|
187
|
+
* @param {Array<{claimId: string, agents: string[]}>} conflicts - Conflict details
|
|
188
|
+
*/
|
|
189
|
+
constructor(message, conflicts = []) {
|
|
190
|
+
super(message, 'MERGE_CONFLICT', {
|
|
191
|
+
conflicts,
|
|
192
|
+
conflictCount: conflicts.length
|
|
193
|
+
}, 'Use --on-conflict=list to review conflicts or --on-conflict=merge to auto-resolve');
|
|
194
|
+
|
|
195
|
+
this.name = 'MergeConflictError';
|
|
196
|
+
|
|
197
|
+
/** @type {Array<{claimId: string, agents: string[]}>} */
|
|
198
|
+
this.conflicts = conflicts;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Error thrown when receipt verification fails
|
|
204
|
+
*
|
|
205
|
+
* @class ReceiptError
|
|
206
|
+
* @extends ProbeError
|
|
207
|
+
* @example
|
|
208
|
+
* throw new ReceiptError('Hash chain verification failed', {
|
|
209
|
+
* expectedHash: '0xabc...',
|
|
210
|
+
* actualHash: '0xdef...'
|
|
211
|
+
* });
|
|
212
|
+
*/
|
|
213
|
+
export class ReceiptError extends ProbeError {
|
|
214
|
+
/**
|
|
215
|
+
* @param {string} message - Error message
|
|
216
|
+
* @param {Object} [context] - Receipt context
|
|
217
|
+
* @param {string} [verificationStep] - Which verification step failed
|
|
218
|
+
*/
|
|
219
|
+
constructor(message, context = {}, verificationStep = undefined) {
|
|
220
|
+
super(message, 'RECEIPT_ERROR', {
|
|
221
|
+
...context,
|
|
222
|
+
verificationStep
|
|
223
|
+
}, 'Regenerate receipts or check artifact integrity');
|
|
224
|
+
|
|
225
|
+
this.name = 'ReceiptError';
|
|
226
|
+
|
|
227
|
+
/** @type {string | undefined} */
|
|
228
|
+
this.verificationStep = verificationStep;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Error thrown when artifact is not found
|
|
234
|
+
*
|
|
235
|
+
* @class ArtifactNotFoundError
|
|
236
|
+
* @extends ProbeError
|
|
237
|
+
* @example
|
|
238
|
+
* throw new ArtifactNotFoundError('run-123');
|
|
239
|
+
*/
|
|
240
|
+
export class ArtifactNotFoundError extends ProbeError {
|
|
241
|
+
/**
|
|
242
|
+
* @param {string} artifactId - Missing artifact ID
|
|
243
|
+
* @param {string} [path] - Path searched
|
|
244
|
+
*/
|
|
245
|
+
constructor(artifactId, path = undefined) {
|
|
246
|
+
super(`Artifact not found: ${artifactId}`, 'ARTIFACT_NOT_FOUND', {
|
|
247
|
+
artifactId,
|
|
248
|
+
path
|
|
249
|
+
}, 'Run a probe scan first or check the artifact path');
|
|
250
|
+
|
|
251
|
+
this.name = 'ArtifactNotFoundError';
|
|
252
|
+
|
|
253
|
+
/** @type {string} */
|
|
254
|
+
this.artifactId = artifactId;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Error thrown when command execution times out
|
|
260
|
+
*
|
|
261
|
+
* @class TimeoutError
|
|
262
|
+
* @extends ProbeError
|
|
263
|
+
* @example
|
|
264
|
+
* throw new TimeoutError('Scan timed out', 30000);
|
|
265
|
+
*/
|
|
266
|
+
export class TimeoutError extends ProbeError {
|
|
267
|
+
/**
|
|
268
|
+
* @param {string} message - Error message
|
|
269
|
+
* @param {number} timeoutMs - Timeout in milliseconds
|
|
270
|
+
* @param {string} [operation] - Operation that timed out
|
|
271
|
+
*/
|
|
272
|
+
constructor(message, timeoutMs, operation = undefined) {
|
|
273
|
+
super(message, 'TIMEOUT', {
|
|
274
|
+
timeoutMs,
|
|
275
|
+
operation
|
|
276
|
+
}, 'Increase --timeout value or check for performance issues');
|
|
277
|
+
|
|
278
|
+
this.name = 'TimeoutError';
|
|
279
|
+
|
|
280
|
+
/** @type {number} */
|
|
281
|
+
this.timeoutMs = timeoutMs;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Error thrown when agent execution fails
|
|
287
|
+
*
|
|
288
|
+
* @class AgentError
|
|
289
|
+
* @extends ProbeError
|
|
290
|
+
* @example
|
|
291
|
+
* throw new AgentError('Agent failed', 'completeness-agent', new Error('Query failed'));
|
|
292
|
+
*/
|
|
293
|
+
export class AgentError extends ProbeError {
|
|
294
|
+
/**
|
|
295
|
+
* @param {string} message - Error message
|
|
296
|
+
* @param {string} agentId - Agent that failed
|
|
297
|
+
* @param {Error} [cause] - Underlying error
|
|
298
|
+
*/
|
|
299
|
+
constructor(message, agentId, cause = undefined) {
|
|
300
|
+
super(message, 'AGENT_ERROR', {
|
|
301
|
+
agentId,
|
|
302
|
+
cause: cause?.message
|
|
303
|
+
}, 'Check agent configuration and dependencies');
|
|
304
|
+
|
|
305
|
+
this.name = 'AgentError';
|
|
306
|
+
|
|
307
|
+
/** @type {string} */
|
|
308
|
+
this.agentId = agentId;
|
|
309
|
+
|
|
310
|
+
/** @type {Error | undefined} */
|
|
311
|
+
this.cause = cause;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Error thrown when storage operation fails
|
|
317
|
+
*
|
|
318
|
+
* @class StorageError
|
|
319
|
+
* @extends ProbeError
|
|
320
|
+
* @example
|
|
321
|
+
* throw new StorageError('Failed to write artifact', 'write', { path: '/tmp/artifact.json' });
|
|
322
|
+
*/
|
|
323
|
+
export class StorageError extends ProbeError {
|
|
324
|
+
/**
|
|
325
|
+
* @param {string} message - Error message
|
|
326
|
+
* @param {string} operation - Storage operation (read|write|delete)
|
|
327
|
+
* @param {Object} [context] - Additional context
|
|
328
|
+
*/
|
|
329
|
+
constructor(message, operation, context = {}) {
|
|
330
|
+
super(message, 'STORAGE_ERROR', {
|
|
331
|
+
...context,
|
|
332
|
+
operation
|
|
333
|
+
}, 'Check storage permissions and disk space');
|
|
334
|
+
|
|
335
|
+
this.name = 'StorageError';
|
|
336
|
+
|
|
337
|
+
/** @type {string} */
|
|
338
|
+
this.operation = operation;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Error thrown when configuration is invalid
|
|
344
|
+
*
|
|
345
|
+
* @class ConfigurationError
|
|
346
|
+
* @extends ProbeError
|
|
347
|
+
* @example
|
|
348
|
+
* throw new ConfigurationError('Invalid config file', { path: '.kgc-probe.json' });
|
|
349
|
+
*/
|
|
350
|
+
export class ConfigurationError extends ProbeError {
|
|
351
|
+
/**
|
|
352
|
+
* @param {string} message - Error message
|
|
353
|
+
* @param {Object} [context] - Configuration context
|
|
354
|
+
*/
|
|
355
|
+
constructor(message, context = {}) {
|
|
356
|
+
super(message, 'CONFIG_ERROR', context, 'Check configuration file format and required fields');
|
|
357
|
+
|
|
358
|
+
this.name = 'ConfigurationError';
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Error code constants
|
|
364
|
+
* @type {Object<string, string>}
|
|
365
|
+
*/
|
|
366
|
+
export const ErrorCodes = {
|
|
367
|
+
PROBE_ERROR: 'PROBE_ERROR',
|
|
368
|
+
GUARD_VIOLATION: 'GUARD_VIOLATION',
|
|
369
|
+
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
370
|
+
MERGE_CONFLICT: 'MERGE_CONFLICT',
|
|
371
|
+
RECEIPT_ERROR: 'RECEIPT_ERROR',
|
|
372
|
+
ARTIFACT_NOT_FOUND: 'ARTIFACT_NOT_FOUND',
|
|
373
|
+
TIMEOUT: 'TIMEOUT',
|
|
374
|
+
AGENT_ERROR: 'AGENT_ERROR',
|
|
375
|
+
STORAGE_ERROR: 'STORAGE_ERROR',
|
|
376
|
+
CONFIG_ERROR: 'CONFIG_ERROR',
|
|
377
|
+
UNKNOWN_ERROR: 'UNKNOWN_ERROR'
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Check if error is a ProbeError
|
|
382
|
+
* @param {unknown} error - Error to check
|
|
383
|
+
* @returns {boolean}
|
|
384
|
+
*/
|
|
385
|
+
export function isProbeError(error) {
|
|
386
|
+
return error instanceof ProbeError;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Wrap error in ProbeError if not already
|
|
391
|
+
* @param {unknown} error - Error to wrap
|
|
392
|
+
* @param {string} [code] - Error code
|
|
393
|
+
* @returns {ProbeError}
|
|
394
|
+
*/
|
|
395
|
+
export function wrapError(error, code) {
|
|
396
|
+
return ProbeError.from(error, code);
|
|
397
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview KGC Probe - Utilities Index
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all utility modules for convenience.
|
|
5
|
+
*
|
|
6
|
+
* @module @unrdf/kgc-probe/utils
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Logger
|
|
10
|
+
export {
|
|
11
|
+
Logger,
|
|
12
|
+
createLogger,
|
|
13
|
+
defaultLogger,
|
|
14
|
+
LOG_LEVELS
|
|
15
|
+
} from './logger.mjs';
|
|
16
|
+
|
|
17
|
+
// Error classes
|
|
18
|
+
export {
|
|
19
|
+
ProbeError,
|
|
20
|
+
GuardViolationError,
|
|
21
|
+
ValidationError,
|
|
22
|
+
MergeConflictError,
|
|
23
|
+
ReceiptError,
|
|
24
|
+
ArtifactNotFoundError,
|
|
25
|
+
TimeoutError,
|
|
26
|
+
AgentError,
|
|
27
|
+
StorageError,
|
|
28
|
+
ConfigurationError,
|
|
29
|
+
ErrorCodes,
|
|
30
|
+
isProbeError,
|
|
31
|
+
wrapError
|
|
32
|
+
} from './errors.mjs';
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview KGC Probe - Structured Logger
|
|
3
|
+
*
|
|
4
|
+
* Simple, structured logging utility for observability.
|
|
5
|
+
* No external dependencies - outputs JSON for log aggregation.
|
|
6
|
+
*
|
|
7
|
+
* @module @unrdf/kgc-probe/utils/logger
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Log levels in order of severity
|
|
12
|
+
* @type {Object<string, number>}
|
|
13
|
+
*/
|
|
14
|
+
const LOG_LEVELS = {
|
|
15
|
+
debug: 0,
|
|
16
|
+
info: 1,
|
|
17
|
+
warn: 2,
|
|
18
|
+
error: 3,
|
|
19
|
+
silent: 4
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Logger configuration
|
|
24
|
+
* @typedef {Object} LoggerConfig
|
|
25
|
+
* @property {string} [level='info'] - Minimum log level
|
|
26
|
+
* @property {boolean} [json=true] - Output JSON format
|
|
27
|
+
* @property {string} [prefix=''] - Log prefix
|
|
28
|
+
* @property {boolean} [timestamps=true] - Include timestamps
|
|
29
|
+
* @property {Function} [output] - Custom output function
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Structured log entry
|
|
34
|
+
* @typedef {Object} LogEntry
|
|
35
|
+
* @property {string} level - Log level
|
|
36
|
+
* @property {string} message - Log message
|
|
37
|
+
* @property {string} [timestamp] - ISO timestamp
|
|
38
|
+
* @property {Object} [context] - Additional context
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Logger - Structured logging with JSON output
|
|
43
|
+
*
|
|
44
|
+
* @class Logger
|
|
45
|
+
* @example
|
|
46
|
+
* const logger = createLogger({ prefix: 'kgc-probe' });
|
|
47
|
+
* logger.info('Scan started', { universe: 'my-universe' });
|
|
48
|
+
* logger.error('Scan failed', { error: err.message });
|
|
49
|
+
*/
|
|
50
|
+
export class Logger {
|
|
51
|
+
/**
|
|
52
|
+
* Create logger instance
|
|
53
|
+
* @param {LoggerConfig} [config] - Logger configuration
|
|
54
|
+
*/
|
|
55
|
+
constructor(config = {}) {
|
|
56
|
+
/** @type {string} */
|
|
57
|
+
this.level = config.level || 'info';
|
|
58
|
+
|
|
59
|
+
/** @type {boolean} */
|
|
60
|
+
this.json = config.json !== false;
|
|
61
|
+
|
|
62
|
+
/** @type {string} */
|
|
63
|
+
this.prefix = config.prefix || '';
|
|
64
|
+
|
|
65
|
+
/** @type {boolean} */
|
|
66
|
+
this.timestamps = config.timestamps !== false;
|
|
67
|
+
|
|
68
|
+
/** @type {Function} */
|
|
69
|
+
this.output = config.output || console.log;
|
|
70
|
+
|
|
71
|
+
/** @type {Function} */
|
|
72
|
+
this.errorOutput = config.errorOutput || console.error;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if level should be logged
|
|
77
|
+
* @private
|
|
78
|
+
* @param {string} level - Level to check
|
|
79
|
+
* @returns {boolean}
|
|
80
|
+
*/
|
|
81
|
+
shouldLog(level) {
|
|
82
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Format log entry
|
|
87
|
+
* @private
|
|
88
|
+
* @param {string} level - Log level
|
|
89
|
+
* @param {string} message - Log message
|
|
90
|
+
* @param {Object} [context] - Additional context
|
|
91
|
+
* @returns {string}
|
|
92
|
+
*/
|
|
93
|
+
format(level, message, context) {
|
|
94
|
+
/** @type {LogEntry} */
|
|
95
|
+
const entry = {
|
|
96
|
+
level,
|
|
97
|
+
message: this.prefix ? `[${this.prefix}] ${message}` : message
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
if (this.timestamps) {
|
|
101
|
+
entry.timestamp = new Date().toISOString();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (context && Object.keys(context).length > 0) {
|
|
105
|
+
entry.context = context;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (this.json) {
|
|
109
|
+
return JSON.stringify(entry);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Human-readable format
|
|
113
|
+
const ts = this.timestamps ? `[${entry.timestamp}] ` : '';
|
|
114
|
+
const ctx = context ? ` ${JSON.stringify(context)}` : '';
|
|
115
|
+
return `${ts}${level.toUpperCase()}: ${entry.message}${ctx}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Log debug message
|
|
120
|
+
* @param {string} message - Log message
|
|
121
|
+
* @param {Object} [context] - Additional context
|
|
122
|
+
*/
|
|
123
|
+
debug(message, context) {
|
|
124
|
+
if (this.shouldLog('debug')) {
|
|
125
|
+
this.output(this.format('debug', message, context));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Log info message
|
|
131
|
+
* @param {string} message - Log message
|
|
132
|
+
* @param {Object} [context] - Additional context
|
|
133
|
+
*/
|
|
134
|
+
info(message, context) {
|
|
135
|
+
if (this.shouldLog('info')) {
|
|
136
|
+
this.output(this.format('info', message, context));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Log warning message
|
|
142
|
+
* @param {string} message - Log message
|
|
143
|
+
* @param {Object} [context] - Additional context
|
|
144
|
+
*/
|
|
145
|
+
warn(message, context) {
|
|
146
|
+
if (this.shouldLog('warn')) {
|
|
147
|
+
this.errorOutput(this.format('warn', message, context));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Log error message
|
|
153
|
+
* @param {string} message - Log message
|
|
154
|
+
* @param {Object} [context] - Additional context
|
|
155
|
+
*/
|
|
156
|
+
error(message, context) {
|
|
157
|
+
if (this.shouldLog('error')) {
|
|
158
|
+
this.errorOutput(this.format('error', message, context));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Create child logger with additional prefix
|
|
164
|
+
* @param {string} childPrefix - Child prefix
|
|
165
|
+
* @returns {Logger}
|
|
166
|
+
*/
|
|
167
|
+
child(childPrefix) {
|
|
168
|
+
const newPrefix = this.prefix
|
|
169
|
+
? `${this.prefix}:${childPrefix}`
|
|
170
|
+
: childPrefix;
|
|
171
|
+
|
|
172
|
+
return new Logger({
|
|
173
|
+
level: this.level,
|
|
174
|
+
json: this.json,
|
|
175
|
+
prefix: newPrefix,
|
|
176
|
+
timestamps: this.timestamps,
|
|
177
|
+
output: this.output,
|
|
178
|
+
errorOutput: this.errorOutput
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Set log level
|
|
184
|
+
* @param {string} level - New log level
|
|
185
|
+
*/
|
|
186
|
+
setLevel(level) {
|
|
187
|
+
if (LOG_LEVELS[level] === undefined) {
|
|
188
|
+
throw new Error(`Invalid log level: ${level}`);
|
|
189
|
+
}
|
|
190
|
+
this.level = level;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Log with timing
|
|
195
|
+
* @param {string} message - Log message
|
|
196
|
+
* @param {Function} fn - Function to time
|
|
197
|
+
* @returns {Promise<any>} Function result
|
|
198
|
+
*/
|
|
199
|
+
async timed(message, fn) {
|
|
200
|
+
const start = Date.now();
|
|
201
|
+
try {
|
|
202
|
+
const result = await fn();
|
|
203
|
+
const duration = Date.now() - start;
|
|
204
|
+
this.info(message, { duration_ms: duration, status: 'success' });
|
|
205
|
+
return result;
|
|
206
|
+
} catch (err) {
|
|
207
|
+
const duration = Date.now() - start;
|
|
208
|
+
this.error(message, { duration_ms: duration, status: 'error', error: err.message });
|
|
209
|
+
throw err;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Create logger instance
|
|
216
|
+
* @param {LoggerConfig} [config] - Logger configuration
|
|
217
|
+
* @returns {Logger}
|
|
218
|
+
* @example
|
|
219
|
+
* const logger = createLogger({ prefix: 'my-module', level: 'debug' });
|
|
220
|
+
* logger.info('Hello', { key: 'value' });
|
|
221
|
+
*/
|
|
222
|
+
export function createLogger(config) {
|
|
223
|
+
return new Logger(config);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Default logger instance
|
|
228
|
+
* @type {Logger}
|
|
229
|
+
*/
|
|
230
|
+
export const defaultLogger = createLogger({ prefix: 'kgc-probe' });
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Log levels constant
|
|
234
|
+
* @type {Object<string, number>}
|
|
235
|
+
*/
|
|
236
|
+
export { LOG_LEVELS };
|