@orbytautomation/engine 0.5.0 → 0.6.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.
Files changed (48) hide show
  1. package/dist/core/OrbytEngine.d.ts.map +1 -1
  2. package/dist/core/OrbytEngine.js +18 -4
  3. package/dist/core/OrbytEngine.js.map +1 -1
  4. package/dist/errors/ErrorDebugger.d.ts +78 -25
  5. package/dist/errors/ErrorDebugger.d.ts.map +1 -1
  6. package/dist/errors/ErrorDebugger.js +383 -2
  7. package/dist/errors/ErrorDebugger.js.map +1 -1
  8. package/dist/errors/ErrorDetector.d.ts +107 -5
  9. package/dist/errors/ErrorDetector.d.ts.map +1 -1
  10. package/dist/errors/ErrorDetector.js +195 -40
  11. package/dist/errors/ErrorDetector.js.map +1 -1
  12. package/dist/errors/ErrorFormatter.d.ts +51 -0
  13. package/dist/errors/ErrorFormatter.d.ts.map +1 -1
  14. package/dist/errors/ErrorFormatter.js +128 -0
  15. package/dist/errors/ErrorFormatter.js.map +1 -1
  16. package/dist/errors/ErrorHandler.d.ts +93 -7
  17. package/dist/errors/ErrorHandler.d.ts.map +1 -1
  18. package/dist/errors/ErrorHandler.js +91 -42
  19. package/dist/errors/ErrorHandler.js.map +1 -1
  20. package/dist/errors/OrbytError.d.ts +28 -0
  21. package/dist/errors/OrbytError.d.ts.map +1 -1
  22. package/dist/errors/OrbytError.js.map +1 -1
  23. package/dist/errors/SecurityErrors.d.ts +2 -25
  24. package/dist/errors/SecurityErrors.d.ts.map +1 -1
  25. package/dist/errors/SecurityErrors.js +5 -119
  26. package/dist/errors/SecurityErrors.js.map +1 -1
  27. package/dist/errors/WorkflowError.d.ts +11 -1
  28. package/dist/errors/WorkflowError.d.ts.map +1 -1
  29. package/dist/errors/WorkflowError.js +104 -0
  30. package/dist/errors/WorkflowError.js.map +1 -1
  31. package/dist/loader/WorkflowLoader.d.ts +71 -5
  32. package/dist/loader/WorkflowLoader.d.ts.map +1 -1
  33. package/dist/loader/WorkflowLoader.js +135 -50
  34. package/dist/loader/WorkflowLoader.js.map +1 -1
  35. package/dist/logging/EngineLogger.d.ts +252 -337
  36. package/dist/logging/EngineLogger.d.ts.map +1 -1
  37. package/dist/logging/EngineLogger.js +460 -1012
  38. package/dist/logging/EngineLogger.js.map +1 -1
  39. package/dist/logging/LoggerManager.d.ts +109 -33
  40. package/dist/logging/LoggerManager.d.ts.map +1 -1
  41. package/dist/logging/LoggerManager.js +136 -47
  42. package/dist/logging/LoggerManager.js.map +1 -1
  43. package/dist/logging/index.d.ts.map +1 -1
  44. package/dist/logging/index.js.map +1 -1
  45. package/dist/types/log-types.d.ts +50 -11
  46. package/dist/types/log-types.d.ts.map +1 -1
  47. package/dist/types/log-types.js.map +1 -1
  48. package/package.json +1 -1
@@ -20,49 +20,124 @@
20
20
  * - Monitoring systems
21
21
  * - Audit trails
22
22
  *
23
- * @module core
23
+ * @module logging
24
24
  */
25
25
  import { LogLevel, LogLevelSeverity, formatLog, createLogEntry, shouldLog, formatTimestamp, } from '@dev-ecosystem/core';
26
- import { EngineLogType, LogCategoryEnum } from '../types/log-types.js';
26
+ import { EngineLogType, LogCategoryEnum, } from '../types/log-types.js';
27
+ // ─── CategoryLogger ────────────────────────────────────────────────────────────
27
28
  /**
28
- * Re-export formatTimestamp for external use
29
- * Allows other parts of the system to format timestamps consistently
29
+ * A thin wrapper that pins every log call to a specific category.
30
+ *
31
+ * Obtain instances via `EngineLogger.runtime`, `.analysis`, `.system`, or `.security`.
32
+ *
33
+ * ```typescript
34
+ * const logger = LoggerManager.getLogger();
35
+ *
36
+ * // inside run() — runtime category
37
+ * logger.runtime.info('Step started', { stepId });
38
+ *
39
+ * // inside explain() / validate() — analysis category
40
+ * logger.analysis.debug('Building execution plan');
41
+ * ```
30
42
  */
31
- export { formatTimestamp };
43
+ export class CategoryLogger {
44
+ parent;
45
+ category;
46
+ constructor(parent, category) {
47
+ this.parent = parent;
48
+ this.category = category;
49
+ }
50
+ debug(message, context) {
51
+ this.parent._logWithCategory(LogLevel.DEBUG, message, context, undefined, this.category);
52
+ }
53
+ info(message, context) {
54
+ this.parent._logWithCategory(LogLevel.INFO, message, context, undefined, this.category);
55
+ }
56
+ warn(message, context) {
57
+ this.parent._logWithCategory(LogLevel.WARN, message, context, undefined, this.category);
58
+ }
59
+ error(message, error, context) {
60
+ this.parent._logWithCategory(LogLevel.ERROR, message, context, error, this.category);
61
+ }
62
+ fatal(message, error, context) {
63
+ this.parent._logWithCategory(LogLevel.FATAL, message, context, error, this.category);
64
+ }
65
+ /**
66
+ * Measure execution time of `fn` and log the result under this category.
67
+ *
68
+ * ```typescript
69
+ * const result = await logger.runtime.measureExecution(
70
+ * 'step:http-request',
71
+ * () => httpAdapter.run(step),
72
+ * { warn: 3000, error: 10000 },
73
+ * );
74
+ * ```
75
+ */
76
+ async measureExecution(label, fn, thresholds) {
77
+ const start = Date.now();
78
+ try {
79
+ const result = await fn();
80
+ const duration = Date.now() - start;
81
+ let level = LogLevel.INFO;
82
+ if (thresholds?.error && duration > thresholds.error)
83
+ level = LogLevel.ERROR;
84
+ else if (thresholds?.warn && duration > thresholds.warn)
85
+ level = LogLevel.WARN;
86
+ this.parent._logWithCategory(level, `${label} completed`, { duration: `${duration}ms` }, undefined, this.category, EngineLogType.EXECUTION_TIME);
87
+ return result;
88
+ }
89
+ catch (err) {
90
+ const duration = Date.now() - start;
91
+ this.parent._logWithCategory(LogLevel.ERROR, `${label} failed`, { duration: `${duration}ms` }, err instanceof Error ? err : new Error(String(err)), this.category, EngineLogType.ERROR_DETECTED);
92
+ throw err;
93
+ }
94
+ }
95
+ }
96
+ // ─── EngineLogger ──────────────────────────────────────────────────────────────
32
97
  /**
33
98
  * Engine Logger
34
99
  *
35
100
  * Wraps ecosystem-core logging utilities with engine-specific configuration.
101
+ * Emits structured JSON logs and maintains a typed log history.
102
+ *
103
+ * ### Category sub-loggers
104
+ * Every log is tagged with a phase-based category so tooling / formatters can
105
+ * filter or colour-code entries without parsing the message text.
106
+ *
107
+ * | Sub-logger | Category | When to use |
108
+ * |--------------------|--------------|---------------------------------------------------|
109
+ * | `logger.runtime` | `runtime` | `run()` — step start/complete/fail/retry |
110
+ * | `logger.analysis` | `analysis` | `explain()` / `validate()` — plan & parse phase |
111
+ * | `logger.system` | `system` | Engine init/shutdown, adapter registration |
112
+ * | `logger.security` | `security` | Reserved-field violations, permission rejections |
113
+ *
114
+ * Generic `logger.info(...)` etc. fall back to the category set at construction.
36
115
  */
