Skip to content

Commit

Permalink
feat(instrumentation-aws-lambda): Adds lambdaHandler config option (#…
Browse files Browse the repository at this point in the history
…1627)

* feat(lambda): Adds lambdaHandler config option

* Apply suggestions from code review

Co-authored-by: Amir Blum <[email protected]>

* Apply suggestions from code review

Co-authored-by: Carol Abadeer <[email protected]>

* Update README.md

---------

Co-authored-by: Amir Blum <[email protected]>
Co-authored-by: Carol Abadeer <[email protected]>
  • Loading branch information
3 people authored Aug 14, 2023
1 parent 825b5a8 commit c4a8e82
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 1 deletion.
13 changes: 13 additions & 0 deletions plugins/node/opentelemetry-instrumentation-aws-lambda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ In your Lambda function configuration, add or update the `NODE_OPTIONS` environm
| `responseHook` | `ResponseHook` (function) | Hook for adding custom attributes before lambda returns the response. Receives params: `span, { err?, res? }` |
| `disableAwsContextPropagation` | `boolean` | By default, this instrumentation will try to read the context from the `_X_AMZN_TRACE_ID` environment variable set by Lambda, set this to `true` or set the environment variable `OTEL_LAMBDA_DISABLE_AWS_CONTEXT_PROPAGATION=true` to disable this behavior |
| `eventContextExtractor` | `EventContextExtractor` (function) | Function for providing custom context extractor in order to support different event types that are handled by AWS Lambda (e.g., SQS, CloudWatch, Kinesis, API Gateway). Applied only when `disableAwsContextPropagation` is set to `true`. Receives params: `event, context` |
| `lambdaHandler` | `string` | By default, this instrumentation automatically determines the Lambda handler function to instrument. This option is used to override that behavior by explicitly specifying the Lambda handler to instrument. See [Specifying the Lambda Handler](#specifying-the-lambda-handler) for additional information. |

### Hooks Usage Example

Expand All @@ -69,6 +70,18 @@ new AwsLambdaInstrumentation({
})
```

### Specifying the Lambda Handler

The instrumentation will attempt to automatically determine the Lambda handler function to instrument. To do this, it relies on the `_HANDLER` environment variable which is [set by the Lambda runtime](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime). For most use cases, this will accurately represent the handler that should be targeted by this instrumentation.

There exist use cases where the `_HANDLER` environment variable does not accurately represent the module that should be targeted by this instrumentation. For these use cases, the `lambdaHandler` option can be used to explicitly specify the Lambda handler that should be instrumented.

To better explain when `lambdaHandler` should be specified, consider how some telemetry tools, such as [Datadog](https://www.datadoghq.com/), are instrumented into the Lambda runtime. Datadog does this by overriding the handler function with a wrapper function that is loaded via a [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html). In these examples, the Lambda's handler will point to the Datadog wrapper and not to the actual handler that should be instrumented. In cases like this, `lambdaHandler` should be used to explicitly specify the handler that should be instrumented.

The `lambdaHandler` should be specified as a string in the format `<file>.<handler>`, where `<file>` is the name of the file that contains the handler and `<handler>` is the name of the handler function. For example, if the handler is defined in the file `index.js` and the handler function is named `handler`, the `lambdaHandler` should be specified as `index.handler`.

One way to determine if the `lambdaHandler` option should be used is to check the handler defined on your Lambda. This can be done by determining the value of the `_HANDLER` environment variable or by viewing the **Runtime Settings** of your Lambda in AWS Console. If the handler is what you expect, then the instrumentation should work without the `lambdaHandler` option. If the handler points to something else, then the `lambdaHandler` option should be used to explicitly specify the handler that should be instrumented.

## Useful links

- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,14 @@ export class AwsLambdaInstrumentation extends InstrumentationBase {

init() {
const taskRoot = process.env.LAMBDA_TASK_ROOT;
const handlerDef = process.env._HANDLER;
const handlerDef = this._config.lambdaHandler ?? process.env._HANDLER;

// _HANDLER and LAMBDA_TASK_ROOT are always defined in Lambda but guard bail out if in the future this changes.
if (!taskRoot || !handlerDef) {
diag.error(
'Unable to initialize instrumentation for lambda. Cannot identify lambda handler or task root.',
{ taskRoot, handlerDef }
);
return [];
}

Expand All @@ -123,6 +127,16 @@ export class AwsLambdaInstrumentation extends InstrumentationBase {
}
}

diag.debug('Instrumenting lambda handler', {
taskRoot,
handlerDef,
handler,
moduleRoot,
module,
filename,
functionName,
});

return [
new InstrumentationNodeModuleDefinition(
// NB: The patching infrastructure seems to match names backwards, this must be the filename, while
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ export interface AwsLambdaInstrumentationConfig extends InstrumentationConfig {
responseHook?: ResponseHook;
disableAwsContextPropagation?: boolean;
eventContextExtractor?: EventContextExtractor;
lambdaHandler?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -964,4 +964,25 @@ describe('lambda handler', () => {
});
});
});

describe('custom handler', () => {
it('prioritizes instrumenting the handler specified on the config over the handler implied from the _HANDLER env var', async () => {
initializeHandler('not-a-real-handler', {
lambdaHandler: 'lambda-test/async.handler',
});

const otherEvent = {};
const result = await lambdaRequire('lambda-test/async').handler(
otherEvent,
ctx
);

assert.strictEqual(result, 'ok');
const spans = memoryExporter.getFinishedSpans();
const [span] = spans;
assert.strictEqual(spans.length, 1);
assertSpanSuccess(span);
assert.strictEqual(span.parentSpanId, undefined);
});
});
});

0 comments on commit c4a8e82

Please sign in to comment.