@orbytautomation/engine 0.4.1 → 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 (57) hide show
  1. package/dist/core/OrbytEngine.d.ts +3 -21
  2. package/dist/core/OrbytEngine.d.ts.map +1 -1
  3. package/dist/core/OrbytEngine.js +48 -139
  4. package/dist/core/OrbytEngine.js.map +1 -1
  5. package/dist/errors/ErrorDebugger.d.ts +78 -25
  6. package/dist/errors/ErrorDebugger.d.ts.map +1 -1
  7. package/dist/errors/ErrorDebugger.js +383 -2
  8. package/dist/errors/ErrorDebugger.js.map +1 -1
  9. package/dist/errors/ErrorDetector.d.ts +107 -5
  10. package/dist/errors/ErrorDetector.d.ts.map +1 -1
  11. package/dist/errors/ErrorDetector.js +195 -40
  12. package/dist/errors/ErrorDetector.js.map +1 -1
  13. package/dist/errors/ErrorFormatter.d.ts +51 -0
  14. package/dist/errors/ErrorFormatter.d.ts.map +1 -1
  15. package/dist/errors/ErrorFormatter.js +128 -0
  16. package/dist/errors/ErrorFormatter.js.map +1 -1
  17. package/dist/errors/ErrorHandler.d.ts +93 -7
  18. package/dist/errors/ErrorHandler.d.ts.map +1 -1
  19. package/dist/errors/ErrorHandler.js +91 -42
  20. package/dist/errors/ErrorHandler.js.map +1 -1
  21. package/dist/errors/OrbytError.d.ts +28 -0
  22. package/dist/errors/OrbytError.d.ts.map +1 -1
  23. package/dist/errors/OrbytError.js.map +1 -1
  24. package/dist/errors/SecurityErrors.d.ts +2 -25
  25. package/dist/errors/SecurityErrors.d.ts.map +1 -1
  26. package/dist/errors/SecurityErrors.js +5 -119
  27. package/dist/errors/SecurityErrors.js.map +1 -1
  28. package/dist/errors/WorkflowError.d.ts +11 -1
  29. package/dist/errors/WorkflowError.d.ts.map +1 -1
  30. package/dist/errors/WorkflowError.js +104 -0
  31. package/dist/errors/WorkflowError.js.map +1 -1
  32. package/dist/loader/WorkflowLoader.d.ts +77 -5
  33. package/dist/loader/WorkflowLoader.d.ts.map +1 -1
  34. package/dist/loader/WorkflowLoader.js +170 -35
  35. package/dist/loader/WorkflowLoader.js.map +1 -1
  36. package/dist/logging/EngineLogger.d.ts +240 -289
  37. package/dist/logging/EngineLogger.d.ts.map +1 -1
  38. package/dist/logging/EngineLogger.js +424 -842
  39. package/dist/logging/EngineLogger.js.map +1 -1
  40. package/dist/logging/LoggerManager.d.ts +111 -22
  41. package/dist/logging/LoggerManager.d.ts.map +1 -1
  42. package/dist/logging/LoggerManager.js +138 -32
  43. package/dist/logging/LoggerManager.js.map +1 -1
  44. package/dist/logging/index.d.ts.map +1 -1
  45. package/dist/logging/index.js.map +1 -1
  46. package/dist/parser/WorkflowParser.d.ts.map +1 -1
  47. package/dist/parser/WorkflowParser.js +0 -5
  48. package/dist/parser/WorkflowParser.js.map +1 -1
  49. package/dist/security/ReservedFields.d.ts +11 -0
  50. package/dist/security/ReservedFields.d.ts.map +1 -1
  51. package/dist/security/ReservedFields.js +71 -34
  52. package/dist/security/ReservedFields.js.map +1 -1
  53. package/dist/types/log-types.d.ts +134 -2
  54. package/dist/types/log-types.d.ts.map +1 -1
  55. package/dist/types/log-types.js +10 -0
  56. package/dist/types/log-types.js.map +1 -1
  57. package/package.json +1 -1
@@ -20,34 +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 } 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
117
  config;
39
118
  formatOptions;
40
- eventListeners;
41
- logBuffer; // Structured JSON log buffer
42
- 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;
43
131
  constructor(config) {
44
132
  this.config = {
45
133
  level: config.level,
46
- format: config.format || 'text',
134
+ format: config.format || 'json',
47
135
  colors: config.colors ?? true,
48
136
  timestamp: config.timestamp ?? true,
49
137
  source: config.source || 'Orbyt',
50
- structuredEvents: config.structuredEvents ?? true, // Default to true for JSON collection
138
+ structuredEvents: config.structuredEvents ?? true,
139
+ category: config.category || LogCategoryEnum.SYSTEM,
140
+ maxHistorySize: config.maxHistorySize ?? 0,
51
141
  };
52
142
  this.formatOptions = {
53
143
  format: this.config.format,
@@ -55,8 +145,11 @@ export class EngineLogger {
55
145
  timestamp: this.config.timestamp,
56
146
  includeSource: true,
57
147
  };
58
- this.eventListeners = new Map();
59
- 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);
60
153
  }
