@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.
- package/dist/core/OrbytEngine.d.ts.map +1 -1
- package/dist/core/OrbytEngine.js +18 -4
- package/dist/core/OrbytEngine.js.map +1 -1
- package/dist/errors/ErrorDebugger.d.ts +78 -25
- package/dist/errors/ErrorDebugger.d.ts.map +1 -1
- package/dist/errors/ErrorDebugger.js +383 -2
- package/dist/errors/ErrorDebugger.js.map +1 -1
- package/dist/errors/ErrorDetector.d.ts +107 -5
- package/dist/errors/ErrorDetector.d.ts.map +1 -1
- package/dist/errors/ErrorDetector.js +195 -40
- package/dist/errors/ErrorDetector.js.map +1 -1
- package/dist/errors/ErrorFormatter.d.ts +51 -0
- package/dist/errors/ErrorFormatter.d.ts.map +1 -1
- package/dist/errors/ErrorFormatter.js +128 -0
- package/dist/errors/ErrorFormatter.js.map +1 -1
- package/dist/errors/ErrorHandler.d.ts +93 -7
- package/dist/errors/ErrorHandler.d.ts.map +1 -1
- package/dist/errors/ErrorHandler.js +91 -42
- package/dist/errors/ErrorHandler.js.map +1 -1
- package/dist/errors/OrbytError.d.ts +28 -0
- package/dist/errors/OrbytError.d.ts.map +1 -1
- package/dist/errors/OrbytError.js.map +1 -1
- package/dist/errors/SecurityErrors.d.ts +2 -25
- package/dist/errors/SecurityErrors.d.ts.map +1 -1
- package/dist/errors/SecurityErrors.js +5 -119
- package/dist/errors/SecurityErrors.js.map +1 -1
- package/dist/errors/WorkflowError.d.ts +11 -1
- package/dist/errors/WorkflowError.d.ts.map +1 -1
- package/dist/errors/WorkflowError.js +104 -0
- package/dist/errors/WorkflowError.js.map +1 -1
- package/dist/loader/WorkflowLoader.d.ts +71 -5
- package/dist/loader/WorkflowLoader.d.ts.map +1 -1
- package/dist/loader/WorkflowLoader.js +135 -50
- package/dist/loader/WorkflowLoader.js.map +1 -1
- package/dist/logging/EngineLogger.d.ts +252 -337
- package/dist/logging/EngineLogger.d.ts.map +1 -1
- package/dist/logging/EngineLogger.js +460 -1012
- package/dist/logging/EngineLogger.js.map +1 -1
- package/dist/logging/LoggerManager.d.ts +109 -33
- package/dist/logging/LoggerManager.d.ts.map +1 -1
- package/dist/logging/LoggerManager.js +136 -47
- package/dist/logging/LoggerManager.js.map +1 -1
- package/dist/logging/index.d.ts.map +1 -1
- package/dist/logging/index.js.map +1 -1
- package/dist/types/log-types.d.ts +50 -11
- package/dist/types/log-types.d.ts.map +1 -1
- package/dist/types/log-types.js.map +1 -1
- package/package.json +1 -1
|
@@ -20,49 +20,124 @@
|
|
|
20
20
|
* - Monitoring systems
|
|
21
21
|
* - Audit trails
|
|
22
22
|
*
|
|
23
|
-
* @module
|
|
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
|
-
*
|
|
29
|
-
*
|
|
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
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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 || '
|
|
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
|
-
|
|
74
|
-
this.
|
|
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
|
-
|
|
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
|
|
250
|
-
*/
|
|
251
|
-
/**
|
|
252
|
-
* Log step timeout event (runtime phase)
|
|
161
|
+
* Log an info message
|
|
253
162
|
*/
|
|
254
|
-
|
|
255
|
-
this.
|
|
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
|
|
167
|
+
* Log a warning message
|
|
269
168
|
*/
|
|
270
|
-
|
|
271
|
-
|
|
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
|
|
173
|
+
* Log an error message
|
|
285
174
|
*/
|
|
286
|
-
|
|
287
|
-
|
|
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
|
|
179
|
+
* Log a fatal error message
|
|
304
180
|
*/
|
|
305
|
-
|
|
306
|
-
|
|
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
|
|
320
|
-
*/
|
|
321
|
-
/**
|
|
322
|
-
* Log adapter failed event (system phase)
|
|
185
|
+
* Log with a custom level
|
|
323
186
|
*/
|
|
324
|
-
|
|
325
|
-
this.
|
|
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
|
|
337
|
-
*/
|
|
338
|
-
/**
|
|
339
|
-
* Log plugin installed event (system phase)
|
|
191
|
+
* Log only if severity meets minimum threshold
|
|
340
192
|
*/
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
*
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
*
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
*
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
*
|
|
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
|
-
|
|
377
|
-
this.
|
|
378
|
-
|
|
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
|
|
381
|
-
|
|
268
|
+
message,
|
|
269
|
+
category,
|
|
270
|
+
source: this.config.source,
|
|
271
|
+
context: finalContext,
|
|
382
272
|
error,
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
*
|
|
290
|
+
* Private shorthand — uses the default category from config.
|
|
291
|
+
* Prefer the categorised sub-loggers (`runtime`, `analysis`, etc.) for new code.
|
|
476
292
|
*/
|
|
477
|
-
|
|
478
|
-
this.
|
|
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
|
-
*
|
|
298
|
+
* Export collected logs as a structured ExportedLogs object.
|
|
299
|
+
* Includes `byCategory` statistics and the active `workflowContext` snapshot.
|
|
486
300
|
*/
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
*
|
|
338
|
+
* Get all collected logs as a JSON string
|
|
496
339
|
*/
|
|
497
|
-
|
|
498
|
-
this.
|
|
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
|
-
*
|
|
344
|
+
* Clear the log history (does NOT clear the workflow context)
|
|
506
345
|
*/
|
|
507
|
-
|
|
508
|
-
this.
|
|
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
|
-
*
|
|
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
|
-
|
|
518
|
-
this.
|
|
519
|
-
variable,
|
|
520
|
-
resolved: this.sanitizeValue(resolvedValue),
|
|
521
|
-
...context,
|
|
522
|
-
});
|
|
365
|
+
setWorkflowContext(ctx) {
|
|
366
|
+
this.workflowContext = { ...ctx };
|
|
523
367
|
}
|
|
524
368
|
/**
|
|
525
|
-
*
|
|
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
|
-
|
|
528
|
-
|
|
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
|
-
*
|
|
376
|
+
* Returns a read-only snapshot of the currently active workflow context,
|
|
377
|
+
* or `null` if none has been set.
|
|
542
378
|
*/
|
|
543
|
-
|
|
544
|
-
this.
|
|
545
|
-
source,
|
|
546
|
-
format,
|
|
547
|
-
});
|
|
379
|
+
getWorkflowContext() {
|
|
380
|
+
return this.workflowContext ? { ...this.workflowContext } : null;
|
|
548
381
|
}
|
|
549
382
|
/**
|
|
550
|
-
*
|
|
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
|
-
|
|
553
|
-
this.
|
|
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
|
-
*
|
|
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
|
-
|
|
563
|
-
this.
|
|
564
|
-
source,
|
|
565
|
-
...context,
|
|
566
|
-
});
|
|
432
|
+
workflowCtx() {
|
|
433
|
+
return this.workflowContext ? { ...this.workflowContext } : {};
|
|
567
434
|
}
|
|
435
|
+
// ─── Dynamic Lifecycle Helpers ────────────────────────────────────────────
|
|
568
436
|
/**
|
|
569
|
-
* Log
|
|
437
|
+
* Log workflow execution started (runtime category).
|
|
438
|
+
* Called at the top of `WorkflowExecutor.execute()`.
|
|
570
439
|
*/
|
|
571
|
-
|
|
572
|
-
this.
|
|
573
|
-
target,
|
|
574
|
-
type,
|
|
575
|
-
});
|
|
440
|
+
workflowStarted(workflowName, context) {
|
|
441
|
+
this.runtime.info(`Workflow "${workflowName}" started`, { workflowName, ...context });
|
|
576
442
|
}
|
|
577
443
|
/**
|
|
578
|
-
* Log
|
|
444
|
+
* Log a workflow input field that was resolved (runtime category).
|
|
579
445
|
*/
|
|
580
|
-
|
|
581
|
-
this.
|
|
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
|
|
450
|
+
* Log a context field used during execution (runtime category).
|
|
589
451
|
*/
|
|
590
|
-
|
|
591
|
-
this.
|
|
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
|
|
456
|
+
* Log step execution started (runtime category).
|
|
457
|
+
* ```typescript
|
|
458
|
+
* logger.stepStarted(step.id, step.name, { adapter: step.adapter });
|
|
459
|
+
* ```
|
|
603
460
|
*/
|
|
604
|
-
|
|
605
|
-
this.
|
|
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
|
|
465
|
+
* Log step execution completed (runtime category).
|
|
613
466
|
*/
|
|
614
|
-
|
|
615
|
-
this.
|
|
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
|
|
471
|
+
* Log step timeout (runtime category).
|
|
627
472
|
*/
|
|
628
|
-
|
|
629
|
-
this.
|
|
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
|
|
477
|
+
* Log step retry attempt (runtime category).
|
|
637
478
|
*/
|
|
638
|
-
|
|
639
|
-
this.
|
|
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
|
-
*
|
|
483
|
+
* Log step failure after all attempts (runtime category).
|
|
652
484
|
*/
|
|
653
|
-
|
|
654
|
-
|
|
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
|
-
*
|
|
489
|
+
* Log adapter action execution result (runtime category).
|
|
669
490
|
*/
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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
|
-
*
|
|
496
|
+
* Log validation phase started (analysis category).
|
|
697
497
|
*/
|
|
698
|
-
|
|
699
|
-
|
|
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
|
|
502
|
+
* Log validation passed (analysis category).
|
|
708
503
|
*/
|
|
709
|
-
|
|
710
|
-
this.
|
|
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
|
|
508
|
+
* Log validation failure (analysis category).
|
|
714
509
|
*/
|
|
715
|
-
|
|
716
|
-
|
|
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
|
-
*
|
|
514
|
+
* Log parsing started (analysis category).
|
|
722
515
|
*/
|
|
723
|
-
|
|
724
|
-
|
|
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
|
|
750
|
-
* Uses EngineLog interface for strong typing.
|
|
520
|
+
* Log parsing completed (analysis category).
|
|
751
521
|
*/
|
|
752
|
-
|
|
753
|
-
|
|
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
|
-
*
|
|
526
|
+
* Log parsing failure (analysis category).
|
|
779
527
|
*/
|
|
780
|
-
|
|
781
|
-
|
|
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
|
|
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
|
-
|
|
800
|
-
|
|
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
|
-
*
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
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(
|
|
1302
|
-
|
|
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
|