From 6f90794d5cae01aa0a4120dc1b51f17bdd2f1fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Wed, 10 May 2023 11:13:12 +0200 Subject: [PATCH 1/5] feat: add `apm.setGlobalLabel` --- CHANGELOG.asciidoc | 2 ++ docs/agent-api.asciidoc | 19 +++++++++++++++++++ index.d.ts | 1 + lib/agent.js | 12 ++++++++++++ test/agent.test.js | 36 ++++++++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 47e0b18e534..9756562c130 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -62,6 +62,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] ===== Bug fixes diff --git a/docs/agent-api.asciidoc b/docs/agent-api.asciidoc index fae5fd4751b..1bd60412a9b 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: v3.46.0# + +* `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..e2ecc452bea 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): boolean; 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 be3ebd390b1..d9b7df711cc 100644 --- a/lib/agent.js +++ b/lib/agent.js @@ -354,6 +354,18 @@ 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: Object.fromEntries(this._conf.globalLabels) }) + return true +} + 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 95f3d6560a0..4f775c9ed39 100644 --- a/test/agent.test.js +++ b/test/agent.test.js @@ -497,6 +497,42 @@ test('#setCustomContext()', function (t) { t.end() }) +test('#setGlobalLabel()', function (t) { + t.test('sets a global label', function (t) { + const agent = new Agent().start(agentOptsNoopTransport) + t.strictEqual(agent.setGlobalLabel('goo', 1), true) + t.deepEqual(agent._conf.globalLabels, Object.entries({ goo: 1 })) + agent.destroy() + t.end() + }) + + t.test('extends the predefined global labels', function (t) { + const agentOptsWithGlobalLabels = Object.assign( + {}, + agentOptsNoopTransport, + { globalLabels: { some: true } } + ) + const agent = new Agent().start(agentOptsWithGlobalLabels) + t.strictEqual(agent.setGlobalLabel('goo', 1), true) + t.deepEqual(agent._conf.globalLabels, Object.entries({ some: true, goo: 1 })) + agent.destroy() + t.end() + }) + + t.test('overrides an existing global label', function (t) { + const agentOptsWithGlobalLabels = Object.assign( + {}, + agentOptsNoopTransport, + { globalLabels: { some: true, goo: 0 } } + ) + const agent = new Agent().start(agentOptsWithGlobalLabels) + t.strictEqual(agent.setGlobalLabel('goo', 1), true) + t.deepEqual(agent._conf.globalLabels, Object.entries({ some: true, goo: 1 })) + agent.destroy() + t.end() + }) +}) + test('#setLabel()', function (t) { t.test('no active transaction', function (t) { const agent = new Agent().start(agentOptsNoopTransport) From b9d22c2b52d3e14438d799a8db4d3aaccb2b350c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Wed, 10 May 2023 13:12:00 +0200 Subject: [PATCH 2/5] Use alternative to `Object.fromEntries` --- lib/agent.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/agent.js b/lib/agent.js index d9b7df711cc..1c37835305d 100644 --- a/lib/agent.js +++ b/lib/agent.js @@ -362,7 +362,12 @@ Agent.prototype.setGlobalLabel = function (key, value) { } else { this._conf.globalLabels.push([key, value]) } - this._transport.config({ globalLabels: Object.fromEntries(this._conf.globalLabels) }) + this._transport.config({ + globalLabels: this._conf.globalLabels.reduce((acc, [k, v]) => { + acc[k] = v + return acc + }, {}) + }) return true } From e17ec7ab3e63343263166407d3de48785d08dc32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Wed, 17 May 2023 12:51:26 +0200 Subject: [PATCH 3/5] Update docs/agent-api.asciidoc Co-authored-by: Trent Mick --- docs/agent-api.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/agent-api.asciidoc b/docs/agent-api.asciidoc index 1bd60412a9b..ddcbed51340 100644 --- a/docs/agent-api.asciidoc +++ b/docs/agent-api.asciidoc @@ -294,7 +294,7 @@ Defining too many unique fields in an index is a condition that can lead to a [[apm-set-global-label]] ==== `apm.setGlobalLabel(name, value)` -[small]#Added in: v3.46.0# +[small]#Added in: REPLACEME# * `name` +{type-string}+ * `value` +{type-string}+ | +{type-number}+ | +{type-boolean}+ From a8dab7865d38bc952dc35fd2010e8c4e53c4438a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Wed, 17 May 2023 13:11:25 +0200 Subject: [PATCH 4/5] Address feedback --- index.d.ts | 2 +- lib/agent.js | 1 - test/agent.test.js | 68 ++++++++++++++++++++++++++++++++++++---------- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/index.d.ts b/index.d.ts index e2ecc452bea..b3614ca4233 100644 --- a/index.d.ts +++ b/index.d.ts @@ -116,7 +116,7 @@ declare namespace apm { currentSpan: Span | null; // Context - setGlobalLabel (name: string, value: LabelValue): boolean; + 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 1c37835305d..d11320becff 100644 --- a/lib/agent.js +++ b/lib/agent.js @@ -368,7 +368,6 @@ Agent.prototype.setGlobalLabel = function (key, value) { return acc }, {}) }) - return true } Agent.prototype.setLabel = function (key, value, stringify) { diff --git a/test/agent.test.js b/test/agent.test.js index 4f775c9ed39..03d6420be64 100644 --- a/test/agent.test.js +++ b/test/agent.test.js @@ -497,40 +497,78 @@ test('#setCustomContext()', function (t) { t.end() }) -test('#setGlobalLabel()', function (t) { - t.test('sets a global label', function (t) { - const agent = new Agent().start(agentOptsNoopTransport) - t.strictEqual(agent.setGlobalLabel('goo', 1), true) - t.deepEqual(agent._conf.globalLabels, Object.entries({ goo: 1 })) +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() }) - t.test('extends the predefined global labels', function (t) { + suite.test('extends the predefined global labels', async function (t) { + apmServer.clear() const agentOptsWithGlobalLabels = Object.assign( {}, - agentOptsNoopTransport, + suiteAgentOpts, { globalLabels: { some: true } } ) const agent = new Agent().start(agentOptsWithGlobalLabels) - t.strictEqual(agent.setGlobalLabel('goo', 1), true) - t.deepEqual(agent._conf.globalLabels, Object.entries({ some: true, goo: 1 })) + 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() }) - t.test('overrides an existing global label', function (t) { + suite.test('overrides an existing global label', async function (t) { + apmServer.clear() const agentOptsWithGlobalLabels = Object.assign( {}, - agentOptsNoopTransport, + suiteAgentOpts, { globalLabels: { some: true, goo: 0 } } ) const agent = new Agent().start(agentOptsWithGlobalLabels) - t.strictEqual(agent.setGlobalLabel('goo', 1), true) - t.deepEqual(agent._conf.globalLabels, Object.entries({ some: true, goo: 1 })) + 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) { @@ -1959,8 +1997,8 @@ test('patches', function (t) { agent.addPatch(moduleName, handler2) const modulePatches = agent._instrumentation._patches.get(moduleName) t.ok(modulePatches.length === 2 && - modulePatches[0] === handler1 && - modulePatches[1] === handler2, 'module patches are as expected') + modulePatches[0] === handler1 && + modulePatches[1] === handler2, 'module patches are as expected') agent.removePatch(moduleName) t.equal(agent._instrumentation._patches.get(moduleName).length, 2, From f7809dbdce0337f8288139dd09ab773c15aeb50a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Wed, 17 May 2023 15:27:12 +0200 Subject: [PATCH 5/5] Remove extra spaces --- test/agent.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/agent.test.js b/test/agent.test.js index fcce8750c88..4a1f85ec2e1 100644 --- a/test/agent.test.js +++ b/test/agent.test.js @@ -2002,8 +2002,8 @@ test('patches', function (t) { agent.addPatch(moduleName, handler2) const modulePatches = agent._instrumentation._patches.get(moduleName) t.ok(modulePatches.length === 2 && - modulePatches[0] === handler1 && - modulePatches[1] === handler2, 'module patches are as expected') + modulePatches[0] === handler1 && + modulePatches[1] === handler2, 'module patches are as expected') agent.removePatch(moduleName) t.equal(agent._instrumentation._patches.get(moduleName).length, 2,