Skip to content

Commit fc70535

Browse files
authored
feat(lambda): add support for auto-instrumentation with ADOT Lambda layer (#23027)
AWS Distro for OpenTelemetry (ADOT) layers provide OpenTelemetry agent with an out-of-the-box configuration for AWS Lambda and AWS X-Ray. For the popular runtimes (Java, Python, Node), it provides automatic instrumentation without code change. This commit allows CDK users to easily add an ADOT Lambda layer to their Lambda function with the new prop `adotInstrumentationConfig`. For example: ```ts const fn = new lambda.Function(this, 'MyFunction', { ... adotInstrumentation: { layerVersion: AdotLayerVersion.fromJavaScriptSdkLayerVersion(AdotLambdaLayerJavaScriptSdkVersion.V1_7_0), execWrapper: AdotLambdaExecWrapper.REGULAR_HANDLER, } ... }); ``` To support this feature, we provides the following a new classes, enum, and helper functions to users: * `AdotLambdaExecWrapper` is the enum of all possible wrapper script that's provided by ADOT Lambda layer and must be set to the `AWS_LAMBDA_EXEC_WRAPPER` environment variable * `AdotLambdaLayer*Version` are five classes representing five different Lambda layer types offered by ADOT. * `AdotLayerVersion.from*LayerVersion` are five helper functions that return the layer ARN for the Lambda function during synthesis time. If users want to retrieve the ARN independently from enabling ADOT in the Lambda function, they can use the following pattern: ```ts declare const fn: lambda.Function; const layerArn = lambda.AdotLambdaLayerJavaSdkVersion.V1_19_0.layerArn(fn.stack, fn.architecture); ``` ---- ### All Submissions: * [X] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [X] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [X] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 1b7a384 commit fc70535

21 files changed

+4198
-8
lines changed

packages/@aws-cdk/aws-lambda/README.md

+49
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,55 @@ const fn = new lambda.Function(this, 'MyFunction', {
764764
See [the AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html)
765765
to learn more about AWS Lambda's X-Ray support.
766766

767+
## Lambda with AWS Distro for OpenTelemetry layer
768+
769+
To have automatic integration with XRay without having to add dependencies or change your code, you can use the
770+
[AWS Distro for OpenTelemetry Lambda (ADOT) layer](https://aws-otel.github.io/docs/getting-started/lambda).
771+
Consuming the latest ADOT layer can be done with the following snippet:
772+
773+
```ts
774+
import {
775+
AdotLambdaExecWrapper,
776+
AdotLayerVersion,
777+
AdotLambdaLayerJavaScriptSdkVersion,
778+
} from 'aws-cdk-lib/aws-lambda';
779+
780+
const fn = new lambda.Function(this, 'MyFunction', {
781+
runtime: lambda.Runtime.NODEJS_18_X,
782+
handler: 'index.handler',
783+
code: lambda.Code.fromInline('exports.handler = function(event, ctx, cb) { return cb(null, "hi"); }'),
784+
adotInstrumentation: {
785+
layerVersion: AdotLayerVersion.fromJavaScriptSdkLayerVersion(AdotLambdaLayerJavaScriptSdkVersion.LATEST),
786+
execWrapper: AdotLambdaExecWrapper.REGULAR_HANDLER,
787+
},
788+
});
789+
```
790+
791+
To use a different layer version, use one of the following helper functions for the `layerVersion` prop:
792+
793+
* `AdotLayerVersion.fromJavaScriptSdkLayerVersion`
794+
* `AdotLayerVersion.fromPythonSdkLayerVersion`
795+
* `AdotLayerVersion.fromJavaSdkLayerVersion`
796+
* `AdotLayerVersion.fromJavaAutoInstrumentationSdkLayerVersion`
797+
* `AdotLayerVersion.fromGenericSdkLayerVersion`
798+
799+
Each helper function expects a version value from a corresponding enum-like class as below:
800+
801+
* `AdotLambdaLayerJavaScriptSdkVersion`
802+
* `AdotLambdaLayerPythonSdkVersion`
803+
* `AdotLambdaLayerJavaSdkVersion`
804+
* `AdotLambdaLayerJavaAutoInstrumentationSdkVersion`
805+
* `AdotLambdaLayerGenericSdkVersion`
806+
807+
For more examples, see our [the integration test](test/integ.lambda-adot.ts).
808+
809+
If you want to retrieve the ARN of the ADOT Lambda layer without enabling ADOT in a Lambda function:
810+
811+
```ts
812+
declare const fn: lambda.Function;
813+
const layerArn = lambda.AdotLambdaLayerJavaSdkVersion.V1_19_0.layerArn(fn.stack, fn.architecture);
814+
```
815+
767816
## Lambda with Profiling
768817

769818
The following code configures the lambda function with CodeGuru profiling. By default, this creates a new CodeGuru
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
import { RegionInfo } from '@aws-cdk/region-info';
2+
import { IConstruct } from 'constructs';
3+
import { Stack } from '../../core/lib/stack';
4+
import { Token } from '../../core/lib/token';
5+
import { FactName } from '../../region-info/lib/fact';
6+
import { Architecture } from './architecture';
7+
import { IFunction } from './function-base';
8+
9+
/**
10+
* The type of ADOT Lambda layer
11+
*/
12+
enum AdotLambdaLayerType {
13+
/**
14+
* The Lambda layer for ADOT Java instrumentation library. This layer only auto-instruments AWS
15+
* SDK libraries.
16+
*/
17+
JAVA_SDK = 'JAVA_SDK',
18+
19+
/**
20+
* The Lambda layer for ADOT Java Auto-Instrumentation Agent. This layer automatically instruments
21+
* a large number of libraries and frameworks out of the box and has notable impact on startup
22+
* performance.
23+
*/
24+
JAVA_AUTO_INSTRUMENTATION = 'JAVA_AUTO_INSTRUMENTATION',
25+
26+
/**
27+
* The Lambda layer for ADOT Collector, OpenTelemetry for JavaScript and supported libraries.
28+
*/
29+
JAVASCRIPT_SDK = 'JAVASCRIPT_SDK',
30+
31+
/**
32+
* The Lambda layer for ADOT Collector, OpenTelemetry for Python and supported libraries.
33+
*/
34+
PYTHON_SDK = 'PYTHON_SDK',
35+
36+
/**
37+
* The generic Lambda layer that contains only ADOT Collector, used for manual instrumentation
38+
* use cases (such as Go or DotNet runtimes).
39+
*/
40+
GENERIC = 'GENERIC',
41+
}
42+
43+
/**
44+
* Config returned from {@link AdotLambdaLayerVersion._bind}
45+
*/
46+
interface AdotLambdaLayerBindConfig {
47+
/**
48+
* ARN of the ADOT Lambda layer version
49+
*/
50+
readonly arn: string;
51+
}
52+
53+
/**
54+
* Return the ARN of an ADOT Lambda layer given its properties. If the region name is unknown
55+
* at synthesis time, it will generate a map in the CloudFormation template and perform the look
56+
* up at deployment time.
57+
*
58+
* @param scope the parent Construct that will use the imported layer.
59+
* @param type the type of the ADOT Lambda layer
60+
* @param version The version of the ADOT Lambda layer
61+
* @param architecture the architecture of the Lambda layer ('amd64' or 'arm64')
62+
*/
63+
function getLayerArn(scope: IConstruct, type: string, version: string, architecture: string): string {
64+
const scopeStack = Stack.of(scope);
65+
const region = scopeStack.region;
66+
67+
// Region is defined, look up the arn, or throw an error if the version isn't supported by a region
68+
if (region !== undefined && !Token.isUnresolved(region)) {
69+
const arn = RegionInfo.get(region).adotLambdaLayerArn(type, version, architecture);
70+
if (arn === undefined) {
71+
throw new Error(
72+
`Could not find the ARN information for the ADOT Lambda Layer of type ${type} and version ${version} in ${region}`,
73+
);
74+
}
75+
return arn;
76+
}
77+
78+
// Otherwise, need to add a mapping to be looked up at deployment time
79+
return scopeStack.regionalFact(FactName.adotLambdaLayer(type, version, architecture));
80+
}
81+
82+
/**
83+
* Properties for an ADOT instrumentation in Lambda
84+
*/
85+
export interface AdotInstrumentationConfig {
86+
/**
87+
* The ADOT Lambda layer.
88+
*/
89+
readonly layerVersion: AdotLayerVersion;
90+
91+
/**
92+
* The startup script to run, see ADOT documentation to pick the right script for your use case: https://aws-otel.github.io/docs/getting-started/lambda
93+
*/
94+
readonly execWrapper: AdotLambdaExecWrapper;
95+
}
96+
97+
/**
98+
* An ADOT Lambda layer version that's specific to a lambda layer type and an architecture.
99+
*/
100+
export abstract class AdotLayerVersion {
101+
/**
102+
* The ADOT Lambda layer for Java SDK
103+
*
104+
* @param version The version of the Lambda layer to use
105+
*/
106+
public static fromJavaSdkLayerVersion(version: AdotLambdaLayerJavaSdkVersion): AdotLayerVersion {
107+
return AdotLayerVersion.fromAdotVersion(version);
108+
}
109+
110+
/**
111+
* The ADOT Lambda layer for Java auto instrumentation
112+
*
113+
* @param version The version of the Lambda layer to use
114+
*/
115+
public static fromJavaAutoInstrumentationLayerVersion(
116+
version: AdotLambdaLayerJavaAutoInstrumentationVersion,
117+
): AdotLayerVersion {
118+
return AdotLayerVersion.fromAdotVersion(version);
119+
}
120+
121+
/**
122+
* The ADOT Lambda layer for JavaScript SDK
123+
*
124+
* @param version The version of the Lambda layer to use
125+
*/
126+
public static fromJavaScriptSdkLayerVersion(version: AdotLambdaLayerJavaScriptSdkVersion): AdotLayerVersion {
127+
return AdotLayerVersion.fromAdotVersion(version);
128+
}
129+
130+
/**
131+
* The ADOT Lambda layer for Python SDK
132+
*
133+
* @param version The version of the Lambda layer to use
134+
*/
135+
public static fromPythonSdkLayerVersion(version: AdotLambdaLayerPythonSdkVersion): AdotLayerVersion {
136+
return AdotLayerVersion.fromAdotVersion(version);
137+
}
138+
139+
/**
140+
* The ADOT Lambda layer for generic use cases
141+
*
142+
* @param version The version of the Lambda layer to use
143+
*/
144+
public static fromGenericLayerVersion(version: AdotLambdaLayerGenericVersion): AdotLayerVersion {
145+
return AdotLayerVersion.fromAdotVersion(version);
146+
}
147+
148+
private static fromAdotVersion(adotVersion: AdotLambdaLayerVersion): AdotLayerVersion {
149+
return new (class extends AdotLayerVersion {
150+
_bind(_function: IFunction): AdotLambdaLayerBindConfig {
151+
return {
152+
arn: adotVersion.layerArn(_function.stack, _function.architecture),
153+
};
154+
}
155+
})();
156+
}
157+
158+
/**
159+
* Produce a `AdotLambdaLayerBindConfig` instance from this `AdotLayerVersion` instance.
160+
*
161+
* @internal
162+
*/
163+
public abstract _bind(_function: IFunction): AdotLambdaLayerBindConfig;
164+
}
165+
166+
/**
167+
* The wrapper script to be used for the Lambda function in order to enable auto instrumentation
168+
* with ADOT.
169+
*/
170+
export enum AdotLambdaExecWrapper {
171+
/**
172+
* Wrapping regular Lambda handlers.
173+
*/
174+
REGULAR_HANDLER = '/opt/otel-handler',
175+
176+
/**
177+
* Wrapping regular handlers (implementing RequestHandler) proxied through API Gateway, enabling
178+
* HTTP context propagation.
179+
*/
180+
PROXY_HANDLER = '/opt/otel-proxy-handler',
181+
182+
/**
183+
* Wrapping streaming handlers (implementing RequestStreamHandler), enabling HTTP context
184+
* propagation for HTTP requests.
185+
*/
186+
STREAM_HANDLER = '/opt/otel-stream-handler',
187+
}
188+
189+
abstract class AdotLambdaLayerVersion {
190+
protected constructor(protected readonly type: AdotLambdaLayerType, protected readonly version: string) {}
191+
192+
/**
193+
* The ARN of the Lambda layer
194+
*
195+
* @param scope The binding scope. Usually this is the stack where the Lambda layer is bound to
196+
* @param architecture The architecture of the Lambda layer (either X86_64 or ARM_64)
197+
*/
198+
public layerArn(scope: IConstruct, architecture: Architecture): string {
199+
return getLayerArn(scope, this.type, this.version, architecture.name);
200+
}
201+
}
202+
203+
/**
204+
* The collection of versions of the ADOT Lambda Layer for Java SDK
205+
*/
206+
export class AdotLambdaLayerJavaSdkVersion extends AdotLambdaLayerVersion {
207+
/**
208+
* The latest layer version available in this CDK version. New versions could
209+
* introduce incompatible changes. Make sure to test them before deploying to production.
210+
*/
211+
public static readonly LATEST = new AdotLambdaLayerJavaSdkVersion('1.19.0');
212+
213+
/**
214+
* Version 1.19.0
215+
*/
216+
public static readonly V1_19_0 = new AdotLambdaLayerJavaSdkVersion('1.19.0');
217+
218+
private constructor(protected readonly layerVersion: string) {
219+
super(AdotLambdaLayerType.JAVA_SDK, layerVersion);
220+
}
221+
}
222+
223+
/**
224+
* The collection of versions of the ADOT Lambda Layer for Java auto-instrumentation
225+
*/
226+
export class AdotLambdaLayerJavaAutoInstrumentationVersion extends AdotLambdaLayerVersion {
227+
/**
228+
* The latest layer version available in this CDK version. New versions could
229+
* introduce incompatible changes. Make sure to test them before deploying to production.
230+
*/
231+
public static readonly LATEST = new AdotLambdaLayerJavaAutoInstrumentationVersion('1.19.2');
232+
233+
/**
234+
* Version 1.19.2
235+
*/
236+
public static readonly V1_19_2 = new AdotLambdaLayerJavaAutoInstrumentationVersion('1.19.2');
237+
238+
private constructor(protected readonly layerVersion: string) {
239+
super(AdotLambdaLayerType.JAVA_AUTO_INSTRUMENTATION, layerVersion);
240+
}
241+
}
242+
243+
/**
244+
* The collection of versions of the ADOT Lambda Layer for Python SDK
245+
*/
246+
export class AdotLambdaLayerPythonSdkVersion extends AdotLambdaLayerVersion {
247+
/**
248+
* The latest layer version available in this CDK version. New versions could
249+
* introduce incompatible changes. Make sure to test them before deploying to production.
250+
*/
251+
public static readonly LATEST = new AdotLambdaLayerPythonSdkVersion('1.13.0');
252+
253+
/**
254+
* Version 1.13.0
255+
*/
256+
public static readonly V1_13_0 = new AdotLambdaLayerPythonSdkVersion('1.13.0');
257+
258+
private constructor(protected readonly layerVersion: string) {
259+
super(AdotLambdaLayerType.PYTHON_SDK, layerVersion);
260+
}
261+
}
262+
263+
/**
264+
* The collection of versions of the ADOT Lambda Layer for JavaScript SDK
265+
*/
266+
export class AdotLambdaLayerJavaScriptSdkVersion extends AdotLambdaLayerVersion {
267+
/**
268+
* The latest layer version available in this CDK version. New versions could
269+
* introduce incompatible changes. Make sure to test them before deploying to production.
270+
*/
271+
public static readonly LATEST = new AdotLambdaLayerJavaScriptSdkVersion('1.7.0');
272+
273+
/**
274+
* Version 1.7.0
275+
*/
276+
public static readonly V1_7_0 = new AdotLambdaLayerJavaScriptSdkVersion('1.7.0');
277+
278+
private constructor(protected readonly layerVersion: string) {
279+
super(AdotLambdaLayerType.JAVASCRIPT_SDK, layerVersion);
280+
}
281+
}
282+
283+
/**
284+
* The collection of versions of the ADOT Lambda Layer for generic purpose
285+
*/
286+
export class AdotLambdaLayerGenericVersion extends AdotLambdaLayerVersion {
287+
/**
288+
* The latest layer version available in this CDK version. New versions could
289+
* introduce incompatible changes. Make sure to test them before deploying to production.
290+
*/
291+
public static readonly LATEST = new AdotLambdaLayerGenericVersion('0.62.1');
292+
293+
/**
294+
* Version 0.62.1
295+
*/
296+
public static readonly V0_62_1 = new AdotLambdaLayerGenericVersion('0.62.1');
297+
298+
private constructor(protected readonly layerVersion: string) {
299+
super(AdotLambdaLayerType.GENERIC, layerVersion);
300+
}
301+
}

0 commit comments

Comments
 (0)