Skip to content

Commit c076bb1

Browse files
authored
Add transaction_name_groups config (#2676)
1 parent 94ba232 commit c076bb1

File tree

8 files changed

+131
-12
lines changed

8 files changed

+131
-12
lines changed

CHANGELOG.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ endif::[]
2727
===== Features
2828
* Add support for Spring WebClient - {pull}2229[#2229]
2929
* Added undocumented and unsupported configuration `metric_set_limit` to increase the metric set limit - {pull}2148[#2148]
30+
* Added <<config-transaction-name-groups, `transaction_name_groups`>> configuration option.
31+
** Deprecated <<config-url-groups, `url_groups`>> in favor of <<config-transaction-name-groups, `transaction_name_groups`>>.
3032
3133
[float]
3234
===== Bug fixes

apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,22 @@ public String toString(Collection<String> value) {
714714
.dynamic(false)
715715
.buildWithDefault(false);
716716

717+
private final ConfigurationOption<List<WildcardMatcher>> transactionNameGroups = ConfigurationOption
718+
.builder(new org.stagemonitor.configuration.converter.ListValueConverter<>(new WildcardMatcherValueConverter()), List.class)
719+
.key("transaction_name_groups")
720+
.tags("added[1.33.0]")
721+
.configurationCategory(CORE_CATEGORY)
722+
.description("With this option,\n" +
723+
"you can group transaction names that contain dynamic parts with a wildcard expression.\n" +
724+
"For example,\n" +
725+
"the pattern `GET /user/*/cart` would consolidate transactions,\n" +
726+
"such as `GET /users/42/cart` and `GET /users/73/cart` into a single transaction name `GET /users/*/cart`,\n" +
727+
"hence reducing the transaction name cardinality.\n" +
728+
"\n" +
729+
WildcardMatcher.DOCUMENTATION)
730+
.dynamic(true)
731+
.buildWithDefault(Collections.<WildcardMatcher>emptyList());
732+
717733
public boolean isEnabled() {
718734
return enabled.get();
719735
}
@@ -943,6 +959,10 @@ public boolean isEnablePublicApiAnnotationInheritance() {
943959
return enablePublicApiAnnotationInheritance.get();
944960
}
945961

962+
public List<WildcardMatcher> getTransactionNameGroups() {
963+
return transactionNameGroups.get();
964+
}
965+
946966
public enum EventType {
947967
/**
948968
* Request bodies will never be reported

apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/web/WebConfiguration.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,15 +103,18 @@ public class WebConfiguration extends ConfigurationOptionProvider {
103103
"WARNING: If your URLs contain path parameters like `/user/$userId`,\n" +
104104
"you should be very careful when enabling this flag,\n" +
105105
"as it can lead to an explosion of transaction groups.\n" +
106-
"Take a look at the `url_groups` option on how to mitigate this problem by grouping URLs together.")
106+
"Take a look at the <<config-transaction-name-groups,`transaction_name_groups`>> option on how to mitigate this problem by grouping URLs together.")
107107
.dynamic(true)
108108
.buildWithDefault(false);
109109

110110
private final ConfigurationOption<List<WildcardMatcher>> urlGroups = ConfigurationOption
111111
.builder(new ListValueConverter<>(new WildcardMatcherValueConverter()), List.class)
112112
.key("url_groups")
113+
.tags("deprecated")
113114
.configurationCategory(HTTP_CATEGORY)
114-
.description("This option is only considered, when `use_path_as_transaction_name` is active.\n" +
115+
.description("Deprecated in favor of <<config-transaction-name-groups,`transaction_name_groups`>>.\n" +
116+
"\n" +
117+
"This option is only considered, when `use_path_as_transaction_name` is active.\n" +
115118
"\n" +
116119
"With this option, you can group several URL paths together by using a wildcard expression like `/user/*`.\n" +
117120
"\n" +

apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/AbstractSpan.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ public void updateName(Class<?> clazz, String methodName) {
306306
* @return name
307307
*/
308308
public String getNameAsString() {
309-
return name.toString();
309+
return getNameForSerialization().toString();
310310
}
311311

312312
/**

apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/Transaction.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import co.elastic.apm.agent.impl.context.TransactionContext;
2626
import co.elastic.apm.agent.impl.context.web.ResultUtil;
2727
import co.elastic.apm.agent.impl.sampling.Sampler;
28+
import co.elastic.apm.agent.matcher.WildcardMatcher;
2829
import co.elastic.apm.agent.metrics.Labels;
2930
import co.elastic.apm.agent.metrics.MetricRegistry;
3031
import co.elastic.apm.agent.metrics.Timer;
@@ -67,6 +68,8 @@ protected Labels.Mutable initialValue() {
6768
*/
6869
private final KeyListConcurrentHashMap<String, KeyListConcurrentHashMap<String, Timer>> timerBySpanTypeAndSubtype = new KeyListConcurrentHashMap<>();
6970
private final WriterReaderPhaser phaser = new WriterReaderPhaser();
71+
private final CoreConfiguration coreConfig;
72+
private final SpanConfiguration spanConfig;
7073

7174
/**
7275
* The result of the transaction. HTTP status code for HTTP-related
@@ -110,6 +113,8 @@ public Transaction getTransaction() {
110113

111114
public Transaction(ElasticApmTracer tracer) {
112115
super(tracer);
116+
coreConfig = tracer.getConfig(CoreConfiguration.class);
117+
spanConfig = tracer.getConfig(SpanConfiguration.class);
113118
}
114119

115120
public <T> Transaction start(TraceContext.ChildContextCreator<T> childContextCreator, @Nullable T parent, long epochMicros, Sampler sampler) {
@@ -125,10 +130,10 @@ public <T, A> Transaction start(TraceContext.ChildContextCreatorTwoArg<T, A> chi
125130
}
126131

127132
private void onTransactionStart(boolean startedAsChild, long epochMicros, Sampler sampler) {
128-
maxSpans = tracer.getConfig(CoreConfiguration.class).getTransactionMaxSpans();
129-
spanCompressionEnabled = tracer.getConfig(SpanConfiguration.class).isSpanCompressionEnabled();
130-
spanCompressionExactMatchMaxDurationUs = tracer.getConfig(SpanConfiguration.class).getSpanCompressionExactMatchMaxDuration().getMicros();
131-
spanCompressionSameKindMaxDurationUs = tracer.getConfig(SpanConfiguration.class).getSpanCompressionSameKindMaxDuration().getMicros();
133+
maxSpans = coreConfig.getTransactionMaxSpans();
134+
spanCompressionEnabled = spanConfig.isSpanCompressionEnabled();
135+
spanCompressionExactMatchMaxDurationUs = spanConfig.getSpanCompressionExactMatchMaxDuration().getMicros();
136+
spanCompressionSameKindMaxDurationUs = spanConfig.getSpanCompressionSameKindMaxDuration().getMicros();
132137
if (!startedAsChild) {
133138
traceContext.asRootSpan(sampler);
134139
}
@@ -374,6 +379,17 @@ public long getSpanCompressionSameKindMaxDurationUs() {
374379
return spanCompressionSameKindMaxDurationUs;
375380
}
376381

382+
@Override
383+
public StringBuilder getNameForSerialization() {
384+
StringBuilder name = this.name;
385+
WildcardMatcher match = WildcardMatcher.anyMatch(coreConfig.getTransactionNameGroups(), name);
386+
if (match != null) {
387+
name.setLength(0);
388+
name.append(match);
389+
}
390+
return name;
391+
}
392+
377393
@Override
378394
protected Transaction thiz() {
379395
return this;

apm-agent-core/src/main/java/co/elastic/apm/agent/matcher/WildcardMatcher.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ public static boolean isNoneMatch(List<WildcardMatcher> matchers, @Nullable Char
164164
*/
165165
@Nullable
166166
public static WildcardMatcher anyMatch(List<WildcardMatcher> matchers, @Nullable CharSequence s) {
167-
if (s == null) {
167+
if (s == null || matchers.isEmpty()) {
168168
return null;
169169
}
170170
return anyMatch(matchers, s, null);

apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@
3232
import co.elastic.apm.agent.impl.transaction.TraceContext;
3333
import co.elastic.apm.agent.impl.transaction.Transaction;
3434
import co.elastic.apm.agent.matcher.WildcardMatcher;
35+
import co.elastic.apm.agent.metrics.Labels;
3536
import co.elastic.apm.agent.objectpool.TestObjectPoolFactory;
3637
import co.elastic.apm.agent.report.ApmServerClient;
3738
import co.elastic.apm.agent.report.ReporterConfiguration;
3839
import org.junit.jupiter.api.AfterEach;
3940
import org.junit.jupiter.api.BeforeEach;
4041
import org.junit.jupiter.api.Test;
41-
import org.mockito.Mockito;
4242
import org.stagemonitor.configuration.ConfigurationRegistry;
4343

4444
import javax.annotation.Nullable;
@@ -224,6 +224,28 @@ void testDoesNotRecordIgnoredExceptions() {
224224
assertThat(reporter.getErrors()).isEmpty();
225225
}
226226

227+
@Test
228+
void testTransactionNameGrouping() {
229+
when(config.getConfig(CoreConfiguration.class).getTransactionNameGroups())
230+
.thenReturn(List.of(WildcardMatcher.valueOf("GET /foo/*/bar")));
231+
232+
Transaction transaction = tracerImpl.startRootTransaction(null).appendToName("GET ").appendToName("/foo/42/bar");
233+
try (Scope scope = transaction.activateInScope()) {
234+
transaction.captureException(new RuntimeException("Test error capturing"));
235+
}
236+
transaction.end();
237+
assertThat(reporter.getFirstTransaction().getNameAsString()).isEqualTo("GET /foo/*/bar");
238+
assertThat(reporter.getFirstError().getTransactionInfo().getName().toString()).isEqualTo("GET /foo/*/bar");
239+
tracerImpl.getMetricRegistry().flipPhaseAndReport(metricSets -> {
240+
assertThat(metricSets.get(Labels.Mutable.of()
241+
.transactionName("GET /foo/*/bar")
242+
.transactionType("custom")
243+
.spanType("app")))
244+
.isNotNull();
245+
});
246+
247+
}
248+
227249
private static class DummyException1 extends Exception {
228250
DummyException1() {
229251
}

docs/configuration.asciidoc

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ Click on a key to get more information.
131131
** <<config-span-min-duration>>
132132
** <<config-cloud-provider>>
133133
** <<config-enable-public-api-annotation-inheritance>>
134+
** <<config-transaction-name-groups>>
134135
* <<config-http>>
135136
** <<config-capture-body-content-types>>
136137
** <<config-transaction-ignore-urls>>
@@ -1371,6 +1372,39 @@ A boolean specifying if the agent should search the class hierarchy for public a
13711372
| `elastic.apm.enable_public_api_annotation_inheritance` | `enable_public_api_annotation_inheritance` | `ELASTIC_APM_ENABLE_PUBLIC_API_ANNOTATION_INHERITANCE`
13721373
|============
13731374

1375+
// This file is auto generated. Please make your changes in *Configuration.java (for example CoreConfiguration.java) and execute ConfigurationExporter
1376+
[float]
1377+
[[config-transaction-name-groups]]
1378+
==== `transaction_name_groups` (added[1.33.0])
1379+
1380+
With this option,
1381+
you can group transaction names that contain dynamic parts with a wildcard expression.
1382+
For example,
1383+
the pattern `GET /user/*/cart` would consolidate transactions,
1384+
such as `GET /users/42/cart` and `GET /users/73/cart` into a single transaction name `GET /users/*/cart`,
1385+
hence reducing the transaction name cardinality.
1386+
1387+
This option supports the wildcard `*`, which matches zero or more characters.
1388+
Examples: `/foo/*/bar/*/baz*`, `*foo*`.
1389+
Matching is case insensitive by default.
1390+
Prepending an element with `(?-i)` makes the matching case sensitive.
1391+
1392+
<<configuration-dynamic, image:./images/dynamic-config.svg[] >>
1393+
1394+
1395+
[options="header"]
1396+
|============
1397+
| Default | Type | Dynamic
1398+
| `<none>` | List | true
1399+
|============
1400+
1401+
1402+
[options="header"]
1403+
|============
1404+
| Java System Properties | Property file | Environment
1405+
| `elastic.apm.transaction_name_groups` | `transaction_name_groups` | `ELASTIC_APM_TRANSACTION_NAME_GROUPS`
1406+
|============
1407+
13741408
[[config-http]]
13751409
=== HTTP configuration options
13761410
// This file is auto generated. Please make your changes in *Configuration.java (for example CoreConfiguration.java) and execute ConfigurationExporter
@@ -1477,7 +1511,7 @@ transaction names of unsupported or partially-supported frameworks will be in th
14771511
WARNING: If your URLs contain path parameters like `/user/$userId`,
14781512
you should be very careful when enabling this flag,
14791513
as it can lead to an explosion of transaction groups.
1480-
Take a look at the `url_groups` option on how to mitigate this problem by grouping URLs together.
1514+
Take a look at the <<config-transaction-name-groups,`transaction_name_groups`>> option on how to mitigate this problem by grouping URLs together.
14811515

14821516
<<configuration-dynamic, image:./images/dynamic-config.svg[] >>
14831517

@@ -1498,7 +1532,9 @@ Take a look at the `url_groups` option on how to mitigate this problem by groupi
14981532
// This file is auto generated. Please make your changes in *Configuration.java (for example CoreConfiguration.java) and execute ConfigurationExporter
14991533
[float]
15001534
[[config-url-groups]]
1501-
==== `url_groups`
1535+
==== `url_groups` (deprecated)
1536+
1537+
Deprecated in favor of <<config-transaction-name-groups,`transaction_name_groups`>>.
15021538

15031539
This option is only considered, when `use_path_as_transaction_name` is active.
15041540

@@ -3476,6 +3512,24 @@ Example: `5ms`.
34763512
#
34773513
# enable_public_api_annotation_inheritance=false
34783514
3515+
# With this option,
3516+
# you can group transaction names that contain dynamic parts with a wildcard expression.
3517+
# For example,
3518+
# the pattern `GET /user/*/cart` would consolidate transactions,
3519+
# such as `GET /users/42/cart` and `GET /users/73/cart` into a single transaction name `GET /users/*/cart`,
3520+
# hence reducing the transaction name cardinality.
3521+
#
3522+
# This option supports the wildcard `*`, which matches zero or more characters.
3523+
# Examples: `/foo/*/bar/*/baz*`, `*foo*`.
3524+
# Matching is case insensitive by default.
3525+
# Prepending an element with `(?-i)` makes the matching case sensitive.
3526+
#
3527+
# This setting can be changed at runtime
3528+
# Type: comma separated list
3529+
# Default value:
3530+
#
3531+
# transaction_name_groups=
3532+
34793533
############################################
34803534
# HTTP #
34813535
############################################
@@ -3534,14 +3588,16 @@ Example: `5ms`.
35343588
# WARNING: If your URLs contain path parameters like `/user/$userId`,
35353589
# you should be very careful when enabling this flag,
35363590
# as it can lead to an explosion of transaction groups.
3537-
# Take a look at the `url_groups` option on how to mitigate this problem by grouping URLs together.
3591+
# Take a look at the <<config-transaction-name-groups,`transaction_name_groups`>> option on how to mitigate this problem by grouping URLs together.
35383592
#
35393593
# This setting can be changed at runtime
35403594
# Type: Boolean
35413595
# Default value: false
35423596
#
35433597
# use_path_as_transaction_name=false
35443598
3599+
# Deprecated in favor of <<config-transaction-name-groups,`transaction_name_groups`>>.
3600+
#
35453601
# This option is only considered, when `use_path_as_transaction_name` is active.
35463602
#
35473603
# With this option, you can group several URL paths together by using a wildcard expression like `/user/*`.

0 commit comments

Comments
 (0)