Skip to content
This repository has been archived by the owner on Aug 4, 2023. It is now read-only.

Commit

Permalink
feat!: metadata.system.{configured_hostname,detected_hostname}, prefe…
Browse files Browse the repository at this point in the history
  • Loading branch information
trentm committed Jun 20, 2023
1 parent b30b2f3 commit 13641c8
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 17 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ jobs:
test-vers:
strategy:
matrix:
node: ['8.6', '8', '10', '12', '14', '15', '16', '18', '19']
node: ['8.6', '8', '10', '12', '14', '15', '16', '18', '19', '20']
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- run: npm install
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# elastic-apm-http-client changelog

## v12.0.0

- **Breaking change.** The `hostname` configuration option has been renamed to
`configuredHostname`. As well, the hostname detection has changed to prefer
using a FQDN, if available. See [the spec](https://github.com/elastic/apm/blob/main/specs/agents/metadata.md#hostname).
(https://github.com/elastic/apm-agent-nodejs/issues/3310)

- The APM client will send `metadata.system.detected_hostname` and
`metadata.system.configured_hostname` as appropriate for APM server versions
>=7.4, rather than the now deprecated `metadata.system.hostname`.
See [the spec](https://github.com/elastic/apm/blob/main/specs/agents/metadata.md#hostname).

## v11.4.0

- Add support for pre-registering of partial transactions for AWS Lambda.
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ See also the "Cloud & Extra Metadata" section below.
specific framework, use this config option to log its name
- `frameworkVersion` - If the service being instrumented is running a
specific framework, use this config option to log its version
- `hostname` - Custom hostname (default: OS hostname)
- `configuredHostname` - A user-configured hostname, if any, e.g. from the `ELASTIC_APM_HOSTNAME` envvar.
See <https://github.com/elastic/apm/blob/main/specs/agents/metadata.md#hostname>.
- `environment` - Environment name (default: `process.env.NODE_ENV || 'development'`)
- `containerId` - Docker container id, if not given will be parsed from `/proc/self/cgroup`
- `kubernetesNodeName` - Kubernetes node name
Expand Down
31 changes: 26 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const semver = require('semver')
const streamToBuffer = require('fast-stream-to-buffer')
const StreamChopper = require('stream-chopper')

const { detectHostname } = require('./lib/detect-hostname')
const ndjson = require('./lib/ndjson')
const { NoopLogger } = require('./lib/logging')
const truncate = require('./lib/truncate')
Expand All @@ -37,7 +38,6 @@ function isFlushMarker (obj) {
return obj === kFlush || obj === kLambdaEndFlush
}

const hostname = os.hostname()
const requiredOpts = [
'agentName',
'agentVersion',
Expand Down Expand Up @@ -243,7 +243,6 @@ Client.prototype.config = function (opts) {
if (!this._conf.time && this._conf.time !== 0) this._conf.time = 10000
if (!this._conf.serverTimeout && this._conf.serverTimeout !== 0) this._conf.serverTimeout = 15000
if (!this._conf.serverUrl) this._conf.serverUrl = 'http://127.0.0.1:8200'
if (!this._conf.hostname) this._conf.hostname = hostname
if (!this._conf.environment) this._conf.environment = process.env.NODE_ENV || 'development'
if (!this._conf.truncateKeywordsAt) this._conf.truncateKeywordsAt = 1024
if (!this._conf.truncateStringsAt) this._conf.truncateStringsAt = 1024
Expand All @@ -265,6 +264,8 @@ Client.prototype.config = function (opts) {
// processed values
this._conf.serverUrl = new URL(this._conf.serverUrl)

this._conf.detectedHostname = detectHostname()

if (containerInfo) {
if (!this._conf.containerId && containerInfo.containerId) {
this._conf.containerId = containerInfo.containerId
Expand All @@ -273,7 +274,7 @@ Client.prototype.config = function (opts) {
this._conf.kubernetesPodUID = containerInfo.podId
}
if (!this._conf.kubernetesPodName && containerInfo.podId) {
this._conf.kubernetesPodName = hostname
this._conf.kubernetesPodName = this._conf.detectedHostname
}
}

Expand Down Expand Up @@ -1223,7 +1224,8 @@ function getChoppedStreamHandler (client, onerror) {
* Some behaviors in the APM depend on the APM Server version. These are
* exposed as `Client#supports...` boolean methods.
*
* These `Client#supports...` method names intentionally match those from the Java agent:
* These `Client#supports...` method names, if not always the implementation,
* intentionally match those from the Java agent:
* https://github.com/elastic/apm-agent-java/blob/master/apm-agent-core/src/main/java/co/elastic/apm/agent/report/ApmServerClient.java#L322-L349
*/
Client.prototype.supportsKeepingUnsampledTransaction = function () {
Expand All @@ -1245,6 +1247,13 @@ Client.prototype.supportsActivationMethodField = function () {
return semver.gte(this._apmServerVersion, '8.7.1')
}
}
Client.prototype.supportsConfiguredAndDetectedHostname = function () {
if (!this._apmServerVersion) {
return true // Optimistically assume APM server is >=7.4.
} else {
return semver.gte(this._apmServerVersion, '7.4.0')
}
}

/**
* Signal to the Elastic AWS Lambda extension that a lambda function execution
Expand Down Expand Up @@ -1485,7 +1494,6 @@ function metadataFromConf (opts, client) {
argv: process.argv
},
system: {
hostname: opts.hostname,
architecture: process.arch,
platform: process.platform,
container: undefined,
Expand All @@ -1494,6 +1502,19 @@ function metadataFromConf (opts, client) {
labels: opts.globalLabels
}

// On `system.*hostname` fields:
// - `hostname` was deprecated in APM server v7.4, replaced by the next two.
// - Around Elastic v8.9, ECS changed `host.name` to prefer the FQDN,
// hence APM agents now prefer FQDN for `detected_hostname`.
if (client.supportsConfiguredAndDetectedHostname()) {
payload.system.detected_hostname = opts.detectedHostname
if (opts.configuredHostname) {
payload.system.configured_hostname = opts.configuredHostname
}
} else {
payload.system.hostname = opts.configuredHostname || opts.detectedHostname
}

if (opts.agentActivationMethod && client.supportsActivationMethodField()) {
payload.service.agent.activation_method = opts.agentActivationMethod
}
Expand Down
61 changes: 61 additions & 0 deletions lib/detect-hostname.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use strict'

// Detect the current hostname, preferring the FQDN if possible.
// Spec: https://github.com/elastic/apm/blob/main/specs/agents/metadata.md#hostname

const os = require('os')
const { spawnSync } = require('child_process')

/**
* *Synchronously* detect the current hostname, preferring the FQDN.
* This is sent to APM server as `metadata.system.detected_hostname`
* and is intended to fit the ECS `host.name` value
* (https://www.elastic.co/guide/en/ecs/current/ecs-host.html#field-host-name).
*
* @returns {String}
*/
function detectHostname () {
let hostname = null
let out
const fallback = os.hostname()

switch (os.platform()) {
case 'win32':
// https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/hostname
// XXX want windowsHide:true for v8.8.0 and later?
out = spawnSync('hostname', {encoding: 'utf8', shell: true, timeout: 1000})
if (out.error) {
if ('COMPUTERNAME' in process.env) {
hostname = process.env['COMPUTERNAME'].trim()
}
} else {
hostname = out.stdout.trim()
}
const domain = process.env['USERDNSDOMAIN']
if (hostname && 'USERDNSDOMAIN' in process.env) {
hostname += '.' + process.env['USERDNSDOMAIN'].trim()
}
break

default:
out = spawnSync('/bin/hostname', ['-f'], {encoding: 'utf8', shell: false, timeout: 500})
if (!out.error) {
hostname = out.stdout.trim()
}
// I'm going a little off of the APM spec here by *not* falling back to
// HOSTNAME or HOST envvars. Latest discussion point is here:
// https://github.com/elastic/apm/pull/517#issuecomment-940973458
// My understanding is HOSTNAME is a *Bash*-set envvar.
break
}

if (!hostname) {
hostname = fallback
}
hostname = hostname.trim().toLowerCase()
return hostname
}

module.exports = {
detectHostname
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "elastic-apm-http-client",
"version": "11.4.0",
"version": "12.0.0",
"description": "A low-level HTTP client for communicating with the Elastic APM intake API",
"main": "index.js",
"directories": {
Expand Down
14 changes: 9 additions & 5 deletions test/config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ const URL = require('url').URL
const utils = require('./lib/utils')
const pkg = require('../package')
const Client = require('../')
const { detectHostname } = require('../lib/detect-hostname')

const APMServer = utils.APMServer
const processIntakeReq = utils.processIntakeReq
const validOpts = utils.validOpts

const detectedHostname = detectHostname()

test('package', function (t) {
// these values are in the User-Agent header tests, so we need to make sure
// they are as we expect
Expand Down Expand Up @@ -220,7 +223,7 @@ test('metadata', function (t) {
serviceVersion: 'custom-serviceVersion',
frameworkName: 'custom-frameworkName',
frameworkVersion: 'custom-frameworkVersion',
hostname: 'custom-hostname',
configuredHostname: 'custom-hostname',
environment: 'production',
globalLabels: {
foo: 'bar',
Expand Down Expand Up @@ -265,9 +268,10 @@ test('metadata', function (t) {
argv: process.argv
},
system: {
hostname: 'custom-hostname',
architecture: process.arch,
platform: process.platform
platform: process.platform,
detected_hostname: detectedHostname,
configured_hostname: 'custom-hostname',
},
labels: {
foo: 'bar',
Expand Down Expand Up @@ -346,9 +350,9 @@ test('metadata - default values', function (t) {
argv: process.argv
},
system: {
hostname: os.hostname(),
architecture: process.arch,
platform: process.platform
platform: process.platform,
detected_hostname: detectedHostname
}
}
}
Expand Down
9 changes: 7 additions & 2 deletions test/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,13 @@ function assertMetadata (t, obj) {
const regex = /(\/test\/.*\.js|node_modules\/\.bin\/tape)$/
t.ok(regex.test(_process.argv[1]), `process.argv[1] should match ${regex} (was: ${_process.argv[1]})"`)
const system = metadata.system
t.ok(typeof system.hostname, 'string')
t.ok(system.hostname.length > 0)
if ('detected_hostname' in system) {
t.ok(typeof system.detected_hostname, 'string')
t.ok(system.detected_hostname.length > 0)
} else {
t.ok(typeof system.hostname, 'string')
t.ok(system.hostname.length > 0)
}
t.ok(typeof system.architecture, 'string')
t.ok(system.architecture.length > 0)
t.ok(typeof system.platform, 'string')
Expand Down

0 comments on commit 13641c8

Please sign in to comment.