Skip to content

Commit 4f7684c

Browse files
authored
refactor: Updated Samplers class to normalize logger messages, short circuit when applicable and remove optional chaining checks (#3546)
1 parent 02268a4 commit 4f7684c

File tree

6 files changed

+171
-72
lines changed

6 files changed

+171
-72
lines changed

lib/samplers/index.js

Lines changed: 74 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,20 @@ class Samplers {
4444
* @param {Transaction} params.transaction The transaction to apply the sampling decision to.
4545
*/
4646
applyDefaultDecision({ transaction }) {
47-
if (!transaction) {
48-
logger.trace('Both full and partial granularity samplers are disabled. No transaction provided to apply default sampling decision.')
49-
return
50-
}
51-
52-
logger.trace('Both full and partial granularity samplers are disabled. Applying default sampling decision of not sampled and priority 0 for transaction %s', transaction?.id)
47+
logger.trace('Both full and partial granularity samplers are disabled. Applying default sampling decision of not sampled and priority 0 for transaction %s', transaction.id)
5348
transaction.sampled = false
5449
transaction.priority = 0
5550
}
5651

52+
/**
53+
* Determines if the partial granularity sampler should run based on the transaction's sampled value and if partial granularity is enabled.
54+
* @param {Transaction} transaction The transaction to check.
55+
* @returns {boolean} True if the partial granularity sampler should run, false otherwise.
56+
*/
57+
shouldRunPartialSampling(transaction) {
58+
return transaction.sampled !== true && this.partialEnabled
59+
}
60+
5761
/**
5862
* Applies the root sampler's sampling decision to the transaction
5963
* if priority has not already been set. If both full and partial granularity
@@ -62,20 +66,29 @@ class Samplers {
6266
* @param {Transaction} transaction The transaction to apply the sampling decision to.
6367
*/
6468
applySamplingDecision({ transaction }) {
65-
if (transaction?.priority === null) {
66-
if (this.fullEnabled === false && this.partialEnabled === false) {
67-
this.applyDefaultDecision({ transaction })
68-
return
69-
}
69+
if (!transaction) {
70+
logger.trace('No transaction provided to applySamplingDecision, not applying sampler.')
71+
return
72+
}
7073

71-
if (this.fullEnabled) {
72-
this.root.applySamplingDecision({ transaction, isFullTrace: true })
73-
logger.trace('Ran full granularity %s sampler for transaction %s, decision: { sampled: %s, priority: %n}', this.root.toString(), transaction?.id, transaction?.sampled, transaction?.priority)
74-
}
75-
if (!transaction?.sampled && this.partialEnabled) {
76-
this.partialRoot.applySamplingDecision({ transaction })
77-
logger.trace('Ran partial granularity %s sampler for transaction %s, decision: { sampled: %s, priority: %n}', this.partialRoot.toString(), transaction?.id, transaction?.sampled, transaction?.priority)
78-
}
74+
if (transaction.priority !== null) {
75+
logger.trace('Transaction %s already has a sampling decision, not applying sampler.', transaction.id)
76+
return
77+
}
78+
79+
if (this.fullEnabled === false && this.partialEnabled === false) {
80+
this.applyDefaultDecision({ transaction })
81+
return
82+
}
83+
84+
if (this.fullEnabled) {
85+
this.root.applySamplingDecision({ transaction, isFullTrace: true })
86+
logger.trace('Ran full granularity applySamplingDecision %s sampler for transaction %s, decision: { sampled: %s, priority: %n}', this.root.toString(), transaction.id, transaction.sampled, transaction.priority)
87+
}
88+
89+
if (this.shouldRunPartialSampling(transaction)) {
90+
this.partialRoot.applySamplingDecision({ transaction })
91+
logger.trace('Ran partial granularity applySamplingDecision %s sampler for transaction %s, decision: { sampled: %s, priority: %n}', this.partialRoot.toString(), transaction.id, transaction.sampled, transaction.priority)
7992
}
8093
}
8194

@@ -90,30 +103,26 @@ class Samplers {
90103
* @param {TraceState} params.tracestate The W3C tracestate object.
91104
*/
92105
applyDTSamplingDecision({ transaction, traceparent, tracestate }) {
106+
if (!transaction) {
107+
logger.trace('No transaction provided to applyDTSamplingDecision, not applying sampler.')
108+
return
109+
}
110+
93111
if (this.fullEnabled === false && this.partialEnabled === false) {
94112
this.applyDefaultDecision({ transaction })
95113
return
96114
}
97115

98-
// Decide sampling from w3c data by supplying tracestate to sampler
99-
if (traceparent?.isSampled === true) {
100-
if (this.fullEnabled) {
101-
this.remoteParentSampled.applySamplingDecision({ transaction, tracestate, isFullTrace: true })
102-
logger.trace('Ran DT Sampling full granularity %s sampler for transaction %s, decision: { sampled: %s, priority: %n}', this.remoteParentSampled.toString(), transaction?.id, transaction?.sampled, transaction?.priority)
103-
}
104-
if (!transaction?.sampled && this.partialEnabled) {
105-
this.partialRemoteParentSampled.applySamplingDecision({ transaction, tracestate })
106-
logger.trace('Ran DT Sampling partial granularity %s sampler for transaction %s, decision: { sampled: %s, priority: %n}', this.partialRemoteParentSampled.toString(), transaction?.id, transaction?.sampled, transaction?.priority)
107-
}
108-
} else if (traceparent?.isSampled === false) {
109-
if (this.fullEnabled) {
110-
this.remoteParentNotSampled.applySamplingDecision({ transaction, tracestate, isFullTrace: true })
111-
logger.trace('Ran DT Sampling full granularity %s sampler for transaction %s, decision: { sampled: %s, priority: %n}', this.remoteParentNotSampled.toString(), transaction?.id, transaction?.sampled, transaction?.priority)
112-
}
113-
if (!transaction?.sampled && this.partialEnabled) {
114-
this.partialRemoteParentNotSampled.applySamplingDecision({ transaction, tracestate })
115-
logger.trace('Ran DT Sampling partial granularity %s sampler for transaction %s, decision: { sampled: %s, priority: %n}', this.partialRemoteParentNotSampled.toString(), transaction?.id, transaction?.sampled, transaction?.priority)
116-
}
116+
if (this.fullEnabled) {
117+
const sampler = traceparent?.isSampled ? this.remoteParentSampled : this.remoteParentNotSampled
118+
sampler.applySamplingDecision({ transaction, tracestate, isFullTrace: true })
119+
logger.trace('Ran full granularity applyDTSamplingDecision %s sampler for transaction %s, decision: { sampled: %s, priority: %n}', sampler.toString(), transaction.id, transaction.sampled, transaction.priority)
120+
}
121+
122+
if (this.shouldRunPartialSampling(transaction)) {
123+
const partialSampler = traceparent?.isSampled ? this.partialRemoteParentSampled : this.partialRemoteParentNotSampled
124+
partialSampler.applySamplingDecision({ transaction, tracestate })
125+
logger.trace('Ran partial granularity applyDTSamplingDecision %s sampler for transaction %s, decision: { sampled: %s, priority: %n}', partialSampler.toString(), transaction.id, transaction.sampled, transaction.priority)
117126
}
118127
}
119128

@@ -130,28 +139,33 @@ class Samplers {
130139
* @param {boolean} params.isSampled The sampled value from the legacy New Relic headers.
131140
*/
132141
applyLegacyDTSamplingDecision({ transaction, isSampled }) {
142+
if (!transaction) {
143+
logger.trace('No transaction provided to applyLegacyDTSamplingDecision, not applying sampler.')
144+
return
145+
}
146+
133147
if (this.fullEnabled === false && this.partialEnabled === false) {
134148
this.applyDefaultDecision({ transaction })
135149
return
136150
}
137151

138-
const sampler = isSampled ? this.remoteParentSampled : this.remoteParentNotSampled
139-
const partialSampler = isSampled ? this.partialRemoteParentSampled : this.partialRemoteParentNotSampled
140152
if (this.fullEnabled) {
141-
if (sampler?.toString() === 'AdaptiveSampler') {
142-
logger.trace('Not applying full granularity sampling decision from legacy DT headers for transaction %s because sampler is AdaptiveSampler', transaction?.id)
153+
const sampler = isSampled ? this.remoteParentSampled : this.remoteParentNotSampled
154+
if (sampler.toString() === 'AdaptiveSampler') {
155+
logger.trace('Not running full granularity applyLegacyDTSamplingDecision for transaction %s because sampler is AdaptiveSampler', transaction.id)
143156
} else {
144157
sampler.applySamplingDecision({ transaction, isFullTrace: true })
145-
logger.trace('Ran Legacy DT Sampling full granularity %s sampler for transaction %s, decision: { sampled: %s, priority: %n}', sampler.toString(), transaction?.id, transaction?.sampled, transaction?.priority)
158+
logger.trace('Ran full granularity applyLegacyDTSamplingDecision %s sampler for transaction %s, decision: { sampled: %s, priority: %n}', sampler.toString(), transaction.id, transaction.sampled, transaction.priority)
146159
}
147160
}
148161

149-
if (!transaction.sampled && this.partialEnabled) {
150-
if (partialSampler?.toString() === 'AdaptiveSampler') {
151-
logger.trace('Not applying partial granularity sampling decision from legacy DT headers for transaction %s because sampler is AdaptiveSampler', transaction?.id)
162+
if (this.shouldRunPartialSampling(transaction)) {
163+
const partialSampler = isSampled ? this.partialRemoteParentSampled : this.partialRemoteParentNotSampled
164+
if (partialSampler.toString() === 'AdaptiveSampler') {
165+
logger.trace('Not running partial granularity applyLegacyDTSamplingDecision for transaction %s because sampler is AdaptiveSampler', transaction.id)
152166
} else {
153167
partialSampler.applySamplingDecision({ transaction })
154-
logger.trace('Ran Legacy DT Sampling partial granularity %s sampler for transaction %s, decision: { sampled: %s, priority: %n}', partialSampler.toString(), transaction?.id, transaction?.sampled, transaction?.priority)
168+
logger.trace('Ran partial granularity applyLegacyDTSamplingDecision %s sampler for transaction %s, decision: { sampled: %s, priority: %n}', partialSampler.toString(), transaction.id, transaction.sampled, transaction.priority)
155169
}
156170
}
157171
}
@@ -182,15 +196,17 @@ class Samplers {
182196
* @returns {AdaptiveSampler} The global AdaptiveSampler instance.
183197
*/
184198
getAdaptiveSampler(agent) {
185-
const config = agent.config
186-
if (!this.adaptiveSampler) {
187-
this.adaptiveSampler = new AdaptiveSampler({
188-
agent,
189-
serverless: config.serverless_mode.enabled,
190-
period: config.sampling_target_period_in_seconds * 1000,
191-
target: config.sampling_target
192-
})
199+
if (this.adaptiveSampler !== null) {
200+
return this.adaptiveSampler
193201
}
202+
203+
const config = agent.config
204+
this.adaptiveSampler = new AdaptiveSampler({
205+
agent,
206+
serverless: config.serverless_mode.enabled,
207+
period: config.sampling_target_period_in_seconds * 1000,
208+
target: config.sampling_target
209+
})
194210
return this.adaptiveSampler
195211
}
196212

@@ -206,9 +222,9 @@ class Samplers {
206222
const config = agent.config
207223
let samplerDefinition = null
208224
if (isPartial) {
209-
samplerDefinition = config?.distributed_tracing?.sampler?.partial_granularity?.[sampler]
225+
samplerDefinition = config.distributed_tracing.sampler.partial_granularity[sampler]
210226
} else {
211-
samplerDefinition = config?.distributed_tracing?.sampler?.[sampler]
227+
samplerDefinition = config.distributed_tracing.sampler[sampler]
212228
}
213229

214230
// Always on?
@@ -225,7 +241,7 @@ class Samplers {
225241
if (samplerDefinition?.trace_id_ratio_based) {
226242
let ratio = samplerDefinition.trace_id_ratio_based?.ratio
227243
// If both partial and full granularity for a particular section are both set to trace ratio, agent **MUST** set the partial granularity ratio = full granularity ratio + partial granularity ratio
228-
if (isPartial && this.fullEnabled && config?.distributed_tracing?.sampler?.[sampler]?.trace_id_ratio_based?.ratio) {
244+
if (isPartial && this.fullEnabled && config.distributed_tracing.sampler[sampler]?.trace_id_ratio_based?.ratio) {
229245
ratio += config.distributed_tracing.sampler[sampler].trace_id_ratio_based.ratio
230246
}
231247
return new TraceIdRatioBasedSampler({

lib/samplers/sampler.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Copyright 2025 New Relic Corporation. All rights reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
5+
const util = require('node:util')
56

67
/**
78
* @private
@@ -20,7 +21,8 @@ class Sampler {
2021
* @param {boolean} params.isFullTrace whether or not full tracing is enabled
2122
*/
2223
applySamplingDecision({ transaction, tracestate, isFullTrace }) {
23-
throw new Error('must implement applySamplingDecision, arguments are: { transaction: %d, tracestate: %s, isFullTrace: %s}', transaction?.id, tracestate, isFullTrace)
24+
const formattedError = util.format('must implement applySamplingDecision, arguments are: { transaction: %d, tracestate: %s, isFullTrace: %s}', transaction?.id, tracestate, isFullTrace)
25+
throw new Error(formattedError)
2426
}
2527
}
2628

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2025 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
const test = require('node:test')
8+
const assert = require('node:assert')
9+
const AlwaysOffSampler = require('#agentlib/samplers/always-off-sampler.js')
10+
11+
test.beforeEach((ctx) => {
12+
const sampler = new AlwaysOffSampler()
13+
ctx.nr = { sampler }
14+
})
15+
16+
test('AlwaysOffSampler should always sample', (t) => {
17+
const { sampler } = t.nr
18+
const transaction = {}
19+
sampler.applySamplingDecision({ transaction, isFullTrace: true })
20+
assert.equal(transaction.sampled, false)
21+
assert.equal(transaction.priority, 0)
22+
assert.equal(transaction.isPartialTrace, false)
23+
})
24+
25+
test('AlwaysOffSampler should assign isPartialTrace to true when not a fullTrace', (t) => {
26+
const { sampler } = t.nr
27+
const transaction = {}
28+
sampler.applySamplingDecision({ transaction, isFullTrace: false })
29+
assert.equal(transaction.sampled, false)
30+
assert.equal(transaction.priority, 0)
31+
assert.equal(transaction.isPartialTrace, true)
32+
})
33+
34+
test('AlwaysOffSampler should return null when transaction is not provided', (t) => {
35+
const { sampler } = t.nr
36+
assert.doesNotThrow(() => {
37+
sampler.applySamplingDecision({ transaction: null, isFullTrace: true })
38+
})
39+
})
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2025 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
const test = require('node:test')
8+
const assert = require('node:assert')
9+
const AlwaysOnSampler = require('#agentlib/samplers/always-on-sampler.js')
10+
11+
test.beforeEach((ctx) => {
12+
const sampler = new AlwaysOnSampler()
13+
ctx.nr = { sampler }
14+
})
15+
16+
test('AlwaysOnSampler should always sample', (t) => {
17+
const { sampler } = t.nr
18+
const transaction = {}
19+
sampler.applySamplingDecision({ transaction, isFullTrace: true })
20+
assert.equal(transaction.sampled, true)
21+
assert.equal(transaction.priority, 2)
22+
assert.equal(transaction.isPartialTrace, false)
23+
})
24+
25+
test('AlwaysOnSampler should assign isPartialTrace to true when not a fullTrace', (t) => {
26+
const { sampler } = t.nr
27+
const transaction = {}
28+
sampler.applySamplingDecision({ transaction, isFullTrace: false })
29+
assert.equal(transaction.sampled, true)
30+
assert.equal(transaction.priority, 2)
31+
assert.equal(transaction.isPartialTrace, true)
32+
})
33+
34+
test('AlwaysOnSampler should return null when transaction is not provided', (t) => {
35+
const { sampler } = t.nr
36+
assert.doesNotThrow(() => {
37+
sampler.applySamplingDecision({ transaction: null, isFullTrace: true })
38+
})
39+
})

test/unit/samplers/index.test.js

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -540,19 +540,6 @@ test('applyDTSamplingDecision', async (t) => {
540540
})
541541
})
542542

543-
await t.test('should not apply decision when traceparent.isSampled is undefined', (t) => {
544-
const samplers = new Samplers(t.nr.agent)
545-
546-
const transaction = { priority: null, sampled: null }
547-
const traceparent = { isSampled: undefined }
548-
const tracestate = null
549-
550-
samplers.applyDTSamplingDecision({ transaction, traceparent, tracestate })
551-
552-
assert.equal(transaction.priority, null)
553-
assert.equal(transaction.sampled, null)
554-
})
555-
556543
await t.test('should handle tracestate without intrinsics', (t) => {
557544
t.nr.agent.config = new Config({
558545
distributed_tracing: {

test/unit/samplers/sampler.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright 2025 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
const test = require('node:test')
8+
const assert = require('node:assert')
9+
const Sampler = require('#agentlib/samplers/sampler.js')
10+
11+
test('should throw error if applySamplingDecision is not implemented', () => {
12+
const sampler = new Sampler()
13+
assert.throws(() => {
14+
sampler.applySamplingDecision({ transaction: { id: 1 }, tracestate: 'tracestate', isFullTrace: true })
15+
}, /^Error: must implement applySamplingDecision, arguments are: { transaction: 1, tracestate: tracestate, isFullTrace: true/)
16+
})

0 commit comments

Comments
 (0)