37
116
  export class EngineLogger {
38
- // Config may have optional category/source
39
117
  config;
40
118
  formatOptions;
41
- eventListeners;
42
- logBuffer; // Structured JSON log buffer (strongly typed)
43
- /**
44
- * Default log source (component/module)
45
- */
46
- /**
47
- * Default log source (component/module)
48
- */
49
- // defaultSource removed: source is always required and used directly
50
- maxBufferSize = 10000; // Prevent memory leaks
119
+ logHistory = [];
120
+ /** Workflow context set by the engine before run/explain/validate */
121
+ workflowContext = null;
122
+ // ─── Category sub-loggers ────────────────────────────────────────────────
123
+ /** Use inside `run()` and step-execution paths. Category: `runtime` */
124
+ runtime;
125
+ /** Use inside `explain()` and `validate()` paths. Category: `analysis` */
126
+ analysis;
127
+ /** Use for engine init, shutdown, adapter registration. Category: `system` */
128
+ system;
129
+ /** Use for reserved-field violations, permission rejections. Category: `security` */
130
+ security;
51
131
  constructor(config) {
52
- if (!config.source) {
53
- throw new Error('EngineLoggerConfig: source is required');
54
- }
55
- if (!config.category) {
56
- throw new Error('EngineLoggerConfig: category is required');
57
- }
58
132
  this.config = {
59
133
  level: config.level,
60
- format: config.format || 'text',
134
+ format: config.format || 'json',
61
135
  colors: config.colors ?? true,
62
136
  timestamp: config.timestamp ?? true,
63
- source: config.source,
137
+ source: config.source || 'Orbyt',
64
138
  structuredEvents: config.structuredEvents ?? true,
65
- category: config.category,
139
+ category: config.category || LogCategoryEnum.SYSTEM,
140
+ maxHistorySize: config.maxHistorySize ?? 0,
66
141
  };
67
142
  this.formatOptions = {
68
143
  format: this.config.format,
@@ -70,842 +145,453 @@ export class EngineLogger {
70
145
  timestamp: this.config.timestamp,
71
146
  includeSource: true,
72
147
  };
73
- this.eventListeners = new Map();
74
- this.logBuffer = [];
148
+ // Bind category sub-loggers
149
+ this.runtime = new CategoryLogger(this, LogCategoryEnum.RUNTIME);
150
+ this.analysis = new CategoryLogger(this, LogCategoryEnum.ANALYSIS);
151
+ this.system = new CategoryLogger(this, LogCategoryEnum.SYSTEM);
152
+ this.security = new CategoryLogger(this, LogCategoryEnum.SECURITY);
75
153
  }
76
154
  /**
77
155
  * Log a debug message
78
156
  */
79
- /**
80
- * Log a debug message (category and source required)
81
- */
82
- debug(message, context, category, source) {
83
- this.log(LogLevel.DEBUG, message, context, undefined, category, source);
84
- }
85
- /**
86
- * Log an info message (category and source required)
87
- */
88
- info(message, context, category, source) {
89
- this.log(LogLevel.INFO, message, context, undefined, category, source);
90
- }
91
- /**
92
- * Log a warning message (category and source required)
93
- */
94
- warn(message, context, category, source) {
95
- this.log(LogLevel.WARN, message, context, undefined, category, source);
96
- }
97
- /**
98
- * Log an error message (category and source required)
99
- */
100
- error(message, error, context, category, source) {
101
- this.log(LogLevel.ERROR, message, context, error, category, source);
102
- }
103
- /**
104
- * Log a fatal error message (category and source required)
105
- */
106
- fatal(message, error, context, category, source) {
107
- this.log(LogLevel.FATAL, message, context, error, category, source);
108
- }
109
- // ============================================================================
110
- // WORKFLOW LIFECYCLE LOGGING
111
- // ============================================================================
112
- /**
113
- * Log workflow started event
114
- */
115
- /**
116
- * Log workflow started event (runtime phase)
117
- */
118
- workflowStarted(workflowName, context) {
119
- this.logEvent({
120
- type: EngineLogType.WORKFLOW_STARTED,
121
- timestamp: new Date(),
122
- message: `Workflow "${workflowName}" started`,
123
- context,
124
- category: LogCategoryEnum.RUNTIME,
125
- source: 'WorkflowExecutor',
126
- });
127
- }
128
- /**
129
- * Log workflow completed event
130
- */
131
- /**
132
- * Log workflow completed event (runtime phase)
133
- */
134
- workflowCompleted(workflowName, duration, context) {
135
- this.logEvent({
136
- type: EngineLogType.WORKFLOW_COMPLETED,
137
- timestamp: new Date(),
138
- message: `Workflow "${workflowName}" completed successfully`,
139
- context,
140
- metrics: { duration },
141
- category: LogCategoryEnum.RUNTIME,
142
- source: 'WorkflowExecutor',
143
- });
144
- }
145
- /**
146
- * Log workflow failed event
147
- */
148
- /**
149
- * Log workflow failed event (runtime phase)
150
- */
151
- workflowFailed(workflowName, error, duration, context) {
152
- this.logEvent({
153
- type: EngineLogType.WORKFLOW_FAILED,
154
- timestamp: new Date(),
155
- message: `Workflow "${workflowName}" failed: ${error.message}`,
156
- context,
157
- error,
158
- metrics: { duration },
159
- category: LogCategoryEnum.RUNTIME,
160
- source: 'WorkflowExecutor',
161
- });
162
- }
163
- /**
164
- * Log workflow validation event
165
- */
166
- /**
167
- * Log workflow validation event (analysis phase)
168
- */
169
- workflowValidation(workflowName, isValid, errors) {
170
- this.logEvent({
171
- type: EngineLogType.WORKFLOW_VALIDATION,
172
- timestamp: new Date(),
173
- message: `Workflow "${workflowName}" validation: ${isValid ? 'PASSED' : 'FAILED'}`,
174
- context: errors ? { errors } : undefined,
175
- category: LogCategoryEnum.ANALYSIS,
176
- source: 'WorkflowLoader',
177
- });
178
- }
179
- // ============================================================================
180
- // STEP LIFECYCLE LOGGING
181
- // ============================================================================
182
- /**
183
- * Log step started event
184
- */
185
- /**
186
- * Log step started event (runtime phase)
187
- */
188
- stepStarted(stepId, stepName, context) {
189
- this.logEvent({
190
- type: EngineLogType.STEP_STARTED,
191
- timestamp: new Date(),
192
- message: `Step "${stepName}" (${stepId}) started`,
193
- context,
194
- category: LogCategoryEnum.RUNTIME,
195
- source: 'StepExecutor',
196
- });
197
- }
198
- /**
199
- * Log step completed event
200
- */
201
- /**
202
- * Log step completed event (runtime phase)
203
- */
204
- stepCompleted(stepId, stepName, duration, context) {
205
- this.logEvent({
206
- type: EngineLogType.STEP_COMPLETED,
207
- timestamp: new Date(),
208
- message: `Step "${stepName}" (${stepId}) completed`,
209
- context,
210
- metrics: { duration },
211
- category: LogCategoryEnum.RUNTIME,
212
- source: 'StepExecutor',
213
- });
214
- }
215
- /**
216
- * Log step failed event
217
- */
218
- /**
219
- * Log step failed event (runtime phase)
220
- */
221
- stepFailed(stepId, stepName, error, context) {
222
- this.logEvent({
223
- type: EngineLogType.STEP_FAILED,
224
- timestamp: new Date(),
225
- message: `Step "${stepName}" (${stepId}) failed: ${error.message}`,
226
- context,
227
- error,
228
- category: LogCategoryEnum.RUNTIME,
229
- source: 'StepExecutor',
230
- });
231
- }
232
- /**
233
- * Log step retry event
234
- */
235
- /**
236
- * Log step retry event (runtime phase)
237
- */
238
- stepRetry(stepId, stepName, attempt, maxAttempts) {
239
- this.logEvent({
240
- type: EngineLogType.STEP_RETRY,
241
- timestamp: new Date(),
242
- message: `Step "${stepName}" (${stepId}) retry ${attempt}/${maxAttempts}`,
243
- context: { attempt, maxAttempts },
244
- category: LogCategoryEnum.RUNTIME,
245
- source: 'StepExecutor',
246
- });
157
+ debug(message, context) {
158
+ this.log(LogLevel.DEBUG, message, context);
247
159
  }
248
160
  /**
249
- * Log step timeout event
250
- */
251
- /**
252
- * Log step timeout event (runtime phase)
161
+ * Log an info message
253
162
  */
254
- stepTimeout(stepId, stepName, timeout) {
255
- this.logEvent({
256
- type: EngineLogType.STEP_TIMEOUT,
257
- timestamp: new Date(),
258
- message: `Step "${stepName}" (${stepId}) timed out after ${timeout}ms`,
259
- context: { timeout },
260
- category: LogCategoryEnum.RUNTIME,
261
- source: 'StepExecutor',
262
- });
163
+ info(message, context) {
164
+ this.log(LogLevel.INFO, message, context);
263
165
  }
264
- // ============================================================================
265
- // EXPLANATION LOGGING
266
- // ============================================================================
267
166
  /**
268
- * Log explanation generated event
167
+ * Log a warning message
269
168
  */
270
- /**
271
- * Log explanation generated event (analysis phase)
272
- */
273
- explanationGenerated(workflowName, stepCount, strategy) {
274
- this.logEvent({
275
- type: EngineLogType.EXPLANATION_GENERATED,
276
- timestamp: new Date(),
277
- message: `Explanation generated for "${workflowName}" (${stepCount} steps, ${strategy} strategy)`,
278
- context: { workflowName, stepCount, strategy },
279
- category: LogCategoryEnum.ANALYSIS,
280
- source: 'ExplanationGenerator',
281
- });
169
+ warn(message, context) {
170
+ this.log(LogLevel.WARN, message, context);
282
171
  }
283
172
  /**
284
- * Log circular dependencies detected
173
+ * Log an error message
285
174
  */
286
- /**
287
- * Log circular dependencies detected (analysis phase)
288
- */
289
- explanationCycles(workflowName, cycles) {
290
- this.logEvent({
291
- type: EngineLogType.EXPLANATION_CYCLES,
292
- timestamp: new Date(),
293
- message: `Circular dependencies detected in "${workflowName}"`,
294
- context: { cycles },
295
- category: LogCategoryEnum.ANALYSIS,
296
- source: 'ExplanationGenerator',
297
- });
175
+ error(message, error, context) {
176
+ this.log(LogLevel.ERROR, message, context, error);
298
177
  }
299
- // ============================================================================
300
- // ADAPTER & PLUGIN LOGGING
301
- // ============================================================================
302
178
  /**
303
- * Log adapter loaded event
179
+ * Log a fatal error message
304
180
  */
305
- /**
306
- * Log adapter loaded event (system phase)
307
- */
308
- adapterLoaded(adapterName, version) {
309
- this.logEvent({
310
- type: EngineLogType.ADAPTER_LOADED,
311
- timestamp: new Date(),
312
- message: `Adapter "${adapterName}" loaded${version ? ` (v${version})` : ''}`,
313
- context: { adapterName, version },
314
- category: LogCategoryEnum.SYSTEM,
315
- source: 'AdapterRegistry',
316
- });
181
+ fatal(message, error, context) {
182
+ this.log(LogLevel.FATAL, message, context, error);
317
183
  }
318
184
  /**
319
- * Log adapter failed event
320
- */
321
- /**
322
- * Log adapter failed event (system phase)
185
+ * Log with a custom level
323
186
  */
324
- adapterFailed(adapterName, error) {
325
- this.logEvent({
326
- type: EngineLogType.ADAPTER_FAILED,
327
- timestamp: new Date(),
328
- message: `Adapter "${adapterName}" failed: ${error.message}`,
329
- context: { adapterName },
330
- error,
331
- category: LogCategoryEnum.SYSTEM,
332
- source: 'AdapterRegistry',
333
- });
187
+ logWithLevel(level, message, context) {
188
+ this.log(level, message, context);
334
189
  }
335
190
  /**
336
- * Log plugin installed event
337
- */
338
- /**
339
- * Log plugin installed event (system phase)
191
+ * Log only if severity meets minimum threshold
340
192
  */
341
- pluginInstalled(pluginName, source) {
342
- this.logEvent({
343
- type: EngineLogType.PLUGIN_INSTALLED,
344
- timestamp: new Date(),
345
- message: `Plugin "${pluginName}" installed from ${source}`,
346
- context: { pluginName, source },
347
- category: LogCategoryEnum.SYSTEM,
348
- source: 'AdapterRegistry',
349
- });
193
+ logIfSeverity(minSeverity, level, message, context) {
194
+ if (LogLevelSeverity[level] >= minSeverity) {
195
+ this.log(level, message, context);
196
+ }
350
197
  }
351
198
  /**
352
- * Log plugin verified event
353
- */
354
- /**
355
- * Log plugin verified event (system phase)
199
+ * Measure and log execution time with appropriate log level based on duration.
200
+ *
201
+ * Pass `category` to override (`'runtime'` during `run()`, `'analysis'` during `explain()`).
202
+ * Prefer `logger.runtime.measureExecution(...)` or `logger.analysis.measureExecution(...)`.
356
203
  */
357
- pluginVerified(pluginName, verified) {
358
- this.logEvent({
359
- type: EngineLogType.PLUGIN_VERIFIED,
360
- timestamp: new Date(),
361
- message: `Plugin "${pluginName}" verification: ${verified ? 'PASSED' : 'FAILED'}`,
362
- context: { pluginName, verified },
363
- category: LogCategoryEnum.SYSTEM,
364
- source: 'AdapterRegistry',
365
- });
204
+ async measureExecution(label, fn, thresholds, category) {
205
+ const start = Date.now();
206
+ try {
207
+ const result = await fn();
208
+ const duration = Date.now() - start;
209
+ let level = LogLevel.DEBUG;
210
+ if (thresholds?.error && duration > thresholds.error) {
211
+ level = LogLevel.ERROR;
212
+ }
213
+ else if (thresholds?.warn && duration > thresholds.warn) {
214
+ level = LogLevel.WARN;
215
+ }
216
+ else {
217
+ level = LogLevel.INFO;
218
+ }
219
+ this._logWithCategory(level, `${label} completed`, { duration: `${duration}ms` }, undefined, category, EngineLogType.EXECUTION_TIME);
220
+ return result;
221
+ }
222
+ catch (error) {
223
+ const duration = Date.now() - start;
224
+ this._logWithCategory(LogLevel.ERROR, `${label} failed`, { duration: `${duration}ms` }, error instanceof Error ? error : new Error(String(error)), category, EngineLogType.ERROR_DETECTED);
225
+ throw error;
226
+ }
366
227
  }
367
- // ============================================================================
368
- // ERROR & DEBUG LOGGING
369
- // ============================================================================
228
+ // ─── Core log dispatch ────────────────────────────────────────────────────
370
229
  /**
371
- * Log error detected event
372
- */
373
- /**
374
- * Log error detected event (security phase)
230
+ * Central log dispatcher — used directly by `CategoryLogger` sub-loggers
231
+ * and by the private `log()` helper below.
232
+ *
233
+ * @param level - Severity level
234
+ * @param message - Human-readable message
235
+ * @param context - Optional key-value metadata (merged with workflow ctx)
236
+ * @param error - Optional error object
237
+ * @param categoryOverride - Pin to a specific category (defaults to config category)
238
+ * @param typeOverride - Explicit `EngineLogType`; inferred from level when omitted
239
+ *
240
+ * @internal Called via sub-loggers (`runtime`, `analysis`, etc.) or internally.
375
241
  */
376
- errorDetected(error, context) {
377
- this.logEvent({
378
- type: EngineLogType.ERROR_DETECTED,
242
+ _logWithCategory(level, message, context, error, categoryOverride, typeOverride) {
243
+ if (!this.shouldLogLevel(level))
244
+ return;
245
+ // Map severity level → structured event type
246
+ const typeMap = {
247
+ [LogLevel.DEBUG]: EngineLogType.DEBUG,
248
+ [LogLevel.INFO]: EngineLogType.INFO,
249
+ [LogLevel.WARN]: EngineLogType.WARNING,
250
+ [LogLevel.ERROR]: EngineLogType.ERROR_DETECTED,
251
+ [LogLevel.FATAL]: EngineLogType.ERROR,
252
+ };
253
+ const eventType = typeOverride ?? typeMap[level] ?? EngineLogType.INFO;
254
+ const category = categoryOverride ?? this.config.category;
255
+ // Automatically merge workflow context so every log carries the active workflow
256
+ const enrichedContext = {};
257
+ if (this.workflowContext) {
258
+ enrichedContext['workflow'] = { ...this.workflowContext };
259
+ }
260
+ if (context) {
261
+ Object.assign(enrichedContext, context);
262
+ }
263
+ const finalContext = Object.keys(enrichedContext).length > 0 ? enrichedContext : undefined;
264
+ // Record in history
265
+ const event = {
266
+ type: eventType,
379
267
  timestamp: new Date(),
380
- message: `Error detected: ${error.message}`,
381
- context,
268
+ message,
269
+ category,
270
+ source: this.config.source,
271
+ context: finalContext,
382
272
  error,
383
- category: LogCategoryEnum.SECURITY,
384
- source: 'SecurityError',
385
- });
386
- }
387
- /**
388
- * Log error debugged event (with debugging info)
389
- */
390
- /**
391
- * Log error debugged event (security phase)
392
- */
393
- errorDebugged(error, debugInfo) {
394
- this.logEvent({
395
- type: EngineLogType.ERROR_DEBUGGED,
396
- timestamp: new Date(),
397
- message: `Error debugged: ${error.message}`,
398
- context: debugInfo,
273
+ };
274
+ this.logHistory.push(event);
275
+ // Ring-buffer: drop the oldest entry when the limit is reached.
276
+ // A maxHistorySize of 0 means unbounded (no trimming).
277
+ if (this.config.maxHistorySize > 0 && this.logHistory.length > this.config.maxHistorySize) {
278
+ this.logHistory.shift();
279
+ }
280
+ // Build + emit formatted entry
281
+ const entry = createLogEntry(level, message, {
282
+ source: this.config.source,
283
+ context: finalContext,
399
284
  error,
400
- category: LogCategoryEnum.SECURITY,
401
- source: 'SecurityError',
402
- });
403
- }
404
- /**
405
- * Log validation error event
406
- */
407
- /**
408
- * Log validation error event (analysis phase)
409
- */
410
- validationError(message, errors) {
411
- this.logEvent({
412
- type: EngineLogType.VALIDATION_ERROR,
413
- timestamp: new Date(),
414
- message,
415
- context: { errors },
416
- category: LogCategoryEnum.ANALYSIS,
417
- source: 'WorkflowLoader',
418
- });
419
- }
420
- // ============================================================================
421
- // PERFORMANCE LOGGING
422
- // ============================================================================
423
- /**
424
- * Log performance metric
425
- */
426
- /**
427
- * Log performance metric (system phase)
428
- */
429
- performanceMetric(label, metrics) {
430
- this.logEvent({
431
- type: EngineLogType.PERFORMANCE_METRIC,
432
- timestamp: new Date(),
433
- message: `Performance: ${label}`,
434
- metrics,
435
- category: LogCategoryEnum.SYSTEM,
436
- source: 'EngineLogger',
437
- });
438
- }
439
- /**
440
- * Log execution time
441
- */
442
- /**
443
- * Log execution time (runtime phase)
444
- */
445
- executionTime(label, duration, context) {
446
- this.logEvent({
447
- type: EngineLogType.EXECUTION_TIME,
448
- timestamp: new Date(),
449
- message: `${label}: ${duration}ms`,
450
- context,
451
- metrics: { duration },
452
- category: LogCategoryEnum.RUNTIME,
453
- source: 'WorkflowExecutor',
454
- });
455
- }
456
- // ============================================================================
457
- // FIELD-LEVEL EXECUTION LOGGING (Dynamic Explanation)
458
- // ============================================================================
459
- /**
460
- * Log field execution - captures every field that gets executed
461
- * This enables dynamic explanation generation from runtime logs
462
- */
463
- fieldExecution(type, field, value, context) {
464
- // Filter out internal fields (starting with _)
465
- if (field.startsWith('_'))
466
- return;
467
- this.debug(`[Field] ${type}.${field} = ${this.formatValue(value)}`, {
468
- fieldType: type,
469
- fieldName: field,
470
- value: this.sanitizeValue(value),
471
- ...context,
472
285
  });
286
+ const formatted = formatLog(entry, this.formatOptions);
287
+ console.log(formatted);
473
288
  }
474
289
  /**
475
- * Log input processing
290
+ * Private shorthand — uses the default category from config.
291
+ * Prefer the categorised sub-loggers (`runtime`, `analysis`, etc.) for new code.
476
292
  */
477
- inputProcessed(inputName, value, source) {
478
- this.debug(`[Input] ${inputName} received from ${source}`, {
479
- input: inputName,
480
- source,
481
- value: this.sanitizeValue(value),
482
- });
293
+ log(level, message, context, error) {
294
+ this._logWithCategory(level, message, context, error);
483
295
  }
296
+ // ─── Log History ────────────────────────────────────────────────────
484
297
  /**
485
- * Log output generation
298
+ * Export collected logs as a structured ExportedLogs object.
299
+ * Includes `byCategory` statistics and the active `workflowContext` snapshot.
486
300
  */
487
- outputGenerated(outputName, value, step) {
488
- this.debug(`[Output] ${outputName} generated${step ? ` by ${step}` : ''}`, {
489
- output: outputName,
490
- step,
491
- value: this.sanitizeValue(value),
492
- });
301
+ exportLogs() {
302
+ const grouped = {};
303
+ let withErrors = 0;
304
+ let withMetrics = 0;
305
+ let first;
306
+ let last;
307
+ const byType = {};
308
+ const byCategory = {};
309
+ for (const event of this.logHistory) {
310
+ const typeKey = event.type;
311
+ grouped[typeKey] = grouped[typeKey] ? [...grouped[typeKey], event] : [event];
312
+ byType[typeKey] = (byType[typeKey] ?? 0) + 1;
313
+ byCategory[event.category] = (byCategory[event.category] ?? 0) + 1;
314
+ if (event.error)
315
+ withErrors++;
316
+ if (event.metrics)
317
+ withMetrics++;
318
+ if (!first || event.timestamp < first)
319
+ first = event.timestamp;
320
+ if (!last || event.timestamp > last)
321
+ last = event.timestamp;
322
+ }
323
+ return {
324
+ raw: [...this.logHistory],
325
+ grouped,
326
+ workflowContext: this.workflowContext ?? undefined,
327
+ stats: {
328
+ total: this.logHistory.length,
329
+ byType,
330
+ byCategory,
331
+ withErrors,
332
+ withMetrics,
333
+ timeRange: { first, last },
334
+ },
335
+ };
493
336
  }
494
337
  /**
495
- * Log context variable access
338
+ * Get all collected logs as a JSON string
496
339
  */
497
- contextAccessed(variable, value, step) {
498
- this.debug(`[Context] ${variable} accessed by ${step}`, {
499
- variable,
500
- step,
501
- value: this.sanitizeValue(value),
502
- });
340
+ getJSONLogs() {
341
+ return JSON.stringify(this.exportLogs(), null, 2);
503
342
  }
504
343
  /**
505
- * Log secret access (without exposing values)
344
+ * Clear the log history (does NOT clear the workflow context)
506
345
  */
507
- secretAccessed(secretKey, step) {
508
- this.debug(`[Secret] ${secretKey} accessed by ${step}`, {
509
- secretKey,
510
- step,
511
- exposed: false, // Never log secret values
512
- });
346
+ clearHistory() {
347
+ this.logHistory = [];
513
348
  }
349
+ // ─── Workflow Context ─────────────────────────────────────────────────────
514
350
  /**
515
- * Log variable resolution
351
+ * Attach workflow context so every subsequent log entry automatically
352
+ * includes a `workflow` field. Call this before `run()`, `explain()`,
353
+ * or `validate()` with the parsed workflow data.
354
+ *
355
+ * ```typescript
356
+ * logger.setWorkflowContext({
357
+ * name: workflow.name,
358
+ * version: workflow.version,
359
+ * kind: workflow.kind,
360
+ * stepCount: workflow.steps.length,
361
+ * filePath: resolvedPath,
362
+ * });
363
+ * ```
516
364
  */
517
- variableResolved(variable, resolvedValue, context) {
518
- this.debug(`[Variable] ${variable} resolved`, {
519
- variable,
520
- resolved: this.sanitizeValue(resolvedValue),
521
- ...context,
522
- });
365
+ setWorkflowContext(ctx) {
366
+ this.workflowContext = { ...ctx };
523
367
  }
524
368
  /**
525
- * Log adapter action execution
369
+ * Detach the workflow context (e.g. after a run completes).
370
+ * Logs emitted after this call will no longer include the `workflow` field.
526
371
  */
527
- adapterActionExecuted(adapter, action, duration, success) {
528
- const message = `[Adapter] ${adapter}.${action} ${success ? 'completed' : 'failed'} in ${duration}ms`;
529
- const context = { adapter, action, duration, success };
530
- if (success) {
531
- this.info(message, context);
532
- }
533
- else {
534
- this.error(message, undefined, context);
535
- }
372
+ clearWorkflowContext() {
373
+ this.workflowContext = null;
536
374
  }
537
- // ============================================================================
538
- // PARSING & VALIDATION LOGGING
539
- // ============================================================================
540
375
  /**
541
- * Log parsing start
376
+ * Returns a read-only snapshot of the currently active workflow context,
377
+ * or `null` if none has been set.
542
378
  */
543
- parsingStarted(source, format) {
544
- this.debug(`[Parser] Parsing ${format} from ${source}`, {
545
- source,
546
- format,
547
- });
379
+ getWorkflowContext() {
380
+ return this.workflowContext ? { ...this.workflowContext } : null;
548
381
  }
549
382
  /**
550
- * Log parsing success
383
+ * Partially update the active workflow context without replacing it.
384
+ * Only the supplied fields are merged in; the rest are preserved.
385
+ *
386
+ * Useful for enriching the context mid-run (e.g. after the execution
387
+ * strategy is determined):
388
+ *
389
+ * ```typescript
390
+ * logger.patchWorkflowContext({ executionStrategy: 'parallel' });
391
+ * ```
392
+ *
393
+ * If no context has been set yet, the patch is applied as if it were
394
+ * a fresh `setWorkflowContext` call.
551
395
  */
552
- parsingCompleted(source, duration, stats) {
553
- this.info(`[Parser] Parsed successfully in ${duration}ms`, {
554
- source,
555
- duration,
556
- ...stats,
557
- });
396
+ patchWorkflowContext(partial) {
397
+ this.workflowContext = { ...(this.workflowContext ?? {}), ...partial };
558
398
  }
399
+ // ─── Dynamic Context Helpers ──────────────────────────────────────────────
559
400
  /**
560
- * Log parsing error
401
+ * Build a log-context object for a step event.
402
+ *
403
+ * Use this wherever you log step lifecycle events so context is consistent:
404
+ * ```typescript
405
+ * logger.runtime.info('Step started', logger.stepCtx({ id: step.id, adapter: step.adapter }));
406
+ * logger.runtime.error('Step failed', error, logger.stepCtx({ id: step.id, adapter: step.adapter }));
407
+ * ```
408
+ */
409
+ stepCtx(step) {
410
+ const ctx = { stepId: step.id };
411
+ if (step.adapter)
412
+ ctx['adapter'] = step.adapter;
413
+ if (step.name)
414
+ ctx['stepName'] = step.name;
415
+ // Forward any extra fields the caller added
416
+ for (const [k, v] of Object.entries(step)) {
417
+ if (k !== 'id' && k !== 'adapter' && k !== 'name')
418
+ ctx[k] = v;
419
+ }
420
+ return ctx;
421
+ }
422
+ /**
423
+ * Build a log-context object for a workflow-level event.
424
+ *
425
+ * Returns a flat object suitable for passing as `context` to any log call.
426
+ * If no workflow context is set, returns `{}`.
427
+ *
428
+ * ```typescript
429
+ * logger.system.info('Workflow validated', logger.workflowCtx());
430
+ * ```
561
431
  */
562
- parsingFailed(source, error, context) {
563
- this.error(`[Parser] Parsing failed: ${error.message}`, error, {
564
- source,
565
- ...context,
566
- });
432
+ workflowCtx() {
433
+ return this.workflowContext ? { ...this.workflowContext } : {};
567
434
  }
435
+ // ─── Dynamic Lifecycle Helpers ────────────────────────────────────────────
568
436
  /**
569
- * Log validation start
437
+ * Log workflow execution started (runtime category).
438
+ * Called at the top of `WorkflowExecutor.execute()`.
570
439
  */
571
- validationStarted(target, type) {
572
- this.debug(`[Validator] Validating ${type}: ${target}`, {
573
- target,
574
- type,
575
- });
440
+ workflowStarted(workflowName, context) {
441
+ this.runtime.info(`Workflow "${workflowName}" started`, { workflowName, ...context });
576
442
  }
577
443
  /**
578
- * Log validation success
444
+ * Log a workflow input field that was resolved (runtime category).
579
445
  */
580
- validationPassed(target, type, duration) {
581
- this.info(`[Validator] Validation passed: ${target}`, {
582
- target,
583
- type,
584
- duration,
585
- });
446
+ inputProcessed(key, value, source) {
447
+ this.runtime.debug(`Input resolved: ${key}`, { key, value, source });
586
448
  }
587
449
  /**
588
- * Log validation failure
450
+ * Log a context field used during execution (runtime category).
589
451
  */
590
- validationFailed(target, type, errors) {
591
- this.error(`[Validator] Validation failed: ${target}`, undefined, {
592
- target,
593
- type,
594
- errors,
595
- errorCount: errors.length,
596
- });
452
+ fieldExecution(fieldType, key, value) {
453
+ this.runtime.debug(`Context field accessed: ${fieldType}.${key}`, { fieldType, key, value });
597
454
  }
598
- // ============================================================================
599
- // ERROR DETECTION & DEBUGGING LOGGING
600
- // ============================================================================
601
455
  /**
602
- * Log error enrichment (when ErrorDetector adds debugging info)
456
+ * Log step execution started (runtime category).
457
+ * ```typescript
458
+ * logger.stepStarted(step.id, step.name, { adapter: step.adapter });
459
+ * ```
603
460
  */
604
- errorEnriched(error, debugInfo) {
605
- this.debug(`[ErrorDetector] Error enriched with debugging info`, {
606
- errorType: error.name,
607
- errorMessage: error.message,
608
- ...debugInfo,
609
- });
461
+ stepStarted(stepId, stepName, context) {
462
+ this._logWithCategory(LogLevel.INFO, `Step "${stepName}" (${stepId}) started`, { stepId, stepName, ...context }, undefined, LogCategoryEnum.RUNTIME, EngineLogType.STEP_STARTED);
610
463
  }
611
464
  /**
612
- * Log error debugging output
465
+ * Log step execution completed (runtime category).
613
466
  */
614
- errorDebugOutput(error, explanation, fixSteps) {
615
- this.info(`[ErrorDebugger] Generated debugging output for ${error.name}`, {
616
- errorType: error.name,
617
- explanation,
618
- fixSteps,
619
- stepCount: fixSteps.length,
620
- });
467
+ stepCompleted(stepId, stepName, duration, context) {
468
+ this._logWithCategory(LogLevel.INFO, `Step "${stepName}" (${stepId}) completed in ${duration}ms`, { stepId, stepName, duration, ...context }, undefined, LogCategoryEnum.RUNTIME, EngineLogType.STEP_COMPLETED);
621
469
  }
622
- // ============================================================================
623
- // EXECUTION PHASE LOGGING
624
- // ============================================================================
625
470
  /**
626
- * Log execution phase start
471
+ * Log step timeout (runtime category).
627
472
  */
628
- phaseStarted(phase, stepIds) {
629
- this.info(`[Execution] Phase ${phase} started with ${stepIds.length} step(s)`, {
630
- phase,
631
- stepIds,
632
- stepCount: stepIds.length,
633
- });
473
+ stepTimeout(stepId, stepName, timeoutMs) {
474
+ this._logWithCategory(LogLevel.WARN, `Step "${stepName}" (${stepId}) timed out after ${timeoutMs}ms`, { stepId, stepName, timeoutMs }, undefined, LogCategoryEnum.RUNTIME, EngineLogType.STEP_TIMEOUT);
634
475
  }
635
476
  /**
636
- * Log execution phase completion
477
+ * Log step retry attempt (runtime category).
637
478
  */
638
- phaseCompleted(phase, duration, successCount, failureCount) {
639
- this.info(`[Execution] Phase ${phase} completed in ${duration}ms`, {
640
- phase,
641
- duration,
642
- successCount,
643
- failureCount,
644
- totalSteps: successCount + failureCount,
645
- });
479
+ stepRetry(stepId, stepName, attempt, maxAttempts) {
480
+ this._logWithCategory(LogLevel.WARN, `Step "${stepName}" (${stepId}) retrying attempt ${attempt}/${maxAttempts}`, { stepId, stepName, attempt, maxAttempts }, undefined, LogCategoryEnum.RUNTIME, EngineLogType.STEP_RETRY);
646
481
  }
647
- // ============================================================================
648
- // UTILITY METHODS
649
- // ============================================================================
650
482
  /**
651
- * Format value for logging (truncate large values)
483
+ * Log step failure after all attempts (runtime category).
652
484
  */
653
- formatValue(value) {
654
- if (value === null)
655
- return 'null';
656
- if (value === undefined)
657
- return 'undefined';
658
- if (typeof value === 'string') {
659
- return value.length > 100 ? `${value.substring(0, 97)}...` : value;
660
- }
661
- if (typeof value === 'object') {
662
- const str = JSON.stringify(value);
663
- return str.length > 200 ? `${str.substring(0, 197)}...` : str;
664
- }
665
- return String(value);
485
+ stepFailed(stepId, stepName, error, context) {
486
+ this._logWithCategory(LogLevel.ERROR, `Step "${stepName}" (${stepId}) failed: ${error.message}`, { stepId, stepName, ...context }, error, LogCategoryEnum.RUNTIME, EngineLogType.STEP_FAILED);
666
487
  }
667
488
  /**
668
- * Sanitize value for logging (remove sensitive data)
489
+ * Log adapter action execution result (runtime category).
669
490
  */
670
- sanitizeValue(value) {
671
- if (value === null || value === undefined)
672
- return value;
673
- // For objects, recursively sanitize
674
- if (typeof value === 'object' && !Array.isArray(value)) {
675
- const sanitized = {};
676
- for (const [key, val] of Object.entries(value)) {
677
- // Skip internal fields
678
- if (key.startsWith('_'))
679
- continue;
680
- // Mask sensitive fields
681
- if (this.isSensitiveField(key)) {
682
- sanitized[key] = '***REDACTED***';
683
- }
684
- else if (typeof val === 'object') {
685
- sanitized[key] = this.sanitizeValue(val);
686
- }
687
- else {
688
- sanitized[key] = val;
689
- }
690
- }
691
- return sanitized;
692
- }
693
- return value;
491
+ adapterActionExecuted(adapter, action, duration, success) {
492
+ const level = success ? LogLevel.DEBUG : LogLevel.WARN;
493
+ this._logWithCategory(level, `Adapter "${adapter}" action "${action}" ${success ? 'succeeded' : 'failed'} in ${duration}ms`, { adapter, action, duration, success }, undefined, LogCategoryEnum.RUNTIME, EngineLogType.EXECUTION_TIME);
694
494
  }
695
495
  /**
696
- * Check if field name suggests sensitive data
496
+ * Log validation phase started (analysis category).
697
497
  */
698
- isSensitiveField(fieldName) {
699
- const sensitivePatterns = [
700
- 'password', 'secret', 'token', 'key', 'credential',
701
- 'apikey', 'api_key', 'auth', 'private',
702
- ];
703
- const lowerField = fieldName.toLowerCase();
704
- return sensitivePatterns.some(pattern => lowerField.includes(pattern));
498
+ validationStarted(type, schema) {
499
+ this._logWithCategory(LogLevel.DEBUG, `Validation started: ${type} against ${schema} schema`, { type, schema }, undefined, LogCategoryEnum.ANALYSIS, EngineLogType.WORKFLOW_VALIDATION);
705
500
  }
706
501
  /**
707
- * Log with a custom level
502
+ * Log validation passed (analysis category).
708
503
  */
709
- logWithLevel(level, message, context) {
710
- this.log(level, message, context);
504
+ validationPassed(type, schema) {
505
+ this._logWithCategory(LogLevel.INFO, `Validation passed: ${type} (${schema})`, { type, schema }, undefined, LogCategoryEnum.ANALYSIS, EngineLogType.WORKFLOW_VALIDATION);
711
506
  }
712
507
  /**
713
- * Log only if severity meets minimum threshold
508
+ * Log validation failure (analysis category).
714
509
  */
715
- logIfSeverity(minSeverity, level, message, context) {
716
- if (LogLevelSeverity[level] >= minSeverity) {
717
- this.log(level, message, context);
718
- }
510
+ validationFailed(type, schema, errors) {
511
+ this._logWithCategory(LogLevel.ERROR, `Validation failed: ${type} (${schema}) ${errors.length} error(s)`, { type, schema, errors }, undefined, LogCategoryEnum.ANALYSIS, EngineLogType.VALIDATION_ERROR);
719
512
  }
720
513
  /**
721
- * Measure and log execution time with appropriate log level based on duration
514
+ * Log parsing started (analysis category).
722
515
  */
723
- async measureExecution(label, fn, thresholds) {
724
- const start = Date.now();
725
- try {
726
- const result = await fn();
727
- const duration = Date.now() - start;
728
- // Choose log level based on duration thresholds
729
- let level = LogLevel.DEBUG;
730
- if (thresholds?.error && duration > thresholds.error) {
731
- level = LogLevel.ERROR;
732
- }
733
- else if (thresholds?.warn && duration > thresholds.warn) {
734
- level = LogLevel.WARN;
735
- }
736
- else {
737
- level = LogLevel.INFO;
738
- }
739
- this.log(level, `${label} completed`, { duration: `${duration}ms` });
740
- return result;
741
- }
742
- catch (error) {
743
- const duration = Date.now() - start;
744
- this.log(LogLevel.ERROR, `${label} failed`, { duration: `${duration}ms` }, error instanceof Error ? error : new Error(String(error)));
745
- throw error;
746
- }
516
+ parsingStarted(format, source) {
517
+ this._logWithCategory(LogLevel.DEBUG, `Parsing started: ${format} from ${source}`, { format, source }, undefined, LogCategoryEnum.ANALYSIS, EngineLogType.INFO);
747
518
  }
748
519
  /**
749
- * Log a structured engine event (category and source required)
750
- * Uses EngineLog interface for strong typing.
520
+ * Log parsing completed (analysis category).
751
521
  */
752
- logEvent(event) {
753
- // Validate category and source
754
- if (!event.category || !event.source) {
755
- throw new Error('Log event must have a category and source');
756
- }
757
- // Add to JSON buffer (always, regardless of log level)
758
- this.addToBuffer(event);
759
- // Emit event to listeners if structured events enabled
760
- if (this.config.structuredEvents) {
761
- this.emitEvent(event);
762
- }
763
- // Determine log level based on event type
764
- const level = this.getLogLevelForEventType(event.type);
765
- // Check if this level should be logged
766
- if (!this.shouldLogLevel(level)) {
767
- return;
768
- }
769
- // Build context with metrics if present
770
- const fullContext = {
771
- ...event.context,
772
- ...(event.metrics && { metrics: event.metrics }),
773
- };
774
- // Log through standard log method
775
- this.log(level, event.message, fullContext, event.error, event.category, event.source);
522
+ parsingCompleted(format, duration, context) {
523
+ this._logWithCategory(LogLevel.INFO, `Parsing completed: ${format} in ${duration}ms`, { format, duration, ...context }, undefined, LogCategoryEnum.ANALYSIS, EngineLogType.INFO);
776
524
  }
777
525
  /**
778
- * Get appropriate log level for event type
526
+ * Log parsing failure (analysis category).
779
527
  */
780
- getLogLevelForEventType(type) {
781
- // Error events
782
- if (type.includes('failed') || type.includes('error') || type.includes('timeout')) {
783
- return LogLevel.ERROR;
784
- }
785
- // Warning events
786
- if (type.includes('retry') || type.includes('cycles') || type === EngineLogType.WARNING) {
787
- return LogLevel.WARN;
788
- }
789
- // Debug events
790
- if (type === EngineLogType.DEBUG || type.includes('debug')) {
791
- return LogLevel.DEBUG;
792
- }
793
- // Default to INFO
794
- return LogLevel.INFO;
528
+ parsingFailed(format, error, context) {
529
+ this._logWithCategory(LogLevel.ERROR, `Parsing failed: ${format} — ${error.message}`, { format, ...context }, error, LogCategoryEnum.ANALYSIS, EngineLogType.ERROR_DETECTED);
795
530
  }
531
+ // ─── Typed Event Logger ───────────────────────────────────────────────────
796
532
  /**
797
- * Emit event to registered listeners
533
+ * Emit a log with an explicit `EngineLogType`.
534
+ *
535
+ * Useful when you want the structured event type to be distinct from the
536
+ * generic level-based inference — e.g. to mark a `STEP_RETRY` event:
537
+ *
538
+ * ```typescript
539
+ * logger.logEvent(EngineLogType.STEP_RETRY, LogLevel.WARN, 'Retrying step', {
540
+ * stepId: 'http-request',
541
+ * attempt: 2,
542
+ * }, 'runtime');
543
+ * ```
798
544
  */
799
- emitEvent(event) {
800
- const listeners = this.eventListeners.get(event.type) || [];
801
- for (const listener of listeners) {
802
- try {
803
- listener(event);
804
- }
805
- catch (error) {
806
- // Don't let listener errors crash the logger
807
- console.error('Error in log event listener:', error);
808
- }
809
- }
810
- // Also emit to wildcard listeners
811
- const wildcardListeners = this.eventListeners.get(EngineLogType.INFO) || [];
812
- for (const listener of wildcardListeners) {
813
- try {
814
- listener(event);
815
- }
816
- catch (error) {
817
- console.error('Error in wildcard log event listener:', error);
818
- }
819
- }
545
+ logEvent(type, level, message, context, category) {
546
+ this._logWithCategory(level, message, context, undefined, category, type);
820
547
  }
548
+ // ─── History Queries ──────────────────────────────────────────────────────
821
549
  /**
822
- * Subscribe to specific log event types
823
- */
824
- on(type, listener) {
825
- if (!this.eventListeners.has(type)) {
826
- this.eventListeners.set(type, []);
827
- }
828
- this.eventListeners.get(type).push(listener);
829
- // Return unsubscribe function
830
- return () => {
831
- const listeners = this.eventListeners.get(type);
832
- if (listeners) {
833
- const index = listeners.indexOf(listener);
834
- if (index > -1) {
835
- listeners.splice(index, 1);
836
- }
550
+ * Return log events that match every supplied filter field.
551
+ * Omit a field to skip that filter.
552
+ *
553
+ * ```typescript
554
+ * // All runtime errors for the current workflow
555
+ * logger.filterHistory({ category: 'runtime', withErrors: true });
556
+ *
557
+ * // All analysis events since the explain started
558
+ * const since = new Date();
559
+ * logger.filterHistory({ category: 'analysis', since });
560
+ * ```
561
+ */
562
+ filterHistory(filter = {}) {
563
+ return this.logHistory.filter(event => {
564
+ if (filter.category && event.category !== filter.category)
565
+ return false;
566
+ if (filter.type && event.type !== filter.type)
567
+ return false;
568
+ if (filter.since && event.timestamp < filter.since)
569
+ return false;
570
+ if (filter.until && event.timestamp > filter.until)
571
+ return false;
572
+ if (filter.source && event.source !== filter.source)
573
+ return false;
574
+ if (filter.withErrors && !event.error)
575
+ return false;
576
+ if (filter.workflowName) {
577
+ const wf = event.context?.['workflow'];
578
+ if (!wf || wf['name'] !== filter.workflowName)
579
+ return false;
837
580
  }
838
- };
839
- }
840
- /**
841
- * Internal log method
842
- */
843
- /**
844
- * Internal log method (category and source required)
845
- */
846
- /**
847
- * Internal log method (category and source required)
848
- * Uses EngineLog interface for strong typing.
849
- */
850
- log(level, message, context, error, category = LogCategoryEnum.SYSTEM, source) {
851
- // Check if this level should be logged (using severity for better performance)
852
- if (!this.shouldLogLevel(level)) {
853
- return;
854
- }
855
- // Create log entry
856
- const entry = createLogEntry(level, message, {
857
- source: this.config.source,
858
- context,
859
- error,
581
+ return true;
860
582
  });
861
- // Add basic logs to buffer as well (for comprehensive JSON output)
862
- // Use EngineLog type for legacy compatibility
863
- const logLegacy = {
864
- timestamp: Date.now(),
865
- level: this.mapLogLevelToString(level),
866
- category: category || this.config.category,
867
- source: source || this.config.source,
868
- message,
869
- context,
870
- };
871
- // Actually use logLegacy: output to console for legacy consumers
872
- console.debug('Legacy log:', logLegacy);
873
- // Also create EngineLogEvent for event system
874
- const logEvent = {
875
- type: EngineLogType.INFO,
876
- timestamp: new Date(),
877
- message,
878
- category: category || this.config.category,
879
- source: source || this.config.source,
880
- context,
881
- ...(error ? { error } : {}),
882
- };
883
- this.addToBuffer(logEvent);
884
- // Optionally: expose legacy log for debugging
885
- // console.debug('Legacy log:', logLegacy);
886
- // Format and output
887
- const formatted = formatLog(entry, this.formatOptions);
888
- console.log(formatted);
889
583
  }
890
584
  /**
891
- * Map LogLevel to EngineLogType
892
- */
893
- /**
894
- * Map LogLevel to string for EngineLog.level
585
+ * Return all history entries grouped by category.
586
+ * Convenience wrapper around `filterHistory`.
895
587
  */
896
- mapLogLevelToString(level) {
897
- switch (level) {
898
- case LogLevel.DEBUG:
899
- case LogLevel.INFO:
900
- return 'info';
901
- case LogLevel.WARN:
902
- return 'warn';
903
- case LogLevel.ERROR:
904
- case LogLevel.FATAL:
905
- return 'error';
906
- default:
907
- return 'info';
588
+ getHistoryByCategory() {
589
+ const result = {};
590
+ for (const event of this.logHistory) {
591
+ result[event.category] = result[event.category] ?? [];
592
+ result[event.category].push(event);
908
593
  }
594
+ return result;
909
595
  }
910
596
  /**
911
597
  * Check if a level should be logged using severity comparison
@@ -1030,275 +716,37 @@ export class EngineLogger {
1030
716
  isErrorEnabled() {
1031
717
  return this.willLog(LogLevel.ERROR);
1032
718
  }
1033
- // ============================================================================
1034
- // JSON LOG BUFFER MANAGEMENT
1035
- // ============================================================================
1036
- /**
1037
- * Add event to JSON log buffer
1038
- */
1039
- addToBuffer(event) {
1040
- // Add to buffer
1041
- this.logBuffer.push(event);
1042
- // Prevent buffer overflow
1043
- if (this.logBuffer.length > this.maxBufferSize) {
1044
- this.logBuffer.shift(); // Remove oldest log
1045
- }
1046
- }
1047
- /**
1048
- * Get all logs as JSON array
1049
- * This is what CLI, API, dashboards, and explanation system will consume
1050
- */
1051
- getJSONLogs() {
1052
- return [...this.logBuffer]; // Return copy to prevent external mutations
1053
- }
1054
- /**
1055
- * Get logs filtered by type
1056
- */
1057
- getLogsByType(type) {
1058
- return this.logBuffer.filter(log => log.type === type);
1059
- }
1060
- /**
1061
- * Get logs filtered by time range
1062
- */
1063
- getLogsByTimeRange(startTime, endTime) {
1064
- return this.logBuffer.filter(log => log.timestamp >= startTime && log.timestamp <= endTime);
1065
- }
1066
- /**
1067
- * Get logs filtered by multiple criteria
1068
- */
1069
- getLogsFiltered(filter) {
1070
- let filtered = this.logBuffer;
1071
- if (filter.types) {
1072
- filtered = filtered.filter(log => filter.types.includes(log.type));
1073
- }
1074
- if (filter.startTime) {
1075
- filtered = filtered.filter(log => log.timestamp >= filter.startTime);
1076
- }
1077
- if (filter.endTime) {
1078
- filtered = filtered.filter(log => log.timestamp <= filter.endTime);
1079
- }
1080
- if (filter.hasError !== undefined) {
1081
- filtered = filtered.filter(log => filter.hasError ? !!log.error : !log.error);
1082
- }
1083
- if (filter.searchText) {
1084
- const searchLower = filter.searchText.toLowerCase();
1085
- filtered = filtered.filter(log => log.message.toLowerCase().includes(searchLower));
1086
- }
1087
- return filtered;
1088
- }
1089
- /**
1090
- * Get logs by category: Parse events
1091
- */
1092
- getParseLogEvents() {
1093
- return this.logBuffer.filter(log => log.type === EngineLogType.WORKFLOW_VALIDATION);
1094
- }
1095
- /**
1096
- * Get logs by category: Validation events
1097
- */
1098
- getValidationLogEvents() {
1099
- return this.logBuffer.filter(log => log.type === EngineLogType.WORKFLOW_VALIDATION ||
1100
- log.type === EngineLogType.VALIDATION_ERROR);
1101
- }
1102
- /**
1103
- * Get logs by category: Execution events
1104
- */
1105
- getExecutionLogEvents() {
1106
- return this.logBuffer.filter(log => log.type === EngineLogType.WORKFLOW_STARTED ||
1107
- log.type === EngineLogType.WORKFLOW_COMPLETED ||
1108
- log.type === EngineLogType.WORKFLOW_FAILED ||
1109
- log.type === EngineLogType.STEP_STARTED ||
1110
- log.type === EngineLogType.STEP_COMPLETED ||
1111
- log.type === EngineLogType.STEP_FAILED ||
1112
- log.type === EngineLogType.STEP_RETRY ||
1113
- log.type === EngineLogType.STEP_TIMEOUT ||
1114
- log.type === EngineLogType.EXECUTION_TIME ||
1115
- log.type === EngineLogType.QUEUE_PROCESSING);
1116
- }
1117
- /**
1118
- * Get logs by category: Error events
1119
- */
1120
- getErrorLogEvents() {
1121
- return this.logBuffer.filter(log => log.type === EngineLogType.ERROR_DETECTED ||
1122
- log.type === EngineLogType.ERROR_DEBUGGED ||
1123
- log.type === EngineLogType.VALIDATION_ERROR ||
1124
- log.type === EngineLogType.WORKFLOW_FAILED ||
1125
- log.type === EngineLogType.STEP_FAILED ||
1126
- log.type === EngineLogType.ADAPTER_FAILED ||
1127
- log.type === EngineLogType.ERROR);
1128
- }
1129
- /**
1130
- * Get logs by category: Lifecycle events
1131
- */
1132
- getLifecycleLogEvents() {
1133
- return this.logBuffer.filter(log => log.type === EngineLogType.ENGINE_STARTED ||
1134
- log.type === EngineLogType.ENGINE_STOPPED ||
1135
- log.type === EngineLogType.ADAPTER_LOADED ||
1136
- log.type === EngineLogType.PLUGIN_INSTALLED ||
1137
- log.type === EngineLogType.PLUGIN_VERIFIED);
1138
- }
1139
- /**
1140
- * Get logs by category: Performance events
1141
- */
1142
- getPerformanceLogEvents() {
1143
- return this.logBuffer.filter(log => log.type === EngineLogType.PERFORMANCE_METRIC ||
1144
- log.type === EngineLogType.EXECUTION_TIME);
1145
- }
1146
- /**
1147
- * Export logs as formatted JSON string
1148
- */
1149
- exportLogsAsJSON(pretty = true) {
1150
- return JSON.stringify(this.logBuffer, null, pretty ? 2 : 0);
1151
- }
1152
- /**
1153
- * Export logs grouped by type
1154
- */
1155
- exportLogsGrouped() {
1156
- const grouped = {};
1157
- for (const log of this.logBuffer) {
1158
- const type = log.category;
1159
- if (!grouped[type]) {
1160
- grouped[type] = [];
1161
- }
1162
- grouped[type].push(log);
1163
- }
1164
- return grouped;
1165
- }
1166
- /**
1167
- * Get log statistics
1168
- */
1169
- getLogStats() {
1170
- const stats = {
1171
- total: this.logBuffer.length,
1172
- byType: {},
1173
- withErrors: 0,
1174
- withMetrics: 0,
1175
- timeRange: {
1176
- first: this.logBuffer[0]?.timestamp,
1177
- last: this.logBuffer[this.logBuffer.length - 1]?.timestamp,
1178
- },
1179
- };
1180
- for (const log of this.logBuffer) {
1181
- // Count by type
1182
- stats.byType[log.type] = (stats.byType[log.type] || 0) + 1;
1183
- // Count errors and metrics
1184
- if (log.error)
1185
- stats.withErrors++;
1186
- if (log.metrics)
1187
- stats.withMetrics++;
1188
- }
1189
- return stats;
1190
- }
1191
- /**
1192
- * Clear log buffer
1193
- */
1194
- clearLogs() {
1195
- this.logBuffer = [];
1196
- }
1197
- /**
1198
- * Set maximum buffer size
1199
- */
1200
- setMaxBufferSize(size) {
1201
- this.maxBufferSize = size;
1202
- // Trim buffer if needed
1203
- while (this.logBuffer.length > this.maxBufferSize) {
1204
- this.logBuffer.shift();
1205
- }
1206
- }
1207
- /**
1208
- * Get current buffer size
1209
- */
1210
- getBufferSize() {
1211
- return this.logBuffer.length;
1212
- }
1213
- /**
1214
- * Export logs in a comprehensive format for consumers (CLI, API, dashboards)
1215
- */
1216
- exportLogs() {
1217
- const stats = this.getLogStats();
1218
- const grouped = this.exportLogsGrouped();
1219
- // Build execution summary from logs
1220
- const execution = this.buildExecutionSummary();
1221
- return {
1222
- raw: this.getJSONLogs(),
1223
- grouped,
1224
- stats,
1225
- execution,
1226
- };
1227
- }
1228
- /**
1229
- * Build execution summary from collected logs
1230
- * This powers dynamic explanation generation
1231
- */
1232
- buildExecutionSummary() {
1233
- const workflowLogs = this.logBuffer.filter(log => log.type === EngineLogType.WORKFLOW_STARTED ||
1234
- log.type === EngineLogType.WORKFLOW_COMPLETED ||
1235
- log.type === EngineLogType.WORKFLOW_FAILED);
1236
- const stepLogs = this.logBuffer.filter(log => log.type === EngineLogType.STEP_STARTED ||
1237
- log.type === EngineLogType.STEP_COMPLETED ||
1238
- log.type === EngineLogType.STEP_FAILED);
1239
- const errorLogs = this.logBuffer.filter(log => log.error !== undefined ||
1240
- log.type === EngineLogType.ERROR_DETECTED ||
1241
- log.type === EngineLogType.VALIDATION_ERROR);
1242
- const metricLogs = this.logBuffer.filter(log => log.type === EngineLogType.PERFORMANCE_METRIC ||
1243
- log.type === EngineLogType.EXECUTION_TIME);
1244
- // Extract workflow info
1245
- let workflow;
1246
- const workflowStarted = workflowLogs.find(log => log.type === EngineLogType.WORKFLOW_STARTED);
1247
- const workflowEnded = workflowLogs.find(log => log.type === EngineLogType.WORKFLOW_COMPLETED ||
1248
- log.type === EngineLogType.WORKFLOW_FAILED);
1249
- if (workflowStarted && workflowEnded) {
1250
- workflow = {
1251
- name: workflowStarted.context?.workflowName || 'Unknown',
1252
- status: workflowEnded.type === EngineLogType.WORKFLOW_COMPLETED ? 'completed' : 'failed',
1253
- duration: workflowEnded.metrics?.duration,
1254
- };
1255
- }
1256
- // Extract step info
1257
- const steps = [];
1258
- const stepMap = new Map();
1259
- for (const log of stepLogs) {
1260
- const stepId = log.context?.stepId || '';
1261
- const stepName = log.context?.stepName || '';
1262
- if (!stepMap.has(stepId)) {
1263
- stepMap.set(stepId, { id: stepId, name: stepName });
1264
- }
1265
- const step = stepMap.get(stepId);
1266
- if (log.type === EngineLogType.STEP_COMPLETED) {
1267
- step.status = 'completed';
1268
- step.duration = log.metrics?.duration;
1269
- }
1270
- else if (log.type === EngineLogType.STEP_FAILED) {
1271
- step.status = 'failed';
1272
- }
1273
- else {
1274
- step.status = 'started';
1275
- }
1276
- }
1277
- steps.push(...stepMap.values());
1278
- // Extract error info
1279
- const errors = errorLogs.map(log => ({
1280
- step: log.context?.stepId || log.context?.stepName,
1281
- message: log.message,
1282
- error: log.error,
1283
- }));
1284
- // Extract metrics
1285
- const metrics = metricLogs.map(log => ({
1286
- label: log.context?.label || 'Unknown',
1287
- duration: log.metrics?.duration,
1288
- memory: log.metrics?.memory,
1289
- }));
1290
- return {
1291
- workflow,
1292
- steps,
1293
- errors,
1294
- metrics,
1295
- };
1296
- }
1297
719
  }
1298
720
  /**
1299
721
  * Create a logger instance from engine config
1300
722
  */
1301
- export function createEngineLogger(config) {
1302
- return new EngineLogger(config);
723
+ export function createEngineLogger(logLevel, verbose = false) {
724
+ // Silent mode - no logger
725
+ if (logLevel === 'silent') {
726
+ return null;
727
+ }
728
+ // Map engine log level to ecosystem LogLevel
729
+ const levelMap = {
730
+ debug: LogLevel.DEBUG,
731
+ info: LogLevel.INFO,
732
+ warn: LogLevel.WARN,
733
+ error: LogLevel.ERROR,
734
+ fatal: LogLevel.FATAL,
735
+ };
736
+ const level = levelMap[logLevel] || LogLevel.INFO;
737
+ return new EngineLogger({
738
+ level,
739
+ format: verbose ? 'pretty' : 'json',
740
+ colors: true,
741
+ timestamp: true,
742
+ source: 'Orbyt',
743
+ category: LogCategoryEnum.SYSTEM,
744
+ structuredEvents: true,
745
+ });
1303
746
  }
747
+ /**
748
+ * Re-export formatTimestamp for external use
749
+ * Allows other parts of the system to format timestamps consistently
750
+ */
751
+ export { formatTimestamp };
1304
752
  //# sourceMappingURL=EngineLogger.js.map