61
154
  /**
62
155
  * Log a debug message
@@ -88,670 +181,417 @@ export class EngineLogger {
88
181
  fatal(message, error, context) {
89
182
  this.log(LogLevel.FATAL, message, context, error);
90
183
  }
91
- // ============================================================================
92
- // WORKFLOW LIFECYCLE LOGGING
93
- // ============================================================================
94
- /**
95
- * Log workflow started event
96
- */
97
- workflowStarted(workflowName, context) {
98
- this.logEvent({
99
- type: EngineLogType.WORKFLOW_STARTED,
100
- timestamp: new Date(),
101
- message: `Workflow "${workflowName}" started`,
102
- context,
103
- });
104
- }
105
- /**
106
- * Log workflow completed event
107
- */
108
- workflowCompleted(workflowName, duration, context) {
109
- this.logEvent({
110
- type: EngineLogType.WORKFLOW_COMPLETED,
111
- timestamp: new Date(),
112
- message: `Workflow "${workflowName}" completed successfully`,
113
- context,
114
- metrics: { duration },
115
- });
116
- }
117
- /**
118
- * Log workflow failed event
119
- */
120
- workflowFailed(workflowName, error, duration, context) {
121
- this.logEvent({
122
- type: EngineLogType.WORKFLOW_FAILED,
123
- timestamp: new Date(),
124
- message: `Workflow "${workflowName}" failed: ${error.message}`,
125
- context,
126
- error,
127
- metrics: { duration },
128
- });
129
- }
130
- /**
131
- * Log workflow validation event
132
- */
133
- workflowValidation(workflowName, isValid, errors) {
134
- this.logEvent({
135
- type: EngineLogType.WORKFLOW_VALIDATION,
136
- timestamp: new Date(),
137
- message: `Workflow "${workflowName}" validation: ${isValid ? 'PASSED' : 'FAILED'}`,
138
- context: errors ? { errors } : undefined,
139
- });
140
- }
141
- // ============================================================================
142
- // STEP LIFECYCLE LOGGING
143
- // ============================================================================
144
- /**
145
- * Log step started event
146
- */
147
- stepStarted(stepId, stepName, context) {
148
- this.logEvent({
149
- type: EngineLogType.STEP_STARTED,
150
- timestamp: new Date(),
151
- message: `Step "${stepName}" (${stepId}) started`,
152
- context,
153
- });
154
- }
155
- /**
156
- * Log step completed event
157
- */
158
- stepCompleted(stepId, stepName, duration, context) {
159
- this.logEvent({
160
- type: EngineLogType.STEP_COMPLETED,
161
- timestamp: new Date(),
162
- message: `Step "${stepName}" (${stepId}) completed`,
163
- context,
164
- metrics: { duration },
165
- });
166
- }
167
- /**
168
- * Log step failed event
169
- */
170
- stepFailed(stepId, stepName, error, context) {
171
- this.logEvent({
172
- type: EngineLogType.STEP_FAILED,
173
- timestamp: new Date(),
174
- message: `Step "${stepName}" (${stepId}) failed: ${error.message}`,
175
- context,
176
- error,
177
- });
178
- }
179
- /**
180
- * Log step retry event
181
- */
182
- stepRetry(stepId, stepName, attempt, maxAttempts) {
183
- this.logEvent({
184
- type: EngineLogType.STEP_RETRY,
185
- timestamp: new Date(),
186
- message: `Step "${stepName}" (${stepId}) retry ${attempt}/${maxAttempts}`,
187
- context: { attempt, maxAttempts },
188
- });
189
- }
190
- /**
191
- * Log step timeout event
192
- */
193
- stepTimeout(stepId, stepName, timeout) {
194
- this.logEvent({
195
- type: EngineLogType.STEP_TIMEOUT,
196
- timestamp: new Date(),
197
- message: `Step "${stepName}" (${stepId}) timed out after ${timeout}ms`,
198
- context: { timeout },
199
- });
200
- }
201
- // ============================================================================
202
- // EXPLANATION LOGGING
203
- // ============================================================================
204
- /**
205
- * Log explanation generated event
206
- */
207
- explanationGenerated(workflowName, stepCount, strategy) {
208
- this.logEvent({
209
- type: EngineLogType.EXPLANATION_GENERATED,
210
- timestamp: new Date(),
211
- message: `Explanation generated for "${workflowName}" (${stepCount} steps, ${strategy} strategy)`,
212
- context: { workflowName, stepCount, strategy },
213
- });
214
- }
215
184
  /**
216
- * Log circular dependencies detected
217
- */
218
- explanationCycles(workflowName, cycles) {
219
- this.logEvent({
220
- type: EngineLogType.EXPLANATION_CYCLES,
221
- timestamp: new Date(),
222
- message: `Circular dependencies detected in "${workflowName}"`,
223
- context: { cycles },
224
- });
225
- }
226
- // ============================================================================
227
- // ADAPTER & PLUGIN LOGGING
228
- // ============================================================================
229
- /**
230
- * Log adapter loaded event
231
- */
232
- adapterLoaded(adapterName, version) {
233
- this.logEvent({
234
- type: EngineLogType.ADAPTER_LOADED,
235
- timestamp: new Date(),
236
- message: `Adapter "${adapterName}" loaded${version ? ` (v${version})` : ''}`,
237
- context: { adapterName, version },
238
- });
239
- }
240
- /**
241
- * Log adapter failed event
185
+ * Log with a custom level
242
186
  */
