Skip to content

Commit 7acce6d

Browse files
committed
Change StackElementFilter into an interface and add a Builder for common filters
These common filters are in preparation for introducing "stackhash" and the desire to make that easy to configure and perform well.
1 parent 1496c0b commit 7acce6d

File tree

3 files changed

+313
-32
lines changed

3 files changed

+313
-32
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package io.avaje.logback.encoder;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.regex.Pattern;
6+
7+
final class FilterBuilder implements StackElementFilter.Builder {
8+
9+
private final List<StackElementFilter> filters = new ArrayList<>();
10+
11+
@Override
12+
public StackElementFilter.Builder generated() {
13+
filters.add(new Generated());
14+
return this;
15+
}
16+
17+
@Override
18+
public StackElementFilter.Builder reflectiveInvocation() {
19+
filters.add(new ReflectiveInvocation());
20+
return this;
21+
}
22+
23+
@Override
24+
public StackElementFilter.Builder jdkInternals() {
25+
filters.add(new JDKInternals());
26+
return this;
27+
}
28+
29+
@Override
30+
public StackElementFilter.Builder spring() {
31+
filters.add(new SpringFilter());
32+
return this;
33+
}
34+
35+
@Override
36+
public StackElementFilter.Builder byPattern(List<Pattern> excludes) {
37+
if (excludes != null && !excludes.isEmpty()) {
38+
filters.add(new PatternFilter(excludes));
39+
}
40+
return this;
41+
}
42+
43+
@Override
44+
public StackElementFilter build() {
45+
if (filters.isEmpty()) {
46+
return StackElementFilter.any();
47+
}
48+
return new Group(filters.toArray(new StackElementFilter[0]));
49+
}
50+
51+
private static final class Generated implements StackElementFilter {
52+
53+
@Override
54+
public boolean accept(StackTraceElement element) {
55+
String className = element.getClassName();
56+
return !className.contains("$$FastClassByCGLIB$$")
57+
&& !className.contains("$$EnhancerBySpringCGLIB$$");
58+
}
59+
}
60+
61+
private static final class ReflectiveInvocation implements StackElementFilter {
62+
63+
@Override
64+
public boolean accept(StackTraceElement element) {
65+
String methodName = element.getMethodName();
66+
if (methodName.equals("invoke")) {
67+
String className = element.getClassName();
68+
return !className.startsWith("sun.reflect.")
69+
&& !className.startsWith("java.lang.reflect.")
70+
&& !className.startsWith("net.sf.cglib.proxy.MethodProxy");
71+
}
72+
return true;
73+
}
74+
}
75+
76+
private static final class JDKInternals implements StackElementFilter {
77+
78+
@Override
79+
public boolean accept(StackTraceElement element) {
80+
String className = element.getClassName();
81+
return !className.startsWith("com.sun.")
82+
&& !className.startsWith("sun.net.");
83+
}
84+
}
85+
86+
private static final class SpringFilter implements StackElementFilter {
87+
88+
private static final String[] MATCHES = {
89+
"org.springframework.cglib.",
90+
"org.springframework.transaction.",
91+
"org.springframework.validation.",
92+
"org.springframework.app.",
93+
"org.springframework.aop.",
94+
"org.springframework.ws.",
95+
"org.springframework.web.",
96+
"org.springframework.transaction"
97+
};
98+
99+
@Override
100+
public boolean accept(StackTraceElement element) {
101+
String className = element.getClassName();
102+
if (className.startsWith("org.springframework")) {
103+
for (String match : MATCHES) {
104+
if (className.startsWith(match)) {
105+
return false;
106+
}
107+
}
108+
return true;
109+
}
110+
if (className.startsWith("org.apache")) {
111+
return !className.startsWith("org.apache.tomcat.")
112+
&& !className.startsWith("org.apache.catalina.")
113+
&& !className.startsWith("org.apache.coyote.");
114+
}
115+
return true;
116+
}
117+
}
118+
119+
private static final class PatternFilter implements StackElementFilter {
120+
121+
private final Pattern[] excludes;
122+
123+
PatternFilter(final List<Pattern> excludes) {
124+
this.excludes = excludes.toArray(new Pattern[0]);
125+
}
126+
127+
@Override
128+
public boolean accept(StackTraceElement element) {
129+
final String classNameAndMethod = element.getClassName() + "." + element.getMethodName();
130+
for (final Pattern exclusionPattern : excludes) {
131+
if (exclusionPattern.matcher(classNameAndMethod).find()) {
132+
return false;
133+
}
134+
}
135+
return true;
136+
}
137+
}
138+
139+
private static final class Group implements StackElementFilter {
140+
141+
private final StackElementFilter[] filters;
142+
143+
public Group(StackElementFilter[] filters) {
144+
this.filters = filters;
145+
}
146+
147+
@Override
148+
public boolean accept(StackTraceElement element) {
149+
for (StackElementFilter filter : filters) {
150+
if (!filter.accept(element)) {
151+
return false;
152+
}
153+
}
154+
return true;
155+
}
156+
}
157+
158+
}

