DEV Community

Akram Tabka
Akram Tabka

Posted on

Mastering TraceOptions in execution-engine: Advanced Code Tracing (part 2)

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
Enter fullscreen mode Exit fullscreen mode

Using yarn:

yarn add execution-engine@2.0.2
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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';
  };
}
Enter fullscreen mode Exit fullscreen mode

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(' ');
      }
    };
  }

}
Enter fullscreen mode Exit fullscreen mode

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'}
  );
Enter fullscreen mode Exit fullscreen mode

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 the traceOptions.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"
  }
]
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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"
  }
]
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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"
  }
]
Enter fullscreen mode Exit fullscreen mode

⚠️ 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');
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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"
  }
]
Enter fullscreen mode Exit fullscreen mode

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! 🚀

Additional Resources

Top comments (0)