Mastering TraceOptions in execution-engine: Advanced Code Tracing (part 2)
Tags: node, engine, workflow, typescript
Published: 2023-12-14 23:05:05

## Introduction

Welcome to the next chapter of our exploration into the fascinating capabilities of the **[execution-engine](https://github.com/tabkram/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](https://dev.to/tabkram/execution-workflow-tracing-a-guide-to-execution-engine-library-14he)**, 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](https://www.npmjs.com/package/execution-engine), you can do it by following these steps:

### Using npm:

```bash
npm install execution-engine@2.0.2
```

### Using yarn:

```bash
yarn add execution-engine@2.0.2
```

Refer to the [npm package page](https://www.npmjs.com/package/execution-engine) for updates or additional information, and explore the source code on [GitHub](https://github.com/tabkram/execution-engine) 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:

```typescript
{
  id: string;
  label: string;
  parent?: string;
}
```

### Full Trace Options Object:

```typescript
{
  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 <u>the same tracing can be achieved with the basic usage method</u> via the `.run()` approach.


Let's suppose we have a `GreetingTask` class defined as follows:

```typescript
@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:

```typescript
  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 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:

```typescript
[
  {
    "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:

```typescript
@run({
  config: {
    traceExecution: false
  }
})
generateGreeting(person: any, greeter: any) {
  // Function implementation
}
```

This will result in a trace node with only essential details:

```json
[
  {
    "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:

```typescript
@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:

```json
[
  {
    "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:

```typescript
@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:

```typescript
@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:
```json
[
  {
    "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](https://github.com/tabkram/execution-engine/blob/v2.0.2/examples/greeting.json)** and **visualize the trace graph [here](https://tabkram.github.io/json-to-graph/?data=https://raw.githubusercontent.com/tabkram/execution-engine/v2.0.2/examples/greeting.json)**.

## Conclusion

Customizing `TraceOptions` provides flexibility and control over your tracing experience. You can dive into more examples [here](https://github.com/tabkram/execution-engine/tree/v2.0.2/examples) and explore these configurations to enhance your execution flow traces.

Happy tracing! 🚀

## Additional Resources
- [Execution workflow tracing: a guide to execution-engine library](https://dev.to/tabkram/execution-workflow-tracing-a-guide-to-execution-engine-library-14he)
- [Execution Engine GitHub Repository](https://github.com/tabkram/execution-engine)
- [Execution Engine Documentation](https://tabkram.github.io/execution-engine)
- [json-to-graph Online Demo](https://tabkram.github.io/json-to-graph/)