diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index a93ffef990f..a1192836ee9 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -65,6 +65,8 @@ Notes: and the APM agent will automatically add a MetricReader to ship metrics to APM server. See the <> for details. ({pull}3152[#3152]) +* Add `apm.setGlobalLabel()` to dynamically extend the `globalLabels` set in the initial config. Refer to <> for details. ({pull}3337[#3337]) + [float] ===== Chores diff --git a/docs/agent-api.asciidoc b/docs/agent-api.asciidoc index fae5fd4751b..ddcbed51340 100644 --- a/docs/agent-api.asciidoc +++ b/docs/agent-api.asciidoc @@ -291,6 +291,25 @@ WARNING: Avoid defining too many user-specified labels. Defining too many unique fields in an index is a condition that can lead to a {ref}/mapping.html#mapping-limit-settings[mapping explosion]. +[[apm-set-global-label]] +==== `apm.setGlobalLabel(name, value)` + +[small]#Added in: REPLACEME# + +* `name` +{type-string}+ +* `value` +{type-string}+ | +{type-number}+ | +{type-boolean}+ + +Extends the <> configuration. It allows setting labels that are applied to all transactions. A potential use case is to specify a label with the state of your application: `'initializing' | 'available' | 'unhealthy'`. + +TIP: Labels are key/value pairs that are indexed by Elasticsearch and therefore searchable +(as opposed to data set via <>). +Before using custom labels, ensure you understand the different types of +{apm-guide-ref}/metadata.html[metadata] that are available. + +WARNING: Avoid defining too many user-specified labels. +Defining too many unique fields in an index is a condition that can lead to a +{ref}/mapping.html#mapping-limit-settings[mapping explosion]. + [[apm-capture-error]] ==== `apm.captureError(error[, options][, callback])` diff --git a/index.d.ts b/index.d.ts index a67ed7aaf75..b3614ca4233 100644 --- a/index.d.ts +++ b/index.d.ts @@ -116,6 +116,7 @@ declare namespace apm { currentSpan: Span | null; // Context + setGlobalLabel (name: string, value: LabelValue): void; setLabel (name: string, value: LabelValue, stringify?: boolean): boolean; addLabels (labels: Labels, stringify?: boolean): boolean; setUserContext (user: UserObject): void; diff --git a/lib/agent.js b/lib/agent.js index 8cd293aa8fc..33596ab4f02 100644 --- a/lib/agent.js +++ b/lib/agent.js @@ -355,6 +355,22 @@ Agent.prototype.setCustomContext = function (context) { return true } +Agent.prototype.setGlobalLabel = function (key, value) { + if (!this._conf.globalLabels) this._conf.globalLabels = [] + const foundPos = this._conf.globalLabels.findIndex(([name]) => key === name) + if (foundPos > -1) { + this._conf.globalLabels[foundPos][1] = value + } else { + this._conf.globalLabels.push([key, value]) + } + this._transport.config({ + globalLabels: this._conf.globalLabels.reduce((acc, [k, v]) => { + acc[k] = v + return acc + }, {}) + }) +} + Agent.prototype.setLabel = function (key, value, stringify) { var trans = this._instrumentation.currTransaction() if (!trans) return false diff --git a/test/agent.test.js b/test/agent.test.js index 523bc598ff6..4a1f85ec2e1 100644 --- a/test/agent.test.js +++ b/test/agent.test.js @@ -502,6 +502,80 @@ test('#setCustomContext()', function (t) { t.end() }) +test('#setGlobalLabel()', function (suite) { + let apmServer + let suiteAgentOpts + + suite.test('setup mock APM server', function (t) { + apmServer = new MockAPMServer() + apmServer.start(function (serverUrl) { + t.comment('mock APM serverUrl: ' + serverUrl) + suiteAgentOpts = Object.assign( + {}, + agentOpts, + { serverUrl } + ) + t.end() + }) + }) + + suite.test('sets a global label', async function (t) { + apmServer.clear() + const agent = new Agent().start(suiteAgentOpts) + agent.setGlobalLabel('goo', 1) + t.deepEqual(agent._conf.globalLabels, Object.entries({ goo: 1 }), 'agent._conf.globalLabels') + agent.startTransaction('manual') + agent.endTransaction() + await agent.flush() + t.deepEqual(apmServer.events[0].metadata.labels, { goo: 1 }, 'APM server metadata.labels') + agent.destroy() + t.end() + }) + + suite.test('extends the predefined global labels', async function (t) { + apmServer.clear() + const agentOptsWithGlobalLabels = Object.assign( + {}, + suiteAgentOpts, + { globalLabels: { some: true } } + ) + const agent = new Agent().start(agentOptsWithGlobalLabels) + agent.setGlobalLabel('goo', 1) + t.deepEqual(agent._conf.globalLabels, Object.entries({ some: true, goo: 1 }), 'agent._conf.globalLabels') + agent.startTransaction('manual') + agent.endTransaction() + await agent.flush() + t.deepEqual(apmServer.events[0].metadata.labels, { some: true, goo: 1 }, 'APM server metadata.labels') + agent.destroy() + t.end() + }) + + suite.test('overrides an existing global label', async function (t) { + apmServer.clear() + const agentOptsWithGlobalLabels = Object.assign( + {}, + suiteAgentOpts, + { globalLabels: { some: true, goo: 0 } } + ) + const agent = new Agent().start(agentOptsWithGlobalLabels) + agent.setGlobalLabel('goo', 1) + t.deepEqual(agent._conf.globalLabels, Object.entries({ some: true, goo: 1 }), 'agent._conf.globalLabels') + agent.startTransaction('manual') + agent.endTransaction() + await agent.flush() + t.deepEqual(apmServer.events[0].metadata.labels, { some: true, goo: 1 }, 'APM server metadata.labels') + agent.destroy() + t.end() + }) + + suite.test('teardown mock APM server', function (t) { + apmServer.close() + t.end() + }) + + suite.end() +}) + test('#setLabel()', function (t) { t.test('no active transaction', function (t) { const agent = new Agent().start(agentOptsNoopTransport)