@output.ai/core 0.3.3 → 0.3.5
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/package.json +3 -1
- package/src/index.js +2 -1
- package/src/internal_activities/index.js +4 -1
- package/src/logger.d.ts +4 -0
- package/src/logger.js +56 -0
- package/src/tracing/trace_engine.js +5 -2
- package/src/worker/catalog_workflow/index.js +4 -1
- package/src/worker/index.js +18 -15
- package/src/worker/loader.js +6 -3
- package/src/worker/sinks.js +57 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@output.ai/core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "The core module of the output framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"redis": "5.8.3",
|
|
40
40
|
"stacktrace-parser": "0.1.11",
|
|
41
41
|
"undici": "7.18.2",
|
|
42
|
+
"winston": "3.17.0",
|
|
42
43
|
"zod": "4.1.12"
|
|
43
44
|
},
|
|
44
45
|
"license": "Apache-2.0",
|
|
@@ -48,6 +49,7 @@
|
|
|
48
49
|
"imports": {
|
|
49
50
|
"#consts": "./src/consts.js",
|
|
50
51
|
"#errors": "./src/errors.js",
|
|
52
|
+
"#logger": "./src/logger.js",
|
|
51
53
|
"#utils": "./src/utils/index.js",
|
|
52
54
|
"#tracing": "./src/tracing/internal_interface.js",
|
|
53
55
|
"#async_storage": "./src/async_storage.js",
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { evaluator, EvaluationStringResult, EvaluationNumberResult, EvaluationBooleanResult } from './interface/evaluator.js';
|
|
1
|
+
import { evaluator, EvaluationStringResult, EvaluationNumberResult, EvaluationBooleanResult, EvaluationFeedback } from './interface/evaluator.js';
|
|
2
2
|
import { step } from './interface/step.js';
|
|
3
3
|
import { workflow } from './interface/workflow.js';
|
|
4
4
|
import { executeInParallel } from './interface/workflow_utils.js';
|
|
@@ -16,6 +16,7 @@ export {
|
|
|
16
16
|
EvaluationNumberResult,
|
|
17
17
|
EvaluationStringResult,
|
|
18
18
|
EvaluationBooleanResult,
|
|
19
|
+
EvaluationFeedback,
|
|
19
20
|
// webhook tools
|
|
20
21
|
executeInParallel,
|
|
21
22
|
sendHttpRequest,
|
|
@@ -4,6 +4,9 @@ import { setMetadata, isStringboolTrue, serializeFetchResponse, serializeBodyAnd
|
|
|
4
4
|
import { ComponentType } from '#consts';
|
|
5
5
|
import * as localProcessor from '../tracing/processors/local/index.js';
|
|
6
6
|
import * as s3Processor from '../tracing/processors/s3/index.js';
|
|
7
|
+
import { createChildLogger } from '#logger';
|
|
8
|
+
|
|
9
|
+
const log = createChildLogger( 'HttpClient' );
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* Send a HTTP request.
|
|
@@ -42,7 +45,7 @@ export const sendHttpRequest = async ( { url, method, payload = undefined, heade
|
|
|
42
45
|
}
|
|
43
46
|
} )();
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
log.info( 'HTTP request completed', { url, method, status: response.status, statusText: response.statusText } );
|
|
46
49
|
|
|
47
50
|
if ( !response.ok ) {
|
|
48
51
|
throw new FatalError( `${method} ${url} ${response.status}` );
|
package/src/logger.d.ts
ADDED
package/src/logger.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import winston from 'winston';
|
|
2
|
+
|
|
3
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
4
|
+
|
|
5
|
+
const levels = {
|
|
6
|
+
error: 0,
|
|
7
|
+
warn: 1,
|
|
8
|
+
info: 2,
|
|
9
|
+
http: 3,
|
|
10
|
+
debug: 4
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Format metadata as friendly JSON: "{ name: "foo", count: 5 }"
|
|
14
|
+
const formatMeta = obj => {
|
|
15
|
+
const entries = Object.entries( obj );
|
|
16
|
+
if ( !entries.length ) {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
return ' { ' + entries.map( ( [ k, v ] ) => `${k}: ${JSON.stringify( v )}` ).join( ', ' ) + ' }';
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Development format: colorized with namespace prefix
|
|
23
|
+
const devFormat = winston.format.combine(
|
|
24
|
+
winston.format.colorize(),
|
|
25
|
+
winston.format.printf( ( { level, message, namespace, service: _, environment: __, ...rest } ) => {
|
|
26
|
+
const ns = namespace ? `{Core.${namespace}}` : '{Core}';
|
|
27
|
+
const meta = formatMeta( rest );
|
|
28
|
+
return `[${level}] ${ns} ${message}${meta}`;
|
|
29
|
+
} )
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Production format: structured JSON
|
|
33
|
+
const prodFormat = winston.format.combine(
|
|
34
|
+
winston.format.timestamp( { format: 'YYYY-MM-DDTHH:mm:ss.SSSZ' } ),
|
|
35
|
+
winston.format.errors( { stack: true } ),
|
|
36
|
+
winston.format.json()
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export const logger = winston.createLogger( {
|
|
40
|
+
levels,
|
|
41
|
+
level: isProduction ? 'info' : 'debug',
|
|
42
|
+
format: isProduction ? prodFormat : devFormat,
|
|
43
|
+
defaultMeta: {
|
|
44
|
+
service: 'output-worker',
|
|
45
|
+
environment: process.env.NODE_ENV || 'development'
|
|
46
|
+
},
|
|
47
|
+
transports: [ new winston.transports.Console() ]
|
|
48
|
+
} );
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Creates a child logger with a specific namespace
|
|
52
|
+
*
|
|
53
|
+
* @param {string} namespace - The namespace for this logger (e.g., 'Scanner', 'Tracing')
|
|
54
|
+
* @returns {winston.Logger} Child logger instance with namespace metadata
|
|
55
|
+
*/
|
|
56
|
+
export const createChildLogger = namespace => logger.child( { namespace } );
|
|
@@ -5,6 +5,9 @@ import { isStringboolTrue } from '#utils';
|
|
|
5
5
|
import * as localProcessor from './processors/local/index.js';
|
|
6
6
|
import * as s3Processor from './processors/s3/index.js';
|
|
7
7
|
import { ComponentType } from '#consts';
|
|
8
|
+
import { createChildLogger } from '#logger';
|
|
9
|
+
|
|
10
|
+
const log = createChildLogger( 'Tracing' );
|
|
8
11
|
|
|
9
12
|
const traceBus = new EventEmitter();
|
|
10
13
|
const processors = [
|
|
@@ -32,7 +35,7 @@ export const init = async () => {
|
|
|
32
35
|
try {
|
|
33
36
|
await p.exec( ...args );
|
|
34
37
|
} catch ( error ) {
|
|
35
|
-
|
|
38
|
+
log.error( 'Processor execution error', { processor: p.name, error: error.message, stack: error.stack } );
|
|
36
39
|
}
|
|
37
40
|
} );
|
|
38
41
|
}
|
|
@@ -44,7 +47,7 @@ export const init = async () => {
|
|
|
44
47
|
const serializeDetails = details => details instanceof Error ? serializeError( details ) : details;
|
|
45
48
|
|
|
46
49
|
/**
|
|
47
|
-
* Creates a new trace event phase and
|
|
50
|
+
* Creates a new trace event phase and sends it to be written
|
|
48
51
|
*
|
|
49
52
|
* @param {string} phase - The phase
|
|
50
53
|
* @param {object} fields - All the trace fields
|
|
@@ -2,6 +2,9 @@ import { z } from 'zod';
|
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
import { METADATA_ACCESS_SYMBOL } from '#consts';
|
|
4
4
|
import { Catalog, CatalogActivity, CatalogWorkflow } from './catalog.js';
|
|
5
|
+
import { createChildLogger } from '#logger';
|
|
6
|
+
|
|
7
|
+
const log = createChildLogger( 'Catalog' );
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Converts a Zod schema to JSON Schema format.
|
|
@@ -17,7 +20,7 @@ const convertToJsonSchema = schema => {
|
|
|
17
20
|
try {
|
|
18
21
|
return z.toJSONSchema( schema );
|
|
19
22
|
} catch ( error ) {
|
|
20
|
-
|
|
23
|
+
log.warn( 'Invalid schema provided (expected Zod schema)', { error: error.message } );
|
|
21
24
|
return null;
|
|
22
25
|
}
|
|
23
26
|
};
|
package/src/worker/index.js
CHANGED
|
@@ -9,31 +9,34 @@ import { init as initTracing } from '#tracing';
|
|
|
9
9
|
import { WORKFLOW_CATALOG } from '#consts';
|
|
10
10
|
import { webpackConfigHook } from './bundler_options.js';
|
|
11
11
|
import { initInterceptors } from './interceptors.js';
|
|
12
|
+
import { createChildLogger } from '#logger';
|
|
13
|
+
|
|
14
|
+
const log = createChildLogger( 'Worker' );
|
|
12
15
|
|
|
13
16
|
// Get caller directory from command line arguments
|
|
14
17
|
const callerDir = process.argv[2];
|
|
15
18
|
|
|
16
19
|
( async () => {
|
|
17
|
-
|
|
20
|
+
log.info( 'Loading workflows...', { callerDir } );
|
|
18
21
|
const workflows = await loadWorkflows( callerDir );
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
log.info( 'Loading activities...', { callerDir } );
|
|
21
24
|
const activities = await loadActivities( callerDir, workflows );
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
log.info( 'Creating worker entry point...' );
|
|
24
27
|
const workflowsPath = createWorkflowsEntryPoint( workflows );
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
log.info( 'Initializing tracing...' );
|
|
27
30
|
await initTracing();
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
log.info( 'Creating workflows catalog...' );
|
|
30
33
|
const catalog = createCatalog( { workflows, activities } );
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
//
|
|
35
|
+
log.info( 'Connecting Temporal...' );
|
|
36
|
+
// Enable TLS when connecting to remote Temporal (API key present)
|
|
34
37
|
const connection = await NativeConnection.connect( { address, tls: Boolean( apiKey ), apiKey } );
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
log.info( 'Creating worker...' );
|
|
37
40
|
const worker = await Worker.create( {
|
|
38
41
|
connection,
|
|
39
42
|
namespace,
|
|
@@ -47,7 +50,7 @@ const callerDir = process.argv[2];
|
|
|
47
50
|
bundlerOptions: { webpackConfigHook }
|
|
48
51
|
} );
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
log.info( 'Starting catalog workflow...' );
|
|
51
54
|
await new Client( { connection, namespace } ).workflow.start( WORKFLOW_CATALOG, {
|
|
52
55
|
taskQueue,
|
|
53
56
|
workflowId: catalogId, // use the name of the task queue as the catalog name, ensuring uniqueness
|
|
@@ -55,7 +58,7 @@ const callerDir = process.argv[2];
|
|
|
55
58
|
args: [ catalog ]
|
|
56
59
|
} );
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
log.info( 'Running worker...' );
|
|
59
62
|
|
|
60
63
|
// FORCE_QUIT_GRACE_MS delays the second instance of a shutdown command.
|
|
61
64
|
// If running output-worker directly with npx, 2 signals are recieved in
|
|
@@ -69,12 +72,12 @@ const callerDir = process.argv[2];
|
|
|
69
72
|
if ( elapsed < FORCE_QUIT_GRACE_MS ) {
|
|
70
73
|
return; // ignore rapid duplicate signals
|
|
71
74
|
}
|
|
72
|
-
|
|
75
|
+
log.warn( 'Force quitting...' );
|
|
73
76
|
process.exit( 1 );
|
|
74
77
|
}
|
|
75
78
|
state.isShuttingDown = true;
|
|
76
79
|
state.shutdownStartedAt = Date.now();
|
|
77
|
-
|
|
80
|
+
log.info( 'Shutting down...', { signal } );
|
|
78
81
|
worker.shutdown();
|
|
79
82
|
};
|
|
80
83
|
|
|
@@ -82,13 +85,13 @@ const callerDir = process.argv[2];
|
|
|
82
85
|
process.on( 'SIGINT', () => shutdown( 'SIGINT' ) );
|
|
83
86
|
|
|
84
87
|
await worker.run();
|
|
85
|
-
|
|
88
|
+
log.info( 'Worker stopped.' );
|
|
86
89
|
|
|
87
90
|
await connection.close();
|
|
88
|
-
|
|
91
|
+
log.info( 'Connection closed.' );
|
|
89
92
|
|
|
90
93
|
process.exit( 0 );
|
|
91
94
|
} )().catch( error => {
|
|
92
|
-
|
|
95
|
+
log.error( 'Fatal error', { message: error.message, stack: error.stack } );
|
|
93
96
|
process.exit( 1 );
|
|
94
97
|
} );
|
package/src/worker/loader.js
CHANGED
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
WORKFLOW_CATALOG,
|
|
13
13
|
ACTIVITY_GET_TRACE_DESTINATIONS
|
|
14
14
|
} from '#consts';
|
|
15
|
+
import { createChildLogger } from '#logger';
|
|
16
|
+
|
|
17
|
+
const log = createChildLogger( 'Scanner' );
|
|
15
18
|
|
|
16
19
|
const __dirname = dirname( fileURLToPath( import.meta.url ) );
|
|
17
20
|
|
|
@@ -64,7 +67,7 @@ export async function loadActivities( rootDir, workflows ) {
|
|
|
64
67
|
for ( const { path: workflowPath, name: workflowName } of workflows ) {
|
|
65
68
|
const dir = dirname( workflowPath );
|
|
66
69
|
for await ( const { fn, metadata, path } of importComponents( dir, Object.values( activityMatchersBuilder( dir ) ) ) ) {
|
|
67
|
-
|
|
70
|
+
log.info( 'Component loaded', { type: metadata.type, name: metadata.name, path, workflow: workflowName } );
|
|
68
71
|
// Activities loaded from a workflow path will use the workflow name as a namespace, which is unique across the platform, avoiding collision
|
|
69
72
|
const activityKey = generateActivityKey( { namespace: workflowName, activityName: metadata.name } );
|
|
70
73
|
activities[activityKey] = fn;
|
|
@@ -75,7 +78,7 @@ export async function loadActivities( rootDir, workflows ) {
|
|
|
75
78
|
|
|
76
79
|
// Load shared activities/evaluators
|
|
77
80
|
for await ( const { fn, metadata, path } of importComponents( rootDir, [ staticMatchers.sharedStepsDir, staticMatchers.sharedEvaluatorsDir ] ) ) {
|
|
78
|
-
|
|
81
|
+
log.info( 'Shared component loaded', { type: metadata.type, name: metadata.name, path } );
|
|
79
82
|
// The namespace for shared activities is fixed
|
|
80
83
|
const activityKey = generateActivityKey( { namespace: SHARED_STEP_PREFIX, activityName: metadata.name } );
|
|
81
84
|
activities[activityKey] = fn;
|
|
@@ -105,7 +108,7 @@ export async function loadWorkflows( rootDir ) {
|
|
|
105
108
|
if ( staticMatchers.workflowPathHasShared( path ) ) {
|
|
106
109
|
throw new Error( 'Workflow directory can\'t be named "shared"' );
|
|
107
110
|
}
|
|
108
|
-
|
|
111
|
+
log.info( 'Workflow loaded', { name: metadata.name, path } );
|
|
109
112
|
workflows.push( { ...metadata, path } );
|
|
110
113
|
}
|
|
111
114
|
return workflows;
|
package/src/worker/sinks.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { WORKFLOW_CATALOG } from '#consts';
|
|
2
2
|
import { addEventStart, addEventEnd, addEventError } from '#tracing';
|
|
3
|
+
import { createChildLogger } from '#logger';
|
|
4
|
+
|
|
5
|
+
const log = createChildLogger( 'Worker' );
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Start a workflow trace event
|
|
6
9
|
*
|
|
7
10
|
* @param {function} method - Trace function to call
|
|
8
11
|
* @param {object} workflowInfo - Temporal workflowInfo object
|
|
9
|
-
* @param {object} details -
|
|
12
|
+
* @param {object} details - The details to attach to the event
|
|
10
13
|
*/
|
|
11
14
|
const addWorkflowEvent = ( method, workflowInfo, details ) => {
|
|
12
15
|
const { workflowId: id, workflowType: name, memo: { parentId, executionContext } } = workflowInfo;
|
|
@@ -16,6 +19,47 @@ const addWorkflowEvent = ( method, workflowInfo, details ) => {
|
|
|
16
19
|
method( { id, kind: 'workflow', name, details, parentId, executionContext } );
|
|
17
20
|
};
|
|
18
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Log workflow start
|
|
24
|
+
*
|
|
25
|
+
* @param {object} workflowInfo - Temporal workflowInfo object
|
|
26
|
+
*/
|
|
27
|
+
const logWorkflowStart = workflowInfo => {
|
|
28
|
+
const { workflowId, workflowType: workflowName } = workflowInfo;
|
|
29
|
+
if ( workflowName === WORKFLOW_CATALOG ) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
log.info( 'Workflow started', { workflowName, workflowId } );
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Log workflow completion with duration
|
|
37
|
+
*
|
|
38
|
+
* @param {object} workflowInfo - Temporal workflowInfo object
|
|
39
|
+
*/
|
|
40
|
+
const logWorkflowEnd = workflowInfo => {
|
|
41
|
+
const { workflowId, workflowType: workflowName, startTime } = workflowInfo;
|
|
42
|
+
if ( workflowName === WORKFLOW_CATALOG ) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const durationMs = Date.now() - startTime.getTime();
|
|
46
|
+
log.info( 'Workflow completed', { workflowName, workflowId, durationMs } );
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Log workflow failure with duration
|
|
51
|
+
*
|
|
52
|
+
* @param {object} workflowInfo - Temporal workflowInfo object
|
|
53
|
+
*/
|
|
54
|
+
const logWorkflowError = workflowInfo => {
|
|
55
|
+
const { workflowId, workflowType: workflowName, startTime } = workflowInfo;
|
|
56
|
+
if ( workflowName === WORKFLOW_CATALOG ) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const durationMs = Date.now() - startTime.getTime();
|
|
60
|
+
log.error( 'Workflow failed', { workflowName, workflowId, durationMs } );
|
|
61
|
+
};
|
|
62
|
+
|
|
19
63
|
/**
|
|
20
64
|
* Start a trace event with given configuration
|
|
21
65
|
*
|
|
@@ -33,17 +77,26 @@ const addEvent = ( method, workflowInfo, options ) => {
|
|
|
33
77
|
export const sinks = {
|
|
34
78
|
trace: {
|
|
35
79
|
addWorkflowEventStart: {
|
|
36
|
-
fn: ( ...
|
|
80
|
+
fn: ( workflowInfo, ...rest ) => {
|
|
81
|
+
logWorkflowStart( workflowInfo );
|
|
82
|
+
addWorkflowEvent( addEventStart, workflowInfo, ...rest );
|
|
83
|
+
},
|
|
37
84
|
callDuringReplay: false
|
|
38
85
|
},
|
|
39
86
|
|
|
40
87
|
addWorkflowEventEnd: {
|
|
41
|
-
fn: ( ...
|
|
88
|
+
fn: ( workflowInfo, ...rest ) => {
|
|
89
|
+
logWorkflowEnd( workflowInfo );
|
|
90
|
+
addWorkflowEvent( addEventEnd, workflowInfo, ...rest );
|
|
91
|
+
},
|
|
42
92
|
callDuringReplay: false
|
|
43
93
|
},
|
|
44
94
|
|
|
45
95
|
addWorkflowEventError: {
|
|
46
|
-
fn: ( ...
|
|
96
|
+
fn: ( workflowInfo, ...rest ) => {
|
|
97
|
+
logWorkflowError( workflowInfo );
|
|
98
|
+
addWorkflowEvent( addEventError, workflowInfo, ...rest );
|
|
99
|
+
},
|
|
47
100
|
callDuringReplay: false
|
|
48
101
|
},
|
|
49
102
|
|