243
- adapterFailed(adapterName, error) {
244
- this.logEvent({
245
- type: EngineLogType.ADAPTER_FAILED,
246
- timestamp: new Date(),
247
- message: `Adapter "${adapterName}" failed: ${error.message}`,
248
- context: { adapterName },
249
- error,
250
- });
187
+ logWithLevel(level, message, context) {
188
+ this.log(level, message, context);
251
189
  }
252
190
  /**
253
- * Log plugin installed event
191
+ * Log only if severity meets minimum threshold
254
192
  */
255
- pluginInstalled(pluginName, source) {
256
- this.logEvent({
257
- type: EngineLogType.PLUGIN_INSTALLED,
258
- timestamp: new Date(),
259
- message: `Plugin "${pluginName}" installed from ${source}`,
260
- context: { pluginName, source },
261
- });
193
+ logIfSeverity(minSeverity, level, message, context) {
194
+ if (LogLevelSeverity[level] >= minSeverity) {
195
+ this.log(level, message, context);
196
+ }
262
197
  }
263
198
  /**
264
- * Log plugin verified event
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(...)`.
265
203
  */
266
- pluginVerified(pluginName, verified) {
267
- this.logEvent({
268
- type: EngineLogType.PLUGIN_VERIFIED,
269
- timestamp: new Date(),
270
- message: `Plugin "${pluginName}" verification: ${verified ? 'PASSED' : 'FAILED'}`,
271
- context: { pluginName, verified },
272
- });
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
+ }
273
227
  }
274
- // ============================================================================
275
- // ERROR & DEBUG LOGGING
276
- // ============================================================================
228
+ // ─── Core log dispatch ────────────────────────────────────────────────────
277
229
  /**
278
- * Log error detected event
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.
279
241
  */
280
- errorDetected(error, context) {
281
- this.logEvent({
282
- 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,
283
267
  timestamp: new Date(),
284
- message: `Error detected: ${error.message}`,
285
- context,
268
+ message,
269
+ category,
270
+ source: this.config.source,
271
+ context: finalContext,
286
272
  error,
287
- });
288
- }
289
- /**
290
- * Log error debugged event (with debugging info)
291
- */
292
- errorDebugged(error, debugInfo) {
293
- this.logEvent({
294
- type: EngineLogType.ERROR_DEBUGGED,
295
- timestamp: new Date(),
296
- message: `Error debugged: ${error.message}`,
297
- 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,
298
284
  error,
299
285
  });
286
+ const formatted = formatLog(entry, this.formatOptions);
287
+ console.log(formatted);
300
288
  }
301
289
  /**
302
- * Log validation error event
303
- */
304
- validationError(message, errors) {
305
- this.logEvent({
306
- type: EngineLogType.VALIDATION_ERROR,
307
- timestamp: new Date(),
308
- message,
309
- context: { errors },
310
- });
311
- }
312
- // ============================================================================
313
- // PERFORMANCE LOGGING
314
- // ============================================================================
315
- /**
316
- * Log performance metric
290
+ * Private shorthand uses the default category from config.
291
+ * Prefer the categorised sub-loggers (`runtime`, `analysis`, etc.) for new code.
317
292
  */
318
- performanceMetric(label, metrics) {
319
- this.logEvent({
320
- type: EngineLogType.PERFORMANCE_METRIC,
321
- timestamp: new Date(),
322
- message: `Performance: ${label}`,
323
- metrics,
324
- });
293
+ log(level, message, context, error) {
294
+ this._logWithCategory(level, message, context, error);
325
295
  }
296
+ // ─── Log History ────────────────────────────────────────────────────
326
297
  /**
327
- * Log execution time
298
+ * Export collected logs as a structured ExportedLogs object.
299
+ * Includes `byCategory` statistics and the active `workflowContext` snapshot.
328
300
  */
329
- executionTime(label, duration, context) {
330
- this.logEvent({
331
- type: EngineLogType.EXECUTION_TIME,
332
- timestamp: new Date(),
333
- message: `${label}: ${duration}ms`,
334
- context,
335
- metrics: { duration },
336
- });
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
+ };
337
336
  }
338
- // ============================================================================
339
- // FIELD-LEVEL EXECUTION LOGGING (Dynamic Explanation)
340
- // ============================================================================
341
337
  /**
342
- * Log field execution - captures every field that gets executed
343
- * This enables dynamic explanation generation from runtime logs
338
+ * Get all collected logs as a JSON string
344
339
  */
345
- fieldExecution(type, field, value, context) {
346
- // Filter out internal fields (starting with _)
347
- if (field.startsWith('_'))
348
- return;
349
- this.debug(`[Field] ${type}.${field} = ${this.formatValue(value)}`, {
350
- fieldType: type,
351
- fieldName: field,
352
- value: this.sanitizeValue(value),
353
- ...context,
354
- });
340
+ getJSONLogs() {
341
+ return JSON.stringify(this.exportLogs(), null, 2);
355
342
  }
356
343
  /**
357
- * Log input processing
344
+ * Clear the log history (does NOT clear the workflow context)
358
345
  */
359
- inputProcessed(inputName, value, source) {
360
- this.debug(`[Input] ${inputName} received from ${source}`, {
361
- input: inputName,
362
- source,
363
- value: this.sanitizeValue(value),
364
- });
346
+ clearHistory() {
347
+ this.logHistory = [];
365
348
  }
349
+ // ─── Workflow Context ─────────────────────────────────────────────────────
366
350
  /**
367
- * Log output generation
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
+ * ```
368
364
  */
369
- outputGenerated(outputName, value, step) {
370
- this.debug(`[Output] ${outputName} generated${step ? ` by ${step}` : ''}`, {
371
- output: outputName,
372
- step,
373
- value: this.sanitizeValue(value),
374
- });
365
+ setWorkflowContext(ctx) {
366
+ this.workflowContext = { ...ctx };
375
367
  }
376
368
  /**
377
- * Log context variable access
369
+ * Detach the workflow context (e.g. after a run completes).
370
+ * Logs emitted after this call will no longer include the `workflow` field.
378
371
  */
379
- contextAccessed(variable, value, step) {
380
- this.debug(`[Context] ${variable} accessed by ${step}`, {
381
- variable,
382
- step,
383
- value: this.sanitizeValue(value),
384
- });
372
+ clearWorkflowContext() {
373
+ this.workflowContext = null;
385
374
  }
386
375
  /**
387
- * Log secret access (without exposing values)
376
+ * Returns a read-only snapshot of the currently active workflow context,
377
+ * or `null` if none has been set.
388
378
  */
389
- secretAccessed(secretKey, step) {
390
- this.debug(`[Secret] ${secretKey} accessed by ${step}`, {
391
- secretKey,
392
- step,
393
- exposed: false, // Never log secret values
394
- });
379
+ getWorkflowContext() {
380
+ return this.workflowContext ? { ...this.workflowContext } : null;
395
381
  }
396
382
  /**
397
- * Log variable resolution
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.
398
395
  */
399
- variableResolved(variable, resolvedValue, context) {
400
- this.debug(`[Variable] ${variable} resolved`, {
401
- variable,
402
- resolved: this.sanitizeValue(resolvedValue),
403
- ...context,
404
- });
396
+ patchWorkflowContext(partial) {
397
+ this.workflowContext = { ...(this.workflowContext ?? {}), ...partial };
405
398
  }
399
+ // ─── Dynamic Context Helpers ──────────────────────────────────────────────
406
400
  /**
407
- * Log adapter action execution
408
- */
409
- adapterActionExecuted(adapter, action, duration, success) {
410
- const message = `[Adapter] ${adapter}.${action} ${success ? 'completed' : 'failed'} in ${duration}ms`;
411
- const context = { adapter, action, duration, success };
412
- if (success) {
413
- this.info(message, context);
414
- }
415
- else {
416
- this.error(message, undefined, context);
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;
417
419
  }
418
- }
419
- // ============================================================================
420
- // PARSING & VALIDATION LOGGING
421
- // ============================================================================
422
- /**
423
- * Log parsing start
424
- */
425
- parsingStarted(source, format) {
426
- this.debug(`[Parser] Parsing ${format} from ${source}`, {
427
- source,
428
- format,
429
- });
430
- }
431
- /**
432
- * Log parsing success
433
- */
434
- parsingCompleted(source, duration, stats) {
435
- this.info(`[Parser] Parsed successfully in ${duration}ms`, {
436
- source,
437
- duration,
438
- ...stats,
439
- });
420
+ return ctx;
440
421
  }
441
422
  /**
442
- * Log parsing error
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
+ * ```
443
431
  */
444
- parsingFailed(source, error, context) {
445
- this.error(`[Parser] Parsing failed: ${error.message}`, error, {
446
- source,
447
- ...context,
448
- });
432
+ workflowCtx() {
433
+ return this.workflowContext ? { ...this.workflowContext } : {};
449
434
  }
435
+ // ─── Dynamic Lifecycle Helpers ────────────────────────────────────────────
450
436
  /**
451
- * Log validation start
437
+ * Log workflow execution started (runtime category).
438
+ * Called at the top of `WorkflowExecutor.execute()`.
452
439
  */
453
- validationStarted(target, type) {
454
- this.debug(`[Validator] Validating ${type}: ${target}`, {
455
- target,
456
- type,
457
- });
440
+ workflowStarted(workflowName, context) {
441
+ this.runtime.info(`Workflow "${workflowName}" started`, { workflowName, ...context });
458
442
  }
459
443
  /**
460
- * Log validation success
444
+ * Log a workflow input field that was resolved (runtime category).
461
445
  */
462
- validationPassed(target, type, duration) {
463
- this.info(`[Validator] Validation passed: ${target}`, {
464
- target,
465
- type,
466
- duration,
467
- });
446
+ inputProcessed(key, value, source) {
447
+ this.runtime.debug(`Input resolved: ${key}`, { key, value, source });
468
448
  }
469
449
  /**
470
- * Log validation failure
450
+ * Log a context field used during execution (runtime category).
471
451
  */
472
- validationFailed(target, type, errors) {
473
- this.error(`[Validator] Validation failed: ${target}`, undefined, {
474
- target,
475
- type,
476
- errors,
477
- errorCount: errors.length,
478
- });
452
+ fieldExecution(fieldType, key, value) {
453
+ this.runtime.debug(`Context field accessed: ${fieldType}.${key}`, { fieldType, key, value });
479
454
  }
480
- // ============================================================================
481
- // ERROR DETECTION & DEBUGGING LOGGING
482
- // ============================================================================
483
455
  /**
484
- * 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
+ * ```
485
460
  */
486
- errorEnriched(error, debugInfo) {
487
- this.debug(`[ErrorDetector] Error enriched with debugging info`, {
488
- errorType: error.name,
489
- errorMessage: error.message,
490
- ...debugInfo,
491
- });
461
+ stepStarted(stepId, stepName, context) {
462
+ this._logWithCategory(LogLevel.INFO, `Step "${stepName}" (${stepId}) started`, { stepId, stepName, ...context }, undefined, LogCategoryEnum.RUNTIME, EngineLogType.STEP_STARTED);
492
463
  }
493
464
  /**
494
- * Log error debugging output
465
+ * Log step execution completed (runtime category).
495
466
  */
496
- errorDebugOutput(error, explanation, fixSteps) {
497
- this.info(`[ErrorDebugger] Generated debugging output for ${error.name}`, {
498
- errorType: error.name,
499
- explanation,
500
- fixSteps,
501
- stepCount: fixSteps.length,
502
- });
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);
503
469
  }
504
- // ============================================================================
505
- // EXECUTION PHASE LOGGING
506
- // ============================================================================
507
470
  /**
508
- * Log execution phase start
471
+ * Log step timeout (runtime category).
509
472
  */
510
- phaseStarted(phase, stepIds) {
511
- this.info(`[Execution] Phase ${phase} started with ${stepIds.length} step(s)`, {
512
- phase,
513
- stepIds,
514
- stepCount: stepIds.length,
515
- });
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);
516
475
  }
517
476
  /**
518
- * Log execution phase completion
477
+ * Log step retry attempt (runtime category).
519
478
  */
520
- phaseCompleted(phase, duration, successCount, failureCount) {
521
- this.info(`[Execution] Phase ${phase} completed in ${duration}ms`, {
522
- phase,
523
- duration,
524
- successCount,
525
- failureCount,
526
- totalSteps: successCount + failureCount,
527
- });
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);
528
481
  }
529
- // ============================================================================
530
- // UTILITY METHODS
531
- // ============================================================================
532
482
  /**
533
- * Format value for logging (truncate large values)
483
+ * Log step failure after all attempts (runtime category).
534
484
  */
535
- formatValue(value) {
536
- if (value === null)
537
- return 'null';
538
- if (value === undefined)
539
- return 'undefined';
540
- if (typeof value === 'string') {
541
- return value.length > 100 ? `${value.substring(0, 97)}...` : value;
542
- }
543
- if (typeof value === 'object') {
544
- const str = JSON.stringify(value);
545
- return str.length > 200 ? `${str.substring(0, 197)}...` : str;
546
- }
547
- 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);
548
487
  }
549
488
  /**
550
- * Sanitize value for logging (remove sensitive data)
489
+ * Log adapter action execution result (runtime category).
551
490
  */
552
- sanitizeValue(value) {
553
- if (value === null || value === undefined)
554
- return value;
555
- // For objects, recursively sanitize
556
- if (typeof value === 'object' && !Array.isArray(value)) {
557
- const sanitized = {};
558
- for (const [key, val] of Object.entries(value)) {
559
- // Skip internal fields
560
- if (key.startsWith('_'))
561
- continue;
562
- // Mask sensitive fields
563
- if (this.isSensitiveField(key)) {
564
- sanitized[key] = '***REDACTED***';
565
- }
566
- else if (typeof val === 'object') {
567
- sanitized[key] = this.sanitizeValue(val);
568
- }
569
- else {
570
- sanitized[key] = val;
571
- }
572
- }
573
- return sanitized;
574
- }
575
- 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);
576
494
  }
577
495
  /**
578
- * Check if field name suggests sensitive data
496
+ * Log validation phase started (analysis category).
579
497
  */
580
- isSensitiveField(fieldName) {
581
- const sensitivePatterns = [
582
- 'password', 'secret', 'token', 'key', 'credential',
583
- 'apikey', 'api_key', 'auth', 'private',
584
- ];
585
- const lowerField = fieldName.toLowerCase();
586
- 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);
587
500
  }
588
501
  /**
589
- * Log with a custom level
502
+ * Log validation passed (analysis category).
590
503
  */
591
- logWithLevel(level, message, context) {
592
- 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);
593
506
  }
594
507
  /**
595
- * Log only if severity meets minimum threshold
508
+ * Log validation failure (analysis category).
596
509
  */
597
- logIfSeverity(minSeverity, level, message, context) {
598
- if (LogLevelSeverity[level] >= minSeverity) {
599
- this.log(level, message, context);
600
- }
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);
601
512
  }
602
513
  /**
603
- * Measure and log execution time with appropriate log level based on duration
514
+ * Log parsing started (analysis category).
604
515
  */
605
- async measureExecution(label, fn, thresholds) {
606
- const start = Date.now();
607
- try {
608
- const result = await fn();
609
- const duration = Date.now() - start;
610
- // Choose log level based on duration thresholds
611
- let level = LogLevel.DEBUG;
612
- if (thresholds?.error && duration > thresholds.error) {
613
- level = LogLevel.ERROR;
614
- }
615
- else if (thresholds?.warn && duration > thresholds.warn) {
616
- level = LogLevel.WARN;
617
- }
618
- else {
619
- level = LogLevel.INFO;
620
- }
621
- this.log(level, `${label} completed`, { duration: `${duration}ms` });
622
- return result;
623
- }
624
- catch (error) {
625
- const duration = Date.now() - start;
626
- this.log(LogLevel.ERROR, `${label} failed`, { duration: `${duration}ms` }, error instanceof Error ? error : new Error(String(error)));
627
- throw error;
628
- }
516
+ parsingStarted(format, source) {
517
+ this._logWithCategory(LogLevel.DEBUG, `Parsing started: ${format} from ${source}`, { format, source }, undefined, LogCategoryEnum.ANALYSIS, EngineLogType.INFO);
629
518
  }
630
519
  /**
631
- * Log a structured engine event
520
+ * Log parsing completed (analysis category).
632
521
  */
633
- logEvent(event) {
634
- // Add to JSON buffer (always, regardless of log level)
635
- this.addToBuffer(event);
636
- // Emit event to listeners if structured events enabled
637
- if (this.config.structuredEvents) {
638
- this.emitEvent(event);
639
- }
640
- // Determine log level based on event type
641
- const level = this.getLogLevelForEventType(event.type);
642
- // Check if this level should be logged
643
- if (!this.shouldLogLevel(level)) {
644
- return;
645
- }
646
- // Build context with metrics if present
647
- const fullContext = {
648
- ...event.context,
649
- ...(event.metrics && { metrics: event.metrics }),
650
- };
651
- // Log through standard log method
652
- this.log(level, event.message, fullContext, event.error);
522
+ parsingCompleted(format, duration, context) {
523
+ this._logWithCategory(LogLevel.INFO, `Parsing completed: ${format} in ${duration}ms`, { format, duration, ...context }, undefined, LogCategoryEnum.ANALYSIS, EngineLogType.INFO);
653
524
  }
654
525
  /**
655
- * Get appropriate log level for event type
526
+ * Log parsing failure (analysis category).
656
527
  */
657
- getLogLevelForEventType(type) {
658
- // Error events
659
- if (type.includes('failed') || type.includes('error') || type.includes('timeout')) {
660
- return LogLevel.ERROR;
661
- }
662
- // Warning events
663
- if (type.includes('retry') || type.includes('cycles') || type === EngineLogType.WARNING) {
664
- return LogLevel.WARN;
665
- }
666
- // Debug events
667
- if (type === EngineLogType.DEBUG || type.includes('debug')) {
668
- return LogLevel.DEBUG;
669
- }
670
- // Default to INFO
671
- 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);
672
530
  }
531
+ // ─── Typed Event Logger ───────────────────────────────────────────────────
673
532
  /**
674
- * 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
+ * ```
675
544
  */
676
- emitEvent(event) {
677
- const listeners = this.eventListeners.get(event.type) || [];
678
- for (const listener of listeners) {
679
- try {
680
- listener(event);
681
- }
682
- catch (error) {
683
- // Don't let listener errors crash the logger
684
- console.error('Error in log event listener:', error);
685
- }
686
- }
687
- // Also emit to wildcard listeners
688
- const wildcardListeners = this.eventListeners.get(EngineLogType.INFO) || [];
689
- for (const listener of wildcardListeners) {
690
- try {
691
- listener(event);
692
- }
693
- catch (error) {
694
- console.error('Error in wildcard log event listener:', error);
695
- }
696
- }
545
+ logEvent(type, level, message, context, category) {
546
+ this._logWithCategory(level, message, context, undefined, category, type);
697
547
  }
548
+ // ─── History Queries ──────────────────────────────────────────────────────
698
549
  /**
699
- * Subscribe to specific log event types
700
- */
701
- on(type, listener) {
702
- if (!this.eventListeners.has(type)) {
703
- this.eventListeners.set(type, []);
704
- }
705
- this.eventListeners.get(type).push(listener);
706
- // Return unsubscribe function
707
- return () => {
708
- const listeners = this.eventListeners.get(type);
709
- if (listeners) {
710
- const index = listeners.indexOf(listener);
711
- if (index > -1) {
712
- listeners.splice(index, 1);
713
- }
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;
714
580
  }
715
- };
716
- }
717
- /**
718
- * Internal log method
719
- */
720
- log(level, message, context, error) {
721
- // Check if this level should be logged (using severity for better performance)
722
- if (!this.shouldLogLevel(level)) {
723
- return;
724
- }
725
- // Create log entry
726
- const entry = createLogEntry(level, message, {
727
- source: this.config.source,
728
- context,
729
- error,
581
+ return true;
730
582
  });
731
- // Add basic logs to buffer as well (for comprehensive JSON output)
732
- this.addToBuffer({
733
- type: this.mapLogLevelToEventType(level),
734
- timestamp: new Date(),
735
- message,
736
- context,
737
- error,
738
- });
739
- // Format and output
740
- const formatted = formatLog(entry, this.formatOptions);
741
- console.log(formatted);
742
583
  }
743
584
  /**
744
- * Map LogLevel to EngineLogType
585
+ * Return all history entries grouped by category.
586
+ * Convenience wrapper around `filterHistory`.
745
587
  */
746
- mapLogLevelToEventType(level) {
747
- switch (level) {
748
- case LogLevel.DEBUG: return EngineLogType.DEBUG;
749
- case LogLevel.INFO: return EngineLogType.INFO;
750
- case LogLevel.WARN: return EngineLogType.WARNING;
751
- case LogLevel.ERROR:
752
- case LogLevel.FATAL: return EngineLogType.ERROR;
753
- default: return EngineLogType.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);
754
593
  }
594
+ return result;
755
595
  }
756
596
  /**
757
597
  * Check if a level should be logged using severity comparison
@@ -876,270 +716,6 @@ export class EngineLogger {
876
716
  isErrorEnabled() {
877
717
  return this.willLog(LogLevel.ERROR);
878
718
  }
879
- // ============================================================================
880
- // JSON LOG BUFFER MANAGEMENT
881
- // ============================================================================
882
- /**
883
- * Add event to JSON log buffer
884
- */
885
- addToBuffer(event) {
886
- // Add to buffer
887
- this.logBuffer.push(event);
888
- // Prevent buffer overflow
889
- if (this.logBuffer.length > this.maxBufferSize) {
890
- this.logBuffer.shift(); // Remove oldest log
891
- }
892
- }
893
- /**
894
- * Get all logs as JSON array
895
- * This is what CLI, API, dashboards, and explanation system will consume
896
- */
897
- getJSONLogs() {
898
- return [...this.logBuffer]; // Return copy to prevent external mutations
899
- }
900
- /**
901
- * Get logs filtered by type
902
- */
903
- getLogsByType(type) {
904
- return this.logBuffer.filter(log => log.type === type);
905
- }
906
- /**
907
- * Get logs filtered by time range
908
- */
909
- getLogsByTimeRange(startTime, endTime) {
910
- return this.logBuffer.filter(log => log.timestamp >= startTime && log.timestamp <= endTime);
911
- }
912
- /**
913
- * Get logs filtered by multiple criteria
914
- */
915
- getLogsFiltered(filter) {
916
- let filtered = this.logBuffer;
917
- if (filter.types) {
918
- filtered = filtered.filter(log => filter.types.includes(log.type));
919
- }
920
- if (filter.startTime) {
921
- filtered = filtered.filter(log => log.timestamp >= filter.startTime);
922
- }
923
- if (filter.endTime) {
924
- filtered = filtered.filter(log => log.timestamp <= filter.endTime);
925
- }
926
- if (filter.hasError !== undefined) {
927
- filtered = filtered.filter(log => filter.hasError ? !!log.error : !log.error);
928
- }
929
- if (filter.searchText) {
930
- const searchLower = filter.searchText.toLowerCase();
931
- filtered = filtered.filter(log => log.message.toLowerCase().includes(searchLower));
932
- }
933
- return filtered;
934
- }
935
- /**
936
- * Get logs by category: Parse events
937
- */
938
- getParseLogEvents() {
939
- return this.logBuffer.filter(log => log.type === EngineLogType.WORKFLOW_VALIDATION);
940
- }
941
- /**
942
- * Get logs by category: Validation events
943
- */
944
- getValidationLogEvents() {
945
- return this.logBuffer.filter(log => log.type === EngineLogType.WORKFLOW_VALIDATION ||
946
- log.type === EngineLogType.VALIDATION_ERROR);
947
- }
948
- /**
949
- * Get logs by category: Execution events
950
- */
951
- getExecutionLogEvents() {
952
- return this.logBuffer.filter(log => log.type === EngineLogType.WORKFLOW_STARTED ||
953
- log.type === EngineLogType.WORKFLOW_COMPLETED ||
954
- log.type === EngineLogType.WORKFLOW_FAILED ||
955
- log.type === EngineLogType.STEP_STARTED ||
956
- log.type === EngineLogType.STEP_COMPLETED ||
957
- log.type === EngineLogType.STEP_FAILED ||
958
- log.type === EngineLogType.STEP_RETRY ||
959
- log.type === EngineLogType.STEP_TIMEOUT ||
960
- log.type === EngineLogType.EXECUTION_TIME ||
961
- log.type === EngineLogType.QUEUE_PROCESSING);
962
- }
963
- /**
964
- * Get logs by category: Error events
965
- */
966
- getErrorLogEvents() {
967
- return this.logBuffer.filter(log => log.type === EngineLogType.ERROR_DETECTED ||
968
- log.type === EngineLogType.ERROR_DEBUGGED ||
969
- log.type === EngineLogType.VALIDATION_ERROR ||
970
- log.type === EngineLogType.WORKFLOW_FAILED ||
971
- log.type === EngineLogType.STEP_FAILED ||
972
- log.type === EngineLogType.ADAPTER_FAILED ||
973
- log.type === EngineLogType.ERROR);
974
- }
975
- /**
976
- * Get logs by category: Lifecycle events
977
- */
978
- getLifecycleLogEvents() {
979
- return this.logBuffer.filter(log => log.type === EngineLogType.ENGINE_STARTED ||
980
- log.type === EngineLogType.ENGINE_STOPPED ||
981
- log.type === EngineLogType.ADAPTER_LOADED ||
982
- log.type === EngineLogType.PLUGIN_INSTALLED ||
983
- log.type === EngineLogType.PLUGIN_VERIFIED);
984
- }
985
- /**
986
- * Get logs by category: Performance events
987
- */
988
- getPerformanceLogEvents() {
989
- return this.logBuffer.filter(log => log.type === EngineLogType.PERFORMANCE_METRIC ||
990
- log.type === EngineLogType.EXECUTION_TIME);
991
- }
992
- /**
993
- * Export logs as formatted JSON string
994
- */
995
- exportLogsAsJSON(pretty = true) {
996
- return JSON.stringify(this.logBuffer, null, pretty ? 2 : 0);
997
- }
998
- /**
999
- * Export logs grouped by type
1000
- */
1001
- exportLogsGrouped() {
1002
- const grouped = {};
1003
- for (const log of this.logBuffer) {
1004
- const type = log.type;
1005
- if (!grouped[type]) {
1006
- grouped[type] = [];
1007
- }
1008
- grouped[type].push(log);
1009
- }
1010
- return grouped;
1011
- }
1012
- /**
1013
- * Get log statistics
1014
- */
1015
- getLogStats() {
1016
- const stats = {
1017
- total: this.logBuffer.length,
1018
- byType: {},
1019
- withErrors: 0,
1020
- withMetrics: 0,
1021
- timeRange: {
1022
- first: this.logBuffer[0]?.timestamp,
1023
- last: this.logBuffer[this.logBuffer.length - 1]?.timestamp,
1024
- },
1025
- };
1026
- for (const log of this.logBuffer) {
1027
- // Count by type
1028
- stats.byType[log.type] = (stats.byType[log.type] || 0) + 1;
1029
- // Count errors and metrics
1030
- if (log.error)
1031
- stats.withErrors++;
1032
- if (log.metrics)
1033
- stats.withMetrics++;
1034
- }
1035
- return stats;
1036
- }
1037
- /**
1038
- * Clear log buffer
1039
- */
1040
- clearLogs() {
1041
- this.logBuffer = [];
1042
- }
1043
- /**
1044
- * Set maximum buffer size
1045
- */
1046
- setMaxBufferSize(size) {
1047
- this.maxBufferSize = size;
1048
- // Trim buffer if needed
1049
- while (this.logBuffer.length > this.maxBufferSize) {
1050
- this.logBuffer.shift();
1051
- }
1052
- }
1053
- /**
1054
- * Get current buffer size
1055
- */
1056
- getBufferSize() {
1057
- return this.logBuffer.length;
1058
- }
1059
- /**
1060
- * Export logs in a comprehensive format for consumers (CLI, API, dashboards)
1061
- */
1062
- exportLogs() {
1063
- const stats = this.getLogStats();
1064
- const grouped = this.exportLogsGrouped();
1065
- // Build execution summary from logs
1066
- const execution = this.buildExecutionSummary();
1067
- return {
1068
- raw: this.getJSONLogs(),
1069
- grouped,
1070
- stats,
1071
- execution,
1072
- };
1073
- }
1074
- /**
1075
- * Build execution summary from collected logs
1076
- * This powers dynamic explanation generation
1077
- */
1078
- buildExecutionSummary() {
1079
- const workflowLogs = this.logBuffer.filter(log => log.type === EngineLogType.WORKFLOW_STARTED ||
1080
- log.type === EngineLogType.WORKFLOW_COMPLETED ||
1081
- log.type === EngineLogType.WORKFLOW_FAILED);
1082
- const stepLogs = this.logBuffer.filter(log => log.type === EngineLogType.STEP_STARTED ||
1083
- log.type === EngineLogType.STEP_COMPLETED ||
1084
- log.type === EngineLogType.STEP_FAILED);
1085
- const errorLogs = this.logBuffer.filter(log => log.error !== undefined ||
1086
- log.type === EngineLogType.ERROR_DETECTED ||
1087
- log.type === EngineLogType.VALIDATION_ERROR);
1088
- const metricLogs = this.logBuffer.filter(log => log.type === EngineLogType.PERFORMANCE_METRIC ||
1089
- log.type === EngineLogType.EXECUTION_TIME);
1090
- // Extract workflow info
1091
- let workflow;
1092
- const workflowStarted = workflowLogs.find(log => log.type === EngineLogType.WORKFLOW_STARTED);
1093
- const workflowEnded = workflowLogs.find(log => log.type === EngineLogType.WORKFLOW_COMPLETED ||
1094
- log.type === EngineLogType.WORKFLOW_FAILED);
1095
- if (workflowStarted && workflowEnded) {
1096
- workflow = {
1097
- name: workflowStarted.context?.workflowName || 'Unknown',
1098
- status: workflowEnded.type === EngineLogType.WORKFLOW_COMPLETED ? 'completed' : 'failed',
1099
- duration: workflowEnded.metrics?.duration,
1100
- };
1101
- }
1102
- // Extract step info
1103
- const steps = [];
1104
- const stepMap = new Map();
1105
- for (const log of stepLogs) {
1106
- const stepId = log.context?.stepId || '';
1107
- const stepName = log.context?.stepName || '';
1108
- if (!stepMap.has(stepId)) {
1109
- stepMap.set(stepId, { id: stepId, name: stepName });
1110
- }
1111
- const step = stepMap.get(stepId);
1112
- if (log.type === EngineLogType.STEP_COMPLETED) {
1113
- step.status = 'completed';
1114
- step.duration = log.metrics?.duration;
1115
- }
1116
- else if (log.type === EngineLogType.STEP_FAILED) {
1117
- step.status = 'failed';
1118
- }
1119
- else {
1120
- step.status = 'started';
1121
- }
1122
- }
1123
- steps.push(...stepMap.values());
1124
- // Extract error info
1125
- const errors = errorLogs.map(log => ({
1126
- step: log.context?.stepId || log.context?.stepName,
1127
- message: log.message,
1128
- error: log.error,
1129
- }));
1130
- // Extract metrics
1131
- const metrics = metricLogs.map(log => ({
1132
- label: log.context?.label || 'Unknown',
1133
- duration: log.metrics?.duration,
1134
- memory: log.metrics?.memory,
1135
- }));
1136
- return {
1137
- workflow,
1138
- steps,
1139
- errors,
1140
- metrics,
1141
- };
1142
- }
1143
719
  }
1144
720
  /**
1145
721
  * Create a logger instance from engine config
@@ -1160,11 +736,17 @@ export function createEngineLogger(logLevel, verbose = false) {
1160
736
  const level = levelMap[logLevel] || LogLevel.INFO;
1161
737
  return new EngineLogger({
1162
738
  level,
1163
- format: verbose ? 'pretty' : 'text',
739
+ format: verbose ? 'pretty' : 'json',
1164
740
  colors: true,
1165
- timestamp: true, // Enable human-readable timestamps
741
+ timestamp: true,
1166
742
  source: 'Orbyt',
1167
- structuredEvents: true, // Always collect structured JSON logs
743
+ category: LogCategoryEnum.SYSTEM,
744
+ structuredEvents: true,
1168
745
  });
1169
746
  }
747
+ /**
748
+ * Re-export formatTimestamp for external use
749
+ * Allows other parts of the system to format timestamps consistently
750
+ */
751
+ export { formatTimestamp };
1170
752
  //# sourceMappingURL=EngineLogger.js.map