Skip to content

Commit 718bbec

Browse files
committed
Merge remote-tracking branch 'upstream/main' into zhaez/cold-start-improvements
2 parents 7e43382 + b542192 commit 718bbec

File tree

18 files changed

+1332
-22
lines changed

18 files changed

+1332
-22
lines changed

.github/workflows/release-build.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,12 @@ jobs:
105105
aws-distro-opentelemetry-node-autoinstrumentation/${{ env.ARTIFACT_NAME }} \
106106
${{ env.ARTIFACT_NAME }}.sha256
107107
108-
# Publish to npm
109-
- name: Publish to npm
108+
# Publish '@aws/aws-distro-opentelemetry-node-autoinstrumentation' to npm
109+
- name: Publish autoinstrumentation to npm
110+
working-directory: aws-distro-opentelemetry-node-autoinstrumentation
110111
env:
111112
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
112113
NPM_CONFIG_PROVENANCE: true
113-
run: npx lerna publish from-package --no-push --no-private --no-git-tag-version --no-verify-access --yes
114+
run: npm publish
114115

115116

.github/workflows/release-lambda.yml

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -174,24 +174,22 @@ jobs:
174174
- name: generate tf layer
175175
working-directory: ${{ env.LAYER_NAME }}
176176
run: |
177-
echo "locals {" >> ../layer.tf
178-
echo " sdk_layer_arns = {" >> ../layer.tf
177+
echo "locals {" >> ../layer_arns.tf
178+
echo " sdk_layer_arns = {" >> ../layer_arns.tf
179179
for file in *
180180
do
181181
read arn < $file
182-
echo " \""$file"\" = \""$arn"\"" >> ../layer.tf
182+
echo " \""$file"\" = \""$arn"\"" >> ../layer_arns.tf
183183
done
184184
cd ..
185-
echo " }" >> layer.tf
186-
echo "}" >> layer.tf
187-
terraform fmt layer.tf
188-
cat layer.tf
189-
- name: upload layer tf file
190-
uses: actions/upload-artifact@v4
185+
echo " }" >> layer_arns.tf
186+
echo "}" >> layer_arns.tf
187+
terraform fmt layer_arns.tf
188+
cat layer_arns.tf
189+
- name: download layer.zip
190+
uses: actions/download-artifact@v4
191191
with:
192-
name: layer.tf
193-
path: layer.tf
194-
192+
name: layer.zip
195193
- name: Get commit hash
196194
id: commit
197195
run: |
@@ -216,6 +214,6 @@ jobs:
216214
--notes-file release_notes.md \
217215
--draft \
218216
"lambda-v${{ github.event.inputs.version }}-${{ steps.commit.outputs.sha_short }}" \
219-
lambda-layer/terraform/lambda/layer.tf
217+
layer_arns.tf layer.zip
220218
echo Removing release_notes.md ...
221219
rm -f release_notes.md

