@depup/elastic-apm-node 4.15.0-depup.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +26 -0
- package/NOTICE.md +442 -0
- package/README.md +48 -0
- package/changes.json +78 -0
- package/index.d.ts +398 -0
- package/index.js +11 -0
- package/lib/InflightEventSet.js +53 -0
- package/lib/activation-method.js +119 -0
- package/lib/agent.js +941 -0
- package/lib/apm-client/apm-client.js +313 -0
- package/lib/apm-client/http-apm-client/CHANGELOG.md +271 -0
- package/lib/apm-client/http-apm-client/README.md +485 -0
- package/lib/apm-client/http-apm-client/central-config.js +41 -0
- package/lib/apm-client/http-apm-client/container-info.js +111 -0
- package/lib/apm-client/http-apm-client/detect-hostname.js +96 -0
- package/lib/apm-client/http-apm-client/index.js +1975 -0
- package/lib/apm-client/http-apm-client/logging.js +31 -0
- package/lib/apm-client/http-apm-client/ndjson.js +20 -0
- package/lib/apm-client/http-apm-client/truncate.js +434 -0
- package/lib/apm-client/noop-apm-client.js +73 -0
- package/lib/async-hooks-polyfill.js +58 -0
- package/lib/cloud-metadata/aws.js +175 -0
- package/lib/cloud-metadata/azure.js +123 -0
- package/lib/cloud-metadata/callback-coordination.js +159 -0
- package/lib/cloud-metadata/gcp.js +133 -0
- package/lib/cloud-metadata/index.js +175 -0
- package/lib/config/config.js +458 -0
- package/lib/config/normalizers.js +701 -0
- package/lib/config/schema.js +1007 -0
- package/lib/constants.js +35 -0
- package/lib/errors.js +303 -0
- package/lib/filters/sanitize-field-names.js +69 -0
- package/lib/http-request.js +249 -0
- package/lib/instrumentation/azure-functions.js +519 -0
- package/lib/instrumentation/context.js +56 -0
- package/lib/instrumentation/dropped-spans-stats.js +112 -0
- package/lib/instrumentation/elasticsearch-shared.js +63 -0
- package/lib/instrumentation/express-utils.js +91 -0
- package/lib/instrumentation/generic-span.js +322 -0
- package/lib/instrumentation/http-shared.js +424 -0
- package/lib/instrumentation/ids.js +39 -0
- package/lib/instrumentation/index.js +1127 -0
- package/lib/instrumentation/modules/@apollo/server.js +30 -0
- package/lib/instrumentation/modules/@aws-sdk/client-dynamodb.js +143 -0
- package/lib/instrumentation/modules/@aws-sdk/client-s3.js +230 -0
- package/lib/instrumentation/modules/@aws-sdk/client-sns.js +197 -0
- package/lib/instrumentation/modules/@aws-sdk/client-sqs.js +336 -0
- package/lib/instrumentation/modules/@elastic/elasticsearch.js +343 -0
- package/lib/instrumentation/modules/@hapi/hapi.js +221 -0
- package/lib/instrumentation/modules/@opentelemetry/api.js +86 -0
- package/lib/instrumentation/modules/@opentelemetry/sdk-metrics.js +79 -0
- package/lib/instrumentation/modules/@redis/client/dist/lib/client/commands-queue.js +178 -0
- package/lib/instrumentation/modules/@redis/client/dist/lib/client/index.js +49 -0
- package/lib/instrumentation/modules/@smithy/smithy-client.js +198 -0
- package/lib/instrumentation/modules/_lambda-handler.js +40 -0
- package/lib/instrumentation/modules/apollo-server-core.js +49 -0
- package/lib/instrumentation/modules/aws-sdk/dynamodb.js +155 -0
- package/lib/instrumentation/modules/aws-sdk/s3.js +184 -0
- package/lib/instrumentation/modules/aws-sdk/sns.js +232 -0
- package/lib/instrumentation/modules/aws-sdk/sqs.js +361 -0
- package/lib/instrumentation/modules/aws-sdk.js +76 -0
- package/lib/instrumentation/modules/bluebird.js +93 -0
- package/lib/instrumentation/modules/cassandra-driver.js +280 -0
- package/lib/instrumentation/modules/elasticsearch.js +191 -0
- package/lib/instrumentation/modules/express-graphql.js +66 -0
- package/lib/instrumentation/modules/express-queue.js +28 -0
- package/lib/instrumentation/modules/express.js +162 -0
- package/lib/instrumentation/modules/fastify.js +172 -0
- package/lib/instrumentation/modules/finalhandler.js +41 -0
- package/lib/instrumentation/modules/generic-pool.js +85 -0
- package/lib/instrumentation/modules/graphql.js +256 -0
- package/lib/instrumentation/modules/handlebars.js +22 -0
- package/lib/instrumentation/modules/http.js +112 -0
- package/lib/instrumentation/modules/http2.js +320 -0
- package/lib/instrumentation/modules/https.js +68 -0
- package/lib/instrumentation/modules/ioredis.js +94 -0
- package/lib/instrumentation/modules/jade.js +18 -0
- package/lib/instrumentation/modules/kafkajs.js +476 -0
- package/lib/instrumentation/modules/knex.js +91 -0
- package/lib/instrumentation/modules/koa-router.js +74 -0
- package/lib/instrumentation/modules/koa.js +15 -0
- package/lib/instrumentation/modules/memcached.js +99 -0
- package/lib/instrumentation/modules/mimic-response.js +45 -0
- package/lib/instrumentation/modules/mongodb/lib/cmap/connection_pool.js +40 -0
- package/lib/instrumentation/modules/mongodb-core.js +206 -0
- package/lib/instrumentation/modules/mongodb.js +259 -0
- package/lib/instrumentation/modules/mysql.js +200 -0
- package/lib/instrumentation/modules/mysql2.js +140 -0
- package/lib/instrumentation/modules/pg.js +148 -0
- package/lib/instrumentation/modules/pug.js +18 -0
- package/lib/instrumentation/modules/redis.js +176 -0
- package/lib/instrumentation/modules/restify.js +52 -0
- package/lib/instrumentation/modules/tedious.js +159 -0
- package/lib/instrumentation/modules/undici.js +270 -0
- package/lib/instrumentation/modules/ws.js +59 -0
- package/lib/instrumentation/noop-transaction.js +81 -0
- package/lib/instrumentation/run-context/AbstractRunContextManager.js +215 -0
- package/lib/instrumentation/run-context/AsyncHooksRunContextManager.js +106 -0
- package/lib/instrumentation/run-context/AsyncLocalStorageRunContextManager.js +73 -0
- package/lib/instrumentation/run-context/BasicRunContextManager.js +82 -0
- package/lib/instrumentation/run-context/RunContext.js +151 -0
- package/lib/instrumentation/run-context/index.js +23 -0
- package/lib/instrumentation/shimmer.js +123 -0
- package/lib/instrumentation/span-compression.js +239 -0
- package/lib/instrumentation/span.js +621 -0
- package/lib/instrumentation/template-shared.js +43 -0
- package/lib/instrumentation/timer.js +84 -0
- package/lib/instrumentation/transaction.js +571 -0
- package/lib/lambda.js +992 -0
- package/lib/load-source-map.js +100 -0
- package/lib/logging.js +212 -0
- package/lib/metrics/index.js +92 -0
- package/lib/metrics/platforms/generic/index.js +40 -0
- package/lib/metrics/platforms/generic/process-cpu.js +22 -0
- package/lib/metrics/platforms/generic/process-top.js +157 -0
- package/lib/metrics/platforms/generic/stats.js +34 -0
- package/lib/metrics/platforms/generic/system-cpu.js +51 -0
- package/lib/metrics/platforms/linux/index.js +19 -0
- package/lib/metrics/platforms/linux/stats.js +213 -0
- package/lib/metrics/queue.js +90 -0
- package/lib/metrics/registry.js +52 -0
- package/lib/metrics/reporter.js +119 -0
- package/lib/metrics/runtime.js +77 -0
- package/lib/middleware/connect.js +16 -0
- package/lib/opentelemetry-bridge/OTelBridgeNonRecordingSpan.js +150 -0
- package/lib/opentelemetry-bridge/OTelBridgeRunContext.js +124 -0
- package/lib/opentelemetry-bridge/OTelContextManager.js +82 -0
- package/lib/opentelemetry-bridge/OTelSpan.js +344 -0
- package/lib/opentelemetry-bridge/OTelTracer.js +201 -0
- package/lib/opentelemetry-bridge/OTelTracerProvider.js +25 -0
- package/lib/opentelemetry-bridge/README.md +244 -0
- package/lib/opentelemetry-bridge/index.js +15 -0
- package/lib/opentelemetry-bridge/oblog.js +23 -0
- package/lib/opentelemetry-bridge/opentelemetry-core-mini/README.md +3 -0
- package/lib/opentelemetry-bridge/opentelemetry-core-mini/internal/validators.js +52 -0
- package/lib/opentelemetry-bridge/opentelemetry-core-mini/trace/TraceState.js +109 -0
- package/lib/opentelemetry-bridge/otelutils.js +99 -0
- package/lib/opentelemetry-bridge/setup.js +76 -0
- package/lib/opentelemetry-metrics/ElasticApmMetricExporter.js +285 -0
- package/lib/opentelemetry-metrics/index.js +50 -0
- package/lib/parsers.js +225 -0
- package/lib/propwrap.js +147 -0
- package/lib/stacktraces.js +537 -0
- package/lib/symbols.js +15 -0
- package/lib/tracecontext/index.js +118 -0
- package/lib/tracecontext/traceparent.js +185 -0
- package/lib/tracecontext/tracestate.js +388 -0
- package/lib/wildcard-matcher.js +52 -0
- package/loader.mjs +7 -0
- package/package.json +299 -0
- package/start.d.ts +8 -0
- package/start.js +29 -0
- package/types/aws-lambda.d.ts +98 -0
- package/types/connect.d.ts +23 -0
package/lib/lambda.js
ADDED
|
@@ -0,0 +1,992 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const constants = require('./constants');
|
|
10
|
+
const shimmer = require('./instrumentation/shimmer');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const querystring = require('querystring');
|
|
14
|
+
|
|
15
|
+
const { MAX_MESSAGES_PROCESSED_FOR_TRACE_CONTEXT } = require('./constants');
|
|
16
|
+
|
|
17
|
+
// https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-aws-lambda.md#deriving-cold-starts
|
|
18
|
+
let isFirstRun = true;
|
|
19
|
+
let gFaasId; // Set on first invocation.
|
|
20
|
+
|
|
21
|
+
// The trigger types for which we support special handling.
|
|
22
|
+
// https://docs.aws.amazon.com/lambda/latest/dg/lambda-services.html
|
|
23
|
+
const TRIGGER_GENERIC = 1;
|
|
24
|
+
const TRIGGER_API_GATEWAY = 2; // This includes Lambda URLs which use the same event format.
|
|
25
|
+
const TRIGGER_SNS = 3;
|
|
26
|
+
const TRIGGER_SQS = 4;
|
|
27
|
+
const TRIGGER_S3_SINGLE_EVENT = 5;
|
|
28
|
+
const TRIGGER_ELB = 6; // Elastic Load Balancer, aka Application Load Balancer
|
|
29
|
+
|
|
30
|
+
function triggerTypeFromEvent(event) {
|
|
31
|
+
if (event.requestContext) {
|
|
32
|
+
if (event.requestContext.elb) {
|
|
33
|
+
return TRIGGER_ELB;
|
|
34
|
+
} else if (event.requestContext.requestId) {
|
|
35
|
+
return TRIGGER_API_GATEWAY;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (event.Records && event.Records.length >= 1) {
|
|
39
|
+
const eventSource =
|
|
40
|
+
event.Records[0].eventSource || // S3 and SQS
|
|
41
|
+
event.Records[0].EventSource; // SNS
|
|
42
|
+
if (eventSource === 'aws:sns') {
|
|
43
|
+
return TRIGGER_SNS;
|
|
44
|
+
} else if (eventSource === 'aws:sqs') {
|
|
45
|
+
return TRIGGER_SQS;
|
|
46
|
+
} else if (eventSource === 'aws:s3' && event.Records.length === 1) {
|
|
47
|
+
return TRIGGER_S3_SINGLE_EVENT;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return TRIGGER_GENERIC;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Gather APM metadata for this Lambda executor per
|
|
54
|
+
// https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-aws-lambda.md#overwriting-metadata
|
|
55
|
+
function getMetadata(agent, cloudAccountId) {
|
|
56
|
+
return {
|
|
57
|
+
service: {
|
|
58
|
+
framework: {
|
|
59
|
+
// Passing this service.framework.name to Client#setExtraMetadata()
|
|
60
|
+
// ensures that it "wins" over a framework name from
|
|
61
|
+
// `agent.setFramework()`, because in the client `_extraMetadata`
|
|
62
|
+
// wins over `_conf.frameworkName`.
|
|
63
|
+
name: 'AWS Lambda',
|
|
64
|
+
},
|
|
65
|
+
runtime: {
|
|
66
|
+
name: process.env.AWS_EXECUTION_ENV,
|
|
67
|
+
},
|
|
68
|
+
node: {
|
|
69
|
+
configured_name: process.env.AWS_LAMBDA_LOG_STREAM_NAME,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
cloud: {
|
|
73
|
+
provider: 'aws',
|
|
74
|
+
region: process.env.AWS_REGION,
|
|
75
|
+
service: {
|
|
76
|
+
name: 'lambda',
|
|
77
|
+
},
|
|
78
|
+
account: {
|
|
79
|
+
id: cloudAccountId,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getFaasData(context, faasId, isColdStart, faasTriggerType, requestId) {
|
|
86
|
+
const faasData = {
|
|
87
|
+
id: faasId,
|
|
88
|
+
name: context.functionName,
|
|
89
|
+
version: context.functionVersion,
|
|
90
|
+
coldstart: isColdStart,
|
|
91
|
+
execution: context.awsRequestId,
|
|
92
|
+
trigger: {
|
|
93
|
+
type: faasTriggerType,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
if (requestId) {
|
|
97
|
+
faasData.trigger.request_id = requestId;
|
|
98
|
+
}
|
|
99
|
+
return faasData;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function setGenericData(trans, event, context, faasId, isColdStart) {
|
|
103
|
+
trans.type = 'request';
|
|
104
|
+
trans.setDefaultName(context.functionName);
|
|
105
|
+
|
|
106
|
+
trans.setFaas(getFaasData(context, faasId, isColdStart, 'other'));
|
|
107
|
+
|
|
108
|
+
const cloudContext = {
|
|
109
|
+
origin: {
|
|
110
|
+
provider: 'aws',
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
trans.setCloudContext(cloudContext);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Set transaction data for an API Gateway triggered invocation.
|
|
117
|
+
//
|
|
118
|
+
// Handle API Gateway payload format vers 1.0 (a.k.a "REST") and 2.0 ("HTTP").
|
|
119
|
+
// https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
|
|
120
|
+
function setApiGatewayData(agent, trans, event, context, faasId, isColdStart) {
|
|
121
|
+
const requestContext = event.requestContext;
|
|
122
|
+
|
|
123
|
+
let name;
|
|
124
|
+
let pseudoReq;
|
|
125
|
+
if (requestContext.http) {
|
|
126
|
+
// 2.0
|
|
127
|
+
if (agent._conf.usePathAsTransactionName) {
|
|
128
|
+
name = `${requestContext.http.method} ${requestContext.http.path}`;
|
|
129
|
+
} else {
|
|
130
|
+
// Get a routeKeyPath from the routeKey:
|
|
131
|
+
// GET /some/path -> /some/path
|
|
132
|
+
// ANY /some/path -> /some/path
|
|
133
|
+
// $default -> /$default
|
|
134
|
+
let routeKeyPath = requestContext.routeKey;
|
|
135
|
+
const spaceIdx = routeKeyPath.indexOf(' ');
|
|
136
|
+
if (spaceIdx === -1) {
|
|
137
|
+
routeKeyPath = '/' + routeKeyPath;
|
|
138
|
+
} else {
|
|
139
|
+
routeKeyPath = routeKeyPath.slice(spaceIdx + 1);
|
|
140
|
+
}
|
|
141
|
+
name = `${requestContext.http.method} /${requestContext.stage}${routeKeyPath}`;
|
|
142
|
+
}
|
|
143
|
+
pseudoReq = {
|
|
144
|
+
httpVersion: requestContext.http.protocol
|
|
145
|
+
? requestContext.http.protocol.split('/')[1] // 'HTTP/1.1' -> '1.1'
|
|
146
|
+
: undefined,
|
|
147
|
+
method: requestContext.http.method,
|
|
148
|
+
url:
|
|
149
|
+
event.rawPath +
|
|
150
|
+
(event.rawQueryString ? '?' + event.rawQueryString : ''),
|
|
151
|
+
headers: event.normedHeaders || {},
|
|
152
|
+
socket: { remoteAddress: requestContext.http.sourceIp },
|
|
153
|
+
body: event.body,
|
|
154
|
+
};
|
|
155
|
+
} else {
|
|
156
|
+
// payload version format 1.0
|
|
157
|
+
if (agent._conf.usePathAsTransactionName) {
|
|
158
|
+
name = `${requestContext.httpMethod} ${requestContext.path}`;
|
|
159
|
+
} else {
|
|
160
|
+
name = `${requestContext.httpMethod} /${requestContext.stage}${requestContext.resourcePath}`;
|
|
161
|
+
}
|
|
162
|
+
pseudoReq = {
|
|
163
|
+
httpVersion: requestContext.protocol
|
|
164
|
+
? requestContext.protocol.split('/')[1] // 'HTTP/1.1' -> '1.1'
|
|
165
|
+
: undefined,
|
|
166
|
+
method: requestContext.httpMethod,
|
|
167
|
+
url:
|
|
168
|
+
requestContext.path +
|
|
169
|
+
(event.queryStringParameters
|
|
170
|
+
? '?' + querystring.encode(event.queryStringParameters)
|
|
171
|
+
: ''),
|
|
172
|
+
headers: event.normedHeaders || {},
|
|
173
|
+
socket: {
|
|
174
|
+
remoteAddress:
|
|
175
|
+
requestContext.identity && requestContext.identity.sourceIp,
|
|
176
|
+
},
|
|
177
|
+
// Limitation: Note that `getContextFromRequest` does *not* use this body,
|
|
178
|
+
// because API Gateway payload format 1.0 does not include the
|
|
179
|
+
// Content-Length header from the original request.
|
|
180
|
+
body: event.body,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
trans.type = 'request';
|
|
184
|
+
trans.setDefaultName(name);
|
|
185
|
+
trans.req = pseudoReq; // Used by parsers.getContextFromRequest() for adding context to transaction and errors.
|
|
186
|
+
|
|
187
|
+
trans.setFaas(
|
|
188
|
+
getFaasData(context, faasId, isColdStart, 'http', requestContext.requestId),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const serviceContext = {
|
|
192
|
+
origin: {
|
|
193
|
+
name: requestContext.domainName,
|
|
194
|
+
id: requestContext.apiId,
|
|
195
|
+
version: event.version || '1.0',
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
trans.setServiceContext(serviceContext);
|
|
199
|
+
|
|
200
|
+
const originSvcName =
|
|
201
|
+
// `<url-id>.lambda-url.<region>.on.aws` indicates this is a Lambda URL.
|
|
202
|
+
requestContext.domainName &&
|
|
203
|
+
requestContext.domainPrefix &&
|
|
204
|
+
requestContext.domainName.startsWith(
|
|
205
|
+
requestContext.domainPrefix + '.lambda-url.',
|
|
206
|
+
)
|
|
207
|
+
? 'lambda url'
|
|
208
|
+
: 'api gateway';
|
|
209
|
+
const cloudContext = {
|
|
210
|
+
origin: {
|
|
211
|
+
provider: 'aws',
|
|
212
|
+
service: {
|
|
213
|
+
name: originSvcName,
|
|
214
|
+
},
|
|
215
|
+
account: {
|
|
216
|
+
id: requestContext.accountId,
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
trans.setCloudContext(cloudContext);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function setTransDataFromApiGatewayResult(err, result, trans, event) {
|
|
224
|
+
if (err) {
|
|
225
|
+
trans.result = 'HTTP 5xx';
|
|
226
|
+
trans._setOutcomeFromHttpStatusCode(500);
|
|
227
|
+
} else if (result && result.statusCode) {
|
|
228
|
+
trans.result = 'HTTP ' + result.statusCode.toString()[0] + 'xx';
|
|
229
|
+
trans._setOutcomeFromHttpStatusCode(result.statusCode);
|
|
230
|
+
} else {
|
|
231
|
+
trans.result = constants.RESULT_SUCCESS;
|
|
232
|
+
trans._setOutcomeFromHttpStatusCode(200);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// This doc defines the format of API Gateway-triggered responses, from which
|
|
236
|
+
// we can infer `transaction.context.response` values.
|
|
237
|
+
// https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.response
|
|
238
|
+
// https://docs.aws.amazon.com/lambda/latest/dg/urls-invocation.html#urls-payloads
|
|
239
|
+
if (err) {
|
|
240
|
+
trans.res = {
|
|
241
|
+
statusCode: 500,
|
|
242
|
+
};
|
|
243
|
+
} else if (event.requestContext.http) {
|
|
244
|
+
// payload format version 2.0
|
|
245
|
+
if (result && result.statusCode) {
|
|
246
|
+
trans.res = {
|
|
247
|
+
statusCode: result.statusCode,
|
|
248
|
+
headers: result.headers,
|
|
249
|
+
};
|
|
250
|
+
} else {
|
|
251
|
+
trans.res = {
|
|
252
|
+
statusCode: 200,
|
|
253
|
+
headers: { 'content-type': 'application/json' },
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
// payload format version 1.0
|
|
258
|
+
if (result && result.statusCode) {
|
|
259
|
+
trans.res = {
|
|
260
|
+
statusCode: result.statusCode,
|
|
261
|
+
headers: result.headers,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// https://docs.aws.amazon.com/lambda/latest/dg/services-alb.html
|
|
268
|
+
function setElbData(agent, trans, event, context, faasId, isColdStart) {
|
|
269
|
+
trans.type = 'request';
|
|
270
|
+
let name;
|
|
271
|
+
if (agent._conf.usePathAsTransactionName) {
|
|
272
|
+
name = `${event.httpMethod} ${event.path}`;
|
|
273
|
+
} else {
|
|
274
|
+
name = `${event.httpMethod} unknown route`;
|
|
275
|
+
}
|
|
276
|
+
trans.setDefaultName(name);
|
|
277
|
+
trans.req = {
|
|
278
|
+
// Used by parsers.getContextFromRequest() for adding context to transaction and errors.
|
|
279
|
+
method: event.httpMethod,
|
|
280
|
+
url:
|
|
281
|
+
event.path +
|
|
282
|
+
(event.queryStringParameters &&
|
|
283
|
+
Object.keys(event.queryStringParameters) > 0
|
|
284
|
+
? '?' + querystring.encode(event.queryStringParameters)
|
|
285
|
+
: ''),
|
|
286
|
+
headers: event.normedHeaders || {},
|
|
287
|
+
body: event.body,
|
|
288
|
+
bodyIsBase64Encoded: event.isBase64Encoded,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
trans.setFaas(getFaasData(context, faasId, isColdStart, 'http'));
|
|
292
|
+
|
|
293
|
+
const targetGroupArn = event.requestContext.elb.targetGroupArn;
|
|
294
|
+
const arnParts = targetGroupArn.split(':');
|
|
295
|
+
trans.setServiceContext({
|
|
296
|
+
origin: {
|
|
297
|
+
name: arnParts[5].split('/')[1],
|
|
298
|
+
id: targetGroupArn,
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
trans.setCloudContext({
|
|
303
|
+
origin: {
|
|
304
|
+
provider: 'aws',
|
|
305
|
+
region: arnParts[3],
|
|
306
|
+
service: {
|
|
307
|
+
name: 'elb',
|
|
308
|
+
},
|
|
309
|
+
account: {
|
|
310
|
+
id: arnParts[4],
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function setTransDataFromElbResult(err, result, trans) {
|
|
317
|
+
// ELB defaults to 502 Bad Gateway if there is an error or `result.statusCode`
|
|
318
|
+
// is missing or not an integer.
|
|
319
|
+
const validStatusCode =
|
|
320
|
+
result &&
|
|
321
|
+
result.statusCode &&
|
|
322
|
+
typeof result.statusCode === 'number' &&
|
|
323
|
+
Number.isInteger(result.statusCode)
|
|
324
|
+
? result.statusCode
|
|
325
|
+
: null;
|
|
326
|
+
|
|
327
|
+
if (err) {
|
|
328
|
+
trans.result = 'HTTP 5xx';
|
|
329
|
+
} else if (validStatusCode) {
|
|
330
|
+
trans.result = 'HTTP ' + validStatusCode.toString()[0] + 'xx';
|
|
331
|
+
} else {
|
|
332
|
+
trans.result = 'HTTP 5xx';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Set an appropriate pseudo `trans.res`, used by `parsers.getContextFromResponse()`.
|
|
336
|
+
if (err) {
|
|
337
|
+
trans.res = {
|
|
338
|
+
statusCode: 502,
|
|
339
|
+
};
|
|
340
|
+
} else {
|
|
341
|
+
trans.res = {
|
|
342
|
+
statusCode: validStatusCode || 502,
|
|
343
|
+
headers: result.headers,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
trans._setOutcomeFromHttpStatusCode(validStatusCode || 502);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function setSqsData(agent, trans, event, context, faasId, isColdStart) {
|
|
351
|
+
const record = event && event.Records && event.Records[0];
|
|
352
|
+
const eventSourceARN = record.eventSourceARN ? record.eventSourceARN : '';
|
|
353
|
+
|
|
354
|
+
trans.setFaas(getFaasData(context, faasId, isColdStart, 'pubsub'));
|
|
355
|
+
|
|
356
|
+
const arnParts = eventSourceARN.split(':');
|
|
357
|
+
const queueName = arnParts[5];
|
|
358
|
+
const accountId = arnParts[4];
|
|
359
|
+
|
|
360
|
+
trans.setDefaultName(`RECEIVE ${queueName}`);
|
|
361
|
+
trans.type = 'messaging';
|
|
362
|
+
|
|
363
|
+
const serviceContext = {
|
|
364
|
+
origin: {
|
|
365
|
+
name: queueName,
|
|
366
|
+
id: eventSourceARN,
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
trans.setServiceContext(serviceContext);
|
|
370
|
+
|
|
371
|
+
const cloudContext = {
|
|
372
|
+
origin: {
|
|
373
|
+
provider: 'aws',
|
|
374
|
+
region: record.awsRegion,
|
|
375
|
+
service: {
|
|
376
|
+
name: 'sqs',
|
|
377
|
+
},
|
|
378
|
+
account: {
|
|
379
|
+
id: accountId,
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
trans.setCloudContext(cloudContext);
|
|
384
|
+
|
|
385
|
+
const links = spanLinksFromSqsRecords(event.Records);
|
|
386
|
+
trans.addLinks(links);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function setSnsData(agent, trans, event, context, faasId, isColdStart) {
|
|
390
|
+
const record = event && event.Records && event.Records[0];
|
|
391
|
+
const sns = record && record.Sns;
|
|
392
|
+
|
|
393
|
+
trans.setFaas(getFaasData(context, faasId, isColdStart, 'pubsub'));
|
|
394
|
+
|
|
395
|
+
const topicArn = (sns && sns.TopicArn) || '';
|
|
396
|
+
const arnParts = topicArn.split(':');
|
|
397
|
+
const topicName = arnParts[5];
|
|
398
|
+
const accountId = arnParts[4];
|
|
399
|
+
const region = arnParts[3];
|
|
400
|
+
|
|
401
|
+
trans.setDefaultName(`RECEIVE ${topicName}`);
|
|
402
|
+
trans.type = 'messaging';
|
|
403
|
+
|
|
404
|
+
const serviceContext = {
|
|
405
|
+
origin: {
|
|
406
|
+
name: topicName,
|
|
407
|
+
id: topicArn,
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
trans.setServiceContext(serviceContext);
|
|
411
|
+
|
|
412
|
+
const cloudContext = {
|
|
413
|
+
origin: {
|
|
414
|
+
provider: 'aws',
|
|
415
|
+
region,
|
|
416
|
+
service: {
|
|
417
|
+
name: 'sns',
|
|
418
|
+
},
|
|
419
|
+
account: {
|
|
420
|
+
id: accountId,
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
};
|
|
424
|
+
trans.setCloudContext(cloudContext);
|
|
425
|
+
|
|
426
|
+
const links = spanLinksFromSnsRecords(event.Records);
|
|
427
|
+
trans.addLinks(links);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function setS3SingleData(trans, event, context, faasId, isColdStart) {
|
|
431
|
+
const record = event.Records[0];
|
|
432
|
+
|
|
433
|
+
trans.setFaas(
|
|
434
|
+
getFaasData(
|
|
435
|
+
context,
|
|
436
|
+
faasId,
|
|
437
|
+
isColdStart,
|
|
438
|
+
'datasource',
|
|
439
|
+
record.responseElements && record.responseElements['x-amz-request-id'],
|
|
440
|
+
),
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
trans.setDefaultName(
|
|
444
|
+
`${record && record.eventName} ${
|
|
445
|
+
record && record.s3 && record.s3.bucket && record.s3.bucket.name
|
|
446
|
+
}`,
|
|
447
|
+
);
|
|
448
|
+
trans.type = 'request';
|
|
449
|
+
|
|
450
|
+
const serviceContext = {
|
|
451
|
+
origin: {
|
|
452
|
+
name: record && record.s3 && record.s3.bucket && record.s3.bucket.name,
|
|
453
|
+
id: record && record.s3 && record.s3.bucket && record.s3.bucket.arn,
|
|
454
|
+
version: record.eventVersion,
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
trans.setServiceContext(serviceContext);
|
|
458
|
+
|
|
459
|
+
const cloudContext = {
|
|
460
|
+
origin: {
|
|
461
|
+
provider: 'aws',
|
|
462
|
+
service: {
|
|
463
|
+
name: 's3',
|
|
464
|
+
},
|
|
465
|
+
region: record.awsRegion,
|
|
466
|
+
},
|
|
467
|
+
};
|
|
468
|
+
trans.setCloudContext(cloudContext);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function elasticApmAwsLambda(agent) {
|
|
472
|
+
const log = agent.logger;
|
|
473
|
+
const ins = agent._instrumentation;
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Register this transaction with the Lambda extension, if possible. This
|
|
477
|
+
* function is `await`able so that the transaction is registered before
|
|
478
|
+
* executing the user's Lambda handler.
|
|
479
|
+
*
|
|
480
|
+
* Perf note: Using a Lambda sized to have 1 vCPU (1769MB memory), some
|
|
481
|
+
* rudimentary perf tests showed an average of 0.8ms for this call to the ext.
|
|
482
|
+
*/
|
|
483
|
+
function registerTransaction(trans, awsRequestId) {
|
|
484
|
+
if (!agent._apmClient) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (!agent._apmClient.lambdaShouldRegisterTransactions()) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Reproduce the filtering logic from `Instrumentation.prototype.addEndedTransaction`.
|
|
492
|
+
if (agent._conf.contextPropagationOnly) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
if (
|
|
496
|
+
!trans.sampled &&
|
|
497
|
+
!agent._apmClient.supportsKeepingUnsampledTransaction()
|
|
498
|
+
) {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
var payload = trans.toJSON();
|
|
503
|
+
// If this partial transaction is used, the Lambda Extension will fill in:
|
|
504
|
+
// - `transaction.result` will be set to one of:
|
|
505
|
+
// - The "status" field from the Logs API platform `runtimeDone` message.
|
|
506
|
+
// https://docs.aws.amazon.com/lambda/latest/dg/runtimes-logs-api.html#runtimes-logs-api-ref-done
|
|
507
|
+
// Values: "success", "failure"
|
|
508
|
+
// - The "shutdownReason" field from the `Shutdown` event from the Extensions API.
|
|
509
|
+
// https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html#runtimes-lifecycle-shutdown
|
|
510
|
+
// Values: "spindown", "timeout", "failure" (I think these are the values.)
|
|
511
|
+
// - `transaction.outcome` will be set to "failure" if the status above is
|
|
512
|
+
// not "success". Therefore we want a default outcome value.
|
|
513
|
+
// - `transaction.duration` will be estimated
|
|
514
|
+
delete payload.result;
|
|
515
|
+
delete payload.duration;
|
|
516
|
+
|
|
517
|
+
payload = agent._transactionFilters.process(payload);
|
|
518
|
+
if (!payload) {
|
|
519
|
+
log.trace(
|
|
520
|
+
{ traceId: trans.traceId, transactionId: trans.id },
|
|
521
|
+
'transaction ignored by filter',
|
|
522
|
+
);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return agent._apmClient.lambdaRegisterTransaction(payload, awsRequestId);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function endAndFlushTransaction(
|
|
530
|
+
err,
|
|
531
|
+
result,
|
|
532
|
+
trans,
|
|
533
|
+
event,
|
|
534
|
+
context,
|
|
535
|
+
triggerType,
|
|
536
|
+
cb,
|
|
537
|
+
) {
|
|
538
|
+
log.trace(
|
|
539
|
+
{ awsRequestId: context && context.awsRequestId },
|
|
540
|
+
'lambda: fn end',
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
switch (triggerType) {
|
|
544
|
+
case TRIGGER_API_GATEWAY:
|
|
545
|
+
setTransDataFromApiGatewayResult(err, result, trans, event);
|
|
546
|
+
break;
|
|
547
|
+
case TRIGGER_ELB:
|
|
548
|
+
setTransDataFromElbResult(err, result, trans);
|
|
549
|
+
break;
|
|
550
|
+
default:
|
|
551
|
+
if (err) {
|
|
552
|
+
trans.result = constants.RESULT_FAILURE;
|
|
553
|
+
trans.setOutcome(constants.OUTCOME_FAILURE);
|
|
554
|
+
} else {
|
|
555
|
+
trans.result = constants.RESULT_SUCCESS;
|
|
556
|
+
trans.setOutcome(constants.OUTCOME_SUCCESS);
|
|
557
|
+
}
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (err) {
|
|
562
|
+
// Capture the error before trans.end() so it associates with the
|
|
563
|
+
// current trans. `skipOutcome` to avoid setting outcome on a possible
|
|
564
|
+
// currentSpan, because this error applies to the transaction, not any
|
|
565
|
+
// sub-span.
|
|
566
|
+
agent.captureError(err, { skipOutcome: true });
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
trans.end();
|
|
570
|
+
|
|
571
|
+
agent._flush({ lambdaEnd: true, inflightTimeout: 100 }, (flushErr) => {
|
|
572
|
+
if (flushErr) {
|
|
573
|
+
log.error(
|
|
574
|
+
{ err: flushErr, awsRequestId: context && context.awsRequestId },
|
|
575
|
+
'lambda: flush error',
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
log.trace(
|
|
579
|
+
{ awsRequestId: context && context.awsRequestId },
|
|
580
|
+
'lambda: wrapper end',
|
|
581
|
+
);
|
|
582
|
+
cb();
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function wrapContext(runContext, trans, event, context, triggerType) {
|
|
587
|
+
shimmer.wrap(context, 'succeed', (origSucceed) => {
|
|
588
|
+
return ins.bindFunctionToRunContext(
|
|
589
|
+
runContext,
|
|
590
|
+
function wrappedSucceed(result) {
|
|
591
|
+
endAndFlushTransaction(
|
|
592
|
+
null,
|
|
593
|
+
result,
|
|
594
|
+
trans,
|
|
595
|
+
event,
|
|
596
|
+
context,
|
|
597
|
+
triggerType,
|
|
598
|
+
function () {
|
|
599
|
+
origSucceed(result);
|
|
600
|
+
},
|
|
601
|
+
);
|
|
602
|
+
},
|
|
603
|
+
);
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
shimmer.wrap(context, 'fail', (origFail) => {
|
|
607
|
+
return ins.bindFunctionToRunContext(
|
|
608
|
+
runContext,
|
|
609
|
+
function wrappedFail(err) {
|
|
610
|
+
endAndFlushTransaction(
|
|
611
|
+
err,
|
|
612
|
+
null,
|
|
613
|
+
trans,
|
|
614
|
+
event,
|
|
615
|
+
context,
|
|
616
|
+
triggerType,
|
|
617
|
+
function () {
|
|
618
|
+
origFail(err);
|
|
619
|
+
},
|
|
620
|
+
);
|
|
621
|
+
},
|
|
622
|
+
);
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
shimmer.wrap(context, 'done', (origDone) => {
|
|
626
|
+
return wrapLambdaCallback(
|
|
627
|
+
runContext,
|
|
628
|
+
trans,
|
|
629
|
+
event,
|
|
630
|
+
context,
|
|
631
|
+
triggerType,
|
|
632
|
+
origDone,
|
|
633
|
+
);
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function wrapLambdaCallback(
|
|
638
|
+
runContext,
|
|
639
|
+
trans,
|
|
640
|
+
event,
|
|
641
|
+
context,
|
|
642
|
+
triggerType,
|
|
643
|
+
callback,
|
|
644
|
+
) {
|
|
645
|
+
return ins.bindFunctionToRunContext(
|
|
646
|
+
runContext,
|
|
647
|
+
function wrappedLambdaCallback(err, result) {
|
|
648
|
+
endAndFlushTransaction(
|
|
649
|
+
err,
|
|
650
|
+
result,
|
|
651
|
+
trans,
|
|
652
|
+
event,
|
|
653
|
+
context,
|
|
654
|
+
triggerType,
|
|
655
|
+
() => {
|
|
656
|
+
callback(err, result);
|
|
657
|
+
},
|
|
658
|
+
);
|
|
659
|
+
},
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return function wrapLambdaHandler(type, fn) {
|
|
664
|
+
if (typeof type === 'function') {
|
|
665
|
+
fn = type;
|
|
666
|
+
type = 'request';
|
|
667
|
+
}
|
|
668
|
+
if (!agent._conf.active) {
|
|
669
|
+
// Manual usage of `apm.lambda(...)` should be a no-op when not active.
|
|
670
|
+
return fn;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return async function wrappedLambdaHandler(event, context, callback) {
|
|
674
|
+
if (!(event && context && typeof callback === 'function')) {
|
|
675
|
+
// Skip instrumentation if arguments are unexpected.
|
|
676
|
+
// https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html
|
|
677
|
+
return fn.call(this, ...arguments);
|
|
678
|
+
}
|
|
679
|
+
log.trace({ awsRequestId: context.awsRequestId }, 'lambda: fn start');
|
|
680
|
+
|
|
681
|
+
const isColdStart = isFirstRun;
|
|
682
|
+
if (isFirstRun) {
|
|
683
|
+
isFirstRun = false;
|
|
684
|
+
|
|
685
|
+
// E.g. 'arn:aws:lambda:us-west-2:123456789012:function:my-function:someAlias'
|
|
686
|
+
const arnParts = context.invokedFunctionArn.split(':');
|
|
687
|
+
gFaasId = arnParts.slice(0, 7).join(':');
|
|
688
|
+
const cloudAccountId = arnParts[4];
|
|
689
|
+
|
|
690
|
+
if (agent._apmClient) {
|
|
691
|
+
log.trace(
|
|
692
|
+
{ awsRequestId: context.awsRequestId },
|
|
693
|
+
'lambda: setExtraMetadata',
|
|
694
|
+
);
|
|
695
|
+
agent._apmClient.setExtraMetadata(getMetadata(agent, cloudAccountId));
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (agent._apmClient) {
|
|
700
|
+
agent._apmClient.lambdaStart();
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const triggerType = triggerTypeFromEvent(event);
|
|
704
|
+
|
|
705
|
+
// Look for trace-context info in headers or messageAttributes.
|
|
706
|
+
let traceparent;
|
|
707
|
+
let tracestate;
|
|
708
|
+
if (
|
|
709
|
+
(triggerType === TRIGGER_API_GATEWAY || triggerType === TRIGGER_ELB) &&
|
|
710
|
+
event.headers
|
|
711
|
+
) {
|
|
712
|
+
// https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
|
|
713
|
+
// says "Header names are lowercased." However, that isn't the case for
|
|
714
|
+
// payload format version 1.0. We need lowercased headers for processing.
|
|
715
|
+
if (!event.requestContext.http) {
|
|
716
|
+
// 1.0
|
|
717
|
+
event.normedHeaders = lowerCaseObjectKeys(event.headers);
|
|
718
|
+
} else {
|
|
719
|
+
event.normedHeaders = event.headers;
|
|
720
|
+
}
|
|
721
|
+
traceparent =
|
|
722
|
+
event.normedHeaders.traceparent ||
|
|
723
|
+
event.normedHeaders['elastic-apm-traceparent'];
|
|
724
|
+
tracestate = event.normedHeaders.tracestate;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Start the transaction and set some possibly trigger-specific data.
|
|
728
|
+
const trans = agent.startTransaction(context.functionName, type, {
|
|
729
|
+
childOf: traceparent,
|
|
730
|
+
tracestate,
|
|
731
|
+
});
|
|
732
|
+
switch (triggerType) {
|
|
733
|
+
case TRIGGER_API_GATEWAY:
|
|
734
|
+
setApiGatewayData(agent, trans, event, context, gFaasId, isColdStart);
|
|
735
|
+
break;
|
|
736
|
+
case TRIGGER_ELB:
|
|
737
|
+
setElbData(agent, trans, event, context, gFaasId, isColdStart);
|
|
738
|
+
break;
|
|
739
|
+
case TRIGGER_SQS:
|
|
740
|
+
setSqsData(agent, trans, event, context, gFaasId, isColdStart);
|
|
741
|
+
break;
|
|
742
|
+
case TRIGGER_SNS:
|
|
743
|
+
setSnsData(agent, trans, event, context, gFaasId, isColdStart);
|
|
744
|
+
break;
|
|
745
|
+
case TRIGGER_S3_SINGLE_EVENT:
|
|
746
|
+
setS3SingleData(trans, event, context, gFaasId, isColdStart);
|
|
747
|
+
break;
|
|
748
|
+
case TRIGGER_GENERIC:
|
|
749
|
+
setGenericData(trans, event, context, gFaasId, isColdStart);
|
|
750
|
+
break;
|
|
751
|
+
default:
|
|
752
|
+
log.warn(
|
|
753
|
+
`not setting transaction data for triggerType=${triggerType}`,
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Wrap context and callback to finish and send transaction.
|
|
758
|
+
// Note: Wrapping context needs to happen *before any `await` calls* in
|
|
759
|
+
// this function, otherwise the Lambda Node.js Runtime will call the
|
|
760
|
+
// *unwrapped* `context.{succeed,fail,done}()` methods.
|
|
761
|
+
const transRunContext = ins.currRunContext();
|
|
762
|
+
wrapContext(transRunContext, trans, event, context, triggerType);
|
|
763
|
+
const wrappedCallback = wrapLambdaCallback(
|
|
764
|
+
transRunContext,
|
|
765
|
+
trans,
|
|
766
|
+
event,
|
|
767
|
+
context,
|
|
768
|
+
triggerType,
|
|
769
|
+
callback,
|
|
770
|
+
);
|
|
771
|
+
|
|
772
|
+
await registerTransaction(trans, context.awsRequestId);
|
|
773
|
+
|
|
774
|
+
try {
|
|
775
|
+
const retval = ins.withRunContext(
|
|
776
|
+
transRunContext,
|
|
777
|
+
fn,
|
|
778
|
+
this,
|
|
779
|
+
event,
|
|
780
|
+
context,
|
|
781
|
+
wrappedCallback,
|
|
782
|
+
);
|
|
783
|
+
if (retval instanceof Promise) {
|
|
784
|
+
return retval;
|
|
785
|
+
} else {
|
|
786
|
+
// In this case, our wrapping of the user's handler has changed it
|
|
787
|
+
// from a sync function to an async function. We need to ensure the
|
|
788
|
+
// Lambda Runtime does not end the invocation based on this returned
|
|
789
|
+
// promise -- the invocation should end when the `callback` is called
|
|
790
|
+
// -- so we return a promise that never resolves.
|
|
791
|
+
return new Promise((resolve, reject) => {
|
|
792
|
+
/* never resolves */
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
} catch (handlerErr) {
|
|
796
|
+
wrappedCallback(handlerErr);
|
|
797
|
+
// Return a promise that never resolves, so that the Lambda Runtime's
|
|
798
|
+
// doesn't attempt its "success" handling.
|
|
799
|
+
return new Promise((resolve, reject) => {
|
|
800
|
+
/* never resolves */
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function isLambdaExecutionEnvironment() {
|
|
808
|
+
return !!process.env.AWS_LAMBDA_FUNCTION_NAME;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Returns the full file path to the user's handler handler module
|
|
812
|
+
//
|
|
813
|
+
// The Lambda Runtime allows a user's handler module to have either a .js or
|
|
814
|
+
// .cjs extension. The getFilePath looks for a .js file first, and if not found
|
|
815
|
+
// presumes a csj file exists. If neither file exists this means the user either
|
|
816
|
+
// as a misconfigured handler (they'd never reach this code) or is using a
|
|
817
|
+
// .mjs file extension (which indicates an ECMAScript/import module, which the
|
|
818
|
+
// agent does not support.
|
|
819
|
+
//
|
|
820
|
+
// TODO: support "extensionless"? per https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/blob/v3.2.1/src/UserFunction.js#L149 Is this for a dir/index.js?
|
|
821
|
+
// TODO: support ESM and .mjs
|
|
822
|
+
//
|
|
823
|
+
// @param {string} taskRoot
|
|
824
|
+
// @param {string} moduleRoot - The subdir under `taskRoot` holding the module.
|
|
825
|
+
// @param {string} module - The module name.
|
|
826
|
+
// @return {string | null}
|
|
827
|
+
function getFilePath(taskRoot, moduleRoot, module) {
|
|
828
|
+
const lambdaStylePath = path.resolve(taskRoot, moduleRoot, module);
|
|
829
|
+
if (fs.existsSync(lambdaStylePath + '.js')) {
|
|
830
|
+
return lambdaStylePath + '.js';
|
|
831
|
+
} else if (fs.existsSync(lambdaStylePath + '.cjs')) {
|
|
832
|
+
return lambdaStylePath + '.cjs';
|
|
833
|
+
} else {
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Gather module and export info for the Lambda "handler" string.
|
|
840
|
+
*
|
|
841
|
+
* Compare to the Node.js Lambda runtime's equivalent processing here:
|
|
842
|
+
* https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/blob/v3.2.1/src/UserFunction.js#L288
|
|
843
|
+
*
|
|
844
|
+
* @param {object} env - The process environment.
|
|
845
|
+
* @param {any} [logger] - Optional logger for trace/warn log output.
|
|
846
|
+
*/
|
|
847
|
+
function getLambdaHandlerInfo(env, logger) {
|
|
848
|
+
if (
|
|
849
|
+
!isLambdaExecutionEnvironment() ||
|
|
850
|
+
!env._HANDLER ||
|
|
851
|
+
!env.LAMBDA_TASK_ROOT
|
|
852
|
+
) {
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Dev Note: This intentionally uses some of the same var names at
|
|
857
|
+
// https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/blob/v3.2.1/src/UserFunction.js#L288
|
|
858
|
+
const fullHandlerString = env._HANDLER;
|
|
859
|
+
const moduleAndHandler = path.basename(fullHandlerString);
|
|
860
|
+
const moduleRoot = fullHandlerString.substring(
|
|
861
|
+
0,
|
|
862
|
+
fullHandlerString.indexOf(moduleAndHandler),
|
|
863
|
+
);
|
|
864
|
+
const FUNCTION_EXPR = /^([^.]*)\.(.*)$/;
|
|
865
|
+
const match = moduleAndHandler.match(FUNCTION_EXPR);
|
|
866
|
+
if (!match || match.length !== 3) {
|
|
867
|
+
if (logger) {
|
|
868
|
+
logger.warn(
|
|
869
|
+
{ fullHandlerString, moduleAndHandler },
|
|
870
|
+
'Lambda handler string did not match FUNCTION_EXPR',
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
const module = match[1];
|
|
876
|
+
const handlerPath = match[2];
|
|
877
|
+
|
|
878
|
+
const moduleAbsPath = getFilePath(env.LAMBDA_TASK_ROOT, moduleRoot, module);
|
|
879
|
+
if (!moduleAbsPath) {
|
|
880
|
+
if (logger) {
|
|
881
|
+
logger.warn(
|
|
882
|
+
{ fullHandlerString, moduleRoot, module },
|
|
883
|
+
'could not find Lambda handler module file (ESM not yet supported)',
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
return null;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const lambdaHandlerInfo = {
|
|
890
|
+
filePath: moduleAbsPath,
|
|
891
|
+
modName: module,
|
|
892
|
+
propPath: handlerPath,
|
|
893
|
+
};
|
|
894
|
+
if (logger) {
|
|
895
|
+
logger.trace({ fullHandlerString, lambdaHandlerInfo }, 'lambdaHandlerInfo');
|
|
896
|
+
}
|
|
897
|
+
return lambdaHandlerInfo;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function lowerCaseObjectKeys(obj) {
|
|
901
|
+
const lowerCased = {};
|
|
902
|
+
for (const key of Object.keys(obj)) {
|
|
903
|
+
lowerCased[key.toLowerCase()] = obj[key];
|
|
904
|
+
}
|
|
905
|
+
return lowerCased;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// Extract span links from up to 1000 messages in this batch.
|
|
909
|
+
// https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-messaging.md#receiving-trace-context
|
|
910
|
+
//
|
|
911
|
+
// A span link is created from a `traceparent` message attribute in a message.
|
|
912
|
+
// `msg.messageAttributes` is of the form:
|
|
913
|
+
// { <attribute-name>: { DataType: <attr-type>, StringValue: <attr-value>, ... } }
|
|
914
|
+
// For example:
|
|
915
|
+
// { traceparent: { DataType: 'String', StringValue: 'test-traceparent' } }
|
|
916
|
+
function spanLinksFromSqsRecords(records) {
|
|
917
|
+
const links = [];
|
|
918
|
+
const limit = Math.min(
|
|
919
|
+
records.length,
|
|
920
|
+
MAX_MESSAGES_PROCESSED_FOR_TRACE_CONTEXT,
|
|
921
|
+
);
|
|
922
|
+
for (let i = 0; i < limit; i++) {
|
|
923
|
+
const attrs = records[i].messageAttributes;
|
|
924
|
+
if (!attrs) {
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
let traceparent;
|
|
929
|
+
const attrNames = Object.keys(attrs);
|
|
930
|
+
for (let j = 0; j < attrNames.length; j++) {
|
|
931
|
+
const attrVal = attrs[attrNames[j]];
|
|
932
|
+
if (attrVal.dataType !== 'String') {
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
const attrNameLc = attrNames[j].toLowerCase();
|
|
936
|
+
if (attrNameLc === 'traceparent') {
|
|
937
|
+
traceparent = attrVal.stringValue;
|
|
938
|
+
break;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (traceparent) {
|
|
942
|
+
links.push({ context: traceparent });
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
return links;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Extract span links from up to 1000 messages in this batch.
|
|
949
|
+
// https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-messaging.md#receiving-trace-context
|
|
950
|
+
//
|
|
951
|
+
// A span link is created from a `traceparent` message attribute in a message.
|
|
952
|
+
// `record.Sns.MessageAttributes` is of the form:
|
|
953
|
+
// { <attribute-name>: { Type: <attr-type>, Value: <attr-value> } }
|
|
954
|
+
// For example:
|
|
955
|
+
// { traceparent: { Type: 'String', Value: 'test-traceparent' } }
|
|
956
|
+
function spanLinksFromSnsRecords(records) {
|
|
957
|
+
const links = [];
|
|
958
|
+
const limit = Math.min(
|
|
959
|
+
records.length,
|
|
960
|
+
MAX_MESSAGES_PROCESSED_FOR_TRACE_CONTEXT,
|
|
961
|
+
);
|
|
962
|
+
for (let i = 0; i < limit; i++) {
|
|
963
|
+
const attrs = records[i].Sns && records[i].Sns.MessageAttributes;
|
|
964
|
+
if (!attrs) {
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
let traceparent;
|
|
969
|
+
const attrNames = Object.keys(attrs);
|
|
970
|
+
for (let j = 0; j < attrNames.length; j++) {
|
|
971
|
+
const attrVal = attrs[attrNames[j]];
|
|
972
|
+
if (attrVal.Type !== 'String') {
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
const attrNameLc = attrNames[j].toLowerCase();
|
|
976
|
+
if (attrNameLc === 'traceparent') {
|
|
977
|
+
traceparent = attrVal.Value;
|
|
978
|
+
break;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
if (traceparent) {
|
|
982
|
+
links.push({ context: traceparent });
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return links;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
module.exports = {
|
|
989
|
+
isLambdaExecutionEnvironment,
|
|
990
|
+
elasticApmAwsLambda,
|
|
991
|
+
getLambdaHandlerInfo,
|
|
992
|
+
};
|