Skip to content

Commit

Permalink
chore: move 'elastic-apm-http-client' package code into this repo (#3507
Browse files Browse the repository at this point in the history
)

This moves the apm-nodejs-http-client.git code into 
"{lib,test}/apm-client/http-apm-client/..." in this repo. This will
help maintenance of multiple active branches of the APM agent.

The only changes are to rename the export to `HttpApmClient` and adjust
require paths. Some functions were moved from lib/apm-client/http-apm-client.js
to lib/apm-client/apm-client.js to clarify.

Closes: #3506
Co-authored-by: Trent Mick <trent.mick@elastic.co>
  • Loading branch information
david-luna and trentm authored Jul 29, 2023
1 parent a696dd9 commit 8b68529
Show file tree
Hide file tree
Showing 41 changed files with 8,045 additions and 264 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ Notes:
[float]
===== Chores
* Inline the `elastic-apm-http-client` package code into this repo.
({issues}3506[#3506])
[[release-notes-3.48.0]]
==== 3.48.0 - 2023/07/07
Expand Down
20 changes: 20 additions & 0 deletions NOTICE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,26 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```

## container-info

- **path:** [lib/container-info.js](lib/container-info.js)
- **author:** Stephen Belanger
- **project url:** https://github.com/Qard/container-info
- **original file:** https://github.com/Qard/container-info/blob/master/index.js
- **license:** MIT License (MIT), http://opensource.org/licenses/MIT

```
### Copyright (c) 2018 Stephen Belanger
#### Licensed under MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
```

## shimmer

- **path:** [lib/instrumentation/shimmer.js](lib/instrumentation/shimmer.js)
Expand Down
180 changes: 174 additions & 6 deletions lib/apm-client/apm-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@

'use strict'

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

const { CENTRAL_CONFIG_OPTS } = require('../config/schema')
const fs = require('fs')
const version = require('../../package').version
const { CENTRAL_CONFIG_OPTS, INTAKE_STRING_MAX_SIZE } = require('../config/schema')
const { normalize } = require('../config/config')
const { CloudMetadata } = require('../cloud-metadata')
const { isLambdaExecutionEnvironment } = require('../lambda')
const logging = require('../logging')

const { HttpApmClient } = require('./http-apm-client')
const { NoopApmClient } = require('./noop-apm-client')
const { getHttpClientConfig } = require('./http-apm-client')
const { isAzureFunctionsEnvironment, getAzureFunctionsExtraMetadata } = require('../instrumentation/azure-functions')

/**
* Returns an APM client suited for the configuration provided
Expand All @@ -28,7 +31,7 @@ function createApmClient (config, agent) {
return config.transport(config, agent)
}

const client = new ElasticAPMHttpClient(getHttpClientConfig(config, agent))
const client = new HttpApmClient(getHttpClientConfig(config, agent))

client.on('config', remoteConf => {
agent.logger.debug({ remoteConf }, 'central config received')
Expand Down Expand Up @@ -104,6 +107,171 @@ function createApmClient (config, agent) {
return client
}

/**
* Returns a HTTP client configuration based on agent configuration options
*
* @param {Object} conf The agent configuration object
* @param {Object} agent
* @returns {Object}
*/
function getHttpClientConfig (conf, agent) {
let clientLogger = null
if (!logging.isLoggerCustom(agent.logger)) {
// https://www.elastic.co/guide/en/ecs/current/ecs-event.html#field-event-module
clientLogger = agent.logger.child({ 'event.module': 'apmclient' })
}
const isLambda = isLambdaExecutionEnvironment()

const clientConfig = {
agentName: 'nodejs',
agentVersion: version,
agentActivationMethod: agent._agentActivationMethod,
serviceName: conf.serviceName,
serviceVersion: conf.serviceVersion,
frameworkName: conf.frameworkName,
frameworkVersion: conf.frameworkVersion,
globalLabels: maybePairsToObject(conf.globalLabels),
configuredHostname: conf.hostname,
environment: conf.environment,

// Sanitize conf
truncateKeywordsAt: INTAKE_STRING_MAX_SIZE,
truncateLongFieldsAt: conf.longFieldMaxLength,
// truncateErrorMessagesAt: see below

// HTTP conf
secretToken: conf.secretToken,
apiKey: conf.apiKey,
userAgent: userAgentFromConf(conf),
serverUrl: conf.serverUrl,
serverCaCert: loadServerCaCertFile(conf.serverCaCertFile),
rejectUnauthorized: conf.verifyServerCert,
serverTimeout: conf.serverTimeout * 1000,

// APM Agent Configuration via Kibana:
centralConfig: conf.centralConfig,

// Streaming conf
size: conf.apiRequestSize,
time: conf.apiRequestTime * 1000,
maxQueueSize: conf.maxQueueSize,

// Debugging/testing options
logger: clientLogger,
payloadLogFile: conf.payloadLogFile,
apmServerVersion: conf.apmServerVersion,

// Container conf
containerId: conf.containerId,
kubernetesNodeName: conf.kubernetesNodeName,
kubernetesNamespace: conf.kubernetesNamespace,
kubernetesPodName: conf.kubernetesPodName,
kubernetesPodUID: conf.kubernetesPodUID
}

// `service_node_name` is ignored in Lambda and Azure Functions envs.
if (conf.serviceNodeName) {
if (isLambda) {
agent.logger.warn({ serviceNodeName: conf.serviceNodeName }, 'ignoring "serviceNodeName" config setting in Lambda environment')
} else if (isAzureFunctionsEnvironment) {
agent.logger.warn({ serviceNodeName: conf.serviceNodeName }, 'ignoring "serviceNodeName" config setting in Azure Functions environment')
} else {
clientConfig.serviceNodeName = conf.serviceNodeName
}
}

// Extra metadata handling.
if (isLambda) {
// Tell the Client to wait for a subsequent `.setExtraMetadata()` call
// before allowing intake requests. This will be called by `apm.lambda()`
// on first Lambda function invocation.
clientConfig.expectExtraMetadata = true
} else if (isAzureFunctionsEnvironment) {
clientConfig.extraMetadata = getAzureFunctionsExtraMetadata()
} else if (conf.cloudProvider !== 'none') {
clientConfig.cloudMetadataFetcher = new CloudMetadata(conf.cloudProvider, conf.logger, conf.serviceName)
}

if (conf.errorMessageMaxLength !== undefined) {
// As of v10 of the http client, truncation of error messages will default
// to `truncateLongFieldsAt` if `truncateErrorMessagesAt` is not specified.
clientConfig.truncateErrorMessagesAt = conf.errorMessageMaxLength
}

return clientConfig
}

// Return the User-Agent string the agent will use for its comms to APM Server.
//
// Per https://github.com/elastic/apm/blob/main/specs/agents/transport.md#user-agent
// the pattern is roughly this:
// $repoName/$version ($serviceName $serviceVersion)
//
// The format of User-Agent is governed by https://datatracker.ietf.org/doc/html/rfc7231.
// User-Agent = product *( RWS ( product / comment ) )
// We do not expect `$repoName` and `$version` to have surprise/invalid values.
// From `validateServiceName` above, we know that `$serviceName` is null or a
// string limited to `/^[a-zA-Z0-9 _-]+$/`. However, `$serviceVersion` is
// provided by the user and could have invalid characters.
//
// `comment` is defined by
// https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6 as:
// comment = "(" *( ctext / quoted-pair / comment ) ")"
// obs-text = %x80-FF
// ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text
// quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
//
// `commentBadChar` below *approximates* these rules, and is used to replace
// invalid characters with '_' in the generated User-Agent string. This
// replacement isn't part of the APM spec.
function userAgentFromConf (conf) {
let userAgent = `apm-agent-nodejs/${version}`

// This regex *approximately* matches the allowed syntax for a "comment".
// It does not handle "quoted-pair" or a "comment" in a comment.
const commentBadChar = /[^\t \x21-\x27\x2a-\x5b\x5d-\x7e\x80-\xff]/g
const commentParts = []
if (conf.serviceName) {
commentParts.push(conf.serviceName)
}
if (conf.serviceVersion) {
commentParts.push(conf.serviceVersion.replace(commentBadChar, '_'))
}
if (commentParts.length > 0) {
userAgent += ` (${commentParts.join(' ')})`
}

return userAgent
}

/**
* Reads te server CA cert file and returns a buffer with its contents
* @param {string | undefined} serverCaCertFile
* @param {any} logger
* @returns {Buffer}
*/
function loadServerCaCertFile (serverCaCertFile, logger) {
if (serverCaCertFile) {
try {
return fs.readFileSync(serverCaCertFile)
} catch (err) {
logger.error('Elastic APM initialization error: Can\'t read server CA cert file %s (%s)', serverCaCertFile, err.message)
}
}
}

function maybePairsToObject (pairs) {
return pairs ? pairsToObject(pairs) : undefined
}

function pairsToObject (pairs) {
return pairs.reduce((object, [key, value]) => {
object[key] = value
return object
}, {})
}

module.exports = {
createApmClient
createApmClient,
userAgentFromConf
}
Loading

0 comments on commit 8b68529

Please sign in to comment.