提交 dcb71cde 编写于 作者: K Kanro 提交者: wu-sheng

Optimize trace ignore plugin (#4209)

* Optimize trace ignore plugin

* Add license for FastPathMatcher

* Boot again after setting config in test

* Fix check style

* Fix typos
Co-authored-by: Nkezhenxu94 <kezhenxu94@163.com>
上级 7e9812e8
......@@ -28,14 +28,12 @@ import org.apache.skywalking.apm.agent.core.logging.api.ILog;
import org.apache.skywalking.apm.agent.core.logging.api.LogManager;
import org.apache.skywalking.apm.plugin.trace.ignore.conf.IgnoreConfig;
import org.apache.skywalking.apm.plugin.trace.ignore.conf.IgnoreConfigInitializer;
import org.apache.skywalking.apm.plugin.trace.ignore.matcher.AntPathMatcher;
import org.apache.skywalking.apm.plugin.trace.ignore.matcher.FastPathMatcher;
import org.apache.skywalking.apm.plugin.trace.ignore.matcher.TracePathMatcher;
import org.apache.skywalking.apm.util.StringUtil;
/**
*
* @author liujc [liujunc1993@163.com]
*
* @author liujc [liujunc1993@163.com], kanro
*/
@OverrideImplementor(ContextManagerExtendService.class)
public class TraceIgnoreExtendService extends ContextManagerExtendService {
......@@ -46,30 +44,27 @@ public class TraceIgnoreExtendService extends ContextManagerExtendService {
private static final String PATTERN_SEPARATOR = ",";
private TracePathMatcher pathMatcher = new AntPathMatcher();
private TracePathMatcher pathMatcher = new FastPathMatcher();
private String[] patterns = new String[]{};
@Override
public void boot() {
try {
IgnoreConfigInitializer.initialize();
} catch (ConfigNotFoundException e) {
LOGGER.error("trace ignore config init error", e);
} catch (AgentPackageNotFoundException e) {
if (StringUtil.isNotEmpty(IgnoreConfig.Trace.IGNORE_PATH)) {
patterns = IgnoreConfig.Trace.IGNORE_PATH.split(PATTERN_SEPARATOR);
}
} catch (ConfigNotFoundException | AgentPackageNotFoundException e) {
LOGGER.error("trace ignore config init error", e);
}
}
@Override
public AbstractTracerContext createTraceContext(String operationName, boolean forceSampling) {
String pattens = IgnoreConfig.Trace.IGNORE_PATH;
if (!StringUtil.isEmpty(pattens) && !forceSampling) {
String path = operationName;
if (!StringUtil.isEmpty(path) && path.length() > 1 && path.endsWith(DEFAULT_PATH_SEPARATOR)) {
path = path.substring(0, path.length() - 1);
}
for (String pattern : pattens.split(PATTERN_SEPARATOR)) {
if (pathMatcher.match(pattern, path)) {
if (patterns.length > 0 && !forceSampling) {
for (String pattern : patterns) {
if (pathMatcher.match(pattern, operationName)) {
LOGGER.debug("operationName : " + operationName + " Ignore tracking");
return new IgnoredTracerContext();
}
......
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.skywalking.apm.plugin.trace.ignore.matcher;
import org.apache.skywalking.apm.util.StringUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
* @author liujc [liujunc1993@163.com]
*
*/
public class AntPathMatcher implements TracePathMatcher {
private static final String DEFAULT_PATH_SEPARATOR = "/";
private static final String ANY_ONE_MATCHING_CHAR = "?";
private static final String ANY_MATCHING_CHAR = "*";
private static final String MULTILEVEL_DIRECTORIES_ANY_MATCHING_CHAR = ANY_MATCHING_CHAR.concat(ANY_MATCHING_CHAR);
@Override
public boolean match(String pattern, String path) {
if (!MatchAssist.checkPatternAndPath(pattern, path)) {
return false;
}
// resolve pattern and path by default path separator
String[] resolvedPatterns = MatchAssist.resolvePath(pattern);
String[] resolvedPaths = MatchAssist.resolvePath(path);
int patternIdxStart = 0;
int patternIdxEnd = resolvedPatterns.length - 1;
int pathIdxStart = 0;
int pathIdxEnd = resolvedPaths.length - 1;
// try to match first '**'
while (true) {
if (patternIdxStart > patternIdxEnd || pathIdxStart > pathIdxEnd) {
break;
}
String resolvedPattern = resolvedPatterns[patternIdxStart];
if (MULTILEVEL_DIRECTORIES_ANY_MATCHING_CHAR.equals(resolvedPattern)) {
break;
}
if (!MatchAssist.matchStrings(resolvedPattern, resolvedPaths[pathIdxStart])) {
return false;
}
patternIdxStart++;
pathIdxStart++;
}
if (pathIdxStart > pathIdxEnd) {
if (patternIdxStart > patternIdxEnd) {
return pattern.endsWith(DEFAULT_PATH_SEPARATOR) == path.endsWith(DEFAULT_PATH_SEPARATOR);
}
return patternIdxStart == patternIdxEnd && resolvedPatterns[patternIdxStart].equals(ANY_MATCHING_CHAR) && path.endsWith(DEFAULT_PATH_SEPARATOR)
|| MatchAssist.checkPatternIdx(patternIdxStart, patternIdxEnd, resolvedPatterns);
}
else if (patternIdxStart > patternIdxEnd) {
return false;
}
// try to match last '**'
while (true) {
if (patternIdxStart > patternIdxEnd || pathIdxStart > pathIdxEnd) {
break;
}
String resolvedPattern = resolvedPatterns[patternIdxEnd];
if (resolvedPattern.equals(MULTILEVEL_DIRECTORIES_ANY_MATCHING_CHAR)) {
break;
}
if (!MatchAssist.matchStrings(resolvedPattern, resolvedPaths[pathIdxEnd])) {
return false;
}
patternIdxEnd--;
pathIdxEnd--;
}
if (pathIdxStart > pathIdxEnd) {
return MatchAssist.checkPatternIdx(patternIdxStart, patternIdxEnd, resolvedPatterns);
}
while (patternIdxStart != patternIdxEnd && pathIdxStart <= pathIdxEnd) {
int patIdxTmp = -1;
for (int i = patternIdxStart + 1; i <= patternIdxEnd; i++) {
if (resolvedPatterns[i].equals(MULTILEVEL_DIRECTORIES_ANY_MATCHING_CHAR)) {
patIdxTmp = i;
break;
}
}
if (patIdxTmp == patternIdxStart + 1) {
// '**/**' situation, so skip one
patternIdxStart++;
continue;
}
// Find the pattern between patternIdxStart & padIdxTmp in str between
// strIdxStart & strIdxEnd
int patLength = patIdxTmp - patternIdxStart - 1;
int strLength = pathIdxEnd - pathIdxStart + 1;
int foundIdx = -1;
strLoop:
for (int i = 0; i <= strLength - patLength; i++) {
for (int j = 0; j < patLength; j++) {
String subPat = resolvedPatterns[patternIdxStart + j + 1];
String subStr = resolvedPatterns[pathIdxStart + i + j];
if (!MatchAssist.matchStrings(subPat, subStr)) {
continue strLoop;
}
}
foundIdx = pathIdxStart + i;
break;
}
if (foundIdx == -1) {
return false;
}
patternIdxStart = patIdxTmp;
pathIdxStart = foundIdx + patLength;
}
return MatchAssist.checkPatternIdx(patternIdxStart, patternIdxEnd, resolvedPatterns);
}
private static class MatchAssist {
private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}");
private static final String DEFAULT_VARIABLE_PATTERN = "(.*)";
private static final ConcurrentMap<String, Pattern> GLOBAL_COMPILED_PATTERN_CACHE = new ConcurrentHashMap<String, Pattern>();
private static boolean checkPatternIdx(int patternIdxStart, int patternIdxEnd, String[] resolvedPatterns) {
for (int i = patternIdxStart; i <= patternIdxEnd; i++) {
if (!resolvedPatterns[i].equals(MULTILEVEL_DIRECTORIES_ANY_MATCHING_CHAR)) {
return false;
}
}
return true;
}
/**
* make sure of the pattern and path is validate
*/
private static boolean checkPatternAndPath(String pattern, String path) {
return !StringUtil.isEmpty(pattern) && !StringUtil.isEmpty(path) &&
path.startsWith(DEFAULT_PATH_SEPARATOR) == pattern.startsWith(DEFAULT_PATH_SEPARATOR);
}
/**
* resolve path by default path separator
*/
private static String[] resolvePath(String path) {
if (path == null) {
return null;
}
StringTokenizer st = new StringTokenizer(path, DEFAULT_PATH_SEPARATOR);
List<String> tokens = new ArrayList<String>();
while (st.hasMoreTokens()) {
String token = st.nextToken();
token = token.trim();
if (token.length() > 0) {
tokens.add(token);
}
}
return tokens.toArray(new String[tokens.size()]);
}
/**
* use pattern match path
*/
private static boolean matchStrings(String pattern, String path) {
if (StringUtil.isEmpty(pattern) || StringUtil.isEmpty(path)) {
return false;
}
// if this pattern has been compiled
Pattern compliedPattern = GLOBAL_COMPILED_PATTERN_CACHE.get(pattern);
if (compliedPattern == null) {
// build new pattern
StringBuilder patternBuilder = new StringBuilder();
Matcher matcher = GLOB_PATTERN.matcher(pattern);
int end = 0;
while (matcher.find()) {
patternBuilder.append(quote(pattern, end, matcher.start()));
String match = matcher.group();
if (ANY_ONE_MATCHING_CHAR.equals(match)) {
patternBuilder.append('.');
}
else if (ANY_MATCHING_CHAR.equals(match)) {
patternBuilder.append(".".concat(ANY_MATCHING_CHAR));
}
else if (match.startsWith("{") && match.endsWith("}")) {
int colonIdx = match.indexOf(':');
if (colonIdx == -1) {
patternBuilder.append(DEFAULT_VARIABLE_PATTERN);
}
else {
String variablePattern = match.substring(colonIdx + 1, match.length() - 1);
patternBuilder.append('(');
patternBuilder.append(variablePattern);
patternBuilder.append(')');
}
}
end = matcher.end();
}
patternBuilder.append(quote(pattern, end, pattern.length()));
compliedPattern = Pattern.compile(patternBuilder.toString());
GLOBAL_COMPILED_PATTERN_CACHE.putIfAbsent(pattern, compliedPattern);
}
return compliedPattern.matcher(path).matches();
}
private static String quote(String s, int start, int end) {
if (start == end) {
return "";
}
return Pattern.quote(s.substring(start, end));
}
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.skywalking.apm.plugin.trace.ignore.matcher;
/**
* @author kanro
*/
public class FastPathMatcher implements TracePathMatcher {
@Override
public boolean match(String pattern, String path) {
return normalMatch(pattern, 0, path, 0);
}
private boolean normalMatch(String pat, int p, String str, int s) {
while (p < pat.length()) {
char pc = pat.charAt(p);
char sc = safeCharAt(str, s);
// Got * in pattern, enter the wildcard mode.
// ↓ ↓
// pattern: a/* a/*
// ↓ ↓
// string: a/bcd a/
if (pc == '*') {
p++;
// Got * in pattern again, enter the multi-wildcard mode.
// ↓ ↓
// pattern: a/** a/**
// ↓ ↓
// string: a/bcd a/
if (safeCharAt(pat, p) == '*') {
p++;
// Enter the multi-wildcard mode.
// ↓ ↓
// pattern: a/** a/**
// ↓ ↓
// string: a/bcd a/
return multiWildcardMatch(pat, p, str, s);
} else {
// Enter the wildcard mode.
// ↓
// pattern: a/*
// ↓
// string: a/bcd
return wildcardMatch(pat, p, str, s);
}
}
// Matching ? for non-'/' char, or matching the same chars.
// ↓ ↓ ↓
// pattern: a/?/c a/b/c a/b
// ↓ ↓ ↓
// string: a/b/c a/b/d a/d
if ((pc == '?' && sc != 0 && sc != '/') || pc == sc) {
s++;
p++;
continue;
}
// Not matched.
// ↓
// pattern: a/b
// ↓
// string: a/c
return false;
}
return s == str.length();
}
private boolean wildcardMatch(String pat, int p, String str, int s) {
char pc = safeCharAt(pat, p);
while (true) {
char sc = safeCharAt(str, s);
if (sc == '/') {
// Both of pattern and string '/' matched, exit wildcard mode.
// ↓
// pattern: a/*/
// ↓
// string: a/bc/
if (pc == sc) {
return normalMatch(pat, p + 1, str, s + 1);
}
// Not matched string in current path part.
// ↓ ↓
// pattern: a/* a/*d
// ↓ ↓
// string: a/bc/ a/bc/
return false;
}
// Try to enter normal mode, if not matched, increasing pointer of string and try again.
if (!normalMatch(pat, p, str, s)) {
// End of string, not matched.
if (s >= str.length()) {
return false;
}
s++;
continue;
}
// Matched in next normal mode.
return true;
}
}
private boolean multiWildcardMatch(String pat, int p, String str, int s) {
// End of pattern, just check the end of string is '/' quickly.
if (p >= pat.length() && s < str.length()) {
return str.charAt(str.length() - 1) != '/';
}
while (true) {
// Try to enter normal mode, if not matched, increasing pointer of string and try again.
if (!normalMatch(pat, p, str, s)) {
// End of string, not matched.
if (s >= str.length()) {
return false;
}
s++;
continue;
}
return true;
}
}
private char safeCharAt(String value, int index) {
if (index >= value.length()) {
return 0;
}
return value.charAt(index);
}
}
......@@ -18,15 +18,22 @@
package org.apache.skywalking.apm.plugin.trace.ignore;
import java.util.Properties;
import org.apache.skywalking.apm.agent.core.boot.ServiceManager;
import org.apache.skywalking.apm.agent.core.context.*;
import org.apache.skywalking.apm.agent.core.context.AbstractTracerContext;
import org.apache.skywalking.apm.agent.core.context.ContextManagerExtendService;
import org.apache.skywalking.apm.agent.core.context.IgnoredTracerContext;
import org.apache.skywalking.apm.agent.core.context.TracingContext;
import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule;
import org.apache.skywalking.apm.plugin.trace.ignore.conf.IgnoreConfig;
import org.apache.skywalking.apm.util.*;
import org.junit.*;
import org.apache.skywalking.apm.util.ConfigInitializer;
import org.apache.skywalking.apm.util.PropertyPlaceholderHelper;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.EnvironmentVariables;
import java.util.Properties;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
......@@ -51,6 +58,7 @@ public class TraceIgnoreTest {
public void testTraceIgnore() {
ContextManagerExtendService service = ServiceManager.INSTANCE.findService(ContextManagerExtendService.class);
IgnoreConfig.Trace.IGNORE_PATH = "/eureka/**";
service.boot();
AbstractTracerContext ignoredTracerContext = service.createTraceContext("/eureka/apps", false);
Assert.assertEquals(IgnoredTracerContext.class, ignoredTracerContext.getClass());
......
......@@ -18,7 +18,7 @@
package org.apache.skywalking.apm.plugin.trace.ignore;
import org.apache.skywalking.apm.plugin.trace.ignore.matcher.AntPathMatcher;
import org.apache.skywalking.apm.plugin.trace.ignore.matcher.FastPathMatcher;
import org.apache.skywalking.apm.plugin.trace.ignore.matcher.TracePathMatcher;
import org.junit.Assert;
import org.junit.Test;
......@@ -27,50 +27,86 @@ public class TracePathMatcherTest {
@Test
public void testAntPathMatcher() {
TracePathMatcher pathMatcher = new AntPathMatcher();
TracePathMatcher pathMatcher = new FastPathMatcher();
String patten = "/eureka/*";
String path = "/eureka/app";
String path = "/eureka/apps";
boolean match = pathMatcher.match(patten, path);
Assert.assertTrue(match);
path = "/eureka/";
match = pathMatcher.match(patten, path);
Assert.assertTrue(match);
path = "/eureka/apps/";
match = pathMatcher.match(patten, path);
Assert.assertFalse(match);
patten = "/eureka/*/";
path = "/eureka/apps/";
match = pathMatcher.match(patten, path);
Assert.assertTrue(match);
path = "/eureka/";
match = pathMatcher.match(patten, path);
Assert.assertFalse(match);
path = "/eureka/apps/list";
match = pathMatcher.match(patten, path);
Assert.assertFalse(match);
patten = "/eureka/**";
path = "/eureka/";
match = pathMatcher.match(patten, path);
Assert.assertTrue(match);
patten = "/eureka/apps/lis?";
path = "/eureka/apps/test";
match = pathMatcher.match(patten, path);
Assert.assertTrue(match);
path = "/eureka/apps/test/";
match = pathMatcher.match(patten, path);
Assert.assertFalse(match);
path = "eureka/apps/lists";
patten = "eureka/apps/?";
path = "eureka/apps/list";
match = pathMatcher.match(patten, path);
Assert.assertFalse(match);
path = "eureka/apps/";
match = pathMatcher.match(patten, path);
Assert.assertFalse(match);
path = "eureka/apps/a";
match = pathMatcher.match(patten, path);
Assert.assertTrue(match);
patten = "eureka/**/lists";
path = "eureka/apps/lists";
match = pathMatcher.match(patten, path);
Assert.assertTrue(match);
path = "eureka/apps/a/b/c/lists";
path = "eureka/apps/test/lists";
match = pathMatcher.match(patten, path);
Assert.assertTrue(match);
path = "eureka/apps/a/b/c/";
path = "eureka/apps/test/";
match = pathMatcher.match(patten, path);
Assert.assertFalse(match);
path = "eureka/apps/test";
match = pathMatcher.match(patten, path);
Assert.assertFalse(match);
patten = "eureka/**/b/**";
patten = "eureka/**/test/**";
path = "eureka/apps/test/list";
match = pathMatcher.match(patten, path);
Assert.assertTrue(match);
path = "eureka/apps/foo/test/list/bar";
match = pathMatcher.match(patten, path);
Assert.assertTrue(match);
path = "eureka/apps/foo/test/list/bar/";
match = pathMatcher.match(patten, path);
Assert.assertFalse(match);
path = "eureka/apps/test/list";
match = pathMatcher.match(patten, path);
Assert.assertTrue(match);
path = "eureka/test/list";
match = pathMatcher.match(patten, path);
Assert.assertFalse(match);
patten = "/eureka/**/b/**/*.txt";
path = "/eureka/a/aa/aaa/b/bb/bbb/xxxxxx.txt";
match = pathMatcher.match(patten, path);
Assert.assertTrue(match);
path = "/eureka/a/aa/aaa/b/bb/bbb/xxxxxx";
match = pathMatcher.match(patten, path);
Assert.assertFalse(match);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册