elastic / apm-nodejs-http-client

**Moved to elastic/apm-agent-nodejs.** A low-level Node.js HTTP client for communicating with the Elastic APM intake API
MIT License
21 stars 30 forks source link

Note: This repository will no longer be updated. This APM HTTP client code was moved into the elastic/apm-agent-nodejs repository in 2023-07. The code now lives at: https://github.com/elastic/apm-agent-nodejs/tree/main/lib/apm-client/http-apm-client. Please open any issues, questions, PRs for the APM HTTP client there.

elastic-apm-http-client

A low-level HTTP client for communicating with the Elastic APM intake API version 2. For support for version 1, use version 5.x of this module.

This module is meant for building other modules that needs to communicate with Elastic APM.

If you are looking to use Elastic APM in your app or website, you'd most likely want to check out the official Elastic APM agent for Node.js instead.

Installation

npm install elastic-apm-http-client --save

Example Usage

const Client = require('elastic-apm-http-client')

const client = new Client({
  serviceName: 'My App',
  agentName: 'my-nodejs-agent',
  agentVersion: require('./package.json').version,
  userAgent: 'My Custom Elastic APM Agent'
})

const span = {
  name: 'SELECT FROM users',
  duration: 42,
  start: 0,
  type: 'db.mysql.query'
}

client.sendSpan(span)

API

new Client(options)

Construct a new client object. Data given to the client will be converted to ndjson, compressed using gzip, and streamed to the APM Server.

Arguments:

Data sent to the APM Server as part of the metadata object. See also the "Cloud & Extra Metadata" section below.

HTTP client configuration:

Cloud & Extra Metadata Configuration. Zero or one of the following three options may be used.

APM Agent Configuration via Kibana:

Streaming configuration:

Data sanitizing configuration:

Other options:

Event: config

Emitted every time a change to the agent config is pulled from the APM Server. The listener is passed the updated config options as a key/value object.

Each key is the lowercase version of the environment variable, without the ELASTIC_APM_ prefix, e.g. transaction_sample_rate instead of ELASTIC_APM_TRANSACTION_SAMPLE_RATE.

If no central configuration is set up for the given serviceName / environment when the client is started, this event will be emitted once with an empty object. This will also happen after central configuration for the given serviceName / environment is deleted.

Event: close

The close event is emitted when the client and any of its underlying resources have been closed. The event indicates that no more events will be emitted, and no more data can be sent by the client.

Event: error

Emitted if an error occurs. The listener callback is passed a single Error argument when called.

Event: finish

The finish event is emitted after the client.end() method has been called, and all data has been flushed to the underlying system.

Event: request-error

Emitted if an error occurs while communicating with the APM Server. The listener callback is passed a single Error argument when called.

The request to the APM Server that caused the error is terminated and the data included in that request is lost. This is normally only important to consider for requests to the Intake API.

If a non-2xx response was received from the APM Server, the status code will be available on error.code.

For requests to the Intake API where the response is a structured error message, the error object will have the following properties:

If the response contained an error body that could not be parsed by the client, the raw body will be available on error.response.

The client is not closed when the request-error event is emitted.

client.sent

An integer indicating the number of events (spans, transactions, errors, or metricsets) sent by the client. An event is considered sent when the HTTP request used to transmit it has ended. Note that errors in requests to APM server may mean this value is not the same as the number of events accepted by the APM server.

client.config(options)

Update the configuration given to the Client constructor. All configuration options can be updated except:

client.supportsKeepingUnsampledTransaction()

This method returns a boolean indicating whether the remote APM Server (per the configured serverUrl) is of a version that requires unsampled transactions to be sent.

This defaults to true if the remote APM server version is not known -- either because the background fetch of the APM Server version hasn't yet completed, or the version could not be fetched.

client.supportsActivationMethodField()

This method returns a boolean indicating whether the remote APM Server (per the configured serverUrl) is of a version that supports the metadata.service.agent.activation_method field. This is true for APM server versions >=8.7.1. It defaults to true if the APM server version is not (yet) known.

client.addMetadataFilter(fn)

Add a filter function for the "metadata" object sent to APM server. This will be called once at client creation, and possibly again later if client.config() is called to reconfigure the client or client.addMetadataFilter(fn) is called to add additional filters.

Here is an example of a filter that removes the metadata.process.argv field:

apm.addMetadataFilter(function dropArgv(md) {
  if (md.process && md.process.argv) {
    delete md.process.argv
  }
  return md
})

It is up to the user to ensure the returned object conforms to the metadata schema, otherwise APM data injest will be broken. An example of that (when used with the Node.js APM agent) is this in the application's log:

[2021-04-14T22:28:35.419Z] ERROR (elastic-apm-node): APM Server transport error (400): Unexpected APM Server response
APM Server accepted 0 events in the last request
Error: validation error: 'metadata' required
  Document: {"metadata":null}

See the APM Agent addMetadataFilter documentation for further details.

client.setExtraMetadata([metadata])

Add extra metadata to be included in the "metadata" object sent to APM Server in intake requests. The given metadata object is merged into the metadata determined from the client configuration.

The reason this exists is to allow some metadata to be provided asynchronously, especially in combination with the expectExtraMetadata configuration option to ensure that event data is not sent to APM Server until this extra metadata is provided. For example, in an AWS Lambda function some metadata is not available until the first function invocation -- which is some async time after Client creation.

client.lambdaStart()

Tells the client that a Lambda function invocation has started. See Notes on Lambda Usage below.

client.lambdaShouldRegisterTransactions()

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.

Typically the reason this would return false is when the Lambda extension is too old to support registering transactions.

client.lambdaRegisterTransaction(transaction, awsRequestId)

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 below.

Arguments:

client.sendSpan(span[, callback])

Send a span to the APM Server.

Arguments:

client.sendTransaction(transaction[, callback])

Send a transaction to the APM Server.

Arguments:

client.sendError(error[, callback])

Send a error to the APM Server.

Arguments:

client.sendMetricSet(metricset[, callback])

Send a metricset to the APM Server.

Arguments:

client.flush([opts,] [callback])

Flush the internal buffer and end the current HTTP request to the APM Server. If no HTTP request is in process nothing happens. In an AWS Lambda environment this will also initiate a quicker shutdown of the intake request, because the APM agent always flushes at the end of a Lambda handler.

Arguments:

client.end([callback])

Calling the client.end() method signals that no more data will be sent to the client. If the internal buffer contains any data, this is flushed before ending.

Arguments:

client.destroy()

Destroy the client. After this call, the client has ended and subsequent calls to sendSpan(), sendTransaction(), sendError(), flush(), or end() will result in an error.

Notes on Lambda usage

To properly handle data flushing for instrumented Lambda functions 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