DataDog / datadog-lambda-js

The Datadog AWS Lambda Library for Node
Apache License 2.0
109 stars 35 forks source link

datadog + serverless + async_hooks not working as expected #424

Open mjmarianetti opened 11 months ago

mjmarianetti commented 11 months ago

Expected Behavior

Using async_hooks to manage context inside a lambda function would allow the datadog layer to properly inject all logs with span_id and trace_id

Actual Behavior

Logs are injected with service and version but span_id and trace_id are missing.

Have a class that is used to wrap all operations for a given request and maintain context between each log. Our logger is winston

context.ts file
--------------  
class RequestContextResource extends AsyncResource {
  data: RequestContext;

  constructor() {
    super('RequestContextResource');
    this.data = {};
  }
}

export class ContextService {
  constructor() {
      this.resource = new RequestContextResource();
    }

  public async wrap<T>(callback: () => Promise<T>): Promise<T> {
      return new Promise<T>((resolve, reject) => {
        this.resource.runInAsyncScope(
          () => {
            callback()
              .then(result => resolve(result))
              .catch(error => reject(error))
              .finally(() => {
                // Clean up the context after the callback has completed
               this.resource.data = {};
              });
          },
          null,
          {triggerAsyncId: this.resource.asyncId},
        );
      });
    }

  public addContext(data: unknown): void {
   for (const key of Object.keys(data)) {
      if (Object.prototype.hasOwnProperty.call(data, key)) {
        this.resource.data[key] = data[key];
      }
    }
  }

  public debug(message) {
     winston.debug(message, this.resource.data);
  }

  }

lambda.ts file
--------------  
const context = new ContextService();
handler = return context.wrap((lambdaEvent) => {
  context.addContext({requestId: lambdaEvent.requestId})
  context.debug(`this is a log message`)
  context.addContext({userId: lambdaEvent.userId})
  context.debug(`this is another log message`)
});

Calling this function with lambdaEvent = {userId: "1234", requestId: "321321"} we would expect the following result

  {
    "dd": {
        "service": "service-name",
        "version": "1.0.0",
        "trace_id":"xxxxxx", ---> this is missing in the logs
        "span_id":"xxxxxx" ---> this is missing in the logs
    },
    "level": "debug",
    "message": "this is a log message",
    "requestId": "321321"
}
{
    "dd": {
        "service": "service-name",
        "version": "1.0.0",
        "trace_id":"xxxxxx", ---> this is missing in the logs
        "span_id":"xxxxxx" ---> this is missing in the logs
    },
    "level": "debug",
    "message": "this is another log message",
    "requestId": "321321",
    "userId: "1234"
}  

Steps to Reproduce the Problem

  1. Wrap the lambda handler with a function that allow you to add context to your requests
  2. Call the lambda handler
  3. Expect to have span_id and trace_id being injected in cloudwatch log output from the lambda function.

Extra information:

Specifications

plugins:

Stacktrace