Introduction
Welcome to the next chapter of our exploration into the fascinating capabilities of the execution-engine library. W'll now take a deeper dive into advanced features.
If you haven't checked out the previous post Execution workflow tracing: a guide to execution-engine library, it's worth a read before we explore the intricacies of customizing TraceOptions
.
Let's elevate your code tracing experience to new heights! ๐
Why Customize TraceOptions? ๐จ
Tailoring your trace options provides a unique touch to your code tracing experience. Here's why customizing TraceOptions
matters:
1. Precision in Tracing: Customize tracing configurations for each function or method, gaining insights with precision.
2. Flexibility in Execution Flow: Adjust traceExecution
to control what to trace exactly, from inputs and outputs to custom narratives.
3. Parallel Execution Control: Enable or disable parallel execution with the parallel
option, offering flexibility in execution flow.
4. Error Handling Strategy: Choose how errors are handled, whether to catch them ('catch'
) or let them propagate ('throw'
).
Installation ๐ฆ
If you still haven't installed the execution-engine library, you can do it by following these steps:
Using npm:
npm install execution-engine@2.0.2
Using yarn:
yarn add execution-engine@2.0.2
Refer to the npm package page for updates or additional information, and explore the source code on GitHub if you're interested in contributing.
Configuring TraceOptions ๐ ๏ธ
Let's delve into the core of customization with the traceOptions
parameter. This parameter accepts two formats:
Simple Format:
{
id: string;
label: string;
parent?: string;
}
Full Trace Options Object:
{
trace: {
id: string;
label: string;
parent?: string;
};
config?: {
traceExecution?: boolean | Array<keyof NodeExecutionTrace<I, O>> | NodeExecutionTraceExtractor<I, O>;
parallel?: boolean | string;
errors?: 'catch' | 'throw';
};
}
Examples: Configuring TraceOptions
in Action ๐
In this example, we'll trace the
generateGreeting()
function using the decorator-based approach with@engine()
and@run()
. However, it's essential to note that the same tracing can be achieved with the basic usage method via the.run()
approach.
Let's suppose we have a GreetingTask
class defined as follows:
@engine({ id: 'greetingId' })
class GreetingTask extends EngineTask {
@run()
generateGreeting(person: any, greeter: any, nodeData?: NodeData) {
this.engine.pushNarratives(nodeData.id, [`here is tracing narrative for greeting ${person.name}`]);
return {
greeting: {
fr: `Hello, ${person.name}`,
es: `ยกHola, ${person.name}!`,
en: `Hello, ${person.name}!`
},
greeter: `I'm ${greeter.name}.`,
hobbies: [`Let's explore the world of ${person.hobbies.join(', ')} together!`],
get fullGreeting() {
return [this.greeting.en, this.greeter, ...this.hobbies].join(' ');
}
};
}
}
Now, let's run the generateGreeting
function:
const myInstance = new GreetingTask();
myInstance.generateGreeting(
{
name: 'John Doe',
age: 30,
isStudent: false,
grades: [85, 90, 78, 95],
address: {
street: '123 Main Street',
city: 'Cityville',
zipcode: '12345'
},
contact: {
email: 'john.doe@example.com',
phone: '+1 555-1234'
},
hobbies: ['reading', 'traveling', 'coding'],
isActive: true,
birthday: '1992-05-15',
isMarried: null
},
{ name: 'Akram'}
);
1. Default Tracing Behavior
When using @run()
without specifying the traceOptions
parameter, the Execution Engine will automatically generate trace information for the executed function. Here's what to expect:
-
Node Tracing Information:
- ID: Auto-generated unique identifier.
- Label: Default label, typically the function name.
- Parent: Auto-calculated parent node based on the node execution workflow context.
-
Function Execution:
- Parallel Execution: Not executed in parallel by default.
- Error Handling: Errors are thrown by default, but they won't be traced unless explicitly configured.
-
Traced Attributes (via
traceExecution
):- Inputs: All input parameters will be traced.
- Outputs: All output values will be traced.
- Narratives: Narratives (if any) will be included in the trace.
- Start Time: The start time of the function execution will be captured.
- End Time: The end time of the function execution will be captured.
- Duration: The duration of the function execution is auto-calculated.
- Elapsed Time: The elapsed time is auto-calculated as the time between Start Time and End Time.
โ ๏ธ However, note that errors won't be traced unless explicitly set to
'catch'
in thetraceOptions.config
. If errors occur without proper configuration, they will be thrown, potentially causing the engine to halt.
The resulting trace node will look like the following:
[
{
"data": {
"id": "generateGreeting_1702580284588_9edf5690-e4fe-4f0d-834b-59e0f30b345e",
"label": "generateGreeting",
"inputs": [
// ... (inputs array details)
],
"outputs": {
// ... (outputs details)
},
"narratives": ["here is tracing narrative for greeting John Doe"],
"startTime": "2023-12-14T18:58:04.588Z",
"endTime": "2023-12-14T18:58:04.588Z",
"duration": 0.34549999237060547,
"elapsedTime": "0.345 ms",
"parallel": false,
"abstract": false,
"createTime": "2023-12-14T18:58:04.588Z"
},
"group": "nodes"
}
]
To customize the tracing behaviour, consider explicitly defining traceOptions
to gain more control over what aspects of the function execution you want to include or exclude from the trace.
2. Disabling All Attributes
To disable all attributes and have a minimal trace, you can use the following configuration:
@run({
config: {
traceExecution: false
}
})
generateGreeting(person: any, greeter: any) {
// Function implementation
}
This will result in a trace node with only essential details:
[
{
"data": {
"id": "generateGreeting_1702581056608_5bd87acb-b9b7-4d82-89be-e29559582038",
"label": "1 - generateGreeting",
"abstract": false,
"createTime": "2023-12-14T19:10:56.609Z"
},
"group": "nodes"
}
]
3: Selectively Tracing Attributes
For more granular control, you can selectively choose which attributes to trace. For example, tracing only inputs, outputs, and startTime:
@run({
config: {
traceExecution: {
inputs: true,
outputs: true,
errors: false,
narratives: false,
startTime: true,
endTime: false
}
}
})
generateGreeting(person: any, greeter: any) {
// Function implementation
}
This configuration will produce a trace node with specific details:
[
{
"data": {
"id": "generateGreeting_1702581469571_2cbd0fc0-a0ef-4482-acad-7200743d18e0",
"label": "1 - generateGreeting",
"inputs": [
// ... (inputs array details)
],
"outputs": {
// ... (outputs details)
},
"startTime": "2023-12-14T19:17:49.571Z",
"abstract": false,
"createTime": "2023-12-14T19:17:49.571Z"
},
"group": "nodes"
}
]
โ ๏ธ Note: If you want to trace errors, explicitly set errors: 'catch'
to catch errors in the trace, as shown in the following example:
@run({
config: {
errors: 'catch', // important if we want to trace errors
traceExecution: {
inputs: true,
outputs: true,
errors: true, // errors tracing is activated
narratives: false,
startTime: true,
endTime: false
}
}
})
generateGreeting(person: any, greeter: any) {
throw new Error('fake error');
}
This ensures that errors are caught and included in the trace.
4. Advanced Attribute Tracing with Accessors and Mappers
In certain scenarios, you may want fine-grained control over which attributes are traced in your Execution Engine logs. The traceExecution
configuration allows you to do just that, offering flexibility through accessors and function mappers.
You can selectively trace specific attributes by using accessors. Here's an example:
@run({
config: {
traceExecution: {
inputs: ['0.name', '0.age', '0.address.city', '1.name'],
outputs: (out: any) => `the output I want to trace is: '${out.fullGreeting}'`,
errors: true,
narratives: true,
startTime: true,
endTime: false
}
}
})
generateGreeting(person: any, greeter: any) {
// Function implementation
}
This configuration will produce a trace node with specific details:
[
{
"data": {
"id": "generateGreeting_1702582392702_e90138a9-3849-4ac2-bd46-f86bbad2bbce",
"label": "1 - generateGreeting",
"inputs": [
{ "0.name": "John Doe"},
{ "0.age": 30 },
{ "0.address.city": "Cityville" },
{ "1.name": "Akram" }
],
"outputs": "the output I want to trace is: 'Hello, John Doe! I'm Akram. Let's explore the world of reading, traveling, coding together!'",
"narratives": ["here is tracing narrative for greeting John Doe"],
"startTime": "2023-12-14T19:33:12.702Z",
"abstract": false,
"createTime": "2023-12-14T19:33:12.702Z"
},
"group": "nodes"
}
]
Explore the trace outputs in JSON format here and visualize the trace graph here.
Conclusion
Customizing TraceOptions
provides flexibility and control over your tracing experience. You can dive into more examples here and explore these configurations to enhance your execution flow traces.
Happy tracing! ๐
Top comments (0)