elastic-apm-http-client
Advanced tools
Comparing version 11.3.1 to 11.4.0
110
index.js
@@ -114,2 +114,5 @@ 'use strict' | ||
this._lambdaActive = false | ||
// Whether to forward `.lambdaRegisterTransaction()` calls to the Lambda | ||
// extension. This will be set false if a previous attempt failed. | ||
this._lambdaShouldRegisterTransactions = true | ||
@@ -316,2 +319,3 @@ // Internal runtime stats for developer debugging/tuning. | ||
this._conf.requestSignalLambdaEnd = getSignalLambdaEndRequestOptions(this._conf, this._agent) | ||
this._conf.requestRegisterTransaction = getRegisterTransactionRequestOptions(this._conf, this._agent) | ||
@@ -654,2 +658,97 @@ // fixes bug where cached/memoized _encodedMetadata wouldn't be | ||
/** | ||
* Indicate whether the APM agent -- when in a Lambda environment -- should | ||
* bother calling `.lambdaRegisterTransaction(...)`. | ||
* | ||
* @returns {boolean} | ||
*/ | ||
Client.prototype.lambdaShouldRegisterTransactions = function () { | ||
return this._lambdaShouldRegisterTransactions | ||
} | ||
/** | ||
* Tell the local Lambda extension about the just-started transaction. This | ||
* allows the extension to report the transaction in certain error cases | ||
* where the APM agent isn't able to *end* the transaction and report it, | ||
* e.g. if the function is about to timeout, or if the process crashes. | ||
* | ||
* The expected request is as follows, and a 200 status code is expected in | ||
* response: | ||
* | ||
* POST /register/transaction | ||
* Content-Type: application/vnd.elastic.apm.transaction+ndjson | ||
* x-elastic-aws-request-id: ${awsRequestId} | ||
* | ||
* {"metadata":{...}} | ||
* {"transaction":{...partial transaction data...}} | ||
* | ||
* @param {object} trans - a mostly complete APM Transaction object. It should | ||
* have a default `outcome` value. `duration` and `result` (and possibly | ||
* `outcome`) fields will be set by the Elastic Lambda extension if this | ||
* transaction is used. | ||
* @param {import('crypto').UUID} awsRequestId | ||
* @returns {Promise || undefined} So this can, and should, be `await`ed. | ||
* If returning a promise, it will only resolve, never reject. | ||
*/ | ||
Client.prototype.lambdaRegisterTransaction = function (trans, awsRequestId) { | ||
if (!isLambdaExecutionEnvironment) { | ||
return | ||
} | ||
if (!this._lambdaShouldRegisterTransactions) { | ||
return | ||
} | ||
assert(this._encodedMetadata, '_encodedMetadata is set') | ||
// We expect to be talking to the localhost Elastic Lambda extension, so we | ||
// want a shorter timeout than `_conf.serverTimeout`. | ||
const TIMEOUT_MS = 5000 | ||
const startTime = performance.now() | ||
return new Promise((resolve, reject) => { | ||
this._log.trace({ awsRequestId, traceId: trans.trace_id, transId: trans.id }, 'lambdaRegisterTransaction start') | ||
var out = this._encode({ transaction: trans }, Client.encoding.TRANSACTION) | ||
const finish = errOrErrMsg => { | ||
const durationMs = performance.now() - startTime | ||
if (errOrErrMsg) { | ||
this._log.debug({ awsRequestId, err: errOrErrMsg, durationMs }, 'lambdaRegisterTransaction unsuccessful') | ||
this._lambdaShouldRegisterTransactions = false | ||
} else { | ||
this._log.trace({ awsRequestId, durationMs }, 'lambdaRegisterTransaction success') | ||
} | ||
resolve() // always resolve, never reject | ||
} | ||
// Every `POST /register/transaction` request must set the | ||
// `x-elastic-aws-request-id` header. Instead of creating a new options obj | ||
// each time, we just modify in-place. | ||
this._conf.requestRegisterTransaction.headers['x-elastic-aws-request-id'] = awsRequestId | ||
const req = this._transportRequest(this._conf.requestRegisterTransaction, res => { | ||
res.on('error', err => { | ||
// Not sure this event can ever be emitted, but just in case. | ||
res.destroy(err) | ||
}) | ||
res.resume() | ||
if (res.statusCode !== 200) { | ||
finish(`unexpected response status code: ${res.statusCode}`) | ||
return | ||
} | ||
res.on('end', function () { | ||
finish() | ||
}) | ||
}) | ||
req.setTimeout(TIMEOUT_MS) | ||
req.on('timeout', () => { | ||
req.destroy(new Error(`timeout (${TIMEOUT_MS}ms) registering lambda transaction`)) | ||
}) | ||
req.on('error', err => { | ||
finish(err) | ||
}) | ||
req.write(this._encodedMetadata) | ||
req.write(out) | ||
req.end() | ||
}) | ||
} | ||
// With the cork/uncork handling on this stream, `this.write`ing on this | ||
@@ -1166,5 +1265,5 @@ // stream when already destroyed will lead to: | ||
if (errOrErrMsg) { | ||
this._log.error({ err: errOrErrMsg, durationMs }, 'error signaling lambda invocation done') | ||
this._log.error({ err: errOrErrMsg, durationMs }, '_signalLambdaEnd error') | ||
} else { | ||
this._log.trace({ durationMs }, 'signaled lambda invocation done') | ||
this._log.trace({ durationMs }, '_signalLambdaEnd success') | ||
} | ||
@@ -1194,3 +1293,2 @@ cb() | ||
req.on('timeout', () => { | ||
this._log.trace('_signalLambdaEnd timeout') | ||
req.destroy(new Error(`timeout (${TIMEOUT_MS}ms) signaling Lambda invocation done`)) | ||
@@ -1329,2 +1427,8 @@ }) | ||
function getRegisterTransactionRequestOptions (opts, agent) { | ||
const headers = getHeaders(opts) | ||
headers['Content-Type'] = 'application/vnd.elastic.apm.transaction+ndjson' | ||
return getBasicRequestOptions('POST', '/register/transaction', headers, opts, agent) | ||
} | ||
function getConfigRequestOptions (opts, agent) { | ||
@@ -1331,0 +1435,0 @@ const path = '/config/v1/agents?' + querystring.stringify({ |
{ | ||
"name": "elastic-apm-http-client", | ||
"version": "11.3.1", | ||
"version": "11.4.0", | ||
"description": "A low-level HTTP client for communicating with the Elastic APM intake API", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -366,26 +366,26 @@ # elastic-apm-http-client | ||
Tells the client that a Lambda function invocation has started. | ||
See [Notes on Lambda Usage](#notes-on-lambda-usage) below. | ||
#### Notes on Lambda usage | ||
### `client.lambdaShouldRegisterTransactions()` | ||
To properly handle [data flushing for instrumented Lambda functions](https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-aws-lambda.md#data-flushing) | ||
this Client should be used as follows in a Lambda environment. | ||
This returns a boolean indicating if the APM agent -- when running in a Lambda | ||
environment -- should bother calling `client.lambdaRegisterTransaction(...)`. | ||
This can help the APM agent avoid some processing gathering transaction data. | ||
- When a Lambda invocation starts, `client.lambdaStart()` must be called. | ||
Typically the reason this would return `false` is when the Lambda extension is | ||
too old to support registering transactions. | ||
The Client prevents intake requests to APM Server when in a Lambda environment | ||
when a function invocation is *not* active. This is to ensure that an intake | ||
request does not accidentally span a period when a Lambda VM is frozen, | ||
which can lead to timeouts and lost APM data. | ||
- When a Lambda invocation finishes, `client.flush({lambdaEnd: true}, cb)` must | ||
be called. | ||
### `client.lambdaRegisterTransaction(transaction, awsRequestId)` | ||
The `lambdaEnd: true` tells the Client to (a) mark the lambda as inactive so | ||
a subsequent intake request is not started until the next invocation, and | ||
(b) signal the Elastic AWS Lambda Extension that this invocation is done. | ||
The user's Lambda handler should not finish until `cb` is called. This | ||
ensures that the extension receives tracing data and the end signal before | ||
the Lambda Runtime freezes the VM. | ||
Tells the Lambda Extension about the ongoing transaction, so that data can be | ||
used to report the transaction in certain error cases -- e.g. a Lambda handler | ||
timeout. See [Notes on Lambda Usage](#notes-on-lambda-usage) below. | ||
Arguments: | ||
- `transaction` - A transaction object that can be serialized to JSON. | ||
- `awsRequestId` - The AWS request ID for this invocation. This is a UUID | ||
available on the Lambda context object. | ||
### `client.sendSpan(span[, callback])` | ||
@@ -466,4 +466,41 @@ | ||
## Notes on Lambda usage | ||
To properly handle [data flushing for instrumented Lambda functions](https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-aws-lambda.md#data-flushing) | ||
this Client should be used as follows in a Lambda environment. | ||
1. Ensure that metadata is set before any of the following calls. Typically | ||
in Lambda this is done by (a) configuring the client with | ||
`expectExtraMetadata` and (b) calling `setExtraMetadata()` at the start of | ||
the first invocation. | ||
2. When a Lambda invocation starts, `client.lambdaStart()` must be called. | ||
The Client prevents intake requests to APM Server when in a Lambda | ||
environment when a function invocation is *not* active. This is to ensure | ||
that an intake request does not accidentally span a period when a Lambda VM | ||
is frozen, which can lead to timeouts and lost APM data. | ||
3. When the transaction for this Lambda invocation has been created, | ||
`await client.lambdaRegisterTransaction(<transaction>, <awsRequestId>)` should be | ||
called. This is used to pass transaction details to the Lambda Extension so | ||
a transaction can be reported in certain failure modes (e.g. a Lambda | ||
handler timeout). | ||
`client.lambdaShouldRegisterTransactions()` can be used to avoid gathering | ||
data for this call. | ||
4. When a Lambda invocation finishes, `client.flush({lambdaEnd: true}, cb)` | ||
must be called. | ||
The `lambdaEnd: true` tells the Client to (a) mark the lambda as inactive so | ||
a subsequent intake request is not started until the next invocation, and | ||
(b) signal the Elastic AWS Lambda Extension that this invocation is done. | ||
The user's Lambda handler should not finish until `cb` is called. This | ||
ensures that the extension receives tracing data and the end signal before | ||
the Lambda Runtime freezes the VM. | ||
## License | ||
[MIT](LICENSE) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
100907
2075
505