Add `plugin.jdbc.trace_sql_parameters` into Configuration Discovery S… (#174)

Co-authored-by: yuzhongyu <yuzhongyu@cestc.cn>
This commit is contained in:
zhyyu 2022-05-30 20:42:30 +08:00 committed by GitHub
parent a8214514ae
commit e81638ea4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 195 additions and 45 deletions

View File

@ -19,6 +19,7 @@ Release Notes.
* Fix Span not finished in gateway plugin when the gateway request timeout.
* Support `-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector` in gRPC log report.
* Fix tcnative libraries relocation for aarch64.
* Add `plugin.jdbc.trace_sql_parameters` into Configuration Discovery Service.
#### Documentation

View File

@ -43,6 +43,7 @@ import org.apache.skywalking.apm.agent.core.logging.api.LogManager;
import org.apache.skywalking.apm.agent.core.remote.GRPCChannelListener;
import org.apache.skywalking.apm.agent.core.remote.GRPCChannelManager;
import org.apache.skywalking.apm.agent.core.remote.GRPCChannelStatus;
import org.apache.skywalking.apm.agent.core.util.CollectionUtil;
import org.apache.skywalking.apm.network.language.agent.v3.ConfigurationDiscoveryServiceGrpc;
import org.apache.skywalking.apm.network.language.agent.v3.ConfigurationSyncRequest;
import org.apache.skywalking.apm.network.common.v3.Commands;
@ -118,10 +119,16 @@ public class ConfigurationDiscoveryService implements BootService, GRPCChannelLi
*
* @param watcher dynamic configuration watcher
*/
public void registerAgentConfigChangeWatcher(AgentConfigChangeWatcher watcher) {
public synchronized void registerAgentConfigChangeWatcher(AgentConfigChangeWatcher watcher) {
WatcherHolder holder = new WatcherHolder(watcher);
if (register.containsKey(holder.getKey())) {
throw new IllegalStateException("Duplicate register, watcher=" + watcher);
List<WatcherHolder> watcherHolderList = register.get(holder.getKey());
for (WatcherHolder watcherHolder : watcherHolderList) {
if (watcherHolder.getWatcher().getClass().equals(watcher.getClass())) {
LOGGER.debug("Duplicate register, watcher={}", watcher);
return;
}
}
}
register.put(holder.getKey(), holder);
}
@ -142,31 +149,33 @@ public class ConfigurationDiscoveryService implements BootService, GRPCChannelLi
config.forEach(property -> {
String propertyKey = property.getKey();
WatcherHolder holder = register.get(propertyKey);
if (holder != null) {
AgentConfigChangeWatcher watcher = holder.getWatcher();
String newPropertyValue = property.getValue();
if (StringUtil.isBlank(newPropertyValue)) {
if (watcher.value() != null) {
// Notify watcher, the new value is null with delete event type.
watcher.notify(
new AgentConfigChangeWatcher.ConfigChangeEvent(
null, AgentConfigChangeWatcher.EventType.DELETE
));
List<WatcherHolder> holderList = register.get(propertyKey);
for (WatcherHolder holder : holderList) {
if (holder != null) {
AgentConfigChangeWatcher watcher = holder.getWatcher();
String newPropertyValue = property.getValue();
if (StringUtil.isBlank(newPropertyValue)) {
if (watcher.value() != null) {
// Notify watcher, the new value is null with delete event type.
watcher.notify(
new AgentConfigChangeWatcher.ConfigChangeEvent(
null, AgentConfigChangeWatcher.EventType.DELETE
));
} else {
// Don't need to notify, stay in null.
}
} else {
// Don't need to notify, stay in null.
if (!newPropertyValue.equals(watcher.value())) {
watcher.notify(new AgentConfigChangeWatcher.ConfigChangeEvent(
newPropertyValue, AgentConfigChangeWatcher.EventType.MODIFY
));
} else {
// Don't need to notify, stay in the same config value.
}
}
} else {
if (!newPropertyValue.equals(watcher.value())) {
watcher.notify(new AgentConfigChangeWatcher.ConfigChangeEvent(
newPropertyValue, AgentConfigChangeWatcher.EventType.MODIFY
));
} else {
// Don't need to notify, stay in the same config value.
}
LOGGER.warn("Config {} from OAP, doesn't match any watcher, ignore.", propertyKey);
}
} else {
LOGGER.warn("Config {} from OAP, doesn't match any watcher, ignore.", propertyKey);
}
});
this.uuid = responseUuid;
@ -238,17 +247,25 @@ public class ConfigurationDiscoveryService implements BootService, GRPCChannelLi
* Local dynamic configuration center.
*/
public static class Register {
private final Map<String, WatcherHolder> register = new HashMap<>();
private final Map<String, List<WatcherHolder>> register = new HashMap<>();
private boolean containsKey(String key) {
return register.containsKey(key);
}
private void put(String key, WatcherHolder holder) {
register.put(key, holder);
List<WatcherHolder> watcherHolderList = register.get(key);
if (CollectionUtil.isEmpty(watcherHolderList)) {
ArrayList<WatcherHolder> newWatchHolderList = new ArrayList<>();
newWatchHolderList.add(holder);
register.put(key, newWatchHolderList);
} else {
watcherHolderList.add(holder);
register.put(key, watcherHolderList);
}
}
public WatcherHolder get(String name) {
public List<WatcherHolder> get(String name) {
return register.get(name);
}
@ -259,12 +276,13 @@ public class ConfigurationDiscoveryService implements BootService, GRPCChannelLi
@Override
public String toString() {
ArrayList<String> registerTableDescription = new ArrayList<>(register.size());
register.forEach((key, holder) -> {
AgentConfigChangeWatcher watcher = holder.getWatcher();
registerTableDescription.add(new StringBuilder().append("key:")
.append(key)
.append("value(current):")
.append(watcher.value()).toString());
register.forEach((key, holderList) -> {
for (WatcherHolder holder : holderList) {
registerTableDescription.add(new StringBuilder().append("key:")
.append(key)
.append("value(current):")
.append(holder.getWatcher().value()).toString());
}
});
return registerTableDescription.stream().collect(Collectors.joining(",", "[", "]"));
}

View File

@ -20,6 +20,9 @@ package org.apache.skywalking.apm.plugin.jdbc;
import java.lang.reflect.Method;
import java.sql.Connection;
import org.apache.skywalking.apm.agent.core.boot.ServiceManager;
import org.apache.skywalking.apm.agent.core.conf.dynamic.ConfigurationDiscoveryService;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import org.apache.skywalking.apm.plugin.jdbc.connectionurl.parser.URLParser;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
@ -34,7 +37,10 @@ public class JDBCDriverInterceptor implements InstanceMethodsAroundInterceptor {
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
MethodInterceptResult result) throws Throwable {
TraceSqlParametersWatcher traceSqlParametersWatcher = new TraceSqlParametersWatcher("plugin.jdbc.trace_sql_parameters");
ConfigurationDiscoveryService configurationDiscoveryService = ServiceManager.INSTANCE.findService(
ConfigurationDiscoveryService.class);
configurationDiscoveryService.registerAgentConfigChangeWatcher(traceSqlParametersWatcher);
}
@Override

View File

@ -28,7 +28,7 @@ public class JDBCPluginConfig {
* If set to true, the parameters of the sql (typically {@link java.sql.PreparedStatement}) would be
* collected.
*/
public static boolean TRACE_SQL_PARAMETERS = false;
public static volatile boolean TRACE_SQL_PARAMETERS = false;
/**
* For the sake of performance, SkyWalking won't save the entire parameters string into the tag, but only
* the first {@code SQL_PARAMETERS_MAX_LENGTH} characters.

View File

@ -40,11 +40,10 @@ public class PSSetterDefinitionOfJDBCInstrumentation implements InstanceMethodsI
public ElementMatcher<MethodDescription> getMethodsMatcher() {
ElementMatcher.Junction<MethodDescription> matcher = none();
if (JDBCPluginConfig.Plugin.JDBC.TRACE_SQL_PARAMETERS) {
final Set<String> setters = ignorable ? PS_IGNORABLE_SETTERS : PS_SETTERS;
for (String setter : setters) {
matcher = matcher.or(named(setter));
}
// remove TRACE_SQL_PARAMETERS judgement for dynamic config
final Set<String> setters = ignorable ? PS_IGNORABLE_SETTERS : PS_SETTERS;
for (String setter : setters) {
matcher = matcher.or(named(setter));
}
return matcher;

View File

@ -0,0 +1,46 @@
/*
* 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.jdbc;
import org.apache.skywalking.apm.agent.core.conf.dynamic.AgentConfigChangeWatcher;
public class TraceSqlParametersWatcher extends AgentConfigChangeWatcher {
private final boolean defaultValue;
public TraceSqlParametersWatcher(String propertyKey) {
super(propertyKey);
defaultValue = JDBCPluginConfig.Plugin.JDBC.TRACE_SQL_PARAMETERS;
}
@Override
public void notify(ConfigChangeEvent value) {
if (EventType.DELETE.equals(value.getEventType())) {
JDBCPluginConfig.Plugin.JDBC.TRACE_SQL_PARAMETERS = defaultValue;
} else {
JDBCPluginConfig.Plugin.JDBC.TRACE_SQL_PARAMETERS = Boolean.parseBoolean(value.getNewValue());
}
}
@Override
public String value() {
return Boolean.toString(JDBCPluginConfig.Plugin.JDBC.TRACE_SQL_PARAMETERS);
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.jdbc.mysql.v5.define;
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
import org.apache.skywalking.apm.plugin.jdbc.define.AbstractDriverInstrumentation;
import static org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch.byMultiClassMatch;
/**
* {@link DriverInstrumentation} presents that skywalking intercepts {@link com.mysql.jdbc.Driver}.
*/
public class DriverInstrumentation extends AbstractDriverInstrumentation {
@Override
protected ClassMatch enhanceClass() {
return byMultiClassMatch("com.mysql.jdbc.Driver", "com.mysql.cj.jdbc.Driver", "com.mysql.jdbc.NonRegisteringDriver");
}
@Override
protected String[] witnessClasses() {
return new String[] {Constants.WITNESS_MYSQL_5X_CLASS};
}
}

View File

@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
mysql-5.x=org.apache.skywalking.apm.plugin.jdbc.mysql.v5.define.DriverInstrumentation
mysql-5.x=org.apache.skywalking.apm.plugin.jdbc.mysql.v5.define.Mysql5xConnectionInstrumentation
mysql-5.x=org.apache.skywalking.apm.plugin.jdbc.mysql.v5.define.Mysql50ConnectionInstrumentation
mysql-5.x=org.apache.skywalking.apm.plugin.jdbc.mysql.v5.define.CallableInstrumentation

View File

@ -0,0 +1,39 @@
/*
* 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.jdbc.mysql.v8.define;
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
import org.apache.skywalking.apm.plugin.jdbc.define.AbstractDriverInstrumentation;
import static org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch.byMultiClassMatch;
/**
* {@link DriverInstrumentation} presents that skywalking intercepts {@link com.mysql.jdbc.Driver}.
*/
public class DriverInstrumentation extends AbstractDriverInstrumentation {
@Override
protected ClassMatch enhanceClass() {
return byMultiClassMatch("com.mysql.jdbc.Driver", "com.mysql.cj.jdbc.Driver", "com.mysql.jdbc.NonRegisteringDriver");
}
@Override
protected String[] witnessClasses() {
return new String[] {Constants.WITNESS_MYSQL_8X_CLASS};
}
}

View File

@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
mysql-8.x=org.apache.skywalking.apm.plugin.jdbc.mysql.v8.define.DriverInstrumentation
mysql-8.x=org.apache.skywalking.apm.plugin.jdbc.mysql.v8.define.ConnectionImplCreateInstrumentation
mysql-8.x=org.apache.skywalking.apm.plugin.jdbc.mysql.v8.define.ConnectionInstrumentation
mysql-8.x=org.apache.skywalking.apm.plugin.jdbc.mysql.v8.define.CallableInstrumentation
@ -22,4 +23,4 @@ mysql-8.x=org.apache.skywalking.apm.plugin.jdbc.mysql.v8.define.StatementInstrum
mysql-8.x=org.apache.skywalking.apm.plugin.jdbc.mysql.v8.define.PreparedStatementSetterInstrumentation
mysql-8.x=org.apache.skywalking.apm.plugin.jdbc.mysql.v8.define.PreparedStatementNullSetterInstrumentation
mysql-8.x=org.apache.skywalking.apm.plugin.jdbc.mysql.v8.define.PreparedStatementIgnoredSetterInstrumentation
mysql-8.x=org.apache.skywalking.apm.plugin.jdbc.mysql.v8.define.CacheIpsInstrumentation
mysql-8.x=org.apache.skywalking.apm.plugin.jdbc.mysql.v8.define.CacheIpsInstrumentation

View File

@ -16,23 +16,22 @@ Every plugin maintained in the main repo requires corresponding test cases as we
## Case Base Image Introduction
The test framework provides `JVM-container` and `Tomcat-container` base images including JDK8 and JDK14. You can choose the best one for your test case. If both are suitable for your case, **`JVM-container` is preferred**.
The test framework provides `JVM-container` and `Tomcat-container` base images including JDK8 and JDK17. You can choose the best one for your test case. If both are suitable for your case, **`JVM-container` is preferred**.
### JVM-container Image Introduction
[JVM-container](../../../../../test/plugin/containers/jvm-container) uses `adoptopenjdk/openjdk8:alpine-jre` as the base image. `JVM-container` supports JDK14 and JDK17 as well in CI, which inherits `adoptopenjdk/openjdk8:alpine-jre` and `eclipse-temurin:17-alpine`.
[JVM-container](../../../../../test/plugin/containers/jvm-container) uses `adoptopenjdk/openjdk8:alpine-jre` as the base image. `JVM-container` supports JDK8 and JDK17 as well in CI, which inherits `adoptopenjdk/openjdk8:alpine-jre` and `eclipse-temurin:17-alpine`.
It is supported to custom the base Java docker image by specify `base_image_java`.
The test case project must be packaged as `project-name.zip`, including `startup.sh` and uber jar, by using `mvn clean package`.
Take the following test projects as examples:
* [sofarpc-scenario](../../../../../test/plugin/scenarios/sofarpc-scenario) is a single project case.
* [webflux-scenario](../../../../../test/plugin/scenarios/webflux-scenario) is a case including multiple projects.
* [jdk14-with-gson-scenario](../../../../../test/plugin/scenarios/jdk14-with-gson-scenario) is a single project case with JDK14.
* [jdk17-with-gson-scenario](../../../../../test/plugin/scenarios/jdk17-with-gson-scenario) is a single project case with JDK17.
### Tomcat-container Image Introduction
[Tomcat-container](../../../../../test/plugin/containers/tomcat-container) uses `tomcat:8.5-jdk8-openjdk`, `tomcat:8.5-jdk14-openjdk`, `tomcat:8.5-jdk17-openjdk` as the base image.
[Tomcat-container](../../../../../test/plugin/containers/tomcat-container) uses `tomcat:8.5-jdk8-openjdk`, `tomcat:8.5-jdk17-openjdk` as the base image.
It is supported to custom the base Tomcat docker image by specify `base_image_tomcat`.
The test case project must be packaged as `project-name.war` by using `mvn package`.
@ -623,7 +622,7 @@ Every test case is a GitHub Actions Job. Please use the scenario directory name
mostly you'll just need to decide which file (`plugins-test.<n>.yaml`) to add your test case, and simply put one line (as follows) in it, take the existed cases as examples.
You can run `python3 tools/select-group.py` to see which file contains the least cases and add your cases into it, in order to balance the running time of each group.
If a test case required to run in JDK 14 environment, please add you test case into file `plugins-jdk14-test.<n>.yaml`.
If a test case required to run in JDK 17 environment, please add you test case into file `plugins-jdk17-test.<n>.yaml`.
```yaml
jobs:

View File

@ -28,5 +28,6 @@ Java agent supports the following dynamic configurations.
| agent.ignore_suffix | If the operation name of the first span is included in this set, this segment should be ignored. Multiple values should be separated by `,` | `.txt,.log` | - |
| agent.trace.ignore_path | The value is the path that you need to ignore, multiple paths should be separated by `,` [more details](./agent-optional-plugins/trace-ignore-plugin.md) | `/your/path/1/**,/your/path/2/**` | `apm-trace-ignore-plugin` |
| agent.span_limit_per_segment | The max number of spans per segment. | `300` | - |
| plugin.jdbc.trace_sql_parameters | If set to true, the parameters of the sql (typically java.sql.PreparedStatement) would be collected. | `false` | - |
* `Required plugin(s)`, the configuration affects only when the required plugins activated.