src/main/java/io/avaje/logback/encoder/StackElementFilter.java

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,31 @@
2222
* Component in charge of accepting or rejecting {@link StackTraceElement elements} when computing a
2323
* stack trace hash
2424
*/
25-
public abstract class StackElementFilter {
25+
public interface StackElementFilter {
26+
27+
/**
28+
* Return a Builder for common stack element filters.
29+
*/
30+
static Builder builder() {
31+
return new FilterBuilder();
32+
}
33+
2634
/**
27-
* Tests whether or not the specified {@link StackTraceElement} should be accepted when computing
35+
* Tests whether the specified {@link StackTraceElement} should be accepted when computing
2836
* a stack hash.
2937
*
3038
* @param element The {@link StackTraceElement} to be tested
3139
* @return {@code true} if and only if {@code element} should be accepted
3240
*/
33-
public abstract boolean accept(StackTraceElement element);
41+
boolean accept(StackTraceElement element);
3442

3543
/**
3644
* Creates a {@link StackElementFilter} that accepts any stack trace elements
3745
*
3846
* @return the filter
3947
*/
40-
public static StackElementFilter any() {
41-
return new StackElementFilter() {
42-
@Override
43-
public boolean accept(StackTraceElement element) {
44-
return true;
45-
}
46-
};
48+
static StackElementFilter any() {
49+
return element -> true;
4750
}
4851

4952
/**
@@ -53,13 +56,8 @@ public boolean accept(StackTraceElement element) {
5356
*
5457
* @return the filter
5558
*/
56-
public static StackElementFilter withSourceInfo() {
57-
return new StackElementFilter() {
58-
@Override
59-
public boolean accept(StackTraceElement element) {
60-
return element.getFileName() != null && element.getLineNumber() >= 0;
61-
}
62-
};
59+
static StackElementFilter withSourceInfo() {
60+
return element -> element.getFileName() != null && element.getLineNumber() >= 0;
6361
}
6462

6563
/**
@@ -68,20 +66,44 @@ public boolean accept(StackTraceElement element) {
6866
* @param excludes regular expressions matching {@link StackTraceElement} to filter out
6967
* @return the filter
7068
*/
71-
public static StackElementFilter byPattern(final List<Pattern> excludes) {
72-
return new StackElementFilter() {
73-
@Override
74-
public boolean accept(StackTraceElement element) {
75-
if (!excludes.isEmpty()) {
76-
final String classNameAndMethod = element.getClassName() + "." + element.getMethodName();
77-
for (final Pattern exclusionPattern : excludes) {
78-
if (exclusionPattern.matcher(classNameAndMethod).find()) {
79-
return false;
80-
}
81-
}
82-
}
83-
return true;
84-
}
85-
};
69+
static StackElementFilter byPattern(final List<Pattern> excludes) {
70+
return builder().byPattern(excludes).build();
71+
}
72+
73+
/**
74+
* Builder for common StackElementFilters.
75+
*/
76+
interface Builder {
77+
78+
/**
79+
* Include generated classes in the filter containing
80+
* {@code $$FastClassByCGLIB$$} and {@code $$EnhancerBySpringCGLIB$$}
81+
*/
82+
Builder generated();
83+
84+
/**
85+
* Include reflective invocation in the filter.
86+
*/
87+
Builder reflectiveInvocation();
88+
89+
/**
90+
* Include jdk internal classes in the filter.
91+
*/
92+
Builder jdkInternals();
93+
94+
/**
95+
* Include Spring Framework dynamic invocation and plumbing in the filter.
96+
*/
97+
Builder spring();
98+
99+
/**
100+
* Include the regex patterns in the filter.
101+
*/
102+
Builder byPattern(List<Pattern> excludes);
103+
104+
/**
105+
* Build and return the StackElementFilter with the given options.
106+
*/
107+
StackElementFilter build();
86108
}
87109
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package io.avaje.logback.encoder;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
import java.util.regex.Pattern;
8+
9+
import static org.assertj.core.api.Assertions.assertThat;
10+
import static org.junit.jupiter.api.Assertions.*;
11+
12+
class StackElementFilterTest {
13+
14+
@Test
15+
void generated() {
16+
StackElementFilter filter = StackElementFilter.builder().generated().build();
17+
18+
assertTrue(filter.accept(new StackTraceElement("a.b.C", "foo", null, 0)));
19+
20+
assertFalse(filter.accept(new StackTraceElement("a.b.C$$FastClassByCGLIB$$", "foo", null, 0)));
21+
assertFalse(filter.accept(new StackTraceElement("a.b.C$$FastClassByCGLIB$$D", "foo", null, 0)));
22+
assertFalse(filter.accept(new StackTraceElement("a.b.$$EnhancerBySpringCGLIB$$", "foo", null, 0)));
23+
assertFalse(filter.accept(new StackTraceElement("a.b.$$EnhancerBySpringCGLIB$$D", "foo", null, 0)));
24+
}
25+
26+
@Test
27+
void reflectiveInvoke() {
28+
StackElementFilter filter = StackElementFilter.builder().reflectiveInvocation().build();
29+
30+
assertTrue(filter.accept(new StackTraceElement("a.b.C", "invoke", null, 0)));
31+
assertFalse(filter.accept(new StackTraceElement("java.lang.reflect.A", "notInvoke", null, 0)));
32+
assertFalse(filter.accept(new StackTraceElement("sun.reflect.A", "notInvoke", null, 0)));
33+
assertFalse(filter.accept(new StackTraceElement("net.sf.cglib.proxy.MethodProxy", "notInvoke", null, 0)));
34+
35+
assertFalse(filter.accept(new StackTraceElement("java.lang.reflect.A", "invoke", null, 0)));
36+
assertFalse(filter.accept(new StackTraceElement("sun.reflect.A", "invoke", null, 0)));
37+
assertFalse(filter.accept(new StackTraceElement("net.sf.cglib.proxy.MethodProxy", "invoke", null, 0)));
38+
}
39+
40+
@Test
41+
void jdkInternals() {
42+
StackElementFilter filter = StackElementFilter.builder().jdkInternals().build();
43+
44+
assertTrue(filter.accept(new StackTraceElement("a.b.C", "invoke", null, 0)));
45+
assertTrue(filter.accept(new StackTraceElement("java.lang.C", "invoke", null, 0)));
46+
47+
assertFalse(filter.accept(new StackTraceElement("com.sun.A", "any", null, 0)));
48+
assertFalse(filter.accept(new StackTraceElement("sun.net.A", "any", null, 0)));
49+
}
50+
51+
@Test
52+
void spring() {
53+
StackElementFilter filter = StackElementFilter.builder().spring().build();
54+
55+
// accepted
56+
assertTrue(filter.accept(new StackTraceElement("a.b.C", "any", null, 0)));
57+
assertTrue(filter.accept(new StackTraceElement("org.springframework.cglibX", "any", null, 0)));
58+
assertTrue(filter.accept(new StackTraceElement("org.springframework.foo", "any", null, 0)));
59+
assertTrue(filter.accept(new StackTraceElement("org.springframework.Foo", "any", null, 0)));
60+
61+
// filtered out
62+
final String[] prefixes = new String[]{
63+
"org.springframework.cglib.",
64+
"org.springframework.transaction.",
65+
"org.springframework.validation.",
66+
"org.springframework.app.",
67+
"org.springframework.aop.",
68+
"org.springframework.ws.",
69+
"org.springframework.web.",
70+
"org.springframework.transaction"
71+
};
72+
73+
for (String prefix : prefixes) {
74+
assertThat(filter.accept(new StackTraceElement(prefix, "any", null, 0)))
75+
.describedAs("prefix of "+prefix)
76+
.isFalse();
77+
}
78+
}
79+
80+
@Test
81+
void patterns() {
82+
List<Pattern> patterns = new ArrayList<>();
83+
patterns.add(Pattern.compile("^java\\.util\\.concurrent\\.ThreadPoolExecutor\\.runWorker"));
84+
patterns.add(Pattern.compile("^java\\.lang\\.Thread\\.run$"));
85+
patterns.add(Pattern.compile("My\\$Foo"));
86+
87+
StackElementFilter filter = StackElementFilter.builder().byPattern(patterns).build();
88+
89+
assertFalse(filter.accept(new StackTraceElement("java.util.concurrent.ThreadPoolExecutor", "runWorker", null, 0)));
90+
assertFalse(filter.accept(new StackTraceElement("java.util.concurrent.ThreadPoolExecutor", "runWorkerA", null, 0)));
91+
92+
assertFalse(filter.accept(new StackTraceElement("java.lang.Thread", "run", null, 0)));
93+
assertTrue(filter.accept(new StackTraceElement("java.lang.Thread", "runX", null, 0)));
94+
assertTrue(filter.accept(new StackTraceElement("java.lang.Thread", "xrun", null, 0)));
95+
96+
assertFalse(filter.accept(new StackTraceElement("org.My$Foo", "any", null, 0)));
97+
assertFalse(filter.accept(new StackTraceElement("org.BeforeMy$Foo", "any", null, 0)));
98+
assertFalse(filter.accept(new StackTraceElement("org.BeforeMy$FooAfter", "any", null, 0)));
99+
}
100+
101+
}

0 commit comments

Comments
 (0)