aws-distro-opentelemetry-node-autoinstrumentation/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@
4343
"src/**/*.ts"
4444
],
4545
"exclude": [
46-
"src/third-party/**/*.ts"
46+
"src/third-party/**/*.ts",
47+
"src/otlp-aws-span-exporter.ts"
4748
]
4849
},
4950
"bugs": {
@@ -82,13 +83,17 @@
8283
"@aws-sdk/client-sfn": "^3.632.0",
8384
"@aws-sdk/client-sns": "^3.632.0",
8485
"@opentelemetry/contrib-test-utils": "0.41.0",
86+
"@smithy/protocol-http": "^5.0.1",
87+
"@smithy/signature-v4": "^5.0.1",
8588
"@types/mocha": "7.0.2",
8689
"@types/node": "18.6.5",
90+
"@types/proxyquire": "^1.3.31",
8791
"@types/sinon": "10.0.18",
8892
"expect": "29.2.0",
8993
"mocha": "7.2.0",
9094
"nock": "13.2.1",
9195
"nyc": "15.1.0",
96+
"proxyquire": "^2.1.3",
9297
"rimraf": "5.0.5",
9398
"sinon": "15.2.0",
9499
"ts-loader": "^9.5.2",

aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,14 @@ import { AttributePropagatingSpanProcessorBuilder } from './attribute-propagatin
5656
import { AwsBatchUnsampledSpanProcessor } from './aws-batch-unsampled-span-processor';
5757
import { AwsMetricAttributesSpanExporterBuilder } from './aws-metric-attributes-span-exporter-builder';
5858
import { AwsSpanMetricsProcessorBuilder } from './aws-span-metrics-processor-builder';
59+
import { OTLPAwsSpanExporter } from './otlp-aws-span-exporter';
5960
import { OTLPUdpSpanExporter } from './otlp-udp-exporter';
6061
import { AwsXRayRemoteSampler } from './sampler/aws-xray-remote-sampler';
6162
// This file is generated via `npm run compile`
6263
import { LIB_VERSION } from './version';
6364

65+
const XRAY_OTLP_ENDPOINT_PATTERN = '^https://xray\\.([a-z0-9-]+)\\.amazonaws\\.com/v1/traces$';
66+
6467
const APPLICATION_SIGNALS_ENABLED_CONFIG: string = 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED';
6568
const APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIG: string = 'OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT';
6669
const METRIC_EXPORT_INTERVAL_CONFIG: string = 'OTEL_METRIC_EXPORT_INTERVAL';
@@ -235,6 +238,11 @@ export class AwsOpentelemetryConfigurator {
235238
}
236239

237240
spanProcessors.push(AttributePropagatingSpanProcessorBuilder.create().build());
241+
242+
if (isXrayOtlpEndpoint(process.env['OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'])) {
243+
return;
244+
}
245+
238246
const applicationSignalsMetricExporter: PushMetricExporter =
239247
ApplicationSignalsExporterProvider.Instance.createExporter();
240248
const periodicExportingMetricReader: PeriodicExportingMetricReader = new PeriodicExportingMetricReader({
@@ -422,6 +430,7 @@ export class AwsSpanProcessorProvider {
422430
private resource: Resource;
423431

424432
static configureOtlp(): SpanExporter {
433+
const otlp_exporter_traces_endpoint = process.env['OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'];
425434
// eslint-disable-next-line @typescript-eslint/typedef
426435
let protocol = this.getOtlpProtocol();
427436

@@ -438,12 +447,20 @@ export class AwsSpanProcessorProvider {
438447
case 'http/json':
439448
return new OTLPHttpTraceExporter();
440449
case 'http/protobuf':
450+
if (otlp_exporter_traces_endpoint && isXrayOtlpEndpoint(otlp_exporter_traces_endpoint)) {
451+
diag.debug('Detected XRay OTLP Traces endpoint. Switching exporter to OtlpAwsSpanExporter');
452+
return new OTLPAwsSpanExporter(otlp_exporter_traces_endpoint);
453+
}
441454
return new OTLPProtoTraceExporter();
442455
case 'udp':
443456
diag.debug('Detected AWS Lambda environment and enabling UDPSpanExporter');
444457
return new OTLPUdpSpanExporter(getXrayDaemonEndpoint(), FORMAT_OTEL_SAMPLED_TRACES_BINARY_PREFIX);
445458
default:
446459
diag.warn(`Unsupported OTLP traces protocol: ${protocol}. Using http/protobuf.`);
460+
if (otlp_exporter_traces_endpoint && isXrayOtlpEndpoint(otlp_exporter_traces_endpoint)) {
461+
diag.debug('Detected XRay OTLP Traces endpoint. Switching exporter to OtlpAwsSpanExporter');
462+
return new OTLPAwsSpanExporter(otlp_exporter_traces_endpoint);
463+
}
447464
return new OTLPProtoTraceExporter();
448465
}
449466
}
@@ -652,4 +669,8 @@ function getXrayDaemonEndpoint() {
652669
return process.env[AWS_XRAY_DAEMON_ADDRESS_CONFIG];
653670
}
654671

672+
function isXrayOtlpEndpoint(otlpEndpoint: string | undefined) {
673+
return otlpEndpoint && new RegExp(XRAY_OTLP_ENDPOINT_PATTERN).test(otlpEndpoint.toLowerCase());
674+
}
675+
655676
// END The OpenTelemetry Authors code
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import { OTLPTraceExporter as OTLPProtoTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
4+
import { diag } from '@opentelemetry/api';
5+
import { OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base';
6+
import { ProtobufTraceSerializer } from '@opentelemetry/otlp-transformer';
7+
import { ReadableSpan } from '@opentelemetry/sdk-trace-base';
8+
import { ExportResult } from '@opentelemetry/core';
9+
import { getNodeVersion } from './utils';
10+
11+
/**
12+
* This exporter extends the functionality of the OTLPProtoTraceExporter to allow spans to be exported
13+
* to the XRay OTLP endpoint https://xray.[AWSRegion].amazonaws.com/v1/traces. Utilizes the aws-sdk
14+
* library to sign and directly inject SigV4 Authentication to the exported request's headers. <a
15+
* href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-OTLPEndpoint.html">...</a>
16+
*
17+
* This only works with version >=16 Node.js environments.
18+
*/
19+
export class OTLPAwsSpanExporter extends OTLPProtoTraceExporter {
20+
private static readonly SERVICE_NAME: string = 'xray';
21+
private endpoint: string;
22+
private region: string;
23+
24+
// Holds the dependencies needed to sign the SigV4 headers
25+
private defaultProvider: any;
26+
private sha256: any;
27+
private signatureV4: any;
28+
private httpRequest: any;
29+
30+
// If the required dependencies are installed then we enable SigV4 signing. Otherwise skip it
31+
private hasRequiredDependencies: boolean = false;
32+
33+
constructor(endpoint: string, config?: OTLPExporterNodeConfigBase) {
34+
super(OTLPAwsSpanExporter.changeUrlConfig(endpoint, config));
35+
this.initDependencies();
36+
this.region = endpoint.split('.')[1];
37+
this.endpoint = endpoint;
38+
}
39+
40+
/**
41+
* Overrides the upstream implementation of export. All behaviors are the same except if the
42+
* endpoint is an XRay OTLP endpoint, we will sign the request with SigV4 in headers before
43+
* sending it to the endpoint. Otherwise, we will skip signing.
44+
*/
45+
public override async export(items: ReadableSpan[], resultCallback: (result: ExportResult) => void): Promise<void> {
46+
// Only do SigV4 Signing if the required dependencies are installed. Otherwise default to the regular http/protobuf exporter.
47+
if (this.hasRequiredDependencies) {
48+
const url = new URL(this.endpoint);
49+
const serializedSpans: Uint8Array | undefined = ProtobufTraceSerializer.serializeRequest(items);
50+
51+
if (serializedSpans === undefined) {
52+
return;
53+
}
54+
55+
/*
56+
This is bad practice but there is no other way to access and inject SigV4 headers
57+
into the request headers before the traces get exported.
58+
*/
59+
const oldHeaders = (this as any)._transport?._transport?._parameters?.headers;
60+
61+
if (oldHeaders) {
62+
const request = new this.httpRequest({
63+
method: 'POST',
64+
protocol: 'https',
65+
hostname: url.hostname,
66+
path: url.pathname,
67+
body: serializedSpans,
68+
headers: {
69+
...this.removeSigV4Headers(oldHeaders),
70+
host: url.hostname,
71+
},
72+
});
73+
74+
try {
75+
const signer = new this.signatureV4({
76+
credentials: this.defaultProvider(),
77+
region: this.region,
78+
service: OTLPAwsSpanExporter.SERVICE_NAME,
79+
sha256: this.sha256,
80+
});
81+
82+
const signedRequest = await signer.sign(request);
83+
84+
(this as any)._transport._transport._parameters.headers = signedRequest.headers;
85+
} catch (exception) {
86+
diag.debug(
87+
`Failed to sign/authenticate the given exported Span request to OTLP XRay endpoint with error: ${exception}`
88+
);
89+
}
90+
}
91+
}
92+
93+
await super.export(items, resultCallback);
94+
}
95+
96+
// Removes Sigv4 headers from old headers to avoid accidentally copying them to the new headers
97+
private removeSigV4Headers(headers: Record<string, string>) {
98+
const newHeaders: Record<string, string> = {};
99+
const sigV4Headers = ['x-amz-date', 'authorization', 'x-amz-content-sha256', 'x-amz-security-token'];
100+
101+
for (const key in headers) {
102+
if (!sigV4Headers.includes(key.toLowerCase())) {
103+
newHeaders[key] = headers[key];
104+
}
105+
}
106+
return newHeaders;
107+
}
108+
109+
private initDependencies(): any {
110+
if (getNodeVersion() < 16) {
111+
diag.error('SigV4 signing requires atleast Node major version 16');
112+
return;
113+
}
114+
115+
try {
116+
const awsSdkModule = require('@aws-sdk/credential-provider-node');
117+
const awsCryptoModule = require('@aws-crypto/sha256-js');
118+
const signatureModule = require('@smithy/signature-v4');
119+
const httpModule = require('@smithy/protocol-http');
120+
121+
(this.defaultProvider = awsSdkModule.defaultProvider),
122+
(this.sha256 = awsCryptoModule.Sha256),
123+
(this.signatureV4 = signatureModule.SignatureV4),
124+
(this.httpRequest = httpModule.HttpRequest);
125+
this.hasRequiredDependencies = true;
126+
} catch (error) {
127+
diag.error(`Failed to load required AWS dependency for SigV4 Signing: ${error}`);
128+
}
129+
}
130+
131+
private static changeUrlConfig(endpoint: string, config?: OTLPExporterNodeConfigBase) {
132+
const newConfig =
133+
config === undefined
134+
? { url: endpoint }
135+
: {
136+
...config,
137+
url: endpoint,
138+
};
139+
140+
return newConfig;
141+
}
142+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
export const getNodeVersion = () => {
5+
const nodeVersion = process.versions.node;
6+
const versionParts = nodeVersion.split('.');
7+
8+
if (versionParts.length === 0) {
9+
return -1;
10+
}
11+
12+
const majorVersion = parseInt(versionParts[0], 10);
13+
14+
if (isNaN(majorVersion)) {
15+
return -1;
16+
}
17+
18+
return majorVersion;
19+
};

0 commit comments

Comments
 (0)